From dbb6b470b3b3b016c8e87168846448678fa34e97 Mon Sep 17 00:00:00 2001 From: Louise Fox <2503492+KaboomFox@users.noreply.github.com> Date: Sun, 18 Oct 2026 02:38:00 +0000 Subject: [PATCH] Menus: leave installed plug-ins' names out of the strings to translate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit menu_strings() lists the strings a complete language catalog has to cover, and it leaves out names that are user data: fonts, recent files, saved views and libraries. An installed plug-in's name stayed in. Plug-ins are installed for the whole process, and a test that installs one before complete_languages_translate_every_menu_string runs made that test fail with the plug-in's name as an untranslated menu string. lists_plugin() tells an Object › Plug-ins filter or an Effect › Plug-ins effect by its command and params. Those are part of the menu item itself and stay right while another test installs or removes plug-ins. menu_strings() uses it, and so does menu_catalogs_translate_every_menu_label in place of its own copy of the rule. plugins_are_listed_in_the_object_and_effect_menus checks it. Before the change it failed with "Desaturate… is a menu string to translate". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un --- crates/ui-egui/src/i18n/tests.rs | 7 +++++-- crates/ui-egui/src/menus.rs | 19 +++++++++++++------ crates/ui-egui/src/tests_plugins.rs | 6 ++++++ 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/i18n/tests.rs b/crates/ui-egui/src/i18n/tests.rs index 63c727e15..9fb4b50cf 100644 --- a/crates/ui-egui/src/i18n/tests.rs +++ b/crates/ui-egui/src/i18n/tests.rs @@ -954,8 +954,11 @@ fn menu_catalogs_translate_every_menu_label() { let language_name = |l: &str| Lang::all().any(|lang| lang.name() == l); // Font names and sizes, and installed plug-ins' own names, are not interface text. let interface = |(label, id, p): &(&str, &str, serde_json::Value)| { - let plugin = *id == "plugin.dialog" || p.get("effect").and_then(serde_json::Value::as_str).is_some_and(|e| e.starts_with("plugin.")); - *id != "text.setStyle" && !plugin && shown.contains(*label) && !MENU_KEEP_AS_IS.contains(label) && !language_name(label) + *id != "text.setStyle" + && !crate::menus::lists_plugin(id, p) + && shown.contains(*label) + && !MENU_KEEP_AS_IS.contains(label) + && !language_name(label) }; let mut all: Vec = labels.iter().filter(|l| interface(l)).map(|(l, ..)| l.to_string()).collect(); all.extend(toggled_labels()); diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 3db77498c..92af3c9f6 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1917,6 +1917,13 @@ fn shows_a_name(id: &str, label: &str) -> bool { || (matches!(id, "effect.apply" | "effect.dialog") && vectorcraft_effects::plugin_effects().iter().any(|e| e.label == label)) } +/// Does the menu item `id` with `params` list an installed plug-in (an Object › Plug-ins filter or +/// an Effect › Plug-ins effect), by the plug-in's own name? Told by the item itself, so it holds +/// while another thread installs or removes plug-ins. +pub(crate) fn lists_plugin(id: &str, params: &Value) -> bool { + id == "plugin.dialog" || params.get("effect").and_then(Value::as_str).and_then(vectorcraft_plugins::effect::plugin_id).is_some() +} + /// The label of a menu item as drawn: [`dynamic_label`] in the UI language. Labels assembled /// around a name (Undo *Move*, Reset *Essentials*, Last Effect: *Drop Shadow*) are translated as /// templates so the name can move; names that are user data (files, views, fonts, custom @@ -3952,17 +3959,17 @@ pub const DYNAMIC_LABELS: &[&str] = &[ /// Every English string the menus, the command palette, the panel registry, the toolbar and the /// Preferences dialog can show: what a complete language catalog has to cover. Names that are user -/// data (fonts, recent files, saved views, libraries), the point sizes and the view angles are -/// left out. +/// data (fonts, recent files, saved views, libraries, installed plug-ins), the point sizes and the +/// view angles are left out. pub fn menu_strings() -> std::collections::BTreeSet { use std::collections::BTreeSet; - fn user_data(id: &str) -> bool { - hidden_when_disabled(id) || id.starts_with("type.recentFont") + fn user_data(id: &str, params: &Value) -> bool { + hidden_when_disabled(id) || id.starts_with("type.recentFont") || lists_plugin(id, params) } fn walk(items: &[Item], under: &str, out: &mut BTreeSet) { for it in items { match it { - Item::Cmd(l, id, _) => { + Item::Cmd(l, id, params) => { let size = l.strip_suffix(" pt").is_some_and(|n| n.chars().all(|c| c.is_ascii_digit())); // View › Rotate View lists angles: an optional minus sign, digits and a degree sign. let angle = l @@ -3971,7 +3978,7 @@ pub fn menu_strings() -> std::collections::BTreeSet { .is_some_and(|n| !n.is_empty() && n.chars().all(|c| c.is_ascii_digit())); // VectorCraft › Language lists each language by its own name. let language_name = *id == "app.language" && crate::i18n::Lang::all().any(|lang| lang.name() == *l); - if !user_data(id) && !size && !angle && !language_name && *l != "—" { + if !user_data(id, params) && !size && !angle && !language_name && *l != "—" { out.insert(l.to_string()); } } diff --git a/crates/ui-egui/src/tests_plugins.rs b/crates/ui-egui/src/tests_plugins.rs index 7cd254f1d..c934ef672 100644 --- a/crates/ui-egui/src/tests_plugins.rs +++ b/crates/ui-egui/src/tests_plugins.rs @@ -58,6 +58,12 @@ fn plugins_are_listed_in_the_object_and_effect_menus() { assert_eq!((fx.command.as_deref(), &fx.params), (Some("effect.dialog"), &json!({"effect": "plugin.test.ui.echo"}))); // Menu clicks on items with params don't turn into the generic parameter dialog. assert_eq!(menus::click_target("Desaturate…", "plugin.dialog", &filter.params).0, "plugin.dialog"); + // Their names are the plug-ins' own, not interface text a language catalog has to cover (they + // are installed for the whole test run, while the catalog tests run). + let strings = menus::menu_strings(); + for name in ["Desaturate…", "Test test.ui.echo…"] { + assert!(!strings.contains(name), "{name} is a menu string to translate"); + } } #[test]