use std::env;
#[must_use]
pub fn detect_system_locale() -> Option<String> {
let locale_vars = ["LC_ALL", "LC_MESSAGES", "LANG"];
for var_name in &locale_vars {
if let Ok(locale_str) = env::var(var_name)
&& let Some(parsed) = parse_locale_string(&locale_str)
{
return Some(parsed);
}
}
None
}
fn parse_locale_string(locale_str: &str) -> Option<String> {
let trimmed = locale_str.trim();
if trimmed.is_empty() {
return None;
}
let locale_part = trimmed.split('.').next()?;
let normalized = locale_part.replace('_', "-");
let parts: Vec<&str> = normalized.split('-').collect();
if parts.len() >= 2 && parts.len() <= 3 {
let language = parts[0].to_lowercase();
let region = parts[1].to_uppercase();
if parts.len() == 3 {
let script = parts[2];
Some(format!("{language}-{script}-{region}"))
} else {
Some(format!("{language}-{region}"))
}
} else if parts.len() == 1 {
Some(parts[0].to_lowercase())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_locale_string() {
assert_eq!(
parse_locale_string("de_DE.UTF-8"),
Some("de-DE".to_string())
);
assert_eq!(parse_locale_string("en_US.utf8"), Some("en-US".to_string()));
assert_eq!(parse_locale_string("de-DE"), Some("de-DE".to_string()));
assert_eq!(parse_locale_string("en"), Some("en".to_string()));
assert_eq!(
parse_locale_string("zh_Hans_CN.UTF-8"),
Some("zh-CN-HANS".to_string()) );
assert_eq!(parse_locale_string(""), None);
assert_eq!(
parse_locale_string("invalid_format"),
Some("invalid-FORMAT".to_string())
);
}
#[test]
fn test_detect_system_locale_with_env() {
let original_lang = env::var("LANG").ok();
let original_lc_all = env::var("LC_ALL").ok();
let original_lc_messages = env::var("LC_MESSAGES").ok();
unsafe {
env::set_var("LANG", "de_DE.UTF-8");
env::remove_var("LC_ALL");
env::remove_var("LC_MESSAGES");
}
let result = detect_system_locale();
assert_eq!(result, Some("de-DE".to_string()));
unsafe {
env::set_var("LC_ALL", "fr_FR.UTF-8");
env::set_var("LANG", "de_DE.UTF-8");
}
let result = detect_system_locale();
assert_eq!(result, Some("fr-FR".to_string()));
unsafe {
env::set_var("LC_ALL", "es_ES.UTF-8");
env::set_var("LC_MESSAGES", "it_IT.UTF-8");
env::set_var("LANG", "de_DE.UTF-8");
}
let result = detect_system_locale();
assert_eq!(result, Some("es-ES".to_string()));
unsafe {
env::remove_var("LC_ALL");
env::remove_var("LC_MESSAGES");
env::remove_var("LANG");
}
let result = detect_system_locale();
assert_eq!(result, None);
unsafe {
if let Some(val) = original_lang {
env::set_var("LANG", val);
} else {
env::remove_var("LANG");
}
if let Some(val) = original_lc_all {
env::set_var("LC_ALL", val);
} else {
env::remove_var("LC_ALL");
}
if let Some(val) = original_lc_messages {
env::set_var("LC_MESSAGES", val);
} else {
env::remove_var("LC_MESSAGES");
}
}
}
#[test]
fn test_parse_locale_string_edge_cases() {
assert_eq!(parse_locale_string("C"), Some("c".to_string()));
assert_eq!(parse_locale_string("POSIX"), Some("posix".to_string()));
assert_eq!(
parse_locale_string("en_US.ISO8859-1"),
Some("en-US".to_string())
);
assert_eq!(
parse_locale_string("de_DE@euro"),
Some("de-DE@EURO".to_string())
);
assert_eq!(parse_locale_string(""), None);
assert_eq!(parse_locale_string(" "), None);
}
}