Skip to content

Commit 9676f95

Browse files
committed
fix(uucore): Use embedded locales in release mode.
- Update setup_localization() to have implementation specific to debug or release. - Update create_english_bundle_from_embedded() to not be specific to english. - Update init_localization() to have implementations specific to debug or release. - Delete get_locales_dir() release mode implementation because it was useless.
1 parent dd08296 commit 9676f95

File tree

1 file changed

+150
-140
lines changed

1 file changed

+150
-140
lines changed

src/uucore/src/lib/mods/locale.rs

Lines changed: 150 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::error::UError;
99
use fluent::{FluentArgs, FluentBundle, FluentResource};
1010
use fluent_syntax::parser::ParserError;
1111

12+
// Silence warning about unused import for std::fs
13+
#[cfg(debug_assertions)]
1214
use std::fs;
1315
use std::path::{Path, PathBuf};
1416
use 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)]
115118
fn 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)]
134138
fn 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
206174
fn 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

Comments
 (0)