use std::str::FromStr;
use std::sync::OnceLock;
use icu::locale::Locale;
static GLOBAL_LOCALE: OnceLock<Locale> = OnceLock::new();
static OVERRIDE_LOCALE: std::sync::RwLock<Option<Locale>> = std::sync::RwLock::new(None);
fn detect_locale() -> Locale {
if let Ok(lang) = std::env::var("DBNEXUS_LANG")
&& !lang.is_empty()
&& let Ok(locale) = Locale::from_str(&lang)
{
return locale;
}
if let Some(sys_locale) = sys_locale::get_locale()
&& let Ok(locale) = Locale::from_str(&sys_locale)
{
return locale;
}
Locale::from_str("en").expect("'en' is a valid BCP-47 locale")
}
pub fn current_locale() -> Locale {
if let Ok(guard) = OVERRIDE_LOCALE.read()
&& let Some(locale) = guard.as_ref()
{
return locale.clone();
}
GLOBAL_LOCALE.get_or_init(detect_locale).clone()
}
pub fn set_locale(locale: &str) -> Result<(), super::I18nError> {
let parsed = Locale::from_str(locale).map_err(|e| super::I18nError::InvalidLocale {
input: locale.to_string(),
reason: e.to_string(),
})?;
let mut guard = OVERRIDE_LOCALE.write().expect("locale override RwLock poisoned");
*guard = Some(parsed);
Ok(())
}
pub fn clear_locale_override() {
let mut guard = OVERRIDE_LOCALE.write().expect("locale override RwLock poisoned");
*guard = None;
}
pub fn detected_locale() -> Locale {
GLOBAL_LOCALE.get_or_init(detect_locale).clone()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_locale_returns_valid_locale() {
let locale = detect_locale();
let locale_str = locale.to_string();
assert!(!locale_str.is_empty(), "detected locale should not be empty");
}
#[test]
fn test_current_locale_returns_valid_locale() {
clear_locale_override();
let locale = current_locale();
assert!(!locale.to_string().is_empty());
}
#[test]
fn test_set_locale_override() {
set_locale("zh-CN").expect("zh-CN is valid");
let locale = current_locale();
assert_eq!(locale.to_string(), "zh-CN");
clear_locale_override();
let locale = current_locale();
assert!(!locale.to_string().is_empty());
}
#[test]
fn test_set_locale_invalid() {
let result = set_locale("not-a-valid-locale!!!");
assert!(result.is_err(), "invalid locale should return error");
match result.err().unwrap() {
super::super::I18nError::InvalidLocale { input, .. } => {
assert_eq!(input, "not-a-valid-locale!!!");
}
other => panic!("expected InvalidLocale, got {other:?}"),
}
}
#[test]
fn test_set_locale_various_locales() {
let locales = ["en-US", "zh-CN"];
for locale_str in locales {
set_locale(locale_str).unwrap_or_else(|_| panic!("{locale_str} should be valid"));
let current = current_locale();
assert_eq!(
current.to_string(),
locale_str,
"current_locale should return {locale_str} after set"
);
}
clear_locale_override();
}
#[test]
fn test_clear_locale_override_when_none() {
clear_locale_override();
clear_locale_override(); }
#[test]
fn test_detect_locale_without_env_var() {
clear_locale_override();
let detected = detect_locale();
let locale_str = detected.to_string();
assert!(!locale_str.is_empty(), "detect_locale should return a valid locale");
}
#[test]
fn test_detected_locale_consistent() {
clear_locale_override();
let l1 = detected_locale();
let l2 = detected_locale();
assert_eq!(
l1.to_string(),
l2.to_string(),
"detected_locale should be consistent across calls"
);
}
}