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
@@ -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.@@ -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;