use crate::config::Config;
pub const SUPPORTED_LANGS: &[(&str, &str)] = &[
("en", "English"),
("ko", "한국어"),
("pt-BR", "Português (Brasil)"),
("ja", "日本語"),
];
pub fn init() {
let locale = detect_locale();
rust_i18n::set_locale(&locale);
}
pub fn detect_locale() -> String {
if let Ok(lang) = std::env::var("SCOOP_LANG") {
if let Some(code) = resolve_supported(&lang) {
return code.to_string();
}
}
if let Ok(config) = Config::load() {
if let Some(lang) = config.lang {
if let Some(code) = resolve_supported(&lang) {
return code.to_string();
}
}
}
if let Some(locale) = sys_locale::get_locale() {
if let Some(code) = resolve_supported(&locale) {
return code.to_string();
}
}
"en".to_string()
}
fn resolve_supported(raw: &str) -> Option<&'static str> {
let normalized = normalize(raw);
canonical_supported(&normalized)
.or_else(|| canonical_supported(normalized.split('-').next().unwrap_or("")))
}
fn canonical_supported(candidate: &str) -> Option<&'static str> {
SUPPORTED_LANGS
.iter()
.map(|(code, _)| *code)
.find(|code| code.eq_ignore_ascii_case(candidate))
}
pub fn current() -> String {
rust_i18n::locale().to_string()
}
pub fn is_supported(lang: &str) -> bool {
canonical_supported(lang).is_some()
}
pub fn language_name(code: &str) -> Option<&'static str> {
SUPPORTED_LANGS
.iter()
.find(|(c, _)| *c == code)
.map(|(_, name)| *name)
}
fn normalize(locale: &str) -> String {
locale
.split('.')
.next()
.unwrap_or("en")
.replace('_', "-")
.to_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
#[test]
fn test_normalize() {
assert_eq!(normalize("ko_KR.UTF-8"), "ko-kr");
assert_eq!(normalize("en_US"), "en-us");
assert_eq!(normalize("ja"), "ja");
assert_eq!(normalize("zh_CN.UTF-8"), "zh-cn");
}
#[test]
fn test_is_supported() {
assert!(is_supported("en"));
assert!(is_supported("ko"));
assert!(is_supported("ja"));
assert!(is_supported("pt-BR"));
assert!(!is_supported("fr"));
assert!(!is_supported("zh-CN")); }
#[test]
fn test_language_name() {
assert_eq!(language_name("en"), Some("English"));
assert_eq!(language_name("ko"), Some("한국어"));
assert_eq!(language_name("ja"), Some("日本語"));
assert_eq!(language_name("pt-BR"), Some("Português (Brasil)"));
assert_eq!(language_name("fr"), None);
}
#[test]
#[serial]
fn test_detect_with_env() {
let original = std::env::var("SCOOP_LANG").ok();
unsafe {
std::env::set_var("SCOOP_LANG", "ko");
assert_eq!(detect_locale(), "ko");
std::env::set_var("SCOOP_LANG", "en");
assert_eq!(detect_locale(), "en");
match original {
Some(val) => std::env::set_var("SCOOP_LANG", val),
None => std::env::remove_var("SCOOP_LANG"),
}
}
}
#[test]
#[serial]
fn test_detect_with_unsupported_env() {
let original = std::env::var("SCOOP_LANG").ok();
unsafe {
std::env::set_var("SCOOP_LANG", "fr");
let locale = detect_locale();
assert!(is_supported(&locale) || locale == "en");
match original {
Some(val) => std::env::set_var("SCOOP_LANG", val),
None => std::env::remove_var("SCOOP_LANG"),
}
}
}
#[test]
#[serial]
fn test_detect_pt_br_from_env_variants() {
let original = std::env::var("SCOOP_LANG").ok();
unsafe {
for input in ["pt-BR", "pt-br", "PT-BR", "pt_BR", "pt-BR.UTF-8"] {
std::env::set_var("SCOOP_LANG", input);
assert_eq!(
detect_locale(),
"pt-BR",
"SCOOP_LANG={input} should resolve to canonical pt-BR"
);
}
match original {
Some(val) => std::env::set_var("SCOOP_LANG", val),
None => std::env::remove_var("SCOOP_LANG"),
}
}
}
#[test]
fn test_is_supported_is_case_insensitive() {
assert!(is_supported("pt-BR"));
assert!(is_supported("pt-br"));
assert!(is_supported("PT-BR"));
assert!(is_supported("EN"));
assert!(is_supported("Ja"));
assert!(!is_supported("fr"));
assert!(!is_supported("zh-CN")); }
#[test]
fn test_resolve_supported_full_then_prefix() {
assert_eq!(resolve_supported("pt-BR.UTF-8"), Some("pt-BR"));
assert_eq!(resolve_supported("PT_br"), Some("pt-BR"));
assert_eq!(resolve_supported("ko_KR"), Some("ko"));
assert_eq!(resolve_supported("en_US.UTF-8"), Some("en"));
assert_eq!(resolve_supported("ja"), Some("ja"));
assert_eq!(resolve_supported("fr"), None);
assert_eq!(resolve_supported("zh-CN"), None);
}
}