ferro_rs/lang/
middleware.rs1use crate::config::Config;
4use crate::http::Response;
5use crate::middleware::{Middleware, Next};
6use crate::Request;
7use async_trait::async_trait;
8use ferro_lang::{normalize_locale, LangConfig};
9
10use super::{locale_scope, with_locale_scope};
11
12pub struct LangMiddleware;
29
30#[async_trait]
31impl Middleware for LangMiddleware {
32 async fn handle(&self, request: Request, next: Next) -> Response {
33 let config = Config::get::<LangConfig>().unwrap_or_default();
34
35 let detected = detect_locale(&request, &config);
36
37 let ctx = locale_scope();
38 {
39 let mut guard = ctx.write().await;
40 *guard = Some(detected);
41 }
42
43 with_locale_scope(ctx, async { next(request).await }).await
44 }
45}
46
47fn detect_locale(request: &Request, config: &LangConfig) -> String {
49 if let Some(locale) = request.query("locale") {
51 if !locale.is_empty() {
52 return normalize_locale(&locale);
53 }
54 }
55
56 if let Some(accept) = request.header("accept-language") {
58 if let Some(locale) = parse_accept_language(accept) {
59 return locale;
60 }
61 }
62
63 normalize_locale(&config.locale)
65}
66
67fn parse_accept_language(header: &str) -> Option<String> {
74 let first = header.split(',').next()?.trim();
75 if first.is_empty() {
76 return None;
77 }
78 let lang = first.split(';').next()?.trim();
80 if lang.is_empty() || lang == "*" {
81 return None;
82 }
83 Some(normalize_locale(lang))
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn parse_accept_language_full_header() {
92 let result = parse_accept_language("en-US,en;q=0.9,fr;q=0.8");
93 assert_eq!(result, Some("en-us".to_string()));
94 }
95
96 #[test]
97 fn parse_accept_language_single_tag() {
98 let result = parse_accept_language("fr");
99 assert_eq!(result, Some("fr".to_string()));
100 }
101
102 #[test]
103 fn parse_accept_language_with_quality() {
104 let result = parse_accept_language("de-DE;q=0.8");
105 assert_eq!(result, Some("de-de".to_string()));
106 }
107
108 #[test]
109 fn parse_accept_language_normalizes_underscore() {
110 let result = parse_accept_language("pt_BR,en;q=0.5");
111 assert_eq!(result, Some("pt-br".to_string()));
112 }
113
114 #[test]
115 fn parse_accept_language_empty() {
116 let result = parse_accept_language("");
117 assert_eq!(result, None);
118 }
119
120 #[test]
121 fn parse_accept_language_wildcard() {
122 let result = parse_accept_language("*");
123 assert_eq!(result, None);
124 }
125
126 #[test]
127 fn parse_accept_language_trims_whitespace() {
128 let result = parse_accept_language(" es-MX , en;q=0.5 ");
129 assert_eq!(result, Some("es-mx".to_string()));
130 }
131}