pub const LANGUAGE_OPTIONS: &[(&str, &str)] = &[
("en", "English"),
("fr", "Français"),
("es", "Español"),
("pt", "Português"),
("it", "Italiano"),
("de", "Deutsch"),
("nl", "Nederlands"),
("sv", "Svenska"),
("fi", "Suomi"),
("pl", "Polski"),
("cs", "Čeština"),
("sk", "Slovenčina"),
("tr", "Türkçe"),
("ru", "Русский"),
("ar", "العربية"),
("hi", "हिन्दी"),
("bn", "বাংলা"),
("zh", "中文"),
("zh_tw", "繁體中文"),
("ja", "日本語"),
("ko", "한국어"),
("id", "Bahasa Indonesia"),
("ur", "اردو"),
("sw", "Kiswahili"),
("vi", "Tiếng Việt"),
("th", "ไทย"),
];
pub fn detect_system_language() -> usize {
let lang_raw = detect_system_language_string();
match_language_code(&lang_raw)
}
fn detect_system_language_string() -> String {
if let Ok(lang) = std::env::var("LANG")
.or_else(|_| std::env::var("LC_ALL"))
.or_else(|_| std::env::var("LC_MESSAGES"))
&& !lang.is_empty()
&& lang != "C"
&& lang != "POSIX"
{
return lang.to_lowercase();
}
#[cfg(target_os = "macos")]
{
if let Ok(output) = std::process::Command::new("defaults")
.args(["read", "-g", "AppleLocale"])
.output()
{
if output.status.success() {
let locale = String::from_utf8_lossy(&output.stdout)
.trim()
.to_lowercase();
if !locale.is_empty() {
return locale;
}
}
}
}
#[cfg(target_os = "windows")]
{
let mut buf = [0u16; 85]; let n = unsafe { windows::Win32::Globalization::GetUserDefaultLocaleName(&mut buf) };
if n > 1 {
let locale = String::from_utf16_lossy(&buf[..n as usize - 1]).to_lowercase();
if !locale.is_empty() {
return locale;
}
}
}
String::new()
}
fn match_language_code(raw: &str) -> usize {
let normalized = raw.replace('-', "_");
if normalized.starts_with("zh_tw") {
return LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "zh_tw")
.unwrap_or(0);
}
let code = normalized.get(..2).unwrap_or("");
LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == code)
.unwrap_or(0)
}
pub fn set_locale(lang: &str) {
rust_i18n::set_locale(lang);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn language_options_not_empty() {
assert!(!LANGUAGE_OPTIONS.is_empty());
}
#[test]
fn language_options_starts_with_english() {
assert_eq!(LANGUAGE_OPTIONS[0], ("en", "English"));
}
#[test]
fn language_options_codes_unique() {
let mut codes: Vec<&str> = LANGUAGE_OPTIONS.iter().map(|(c, _)| *c).collect();
let len_before = codes.len();
codes.sort();
codes.dedup();
assert_eq!(codes.len(), len_before, "duplicate language codes found");
}
#[test]
fn match_english() {
assert_eq!(match_language_code("en"), 0);
assert_eq!(match_language_code("en_us.utf-8"), 0);
assert_eq!(match_language_code("en_GB"), 0);
}
#[test]
fn match_french() {
let idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "fr")
.unwrap();
assert_eq!(match_language_code("fr"), idx);
assert_eq!(match_language_code("fr_FR.UTF-8"), idx);
assert_eq!(match_language_code("fr_ca"), idx);
}
#[test]
fn match_chinese_simplified() {
let idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "zh")
.unwrap();
assert_eq!(match_language_code("zh_cn"), idx);
assert_eq!(match_language_code("zh"), idx);
}
#[test]
fn match_chinese_traditional() {
let idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "zh_tw")
.unwrap();
assert_eq!(match_language_code("zh_tw"), idx);
assert_eq!(match_language_code("zh_tw.utf-8"), idx);
}
#[test]
fn match_hyphenated_locale() {
let idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "zh_tw")
.unwrap();
assert_eq!(match_language_code("zh-tw"), idx);
}
#[test]
fn match_unknown_defaults_to_english() {
assert_eq!(match_language_code("xx"), 0);
assert_eq!(match_language_code(""), 0);
assert_eq!(match_language_code("zz_ZZ"), 0);
}
#[test]
fn match_japanese() {
let idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "ja")
.unwrap();
assert_eq!(match_language_code("ja_JP.UTF-8"), idx);
}
#[test]
fn match_arabic() {
let idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "ar")
.unwrap();
assert_eq!(match_language_code("ar_SA"), idx);
}
#[test]
fn match_case_insensitive_input() {
let fr_idx = LANGUAGE_OPTIONS
.iter()
.position(|(c, _)| *c == "fr")
.unwrap();
assert_eq!(match_language_code("fr_fr.utf-8"), fr_idx);
}
}