diff --git a/crates/ui-egui/src/i18n/mod.rs b/crates/ui-egui/src/i18n/mod.rs index e2d8139d6..9698fb82a 100644 --- a/crates/ui-egui/src/i18n/mod.rs +++ b/crates/ui-egui/src/i18n/mod.rs @@ -457,19 +457,33 @@ fn detect_system_lang() -> Lang { Lang::EN } -/// Index into [`LANGUAGES`] of the language the UI is drawn in this frame. -static CURRENT: AtomicUsize = AtomicUsize::new(0); +/// Index into [`LANGUAGES`] of the language the UI is drawn in this frame. The app keeps one +/// index for the whole process. Unit tests keep one per thread, as `ui_mirror!` keeps the +/// shortcut and workspace mirrors, so a language one test sets does not change the language of +/// the tests running beside it on other threads. +fn current_index() -> &'static AtomicUsize { + #[cfg(not(test))] + { + static CURRENT: AtomicUsize = AtomicUsize::new(0); + &CURRENT + } + #[cfg(test)] + { + thread_local!(static CURRENT: &'static AtomicUsize = Box::leak(Box::default())); + CURRENT.with(|c| *c) + } +} /// Set the UI language for drawing (the shell calls this once per frame from the preference), so /// widgets can translate without every call site carrying a language around. pub fn set_current(lang: Lang) { let i = LANGUAGES.iter().position(|l| l.code == lang.code()).unwrap_or(0); - CURRENT.store(i, Ordering::Relaxed); + current_index().store(i, Ordering::Relaxed); } /// The language the UI is drawn in. pub fn current() -> Lang { - Lang(LANGUAGES.get(CURRENT.load(Ordering::Relaxed)).unwrap_or(&LANGUAGES[0])) + Lang(LANGUAGES.get(current_index().load(Ordering::Relaxed)).unwrap_or(&LANGUAGES[0])) } /// Does `lang` have a catalog entry for this plain string? (English never does: it is the source.) diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index f5afab545..3ab33db42 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -100,6 +100,15 @@ fn current_language_is_a_frame_setting() { assert_eq!(t("Layers"), "Layers"); } +/// Unit tests run on parallel threads, and each one draws in its own language: a language set on +/// another thread (by a test running beside this one) leaves this thread's text in English. +#[test] +fn a_language_set_by_another_test_leaves_this_one_in_english() { + std::thread::spawn(|| set_current(ZH())).join().unwrap(); + assert_eq!(current(), Lang::EN); + assert_eq!(t("Layers"), "Layers"); +} + #[test] fn lookups_fall_back_to_english() { assert_eq!(tr(Lang::EN, "Layers"), "Layers"); @@ -197,8 +206,7 @@ fn partial_catalogs_only_translate_strings_the_ui_shows() { } /// The engine learns the language the UI is drawn in each frame: new type takes the Japanese -/// defaults while it is Japanese (#432). The frames stay in English: the drawing language is -/// process-wide, and a Japanese frame would translate the tests running beside this one. +/// defaults while it is Japanese (#432). The frames stay in English. #[test] fn the_engine_follows_the_interface_language() { let mut app = crate::VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());