orinium_browser/platform/
locale.rs1const FALLBACK_LANGUAGE: &str = "en-US";
4
5pub fn preferred_language() -> String {
7 system_language()
8 .and_then(|language| normalize_language_tag(&language))
9 .unwrap_or_else(|| FALLBACK_LANGUAGE.to_string())
10}
11
12pub fn accept_language_header() -> String {
14 let primary = preferred_language();
15 let base = primary.split('-').next().unwrap_or(&primary);
16 if base.eq_ignore_ascii_case(&primary) {
17 primary
18 } else {
19 format!("{primary},{base};q=0.9,en;q=0.8")
20 }
21}
22
23fn normalize_language_tag(language: &str) -> Option<String> {
24 let language = language
25 .split(['.', '@'])
26 .next()
27 .unwrap_or(language)
28 .replace('_', "-");
29 if language.is_empty()
30 || language.eq_ignore_ascii_case("c")
31 || language.eq_ignore_ascii_case("posix")
32 || !language
33 .chars()
34 .all(|character| character.is_ascii_alphanumeric() || character == '-')
35 {
36 return None;
37 }
38 Some(language)
39}
40
41#[cfg(target_os = "windows")]
42fn system_language() -> Option<String> {
43 const LOCALE_NAME_MAX_LENGTH: usize = 85;
44 #[link(name = "kernel32")]
45 unsafe extern "system" {
46 fn GetUserDefaultLocaleName(locale_name: *mut u16, locale_name_count: i32) -> i32;
47 }
48
49 let mut buffer = [0_u16; LOCALE_NAME_MAX_LENGTH];
50 let length =
53 unsafe { GetUserDefaultLocaleName(buffer.as_mut_ptr(), LOCALE_NAME_MAX_LENGTH as i32) };
54 (length > 1).then(|| String::from_utf16_lossy(&buffer[..length as usize - 1]))
55}
56
57#[cfg(not(target_os = "windows"))]
58fn system_language() -> Option<String> {
59 ["LC_ALL", "LC_MESSAGES", "LANG"]
60 .into_iter()
61 .find_map(|name| std::env::var(name).ok().filter(|value| !value.is_empty()))
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn normalizes_common_platform_locale_forms() {
70 assert_eq!(normalize_language_tag("ja_JP.UTF-8"), Some("ja-JP".into()));
71 assert_eq!(
72 normalize_language_tag("en_US@calendar"),
73 Some("en-US".into())
74 );
75 assert_eq!(normalize_language_tag("C"), None);
76 }
77}