diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs index 4afb43b1..84d4d3f9 100644 --- a/crates/ui-egui/src/i18n/ui.rs +++ b/crates/ui-egui/src/i18n/ui.rs @@ -129,7 +129,7 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[ ("Casts Shadows", "投射阴影"), ("Change Renderer…", "更改渲染器…"), ("Changing a shortcut creates a custom preset; the default stays as it is.", "修改快捷键会创建自定义预设;默认预设保持不变。"), - ("Channel", "色版"), + ("Channel", "通道"), ("Channel:", "通道:"), ("Chapter:", "章节:"), ("Choose...", "选择..."), @@ -285,7 +285,7 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[ ("More ArtCraft apps", "更多 ArtCraft 应用"), ("More tutorials", "更多教程"), ("Motion Blur", "运动模糊"), - ("Motion Blur: {}", "动态模糊:{}"), + ("Motion Blur: {}", "运动模糊:{}"), ("Motion Sketch: drag to record", "动态草图:拖动以记录"), ("Move Down", "下移"), ("Move Up", "上移"), @@ -490,6 +490,30 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[ ("⌘/Ctrl-drag a pin to record its motion", "⌘/Ctrl 拖动定位点可记录其运动"), ("▣ {}", "▣ {}"), ("⚠ {} is used by:", "⚠ {} 已被以下项目使用:"), + // Strings a panel used to hand to a helper as a bare literal: the helper draws them (the + // viewer's toolbar tooltips and popup items, the solid dialog's checkbox), so the lookup has + // to reach the catalog from inside the helper. Kept at the end so the catalogs stay parallel. + ("3D Reference Axes", "3D 参考轴"), + ("Affect all layers that use this solid", "影响使用该纯色的所有图层"), + ("Choose grid and guide options", "选择网格和参考线选项"), + ("Colorize", "着色"), + ("Custom", "自定义"), + ("Custom...", "自定义…"), + ("Fit", "适合"), + ("Fit up to 100%", "适合(最大 100%)"), + ("Full", "完整"), + ("Half", "一半"), + ("Only this layer uses this solid.", "仅此图层使用该纯色。"), + ("Proportional Grid", "比例网格"), + ("Quarter", "四分之一"), + ("Region of Interest", "目标区域"), + ("Reset Exposure", "重置曝光"), + ("Show Channel and Color Management Settings", "显示通道和色彩管理设置"), + ("Show Snapshot (hold)", "显示快照(按住)"), + ("Take Snapshot", "拍摄快照"), + ("Third", "三分之一"), + ("Toggle Mask and Shape Path Visibility", "切换蒙版与形状路径可见性"), + ("Toggle Transparency Grid", "切换透明网格"), ]; const TRADITIONAL_CHINESE: &[(&str, &str)] = &[ @@ -907,6 +931,29 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[ ("⌘/Ctrl-drag a pin to record its motion", "⌘/Ctrl 拖曳釘選點可記錄其運動"), ("▣ {}", "▣ {}"), ("⚠ {} is used by:", "⚠ {} 已被以下專案使用:"), + // The same rows as the Simplified catalog (the tests require both lists to match). The + // Traditional wording is the translator's call and is left as the English source for now. + ("3D Reference Axes", "3D Reference Axes"), + ("Affect all layers that use this solid", "Affect all layers that use this solid"), + ("Choose grid and guide options", "Choose grid and guide options"), + ("Colorize", "Colorize"), + ("Custom", "Custom"), + ("Custom...", "Custom..."), + ("Fit", "Fit"), + ("Fit up to 100%", "Fit up to 100%"), + ("Full", "Full"), + ("Half", "Half"), + ("Only this layer uses this solid.", "Only this layer uses this solid."), + ("Proportional Grid", "Proportional Grid"), + ("Quarter", "Quarter"), + ("Region of Interest", "Region of Interest"), + ("Reset Exposure", "Reset Exposure"), + ("Show Channel and Color Management Settings", "Show Channel and Color Management Settings"), + ("Show Snapshot (hold)", "Show Snapshot (hold)"), + ("Take Snapshot", "Take Snapshot"), + ("Third", "Third"), + ("Toggle Mask and Shape Path Visibility", "Toggle Mask and Shape Path Visibility"), + ("Toggle Transparency Grid", "Toggle Transparency Grid"), ]; #[cfg(test)] @@ -976,6 +1023,7 @@ mod tests { ".heading(\"", ".monospace(\"", ".on_hover_text(\"", + ".on_disabled_hover_text(\"", ".hint_text(\"", ".text(\"", ".prefix(\"", @@ -1039,19 +1087,44 @@ mod tests { } /// Every row is asked for by a converted panel: a leftover row means the panel went back to its - /// English literal. + /// English literal. A string a helper draws for the panel (a toolbar tooltip, a popup item, a + /// checkbox label) is looked up inside that helper, so its English source only appears as a + /// literal there - `helper_labels_are_translated` keeps those helpers honest. #[test] fn every_row_is_used_by_a_converted_panel() { for (en, _) in catalogs() { let plain = format!("tr({en:?})"); let formatted = format!("tr_args({en:?}"); + let bare = format!("{en:?}"); assert!( - CONVERTED.iter().any(|(_, src)| src.contains(&plain) || src.contains(&formatted)), + CONVERTED.iter().any(|(_, src)| src.contains(&plain) || src.contains(&formatted) || src.contains(&bare)), "{en:?} is in the catalog but no converted panel asks for it" ); } } + /// A label that a helper takes as an argument is drawn by the helper, so the helper has to ask + /// the catalog: `Checkbox::new` and `RadioButton::new` are not among the scanned call shapes + /// (the label is their second argument), which let the solid dialog's checkbox ship in English. + #[test] + fn helper_labels_are_translated() { + for (path, source) in CONVERTED { + for marker in ["Checkbox::new(", "RadioButton::new("] { + let mut from = 0; + while let Some(at) = source[from..].find(marker) { + let at = from + at; + let rest = &source[at + marker.len()..]; + let line = &rest[..rest.find('\n').unwrap_or(rest.len())]; + if let Some(q) = line.find('"') { + let before = line[..q].trim_end(); + assert!(before.ends_with("tr("), "{path}: the `{marker}` label `{}` is not in the catalog", &line[q..]); + } + from = at + marker.len(); + } + } + } + } + /// A newly hard-coded interface string fails here instead of shipping in English to every /// language. It is a heuristic - it looks at the call shapes the panels use - and it ignores /// one-character literals, which are punctuation or a key name rather than a sentence. diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index f123906b..eea50f07 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -418,8 +418,8 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { if editing.is_some() { ui.add_space(8.0); let r = ui - .add_enabled(shared, egui::Checkbox::new(&mut affect_all, "Affect all layers that use this solid")) - .on_disabled_hover_text("Only this layer uses this solid."); + .add_enabled(shared, egui::Checkbox::new(&mut affect_all, tr("Affect all layers that use this solid"))) + .on_disabled_hover_text(tr("Only this layer uses this solid.")); auto.add("dialog.solid.affectAll", r.rect, "Affect all layers that use this solid"); if shared && !affect_all { ui.label(egui::RichText::new(tr("This change will create a new solid for this layer.")).color(t.text_dim).size(11.0)); diff --git a/crates/ui-egui/src/panels/viewer_tools.rs b/crates/ui-egui/src/panels/viewer_tools.rs index 8d41eb73..2161a030 100644 --- a/crates/ui-egui/src/panels/viewer_tools.rs +++ b/crates/ui-egui/src/panels/viewer_tools.rs @@ -486,7 +486,9 @@ fn popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: &str, anchor: Rect, it ui.separator(); continue; } - let r = ui.selectable_label(*on, label.as_str()); + // The item is the English source: the drawn text is looked up in the catalog, while + // the automation label stays English for scripts and tests. + let r = ui.selectable_label(*on, crate::i18n::tr(label)); app.auto.add(&format!("viewer.{auto}.{i}"), r.rect, label); if r.clicked() { chosen = Some(i); @@ -520,10 +522,10 @@ pub(crate) fn resolution_label(res: Resolution, scale: f64) -> String { } else { "Quarter" }; - format!("({r})") + format!("({})", crate::i18n::tr(r)) } - Resolution::Custom(n) => format!("Custom ({n})"), - r => r.label().to_string(), + Resolution::Custom(n) => format!("{} ({n})", crate::i18n::tr("Custom")), + r => crate::i18n::tr(r.label()).to_string(), } } @@ -602,7 +604,9 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, let tog = |ui: &mut egui::Ui, auto: &mut crate::automation::Registry, x: &mut f32, icon: Icon, on: bool, id: &str, tip: &str| -> (bool, Rect) { let r = Rect::from_min_size(pos2(*x, cy - 11.0), vec2(22.0, 22.0)); - let resp = widgets::icon_button(ui, r, icon, on, &t, egui::Id::new(("vw-btn", id))).on_hover_text(tip); + // The tip is the English source: the tooltip is looked up in the catalog, while the + // automation label stays English for scripts and tests. + let resp = widgets::icon_button(ui, r, icon, on, &t, egui::Id::new(("vw-btn", id))).on_hover_text(crate::i18n::tr(tip)); auto.add(&format!("viewer.{id}"), r, tip); *x += 24.0; (resp.clicked(), r) @@ -632,7 +636,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, } let v = &app.ui.viewer; let items = vec![ - ("Title/Action Safe".to_string(), v.safe_margins), + ("Title/action safe".to_string(), v.safe_margins), ("Proportional Grid".to_string(), v.proportional_grid), ("Grid".to_string(), v.grid), ("Guides".to_string(), v.guides),