Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 51 additions & 10 deletions src/uucore/src/lib/mods/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,20 @@ pub fn get_message_with_args(id: &str, ftl_args: FluentArgs) -> String {

/// Function to detect system locale from environment variables
fn detect_system_locale() -> Result<LanguageIdentifier, LocalizationError> {
let locale_str = std::env::var("LANG")
.unwrap_or_else(|_| DEFAULT_LOCALE.to_string())
// Invalidate an empty string env var (GNU treats unset and empty differently for some vars,
// but an empty value should not override a lower-precedence variable).
let locale_var = |name| std::env::var(name).ok().filter(|v| !v.is_empty());

// Check LC_ALL -> LC_MESSAGES -> LANG, then fall back to DEFAULT_LOCALE.
let locale_str = locale_var("LC_ALL")
.or_else(|| locale_var("LC_MESSAGES"))
.or_else(|| locale_var("LANG"))
.unwrap_or_else(|| DEFAULT_LOCALE.to_string())
Comment on lines +422 to +425
Copy link
Copy Markdown
Contributor

@xtqqczze xtqqczze May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let locale_str = locale_var("LC_ALL")
.or_else(|| locale_var("LC_MESSAGES"))
.or_else(|| locale_var("LANG"))
.unwrap_or_else(|| DEFAULT_LOCALE.to_string())
let locale_str = ["LC_ALL", "LC_MESSAGES", "LANG"]
.into_iter()
.find_map(|k| std::env::var(k).ok().filter(|v| !v.is_empty()))
.as_deref()
.unwrap_or(DEFAULT_LOCALE)

.split('.')
.next()
.unwrap_or(DEFAULT_LOCALE)
.to_string();

LanguageIdentifier::from_str(&locale_str).map_err(|_| {
LocalizationError::ParseLocale(format!("Failed to parse locale: {locale_str}"))
})
Expand All @@ -429,7 +437,7 @@ fn detect_system_locale() -> Result<LanguageIdentifier, LocalizationError> {
/// Always loads common strings in addition to utility-specific strings.
///
/// This function initializes the localization system based on the system's locale
/// preferences (via the LANG environment variable) or falls back to English
/// preferences (via `LC_ALL`, `LC_MESSAGES`, or `LANG`) or falls back to English
/// if the system locale cannot be determined or the locale file doesn't exist.
/// English is always loaded as a fallback.
///
Expand Down Expand Up @@ -1328,25 +1336,58 @@ invalid-syntax = This is { $missing

#[test]
fn test_detect_system_locale_no_lang_env() {
// Save current LANG value
let original_lc_all = env::var("LC_ALL").ok();
let original_lc_messages = env::var("LC_MESSAGES").ok();
let original_lang = env::var("LANG").ok();

// Remove LANG environment variable
unsafe {
env::remove_var("LC_ALL");
env::remove_var("LC_MESSAGES");
env::remove_var("LANG");
}

let result = detect_system_locale();
assert!(result.is_ok());
assert_eq!(result.unwrap().to_string(), "en-US");

// Restore original LANG value
if let Some(val) = original_lang {
unsafe {
unsafe {
if let Some(val) = original_lc_all {
env::set_var("LC_ALL", val);
}
if let Some(val) = original_lc_messages {
env::set_var("LC_MESSAGES", val);
}
if let Some(val) = original_lang {
env::set_var("LANG", val);
}
} else {
{} // Was already unset
}
}

#[test]
fn test_detect_system_locale_prefers_lc_all_over_lang() {
let original_lc_all = env::var("LC_ALL").ok();
let original_lang = env::var("LANG").ok();

unsafe {
env::set_var("LC_ALL", "fr-FR.UTF-8");
env::set_var("LANG", "en-US.UTF-8");
}

let result = detect_system_locale();
assert!(result.is_ok());
assert_eq!(result.unwrap().to_string(), "fr-FR");

unsafe {
if let Some(val) = original_lc_all {
env::set_var("LC_ALL", val);
} else {
env::remove_var("LC_ALL");
}
if let Some(val) = original_lang {
env::set_var("LANG", val);
} else {
env::remove_var("LANG");
}
}
}

Expand Down
Loading