fn parse_quality(param: &str) -> Option<u16> {
let (_, value) = param.split_once('=')?;
let value = value.trim();
if value == "1" {
return Some(1000);
}
if value == "0" {
return Some(0);
}
if let Some(fraction) = value.strip_prefix("1.") {
if fraction.chars().all(|ch| ch == '0') {
return Some(1000);
}
return None;
}
let fraction = value.strip_prefix("0.")?;
if fraction.is_empty() || !fraction.chars().all(|ch| ch.is_ascii_digit()) {
return None;
}
let mut padded = fraction.to_string();
padded.truncate(3);
while padded.len() < 3 {
padded.push('0');
}
padded.parse().ok()
}
fn parse_weighted_language(token: &str) -> Option<(String, u16)> {
let mut parts = token.split(';');
let language = parts.next()?.trim();
if language.is_empty() {
return None;
}
let mut quality = 1000;
for part in parts {
let part = part.trim();
if part.starts_with("q=") {
quality = parse_quality(part)?;
break;
}
}
Some((language.to_ascii_lowercase(), quality))
}
fn primary_subtag(locale: &str) -> &str {
locale.split(['-', '_']).next().unwrap_or(locale)
}
fn match_score(requested: &str, supported: &str) -> u8 {
let supported = supported.to_ascii_lowercase();
if requested == supported {
return 2;
}
if primary_subtag(requested) == primary_subtag(&supported) {
return 1;
}
0
}
fn resolve_accept_language_impl<'a>(
header: &str,
supported_locales: &[&'a str],
default_locale: &'a str,
) -> &'a str {
let mut best_match: Option<(&'a str, u16, u8, usize)> = None;
for (order, token) in header.split(',').enumerate() {
let Some((language, quality)) = parse_weighted_language(token) else {
continue;
};
for &supported in supported_locales {
let score = match_score(&language, supported);
if score == 0 {
continue;
}
let is_better = match best_match {
None => true,
Some((_, best_quality, best_score, best_order)) => {
quality > best_quality
|| (quality == best_quality
&& (score > best_score || (score == best_score && order < best_order)))
}
};
if is_better {
best_match = Some((supported, quality, score, order));
}
}
}
best_match
.map(|(locale, _, _, _)| locale)
.unwrap_or(default_locale)
}
pub fn resolve_accept_language<'a>(
header: &str,
supported_locales: &[&'a str],
default_locale: &'a str,
) -> &'a str {
resolve_accept_language_impl(header, supported_locales, default_locale)
}
pub fn resolve_accept_language_owned<S>(
header: &str,
supported_locales: &[S],
default_locale: &str,
) -> String
where
S: AsRef<str>,
{
let supported_refs: Vec<&str> = supported_locales.iter().map(AsRef::as_ref).collect();
resolve_accept_language_impl(header, &supported_refs, default_locale).to_string()
}