use crate::session::Session;
use axum::extract::Request;
use axum::middleware::Next;
use axum::response::Response;
pub const SESSION_KEY: &str = "locale";
pub async fn negotiate(session: Session, request: Request, next: Next) -> Response {
larust_lang::with_locale_scope(async move {
if let Ok(Some(locale)) = session.get::<String>(SESSION_KEY).await {
larust_lang::set_current_locale(locale);
}
next.run(request).await
})
.await
}
pub async fn negotiate_from_header(request: Request, next: Next) -> Response {
let header = request
.headers()
.get(axum::http::header::ACCEPT_LANGUAGE)
.and_then(|value| value.to_str().ok())
.map(str::to_string);
larust_lang::with_locale_scope(async move {
if let Some(locale) = header.as_deref().and_then(preferred_language) {
larust_lang::set_current_locale(locale);
}
next.run(request).await
})
.await
}
fn preferred_language(header: &str) -> Option<String> {
let mut best: Option<(String, f32)> = None;
for entry in header.split(',') {
let mut segments = entry.trim().split(';');
let tag = segments.next()?.trim();
if tag.is_empty() {
continue;
}
let mut quality = 1.0f32;
let mut has_invalid_quality = false;
for segment in segments {
if let Some(raw_quality) = segment.trim().strip_prefix("q=") {
match raw_quality.parse::<f32>() {
Ok(value) => quality = value,
Err(_) => has_invalid_quality = true,
}
}
}
if has_invalid_quality {
continue;
}
let primary = tag.split('-').next().unwrap_or(tag);
if primary.is_empty() || primary == "*" {
continue;
}
let is_better = match &best {
Some((_, best_quality)) => quality > *best_quality,
None => true,
};
if is_better {
best = Some((primary.to_lowercase(), quality));
}
}
best.map(|(locale, _)| locale)
}
#[cfg(test)]
mod tests {
use super::preferred_language;
#[test]
fn picks_the_only_tag_present() {
assert_eq!(preferred_language("es"), Some("es".to_string()));
}
#[test]
fn strips_the_region_subtag() {
assert_eq!(preferred_language("es-ES"), Some("es".to_string()));
}
#[test]
fn honors_quality_values_over_list_order() {
assert_eq!(
preferred_language("en;q=0.8,es;q=0.9"),
Some("es".to_string())
);
}
#[test]
fn prefers_the_first_listed_tag_among_equal_quality() {
assert_eq!(
preferred_language("en;q=0.9,es;q=0.9"),
Some("en".to_string())
);
}
#[test]
fn defaults_missing_quality_to_one() {
assert_eq!(preferred_language("en;q=0.5,fr"), Some("fr".to_string()));
}
#[test]
fn skips_the_wildcard_tag() {
assert_eq!(preferred_language("*"), None);
assert_eq!(preferred_language("*,es;q=0.5"), Some("es".to_string()));
}
#[test]
fn degrades_to_none_on_an_empty_header() {
assert_eq!(preferred_language(""), None);
}
#[test]
fn ignores_unparseable_quality_values() {
assert_eq!(
preferred_language("en;q=not-a-number,fr;q=0.5"),
Some("fr".to_string())
);
}
}