Skip to content
Merged
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
45 changes: 45 additions & 0 deletions src/uucore/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,47 @@ fn embed_locale_file(
///
/// Returns an error if `for_each_locale` fails, which typically happens if
/// reading a locale file or writing to the `embedded_file` fails.
/// Check if we are cross-compiling for WASI (build.rs runs on the host,
/// so `#[cfg(target_os = "wasi")]` does not work here).
fn is_wasi_target() -> bool {
env::var("CARGO_CFG_TARGET_OS")
.map(|os| os == "wasi")
.unwrap_or(false)
}

/// For WASI/WASM builds, embed ALL available .ftl files in a locale
/// directory so the playground can switch languages at runtime.
fn embed_all_locales_for_component<F>(
embedded_file: &mut File,
component_name: &str,
path_builder: &F,
) -> Result<(), Box<dyn std::error::Error>>
where
F: Fn(&str) -> PathBuf,
{
let en_path = path_builder("en-US");
if let Some(locale_dir) = en_path.parent() {
if locale_dir.exists() {
for entry in std::fs::read_dir(locale_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|e| e == "ftl") {
if let Some(locale) = path.file_stem().and_then(|s| s.to_str()) {
embed_locale_file(
embedded_file,
&path,
&format!("{component_name}/{locale}.ftl"),
locale,
component_name,
)?;
}
}
}
}
}
Ok(())
}

fn embed_component_locales<F>(
embedded_file: &mut File,
locales: &(String, Option<String>),
Expand All @@ -356,6 +397,10 @@ fn embed_component_locales<F>(
where
F: Fn(&str) -> PathBuf,
{
if is_wasi_target() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

as we are in the build.rs
we can't use #cfg here

return embed_all_locales_for_component(embedded_file, component_name, &path_builder);
}

for_each_locale(locales, |locale| {
let locale_path = path_builder(locale);
embed_locale_file(
Expand Down
58 changes: 54 additions & 4 deletions src/uucore/src/lib/mods/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,41 @@ fn create_english_bundle_from_embedded(
}
}

/// Create a bundle from embedded locale files for any locale on WASI.
/// Bypasses the global OnceLock cache (uses Box::leak) so it can be
/// called for multiple locales in the same process.
#[cfg(target_os = "wasi")]
fn create_wasi_bundle_from_embedded(
locale: &LanguageIdentifier,
util_name: &str,
) -> Result<FluentBundle<&'static FluentResource>, LocalizationError> {
let locale_str = locale.to_string();
let mut bundle: FluentBundle<&'static FluentResource> = FluentBundle::new(vec![locale.clone()]);
bundle.set_use_isolating(false);

let mut try_add = |key: &str| {
if let Some(content) = get_embedded_locale(key) {
if let Ok(resource) = FluentResource::try_new(content.to_string()) {
bundle.add_resource_overriding(Box::leak(Box::new(resource)));
}
}
};

try_add(&format!("uucore/{locale_str}.ftl"));
if util_name.ends_with("sum") {
try_add(&format!("checksum_common/{locale_str}.ftl"));
}
try_add(&format!("{util_name}/{locale_str}.ftl"));

if bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about")) {
Ok(bundle)
} else {
Err(LocalizationError::LocalesDirNotFound(format!(
"No embedded locale found for {util_name}/{locale_str}"
)))
}
}

fn get_message_internal(id: &str, args: Option<FluentArgs>) -> String {
LOCALIZER.with(|lock| {
lock.get()
Expand Down Expand Up @@ -446,12 +481,27 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> {
// Load both utility-specific and common strings
init_localization(&locale, &locales_dir, p)?;
} else {
// No locales directory found, use embedded English with common strings directly
// No locales directory found, use embedded locales
let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE)
.expect("Default locale should always be valid");
let english_bundle: FluentBundle<&'static FluentResource> =
create_english_bundle_from_embedded(&default_locale, p)?;
let localizer = Localizer::new(english_bundle);

#[cfg(target_os = "wasi")]
let localizer = {
let english_bundle = create_wasi_bundle_from_embedded(&default_locale, p)?;
if locale == default_locale {
Localizer::new(english_bundle)
} else if let Ok(localized) = create_wasi_bundle_from_embedded(&locale, p) {
Localizer::new(localized).with_fallback(english_bundle)
} else {
Localizer::new(english_bundle)
}
};

#[cfg(not(target_os = "wasi"))]
let localizer = {
let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?;
Localizer::new(english_bundle)
};

LOCALIZER.with(|lock| {
lock.set(localizer)
Expand Down
Loading