Merge pull request #8423 from sylvestre/en-embedded

l10n: embedded english strings + test the whole thing in github
This commit is contained in:
Daniel Hofstetter
2025-08-03 15:08:10 +02:00
committed by GitHub
10 changed files with 1496 additions and 78 deletions
File diff suppressed because it is too large Load Diff
Generated
+1
View File
@@ -4083,6 +4083,7 @@ dependencies = [
"dns-lookup",
"dunce",
"fluent",
"fluent-bundle",
"fluent-syntax",
"glob",
"hex",
+1
View File
@@ -378,6 +378,7 @@ digest = "0.10.7"
# Fluent dependencies
fluent = "0.17.0"
fluent-bundle = "0.16.0"
unic-langid = "0.9.6"
fluent-syntax = "0.12.0"
+18 -14
View File
@@ -418,25 +418,29 @@ endif
ifeq ($(LOCALES),y)
locales:
$(foreach prog, $(INSTALLEES), \
if [ -d "$(BASEDIR)/src/uu/$(prog)/locales" ]; then \
mkdir -p "$(BUILDDIR)/locales/$(prog)"; \
for locale_file in "$(BASEDIR)"/src/uu/$(prog)/locales/*.ftl; do \
$(INSTALL) -v "$$locale_file" "$(BUILDDIR)/locales/$(prog)/"; \
@for prog in $(INSTALLEES); do \
if [ -d "$(BASEDIR)/src/uu/$$prog/locales" ]; then \
mkdir -p "$(BUILDDIR)/locales/$$prog"; \
for locale_file in "$(BASEDIR)"/src/uu/$$prog/locales/*.ftl; do \
if [ "$$(basename "$$locale_file")" != "en-US.ftl" ]; then \
$(INSTALL) -v "$$locale_file" "$(BUILDDIR)/locales/$$prog/"; \
fi; \
done; \
fi $(newline) \
)
fi; \
done
install-locales:
$(foreach prog, $(INSTALLEES), \
if [ -d "$(BASEDIR)/src/uu/$(prog)/locales" ]; then \
mkdir -p "$(DESTDIR)$(DATAROOTDIR)/locales/$(prog)"; \
for locale_file in "$(BASEDIR)"/src/uu/$(prog)/locales/*.ftl; do \
$(INSTALL) -v "$$locale_file" "$(DESTDIR)$(DATAROOTDIR)/locales/$(prog)/"; \
@for prog in $(INSTALLEES); do \
if [ -d "$(BASEDIR)/src/uu/$$prog/locales" ]; then \
mkdir -p "$(DESTDIR)$(DATAROOTDIR)/locales/$$prog"; \
for locale_file in "$(BASEDIR)"/src/uu/$$prog/locales/*.ftl; do \
if [ "$$(basename "$$locale_file")" != "en-US.ftl" ]; then \
$(INSTALL) -v "$$locale_file" "$(DESTDIR)$(DATAROOTDIR)/locales/$$prog/"; \
fi; \
done; \
fi $(newline) \
)
fi; \
done
else
install-locales:
endif
+29 -5
View File
@@ -2,6 +2,15 @@
This guide explains how localization (L10n) is implemented in the **Rust-based coreutils project**, detailing the use of [Fluent](https://projectfluent.org/) files, runtime behavior, and developer integration.
## 🏗️ Architecture Overview
**English (US) locale files (`en-US.ftl`) are embedded directly in the binary**, ensuring that English always works regardless of how the software is installed. Other language locale files are loaded from the filesystem at runtime.
### Source Repository Structure
- **Main repository**: Contains English (`en-US.ftl`) locale files embedded in binaries
- **Translation repository**: [uutils/coreutils-l10n](https://github.com/uutils/coreutils-l10n) contains all other language translations
---
## 📁 Fluent File Layout
@@ -15,8 +24,8 @@ Each utility has its own set of translation files under:
Examples:
```
src/uu/ls/locales/en-US.ftl
src/uu/ls/locales/fr-FR.ftl
src/uu/ls/locales/en-US.ftl # Embedded in binary
src/uu/ls/locales/fr-FR.ftl # Loaded from filesystem
```
These files follow Fluent syntax and contain localized message patterns.
@@ -31,12 +40,11 @@ Localization must be explicitly initialized at runtime using:
setup_localization(path)
```
This is typically done:
- In `src/bin/coreutils.rs` for **multi-call binaries**
- In `src/uucore/src/lib.rs` for **single-call utilities**
The string parameter determines the lookup path for Fluent files.
The string parameter determines the lookup path for Fluent files. **English always works** because it's embedded, but other languages need their `.ftl` files to be available at runtime.
---
@@ -155,9 +163,13 @@ In release mode, **paths are resolved relative to the executable**:
```
<executable_dir>/locales/<utility>/
<prefix>/share/locales/<utility>/
~/.local/share/coreutils/locales/<utility>/
~/.cargo/share/coreutils/locales/<utility>/
/usr/share/coreutils/locales/<utility>/
```
If both fallback paths fail, an error is returned during `setup_localization()`.
If external locale files aren't found, the system falls back to embedded English locales.
---
@@ -184,3 +196,15 @@ Fluent default (disabled here):
```
"\u{2068}Alice\u{2069}"
```
---
## 🔧 Embedded English Locales
English locale files are always embedded directly in the binary during the build process. This ensures that:
- **English always works** regardless of installation method (e.g., `cargo install`)
- **No runtime dependency** on external `.ftl` files for English
- **Fallback behavior** when other language files are missing
The embedded English locales are generated at build time and included in the binary, providing a reliable fallback while still supporting full localization for other languages when their `.ftl` files are available.
+1
View File
@@ -1606,6 +1606,7 @@ dependencies = [
"digest",
"dunce",
"fluent",
"fluent-bundle",
"fluent-syntax",
"glob",
"hex",
+2 -1
View File
@@ -71,10 +71,11 @@ icu_decimal = { workspace = true, optional = true, features = [
icu_locale = { workspace = true, optional = true, features = ["compiled_data"] }
icu_provider = { workspace = true, optional = true }
# Fluent dependencies
# Fluent dependencies (always available for localization)
fluent = { workspace = true }
fluent-syntax = { workspace = true }
unic-langid = { workspace = true }
fluent-bundle = { workspace = true }
thiserror = { workspace = true }
[target.'cfg(unix)'.dependencies]
walkdir = { workspace = true, optional = true }
+204
View File
@@ -0,0 +1,204 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
let out_dir = env::var("OUT_DIR")?;
let mut embedded_file = File::create(Path::new(&out_dir).join("embedded_locales.rs"))?;
writeln!(embedded_file, "// Generated at compile time - do not edit")?;
writeln!(
embedded_file,
"// This file contains embedded English locale files"
)?;
writeln!(embedded_file)?;
writeln!(embedded_file, "use std::collections::HashMap;")?;
writeln!(embedded_file)?;
// Start the function that returns embedded locales
writeln!(
embedded_file,
"pub fn get_embedded_locales() -> HashMap<&'static str, &'static str> {{"
)?;
writeln!(embedded_file, " let mut locales = HashMap::new();")?;
writeln!(embedded_file)?;
// Try to detect if we're building for a specific utility by checking build configuration
// This attempts to identify individual utility builds vs multicall binary builds
let target_utility = detect_target_utility();
match target_utility {
Some(util_name) => {
// Embed only the specific utility's locale (cat.ftl for cat for example)
embed_single_utility_locale(&mut embedded_file, &project_root()?, &util_name)?;
}
None => {
// Embed all utilities locales (multicall binary or fallback)
embed_all_utilities_locales(&mut embedded_file, &project_root()?)?;
}
}
writeln!(embedded_file)?;
writeln!(embedded_file, " locales")?;
writeln!(embedded_file, "}}")?;
embedded_file.flush()?;
Ok(())
}
/// Get the project root directory
fn project_root() -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let manifest_dir = env::var("CARGO_MANIFEST_DIR")?;
let uucore_path = std::path::Path::new(&manifest_dir);
// Navigate from src/uucore to project root
let project_root = uucore_path
.parent() // src/
.and_then(|p| p.parent()) // project root
.ok_or("Could not determine project root")?;
Ok(project_root.to_path_buf())
}
/// Attempt to detect which specific utility is being built
fn detect_target_utility() -> Option<String> {
use std::fs;
// First check if an explicit environment variable was set
if let Ok(target_util) = env::var("UUCORE_TARGET_UTIL") {
if !target_util.is_empty() {
return Some(target_util);
}
}
// Check for a build configuration file in the target directory
if let Ok(target_dir) = env::var("CARGO_TARGET_DIR") {
let config_path = std::path::Path::new(&target_dir).join("uucore_target_util.txt");
if let Ok(content) = fs::read_to_string(&config_path) {
let util_name = content.trim();
if !util_name.is_empty() && util_name != "multicall" {
return Some(util_name.to_string());
}
}
}
// Fallback: Check the default target directory
if let Ok(project_root) = project_root() {
let config_path = project_root.join("target/uucore_target_util.txt");
if let Ok(content) = fs::read_to_string(&config_path) {
let util_name = content.trim();
if !util_name.is_empty() && util_name != "multicall" {
return Some(util_name.to_string());
}
}
}
// If no configuration found, assume multicall build
None
}
/// Embed locale for a single specific utility
fn embed_single_utility_locale(
embedded_file: &mut std::fs::File,
project_root: &Path,
util_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
use std::fs;
// Embed the specific utility's locale
let locale_path = project_root
.join("src/uu")
.join(util_name)
.join("locales/en-US.ftl");
if locale_path.exists() {
let content = fs::read_to_string(&locale_path)?;
writeln!(embedded_file, " // Locale for {util_name}")?;
writeln!(
embedded_file,
" locales.insert(\"{util_name}/en-US.ftl\", r###\"{content}\"###);"
)?;
writeln!(embedded_file)?;
// Tell Cargo to rerun if this file changes
println!("cargo:rerun-if-changed={}", locale_path.display());
}
// Always embed uucore locale file if it exists
let uucore_locale_path = project_root.join("src/uucore/locales/en-US.ftl");
if uucore_locale_path.exists() {
let content = fs::read_to_string(&uucore_locale_path)?;
writeln!(embedded_file, " // Common uucore locale")?;
writeln!(
embedded_file,
" locales.insert(\"uucore/en-US.ftl\", r###\"{content}\"###);"
)?;
println!("cargo:rerun-if-changed={}", uucore_locale_path.display());
}
Ok(())
}
/// Embed locale files for all utilities (multicall binary)
fn embed_all_utilities_locales(
embedded_file: &mut std::fs::File,
project_root: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
use std::fs;
// Discover all uu_* directories
let src_uu_dir = project_root.join("src/uu");
if !src_uu_dir.exists() {
return Ok(());
}
let mut util_dirs = Vec::new();
for entry in fs::read_dir(&src_uu_dir)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
if let Some(dir_name) = entry.file_name().to_str() {
util_dirs.push(dir_name.to_string());
}
}
}
util_dirs.sort();
// Embed locale files for each utility
for util_name in &util_dirs {
let locale_path = src_uu_dir.join(util_name).join("locales/en-US.ftl");
if locale_path.exists() {
let content = fs::read_to_string(&locale_path)?;
writeln!(embedded_file, " // Locale for {util_name}")?;
writeln!(
embedded_file,
" locales.insert(\"{util_name}/en-US.ftl\", r###\"{content}\"###);"
)?;
writeln!(embedded_file)?;
// Tell Cargo to rerun if this file changes
println!("cargo:rerun-if-changed={}", locale_path.display());
}
}
// Also embed uucore locale file if it exists
let uucore_locale_path = project_root.join("src/uucore/locales/en-US.ftl");
if uucore_locale_path.exists() {
let content = fs::read_to_string(&uucore_locale_path)?;
writeln!(embedded_file, " // Common uucore locale")?;
writeln!(
embedded_file,
" locales.insert(\"uucore/en-US.ftl\", r###\"{content}\"###);"
)?;
println!("cargo:rerun-if-changed={}", uucore_locale_path.display());
}
embedded_file.flush()?;
Ok(())
}
+1 -1
View File
@@ -185,7 +185,7 @@ macro_rules! bin {
uucore::locale::LocalizationError::ParseResource {
error: err_msg,
snippet,
} => eprintln!("Localization parse error at {snippet}: {err_msg}"),
} => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
other => eprintln!("Could not init the localization system: {other}"),
}
std::process::exit(99)
+141 -57
View File
@@ -55,6 +55,9 @@ impl UError for LocalizationError {
pub const DEFAULT_LOCALE: &str = "en-US";
// Include embedded locale files as fallback
include!(concat!(env!("OUT_DIR"), "/embedded_locales.rs"));
// A struct to handle localization with optional English fallback
struct Localizer {
primary_bundle: FluentBundle<FluentResource>,
@@ -108,12 +111,22 @@ thread_local! {
fn init_localization(
locale: &LanguageIdentifier,
locales_dir: &Path,
util_name: &str,
) -> Result<(), LocalizationError> {
let en_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE)
let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE)
.expect("Default locale should always be valid");
let english_bundle = create_bundle(&en_locale, locales_dir)?;
let loc = if locale == &en_locale {
// Try to load English from embedded resources first, then fall back to filesystem.
// This ensures consistent behavior and faster loading since embedded resources
// are immediately available. The filesystem fallback allows for development
// and testing scenarios where locale files might be present in the filesystem.
let english_bundle =
create_english_bundle_from_embedded(&default_locale, util_name).or_else(|_| {
// Try filesystem as fallback (useful for development/testing)
create_bundle(&default_locale, locales_dir)
})?;
let loc = if locale == &default_locale {
// If requesting English, just use English as primary (no fallback needed)
Localizer::new(english_bundle)
} else {
@@ -180,6 +193,56 @@ fn create_bundle(
Ok(bundle)
}
/// Create a bundle from embedded English locale files
fn create_english_bundle_from_embedded(
locale: &LanguageIdentifier,
util_name: &str,
) -> Result<FluentBundle<FluentResource>, LocalizationError> {
// Only support English from embedded files
if *locale != "en-US" {
return Err(LocalizationError::LocalesDirNotFound(
"Embedded locales only support en-US".to_string(),
));
}
let embedded_locales = get_embedded_locales();
let locale_key = format!("{util_name}/en-US.ftl");
let ftl_content = embedded_locales.get(locale_key.as_str()).ok_or_else(|| {
LocalizationError::LocalesDirNotFound(format!("No embedded locale found for {util_name}"))
})?;
let resource = FluentResource::try_new(ftl_content.to_string()).map_err(
|(_partial_resource, errs): (FluentResource, Vec<ParserError>)| {
if let Some(first_err) = errs.into_iter().next() {
let snippet = first_err
.slice
.clone()
.and_then(|range| ftl_content.get(range))
.unwrap_or("")
.to_string();
LocalizationError::ParseResource {
error: first_err,
snippet,
}
} else {
LocalizationError::LocalesDirNotFound("Parse error without details".to_string())
}
},
)?;
let mut bundle = FluentBundle::new(vec![locale.clone()]);
bundle.set_use_isolating(false);
bundle.add_resource(resource).map_err(|errs| {
LocalizationError::Bundle(format!(
"Failed to add embedded resource to bundle for {locale}: {errs:?}",
))
})?;
Ok(bundle)
}
fn get_message_internal(id: &str, args: Option<FluentArgs>) -> String {
LOCALIZER.with(|lock| {
lock.get()
@@ -305,8 +368,25 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> {
LanguageIdentifier::from_str(DEFAULT_LOCALE).expect("Default locale should always be valid")
});
let locales_dir = get_locales_dir(p)?;
init_localization(&locale, &locales_dir)
// Try to find the locales directory. If found, use init_localization which
// will prioritize embedded resources but can also load from filesystem.
// If no locales directory exists, directly use embedded English resources.
match get_locales_dir(p) {
Ok(locales_dir) => init_localization(&locale, &locales_dir, p),
Err(_) => {
// No locales directory found, use embedded English directly
let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE)
.expect("Default locale should always be valid");
let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?;
let localizer = Localizer::new(english_bundle);
LOCALIZER.with(|lock| {
lock.set(localizer)
.map_err(|_| LocalizationError::Bundle("Localizer already initialized".into()))
})?;
Ok(())
}
}
}
#[cfg(not(debug_assertions))]
@@ -603,6 +683,7 @@ invalid-syntax = This is { $missing
#[test]
fn test_localizer_format_with_args() {
use fluent::FluentArgs;
let temp_dir = create_test_locales_dir();
let en_bundle = create_bundle(
&LanguageIdentifier::from_str("en-US").unwrap(),
@@ -664,7 +745,10 @@ invalid-syntax = This is { $missing
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("en-US").unwrap();
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
if let Err(e) = &result {
eprintln!("Init localization failed: {}", e);
}
assert!(result.is_ok());
// Test that we can get messages
@@ -681,7 +765,7 @@ invalid-syntax = This is { $missing
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("fr-FR").unwrap();
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
assert!(result.is_ok());
// Test French message
@@ -702,7 +786,7 @@ invalid-syntax = This is { $missing
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("de-DE").unwrap(); // No German file
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
assert!(result.is_ok());
// Should use English as primary since German failed to load
@@ -720,11 +804,11 @@ invalid-syntax = This is { $missing
let locale = LanguageIdentifier::from_str("en-US").unwrap();
// Initialize once
let result1 = init_localization(&locale, temp_dir.path());
let result1 = init_localization(&locale, temp_dir.path(), "test");
assert!(result1.is_ok());
// Try to initialize again - should fail
let result2 = init_localization(&locale, temp_dir.path());
let result2 = init_localization(&locale, temp_dir.path(), "test");
assert!(result2.is_err());
match result2 {
@@ -744,7 +828,7 @@ invalid-syntax = This is { $missing
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("fr-FR").unwrap();
init_localization(&locale, temp_dir.path()).unwrap();
init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap();
let message = get_message("greeting");
assert_eq!(message, "Bonjour, le monde!");
@@ -765,11 +849,12 @@ invalid-syntax = This is { $missing
#[test]
fn test_get_message_with_args() {
use fluent::FluentArgs;
std::thread::spawn(|| {
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("en-US").unwrap();
init_localization(&locale, temp_dir.path()).unwrap();
init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap();
let mut args = FluentArgs::new();
args.set("name".to_string(), "Bob".to_string());
@@ -783,11 +868,12 @@ invalid-syntax = This is { $missing
#[test]
fn test_get_message_with_args_pluralization() {
use fluent::FluentArgs;
std::thread::spawn(|| {
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("en-US").unwrap();
init_localization(&locale, temp_dir.path()).unwrap();
init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap();
// Test singular
let mut args1 = FluentArgs::new();
@@ -804,37 +890,26 @@ invalid-syntax = This is { $missing
.join()
.unwrap();
}
#[test]
fn test_detect_system_locale_from_lang_env() {
// Save current LANG value
let original_lang = env::var("LANG").ok();
// Test locale parsing logic directly instead of relying on environment variables
// which can have race conditions in multi-threaded test environments
// Test with a valid locale
unsafe {
env::set_var("LANG", "fr-FR.UTF-8");
}
let result = detect_system_locale();
assert!(result.is_ok());
assert_eq!(result.unwrap(), "fr-FR");
// Test parsing logic with UTF-8 encoding
let locale_with_encoding = "fr-FR.UTF-8";
let parsed = locale_with_encoding.split('.').next().unwrap();
let lang_id = LanguageIdentifier::from_str(parsed).unwrap();
assert_eq!(lang_id.to_string(), "fr-FR");
// Test with locale without encoding
unsafe {
env::set_var("LANG", "es-ES");
}
let result = detect_system_locale();
assert!(result.is_ok());
assert_eq!(result.unwrap(), "es-ES");
// Test parsing logic without encoding
let locale_without_encoding = "es-ES";
let lang_id = LanguageIdentifier::from_str(locale_without_encoding).unwrap();
assert_eq!(lang_id.to_string(), "es-ES");
// Restore original LANG value
if let Some(val) = original_lang {
unsafe {
env::set_var("LANG", val);
}
} else {
unsafe {
env::remove_var("LANG");
}
}
// Test that DEFAULT_LOCALE is valid
let default_lang_id = LanguageIdentifier::from_str(DEFAULT_LOCALE).unwrap();
assert_eq!(default_lang_id.to_string(), "en-US");
}
#[test]
@@ -928,19 +1003,24 @@ invalid-syntax = This is { $missing
}
#[test]
fn test_setup_localization_missing_english_file() {
fn test_setup_localization_fallback_to_embedded() {
std::thread::spawn(|| {
let temp_dir = TempDir::new().unwrap(); // Empty directory
let result = setup_localization(temp_dir.path().to_str().unwrap());
assert!(result.is_err());
match result {
Err(LocalizationError::Io { source: _, path }) => {
assert!(path.to_string_lossy().contains("en-US.ftl"));
}
_ => panic!("Expected IO error for missing English file"),
// Force English locale for this test
unsafe {
std::env::set_var("LANG", "en-US");
}
// Test with a utility name that has embedded locales
// This should fall back to embedded English when filesystem files aren't found
let result = setup_localization("test");
if let Err(e) = &result {
eprintln!("Setup localization failed: {e}");
}
assert!(result.is_ok());
// Verify we can get messages (using embedded English)
let message = get_message("test-about");
assert_eq!(message, "Check file types and compare values."); // Should use embedded English
})
.join()
.unwrap();
@@ -956,7 +1036,7 @@ invalid-syntax = This is { $missing
let temp_path_main = temp_dir.path().to_path_buf();
let main_handle = thread::spawn(move || {
let locale = LanguageIdentifier::from_str("fr-FR").unwrap();
init_localization(&locale, &temp_path_main).unwrap();
init_localization(&locale, &temp_path_main, "nonexistent_test_util").unwrap();
let main_message = get_message("greeting");
assert_eq!(main_message, "Bonjour, le monde!");
});
@@ -971,7 +1051,7 @@ invalid-syntax = This is { $missing
// Initialize in this thread with English
let en_locale = LanguageIdentifier::from_str("en-US").unwrap();
init_localization(&en_locale, &temp_path).unwrap();
init_localization(&en_locale, &temp_path, "nonexistent_test_util").unwrap();
let thread_message_after_init = get_message("greeting");
assert_eq!(thread_message_after_init, "Hello, world!");
});
@@ -989,11 +1069,12 @@ invalid-syntax = This is { $missing
#[test]
fn test_japanese_localization() {
use fluent::FluentArgs;
std::thread::spawn(|| {
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("ja-JP").unwrap();
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
assert!(result.is_ok());
// Test Japanese greeting
@@ -1018,11 +1099,12 @@ invalid-syntax = This is { $missing
#[test]
fn test_arabic_localization() {
use fluent::FluentArgs;
std::thread::spawn(|| {
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("ar-SA").unwrap();
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
assert!(result.is_ok());
// Test Arabic greeting (RTL text)
@@ -1077,7 +1159,7 @@ invalid-syntax = This is { $missing
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("ar-SA").unwrap();
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
assert!(result.is_ok());
// Test Arabic greeting (RTL text)
@@ -1118,7 +1200,7 @@ invalid-syntax = This is { $missing
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("ar-SA").unwrap();
let result = init_localization(&locale, temp_dir.path());
let result = init_localization(&locale, temp_dir.path(), "nonexistent_test_util");
assert!(result.is_ok());
// Test Arabic message exists
@@ -1132,13 +1214,15 @@ invalid-syntax = This is { $missing
.join()
.unwrap();
}
#[test]
fn test_unicode_directional_isolation_disabled() {
use fluent::FluentArgs;
std::thread::spawn(|| {
let temp_dir = create_test_locales_dir();
let locale = LanguageIdentifier::from_str("ar-SA").unwrap();
init_localization(&locale, temp_dir.path()).unwrap();
init_localization(&locale, temp_dir.path(), "nonexistent_test_util").unwrap();
// Test that Latin script names are NOT isolated in RTL context
// since we disabled Unicode directional isolation