diff --git a/crates/ui-egui/src/credits.rs b/crates/ui-egui/src/credits.rs index 442edb8c..77357e87 100644 --- a/crates/ui-egui/src/credits.rs +++ b/crates/ui-egui/src/credits.rs @@ -3,6 +3,7 @@ //! build.rs from contributors/contributors.json (craftrules standards/contributors.md); nothing is //! read at run time. +use crate::i18n::{tr, tr_args}; use std::cmp::Ordering; use egui::RichText; @@ -243,7 +244,7 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) { let id = egui::Id::new("credits_view"); let mut v = ui.data_mut(|d| d.get_temp::(id)).unwrap_or_default(); ui.horizontal_wrapped(|ui| { - ui.label("Show"); + ui.label(tr("Show")); for m in NameMode::ALL { let r = ui.selectable_label(v.names == m, m.label()); auto.add(&format!("about.credits.show.{}", m.id()), r.rect, m.label()); @@ -252,7 +253,7 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) { } } ui.separator(); - ui.label("Sort"); + ui.label(tr("Sort")); let combo = egui::ComboBox::from_id_salt("credits_sort").selected_text(v.key.label().0).show_ui(ui, |ui| { for k in SortKey::ALL { let r = ui.selectable_label(v.key == k, k.label().0); @@ -264,29 +265,29 @@ pub fn contributors_ui(ui: &mut egui::Ui, auto: &mut Registry) { } }); auto.add("about.credits.sort", combo.response.rect, "Sort"); - let r = ui.button(if v.ascending { "▲" } else { "▼" }).on_hover_text("Reverse the order"); + let r = ui.button(if v.ascending { "▲" } else { "▼" }).on_hover_text(tr("Reverse the order")); auto.add("about.credits.reverse", r.rect, "Reverse the order"); if r.clicked() { v.ascending = !v.ascending; } ui.separator(); - let r = ui.selectable_label(!v.table, "Grab bag"); + let r = ui.selectable_label(!v.table, tr("Grab bag")); auto.add("about.credits.view.grabBag", r.rect, "Grab bag"); if r.clicked() { v.table = false; } - let r = ui.selectable_label(v.table, "Table"); + let r = ui.selectable_label(v.table, tr("Table")); auto.add("about.credits.view.table", r.rect, "Table"); if r.clicked() { v.table = true; } }); let list = sorted(CONTRIBUTORS, v.names, v.key, v.ascending); - ui.label(RichText::new(format!("{} contributors · {} commits", list.len(), group(TOTAL_COMMITS))).small().weak()); + ui.label(RichText::new(tr_args("{} contributors · {} commits", &[&(list.len()), &(group(TOTAL_COMMITS))])).small().weak()); ui.separator(); egui::ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { if list.is_empty() { - ui.label("No contributor data was built into this copy."); + ui.label(tr("No contributor data was built into this copy.")); } else if v.table { table(ui, &list, &mut v, auto); } else { @@ -340,7 +341,7 @@ fn table(ui: &mut egui::Ui, list: &[&Contributor], v: &mut View, auto: &mut Regi /// About ▸ Models: AI models credited in Co-Authored-By trailers. pub fn models_ui(ui: &mut egui::Ui) { if MODELS.is_empty() { - ui.label("No model credits were built into this copy."); + ui.label(tr("No model credits were built into this copy.")); return; } let assisted: u64 = MODELS.iter().map(|m| m.commits).max().unwrap_or(0).max(1); diff --git a/crates/ui-egui/src/panels/comp_settings.rs b/crates/ui-egui/src/panels/comp_settings.rs index 2befd5e6..39b7580e 100644 --- a/crates/ui-egui/src/panels/comp_settings.rs +++ b/crates/ui-egui/src/panels/comp_settings.rs @@ -2,6 +2,7 @@ //! Basic / Advanced / 3D Renderer tabs, Cancel / OK. Every control registers an automation id //! (`dialog.comp.`). +use crate::i18n::{tr, tr_args}; use effectcraft_engine::time::{FrameRate, format_timecode_frames, parse_timecode}; use egui::{Color32, vec2}; use serde_json::json; @@ -174,7 +175,7 @@ fn timecode_field(ui: &mut egui::Ui, buf: &mut Option, secs: &mut f64, r pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registry) -> Option { let mut out = None; ui.horizontal(|ui| { - ui.label("Composition Name:"); + ui.label(tr("Composition Name:")); let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(300.0)); auto.add("dialog.comp.name", r.rect, "Composition Name"); }); @@ -193,7 +194,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr match d.tab { Tab::Basic => { egui::Grid::new("comp-basic").num_columns(2).spacing([12.0, 9.0]).show(ui, |ui| { - ui.label("Preset:"); + ui.label(tr("Preset:")); let current = PRESETS .iter() .find(|(_, w, h, par, f)| *w == d.width && *h == d.height && (par - d.pixel_aspect).abs() < 1e-3 && (f - d.fps).abs() < 1e-3) @@ -208,7 +209,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr }); auto.add("dialog.comp.preset", r.response.rect, "Preset"); ui.end_row(); - ui.label("Width:"); + ui.label(tr("Width:")); ui.horizontal(|ui| { let (ow, oh) = (d.width, d.height); let r = ui.add(egui::DragValue::new(&mut d.width).range(4..=30000).suffix(" px")); @@ -221,7 +222,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr } }); ui.end_row(); - ui.label("Height:"); + ui.label(tr("Height:")); let (ow, oh) = (d.width, d.height); let r = ui.add(egui::DragValue::new(&mut d.height).range(4..=30000).suffix(" px")); auto.add("dialog.comp.height", r.rect, "Height"); @@ -229,7 +230,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr d.width = ((d.height as f64) * ow as f64 / oh as f64).round().max(4.0) as u32; } ui.end_row(); - ui.label("Pixel Aspect Ratio:"); + ui.label(tr("Pixel Aspect Ratio:")); ui.horizontal(|ui| { let cur = PIXEL_ASPECTS .iter() @@ -245,12 +246,12 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr }); auto.add("dialog.comp.pixelAspect", r.response.rect, "Pixel Aspect Ratio"); ui.label( - egui::RichText::new(format!("Frame Aspect Ratio: {}", aspect_label(d.width as f64 * d.pixel_aspect, d.height as f64))) + egui::RichText::new(tr_args("Frame Aspect Ratio: {}", &[&(aspect_label(d.width as f64 * d.pixel_aspect, d.height as f64))])) .color(Color32::GRAY), ); }); ui.end_row(); - ui.label("Frame Rate:"); + ui.label(tr("Frame Rate:")); ui.horizontal(|ui| { // Any rate can be typed; the list holds the common ones. let r = ui.add(egui::DragValue::new(&mut d.fps).range(1.0..=999.0).speed(0.01).max_decimals(3)); @@ -263,25 +264,25 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr } }); auto.add("dialog.comp.frameRates", r.response.rect, "Frame Rate presets"); - ui.label("frames per second"); + ui.label(tr("frames per second")); if rate.supports_drop_frame() { - ui.label(egui::RichText::new("Drop Frame").color(Color32::GRAY)); + ui.label(egui::RichText::new(tr("Drop Frame")).color(Color32::GRAY)); } }); ui.end_row(); ui.label(""); let mb = d.width as f64 * d.height as f64 * 4.0 / (1024.0 * 1024.0); - ui.label(egui::RichText::new(format!("{} x {}, {mb:.1}MB per 8bpc frame", d.width, d.height)).color(Color32::GRAY)); + ui.label(egui::RichText::new(tr_args("{} x {}, {}MB per 8bpc frame", &[&d.width, &d.height, &format!("{mb:.1}")])).color(Color32::GRAY)); ui.end_row(); - ui.label("Start Timecode:"); + ui.label(tr("Start Timecode:")); let r = ui.horizontal(|ui| timecode_field(ui, &mut d.start_tc, &mut d.start, rate, i64::MIN / 4)).inner; auto.add("dialog.comp.startTimecode", r.rect, "Start Timecode"); ui.end_row(); - ui.label("Duration:"); + ui.label(tr("Duration:")); let r = ui.horizontal(|ui| timecode_field(ui, &mut d.dur_tc, &mut d.duration, rate, 1)).inner; auto.add("dialog.comp.duration", r.rect, "Duration"); ui.end_row(); - ui.label("Background Color:"); + ui.label(tr("Background Color:")); ui.horizontal(|ui| { let r = crate::widgets::srgb_color_button(ui, &mut d.bg); auto.add("dialog.comp.background", r.rect, "Background Color"); @@ -292,7 +293,7 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr } Tab::Advanced => { egui::Grid::new("comp-adv").num_columns(2).spacing([12.0, 9.0]).show(ui, |ui| { - ui.label("Anchor:"); + ui.label(tr("Anchor:")); egui::Grid::new("comp-anchor").spacing([2.0, 2.0]).show(ui, |ui| { for i in 0..9u8 { let on = d.anchor == i; @@ -309,28 +310,28 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr }); ui.end_row(); ui.label(""); - let r = ui.checkbox(&mut d.preserve_frame_rate, "Preserve frame rate when nested or in render queue"); + let r = ui.checkbox(&mut d.preserve_frame_rate, tr("Preserve frame rate when nested or in render queue")); auto.add("dialog.comp.preserveFrameRate", r.rect, "Preserve frame rate when nested or in render queue"); ui.end_row(); ui.label(""); - let r = ui.checkbox(&mut d.preserve_resolution, "Preserve resolution when nested"); + let r = ui.checkbox(&mut d.preserve_resolution, tr("Preserve resolution when nested")); auto.add("dialog.comp.preserveResolution", r.rect, "Preserve resolution when nested"); ui.end_row(); - ui.label(egui::RichText::new("Motion Blur").strong()); + ui.label(egui::RichText::new(tr("Motion Blur")).strong()); ui.end_row(); - ui.label("Shutter Angle:"); + ui.label(tr("Shutter Angle:")); let r = ui.add(egui::DragValue::new(&mut d.shutter_angle).range(0.0..=720.0).suffix("°")); auto.add("dialog.comp.shutterAngle", r.rect, "Shutter Angle"); ui.end_row(); - ui.label("Shutter Phase:"); + ui.label(tr("Shutter Phase:")); let r = ui.add(egui::DragValue::new(&mut d.shutter_phase).range(-360.0..=360.0).suffix("°")); auto.add("dialog.comp.shutterPhase", r.rect, "Shutter Phase"); ui.end_row(); - ui.label("Samples Per Frame:"); + ui.label(tr("Samples Per Frame:")); let r = ui.add(egui::DragValue::new(&mut d.samples).range(2..=64)); auto.add("dialog.comp.samples", r.rect, "Samples Per Frame"); ui.end_row(); - ui.label("Adaptive Sample Limit:"); + ui.label(tr("Adaptive Sample Limit:")); let r = ui.add(egui::DragValue::new(&mut d.adaptive_limit).range(16..=256)); auto.add("dialog.comp.adaptiveLimit", r.rect, "Adaptive Sample Limit"); ui.end_row(); @@ -338,11 +339,11 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr } Tab::Renderer => { ui.horizontal(|ui| { - ui.label("Renderer:"); + ui.label(tr("Renderer:")); let r = egui::ComboBox::from_id_salt("comp-renderer").selected_text(if d.advanced_3d { "Advanced 3D" } else { "Classic 3D" }).show_ui(ui, |ui| { - ui.selectable_value(&mut d.advanced_3d, false, "Classic 3D"); - ui.selectable_value(&mut d.advanced_3d, true, "Advanced 3D"); + ui.selectable_value(&mut d.advanced_3d, false, tr("Classic 3D")); + ui.selectable_value(&mut d.advanced_3d, true, tr("Advanced 3D")); }); auto.add("dialog.comp.renderer", r.response.rect, "Renderer"); }); @@ -351,12 +352,12 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr ui.add_space(14.0); ui.horizontal(|ui| { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let r = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent)); + let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); auto.add("dialog.comp.ok", r.rect, "OK"); if r.clicked() { out = Some(params(d)); } - let r = ui.button("Cancel"); + let r = ui.button(tr("Cancel")); auto.add("dialog.comp.cancel", r.rect, "Cancel"); if r.clicked() { out = Some(serde_json::Value::Null); diff --git a/crates/ui-egui/src/panels/effect_controls.rs b/crates/ui-egui/src/panels/effect_controls.rs index 34d89da9..194ff0cc 100644 --- a/crates/ui-egui/src/panels/effect_controls.rs +++ b/crates/ui-egui/src/panels/effect_controls.rs @@ -286,7 +286,9 @@ fn prop_row( if matches!(prop.ui, ParamUi::Point | ParamUi::Point3) { let cr = Rect::from_center_size(pos2(x + 8.0, cy), vec2(18.0, 18.0)); let active = app.ui.fx_pick.as_ref().is_some_and(|k| k.prop == uid && k.kind == "point"); - if fw::crosshair_button(ui, cr, active, egui::Id::new(("ec-xh", uid)), &t).on_hover_text("Click, then click in the Composition panel").clicked() + if fw::crosshair_button(ui, cr, active, egui::Id::new(("ec-xh", uid)), &t) + .on_hover_text(crate::i18n::tr("Click, then click in the Composition panel")) + .clicked() { toggle_pick(app, "point", layer, prop); } @@ -314,7 +316,7 @@ fn prop_row( let er = Rect::from_center_size(pos2(sr.max.x + 14.0, cy), vec2(18.0, 18.0)); let active = app.ui.fx_pick.as_ref().is_some_and(|k| k.prop == uid && k.kind == "color"); if fw::eyedropper_button(ui, er, active, egui::Id::new(("ec-eye", uid)), &t) - .on_hover_text("Eyedropper: click, then click in the Composition panel") + .on_hover_text(crate::i18n::tr("Eyedropper: click, then click in the Composition panel")) .clicked() { toggle_pick(app, "color", layer, prop); @@ -711,7 +713,7 @@ fn channel_popup( auto: &str, ) -> Option { let t = app.tokens; - p.text(pos2(x, cy), Align2::LEFT_CENTER, "Channel:", Tokens::ui(12.0), t.text_dim); + p.text(pos2(x, cy), Align2::LEFT_CENTER, crate::i18n::tr("Channel:"), Tokens::ui(12.0), t.text_dim); let dr = Rect::from_min_size(pos2(x + 58.0, cy - 9.0), vec2(80.0, 18.0)); let pop = id.with("pop"); if widgets::dropdown(ui, dr, names.get(cur).copied().unwrap_or(""), &t, id).clicked() { @@ -764,7 +766,7 @@ fn curves_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, }; let rr = Rect::from_min_size(pos2(mr.max.x + 8.0, cy - 9.0), vec2(44.0, 18.0)); let rresp = ui.interact(rr, egui::Id::new(("ec-creset", euid)), Sense::click()); - p.text(rr.center(), Align2::CENTER_CENTER, "Reset", Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim }); + p.text(rr.center(), Align2::CENTER_CENTER, crate::i18n::tr("Reset"), Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim }); app.auto.add(&format!("effectControls.effect.{euid}.curves.reset"), rr, "Reset curve"); if rresp.clicked() { actions.push(("prop.set".into(), json!({"layer": layer.id.0, "prop": prop.uid, "value": fw::CurvePoints::identity().format()}))); @@ -833,7 +835,7 @@ fn levels_editor( app.ui.fx_levels_channel.insert(euid, n); } } else { - p.text(pos2(x0, cy), Align2::LEFT_CENTER, "Histogram", Tokens::ui(12.0), t.text_dim); + p.text(pos2(x0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Histogram"), Tokens::ui(12.0), t.text_dim); } let w = (r.width() - 72.0).clamp(120.0, 256.0); let hr = Rect::from_min_size(pos2(x0 + 8.0, r.min.y + 34.0), vec2(w, 80.0)); @@ -898,7 +900,7 @@ fn levels_editor( fn histogram_only(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, g: &PropGroup, r: Rect) { let t = app.tokens; let x0 = r.min.x + 30.0; - p.text(pos2(x0, r.min.y + 12.0), Align2::LEFT_CENTER, "Histogram", Tokens::ui(12.0), t.text_dim); + p.text(pos2(x0, r.min.y + 12.0), Align2::LEFT_CENTER, crate::i18n::tr("Histogram"), Tokens::ui(12.0), t.text_dim); let w = (r.width() - 72.0).clamp(120.0, 256.0); let hr = Rect::from_min_size(pos2(x0 + 8.0, r.min.y + 24.0), vec2(w, 80.0)); match frame_histogram(app, ui.ctx()) { @@ -921,7 +923,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let p = ui.painter().with_clip_rect(rect); let ctx = ui.ctx().clone(); let Some(layer) = selected_layer(app) else { - p.text(rect.center(), Align2::CENTER_CENTER, "Select a layer to see its effects", Tokens::ui(12.0), t.text_faint); + p.text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Select a layer to see its effects"), Tokens::ui(12.0), t.text_faint); return; }; let Some(cid) = app.session.active_comp_id() else { return }; @@ -986,7 +988,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { bp.text(pos2(tw.max.x + 6.0, r.center().y), Align2::LEFT_CENTER, &g.name, Tokens::semibold(12.0), t.text); let reset = Rect::from_min_size(pos2(r.max.x - 96.0, r.min.y + 4.0), vec2(40.0, 18.0)); let rresp = ui.interact(reset, egui::Id::new(("ec-reset", g.uid)), Sense::click()); - bp.text(reset.center(), Align2::CENTER_CENTER, "Reset", Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim }); + bp.text(reset.center(), Align2::CENTER_CENTER, crate::i18n::tr("Reset"), Tokens::ui(11.5), if rresp.hovered() { t.hot_text } else { t.text_dim }); app.auto.add(&format!("effectControls.effect.{}.reset", g.uid), reset, "Reset"); if rresp.clicked() { actions.push(("effect.reset".into(), json!({"layer": layer.id.0, "effect": g.uid}))); @@ -994,7 +996,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // About…: the effect's name and category (AE's About dialog). let about = Rect::from_min_size(pos2(r.max.x - 50.0, r.min.y + 4.0), vec2(44.0, 18.0)); let aresp = ui.interact(about, egui::Id::new(("ec-about", g.uid)), Sense::click()); - bp.text(about.center(), Align2::CENTER_CENTER, "About...", Tokens::ui(11.5), if aresp.hovered() { t.hot_text } else { t.text_dim }); + bp.text(about.center(), Align2::CENTER_CENTER, crate::i18n::tr("About..."), Tokens::ui(11.5), if aresp.hovered() { t.hot_text } else { t.text_dim }); app.auto.add(&format!("effectControls.effect.{}.about", g.uid), about, "About"); let apop = egui::Id::new(("ec-about-pop", g.uid)); if aresp.clicked() { diff --git a/crates/ui-egui/src/panels/effects_presets.rs b/crates/ui-egui/src/panels/effects_presets.rs index c5d48095..7b68ba7f 100644 --- a/crates/ui-egui/src/panels/effects_presets.rs +++ b/crates/ui-egui/src/panels/effects_presets.rs @@ -5,6 +5,7 @@ //! selected layers; dragging an effect onto a layer (timeline, viewer, Effect Controls) applies //! it there. +use crate::i18n::tr; use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2}; use serde_json::json; @@ -276,7 +277,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Contents menu. let mr = Rect::from_center_size(pos2(rect.max.x - 18.0, sr.center().y), vec2(22.0, 22.0)); let mpop = egui::Id::new("fx-contents-menu"); - if widgets::icon_button(ui, mr, Icon::Hamburger, false, &t, egui::Id::new("fx-menu")).on_hover_text("Effects & Presets options").clicked() { + if widgets::icon_button(ui, mr, Icon::Hamburger, false, &t, egui::Id::new("fx-menu")).on_hover_text(tr("Effects & Presets options")).clicked() { widgets::open_popup(ui, mpop); } app.auto.add("effects.menu", mr, "Effects & Presets options"); @@ -394,7 +395,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } if y == list.min.y - scroll.offset && searching { - lp.text(pos2(list.center().x, list.min.y + 24.0), Align2::CENTER_CENTER, "No matching effects or presets", Tokens::ui(12.0), t.text_faint); + lp.text(pos2(list.center().x, list.min.y + 24.0), Align2::CENTER_CENTER, tr("No matching effects or presets"), Tokens::ui(12.0), t.text_faint); } let content = y + scroll.offset - list.min.y; scroll.end(ui, &mut app.auto, "effects.scroll", content, &t); diff --git a/crates/ui-egui/src/panels/essential.rs b/crates/ui-egui/src/panels/essential.rs index dd014d31..26d458d4 100644 --- a/crates/ui-egui/src/panels/essential.rs +++ b/crates/ui-egui/src/panels/essential.rs @@ -20,6 +20,7 @@ //! id (`essential.primary`, `essential.solo`, `essential.name`, `essential.control..value`, //! `essential.instance..value`, `essential.export`…). +use crate::i18n::{tr, tr_args}; use effectcraft_engine::keyframe::Value as KV; use effectcraft_engine::project::essential::{self, ControlType, EgControl, EgKind}; use effectcraft_engine::project::{ItemId, ItemKind, LayerSource, ParamUi, Property}; @@ -61,7 +62,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { let mut actions: Actions = vec![]; let mut invoke: Vec<(String, Value)> = vec![]; let Some(cid) = primary(app) else { - ui.label(RichText::new("Open a composition to build Essential Graphics").color(t.text_faint)); + ui.label(RichText::new(tr("Open a composition to build Essential Graphics")).color(t.text_faint)); return (actions, invoke); }; let project = app.session.project.clone(); @@ -69,7 +70,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { // ---- Primary + Solo. ui.horizontal(|ui| { - ui.label(RichText::new("Primary:").color(t.text_dim)); + ui.label(RichText::new(tr("Primary:")).color(t.text_dim)); let r = egui::ComboBox::from_id_salt("eg-primary").selected_text(&comp_name).width(150.0).show_ui(ui, |ui| { for (id, _) in project.comps() { let name = project.item(*id).map(|i| i.name.clone()).unwrap_or_default(); @@ -81,7 +82,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { app.auto.add("essential.primary", r.response.rect, &comp_name); }); let solo = app.session.state.essential_solo; - let r = ui.add(egui::Button::new("Solo Supported Properties").selected(solo)); + let r = ui.add(egui::Button::new(tr("Solo Supported Properties")).selected(solo)); app.auto.add("essential.solo", r.rect, "Solo Supported Properties"); if r.clicked() { actions.push(("essential.soloSupported".into(), json!({"on": !solo}))); @@ -91,7 +92,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { let shown_name = if eg.name.is_empty() { comp_name.clone() } else { eg.name.clone() }; let name_id = egui::Id::new("eg-name-buf"); let mut name: String = ctx.data(|d| d.get_temp(name_id)).unwrap_or_else(|| shown_name.clone()); - let r = ui.add(egui::TextEdit::singleline(&mut name).hint_text("Template name").desired_width(f32::INFINITY)); + let r = ui.add(egui::TextEdit::singleline(&mut name).hint_text(tr("Template name")).desired_width(f32::INFINITY)); app.auto.add("essential.name", r.rect, &shown_name); if r.has_focus() { ctx.data_mut(|d| d.insert_temp(name_id, name.clone())); @@ -107,7 +108,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { let list = ui.scope(|ui| { ui.set_min_height(60.0); if eg.controls.is_empty() { - ui.label(RichText::new("Drag properties here from the timeline,\nor use Animation > Add Property to Essential Graphics.").color(t.text_faint)); + ui.label(RichText::new(tr("Drag properties here from the timeline,\nor use Animation > Add Property to Essential Graphics.")).color(t.text_faint)); } controls(app, ui, cid, &eg.controls, None, &mut actions); }); @@ -128,12 +129,12 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { // ---- Bottom buttons. ui.horizontal_wrapped(|ui| { - let r = ui.menu_button("Add Formatting", |ui| { - if ui.button("Add Group").clicked() { + let r = ui.menu_button(tr("Add Formatting"), |ui| { + if ui.button(tr("Add Group")).clicked() { actions.push(("essential.addGroup".into(), json!({"comp": cid.0}))); ui.close(); } - if ui.button("Add Comment").clicked() { + if ui.button(tr("Add Comment")).clicked() { actions.push(("essential.addComment".into(), json!({"comp": cid.0}))); ui.close(); } @@ -142,7 +143,7 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { // The selected property as a Font control (Source Text) or a uniform Scale slider. let has_sel = !app.session.state.selected_props.is_empty(); let r = ui.add_enabled_ui(has_sel, |ui| { - ui.menu_button("Add Selected As", |ui| { + ui.menu_button(tr("Add Selected As"), |ui| { for (label, as_type) in [("Font Control", "font"), ("Uniform Scale", "scale")] { if ui.button(label).clicked() { actions.push(("essential.addProperty".into(), json!({"as": as_type}))); @@ -155,14 +156,14 @@ fn body(app: &mut EffectcraftApp, ui: &mut egui::Ui) -> (Actions, Actions) { let footage_layer = app.session.active_comp().and_then(|c| { app.session.state.selected_layers.iter().filter_map(|l| c.layer(*l)).find(|l| matches!(l.source, LayerSource::Footage { .. })).map(|l| l.id.0) }); - let r = ui.add_enabled(footage_layer.is_some(), egui::Button::new("Media Replacement")); + let r = ui.add_enabled(footage_layer.is_some(), egui::Button::new(tr("Media Replacement"))); app.auto.add("essential.addMedia", r.rect, "Add Media Replacement"); if r.clicked() && let Some(l) = footage_layer { actions.push(("essential.addMedia".into(), json!({"layer": l}))); } - let r = ui.add_enabled(!eg.controls.is_empty(), egui::Button::new("Export Template…")); + let r = ui.add_enabled(!eg.controls.is_empty(), egui::Button::new(tr("Export Template…"))); app.auto.add("essential.export", r.rect, "Export Template"); if r.clicked() { invoke.push(("essential.exportTemplate".into(), json!({"comp": cid.0}))); @@ -234,7 +235,7 @@ fn controls(app: &mut EffectcraftApp, ui: &mut egui::Ui, cid: ItemId, list: &[Eg } } None => { - ui.label(RichText::new("(missing)").color(t.danger)); + ui.label(RichText::new(tr("(missing)")).color(t.danger)); } } link_menu(app, ui, cid, c, group, actions); @@ -243,10 +244,10 @@ fn controls(app: &mut EffectcraftApp, ui: &mut egui::Ui, cid: ItemId, list: &[Eg name_label(app, ui, cid, c, RichText::new(&c.name), actions); let item = essential::source_media(&app.session.project, cid, c); let name = item.and_then(|i| app.session.project.item(i)).map(|i| i.name.clone()).unwrap_or_default(); - ui.label(RichText::new(format!("▣ {name}")).color(t.text_dim)); + ui.label(RichText::new(tr_args("▣ {}", &[&name])).color(t.text_dim)); } } - let r = ui.add(egui::Button::new(RichText::new("×").size(10.0)).small().frame(false)).on_hover_text("Remove"); + let r = ui.add(egui::Button::new(RichText::new("×").size(10.0)).small().frame(false)).on_hover_text(tr("Remove")); app.auto.add(&format!("essential.control.{}.remove", c.id), r.rect, "Remove"); if r.clicked() { actions.push(("essential.remove".into(), json!({"comp": cid.0, "control": c.id}))); @@ -270,16 +271,16 @@ fn link_menu(app: &mut EffectcraftApp, ui: &mut egui::Ui, cid: ItemId, c: &EgCon }; let has_sel = !app.session.state.selected_props.is_empty(); let r = ui.menu_button(RichText::new("⋯").size(10.0), |ui| { - if ui.button("Add Mirror").clicked() { + if ui.button(tr("Add Mirror")).clicked() { actions.push(("essential.addMirror".into(), json!({"comp": cid.0, "control": c.id, "group": group}))); ui.close(); } - if ui.add_enabled(has_sel, egui::Button::new("Link Selected Property")).clicked() { + if ui.add_enabled(has_sel, egui::Button::new(tr("Link Selected Property"))).clicked() { actions.push(("essential.linkProperty".into(), json!({"comp": cid.0, "control": c.id}))); ui.close(); } for (layer, prop, name) in &links { - if ui.button(format!("Unlink {name}")).clicked() { + if ui.button(tr_args("Unlink {}", &[&name])).clicked() { actions.push(("essential.unlinkProperty".into(), json!({"comp": cid.0, "control": c.id, "layer": layer, "prop": prop}))); ui.close(); } @@ -460,7 +461,7 @@ fn instance(app: &mut EffectcraftApp, ui: &mut egui::Ui, actions: &mut Actions) let over = essential::overridden(&layer); let lt = layer.layer_time(app.session.time()); ui.separator(); - ui.label(RichText::new(format!("Essential Properties — {}", layer.name)).strong()); + ui.label(RichText::new(tr_args("Essential Properties — {}", &[&layer.name])).strong()); let group = essential::group(&layer).cloned().unwrap_or_else(|| effectcraft_engine::project::PropGroup::new(0, "", "")); for c in eg.flat() { let m = essential::match_id(c.id); @@ -527,12 +528,12 @@ fn instance(app: &mut EffectcraftApp, ui: &mut egui::Ui, actions: &mut Actions) } ui.horizontal(|ui| { let any = !over.is_empty(); - let r = ui.add_enabled(any, egui::Button::new("Push All to Comp")); + let r = ui.add_enabled(any, egui::Button::new(tr("Push All to Comp"))); app.auto.add("essential.instance.pushAll", r.rect, "Push Override Values to Source"); if r.clicked() { actions.push(("essential.pushToComp".into(), json!({"layer": layer.id.0}))); } - let r = ui.add_enabled(any, egui::Button::new("Revert All")); + let r = ui.add_enabled(any, egui::Button::new(tr("Revert All"))); app.auto.add("essential.instance.revertAll", r.rect, "Revert All"); if r.clicked() { actions.push(("essential.revert".into(), json!({"layer": layer.id.0}))); diff --git a/crates/ui-egui/src/panels/fx_editors.rs b/crates/ui-egui/src/panels/fx_editors.rs index c9ff2b79..51b146af 100644 --- a/crates/ui-egui/src/panels/fx_editors.rs +++ b/crates/ui-egui/src/panels/fx_editors.rs @@ -16,6 +16,7 @@ //! Every edit is a `prop.set` engine command (undoable; one drag = one undo step) and every //! handle registers an automation id. +use crate::i18n::{tr, tr_args}; use effectcraft_engine::effects::{ColoramaPalette, OffsetCurve}; use effectcraft_engine::geom::Mat3; use effectcraft_engine::keyframe::Value; @@ -110,7 +111,7 @@ fn tabs( fn reset_link(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, r: Rect, id: &str) -> bool { let t = app.tokens; let resp = ui.interact(r, egui::Id::new(id), Sense::click()); - p.text(r.center(), Align2::CENTER_CENTER, "Reset", Tokens::ui(11.5), if resp.hovered() { t.hot_text } else { t.text_dim }); + p.text(r.center(), Align2::CENTER_CENTER, tr("Reset"), Tokens::ui(11.5), if resp.hovered() { t.hot_text } else { t.text_dim }); app.auto.add(id, r, "Reset"); resp.clicked() } @@ -196,7 +197,7 @@ fn rgb_curves( if let Some(np) = edit.changed { set_str(actions, layer, g, id, np.format(), Some(key)); } - p.text(pos2(gr.min.x, gr.max.y + 11.0), Align2::LEFT_CENTER, "Click adds a point; drag one off to remove", Tokens::ui(10.5), t.text_faint); + p.text(pos2(gr.min.x, gr.max.y + 11.0), Align2::LEFT_CENTER, tr("Click adds a point; drag one off to remove"), Tokens::ui(10.5), t.text_faint); } /// A hue / saturation curve's points (`x,y` in 0..1, y = ½ neutral), any count (empty = flat). @@ -394,7 +395,7 @@ fn huesat_curves( if reset_link(app, ui, p, Rect::from_min_size(pos2(gr.max.x - 44.0, gr.max.y + 4.0), vec2(44.0, 18.0)), &format!("{prefix}.reset")) { set_str(actions, layer, g, id, String::new(), None); } - p.text(pos2(gr.min.x, gr.max.y + 13.0), Align2::LEFT_CENTER, "Click adds a point (½ height = no change)", Tokens::ui(10.5), t.text_faint); + p.text(pos2(gr.min.x, gr.max.y + 13.0), Align2::LEFT_CENTER, tr("Click adds a point (½ height = no change)"), Tokens::ui(10.5), t.text_faint); } // --------------------------------------------------------------------------------------------- @@ -458,7 +459,7 @@ fn colorama_wheel( let pal = if custom { ColoramaPalette::parse(&str_value(layer, ectx, g, "palette")) } else { None }.unwrap_or_else(|| ColoramaPalette::preset(preset)); let x0 = r.min.x + 38.0; let label = if custom { "Custom palette" } else { effectcraft_engine::effects::COLORAMA_PRESETS.get(preset as usize).copied().unwrap_or("Preset") }; - p.text(pos2(x0, r.min.y + 15.0), Align2::LEFT_CENTER, format!("Output Cycle: {label}"), Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0, r.min.y + 15.0), Align2::LEFT_CENTER, tr_args("Output Cycle: {}", &[&label]), Tokens::ui(11.5), t.text_dim); let size = wheel_size(r.width()); let c = pos2(x0 + size / 2.0, r.min.y + 30.0 + size / 2.0); let (outer, inner) = (size / 2.0 - 8.0, size / 2.0 - 26.0); @@ -572,7 +573,13 @@ fn colorama_wheel( widgets::open_popup(ui, pop); } app.auto.add(&format!("{prefix}.color"), sr, "Stop color"); - p.text(pos2(sr.max.x + 8.0, sr.center().y), Align2::LEFT_CENTER, format!("Stop {} at {:.0}°", sel + 1, ph * 360.0), Tokens::ui(11.0), t.text_dim); + p.text( + pos2(sr.max.x + 8.0, sr.center().y), + Align2::LEFT_CENTER, + tr_args("Stop {} at {}°", &[&(sel + 1), &format!("{:.0}", ph * 360.0)]), + Tokens::ui(11.0), + t.text_dim, + ); let mut rgb = [col[0], col[1], col[2]]; if crate::header::color_popup(ui, pop, sr.left_bottom(), &mut rgb) { let mut st = pal.stops.clone(); @@ -687,7 +694,7 @@ fn glow_map(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, laye } p.rect_stroke(strip, 0.0, Stroke::new(1.0, t.field_border), StrokeKind::Outside); app.auto.add(&format!("{prefix}.colorMap"), strip, "Glow colour map"); - p.text(pos2(strip.max.x + 8.0, strip.center().y), Align2::LEFT_CENTER, "Colour map", Tokens::ui(10.5), t.text_faint); + p.text(pos2(strip.max.x + 8.0, strip.center().y), Align2::LEFT_CENTER, tr("Colour map"), Tokens::ui(10.5), t.text_faint); if mode != 2 { return; } @@ -844,12 +851,12 @@ fn extractor_layers( None => "Custom".to_string(), }; // Lined up with the parameter names and value popups below (Effect Controls' row layout). - p.text(pos2(r.min.x + 37.0, r.center().y), Align2::LEFT_CENTER, "Layer", Tokens::ui(12.0), t.text); + p.text(pos2(r.min.x + 37.0, r.center().y), Align2::LEFT_CENTER, tr("Layer"), Tokens::ui(12.0), t.text); let vx = (r.min.x + r.width() * 0.48).max(r.min.x + 161.0); let dr = Rect::from_min_size(pos2(vx, r.center().y - 9.0), vec2((r.max.x - vx - 56.0).clamp(80.0, 200.0), 18.0)); let pop = egui::Id::new(("extractor-layers", euid)); if widgets::dropdown(ui, dr, &label, &t, egui::Id::new(("extractor-layer", euid))) - .on_hover_text("The OpenEXR layer shown in red, green, blue and alpha") + .on_hover_text(tr("The OpenEXR layer shown in red, green, blue and alpha")) .clicked() { widgets::open_popup(ui, pop); diff --git a/crates/ui-egui/src/panels/learn.rs b/crates/ui-egui/src/panels/learn.rs index b6423680..b613791f 100644 --- a/crates/ui-egui/src/panels/learn.rs +++ b/crates/ui-egui/src/panels/learn.rs @@ -2,6 +2,7 @@ //! tutorial (engine model in `effectcraft_engine::learn`). The coach highlights the step's target //! (an automation id) and moves on when the user performs the step's command; "Show me" runs it. +use crate::i18n::{tr, tr_args}; use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2}; use serde_json::json; @@ -13,7 +14,7 @@ pub fn home_tab(app: &mut EffectcraftApp, ui: &mut egui::Ui, area: Rect) { let t = app.tokens; let p = ui.painter().with_clip_rect(area); let (x, mut y, w) = (area.min.x, area.min.y, area.width()); - p.text(pos2(x, y + 6.0), Align2::LEFT_CENTER, "Learn", Tokens::semibold(16.0), Color32::WHITE); + p.text(pos2(x, y + 6.0), Align2::LEFT_CENTER, tr("Learn"), Tokens::semibold(16.0), Color32::WHITE); p.text( pos2(x, y + 26.0), Align2::LEFT_CENTER, @@ -112,11 +113,11 @@ pub fn coach(app: &mut EffectcraftApp, ctx: &egui::Context) { .show(ui, |ui| { ui.set_width(w - 28.0); ui.horizontal(|ui| { - ui.label(egui::RichText::new("LEARN").font(Tokens::semibold(10.0)).color(t.accent)); + ui.label(egui::RichText::new(tr("LEARN")).font(Tokens::semibold(10.0)).color(t.accent)); ui.label(egui::RichText::new(&tut.title).font(Tokens::ui(11.0)).color(t.text_dim)); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { let (cr, r) = ui.allocate_exact_size(vec2(16.0, 16.0), Sense::click()); - let r = r.on_hover_text("Close tutorial"); + let r = r.on_hover_text(tr("Close tutorial")); crate::icons::paint(ui.painter(), cr, crate::icons::Icon::Close, if r.hovered() { Color32::WHITE } else { t.text_dim }); app.auto.add("learn.close", r.rect, "Close tutorial"); if r.clicked() { @@ -140,37 +141,43 @@ pub fn coach(app: &mut EffectcraftApp, ctx: &egui::Context) { ui.add_space(4.0); match &step { Some(st) => { - ui.label(egui::RichText::new(format!("Step {} of {n}: {}", i + 1, st.title)).font(Tokens::semibold(13.0)).color(Color32::WHITE)); + ui.label( + egui::RichText::new(tr_args("Step {} of {}: {}", &[&(i + 1), &n, &st.title])) + .font(Tokens::semibold(13.0)) + .color(Color32::WHITE), + ); ui.add_space(2.0); ui.label(egui::RichText::new(&st.text).font(Tokens::ui(12.0)).color(t.text)); } None => { - ui.label(egui::RichText::new("Tutorial complete").font(Tokens::semibold(13.0)).color(Color32::WHITE)); + ui.label(egui::RichText::new(tr("Tutorial complete")).font(Tokens::semibold(13.0)).color(Color32::WHITE)); ui.label( - egui::RichText::new("Nice work. Pick another tutorial on the Home screen's Learn tab.").font(Tokens::ui(12.0)).color(t.text), + egui::RichText::new(tr("Nice work. Pick another tutorial on the Home screen's Learn tab.")) + .font(Tokens::ui(12.0)) + .color(t.text), ); } } ui.add_space(8.0); ui.horizontal(|ui| { - let back = ui.add_enabled(i > 0, egui::Button::new("Back")); + let back = ui.add_enabled(i > 0, egui::Button::new(tr("Back"))); app.auto.add("learn.back", back.rect, "Back"); if back.clicked() { action = Some("back"); } if step.is_some() { - let show = ui.add(egui::Button::new("Show me")); + let show = ui.add(egui::Button::new(tr("Show me"))); app.auto.add("learn.showMe", show.rect, "Show me"); if show.clicked() { action = Some("showMe"); } - let next = ui.add(egui::Button::new("Next")); + let next = ui.add(egui::Button::new(tr("Next"))); app.auto.add("learn.next", next.rect, "Next"); if next.clicked() { action = Some("next"); } } else { - let more = ui.add(egui::Button::new("More tutorials")); + let more = ui.add(egui::Button::new(tr("More tutorials"))); app.auto.add("learn.more", more.rect, "More tutorials"); if more.clicked() { action = Some("more"); diff --git a/crates/ui-egui/src/panels/misc.rs b/crates/ui-egui/src/panels/misc.rs index 5f8ccfbb..cd0ab689 100644 --- a/crates/ui-egui/src/panels/misc.rs +++ b/crates/ui-egui/src/panels/misc.rs @@ -1,5 +1,6 @@ //! Smaller panels: Preview, Audio, History, Markers and placeholders. +use crate::i18n::{tr, tr_args}; use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2}; use serde_json::json; @@ -10,7 +11,7 @@ use crate::{EffectcraftApp, widgets}; pub fn placeholder(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, p: PanelKind) { let t = app.tokens; - ui.painter().text(rect.center(), Align2::CENTER_CENTER, format!("{} — coming soon", p.title()), Tokens::ui(12.0), t.text_faint); + ui.painter().text(rect.center(), Align2::CENTER_CENTER, tr_args("{} — coming soon", &[&(p.title())]), Tokens::ui(12.0), t.text_faint); } /// Preview panel: transport controls, the shortcut whose options are shown, and that @@ -158,7 +159,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { changes.push(json!({"shortcut": current.id(), "frameRate": v})); } let sx = xv + 106.0; - p.text(pos2(sx, yy), Align2::LEFT_CENTER, "Skip", Tokens::ui(12.0), t.text_dim); + p.text(pos2(sx, yy), Align2::LEFT_CENTER, tr("Skip"), Tokens::ui(12.0), t.text_dim); let (r, nv, _) = widgets::hot_number_at(ui, pos2(sx + 30.0, yy - 9.0), egui::Id::new("pv-skip"), o.skip as f64, 0.1, (0.0, 99.0), 0, "", &t); app.auto.add("preview.skip", r, "Skip"); if let Some(nv) = nv { @@ -194,7 +195,7 @@ pub fn preview(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { yy += 24.0; // What stopping does. - p.text(pos2(x0, yy), Align2::LEFT_CENTER, format!("On ({}) stop:", current.label()), Tokens::ui(11.5), t.text_dim); + 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) { changes.push(json!({"shortcut": current.id(), "playCachedFrames": !o.play_cached_frames})); @@ -353,7 +354,7 @@ pub fn audio(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } None => { - p.text(pos2(lx, top + 12.0), Align2::LEFT_CENTER, "No audio layer selected", Tokens::ui(11.0), t.text_faint); + p.text(pos2(lx, top + 12.0), Align2::LEFT_CENTER, tr("No audio layer selected"), Tokens::ui(11.0), t.text_faint); } } if app.meter.active() || app.audio.is_some() { @@ -425,7 +426,7 @@ pub fn markers(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let scroll = widgets::PanelScroll::begin(ui, egui::Id::new("markers-scroll"), rect); let mut y = rect.min.y + 8.0 - scroll.offset; if c.markers.is_empty() { - p.text(rect.center(), Align2::CENTER_CENTER, "No composition markers", Tokens::ui(12.0), t.text_faint); + p.text(rect.center(), Align2::CENTER_CENTER, tr("No composition markers"), Tokens::ui(12.0), t.text_faint); } for m in &c.markers { let r = Rect::from_min_size(pos2(rect.min.x, y), vec2(rect.width(), 22.0)); diff --git a/crates/ui-egui/src/panels/precomp.rs b/crates/ui-egui/src/panels/precomp.rs index 1ee55738..f4bf4272 100644 --- a/crates/ui-egui/src/panels/precomp.rs +++ b/crates/ui-egui/src/panels/precomp.rs @@ -2,6 +2,7 @@ //! composition duration, Open New Composition), the Composition Navigator bar above the viewer //! (the flow of nested comps, click to open) and the Composition Mini-Flowchart popup (Tab). +use crate::i18n::{tr, tr_args}; use effectcraft_engine::project::{ItemId, LayerSource, Project}; use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2}; use serde_json::json; @@ -112,13 +113,13 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let (mut ok, mut cancel) = (false, false); super::dialogs::modal(ctx, "Pre-compose", vec2(520.0, 360.0), t, |ui| { ui.horizontal(|ui| { - ui.label("New composition name:"); + ui.label(tr("New composition name:")); let r = ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(260.0)); app.auto.add("dialog.precompose.name", r.rect, "New composition name"); }); ui.add_space(10.0); ui.add_enabled_ui(d.can_leave, |ui| { - let r = ui.radio_value(&mut d.leave, true, format!("Leave all attributes in \"{}\"", d.layer_name)); + let r = ui.radio_value(&mut d.leave, true, tr_args("Leave all attributes in \"{}\"", &[&d.layer_name])); app.auto.add("dialog.precompose.leave", r.rect, "Leave all attributes"); ui.label( egui::RichText::new(format!( @@ -133,24 +134,24 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let r = ui.radio_value(&mut d.leave, false, "Move all attributes into the new composition"); app.auto.add("dialog.precompose.move", r.rect, "Move all attributes"); ui.label( - egui::RichText::new("Use this option to place the currently selected layers together into a new intermediate composition.") + egui::RichText::new(tr("Use this option to place the currently selected layers together into a new intermediate composition.")) .color(t.text_dim) .size(11.0), ); ui.add_space(4.0); ui.add_enabled_ui(!d.leave, |ui| { - let r = ui.checkbox(&mut d.adjust, "Adjust composition duration to the time span of the selected layers"); + let r = ui.checkbox(&mut d.adjust, tr("Adjust composition duration to the time span of the selected layers")); app.auto.add("dialog.precompose.adjust", r.rect, "Adjust composition duration"); }); ui.add_space(8.0); - let r = ui.checkbox(&mut d.open, "Open New Composition"); + let r = ui.checkbox(&mut d.open, tr("Open New Composition")); app.auto.add("dialog.precompose.open", r.rect, "Open New Composition"); ui.add_space(14.0); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let r = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent)); + let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); app.auto.add("dialog.precompose.ok", r.rect, "OK"); ok = r.clicked(); - let r = ui.button("Cancel"); + let r = ui.button(tr("Cancel")); app.auto.add("dialog.precompose.cancel", r.rect, "Cancel"); cancel = r.clicked(); }); @@ -222,7 +223,7 @@ pub fn navigator(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } let fr = Rect::from_min_size(pos2(rect.max.x - 26.0, rect.min.y + 2.0), vec2(22.0, rect.height() - 4.0)); if crate::widgets::icon_button(ui, fr, crate::icons::Icon::Flowchart, false, &t, egui::Id::new("comp-nav-flow")) - .on_hover_text("Composition Mini-Flowchart") + .on_hover_text(tr("Composition Mini-Flowchart")) .clicked() { app.ui.mini_flowchart = Some([fr.min.x, fr.max.y]); @@ -300,7 +301,7 @@ pub fn mini_flowchart(app: &mut EffectcraftApp, ctx: &egui::Context) { } } if ups.is_empty() && downs.is_empty() { - p.text(pos2(rect.center().x, rect.max.y - 4.0), Align2::CENTER_BOTTOM, "No nested compositions", Tokens::ui(10.5), t.text_faint); + p.text(pos2(rect.center().x, rect.max.y - 4.0), Align2::CENTER_BOTTOM, tr("No nested compositions"), Tokens::ui(10.5), t.text_faint); } }); }); diff --git a/crates/ui-egui/src/panels/render_queue.rs b/crates/ui-egui/src/panels/render_queue.rs index f0fb2941..583c800b 100644 --- a/crates/ui-egui/src/panels/render_queue.rs +++ b/crates/ui-egui/src/panels/render_queue.rs @@ -11,6 +11,7 @@ //! every setting; entries needing numbers (custom time span and frame rate, crop, resize, bitrate, //! key frame interval, Make Template) open a form that runs the same command. +use crate::i18n::{tr, tr_args}; use effectcraft_engine::project::render_queue::{ AlphaMode, AudioFormat, AudioOutput, Channels, ColorDepth, CurrentOrOff, EffectsMode, FieldRender, OpusApplication, OutputFormat, PostRenderAction, ProResProfile, Pulldown, RESIZE_PRESETS, RenderLog, RenderQuality, RenderQueueItem, RenderStatus, ResizeQuality, SwitchOverride, TimeSpan, WebmVideoCodec, @@ -78,33 +79,33 @@ fn settings_menu(it: &RenderQueueItem, templates: &RenderTemplates) -> Vec<(Stri } v.push(("-".into(), Value::Null)); for (u, k) in effectcraft_engine::project::render_queue::ProxyUse::ALL.iter().zip(["current", "all", "comp", "none"]) { - v.push((mark(s.proxy_use == *u, &format!("Proxy Use: {}", u.label())), json!({"proxyUse": k}))); + v.push((mark(s.proxy_use == *u, &tr_args("Proxy Use: {}", &[&(u.label())])), json!({"proxyUse": k}))); } for (e, k) in EffectsMode::ALL.iter().zip(["current", "allOn", "allOff"]) { - v.push((mark(s.effects == *e, &format!("Effects: {}", e.label())), json!({"effects": k}))); + v.push((mark(s.effects == *e, &tr_args("Effects: {}", &[&(e.label())])), json!({"effects": k}))); } for (e, k) in [(CurrentOrOff::Current, "current"), (CurrentOrOff::AllOff, "allOff")] { - v.push((mark(s.solo == e, &format!("Solo Switches: {}", e.label())), json!({"solo": k}))); + v.push((mark(s.solo == e, &tr_args("Solo Switches: {}", &[&(e.label())])), json!({"solo": k}))); } for (e, k) in [(CurrentOrOff::Current, "current"), (CurrentOrOff::AllOff, "allOff")] { - v.push((mark(s.guide_layers == e, &format!("Guide Layers: {}", e.label())), json!({"guideLayers": k}))); + v.push((mark(s.guide_layers == e, &tr_args("Guide Layers: {}", &[&(e.label())])), json!({"guideLayers": k}))); } for (d, k) in ColorDepth::ALL.iter().zip(["current", "8", "16", "32"]) { - v.push((mark(s.color_depth == *d, &format!("Color Depth: {}", d.label())), json!({"colorDepth": k}))); + v.push((mark(s.color_depth == *d, &tr_args("Color Depth: {}", &[&(d.label())])), json!({"colorDepth": k}))); } v.push(("-".into(), Value::Null)); for (m, k) in SwitchOverride::ALL.iter().zip(["current", "onForChecked", "offForAll"]) { - v.push((mark(s.frame_blending == *m, &format!("Frame Blending: {}", m.label())), json!({"frameBlending": k}))); + v.push((mark(s.frame_blending == *m, &tr_args("Frame Blending: {}", &[&(m.label())])), json!({"frameBlending": k}))); } for (m, k) in SwitchOverride::ALL.iter().zip(["current", "onForChecked", "offForAll"]) { - v.push((mark(s.motion_blur_override() == *m, &format!("Motion Blur: {}", m.label())), json!({"motionBlur": k}))); + v.push((mark(s.motion_blur_override() == *m, &tr_args("Motion Blur: {}", &[&(m.label())])), json!({"motionBlur": k}))); } for (f, k) in FieldRender::ALL.iter().zip(["off", "upper", "lower"]) { - v.push((mark(s.field_render == *f, &format!("Field Render: {}", f.label())), json!({"fieldRender": k}))); + v.push((mark(s.field_render == *f, &tr_args("Field Render: {}", &[&(f.label())])), json!({"fieldRender": k}))); } if s.field_render != FieldRender::Off { for pd in Pulldown::ALL { - v.push((mark(s.pulldown == pd, &format!("3:2 Pulldown: {}", pd.label())), json!({"pulldown": pd.label()}))); + v.push((mark(s.pulldown == pd, &tr_args("3:2 Pulldown: {}", &[&(pd.label())])), json!({"pulldown": pd.label()}))); } } v.push(("-".into(), Value::Null)); @@ -136,7 +137,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren v.push(("-".into(), Value::Null)); for f in OutputFormat::ALL { if available.contains(&f) { - v.push((mark(o.format == f, &format!("Format: {}", f.label())), json!({"format": format!("{f:?}")}))); + v.push((mark(o.format == f, &tr_args("Format: {}", &[&(f.label())])), json!({"format": format!("{f:?}")}))); } } v.push(("-".into(), Value::Null)); @@ -168,7 +169,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren OutputFormat::WebM => { v.push(("-".into(), Value::Null)); for c in [WebmVideoCodec::Vp9, WebmVideoCodec::Av1] { - v.push((mark(o.webm_codec == c, &format!("Video Codec: {}", c.label())), json!({"webmCodec": c.label().to_ascii_lowercase()}))); + v.push((mark(o.webm_codec == c, &tr_args("Video Codec: {}", &[&(c.label())])), json!({"webmCodec": c.label().to_ascii_lowercase()}))); } if o.webm_codec == WebmVideoCodec::Av1 { codec_menu(o, &mut v); @@ -185,7 +186,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren v.push((mark(o.opus_bitrate_kbps == kbps, &format!("Opus Bitrate: {kbps} kbps")), json!({"audioBitrate": kbps}))); } for (a, k) in [(OpusApplication::Audio, "audio"), (OpusApplication::Voip, "voice")] { - v.push((mark(o.opus_application == a, &format!("Opus Tuning: {}", a.label())), json!({"opusApplication": k}))); + v.push((mark(o.opus_application == a, &tr_args("Opus Tuning: {}", &[&(a.label())])), json!({"opusApplication": k}))); } } OutputFormat::JpegSequence => { @@ -226,7 +227,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren (PostRenderAction::ImportAndReplace, "importAndReplace"), (PostRenderAction::SetProxy, "setProxy"), ] { - v.push((mark(it.post_render == a, &format!("Post-Render Action: {}", a.label())), json!({"postRenderAction": k}))); + v.push((mark(it.post_render == a, &tr_args("Post-Render Action: {}", &[&(a.label())])), json!({"postRenderAction": k}))); } v.push((mark(o.include_project_link, "Include Project Link"), json!({"includeProjectLink": !o.include_project_link}))); if o.format.supports_audio() { @@ -246,7 +247,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren v.push((mark(o.audio_channels != 1, "Audio Channels: Stereo"), json!({"audioChannels": "stereo"}))); if matches!(o.format, OutputFormat::ProRes | OutputFormat::Wav | OutputFormat::Aiff) { for f in [AudioFormat::S16, AudioFormat::S24, AudioFormat::F32] { - v.push((mark(o.audio_format == f, &format!("Sample Format: {}", f.label())), json!({"audioFormat": f.label()}))); + v.push((mark(o.audio_format == f, &tr_args("Sample Format: {}", &[&(f.label())])), json!({"audioFormat": f.label()}))); } } } @@ -259,7 +260,7 @@ fn codec_menu(o: &effectcraft_engine::project::render_queue::OutputModule, v: &m let c = &o.codec; v.push(("-".into(), Value::Null)); for p in CodecProfile::ALL { - v.push((mark(c.profile == p, &format!("Profile: {}", p.label())), json!({"profile": format!("{p:?}").to_ascii_lowercase()}))); + v.push((mark(c.profile == p, &tr_args("Profile: {}", &[&(p.label())])), json!({"profile": format!("{p:?}").to_ascii_lowercase()}))); } v.push(("-".into(), Value::Null)); v.push((mark(c.level.is_none(), "Level: Auto"), json!({"level": "auto"}))); @@ -271,7 +272,7 @@ fn codec_menu(o: &effectcraft_engine::project::render_queue::OutputModule, v: &m } v.push(("-".into(), Value::Null)); for r in [RateControlMode::Bitrate, RateControlMode::Quality] { - v.push((mark(c.rate_control == r, &format!("Rate Control: {}", r.label())), json!({"rateControl": format!("{r:?}").to_ascii_lowercase()}))); + v.push((mark(c.rate_control == r, &tr_args("Rate Control: {}", &[&(r.label())])), json!({"rateControl": format!("{r:?}").to_ascii_lowercase()}))); } if c.rate_control == RateControlMode::Bitrate { for kbps in [1_000u32, 2_000, 5_000, 10_000, 20_000, 40_000] { @@ -438,7 +439,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let top = Rect::from_min_size(rect.min, vec2(rect.width(), TOP_H)); p.rect_filled(top, 0.0, t.tl_ruler_bg); let x0 = rect.min.x + 12.0; - p.text(pos2(x0, top.min.y + 16.0), Align2::LEFT_CENTER, "Current Render", Tokens::semibold(12.5), t.text); + p.text(pos2(x0, top.min.y + 16.0), Align2::LEFT_CENTER, tr("Current Render"), Tokens::semibold(12.5), t.text); let cur_item = progress.as_ref().and_then(|p| p.current).and_then(|id| queue.iter().find(|i| i.id == id)); let cur_name = cur_item.and_then(|i| app.session.project.item(i.comp)).map(|i| i.name.clone()); if let Some(pr) = &progress { @@ -465,7 +466,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if widgets::checkbox(ui, nb, notify, &t, egui::Id::new("rq-notify")).clicked() { actions.push(("renderQueue.setNotify", json!({"notify": !notify}))); } - p.text(pos2(nb.max.x + 5.0, nb.center().y), Align2::LEFT_CENTER, "Notify", Tokens::ui(11.5), t.text_dim); + p.text(pos2(nb.max.x + 5.0, nb.center().y), Align2::LEFT_CENTER, tr("Notify"), Tokens::ui(11.5), t.text_dim); app.auto.add("renderQueue.notify", nb, if notify { "Notify: on" } else { "Notify: off" }); if rendering { if widgets::text_button(ui, stop_b, "Stop", false, &t, egui::Id::new("rq-stop")).clicked() { @@ -531,7 +532,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { ui.set_clip_rect(list.intersect(panel_clip)); let mut y = list.min.y - scroll.offset; if queue.is_empty() { - lp.text(pos2(list.center().x, list.min.y + 30.0), Align2::CENTER_CENTER, "The render queue is empty.", Tokens::ui(12.0), t.text_faint); + lp.text(pos2(list.center().x, list.min.y + 30.0), Align2::CENTER_CENTER, tr("The render queue is empty."), Tokens::ui(12.0), t.text_faint); } let editing_id = egui::Id::new("rq-edit-output"); let mut editing: Option<(u64, String)> = ui.data(|d| d.get_temp(editing_id)); @@ -559,19 +560,19 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } let item_id = it.id; row_resp.context_menu(|ui| { - if ui.button("Duplicate").clicked() { + if ui.button(tr("Duplicate")).clicked() { actions.push(("renderQueue.duplicate", json!({"item": item_id}))); ui.close(); } - if ui.button("Remove").clicked() { + if ui.button(tr("Remove")).clicked() { actions.push(("renderQueue.remove", json!({"item": item_id}))); ui.close(); } - if k > 0 && ui.button("Move Up").clicked() { + if k > 0 && ui.button(tr("Move Up")).clicked() { actions.push(("renderQueue.move", json!({"item": item_id, "to": n - 1}))); ui.close(); } - if n < queue.len() && ui.button("Move Down").clicked() { + if n < queue.len() && ui.button(tr("Move Down")).clicked() { actions.push(("renderQueue.move", json!({"item": item_id, "to": n + 1}))); ui.close(); } @@ -670,7 +671,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } // Log: Errors Only / Plus Settings / Plus Per Frame Info. let ld = Rect::from_min_size(pos2(r.max.x - 190.0, r.min.y + 3.0), vec2(150.0, ROW_H - 6.0)); - lp.text(pos2(ld.min.x - 6.0, r.center().y), Align2::RIGHT_CENTER, "Log:", Tokens::ui(11.5), t.text_dim); + lp.text(pos2(ld.min.x - 6.0, r.center().y), Align2::RIGHT_CENTER, tr("Log:"), Tokens::ui(11.5), t.text_dim); let lid = egui::Id::new(("rq-log", it.id)); if widgets::dropdown(ui, ld, it.log.label(), &t, lid).clicked() && !rendering { widgets::open_popup(ui, lid); @@ -684,7 +685,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } // Output To. let ox = dd.max.x + 14.0; - lp.text(pos2(ox, r.center().y), Align2::LEFT_CENTER, "Output To:", Tokens::ui(11.5), t.text_dim); + lp.text(pos2(ox, r.center().y), Align2::LEFT_CENTER, tr("Output To:"), Tokens::ui(11.5), t.text_dim); let odd = Rect::from_min_size(pos2(ox + 62.0, r.min.y + 3.0), vec2(22.0, ROW_H - 6.0)); let oid = egui::Id::new(("rq-out", it.id)); if widgets::dropdown(ui, odd, "", &t, oid).clicked() && !rendering { @@ -737,7 +738,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } let how = if dialog { "Click to choose where to save; Alt-click to edit the template" } else { "Click to edit" }; - resp.on_hover_text(format!("Template: {}\n{how}", it.output.output)); + resp.on_hover_text(tr_args("Template: {}\n{}", &[&it.output.output, &how])); app.auto.add(&aid("outputPath"), path_rect, &shown); } } diff --git a/crates/ui-egui/src/panels/rq_templates.rs b/crates/ui-egui/src/panels/rq_templates.rs index c60c3009..f0377113 100644 --- a/crates/ui-egui/src/panels/rq_templates.rs +++ b/crates/ui-egui/src/panels/rq_templates.rs @@ -6,6 +6,7 @@ //! `templates.default.`, `templates.select`, `templates.duplicate`, `templates.delete`, //! `templates.newName`, `templates.saveFromItem`, `templates.ok`. +use crate::i18n::{tr, tr_args}; use effectcraft_engine::project::render_templates::{RenderTemplates, TemplateKind, TemplateSlot}; use egui::{Color32, vec2}; use serde_json::{Value, json}; @@ -51,7 +52,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let mut close = false; let title = if st.output { "Output Module Templates" } else { "Render Settings Templates" }; super::dialogs::modal(ctx, title, vec2(600.0, 560.0), t, |ui| { - ui.label(egui::RichText::new("Defaults").font(Tokens::semibold(12.5)).color(t.text)); + ui.label(egui::RichText::new(tr("Defaults")).font(Tokens::semibold(12.5)).color(t.text)); egui::Grid::new("tpl-defaults").num_columns(2).spacing(vec2(14.0, 6.0)).show(ui, |ui| { for slot in TemplateSlot::ALL { ui.label(slot.label()); @@ -71,9 +72,9 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { }); ui.add_space(12.0); ui.separator(); - ui.label(egui::RichText::new("Settings").font(Tokens::semibold(12.5)).color(t.text)); + ui.label(egui::RichText::new(tr("Settings")).font(Tokens::semibold(12.5)).color(t.text)); ui.horizontal(|ui| { - ui.label("Settings Name:"); + ui.label(tr("Settings Name:")); let r = egui::ComboBox::from_id_salt("tpl-select").selected_text(&st.selected).width(300.0).show_ui(ui, |ui| { for n in &list { ui.selectable_value(&mut st.selected, n.clone(), n); @@ -106,7 +107,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { }); }); ui.horizontal(|ui| { - let r = ui.button("Duplicate"); + let r = ui.button(tr("Duplicate")); app.auto.add("templates.duplicate", r.rect, "Duplicate"); if r.clicked() { let mut n = format!("{} copy", st.selected); @@ -118,13 +119,13 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { actions.push(("renderQueue.saveTemplate", json!({"kind": kind, "name": n, "from": st.selected}))); st.selected = n; } - let r = ui.add_enabled(!builtin, egui::Button::new("Delete")); + let r = ui.add_enabled(!builtin, egui::Button::new(tr("Delete"))); app.auto.add("templates.delete", r.rect, "Delete"); if r.clicked() { actions.push(("renderQueue.deleteTemplate", json!({"kind": kind, "name": st.selected}))); } if builtin { - ui.label(egui::RichText::new("built-in").color(t.text_faint).size(11.0)); + ui.label(egui::RichText::new(tr("built-in")).color(t.text_faint).size(11.0)); } }); ui.add_space(8.0); @@ -132,12 +133,12 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { // New template from the selected (or only) Render Queue item. let queue = &app.session.project.render_queue; ui.horizontal(|ui| { - ui.label("New from Render Queue item:"); - let r = ui.add(egui::TextEdit::singleline(&mut st.new_name).hint_text("template name").desired_width(180.0)); + ui.label(tr("New from Render Queue item:")); + let r = ui.add(egui::TextEdit::singleline(&mut st.new_name).hint_text(tr("template name")).desired_width(180.0)); app.auto.add("templates.newName", r.rect, &st.new_name); let item = queue.last().map(|i| i.id); let ok = item.is_some() && !st.new_name.trim().is_empty(); - let r = ui.add_enabled(ok, egui::Button::new(format!("Save from #{}", queue.len().max(1)))); + let r = ui.add_enabled(ok, egui::Button::new(tr_args("Save from #{}", &[&(queue.len().max(1))]))); app.auto.add("templates.saveFromItem", r.rect, "Save from item"); if r.clicked() && let Some(id) = item @@ -149,7 +150,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { }); ui.add_space(12.0); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let r = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent)); + let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); app.auto.add("templates.ok", r.rect, "OK"); close = r.clicked(); }); diff --git a/crates/ui-egui/src/panels/scopes_panel.rs b/crates/ui-egui/src/panels/scopes_panel.rs index db5279d8..6665164e 100644 --- a/crates/ui-egui/src/panels/scopes_panel.rs +++ b/crates/ui-egui/src/panels/scopes_panel.rs @@ -3,6 +3,7 @@ //! time, with the colour standard (Rec. 601 / 709 / 2020), 8-bit or float scale and clamp. The //! maths is `effectcraft_raster::scopes`; agents read the same numbers with `scopes.analyze`. +use crate::i18n::tr; use effectcraft_engine::commands::panels_cmds::scope_frame; use effectcraft_engine::raster::scopes::{self, ColorStandard, Scope, ScopeKind, ScopeOpts}; use egui::{Align2, Color32, Rect, Stroke, pos2, vec2}; @@ -75,7 +76,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { p.rect_filled(area, 2.0, Color32::from_rgb(0x10, 0x10, 0x10)); app.auto.add("scopes.plot", area, kind.label()); let Some(cid) = app.session.active_comp_id() else { - p.text(area.center(), Align2::CENTER_CENTER, "Open a composition to see its scopes", Tokens::ui(12.0), t.text_faint); + p.text(area.center(), Align2::CENTER_CENTER, tr("Open a composition to see its scopes"), Tokens::ui(12.0), t.text_faint); return; }; // Plot rectangle: square for vectorscopes. diff --git a/crates/ui-egui/src/panels/shortcut_editor.rs b/crates/ui-egui/src/panels/shortcut_editor.rs index 4b21b30b..c0893c5b 100644 --- a/crates/ui-egui/src/panels/shortcut_editor.rs +++ b/crates/ui-egui/src/panels/shortcut_editor.rs @@ -15,6 +15,7 @@ //! `shortcuts.rename`, `shortcuts.delete`, `shortcuts.import`, `shortcuts.export`, //! `shortcuts.resetPreset`, `shortcuts.close`. +use crate::i18n::{tr, tr_args}; use effectcraft_engine::shortcuts::{APP_SCOPE, Bindable, DEFAULT_PRESET, normalize}; use egui::{Align2, Color32, Rect, RichText, Sense, Stroke, StrokeKind, pos2, vec2}; use serde_json::{Value, json}; @@ -237,7 +238,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { super::dialogs::modal(ctx, "Keyboard Shortcuts", vec2(1000.0, 720.0), t, |ui| { // ---- preset bar ui.horizontal(|ui| { - ui.label("Preset:"); + ui.label(tr("Preset:")); let r = egui::ComboBox::from_id_salt("sc-preset").selected_text(&active).width(220.0).show_ui(ui, |ui| { for n in &presets { if ui.selectable_label(*n == active, n).clicked() { @@ -247,7 +248,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { }); app.auto.add("shortcuts.preset", r.response.rect, "Preset"); let custom = active != DEFAULT_PRESET; - let b = ui.button("Duplicate"); + let b = ui.button(tr("Duplicate")); app.auto.add("shortcuts.duplicate", b.rect, "Duplicate"); if b.clicked() { run.push(("shortcuts.preset".into(), json!({"op": "duplicate", "from": active, "name": format!("{active} copy")}))); @@ -255,35 +256,35 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { if let Some(name) = &mut st.rename { let r = ui.add(egui::TextEdit::singleline(name).desired_width(140.0)); app.auto.add("shortcuts.renameField", r.rect, "New name"); - if ui.button("Rename").clicked() || (r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) { + if ui.button(tr("Rename")).clicked() || (r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) { run.push(("shortcuts.preset".into(), json!({"op": "rename", "name": active, "newName": name}))); st.rename = None; } } else { - let b = ui.add_enabled(custom, egui::Button::new("Rename")); + let b = ui.add_enabled(custom, egui::Button::new(tr("Rename"))); app.auto.add("shortcuts.rename", b.rect, "Rename"); if b.clicked() { st.rename = Some(active.clone()); } } - let b = ui.add_enabled(custom, egui::Button::new("Delete")); + let b = ui.add_enabled(custom, egui::Button::new(tr("Delete"))); app.auto.add("shortcuts.delete", b.rect, "Delete"); if b.clicked() { run.push(("shortcuts.preset".into(), json!({"op": "delete", "name": active}))); } - let b = ui.add_enabled(custom, egui::Button::new("Reset Preset")); + let b = ui.add_enabled(custom, egui::Button::new(tr("Reset Preset"))); app.auto.add("shortcuts.resetPreset", b.rect, "Reset Preset"); if b.clicked() { run.push(("shortcuts.reset".into(), json!({}))); } - let b = ui.button("Import..."); + let b = ui.button(tr("Import...")); app.auto.add("shortcuts.import", b.rect, "Import"); if b.clicked() && let Some(p) = app.hooks.pick_files.as_ref().and_then(|f| f(&["json"]).into_iter().next()) { run.push(("shortcuts.import".into(), json!({"path": p}))); } - let b = ui.button("Export..."); + let b = ui.button(tr("Export...")); app.auto.add("shortcuts.export", b.rect, "Export"); if b.clicked() && let Some(p) = app.hooks.pick_save_file.as_ref().and_then(|f| f(&format!("{active}.json"), "json")) @@ -294,7 +295,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { ui.add_space(6.0); // ---- modifiers + legend ui.horizontal(|ui| { - ui.label("Modifiers:"); + ui.label(tr("Modifiers:")); let names = if cfg!(target_os = "macos") { ["⌃ Ctrl", "⌘ Cmd", "⌥ Opt", "⇧ Shift"] } else { ["", "Ctrl", "Alt", "Shift"] }; for (i, (n, id)) in names.iter().zip(["ctrl", "cmd", "alt", "shift"]).enumerate() { if n.is_empty() { @@ -396,13 +397,13 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { p.rect_filled(info, 4.0, t.row); let p = ui.painter_at(info.shrink(2.0)); if let Some(k) = &st.key { - p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, format!("Key: {k}"), Tokens::semibold(12.0), t.tab_text_active); + p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, tr_args("Key: {}", &[&k]), Tokens::semibold(12.0), t.tab_text_active); let mut yy = info.min.y + 26.0; let mut list: Vec<(String, String, String)> = table.bindings().into_iter().filter(|(sc, _)| split(sc).1 == *k).map(|(sc, b)| (sc.to_string(), b.label.clone(), b.scope.clone())).collect(); list.sort(); if list.is_empty() { - p.text(pos2(info.min.x + 8.0, yy), Align2::LEFT_TOP, "Not assigned", Tokens::ui(11.5), t.text_dim); + p.text(pos2(info.min.x + 8.0, yy), Align2::LEFT_TOP, tr("Not assigned"), Tokens::ui(11.5), t.text_dim); } for (sc, label, scope) in list.into_iter().take(9) { let col = if scope == APP_SCOPE { APP_COLOR } else { PANEL_COLOR }; @@ -411,12 +412,12 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { yy += 17.0; } } else { - p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, "Click a key to see its commands.", Tokens::ui(11.5), t.text_dim); + p.text(info.left_top() + vec2(8.0, 6.0), Align2::LEFT_TOP, tr("Click a key to see its commands."), Tokens::ui(11.5), t.text_dim); } ui.add_space(4.0); // ---- search + command list + editor ui.horizontal(|ui| { - let r = ui.add(egui::TextEdit::singleline(&mut st.query).hint_text("Search commands or shortcuts").desired_width(360.0)); + let r = ui.add(egui::TextEdit::singleline(&mut st.query).hint_text(tr("Search commands or shortcuts")).desired_width(360.0)); app.auto.add("shortcuts.search", r.rect, "Search"); }); ui.add_space(4.0); @@ -476,7 +477,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { ui.vertical(|ui| { ui.set_width(310.0); let Some(b) = st.selected.as_ref().and_then(|k| table.find(k)).cloned() else { - ui.label(RichText::new("Select a command to change its shortcut.").color(t.text_dim)); + ui.label(RichText::new(tr("Select a command to change its shortcut.")).color(t.text_dim)); return; }; ui.label(RichText::new(&b.label).font(Tokens::semibold(13.0)).color(t.tab_text_active)); @@ -484,7 +485,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { ui.add_space(6.0); let keys = table.keys.get(&b.key).cloned().unwrap_or_default(); if keys.is_empty() { - ui.label(RichText::new("No shortcut").color(t.text_dim)); + ui.label(RichText::new(tr("No shortcut")).color(t.text_dim)); } for (n, k) in keys.iter().enumerate() { ui.horizontal(|ui| { @@ -509,7 +510,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { st.recording = false; } let norm = normalize(&st.pending); - let b2 = ui.add_enabled(norm.is_some(), egui::Button::new("Assign")); + let b2 = ui.add_enabled(norm.is_some(), egui::Button::new(tr("Assign"))); app.auto.add("shortcuts.assign", b2.rect, "Assign"); if b2.clicked() && let Some(k) = norm @@ -526,31 +527,31 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let conflicts = table.conflicts(&b, &k); if !conflicts.is_empty() { ui.add_space(4.0); - ui.label(RichText::new(format!("⚠ {} is used by:", crate::menus::shortcut_text(&k))).color(t.warning)); + ui.label(RichText::new(tr_args("⚠ {} is used by:", &[&(crate::menus::shortcut_text(&k))])).color(t.warning)); for c in conflicts.iter().take(4) { ui.label(RichText::new(format!(" {} ({})", c.label, c.scope)).color(t.warning)); } } } ui.add_space(6.0); - let r = ui.add_enabled(keys != b.defaults, egui::Button::new("Reset to Default")); + let r = ui.add_enabled(keys != b.defaults, egui::Button::new(tr("Reset to Default"))); app.auto.add("shortcuts.resetCommand", r.rect, "Reset to Default"); if r.clicked() { run.push(("shortcuts.reset".into(), json!({"command": b.command, "params": b.params}))); } if !b.defaults.is_empty() { let d: Vec = b.defaults.iter().map(|k| crate::menus::shortcut_text(k)).collect(); - ui.label(RichText::new(format!("Default: {}", d.join(", "))).color(t.text_faint)); + ui.label(RichText::new(tr_args("Default: {}", &[&(d.join(", "))])).color(t.text_faint)); } if active == DEFAULT_PRESET { ui.add_space(6.0); - ui.label(RichText::new("Changing a shortcut creates a custom preset; the default stays as it is.").color(t.text_faint).small()); + ui.label(RichText::new(tr("Changing a shortcut creates a custom preset; the default stays as it is.")).color(t.text_faint).small()); } }); }); ui.add_space(6.0); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let r = ui.add(egui::Button::new(RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent)); + let r = ui.add(egui::Button::new(RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); app.auto.add("shortcuts.close", r.rect, "OK"); close |= r.clicked(); });