From 660fe22de8b0bee7347299925f7c44bcc7a83f5c Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 11 Oct 2026 17:35:52 +0200 Subject: [PATCH] i18n (cs): Effect > Pathfinder > Add reads "Sjednotit", keyed by the effect id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The key "Add" is shared by Effect > Pathfinder > Add (PathfinderOp::Unite), the File Info button and a label in the raster effects dialog. One plain row cannot fit all three: "Přidat" is right for the button, but the Pathfinder effect unites shapes, so it reads "Sjednotit". - cs.tsv: `@id pathfinder.add` = "Sjednotit"; the plain "Add" row stays "Přidat". - menus.rs: Effect menu items all run effect.apply / effect.dialog, so the command id cannot tell them apart. effect_item_label() looks the effect's own id up first (an @id row), and "Last Effect: {name}" does the same. - appearance.rs: the Appearance panel's effect labels use the same lookup. - i18n/tests.rs: an @id row may now name an effect id as well as a command id; czech_reads_as_czech pins Add and pathfinder.add. No other catalog has an @id row, so their behaviour is unchanged. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/tests.rs | 11 +++++++++-- crates/ui-egui/src/menus.rs | 17 +++++++++++++++-- crates/ui-egui/src/panels/appearance.rs | 5 +++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 2bb30fde..cb76481b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -370,6 +370,7 @@ Zig Zag… Cikcak… Pathfinder Operace s cestami Add Přidat +@id pathfinder.add Sjednotit Intersect Protnout Exclude Vyloučit Subtract Odečíst diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index fe849bc5..657f0acc 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -161,8 +161,11 @@ fn bundled_catalogs_are_consistent() { assert_eq!(src.ends_with('…'), tr.ends_with('…'), "{}: ellipsis mismatch: {src:?}", l.code); } if ctx == "@id" { - let known = - crate::menus::UI_COMMANDS.iter().any(|c| c.0 == src) || vectorcraft_engine::cmd::command_specs().iter().any(|c| c.id == src); + // A command id, or an effect id: Effect menu items share `effect.apply` / + // `effect.dialog`, so the effect's own id is what tells them apart. + let known = crate::menus::UI_COMMANDS.iter().any(|c| c.0 == src) + || vectorcraft_engine::cmd::command_specs().iter().any(|c| c.id == src) + || vectorcraft_effects::effect_catalog().iter().any(|e| e.id == src); assert!(known, "{}: unknown command id {src:?}", l.code); } } @@ -839,6 +842,10 @@ fn czech_reads_as_czech() { assert_eq!(trn(cs(), n, "{n} Layer", "{n} Layers"), format!("{n} {word}")); } assert_eq!(tr_ctx(cs(), "axis", "Both"), "Obě"); + // "Add" is Unite in Effect › Pathfinder (an `@id` row for the effect id); "Přidat" elsewhere. + assert_eq!(tr(cs(), "Add"), "Přidat"); + assert_eq!(tr_id(cs(), "pathfinder.add", "Add"), "Sjednotit"); + assert_eq!(tr_id(cs(), "pathfinder.intersect", "Intersect"), "Protnout"); let nested = "command `object.group` is not available right now: nothing selected"; assert_eq!(message(cs(), nested), "příkaz `object.group` nyní není k dispozici: nic není vybráno"); } diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 95d815c9..25d3737f 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1650,7 +1650,7 @@ pub fn display_label(app: &VectorcraftApp, id: &str, label: &str) -> String { "effect.last" => match &app.last_effect { Some((e, _)) => { let name = vectorcraft_effects::effect_info(e).map(|i| i.label).unwrap_or(e.as_str()); - fmt(tl!("Last Effect: {name}"), &[("name", tl!(name))]) + fmt(tl!("Last Effect: {name}"), &[("name", crate::i18n::tr_id(lang, e, name))]) } None => crate::i18n::tr_id(lang, id, label).to_string(), }, @@ -1661,6 +1661,19 @@ pub fn display_label(app: &VectorcraftApp, id: &str, label: &str) -> String { } } +/// The label of an Effect menu item in the UI language, by the effect's own id. Every effect item +/// runs `effect.apply` or `effect.dialog`, so the command id can't tell them apart; an `@id` row +/// for the effect id (for example `pathfinder.add`) wins over the plain row of the English label. +/// `None` when the catalog has no such row, and the plain translation applies. +fn effect_item_label(id: &str, p: &Value) -> Option { + if !matches!(id, "effect.apply" | "effect.dialog") { + return None; + } + let effect = p.get("effect")?.as_str()?; + let shown = crate::i18n::tr_id(crate::i18n::current(), effect, ""); + (!shown.is_empty()).then(|| shown.to_string()) +} + /// Menu slots that are left out while they have nothing to show (unused saved views, recent files, /// User Defined swatch and graphic style libraries). fn hidden_when_disabled(id: &str) -> bool { @@ -1793,7 +1806,7 @@ pub fn entry<'a>(app: &VectorcraftApp, it: &'a Item) -> Option> { Entry::Item(Row { command: Some((id, p)), source: label, - label: display_label(app, id, label).into(), + label: effect_item_label(id, p).unwrap_or_else(|| display_label(app, id, label)).into(), shortcut: item_shortcut(id, p), enabled, checked, diff --git a/crates/ui-egui/src/panels/appearance.rs b/crates/ui-egui/src/panels/appearance.rs index e8db7719..27b3be5e 100644 --- a/crates/ui-egui/src/panels/appearance.rs +++ b/crates/ui-egui/src/panels/appearance.rs @@ -160,10 +160,11 @@ fn built_in_effect(id: &str) -> bool { catalog().iter().any(|c| c.0 == id) } -/// [`effect_label`] as shown: a built-in effect's in the UI language, a plug-in's as it is. +/// [`effect_label`] as shown: a built-in effect's in the UI language (an `@id` row for the +/// effect's id wins over the plain row of its English label), a plug-in's as it is. fn shown_effect_label(id: &str) -> String { match catalog().iter().find(|c| c.0 == id) { - Some(c) => tl!(&c.1).to_string(), + Some(c) => crate::i18n::t_id(id, &c.1).to_string(), None => effect_label(id), } }