mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-24 10:09:32 -06:00
web: Match System follows ?lang= and the browser's languages
Match System (the default) stayed English in the browser build, which has no system-locale probe (#229). The page now passes ?lang= from the address, then navigator.languages, and Match System takes the first of them with a catalog (an English tag counts), else English. A language chosen in Settings still wins; native builds are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF (cherry picked from commit 73c064e3858068ed19fbfafbdd0b3c8cc229a42e)
This commit is contained in:
@@ -50,6 +50,7 @@ web-sys = { version = "0.3", features = [
|
||||
"HtmlElement",
|
||||
"HtmlInputElement",
|
||||
"Location",
|
||||
"Navigator",
|
||||
"Url",
|
||||
"UrlSearchParams",
|
||||
"Window",
|
||||
|
||||
@@ -184,6 +184,11 @@ fn install_hooks(app: &mut EffectcraftApp) {
|
||||
#[wasm_bindgen]
|
||||
pub async fn start(canvas_id: String) -> Result<(), JsValue> {
|
||||
eframe::WebLogger::init(log::LevelFilter::Info).ok();
|
||||
// Match System follows `?lang=` in the address, then the browser's languages; a language
|
||||
// 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::<Vec<_>>());
|
||||
effectcraft_ui_egui::i18n::set_system_locales(query_value("lang").into_iter().chain(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");
|
||||
|
||||
@@ -66,16 +66,35 @@ pub(crate) fn czech(app: &EffectcraftApp) -> bool {
|
||||
|
||||
/// Settings ▸ General ▸ Language ▸ Match System: the operating system's interface language
|
||||
/// where EffectCraft has it, else English (#229), as After Effects installs in the system's
|
||||
/// language. The browser build stays in English because it has no system-locale probe.
|
||||
/// language. The browser build follows the locales its page passes in ([`set_system_locales`]).
|
||||
fn system_language() -> &'static str {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
static LANGUAGE: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
|
||||
LANGUAGE.get_or_init(|| supported(sys_locale::get_locale().as_deref()))
|
||||
}
|
||||
// No system locale in the browser build: English.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
supported(None)
|
||||
first_supported(SYSTEM_LOCALES.get().map(Vec::as_slice).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// 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<Vec<String>> = 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<String>) {
|
||||
let _ = SYSTEM_LOCALES.set(tags);
|
||||
}
|
||||
|
||||
/// The first of `tags` EffectCraft has a catalog for (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)
|
||||
}
|
||||
|
||||
/// The language EffectCraft shows for a BCP 47 locale (`ja-JP` → `ja`, `zh-Hans-CN` → `zh-hans`,
|
||||
@@ -6643,6 +6662,16 @@ mod tests {
|
||||
assert_eq!(command_label("window.panel", "Project"), "Project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn match_system_takes_the_first_browser_language_with_a_catalog() {
|
||||
let tags = |t: &[&str]| t.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
||||
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");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn czech_translates_menus_without_changing_bindings_or_user_names() {
|
||||
let mut app = EffectcraftApp::new(effectcraft_engine::Session::default());
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ Commands (CLI, MCP, control channel):
|
||||
|
||||
### Settings that change behaviour
|
||||
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk` / `es` / `pt-br`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`, a Spanish one (`es`, `es-ES`, `es_MX.UTF-8`) selects `es`, a Portuguese one (`pt`, `pt-BR`, `pt_PT.UTF-8`) selects `pt-br`; the browser build stays in English with it. Choose **Español** or **Português (Brasil)** for Spanish or Brazilian Portuguese menus (panels stay in English for now); choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`, with rows for Japanese, Simplified and Traditional Chinese and Ukrainian), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the tests in that module fail on an unused row and on a hard-coded string in a converted panel.
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk` / `es` / `pt-br`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`, a Spanish one (`es`, `es-ES`, `es_MX.UTF-8`) selects `es`, a Portuguese one (`pt`, `pt-BR`, `pt_PT.UTF-8`) selects `pt-br`; the browser build follows `?lang=` in the address, then the browser's languages (`navigator.languages`). Choose **Español** or **Português (Brasil)** for Spanish or Brazilian Portuguese menus (panels stay in English for now); choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`, with rows for Japanese, Simplified and Traditional Chinese and Ukrainian), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the tests in that module fail on an unused row and on a hard-coded string in a converted panel.
|
||||
- `general.undoLevels`: Levels of Undo
|
||||
- `general.pathPointSize`: Path Point and Handle Size
|
||||
- `general.recentItems`: Recent Projects Shown
|
||||
|
||||
Reference in New Issue
Block a user