perfect-postcode/server-rs/src/language.rs
2026-07-03 18:01:10 +01:00

107 lines
3 KiB
Rust

pub const DEFAULT_LANGUAGE: &str = "en";
const SUPPORTED_LANGUAGES: &[&str] = &["en", "fr", "de", "zh", "hi", "hu"];
/// The set of language codes the frontend can render, used e.g. to emit
/// `hreflang` alternates for indexable pages.
pub fn supported_languages() -> &'static [&'static str] {
SUPPORTED_LANGUAGES
}
pub fn supported_language(value: &str) -> Option<&'static str> {
let value = value.trim().to_ascii_lowercase();
if value.is_empty() || value == "*" {
return None;
}
let primary = value.split('-').next().unwrap_or("");
SUPPORTED_LANGUAGES
.iter()
.copied()
.find(|language| value == *language || primary == *language)
}
pub fn language_from_accept_language(header: Option<&str>) -> &'static str {
let Some(header) = header else {
return DEFAULT_LANGUAGE;
};
let mut best_language = None;
let mut best_quality = 0.0f32;
for item in header.split(',') {
let mut parts = item.split(';');
let tag = parts.next().unwrap_or("").trim();
let mut quality = 1.0f32;
for param in parts {
let param = param.trim();
if let Some(value) = param.strip_prefix("q=") {
quality = value.parse::<f32>().unwrap_or(0.0);
}
}
if quality <= 0.0 {
continue;
}
if let Some(language) = supported_language(tag) {
if quality > best_quality {
best_language = Some(language);
best_quality = quality;
}
}
}
best_language.unwrap_or(DEFAULT_LANGUAGE)
}
pub fn query_string_with_language(query_string: &str, language: &str) -> String {
if url::form_urlencoded::parse(query_string.as_bytes()).any(|(key, _)| key == "lang") {
return query_string.to_string();
}
if query_string.is_empty() {
format!("lang={language}")
} else {
format!("{query_string}&lang={language}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_browser_language_tags_to_supported_languages() {
assert_eq!(supported_language("fr-CA"), Some("fr"));
assert_eq!(supported_language("zh-Hans-CN"), Some("zh"));
assert_eq!(supported_language("es"), None);
}
#[test]
fn chooses_highest_quality_supported_language() {
assert_eq!(
language_from_accept_language(Some("en-US;q=0.6, fr-FR;q=0.9, de;q=0.7")),
"fr"
);
assert_eq!(
language_from_accept_language(Some("es-ES, hi-IN;q=0.8")),
"hi"
);
assert_eq!(language_from_accept_language(Some("es-ES")), "en");
}
#[test]
fn appends_language_without_overriding_explicit_query_language() {
assert_eq!(query_string_with_language("", "de"), "lang=de");
assert_eq!(
query_string_with_language("lat=51.5&lon=-0.1", "de"),
"lat=51.5&lon=-0.1&lang=de"
);
assert_eq!(
query_string_with_language("lang=fr&lat=1", "de"),
"lang=fr&lat=1"
);
}
}