collective

Browse / github.com/storytold/effectcraft / patch

i18n: the Preview panel's labels reach the catalog

i18n: the Preview panel's labels reach the catalog The Preview panel draws its settings labels from three shapes the call-shape scan cannot see: a local `label` closure, a `(key, label, value)` check table and a `(key, label, value)` roll tuple. Most of them had no row in any catalog, so a Chinese — or Japanese, or Ukrainian — window showed `Shortcut`, `Range`, `Play From`, `Frame Rate`, `Resolution`, `Overlays`, `Layer Controls`, `Loop`, `Cache Before Playback`, `Pre-roll`, `Post-roll` and `If caching, play cached frames` in English. It is the largest single English block left in the interface. They ask the catalog now (`tr(..)` at the call

Status
open
Score
0.00
Adopters
0 other operators
Author
FeiLiuEM (imported from pull request #2427, open)
Base
2961258879214867b88310e57787edc4d36570b8
Size
+70 −9 in 2 files
Review
pending
Shared
2026-10-28
Id
3788b938957f0403e152f4a0

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/i18n/ui.rs crates/ui-egui/src/panels/misc.rs

diff --git a/crates/ui-egui/src/i18n/ui.rs b/crates/ui-egui/src/i18n/ui.rs--- a/crates/ui-egui/src/i18n/ui.rs+++ b/crates/ui-egui/src/i18n/ui.rs@@ -1641,6 +1641,18 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[     ("Total Offset", "总偏移"),     ("Per Layer", "每个图层"),     ("Track", "跟踪"),+    ("Shortcut", "快捷键"),+    ("Range", "范围"),+    ("Play From", "从以下位置播放"),+    ("Frame Rate", "帧速率"),+    ("Resolution", "分辨率"),+    ("Overlays", "叠加"),+    ("Layer Controls", "图层控制"),+    ("Loop", "循环"),+    ("Cache Before Playback", "播放前缓存"),+    ("Pre-roll", "预卷"),+    ("Post-roll", "后卷"),+    ("If caching, play cached frames", "缓存时播放已缓存帧"), ];  const TRADITIONAL_CHINESE: &[(&str, &str)] = &[@@ -2327,6 +2339,18 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[     ("Total Offset", "總偏移"),     ("Per Layer", "每個圖層"),     ("Track", "追蹤"),+    ("Shortcut", "快速鍵"),+    ("Range", "範圍"),+    ("Play From", "從下列位置播放"),+    ("Frame Rate", "影格速率"),+    ("Resolution", "解析度"),+    ("Overlays", "覆疊"),+    ("Layer Controls", "圖層控制"),+    ("Loop", "循環"),+    ("Cache Before Playback", "播放前快取"),+    ("Pre-roll", "預卷"),+    ("Post-roll", "後卷"),+    ("If caching, play cached frames", "快取時播放已快取影格"), ];  /// Ukrainian (uk): original translations of the same interface rows as the Chinese catalogs.@@ -5502,6 +5526,43 @@ mod tests {      /// A row that translates nothing, repeats a source string, or changes the number of values the     /// English source carries is a bug in the catalog itself.+    #[test]+    fn the_preview_panel_labels_are_translated() {+        // The Preview panel draws these from a local closure, a check table and a roll tuple, none of+        // which the call-shape scan sees; they had no rows at all.+        for key in [+            "Shortcut",+            "Range",+            "Play From",+            "Frame Rate",+            "Resolution",+            "Overlays",+            "Layer Controls",+            "Loop",+            "Cache Before Playback",+            "Pre-roll",+            "Post-roll",+            "If caching, play cached frames",+        ] {+            for (name, table) in [("Simplified", SIMPLIFIED_CHINESE), ("Traditional", TRADITIONAL_CHINESE)] {+                let value = table.iter().find(|(k, _)| *k == key).map(|(_, v)| *v);+                assert!(value.is_some_and(|v| !v.is_empty()), "{key} has no {name} row");+            }+        }+        // A row is not a translation until the panel asks for it: the labels drawn one by one carry+        // `tr(..)`, and the table and the roll tuple wrap the text they read at the call.+        let source = include_str!("../panels/misc.rs");+        for literal in ["Shortcut", "Range", "Play From", "Frame Rate", "Resolution", "Full Screen", "If caching, play cached frames"] {+            assert!(source.contains(&format!("tr(\"{literal}\")")), "{literal} is drawn without asking the catalog");+        }+        for call in ["check_row(app, ui, &p, pos2(cx, cy), key, tr(text), on)", "Align2::LEFT_CENTER, tr(text),"] {+            assert!(source.contains(call), "{call} no longer asks the catalog");+        }+        for table in ["\"Overlays\"", "\"Layer Controls\"", "\"Loop\"", "\"Cache Before Playback\"", "\"Pre-roll\"", "\"Post-roll\""] {+            assert!(source.contains(table), "{table} is not in the panel");+        }+    }+     #[test]     fn catalogs_are_unique_and_keep_the_english_shape() {         // Grouped by language: a source listed in two tables of one language is still a repeat.diff --git a/crates/ui-egui/src/panels/misc.rs b/crates/ui-egui/src/panels/misc.rs--- a/crates/ui-egui/src/panels/misc.rs+++ b/crates/ui-egui/src/panels/misc.rs@@ -61,7 +61,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {     let mut yy = y + 46.0;      // Shortcut.-    label(yy, "Shortcut");+    label(yy, tr("Shortcut"));     let names: Vec<String> = PreviewShortcut::ALL.iter().map(|s| s.label().to_string()).collect();     if let Some(i) = dropdown_row(         app,@@ -91,14 +91,14 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {         let row = if i < 4 { i / 2 } else { i - 2 };         let cx = x0 + col as f32 * 110.0 - 2.0;         let cy = yy + row as f32 * 22.0;-        if check_row(app, ui, &p, pos2(cx, cy), key, text, on) {+        if check_row(app, ui, &p, pos2(cx, cy), key, tr(text), on) {             changes.push(json!({"shortcut": current.id(), key: !on}));         }     }     yy += 4.0 * 22.0 + 6.0;      // Range (and the pre/post-roll of Play Around Current Time).-    label(yy, "Range");+    label(yy, tr("Range"));     let ranges: Vec<String> = PreviewRange::ALL.iter().map(|r| r.label().to_string()).collect();     let range_ids = ["workArea", "workAreaExtended", "entireDuration", "aroundCurrentTime"];     if let Some(i) = dropdown_row(@@ -116,7 +116,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {     if o.range == PreviewRange::AroundCurrentTime {         for (k, (key, text, v)) in [("preRoll", "Pre-roll", o.pre_roll), ("postRoll", "Post-roll", o.post_roll)].into_iter().enumerate() {             let lx = xv + k as f32 * 100.0;-            p.text(pos2(lx, yy), Align2::LEFT_CENTER, text, Tokens::ui(11.5), t.text_dim);+            p.text(pos2(lx, yy), Align2::LEFT_CENTER, tr(text), Tokens::ui(11.5), t.text_dim);             let (r, nv, _) = widgets::hot_number_at(ui, pos2(lx + 52.0, yy - 9.0), egui::Id::unique(("pv-roll", key)), v, 0.05, (0.0, 600.0), 1, "s", &t);             app.auto.add(&format!("preview.{key}"), r, text);             if let Some(nv) = nv {@@ -127,7 +127,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {     }      // Play From.-    label(yy, "Play From");+    label(yy, tr("Play From"));     let froms: Vec<String> = PlayFrom::ALL.iter().map(|f| f.label().to_string()).collect();     if let Some(i) = dropdown_row(         app,@@ -144,7 +144,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {      // Frame Rate, Skip, Resolution.     let comp_fps = app.session.active_comp().map(|c| c.frame_rate.as_f64());-    label(yy, "Frame Rate");+    label(yy, tr("Frame Rate"));     const RATES: [f64; 12] = [8.0, 10.0, 12.0, 12.5, 15.0, 23.976, 24.0, 25.0, 29.97, 30.0, 50.0, 60.0];     let auto = format!("({:.2}) Auto", comp_fps.unwrap_or(0.0));     let mut rates = vec![auto.clone()];@@ -166,7 +166,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {         changes.push(json!({"shortcut": current.id(), "skip": nv.round() as u32}));     }     yy += 24.0;-    label(yy, "Resolution");+    label(yy, tr("Resolution"));     let ress: Vec<String> = PreviewResolution::ALL.iter().map(|r| r.label().to_string()).collect();     let res_ids = ["auto", "full", "half", "third", "quarter", "custom"];     if let Some(i) = dropdown_row(@@ -189,7 +189,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {         }     }     yy += 24.0;-    if check_row(app, ui, &p, pos2(x0 - 2.0, yy), "fullScreen", "Full Screen", o.full_screen) {+    if check_row(app, ui, &p, pos2(x0 - 2.0, yy), "fullScreen", tr("Full Screen"), o.full_screen) {         changes.push(json!({"shortcut": current.id(), "fullScreen": !o.full_screen}));     }     yy += 24.0;@@ -197,7 +197,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {     // What stopping does.     p.text(pos2(x0, yy), Align2::LEFT_CENTER, tr_args("On ({}) stop:", &[&(current.label())]), Tokens::ui(11.5), t.text_dim);     yy += 20.0;-    if check_row(app, ui, &p, pos2(x0 - 2.0, yy), "playCachedFrames", "If caching, play cached frames", o.play_cached_frames) {+    if check_row(app, ui, &p, pos2(x0 - 2.0, yy), "playCachedFrames", tr("If caching, play cached frames"), o.play_cached_frames) {         changes.push(json!({"shortcut": current.id(), "playCachedFrames": !o.play_cached_frames}));     }     yy += 22.0;