From 320fc5970c00ec1a9e8300d43f0756470a817a77 Mon Sep 17 00:00:00 2001 From: Simon Johnsson Date: Thu, 16 Apr 2026 14:23:51 +0200 Subject: [PATCH] locale: fix incomplete fallback bundles Fix an error where only the common strings would be used when falling back to embedded locales. This lead to an issue where the raw Fluent message keys were returned rather than actual error messages, such as `wc-error-failed-to-print-result` instead of "failed to print result". Additionally, preserve partial resources instead of discarding everything when not all messages could be parsed. --- src/uucore/src/lib/mods/locale.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 99afc8c0c8a..4e3a3ed3527 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -145,21 +145,28 @@ fn create_bundle( // Disable Unicode directional isolate characters bundle.set_use_isolating(false); - let mut try_add_resource_from = |dir_opt: Option| { + let mut try_add_resource_from = |dir_opt: Option| -> bool { if let Some(resource) = dir_opt .map(|dir| dir.join(format!("{locale}.ftl"))) .and_then(|locale_path| fs::read_to_string(locale_path).ok()) - .and_then(|ftl| FluentResource::try_new(ftl).ok()) + .map(|ftl| match FluentResource::try_new(ftl) { + Ok(resource) => resource, + // Use the partial resource which contains all successfully parsed messages + Err((partial, _)) => partial, + }) { // use Box::leak to provide 'static lifetime for shared FluentBundle between threads bundle.add_resource_overriding(Box::leak(Box::new(resource))); + true + } else { + false } }; // Load common strings from uucore locales directory try_add_resource_from(find_uucore_locales_dir(locales_dir)); // Then, try to load utility-specific strings from the utility's locale directory - try_add_resource_from(get_locales_dir(util_name).ok()); + let util_loaded = try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate if [ @@ -177,8 +184,12 @@ fn create_bundle( try_add_resource_from(get_locales_dir("checksum_common").ok()); } - // If we have at least one resource, return the bundle - if bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about")) { + // Require that the utility locale file was actually loaded. + // If only common strings were loaded (but utility strings weren't), + // return Err so init_localization can fall back to embedded locales. + if util_loaded + && (bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about"))) + { Ok(bundle) } else { Err(LocalizationError::LocalesDirNotFound(format!( @@ -297,7 +308,9 @@ fn create_english_bundle_from_embedded( bundle.add_resource_overriding(resource); } - // Return the bundle if we have either common strings or utility-specific strings + // Return the bundle if we have at least common or utility-specific strings. + // For embedded locales this is the last resort, so accept partial bundles + // rather than failing entirely. if bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about")) { Ok(bundle) } else {