126 lines
3.8 KiB
Rust
126 lines
3.8 KiB
Rust
//! 语言检测工具
|
||
//!
|
||
//! 从 HTTP 请求中提取语言偏好
|
||
//! 优先级:HTTP Header > 环境变量 > 默认值 (en-US)
|
||
|
||
use axum::http::HeaderMap;
|
||
use shared_types::{SUPPORTED_LOCALES, parse_accept_language};
|
||
use std::sync::OnceLock;
|
||
|
||
/// 默认语言(硬编码回退值)
|
||
const FALLBACK_LOCALE: &str = "en-US";
|
||
|
||
/// 缓存的环境变量默认语言
|
||
static DEFAULT_LOCALE_CACHED: OnceLock<&'static str> = OnceLock::new();
|
||
|
||
/// 从环境变量获取默认语言(带缓存)
|
||
///
|
||
/// 环境变量名:DEFAULT_LOCALE
|
||
/// 如果未设置或无效,返回硬编码默认值 "en-US"
|
||
///
|
||
/// 使用 OnceLock 缓存,只在首次调用时读取环境变量
|
||
fn get_default_locale_from_env() -> &'static str {
|
||
*DEFAULT_LOCALE_CACHED.get_or_init(|| {
|
||
if let Ok(locale) = std::env::var("DEFAULT_LOCALE") {
|
||
// 验证是否为支持的语言
|
||
if SUPPORTED_LOCALES.contains(&locale.as_str()) {
|
||
// 匹配已知的静态字符串,避免内存泄漏
|
||
match locale.as_str() {
|
||
"zh-CN" => return "zh-CN",
|
||
"zh-TW" => return "zh-TW",
|
||
"en-US" => return "en-US",
|
||
_ => {}
|
||
}
|
||
}
|
||
}
|
||
FALLBACK_LOCALE
|
||
})
|
||
}
|
||
|
||
/// 从 HTTP 请求头获取语言
|
||
///
|
||
/// 语言检测优先级:
|
||
/// 1. HTTP Accept-Language Header
|
||
/// 2. 环境变量 DEFAULT_LOCALE
|
||
/// 3. 硬编码默认值 "en-US"
|
||
///
|
||
/// # Arguments
|
||
/// * `headers` - HTTP 请求头
|
||
///
|
||
/// # Returns
|
||
/// 语言代码,如 "zh-CN", "en-US"
|
||
pub fn get_locale_from_headers(headers: &HeaderMap) -> &'static str {
|
||
// 1. 首先尝试从 HTTP Header 获取
|
||
if let Some(header) = headers.get("Accept-Language").and_then(|v| v.to_str().ok()) {
|
||
// parse_accept_language 返回的总是支持的语言或默认值
|
||
return parse_accept_language(Some(header));
|
||
}
|
||
|
||
// 2. 尝试从环境变量获取(已缓存)
|
||
get_default_locale_from_env()
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use axum::http::header::HeaderName;
|
||
|
||
#[test]
|
||
fn test_get_locale_from_headers_zh_cn() {
|
||
let mut headers = HeaderMap::new();
|
||
headers.insert(
|
||
HeaderName::from_static("accept-language"),
|
||
"zh-CN".parse().unwrap(),
|
||
);
|
||
assert_eq!(get_locale_from_headers(&headers), "zh-CN");
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_locale_from_headers_zh_tw() {
|
||
let mut headers = HeaderMap::new();
|
||
headers.insert(
|
||
HeaderName::from_static("accept-language"),
|
||
"zh-TW".parse().unwrap(),
|
||
);
|
||
assert_eq!(get_locale_from_headers(&headers), "zh-TW");
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_locale_from_headers_zh_hk() {
|
||
let mut headers = HeaderMap::new();
|
||
headers.insert(
|
||
HeaderName::from_static("accept-language"),
|
||
"zh-HK".parse().unwrap(),
|
||
);
|
||
assert_eq!(get_locale_from_headers(&headers), "zh-TW");
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_locale_from_headers_en_us() {
|
||
let mut headers = HeaderMap::new();
|
||
headers.insert(
|
||
HeaderName::from_static("accept-language"),
|
||
"en-US".parse().unwrap(),
|
||
);
|
||
assert_eq!(get_locale_from_headers(&headers), "en-US");
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_locale_from_headers_none() {
|
||
let headers = HeaderMap::new();
|
||
// 无 Header 且无环境变量时,应返回默认值 en-US
|
||
assert_eq!(get_locale_from_headers(&headers), "en-US");
|
||
}
|
||
|
||
#[test]
|
||
fn test_get_locale_from_headers_unsupported() {
|
||
let mut headers = HeaderMap::new();
|
||
headers.insert(
|
||
HeaderName::from_static("accept-language"),
|
||
"fr-FR".parse().unwrap(),
|
||
);
|
||
// 不支持的语言应返回默认值
|
||
assert_eq!(get_locale_from_headers(&headers), "en-US");
|
||
}
|
||
}
|