Skip to main content

rustbasic_translatable/
middleware.rs

1use rustbasic_core::requests::Request;
2use rustbasic_core::middleware::Next;
3use rustbasic_core::router::Response;
4use crate::translator::TRANSLATOR;
5
6// Define task-local active request locale storage
7rustbasic_core::tokio::task_local! {
8    pub static CURRENT_LOCALE: String;
9}
10
11/// RustBasic Middleware to extract client's preferred language and bind it to the request scope
12pub async fn translatable_middleware(
13    req: Request,
14    next: Next,
15) -> Response {
16    let extracted_locale = detect_locale(&req);
17
18    // Bind locale to task-local context so it is available thread-safely throughout the request lifetime
19    CURRENT_LOCALE.scope(extracted_locale, next.run(req)).await
20}
21
22/// Retrieve the active request locale, falling back to default locale if not bound
23pub fn get_locale() -> String {
24    CURRENT_LOCALE.try_with(|l| l.clone()).unwrap_or_else(|_| TRANSLATOR.get_default_locale())
25}
26
27/// Detect language from request query, cookies, session, or Accept-Language header
28fn detect_locale(req: &Request) -> String {
29    // 1. Cek Query/Input Parameter: ?lang=en atau ?locale=en
30    if let Some(q_locale) = req.input_as_str("lang").or_else(|| req.input_as_str("locale")) {
31        let trimmed = q_locale.trim().to_lowercase();
32        if !trimmed.is_empty() {
33            return trimmed;
34        }
35    }
36
37    // 2. Cek Session jika tersedia
38    if let Some(s_locale) = req.session.get::<String>("locale") {
39        let trimmed = s_locale.trim().to_lowercase();
40        if !trimmed.is_empty() {
41            return trimmed;
42        }
43    }
44
45    // 3. Cek Cookie: lang=en atau locale=en
46    if let Some(cookie_header) = req.headers.get("cookie") {
47        for cookie in cookie_header.split(';') {
48            let parts: Vec<&str> = cookie.splitn(2, '=').collect();
49            if parts.len() == 2 {
50                let name = parts[0].trim();
51                if name == "lang" || name == "locale" {
52                    let val = parts[1].trim();
53                    if !val.is_empty() {
54                        return val.to_lowercase();
55                    }
56                }
57            }
58        }
59    }
60
61    // 4. Cek Header Accept-Language: id-ID,id;q=0.9,en-US;q=0.8
62    if let Some(accept_lang) = req.headers.get("accept-language")
63        && let Some(first_part) = accept_lang.split(',').next()
64        && let Some(lang_code) = first_part.split(';').next() {
65        let primary_code = lang_code.trim().split('-').next().unwrap_or("").trim();
66        if primary_code.len() == 2 {
67            return primary_code.to_lowercase();
68        }
69    }
70
71    // 5. Fallback ke default global translator
72    TRANSLATOR.get_default_locale()
73}