From 476d22826228ce419103fe03b04764129f284cbd Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 16 Oct 2026 16:09:12 -0700 Subject: [PATCH] Tests: a language one ui-egui test sets no longer translates the tests running beside it The language the UI is drawn in was one process-wide value in unit tests too, and the tests run on parallel threads. current_language_is_a_frame_setting sets Traditional Chinese for a moment, and a test that drew text in that moment read it in Chinese. chrome::tests::the_zoom_hint_follows_animated_zoom failed that way in 29 of 500 runs together with the i18n tests. The drawing language is now one per process in the app and one per thread in unit tests, as ui_mirror! keeps the shortcut and workspace mirrors. The app keeps the same static. A new test sets the language on another thread and checks that its own thread still draws in English. --- crates/ui-egui/src/i18n/mod.rs | 22 ++++++++++++++++++---- crates/ui-egui/src/i18n/tests.rs | 12 ++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) 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());