@@ -9,6 +9,8 @@ use crate::error::UError;
99use fluent:: { FluentArgs , FluentBundle , FluentResource } ;
1010use fluent_syntax:: parser:: ParserError ;
1111
12+ // Silence warning about unused import for std::fs
13+ #[ cfg( debug_assertions) ]
1214use std:: fs;
1315use std:: path:: { Path , PathBuf } ;
1416use std:: str:: FromStr ;
@@ -112,6 +114,7 @@ thread_local! {
112114}
113115
114116/// Helper function to find the uucore locales directory from a utility's locales directory
117+ #[ cfg( debug_assertions) ]
115118fn find_uucore_locales_dir ( utility_locales_dir : & Path ) -> Option < PathBuf > {
116119 // Normalize the path to get absolute path
117120 let normalized_dir = utility_locales_dir
@@ -130,7 +133,8 @@ fn find_uucore_locales_dir(utility_locales_dir: &Path) -> Option<PathBuf> {
130133 uucore_locales. exists ( ) . then_some ( uucore_locales)
131134}
132135
133- /// Create a bundle that combines common and utility-specific strings
136+ /// Create a bundle that combines common and utility-specific strings for debug mode using .flt files
137+ #[ cfg( debug_assertions) ]
134138fn create_bundle (
135139 locale : & LanguageIdentifier ,
136140 locales_dir : & Path ,
@@ -166,42 +170,6 @@ fn create_bundle(
166170 }
167171}
168172
169- /// Initialize localization with common strings in addition to utility-specific strings
170- fn init_localization (
171- locale : & LanguageIdentifier ,
172- locales_dir : & Path ,
173- util_name : & str ,
174- ) -> Result < ( ) , LocalizationError > {
175- let default_locale = LanguageIdentifier :: from_str ( DEFAULT_LOCALE )
176- . expect ( "Default locale should always be valid" ) ;
177-
178- // Try to create a bundle that combines common and utility-specific strings
179- let english_bundle = create_bundle ( & default_locale, locales_dir, util_name) . or_else ( |_| {
180- // Fallback to embedded utility-specific and common strings
181- create_english_bundle_from_embedded ( & default_locale, util_name)
182- } ) ?;
183-
184- let loc = if locale == & default_locale {
185- // If requesting English, just use English as primary (no fallback needed)
186- Localizer :: new ( english_bundle)
187- } else {
188- // Try to load the requested locale with common strings
189- if let Ok ( primary_bundle) = create_bundle ( locale, locales_dir, util_name) {
190- // Successfully loaded requested locale, load English as fallback
191- Localizer :: new ( primary_bundle) . with_fallback ( english_bundle)
192- } else {
193- // Failed to load requested locale, just use English as primary
194- Localizer :: new ( english_bundle)
195- }
196- } ;
197-
198- LOCALIZER . with ( |lock| {
199- lock. set ( loc)
200- . map_err ( |_| LocalizationError :: Bundle ( "Localizer already initialized" . into ( ) ) )
201- } ) ?;
202- Ok ( ( ) )
203- }
204-
205173/// Helper function to parse FluentResource from content string
206174fn parse_fluent_resource ( content : & str ) -> Result < FluentResource , LocalizationError > {
207175 FluentResource :: try_new ( content. to_string ( ) ) . map_err (
@@ -224,29 +192,22 @@ fn parse_fluent_resource(content: &str) -> Result<FluentResource, LocalizationEr
224192 )
225193}
226194
227- /// Create a bundle from embedded English locale files with common uucore strings
228- fn create_english_bundle_from_embedded (
195+ /// Create a bundle from embedded locale files with common uucore strings
196+ fn create_bundle_from_embedded (
229197 locale : & LanguageIdentifier ,
230198 util_name : & str ,
231199) -> Result < FluentBundle < FluentResource > , LocalizationError > {
232- // Only support English from embedded files
233- if * locale != "en-US" {
234- return Err ( LocalizationError :: LocalesDirNotFound (
235- "Embedded locales only support en-US" . to_string ( ) ,
236- ) ) ;
237- }
238-
239200 let mut bundle = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
240201 bundle. set_use_isolating ( false ) ;
241202
242203 // First, try to load common uucore strings
243- if let Some ( uucore_content) = get_embedded_locale ( "uucore/en-US .ftl" ) {
204+ if let Some ( uucore_content) = get_embedded_locale ( & format ! ( "uucore/{locale} .ftl" ) ) {
244205 let uucore_resource = parse_fluent_resource ( uucore_content) ?;
245206 bundle. add_resource_overriding ( uucore_resource) ;
246207 }
247208
248209 // Then, try to load utility-specific strings
249- let locale_key = format ! ( "{util_name}/en-US .ftl" ) ;
210+ let locale_key = format ! ( "{util_name}/{locale} .ftl" ) ;
250211 if let Some ( ftl_content) = get_embedded_locale ( & locale_key) {
251212 let resource = parse_fluent_resource ( ftl_content) ?;
252213 bundle. add_resource_overriding ( resource) ;
@@ -262,13 +223,97 @@ fn create_english_bundle_from_embedded(
262223 }
263224}
264225
265- fn get_message_internal ( id : & str , args : Option < FluentArgs > ) -> String {
266- LOCALIZER . with ( |lock| {
267- lock. get ( )
268- . map_or_else ( || id. to_string ( ) , |loc| loc. format ( id, args. as_ref ( ) ) ) // Return the key ID if localizer not initialized
226+ /// Create the fallback bundle from .ftl files
227+ #[ cfg( debug_assertions) ]
228+ fn create_fallback_bundle (
229+ locales_dir : & Path ,
230+ util_name : & str ,
231+ ) -> Result < FluentBundle < FluentResource > , LocalizationError > {
232+ let default_locale = LanguageIdentifier :: from_str ( DEFAULT_LOCALE )
233+ . expect ( "Default locale should always be valid" ) ;
234+
235+ create_bundle ( & default_locale, locales_dir, util_name) . or_else ( |_| {
236+ // Fallback to embedded utility-specific and common strings
237+ create_embedded_fallback_bundle ( util_name)
269238 } )
270239}
271240
241+ /// Create fallback bundle from embedded locale files
242+ fn create_embedded_fallback_bundle (
243+ util_name : & str ,
244+ ) -> Result < FluentBundle < FluentResource > , LocalizationError > {
245+ let default_locale = LanguageIdentifier :: from_str ( DEFAULT_LOCALE )
246+ . expect ( "Default locale should always be valid" ) ;
247+
248+ create_bundle_from_embedded ( & default_locale, util_name)
249+ }
250+
251+ /// Wrapper for debug mode
252+ #[ cfg( debug_assertions) ]
253+ fn init_localization (
254+ locale : & LanguageIdentifier ,
255+ locales_dir : & Path ,
256+ util_name : & str ,
257+ ) -> Result < ( ) , LocalizationError > {
258+ init_localization_impl ( locale, Some ( locales_dir) , util_name)
259+ }
260+ /// Wrapper for release mode
261+ #[ cfg( not( debug_assertions) ) ]
262+ fn init_localization (
263+ locale : & LanguageIdentifier ,
264+ util_name : & str ,
265+ ) -> Result < ( ) , LocalizationError > {
266+ init_localization_impl ( locale, None , util_name)
267+ }
268+
269+ /// Initialize localization with common strings in addition to utility-specific strings
270+ fn init_localization_impl (
271+ locale : & LanguageIdentifier ,
272+ locales_dir : Option < & Path > ,
273+ util_name : & str ,
274+ ) -> Result < ( ) , LocalizationError > {
275+ // Set locales_dir variable accordingly to the compilation profile
276+ #[ cfg( debug_assertions) ]
277+ let locales_dir = locales_dir. expect ( "In debug mode, 'locales_dir' should never be None." ) ;
278+ #[ cfg( not( debug_assertions) ) ]
279+ let _ = locales_dir;
280+
281+ let default_locale = LanguageIdentifier :: from_str ( DEFAULT_LOCALE )
282+ . expect ( "Default locale should always be valid" ) ;
283+
284+ // Try to create a bundle that combines common and utility-specific strings
285+ // On release mode, use the embedded mode for better performance
286+ #[ cfg( debug_assertions) ]
287+ let english_bundle = create_fallback_bundle ( locales_dir, util_name) ?;
288+ #[ cfg( not( debug_assertions) ) ]
289+ let english_bundle = create_embedded_fallback_bundle ( util_name) ?;
290+
291+ let loc = if locale == & default_locale {
292+ // If requesting English, just use English as primary (no fallback needed)
293+ Localizer :: new ( english_bundle)
294+ } else {
295+ #[ cfg( debug_assertions) ]
296+ let bundle = create_bundle ( locale, locales_dir, util_name) ;
297+ #[ cfg( not( debug_assertions) ) ]
298+ let bundle = create_bundle_from_embedded ( locale, util_name) ;
299+
300+ // Try to load the requested locale with common strings
301+ if let Ok ( primary_bundle) = bundle {
302+ // Successfully loaded requested locale, load English as fallback
303+ Localizer :: new ( primary_bundle) . with_fallback ( english_bundle)
304+ } else {
305+ // Failed to load requested locale, just use English as primary
306+ Localizer :: new ( english_bundle)
307+ }
308+ } ;
309+
310+ LOCALIZER . with ( |lock| {
311+ lock. set ( loc)
312+ . map_err ( |_| LocalizationError :: Bundle ( "Localizer already initialized" . into ( ) ) )
313+ } ) ?;
314+ Ok ( ( ) )
315+ }
316+
272317/// Retrieves a localized message by its identifier.
273318///
274319/// Looks up a message with the given ID in the current locale bundle and returns
@@ -297,6 +342,13 @@ pub fn get_message(id: &str) -> String {
297342 get_message_internal ( id, None )
298343}
299344
345+ fn get_message_internal ( id : & str , args : Option < FluentArgs > ) -> String {
346+ LOCALIZER . with ( |lock| {
347+ lock. get ( )
348+ . map_or_else ( || id. to_string ( ) , |loc| loc. format ( id, args. as_ref ( ) ) ) // Return the key ID if localizer not initialized
349+ } )
350+ }
351+
300352/// Retrieves a localized message with variable substitution.
301353///
302354/// Looks up a message with the given ID in the current locale bundle,
@@ -350,7 +402,7 @@ fn detect_system_locale() -> Result<LanguageIdentifier, LocalizationError> {
350402///
351403/// This function initializes the localization system based on the system's locale
352404/// preferences (via the LANG environment variable) or falls back to English
353- /// if the system locale cannot be determined or the locale file doesn't exist.
405+ /// if the system locale cannot be determined or the locale file/embedding doesn't exist.
354406/// English is always loaded as a fallback.
355407///
356408/// # Arguments
@@ -387,103 +439,61 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> {
387439 LanguageIdentifier :: from_str ( DEFAULT_LOCALE ) . expect ( "Default locale should always be valid" )
388440 } ) ;
389441
390- // Load common strings along with utility-specific strings
391- match get_locales_dir ( p) {
392- Ok ( locales_dir) => {
393- // Load both utility-specific and common strings
394- init_localization ( & locale, & locales_dir, p)
395- }
396- Err ( _) => {
397- // No locales directory found, use embedded English with common strings directly
398- let default_locale = LanguageIdentifier :: from_str ( DEFAULT_LOCALE )
399- . expect ( "Default locale should always be valid" ) ;
400- let english_bundle = create_english_bundle_from_embedded ( & default_locale, p) ?;
401- let localizer = Localizer :: new ( english_bundle) ;
402-
403- LOCALIZER . with ( |lock| {
404- lock. set ( localizer)
405- . map_err ( |_| LocalizationError :: Bundle ( "Localizer already initialized" . into ( ) ) )
406- } ) ?;
407- Ok ( ( ) )
408- }
409- }
410- }
411-
412- #[ cfg( not( debug_assertions) ) ]
413- fn resolve_locales_dir_from_exe_dir ( exe_dir : & Path , p : & str ) -> Option < PathBuf > {
414- // 1. <bindir>/locales/<prog>
415- let coreutils = exe_dir. join ( "locales" ) . join ( p) ;
416- if coreutils. exists ( ) {
417- return Some ( coreutils) ;
418- }
419-
420- // 2. <prefix>/share/locales/<prog>
421- if let Some ( prefix) = exe_dir. parent ( ) {
422- let fhs = prefix. join ( "share" ) . join ( "locales" ) . join ( p) ;
423- if fhs. exists ( ) {
424- return Some ( fhs) ;
425- }
426- }
427-
428- // 3. <bindir>/<prog> (legacy fall-back)
429- let fallback = exe_dir. join ( p) ;
430- if fallback. exists ( ) {
431- return Some ( fallback) ;
432- }
433-
434- None
435- }
436-
437- /// Helper function to get the locales directory based on the build configuration
438- fn get_locales_dir ( p : & str ) -> Result < PathBuf , LocalizationError > {
442+ // Load common strings along with utility-specific
443+ // We only need the locale directory for debug mode. In release mode, all locale text is embedded into the file.
439444 #[ cfg( debug_assertions) ]
440445 {
441- // During development, use the project's locales directory
442- let manifest_dir = env ! ( "CARGO_MANIFEST_DIR" ) ;
443- // from uucore path, load the locales directory from the program directory
444- let dev_path = PathBuf :: from ( manifest_dir)
445- . join ( "../uu" )
446- . join ( p)
447- . join ( "locales" ) ;
448-
449- if dev_path. exists ( ) {
450- return Ok ( dev_path) ;
451- }
452-
453- // Fallback for development if the expected path doesn't exist
454- let fallback_dev_path = PathBuf :: from ( manifest_dir) . join ( p) ;
455- if fallback_dev_path. exists ( ) {
456- return Ok ( fallback_dev_path) ;
446+ match get_locales_dir ( p) {
447+ Ok ( locales_dir) => {
448+ // Load both utility-specific and common strings
449+ init_localization ( & locale, & locales_dir, p)
450+ }
451+ Err ( _) => {
452+ // No locales directory found, use embedded English with common strings directly
453+ let english_bundle = create_embedded_fallback_bundle ( p) ?;
454+ let localizer = Localizer :: new ( english_bundle) ;
455+
456+ LOCALIZER . with ( |lock| {
457+ lock. set ( localizer) . map_err ( |_| {
458+ LocalizationError :: Bundle ( "Localizer already initialized" . into ( ) )
459+ } )
460+ } ) ?;
461+ Ok ( ( ) )
462+ }
457463 }
458-
459- Err ( LocalizationError :: LocalesDirNotFound ( format ! (
460- "Development locales directory not found at {} or {}" ,
461- dev_path. quote( ) ,
462- fallback_dev_path. quote( )
463- ) ) )
464464 }
465-
466465 #[ cfg( not( debug_assertions) ) ]
467466 {
468- use std:: env;
469- // In release builds, look relative to executable
470- let exe_path = env:: current_exe ( ) . map_err ( |e| {
471- LocalizationError :: PathResolution ( format ! ( "Failed to get executable path: {e}" ) )
472- } ) ?;
467+ init_localization ( & locale, p)
468+ }
469+ }
473470
474- let exe_dir = exe_path. parent ( ) . ok_or_else ( || {
475- LocalizationError :: PathResolution ( "Failed to get executable directory" . to_string ( ) )
476- } ) ?;
471+ /// Helper function to get the locales directory for .ftl files
472+ #[ cfg( debug_assertions) ]
473+ fn get_locales_dir ( p : & str ) -> Result < PathBuf , LocalizationError > {
474+ // During development, use the project's locales directory
475+ let manifest_dir = env ! ( "CARGO_MANIFEST_DIR" ) ;
476+ // from uucore path, load the locales directory from the program directory
477+ let dev_path = PathBuf :: from ( manifest_dir)
478+ . join ( "../uu" )
479+ . join ( p)
480+ . join ( "locales" ) ;
477481
478- if let Some ( dir ) = resolve_locales_dir_from_exe_dir ( exe_dir , p ) {
479- return Ok ( dir ) ;
480- }
482+ if dev_path . exists ( ) {
483+ return Ok ( dev_path ) ;
484+ }
481485
482- Err ( LocalizationError :: LocalesDirNotFound ( format ! (
483- "Release locales directory not found starting from {}" ,
484- exe_dir . quote ( )
485- ) ) )
486+ // Fallback for development if the expected path doesn't exist
487+ let fallback_dev_path = PathBuf :: from ( manifest_dir ) . join ( p ) ;
488+ if fallback_dev_path . exists ( ) {
489+ return Ok ( fallback_dev_path ) ;
486490 }
491+
492+ Err ( LocalizationError :: LocalesDirNotFound ( format ! (
493+ "Development locales directory not found at {} or {}" ,
494+ dev_path. quote( ) ,
495+ fallback_dev_path. quote( )
496+ ) ) )
487497}
488498
489499/// Macro for retrieving localized messages with optional arguments.
0 commit comments