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]