1use std::env;
4
5#[must_use]
18pub fn detect_system_locale() -> Option<String> {
19 let locale_vars = ["LC_ALL", "LC_MESSAGES", "LANG"];
21
22 for var_name in &locale_vars {
23 if let Ok(locale_str) = env::var(var_name)
24 && let Some(parsed) = parse_locale_string(&locale_str)
25 {
26 return Some(parsed);
27 }
28 }
29
30 None
31}
32
33fn parse_locale_string(locale_str: &str) -> Option<String> {
46 let trimmed = locale_str.trim();
47 if trimmed.is_empty() {
48 return None;
49 }
50
51 let locale_part = trimmed.split('.').next()?;
53
54 let normalized = locale_part.replace('_', "-");
56
57 let parts: Vec<&str> = normalized.split('-').collect();
59 if parts.len() >= 2 && parts.len() <= 3 {
60 let language = parts[0].to_lowercase();
62 let region = parts[1].to_uppercase();
63
64 if parts.len() == 3 {
65 let script = parts[2];
67 Some(format!("{language}-{script}-{region}"))
68 } else {
69 Some(format!("{language}-{region}"))
70 }
71 } else if parts.len() == 1 {
72 Some(parts[0].to_lowercase())
74 } else {
75 None
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_parse_locale_string() {
85 assert_eq!(
86 parse_locale_string("de_DE.UTF-8"),
87 Some("de-DE".to_string())
88 );
89 assert_eq!(parse_locale_string("en_US.utf8"), Some("en-US".to_string()));
90 assert_eq!(parse_locale_string("de-DE"), Some("de-DE".to_string()));
91 assert_eq!(parse_locale_string("en"), Some("en".to_string()));
92 assert_eq!(
95 parse_locale_string("zh_Hans_CN.UTF-8"),
96 Some("zh-CN-HANS".to_string()) );
98 assert_eq!(parse_locale_string(""), None);
99 assert_eq!(
102 parse_locale_string("invalid_format"),
103 Some("invalid-FORMAT".to_string())
104 );
105 }
106
107 #[test]
108 fn test_detect_system_locale_with_env() {
109 let original_lang = env::var("LANG").ok();
111 let original_lc_all = env::var("LC_ALL").ok();
112 let original_lc_messages = env::var("LC_MESSAGES").ok();
113
114 unsafe {
115 env::set_var("LANG", "de_DE.UTF-8");
117 env::remove_var("LC_ALL");
118 env::remove_var("LC_MESSAGES");
119 }
120 let result = detect_system_locale();
121 assert_eq!(result, Some("de-DE".to_string()));
122
123 unsafe {
124 env::set_var("LC_ALL", "fr_FR.UTF-8");
126 env::set_var("LANG", "de_DE.UTF-8");
127 }
128 let result = detect_system_locale();
129 assert_eq!(result, Some("fr-FR".to_string()));
130
131 unsafe {
132 env::set_var("LC_ALL", "es_ES.UTF-8");
134 env::set_var("LC_MESSAGES", "it_IT.UTF-8");
135 env::set_var("LANG", "de_DE.UTF-8");
136 }
137 let result = detect_system_locale();
138 assert_eq!(result, Some("es-ES".to_string())); unsafe {
141 env::remove_var("LC_ALL");
143 env::remove_var("LC_MESSAGES");
144 env::remove_var("LANG");
145 }
146 let result = detect_system_locale();
147 assert_eq!(result, None);
148
149 unsafe {
151 if let Some(val) = original_lang {
152 env::set_var("LANG", val);
153 } else {
154 env::remove_var("LANG");
155 }
156 if let Some(val) = original_lc_all {
157 env::set_var("LC_ALL", val);
158 } else {
159 env::remove_var("LC_ALL");
160 }
161 if let Some(val) = original_lc_messages {
162 env::set_var("LC_MESSAGES", val);
163 } else {
164 env::remove_var("LC_MESSAGES");
165 }
166 }
167 }
168
169 #[test]
170 fn test_parse_locale_string_edge_cases() {
171 assert_eq!(parse_locale_string("C"), Some("c".to_string()));
174 assert_eq!(parse_locale_string("POSIX"), Some("posix".to_string()));
176 assert_eq!(
178 parse_locale_string("en_US.ISO8859-1"),
179 Some("en-US".to_string())
180 );
181 assert_eq!(
184 parse_locale_string("de_DE@euro"),
185 Some("de-DE@EURO".to_string())
186 );
187
188 assert_eq!(parse_locale_string(""), None);
190 assert_eq!(parse_locale_string(" "), None);
191 }
192}