diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index 023dad48e..cf90170cf 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -221,6 +221,10 @@ const JAPANESE: &[(&str, &str)] = &[ ("Drag to slip the source", "ドラッグしてソースをスリップ"), ("Drop Frame", "ドロップフレーム"), ("Duplicate", "複製"), + ("Copy", "コピー"), + ("Paste", "ペースト"), + ("Edit Value…", "値を編集…"), + ("Remove Expression", "エクスプレッションを削除"), ("Duration:", "デュレーション:"), ("Edit Gradient…", "グラデーションを編集…"), ("Edit Target: {}", "編集対象:{}"), @@ -766,6 +770,10 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[ ("Drag to slip the source", "拖动可滑移源"), ("Drop Frame", "丢帧"), ("Duplicate", "重复"), + ("Copy", "复制"), + ("Paste", "粘贴"), + ("Edit Value…", "编辑值…"), + ("Remove Expression", "移除表达式"), ("Duration:", "持续时间:"), ("Edit Gradient…", "编辑渐变…"), ("Edit Target: {}", "编辑目标:{}"), @@ -1499,6 +1507,10 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[ ("Drag to slip the source", "拖曳可滑移源"), ("Drop Frame", "丟格"), ("Duplicate", "重複"), + ("Copy", "拷貝"), + ("Paste", "貼上"), + ("Edit Value…", "編輯值…"), + ("Remove Expression", "移除運算式"), ("Duration:", "持續時間:"), ("Edit Gradient…", "編輯漸變…"), ("Edit Target: {}", "編輯目標:{}"), @@ -2235,6 +2247,10 @@ const UKRAINIAN: &[(&str, &str)] = &[ ("Drag to slip the source", "Перетягніть, щоб зсунути матеріал у межах шару"), ("Drop Frame", "Пропуск номерів"), ("Duplicate", "Дублювати"), + ("Copy", "Копіювати"), + ("Paste", "Вставити"), + ("Edit Value…", "Редагувати значення…"), + ("Remove Expression", "Вилучити вираз"), ("Duration:", "Тривалість:"), ("Edit Gradient…", "Редагувати градієнт…"), ("Edit Target: {}", "Ціль редагування: {}"), @@ -2788,6 +2804,10 @@ const PORTUGUESE: &[(&str, &str)] = &[ ("Drag to slip the source", "Arraste para deslizar a origem"), ("Drop Frame", "Drop frame"), ("Duplicate", "Duplicar"), + ("Copy", "Copiar"), + ("Paste", "Colar"), + ("Edit Value…", "Editar valor…"), + ("Remove Expression", "Remover expressão"), ("Duration:", "Duração:"), ("Edit Gradient…", "Editar gradiente…"), ("Edit Target: {}", "Editar destino: {}"), @@ -3239,6 +3259,10 @@ const CZECH: &[(&str, &str)] = &[ ("Drag to slip the source", "Tažením posunete zdroj v čase"), ("Drop Frame", "Vynechávat čísla snímků"), ("Duplicate", "Duplikovat"), + ("Copy", "Kopírovat"), + ("Paste", "Vložit"), + ("Edit Value…", "Upravit hodnotu…"), + ("Remove Expression", "Odstranit výraz"), ("Duration:", "Délka trvání:"), ("Edit Gradient…", "Upravit přechod…"), ("Edit Target: {}", "Upravit cíl: {}"), @@ -3796,6 +3820,10 @@ const SPANISH: &[(&str, &str)] = &[ ("Drag to slip the source", "Arrastra para deslizar el origen"), ("Drop Frame", "Drop frame"), ("Duplicate", "Duplicar"), + ("Copy", "Copiar"), + ("Paste", "Pegar"), + ("Edit Value…", "Editar valor…"), + ("Remove Expression", "Quitar expresión"), ("Duration:", "Duración:"), ("Edit Gradient…", "Editar degradado…"), ("Edit Target: {}", "Destino de la edición: {}"), diff --git a/crates/ui-egui/src/panels/effect_controls.rs b/crates/ui-egui/src/panels/effect_controls.rs index bad1db39f..05b58d61e 100644 --- a/crates/ui-egui/src/panels/effect_controls.rs +++ b/crates/ui-egui/src/panels/effect_controls.rs @@ -1279,7 +1279,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let lid = layer.id.0; let mut picked_label: Option = None; let mut item = |ui: &mut egui::Ui, label: &str, enabled: bool, cmd: &str, params: serde_json::Value| { - if ui.add_enabled(enabled, egui::Button::new(label)).clicked() { + if ui.add_enabled(enabled, egui::Button::new(crate::i18n::context_item(cmd, ¶ms, label))).clicked() { actions.push((cmd.into(), params)); ui.close(); } diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index ce1c3e4d7..425d48c31 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -3796,7 +3796,7 @@ fn prop_menu( ) { let (l, uid) = (layer.id.0, prop.uid); let mut item = |ui: &mut egui::Ui, key: &str, label: &str, selected: bool, cmd: &str, params: serde_json::Value| { - let resp = ui.add(egui::Button::new(label).selected(selected)); + let resp = ui.add(egui::Button::new(crate::i18n::context_item(cmd, ¶ms, label)).selected(selected)); auto.add(&format!("timeline.prop.{uid}.menu.{key}"), resp.rect, label); if resp.clicked() { actions.push((cmd.into(), params)); @@ -4573,6 +4573,39 @@ mod tests { assert!(actions.is_empty()); } + /// The property context menu draws its items in the interface language too: the commands the + /// menu bar has from its rows, Reset, Edit Value… and Remove Expression from the panel catalog. + /// The automation labels stay English. + #[test] + fn the_property_context_menu_draws_in_the_interface_language() { + let mut s = effectcraft_engine::Session::default(); + s.execute("comp.new", json!({"name": "Main", "width": 64, "height": 64, "duration": 4})).unwrap(); + let l = s.execute("layer.newSolid", json!({"name": "Plate", "color": "#406080"})).unwrap()["layer"].as_u64().unwrap(); + let opacity = s.active_comp().unwrap().layer(LayerId(l)).unwrap().props.prop("transform/opacity").unwrap().uid; + s.execute("prop.setExpression", json!({"layer": l, "prop": opacity})).unwrap(); + let comp = s.active_comp_id().unwrap(); + let layer = s.active_comp().unwrap().layer(LayerId(l)).unwrap().clone(); + let (position, opacity) = (layer.props.prop("transform/position").unwrap(), layer.props.prop("transform/opacity").unwrap()); + let (mut auto, mut actions) = (crate::automation::Registry::default(), Vec::new()); + let mut menu = |language| { + crate::i18n::set_current(language); + let mut drawn = drawn_text(|ui| prop_menu(ui, &mut auto, comp, &layer, position, &mut actions)); + drawn.extend(drawn_text(|ui| prop_menu(ui, &mut auto, comp, &layer, opacity, &mut actions))); + drawn + }; + let chinese = menu("zh-hans"); + let english = menu("en"); + for label in ["重置", "编辑值…", "单独尺寸", "添加表达式", "移除表达式", "向基本图形添加属性"] { + assert!(chinese.iter().any(|t| t == label), "{label} missing from {chinese:?}"); + } + assert!(!chinese.iter().any(|t| t.is_ascii()), "English left in {chinese:?}"); + for label in ["Reset", "Edit Value…", "Separate Dimensions", "Remove Expression", "Add Property to Essential Graphics"] { + assert!(english.iter().any(|t| t == label), "{label} missing from {english:?}"); + } + assert!(auto.elements.iter().any(|e| e.label == "Remove Expression"), "the automation label stays English"); + assert!(actions.is_empty()); + } + /// UI fidelity (M13.5): compact After Effects-like rows and muted label-coloured bars. #[test] fn rows_and_bars_follow_after_effects_proportions() { diff --git a/crates/ui-egui/tests/ui_effect_names.rs b/crates/ui-egui/tests/ui_effect_names.rs index c65d2cea2..28d1a1f3f 100644 --- a/crates/ui-egui/tests/ui_effect_names.rs +++ b/crates/ui-egui/tests/ui_effect_names.rs @@ -85,6 +85,41 @@ fn the_generated_effect_menu_is_translated() { assert_eq!(menu_label(&en, "Effect", "Blur & Sharpen", BLUR).as_deref(), Some(BLUR_EN), "the English menu is unchanged"); } +/// Right-click the Effect Controls header of effect `fx`. +fn open_effect_menu(h: &mut Harness<'_, EffectcraftApp>, fx: u64) { + let e = h.state().auto.find(&format!("effectControls.effect.{fx}")).expect("the effect's header").rect; + let pos = egui::pos2(e[0] + e[2] / 2.0, e[1] + e[3] / 2.0); + h.event(egui::Event::PointerMoved(pos)); + h.step(); + for pressed in [true, false] { + h.event(egui::Event::PointerButton { pos, button: egui::PointerButton::Secondary, pressed, modifiers: Default::default() }); + h.step(); + } + h.run_steps(2); +} + +/// The effect header's context menu draws its items in the interface language, like the layer +/// menu (#1308): it used to draw Copy, Paste, Duplicate, Reset, Remove, Move Up and Move Down in +/// English whatever the language. +#[test] +fn the_effect_context_menu_follows_the_interface_language() { + const ENGLISH: [&str; 7] = ["Copy", "Paste", "Duplicate", "Reset", "Remove", "Move Up", "Move Down"]; + let (mut h, fx) = harness("zh-hans"); + open_effect_menu(&mut h, fx); + for label in ["复制", "粘贴", "重复", "重命名", "重置", "移除", "上移", "下移"] { + assert!(h.query_by_label(label).is_some(), "{label} missing from the effect menu"); + } + for label in ENGLISH { + assert!(h.query_by_label(label).is_none(), "{label} drawn in English"); + } + + let (mut en, fx) = harness("en"); + open_effect_menu(&mut en, fx); + for label in ENGLISH { + assert!(en.query_by_label(label).is_some(), "{label} missing from the English menu"); + } +} + /// Everything an agent or a script addresses stays English: the automation label, the property /// tree's group name and `effect.list`. #[test]