From eb89902c9c13830496794e78fb6bf237efab34c5 Mon Sep 17 00:00:00 2001 From: aiongg Date: Fri, 23 Oct 2026 21:49:37 +0000 Subject: [PATCH] Batch fix: web: Match System skips languages the bundled fonts can't draw (#1842) --- apps/effectcraft-web/src/app.rs | 2 +- crates/ui-egui/src/i18n.rs | 71 +++++++++++++++++++++++---------- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/apps/effectcraft-web/src/app.rs b/apps/effectcraft-web/src/app.rs index 573c2db6..3f462bd2 100644 --- a/apps/effectcraft-web/src/app.rs +++ b/apps/effectcraft-web/src/app.rs @@ -188,7 +188,7 @@ pub async fn start(canvas_id: String) -> Result<(), JsValue> { // chosen in Settings still wins. let navigator = web_sys::window().map(|w| w.navigator()); let browser = navigator.iter().flat_map(|n| n.languages().iter().filter_map(|v| v.as_string()).collect::>()); - effectcraft_ui_egui::i18n::set_system_locales(query_value("lang").into_iter().chain(browser).collect()); + effectcraft_ui_egui::i18n::set_system_locales(query_value("lang"), browser.collect()); let doc = web_sys::window().and_then(|w| w.document()).ok_or("no document")?; let canvas: web_sys::HtmlCanvasElement = doc.get_element_by_id(&canvas_id).ok_or("no canvas")?.dyn_into()?; let empty = query_flag("empty"); diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index ff989170..2c0cbb0f 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -81,27 +81,44 @@ fn system_language() -> &'static str { LANGUAGE.get_or_init(|| supported(sys_locale::get_locale().as_deref())) } #[cfg(target_arch = "wasm32")] - first_supported(SYSTEM_LOCALES.get().map(Vec::as_slice).unwrap_or_default()) + { + let (requested, browser) = SYSTEM_LOCALES.get().map(|(r, b)| (r.as_deref(), b.as_slice())).unwrap_or_default(); + first_supported(requested, browser, web_draws) + } } -/// The browser's preferred languages, most preferred first (`?lang=` in the address, then -/// `navigator.languages`), for Match System in the web build. -static SYSTEM_LOCALES: std::sync::OnceLock> = std::sync::OnceLock::new(); +/// The page's `?lang=`, then the browser's preferred languages, most preferred first +/// (`navigator.languages`), for Match System in the web build. +static SYSTEM_LOCALES: std::sync::OnceLock<(Option, Vec)> = std::sync::OnceLock::new(); -/// Tell Match System the system's preferred languages (the web build has no locale probe of its -/// own). Set before the first frame; only the first call counts. -pub fn set_system_locales(tags: Vec) { - let _ = SYSTEM_LOCALES.set(tags); +/// Tell Match System the language the page asks for (`?lang=`) and the browser's preferred +/// languages (the web build has no locale probe of its own). Set before the first frame; only +/// the first call counts. +pub fn set_system_locales(requested: Option, browser: Vec) { + let _ = SYSTEM_LOCALES.set((requested, browser)); } -/// The first of `tags` EffectCraft has a catalog for (English counts), else English: someone who -/// prefers Dutch, then Czech, gets Czech. +/// `requested` when EffectCraft has a catalog for it, else the first of `browser` it has a +/// catalog for and `drawable` accepts (English counts), else English: someone who prefers Dutch, +/// then Czech, gets Czech. #[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] -fn first_supported(tags: &[String]) -> &'static str { - tags.iter() - .map(|t| (t, supported(Some(t)))) - .find(|(t, code)| *code != "en" || t.split(['-', '_']).next().is_some_and(|p| p.eq_ignore_ascii_case("en"))) - .map_or("en", |(_, code)| code) +fn first_supported(requested: Option<&str>, browser: &[String], drawable: impl Fn(&str) -> bool) -> &'static str { + let english = |t: &str| t.split(['-', '_']).next().is_some_and(|p| p.eq_ignore_ascii_case("en")); + requested + .map(|t| (t, true)) + .into_iter() + .chain(browser.iter().map(|t| (t.as_str(), false))) + .map(|(t, asked)| (t, asked, supported(Some(t)))) + .find(|(t, asked, code)| (*code != "en" && (*asked || drawable(code))) || english(t)) + .map_or("en", |(_, _, code)| code) +} + +/// Whether the web build can draw `language`'s interface. It installs no system fonts and its +/// bundled faces have no CJK or Georgian glyphs, so Match System skips those browser languages +/// rather than draw boxes; `?lang=` and a language chosen in Settings still select them. +#[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))] +fn web_draws(language: &str) -> bool { + !matches!(language, "ja" | "zh-hans" | "zh-hant" | "ka") } /// The language EffectCraft shows for a BCP 47 locale (`ja-JP` → `ja`, `zh-Hans-CN` → `zh-hans`, @@ -7378,11 +7395,25 @@ mod tests { #[test] fn match_system_takes_the_first_browser_language_with_a_catalog() { let tags = |t: &[&str]| t.iter().map(|s| s.to_string()).collect::>(); - assert_eq!(super::first_supported(&tags(&["cs-CZ", "cs", "en"])), "cs"); - assert_eq!(super::first_supported(&tags(&["nl-NL", "cs"])), "cs"); - assert_eq!(super::first_supported(&tags(&["en-US", "cs"])), "en"); - assert_eq!(super::first_supported(&tags(&["nl-NL"])), "en"); - assert_eq!(super::first_supported(&tags(&[])), "en"); + let any = |_: &str| true; + assert_eq!(super::first_supported(None, &tags(&["cs-CZ", "cs", "en"]), any), "cs"); + assert_eq!(super::first_supported(None, &tags(&["nl-NL", "cs"]), any), "cs"); + assert_eq!(super::first_supported(None, &tags(&["en-US", "cs"]), any), "en"); + assert_eq!(super::first_supported(None, &tags(&["nl-NL"]), any), "en"); + assert_eq!(super::first_supported(None, &tags(&[]), any), "en"); + assert_eq!(super::first_supported(Some("uk"), &tags(&["cs"]), any), "uk"); + assert_eq!(super::first_supported(Some("nl"), &tags(&["cs"]), any), "cs"); + } + + /// The web build installs no CJK or Georgian font: Match System passes over those browser + /// languages, while `?lang=` still selects them. + #[test] + fn web_match_system_skips_languages_without_a_font() { + let tags = |t: &[&str]| t.iter().map(|s| s.to_string()).collect::>(); + assert_eq!(super::first_supported(None, &tags(&["ja-JP", "zh-CN", "zh-TW", "ka-GE", "cs"]), super::web_draws), "cs"); + assert_eq!(super::first_supported(None, &tags(&["ka"]), super::web_draws), "en"); + assert_eq!(super::first_supported(Some("ka"), &tags(&["cs"]), super::web_draws), "ka"); + assert_eq!(super::first_supported(Some("ja"), &tags(&[]), super::web_draws), "ja"); } fn keys(nodes: &[MenuNode], out: &mut BTreeSet<(String, String)>) {