Skip to content
Closed
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion src/uu/chown/src/chown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ mod test {
#[test]
fn test_parse_spec() {
unsafe {
env::set_var("LANG", "C");
env::set_var("LC_ALL", "C");
}
let _ = locale::setup_localization("chown");
assert!(matches!(parse_spec(":", ':'), Ok((None, None))));
Expand Down
2 changes: 1 addition & 1 deletion src/uu/dd/src/progress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ mod tests {
use super::{ProgUpdate, ReadStat, WriteStat};
fn init() {
unsafe {
env::set_var("LANG", "C");
env::set_var("LC_ALL", "C");
}
let _ = setup_localization("dd");
}
Expand Down
2 changes: 1 addition & 1 deletion src/uu/df/src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ mod tests {

fn init() {
unsafe {
std::env::set_var("LANG", "C");
std::env::set_var("LC_ALL", "C");
}
let _ = setup_localization("df");
}
Expand Down
2 changes: 1 addition & 1 deletion src/uu/touch/src/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ mod tests {
#[test]
fn test_get_pathbuf_from_stdout_fails_if_stdout_is_not_a_file() {
unsafe {
env::set_var("LANG", "C");
env::set_var("LC_ALL", "C");
}
let _ = locale::setup_localization("touch");
// We can trigger an error by not setting stdout to anything (will
Expand Down
64 changes: 38 additions & 26 deletions src/uucore/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,14 +274,20 @@ fn embed_static_utility_locales(
/// It always includes "en-US" to ensure that a fallback is available if the
/// system locale's translation file is missing or if `LANG` is not set.
fn get_locales_to_embed() -> (String, Option<String>) {
let system_locale = env::var("LANG").ok().and_then(|lang| {
let locale = lang.split('.').next()?.replace('_', "-");
if locale != "en-US" && !locale.is_empty() {
Some(locale)
} else {
None
}
});
// Invalidate an empty string env var
let locale_var = |name| env::var(name).ok().filter(|v| !v.is_empty());

let system_locale = locale_var("LC_ALL")
.or_else(|| locale_var("LC_MESSAGES"))
.or_else(|| locale_var("LANG"))
.and_then(|lang| {
let locale = lang.split('.').next()?.replace('_', "-");
if locale != "en-US" && !locale.is_empty() {
Some(locale)
} else {
None
}
});
("en-US".to_string(), system_locale)
}

Expand Down Expand Up @@ -375,121 +381,127 @@ mod tests {
#[test]
fn get_locales_to_embed_no_lang() {
unsafe {
env::remove_var("LC_ALL");
env::remove_var("LC_MESSAGES");
env::remove_var("LANG");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, None);

unsafe {
env::set_var("LC_ALL", "");
env::set_var("LC_MESSAGES", "");
env::set_var("LANG", "");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, None);
unsafe {
env::remove_var("LC_ALL");
env::remove_var("LC_MESSAGES");
env::remove_var("LANG");
}

unsafe {
env::set_var("LANG", "en_US.UTF-8");
env::set_var("LC_ALL", "en_US.UTF-8");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, None);
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}
}

#[test]
fn get_locales_to_embed_with_lang() {
unsafe {
env::set_var("LANG", "fr_FR.UTF-8");
env::set_var("LC_ALL", "fr_FR.UTF-8");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("fr-FR".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}

unsafe {
env::set_var("LANG", "zh_CN.UTF-8");
env::set_var("LC_ALL", "zh_CN.UTF-8");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("zh-CN".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}

unsafe {
env::set_var("LANG", "de");
env::set_var("LC_ALL", "de");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("de".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}
}

#[test]
fn get_locales_to_embed_invalid_lang() {
// invalid locale format
unsafe {
env::set_var("LANG", "invalid");
env::set_var("LC_ALL", "invalid");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("invalid".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}

// numeric values
unsafe {
env::set_var("LANG", "123");
env::set_var("LC_ALL", "123");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("123".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}

// special characters
unsafe {
env::set_var("LANG", "@@@@");
env::set_var("LC_ALL", "@@@@");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("@@@@".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}

// malformed locale (no country code but with encoding)
unsafe {
env::set_var("LANG", "en.UTF-8");
env::set_var("LC_ALL", "en.UTF-8");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("en".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}

// valid format but unusual locale
unsafe {
env::set_var("LANG", "XX_YY.UTF-8");
env::set_var("LC_ALL", "XX_YY.UTF-8");
}
let (en_locale, system_locale) = get_locales_to_embed();
assert_eq!(en_locale, "en-US");
assert_eq!(system_locale, Some("XX-YY".to_string()));
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/uucore/src/lib/features/uptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ mod tests {
#[test]
fn test_format_nusers() {
unsafe {
std::env::set_var("LANG", "en_US.UTF-8");
std::env::set_var("LC_ALL", "en_US.UTF-8");
}
let _ = locale::setup_localization("uptime");
assert_eq!("0 users", format_nusers(0));
Expand Down
8 changes: 4 additions & 4 deletions src/uucore/src/lib/mods/clap_localization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,10 +676,10 @@ mod tests {
use crate::locale::{get_message, setup_localization};
use std::env;

let original_lang = env::var("LANG").unwrap_or_default();
let original_lang = env::var("LC_ALL").unwrap_or_default();

unsafe {
env::set_var("LANG", "fr_FR.UTF-8");
env::set_var("LC_ALL", "fr_FR.UTF-8");
}

if setup_localization("test").is_ok() {
Expand All @@ -690,9 +690,9 @@ mod tests {

unsafe {
if original_lang.is_empty() {
env::remove_var("LANG");
env::remove_var("LC_ALL");
} else {
env::set_var("LANG", original_lang);
env::set_var("LC_ALL", original_lang);
}
}
}
Expand Down
61 changes: 45 additions & 16 deletions src/uucore/src/lib/mods/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,19 @@ 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
let locale_var = |name| std::env::var(name).ok().filter(|v| !v.is_empty());

// We check LC_ALL -> LC_MESSAGES -> LANG - We fallback 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())
.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 Down Expand Up @@ -1246,18 +1253,40 @@ invalid-syntax = This is { $missing

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

// Remove LANG environment variable
// Remove LC_ALL 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 LC_ALL value
if let Some(val) = original_lc_all {
unsafe {
env::set_var("LC_ALL", val);
}
} else {
{} // Was already unset
}
// Restore original LC_MESSAGES value
if let Some(val) = original_lc_messages {
unsafe {
env::set_var("LC_MESSAGES", val);
}
} else {
{} // Was already unset
}
// Restore original LANG value
if let Some(val) = original_lang {
unsafe {
Expand All @@ -1271,10 +1300,10 @@ invalid-syntax = This is { $missing
#[test]
fn test_setup_localization_success() {
std::thread::spawn(|| {
// Save current LANG value
let original_lang = env::var("LANG").ok();
// Save current LC_ALL value
let original_lang = env::var("LC_ALL").ok();
unsafe {
env::set_var("LANG", "en-US.UTF-8"); // Use English since we have embedded resources for "test"
env::set_var("LC_ALL", "en-US.UTF-8"); // Use English since we have embedded resources for "test"
}

let result = setup_localization("test");
Expand All @@ -1285,14 +1314,14 @@ invalid-syntax = This is { $missing
// Since we're using embedded resources, we should get the expected message
assert!(!message.is_empty());

// Restore original LANG value
// Restore original LC_ALL value
if let Some(val) = original_lang {
unsafe {
env::set_var("LANG", val);
env::set_var("LC_ALL", val);
}
} else {
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}
}
})
Expand All @@ -1303,10 +1332,10 @@ invalid-syntax = This is { $missing
#[test]
fn test_setup_localization_falls_back_to_english() {
std::thread::spawn(|| {
// Save current LANG value
let original_lang = env::var("LANG").ok();
// Save current LC_ALL value
let original_lang = env::var("LC_ALL").ok();
unsafe {
env::set_var("LANG", "de-DE.UTF-8"); // German file doesn't exist, should fallback
env::set_var("LC_ALL", "de-DE.UTF-8"); // German file doesn't exist, should fallback
}

let result = setup_localization("test");
Expand All @@ -1316,14 +1345,14 @@ invalid-syntax = This is { $missing
let message = get_message("test-about");
assert!(!message.is_empty()); // Should get something, not just the key

// Restore original LANG value
// Restore original LC_ALL value
if let Some(val) = original_lang {
unsafe {
env::set_var("LANG", val);
env::set_var("LC_ALL", val);
}
} else {
unsafe {
env::remove_var("LANG");
env::remove_var("LC_ALL");
}
}
})
Expand All @@ -1336,7 +1365,7 @@ invalid-syntax = This is { $missing
std::thread::spawn(|| {
// Force English locale for this test
unsafe {
env::set_var("LANG", "en-US");
env::set_var("LC_ALL", "en-US");
}

// Test with a utility name that has embedded locales
Expand Down
Loading
Loading