diff --git a/crates/ui-egui/src/header.rs b/crates/ui-egui/src/header.rs index e43f7cba9..93d6f1ee3 100644 --- a/crates/ui-egui/src/header.rs +++ b/crates/ui-egui/src/header.rs @@ -1,6 +1,7 @@ //! The Tools bar: home, the tool slots, tool options, snapping, workspaces and the community //! buttons (Discord is always one click away). +use crate::i18n::tr; use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2}; use serde_json::json; @@ -27,14 +28,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { paint_logo(&p, brand); let bresp = ui.interact(brand, egui::Id::new("brand"), Sense::click()); app.auto.add("header.about", brand, "About EffectCraft"); - if bresp.on_hover_text("About EffectCraft").clicked() { + if bresp.on_hover_text(tr("About EffectCraft")).clicked() { app.dialog = Some(Dialog::About); } x += 32.0; // Home. let home = Rect::from_min_size(pos2(x, cy - 13.0), vec2(26.0, 26.0)); - if widgets::icon_button(ui, home, Icon::Home, app.ui.start_screen, &t, egui::Id::new("tool-home")).on_hover_text("Home").clicked() { + if widgets::icon_button(ui, home, Icon::Home, app.ui.start_screen, &t, egui::Id::new("tool-home")).on_hover_text(tr("Home")).clicked() { app.ui.start_screen = !app.ui.start_screen; } app.auto.add("header.home", home, "Home"); @@ -171,7 +172,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { app.ui.snapping = app.session.state.snapping; } app.auto.add("header.snapping", snap, "Snapping"); - let label = p.text(pos2(snap.max.x + 4.0, cy), Align2::LEFT_CENTER, "Snapping", Tokens::ui(12.0), t.text_dim); + let label = p.text(pos2(snap.max.x + 4.0, cy), Align2::LEFT_CENTER, tr("Snapping"), Tokens::ui(12.0), t.text_dim); snapping_options(app, ui, Rect::from_min_size(pos2(label.max.x + 2.0, cy - 9.0), vec2(18.0, 18.0))); // Right side: community buttons, workspaces. @@ -181,9 +182,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let dc = Color32::from_rgb(0x58, 0x65, 0xf2); p.rect_filled(discord, 13.0, if dresp.hovered() { dc.gamma_multiply(1.2) } else { dc }); icons::paint(&p, Rect::from_center_size(pos2(discord.min.x + 16.0, cy), vec2(14.0, 14.0)), Icon::Chat, Color32::WHITE); - p.text(pos2(discord.min.x + 28.0, cy), Align2::LEFT_CENTER, "Discord", Tokens::semibold(12.0), Color32::WHITE); + p.text(pos2(discord.min.x + 28.0, cy), Align2::LEFT_CENTER, tr("Discord"), Tokens::semibold(12.0), Color32::WHITE); app.auto.add("header.discord", discord, "Join the ArtCraft Discord"); - if dresp.on_hover_text("Join the ArtCraft community on Discord").clicked() { + if dresp.on_hover_text(tr("Join the ArtCraft community on Discord")).clicked() { let _ = app.session.execute("help.discord", json!({})); } rx = discord.min.x - 6.0; @@ -256,7 +257,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { fn snapping_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, r: Rect) { let t = app.tokens; let pop = egui::Id::new("snap-options"); - if widgets::icon_button(ui, r, Icon::ChevronDown, false, &t, egui::Id::new("snap-options-button")).on_hover_text("Snapping options").clicked() { + if widgets::icon_button(ui, r, Icon::ChevronDown, false, &t, egui::Id::new("snap-options-button")).on_hover_text(tr("Snapping options")).clicked() { widgets::open_popup(ui, pop); } app.auto.add("header.snappingOptions", r, "Snapping options"); @@ -409,13 +410,13 @@ fn paint_options_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, key: &str, t } } p.text(row(33.0) + vec2(134.0, 0.0), Align2::LEFT_CENTER, tp.kind.label(), Tokens::ui(11.5), t.text_dim); - p.text(row(66.0), Align2::LEFT_CENTER, "Blend Mode:", Tokens::ui(12.0), t.text_dim); + p.text(row(66.0), Align2::LEFT_CENTER, tr("Blend Mode:"), Tokens::ui(12.0), t.text_dim); blend_rect = Rect::from_min_size(row(56.0) + vec2(84.0, 0.0), vec2(150.0, 20.0)); if widgets::dropdown(ui, blend_rect, tp.blend.label(), &t, id.with("blend-dd")).clicked() { widgets::open_popup(ui, blend_pop); } app.auto.add(&format!("header.{key}Options.blend"), blend_rect, "Blend Mode"); - p.text(row(98.0), Align2::LEFT_CENTER, "Opacity:", Tokens::ui(12.0), t.text_dim); + p.text(row(98.0), Align2::LEFT_CENTER, tr("Opacity:"), Tokens::ui(12.0), t.text_dim); let (or, v, _) = widgets::hot_number_at(ui, row(89.0) + vec2(84.0, 0.0), id.with("opacity"), tp.opacity, 0.5, (0.0, 100.0), 0, "%", &t); app.auto.add(&format!("header.{key}Options.opacity"), or, "Opacity"); if let Some(v) = v { @@ -485,14 +486,14 @@ fn paint_swatch(p: &egui::Painter, r: Rect, paint: &ToolPaint, hovered: bool, t: fn puppet_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, mut x: f32, cy: f32) -> f32 { let t = app.tokens; let o = app.session.state.puppet.clone(); - p.text(pos2(x, cy), Align2::LEFT_CENTER, "Mesh:", Tokens::ui(12.0), t.text_dim); + p.text(pos2(x, cy), Align2::LEFT_CENTER, tr("Mesh:"), Tokens::ui(12.0), t.text_dim); x += 40.0; let show = Rect::from_min_size(pos2(x, cy - 10.0), vec2(20.0, 20.0)); if widgets::checkbox(ui, show, o.show_mesh, &t, egui::Id::new("puppet-show-mesh")).clicked() { let _ = app.session.execute("puppet.mesh", json!({"showMesh": !o.show_mesh})); } app.auto.add("header.puppet.showMesh", show, "Show mesh"); - p.text(pos2(show.max.x + 2.0, cy), Align2::LEFT_CENTER, "Show", Tokens::ui(12.0), t.text_dim); + p.text(pos2(show.max.x + 2.0, cy), Align2::LEFT_CENTER, tr("Show"), Tokens::ui(12.0), t.text_dim); x = show.max.x + 44.0; for (label, key, v, range) in [("Expansion:", "expansion", o.expansion, (-100.0, 200.0)), ("Density:", "density", o.density, (0.0, 100.0))] { p.text(pos2(x, cy), Align2::LEFT_CENTER, label, Tokens::ui(12.0), t.text_dim); @@ -508,7 +509,7 @@ fn puppet_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter let label = "Record Options..."; let w = 104.0; let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(w, 20.0)); - let resp = ui.interact(r, egui::Id::new("puppet-record-options"), egui::Sense::click()).on_hover_text("⌘/Ctrl-drag a pin to record its motion"); + let resp = ui.interact(r, egui::Id::new("puppet-record-options"), egui::Sense::click()).on_hover_text(tr("⌘/Ctrl-drag a pin to record its motion")); p.rect_stroke(r, 3.0, egui::Stroke::new(1.0, if resp.hovered() { t.accent } else { t.field_border }), egui::StrokeKind::Inside); p.text(r.center(), Align2::CENTER_CENTER, label, Tokens::ui(11.5), t.text); app.auto.add("header.puppet.recordOptions", r, label); @@ -523,7 +524,7 @@ fn puppet_options(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter let r = Rect::from_min_size(pos2(r.max.x + 8.0, cy - 10.0), vec2(w, 20.0)); let resp = ui .interact(r, egui::Id::new("puppet-follow"), egui::Sense::click()) - .on_hover_text("Select the leader pin, then Shift-click the pins that should trail it (hair, cloth, tails)"); + .on_hover_text(tr("Select the leader pin, then Shift-click the pins that should trail it (hair, cloth, tails)")); p.rect_stroke(r, 3.0, egui::Stroke::new(1.0, if resp.hovered() { t.accent } else { t.field_border }), egui::StrokeKind::Inside); p.text(r.center(), Align2::CENTER_CENTER, label, Tokens::ui(11.5), t.text); app.auto.add("header.puppet.follow", r, label); @@ -544,7 +545,7 @@ fn ws_menu(app: &mut EffectcraftApp, ui: &mut egui::Ui) { } } ui.separator(); - if ui.button("Reset to Saved Layout").clicked() { + if ui.button(tr("Reset to Saved Layout")).clicked() { let n = app.ui.workspace.clone(); app.set_workspace(&n); ui.close(); diff --git a/crates/ui-egui/src/panels/anim_tools.rs b/crates/ui-egui/src/panels/anim_tools.rs index 5da48415a..15a4f4f89 100644 --- a/crates/ui-egui/src/panels/anim_tools.rs +++ b/crates/ui-egui/src/panels/anim_tools.rs @@ -6,6 +6,7 @@ //! Start Capture, pressing in the viewer records the pointer path in real time while the comp //! plays; releasing turns it into Position keys. +use crate::i18n::{tr, tr_args}; use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2}; use serde_json::json; @@ -170,7 +171,7 @@ pub fn motion_sketch(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { label(&p, x0, y, "Smoothing:", &t); st.sketch_smoothing = number(app, ui, pos2(xv, y), "motionSketch.smoothing", st.sketch_smoothing, 0.1, (0.0, 100.0), 1, ""); y += ROW + 2.0; - p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Show:", Tokens::ui(12.0), t.text_dim); + p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Show:"), Tokens::ui(12.0), t.text_dim); y += ROW - 6.0; st.sketch_wireframe = checkbox(app, ui, pos2(x0 + 8.0, y), "motionSketch.wireframe", "Wireframe", st.sketch_wireframe); y += ROW - 4.0; @@ -183,7 +184,7 @@ pub fn motion_sketch(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let fr = c.frame_rate; let (s, e) = (app.session.time(), c.work_area.1); let tc = |x| crate::panels::timecode(&app.session, c, x); - p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, format!("Start: {} Duration: {}", tc(s), tc(e - s)), Tokens::ui(11.0), t.text_faint); + p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr_args("Start: {} Duration: {}", &[&(tc(s)), &(tc(e - s))]), Tokens::ui(11.0), t.text_faint); let _ = fr; y += ROW; let b = Rect::from_min_size(pos2(x0, y), vec2(120.0, 24.0)); @@ -290,7 +291,7 @@ pub fn sketch_overlay(app: &mut EffectcraftApp, ui: &mut egui::Ui) { ctx.data_mut(|d| d.insert_temp(capture_id(), c)); } None => { - painter.text(area.center_top() + vec2(0.0, 18.0), Align2::CENTER_CENTER, "Motion Sketch: drag to record", Tokens::ui(12.0), Color32::WHITE); + painter.text(area.center_top() + vec2(0.0, 18.0), Align2::CENTER_CENTER, tr("Motion Sketch: drag to record"), Tokens::ui(12.0), Color32::WHITE); } } } diff --git a/crates/ui-egui/src/panels/camera_tracker_ui.rs b/crates/ui-egui/src/panels/camera_tracker_ui.rs index 9434f5373..18e248135 100644 --- a/crates/ui-egui/src/panels/camera_tracker_ui.rs +++ b/crates/ui-egui/src/panels/camera_tracker_ui.rs @@ -13,6 +13,7 @@ //! //! Everything is dispatched to the `camera.*` commands. +use crate::i18n::{tr, tr_args}; use std::sync::Arc; use effectcraft_engine::Session; @@ -297,7 +298,7 @@ pub fn viewer_hook( let some = !pts.is_empty(); let many = pts.len() > 1; let mut pick: Option<(&str, serde_json::Value)> = None; - if ui.add_enabled(some, egui::Button::new("Set Ground Plane and Origin")).clicked() { + if ui.add_enabled(some, egui::Button::new(tr("Set Ground Plane and Origin"))).clicked() { pick = Some(("camera.setGroundPlane", with(None, false))); } ui.separator(); @@ -320,7 +321,7 @@ pub fn viewer_hook( } } ui.separator(); - if ui.add_enabled(some, egui::Button::new("Delete Selected Points")).clicked() { + if ui.add_enabled(some, egui::Button::new(tr("Delete Selected Points"))).clicked() { pick = Some(("camera.deletePoints", with(None, false))); } if let Some((cmd, q)) = pick { @@ -419,7 +420,7 @@ pub fn editor( let status = match app.session.camera_progress() { Some(pr) if running => pr.banner(), _ => match &solve { - Some(sv) => format!("Method Used: {} Average Error: {:.2} pixels", sv.method_used.label(), sv.average_error), + Some(sv) => tr_args("Method Used: {} Average Error: {} pixels", &[&(sv.method_used.label()), &format!("{:.2}", sv.average_error)]), None if ct::tracks(¶ms).is_some() && app.session.camera_pending.iter().any(|(_, l, u)| *l == layer.id && *u == g.uid) => { "Waiting to solve".into() } diff --git a/crates/ui-egui/src/panels/expr_bar.rs b/crates/ui-egui/src/panels/expr_bar.rs index 09d4a608b..fc985d150 100644 --- a/crates/ui-egui/src/panels/expr_bar.rs +++ b/crates/ui-egui/src/panels/expr_bar.rs @@ -3,6 +3,7 @@ //! Clicking the message selects the layer and reveals the property in the timeline. //! Automation ids: `viewer.exprErrors`, `viewer.exprErrors.prev`, `viewer.exprErrors.next`. +use crate::i18n::tr; use effectcraft_engine::commands::expr_tools::{self, ExprError}; use effectcraft_engine::project::ItemId; use egui::{Align2, Color32, Rect, Sense, pos2, vec2}; @@ -77,7 +78,7 @@ pub fn draw(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, cid: ItemId let comp_name = app.session.project.item(cid).map(|it| it.name.clone()).unwrap_or_default(); let e = c.errors[i].clone(); let msg_rect = Rect::from_min_max(pos2(rect.min.x + 26.0, rect.min.y), pos2(prev.min.x - 6.0, rect.max.y)); - let resp = ui.interact(msg_rect, egui::Id::new("expr-bar-msg"), Sense::click()).on_hover_text("Click to reveal the expression in the timeline"); + let resp = ui.interact(msg_rect, egui::Id::new("expr-bar-msg"), Sense::click()).on_hover_text(tr("Click to reveal the expression in the timeline")); p.with_clip_rect(msg_rect).text( pos2(msg_rect.min.x, rect.center().y), Align2::LEFT_CENTER, diff --git a/crates/ui-egui/src/panels/flowchart.rs b/crates/ui-egui/src/panels/flowchart.rs index 2ba345cf2..f59d35982 100644 --- a/crates/ui-egui/src/panels/flowchart.rs +++ b/crates/ui-egui/src/panels/flowchart.rs @@ -6,6 +6,7 @@ //! [`build`] is pure and cycle-safe (a comp nested in itself is drawn once, with an edge back), //! so agents get the same graph through `flowchart.graph`. +use crate::i18n::tr_args; use std::collections::{BTreeMap, BTreeSet}; use effectcraft_engine::project::{ItemId, ItemKind, LayerSource, Project}; @@ -256,7 +257,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let bar = Rect::from_min_size(rect.min, vec2(rect.width(), 30.0)); p.rect_filled(bar, 0.0, t.panel_bg); let root_name = app.session.project.item(rid).map(|i| i.name.clone()).unwrap_or_default(); - p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, format!("Flowchart: {root_name}"), Tokens::medium(12.0), t.text); + p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, tr_args("Flowchart: {}", &[&root_name]), Tokens::medium(12.0), t.text); let mut x = bar.max.x - 8.0; let mut cmds: Vec<(String, Value)> = vec![]; let o = app.ui.flowchart.clone(); @@ -363,7 +364,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { _ => {} } } - resp.on_hover_text(format!("{} — double-click to open, {}-click to reveal", n.label, if cfg!(target_os = "macos") { "Cmd" } else { "Ctrl" })); + resp.on_hover_text(tr_args("{} — double-click to open, {}-click to reveal", &[&n.label, &(if cfg!(target_os = "macos") { "Cmd" } else { "Ctrl" })])); } for (id, params) in cmds { if let Err(e) = crate::menus::invoke(app, &ctx, &id, params) { diff --git a/crates/ui-egui/src/panels/footage_panel.rs b/crates/ui-egui/src/panels/footage_panel.rs index 70ba94b5a..0229ce963 100644 --- a/crates/ui-egui/src/panels/footage_panel.rs +++ b/crates/ui-egui/src/panels/footage_panel.rs @@ -3,6 +3,7 @@ //! Insert Edit — that cut the marked range into the active composition at its current time. //! Everything runs `footage.*` commands (state in `EditorState::footage_panel`). +use crate::i18n::{tr, tr_args}; use effectcraft_engine::project::{FootageKind, ItemKind}; use effectcraft_engine::time::Tick; use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2}; @@ -18,7 +19,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let p = ui.painter().with_clip_rect(rect); p.rect_filled(rect, 0.0, t.pasteboard); let Some(view) = app.session.state.footage_panel.clone() else { - p.text(rect.center(), Align2::CENTER_CENTER, "Double-click footage in the Project panel to open it here", Tokens::ui(12.0), t.text_faint); + p.text(rect.center(), Align2::CENTER_CENTER, tr("Double-click footage in the Project panel to open it here"), Tokens::ui(12.0), t.text_faint); app.auto.add("footage.empty", rect, "Footage panel (empty)"); return; }; @@ -38,7 +39,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Title strip. let top = Rect::from_min_size(rect.min, vec2(rect.width(), 24.0)); p.rect_filled(top, 0.0, t.panel_bg); - p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, format!("Footage: {}", item.name), Tokens::semibold(12.0), t.text); + p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, tr_args("Footage: {}", &[&item.name]), Tokens::semibold(12.0), t.text); app.auto.add("footage.title", top, &item.name); // Bottom: ruler and controls. // Rows, bottom up: Edit Target buttons, transport, ruler. @@ -73,7 +74,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let c = so.color; p.rect_filled(area.shrink(20.0), 0.0, Color32::from_rgb((c[0] * 255.0) as u8, (c[1] * 255.0) as u8, (c[2] * 255.0) as u8)); } else { - p.text(area.center(), Align2::CENTER_CENTER, "No video", Tokens::ui(12.0), t.text_faint); + p.text(area.center(), Align2::CENTER_CENTER, tr("No video"), Tokens::ui(12.0), t.text_faint); } // Ruler: ticks, In/Out span, CTI; click or drag to scrub. let x_of = |tt: Tick| ruler.min.x + (tt.seconds() / dur.seconds().max(1e-9)) as f32 * ruler.width(); @@ -139,7 +140,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let ey = edit_row.min.y + 5.0; let bx = (edit_row.max.x - 238.0).max(edit_row.min.x + 10.0); if !target.is_empty() && bx > edit_row.min.x + 140.0 { - p.text(pos2(bx - 8.0, edit_row.center().y), Align2::RIGHT_CENTER, format!("Edit Target: {target}"), Tokens::ui(11.0), t.text_dim); + p.text(pos2(bx - 8.0, edit_row.center().y), Align2::RIGHT_CENTER, tr_args("Edit Target: {}", &[&target]), Tokens::ui(11.0), t.text_dim); } if kit::button(app, ui, Rect::from_min_size(pos2(bx, ey), vec2(110.0, 20.0)), "footage.rippleInsertEdit", "Ripple Insert Edit", false) && comp_ok { kit::exec(app, "footage.rippleInsertEdit", json!({})); diff --git a/crates/ui-egui/src/panels/graph.rs b/crates/ui-egui/src/panels/graph.rs index 8d7bcbdd0..7369c634a 100644 --- a/crates/ui-egui/src/panels/graph.rs +++ b/crates/ui-egui/src/panels/graph.rs @@ -244,7 +244,13 @@ pub(crate) fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painte gv += step; } if cs.is_empty() { - p.text(plot.center(), Align2::CENTER_CENTER, "Select animated properties to show them in the Graph Editor", Tokens::ui(12.0), t.text_faint); + p.text( + plot.center(), + Align2::CENTER_CENTER, + crate::i18n::tr("Select animated properties to show them in the Graph Editor"), + Tokens::ui(12.0), + t.text_faint, + ); } // Show Reference Graph: the other graph type, faint, scaled to the plot. diff --git a/crates/ui-egui/src/panels/info.rs b/crates/ui-egui/src/panels/info.rs index b792538b8..581e43344 100644 --- a/crates/ui-egui/src/panels/info.rs +++ b/crates/ui-egui/src/panels/info.rs @@ -1,5 +1,6 @@ //! Info panel: colour and position under the pointer, plus the current selection. +use crate::i18n::{tr, tr_args}; use egui::{Align2, Color32, Rect, pos2, vec2}; use crate::theme::Tokens; @@ -59,8 +60,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if let Some(c) = comp { let sel: Vec = app.session.state.selected_layers.iter().filter_map(|id| c.layer(*id)).map(|l| l.name.clone()).collect(); let tc = crate::panels::timecode(&app.session, &c, app.session.time()); - p.text(pos2(x0, y), Align2::LEFT_CENTER, if sel.is_empty() { "No layer selected".to_string() } else { sel.join(", ") }, Tokens::semibold(12.0), t.text); - p.text(pos2(x0, y + 18.0), Align2::LEFT_CENTER, format!("Time: {tc}"), Tokens::ui(12.0), t.text_dim); + p.text( + pos2(x0, y), + Align2::LEFT_CENTER, + if sel.is_empty() { tr("No layer selected").to_string() } else { sel.join(", ") }, + Tokens::semibold(12.0), + t.text, + ); + p.text(pos2(x0, y + 18.0), Align2::LEFT_CENTER, tr_args("Time: {}", &[&tc]), Tokens::ui(12.0), t.text_dim); // One layer selected: Duration, In and Out like AE's Info panel. let mut ly = y + 36.0; if let [id] = app.session.state.selected_layers.as_slice() @@ -93,7 +100,13 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if app.session.prefs.composition.show_rendering_progress { let ms = app.frames.last_ms.lock().map(|v| *v).unwrap_or(0.0); let rq = app.session.render_job.as_ref().map(|_| " • Rendering queue…").unwrap_or(""); - p.text(pos2(x0, ly), Align2::LEFT_CENTER, format!("Render: {ms:.0} ms • UI {:.0} fps{rq}", app.fps), Tokens::ui(11.5), t.text_faint); + p.text( + pos2(x0, ly), + Align2::LEFT_CENTER, + tr_args("Render: {} ms • UI {} fps{}", &[&format!("{ms:.0}"), &format!("{:.0}", app.fps), &rq]), + Tokens::ui(11.5), + t.text_faint, + ); } if !app.session.state.selected_keys.is_empty() { // One key: its property, time and value (as After Effects' Info panel shows them). diff --git a/crates/ui-egui/src/panels/layer_panel.rs b/crates/ui-egui/src/panels/layer_panel.rs index e7a689f96..95269bae9 100644 --- a/crates/ui-egui/src/panels/layer_panel.rs +++ b/crates/ui-egui/src/panels/layer_panel.rs @@ -11,6 +11,7 @@ //! overlay, the segmentation span bar and the view / Freeze buttons; the Roto Brush and Refine //! Edge tools paint their strokes here (see [`super::roto_tool`]). +use crate::i18n::{tr, tr_args}; use effectcraft_engine::effects::paint; use effectcraft_engine::project::{Layer, LayerId}; use effectcraft_engine::render::{EvalCtx, RenderOpts, Renderer}; @@ -67,7 +68,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let p = ui.painter().with_clip_rect(rect); let (Some(cid), Some(layer)) = (app.session.active_comp_id(), current_layer(app)) else { p.rect_filled(rect, 0.0, t.pasteboard); - p.text(rect.center(), Align2::CENTER_CENTER, "Double-click a layer to open it in the Layer panel", Tokens::ui(12.0), t.text_faint); + p.text(rect.center(), Align2::CENTER_CENTER, tr("Double-click a layer to open it in the Layer panel"), Tokens::ui(12.0), t.text_faint); app.auto.add("layerPanel.empty", rect, "Layer panel (empty)"); return; }; @@ -80,12 +81,12 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Title strip. let top = Rect::from_min_size(rect.min, vec2(rect.width(), 24.0)); p.rect_filled(top, 0.0, t.panel_bg); - p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, format!("Layer: {}", layer.name), Tokens::semibold(12.0), t.text); + p.text(pos2(top.min.x + 10.0, top.center().y), Align2::LEFT_CENTER, tr_args("Layer: {}", &[&layer.name]), Tokens::semibold(12.0), t.text); app.auto.add("layerPanel.title", top, &layer.name); // Bottom bar: View menu. let bar = Rect::from_min_max(pos2(rect.min.x, rect.max.y - 30.0), rect.max); p.rect_filled(bar, 0.0, t.panel_bg); - p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, "View:", Tokens::ui(12.0), t.text_dim); + p.text(pos2(bar.min.x + 10.0, bar.center().y), Align2::LEFT_CENTER, tr("View:"), Tokens::ui(12.0), t.text_dim); let mut names = vec!["Masks".to_string()]; if let Some(fx) = layer.effects() { names.extend(fx.groups().map(|g| g.name.clone())); diff --git a/crates/ui-egui/src/panels/markers_ui.rs b/crates/ui-egui/src/panels/markers_ui.rs index e66d9c3c7..89a428a60 100644 --- a/crates/ui-egui/src/panels/markers_ui.rs +++ b/crates/ui-egui/src/panels/markers_ui.rs @@ -8,6 +8,7 @@ //! composition marker bin, at the right end of the time ruler, adds a composition marker where it //! is dropped. Every change is an engine command, so it is undoable and agent-drivable. +use crate::i18n::tr; use effectcraft_engine::project::{Comp, Layer, Marker}; use effectcraft_engine::time::Tick; use egui::{Align2, Color32, Pos2, Rect, Sense, Stroke, pos2, vec2}; @@ -126,46 +127,46 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let title = d.title.clone(); super::dialogs::modal(ctx, &title, vec2(520.0, 560.0), t, |ui| { egui::Grid::new("marker-grid").num_columns(2).spacing([12.0, 8.0]).show(ui, |ui| { - ui.label("Time:"); + ui.label(tr("Time:")); let r = ui.add(egui::DragValue::new(&mut d.time).speed(0.01).range(0.0..=1e6).suffix(" s").max_decimals(3)); app.auto.add("dialog.marker.time", r.rect, "Time"); ui.end_row(); - ui.label("Duration:"); + ui.label(tr("Duration:")); let r = ui.add(egui::DragValue::new(&mut d.duration).speed(0.01).range(0.0..=1e6).suffix(" s").max_decimals(3)); app.auto.add("dialog.marker.duration", r.rect, "Duration"); ui.end_row(); - ui.label("Comment:"); + ui.label(tr("Comment:")); let r = ui.add(egui::TextEdit::multiline(&mut d.comment).desired_rows(3).desired_width(320.0)); app.auto.add("dialog.marker.comment", r.rect, "Comment"); ui.end_row(); - ui.label("Chapter:"); + ui.label(tr("Chapter:")); text_field(ui, app, "dialog.marker.chapter", &mut d.chapter, 320.0); ui.end_row(); - ui.label("URL:"); + ui.label(tr("URL:")); text_field(ui, app, "dialog.marker.url", &mut d.url, 320.0); ui.end_row(); - ui.label("Frame Target:"); + ui.label(tr("Frame Target:")); text_field(ui, app, "dialog.marker.frameTarget", &mut d.frame_target, 320.0); ui.end_row(); }); ui.add_space(8.0); - let r = ui.checkbox(&mut d.cue, "Cue Point"); + let r = ui.checkbox(&mut d.cue, tr("Cue Point")); app.auto.add("dialog.marker.cue", r.rect, "Cue Point"); ui.add_enabled_ui(d.cue, |ui| { ui.horizontal(|ui| { - let r = ui.radio_value(&mut d.cue_navigation, false, "Event"); + let r = ui.radio_value(&mut d.cue_navigation, false, tr("Event")); app.auto.add("dialog.marker.cue.event", r.rect, "Event"); - let r = ui.radio_value(&mut d.cue_navigation, true, "Navigation"); + let r = ui.radio_value(&mut d.cue_navigation, true, tr("Navigation")); app.auto.add("dialog.marker.cue.navigation", r.rect, "Navigation"); - ui.label("Name:"); + ui.label(tr("Name:")); text_field(ui, app, "dialog.marker.cue.name", &mut d.cue_name, 160.0); }); let mut remove = None; for (i, (k, v)) in d.cue_params.iter_mut().enumerate() { ui.horizontal(|ui| { - ui.label("Parameter Name:"); + ui.label(tr("Parameter Name:")); text_field(ui, app, &format!("dialog.marker.cue.param.{i}.name"), k, 110.0); - ui.label("Value:"); + ui.label(tr("Value:")); text_field(ui, app, &format!("dialog.marker.cue.param.{i}.value"), v, 110.0); let r = ui.small_button("−"); app.auto.add(&format!("dialog.marker.cue.param.{i}.remove"), r.rect, "Remove"); @@ -185,10 +186,10 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { }); ui.add_space(8.0); ui.horizontal(|ui| { - let r = ui.checkbox(&mut d.protected, "Protected Region"); + let r = ui.checkbox(&mut d.protected, tr("Protected Region")); app.auto.add("dialog.marker.protected", r.rect, "Protected Region"); ui.add_space(20.0); - ui.label("Label:"); + ui.label(tr("Label:")); let labels = effectcraft_engine::color::Label::ALL; let cur = labels.get(d.label).copied().unwrap_or_default(); let r = egui::ComboBox::from_id_salt("dialog.marker.label").selected_text(app.session.prefs.label_name(cur)).show_ui(ui, |ui| { @@ -202,14 +203,14 @@ pub fn dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { }); ui.add_space(14.0); ui.horizontal(|ui| { - let r = ui.button("Delete"); + let r = ui.button(tr("Delete")); app.auto.add("dialog.marker.delete", r.rect, "Delete"); delete = r.clicked(); 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(app.tokens.accent)); + let r = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(app.tokens.accent)); app.auto.add("dialog.marker.ok", r.rect, "OK"); ok = r.clicked(); - let r = ui.button("Cancel"); + let r = ui.button(tr("Cancel")); app.auto.add("dialog.marker.cancel", r.rect, "Cancel"); cancel = r.clicked(); }); @@ -383,7 +384,7 @@ pub(crate) fn comp_markers(app: &mut EffectcraftApp, ui: &mut egui::Ui, comp: &C /// one undo step. Dropped back on the bin or outside the time graph's width, nothing is added. pub(crate) fn marker_bin(app: &mut EffectcraftApp, ui: &mut egui::Ui, comp: &Comp, tm: TMap, bin: Rect, strip: Rect) { let t = app.tokens; - let resp = ui.interact(bin, egui::Id::new("marker-bin"), Sense::drag()).on_hover_text("Composition marker bin: drag a marker to the time ruler"); + let resp = ui.interact(bin, egui::Id::new("marker-bin"), Sense::drag()).on_hover_text(tr("Composition marker bin: drag a marker to the time ruler")); app.auto.add("timeline.markerBin", bin, "Composition marker bin"); let hot = resp.hovered() || resp.dragged(); if hot { diff --git a/crates/ui-egui/src/panels/paint_panels.rs b/crates/ui-egui/src/panels/paint_panels.rs index 42a424078..62f270558 100644 --- a/crates/ui-egui/src/panels/paint_panels.rs +++ b/crates/ui-egui/src/panels/paint_panels.rs @@ -3,6 +3,7 @@ //! Both edit the session's paint options through `paint.options` / `paint.brushPreset`, so //! agents see and set exactly what the panels show. +use crate::i18n::tr; use effectcraft_engine::effects::paint::{BRUSH_PRESETS, CHANNELS, DURATIONS, ERASE_MODES, MODES}; use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2}; use serde_json::{Value, json}; @@ -112,7 +113,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let sresp = ui.interact(swap, egui::Id::new("paint-swap"), Sense::click()); p.text(swap.center(), Align2::CENTER_CENTER, "⇄", Tokens::ui(12.0), if sresp.hovered() { t.text } else { t.text_dim }); app.auto.add("paint.swapColors", swap, "Swap colors"); - if sresp.on_hover_text("Swap Paint Colors (X)").clicked() { + if sresp.on_hover_text(tr("Swap Paint Colors (X)")).clicked() { set(app, json!({"color": o.background, "background": o.color})); } let reset = Rect::from_min_size(pos2(x0 + 46.0, y + 18.0), vec2(18.0, 14.0)); @@ -120,7 +121,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { p.rect_filled(Rect::from_min_size(reset.min + vec2(4.0, 3.0), vec2(7.0, 6.0)), 0.0, Color32::WHITE); p.rect_stroke(Rect::from_min_size(reset.min + vec2(8.0, 6.0), vec2(7.0, 6.0)), 0.0, Stroke::new(1.0, t.text_dim), StrokeKind::Inside); app.auto.add("paint.resetColors", reset, "Default colors"); - if rresp.on_hover_text("Set Paint Colors to Black and White (D)").clicked() { + if rresp.on_hover_text(tr("Set Paint Colors to Black and White (D)")).clicked() { set(app, json!({"color": [1.0, 1.0, 1.0, 1.0], "background": [0.0, 0.0, 0.0, 1.0]})); } y += 40.0; @@ -168,9 +169,9 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Clone Options. p.line_segment([pos2(x0, y), pos2(x0 + w, y)], Stroke::new(1.0, t.separator)); y += 8.0; - p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Clone Options", Tokens::semibold(11.5), if clone { t.text } else { t.text_faint }); + p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Clone Options"), Tokens::semibold(11.5), if clone { t.text } else { t.text_faint }); y += 22.0; - p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, "Preset:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, tr("Preset:"), Tokens::ui(11.5), t.text_dim); for i in 1..=5u32 { let r = Rect::from_min_size(pos2(x0 + 56.0 + (i - 1) as f32 * 26.0, y), vec2(22.0, 20.0)); let resp = ui.interact(r, egui::Id::new(("clone-preset", i)), Sense::click()); @@ -217,7 +218,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } y += 26.0; let off = o.clone_offset.unwrap_or([0.0, 0.0]); - p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, "Offset:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, tr("Offset:"), Tokens::ui(11.5), t.text_dim); for (d, v) in off.iter().enumerate() { let (r, nv, _) = widgets::hot_number_at(ui, pos2(x0 + 56.0 + d as f32 * 64.0, y), egui::Id::new(("clone-off", d)), *v, 1.0, (-1e5, 1e5), 0, "", &t); app.auto.add(&format!("paint.cloneOffset.{d}"), r, "Clone offset"); @@ -240,7 +241,7 @@ pub fn paint(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { set(app, json!({"showOverlay": !o.show_overlay})); } app.auto.add("paint.showOverlay", r, "Clone Source Overlay"); - p.text(pos2(r.max.x + 4.0, r.center().y), Align2::LEFT_CENTER, "Clone Source Overlay:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(r.max.x + 4.0, r.center().y), Align2::LEFT_CENTER, tr("Clone Source Overlay:"), Tokens::ui(11.5), t.text_dim); if let Some(nv) = number(app, ui, &p, x0 + 150.0, y, "", "paint.overlayOpacity", o.overlay_opacity, (0.0, 100.0), " %", true) { set(app, json!({"overlayOpacity": nv})); } @@ -329,7 +330,7 @@ pub fn brushes(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } let r = Rect::from_min_size(pos2(x0, y), vec2(18.0, 20.0)); // Spacing (checkbox in AE turns spacing on; we always space dabs, so it shows the value). - p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, "Spacing:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, tr("Spacing:"), Tokens::ui(11.5), t.text_dim); let _ = r; let (sr, nv, _) = widgets::hot_number_at(ui, pos2(x0 + 58.0, y), egui::Id::new("brushes-spacing"), o.spacing, 0.5, (1.0, 1000.0), 0, " %", &t); app.auto.add("brushes.spacing", sr, "Spacing"); @@ -338,7 +339,7 @@ pub fn brushes(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } y += 30.0; // Brush Dynamics. - p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Brush Dynamics", Tokens::semibold(11.5), t.text); + p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Brush Dynamics"), Tokens::semibold(11.5), t.text); y += 22.0; let opts = vec!["Off".to_string(), "Pen Pressure".to_string()]; for (label, key, on) in diff --git a/crates/ui-egui/src/panels/path_vr_panels.rs b/crates/ui-egui/src/panels/path_vr_panels.rs index 21b02f22a..9dba0dd59 100644 --- a/crates/ui-egui/src/panels/path_vr_panels.rs +++ b/crates/ui-egui/src/panels/path_vr_panels.rs @@ -12,6 +12,7 @@ //! `createNulls.tracePath`, `createNulls.loop`, `vrEditor.env.`, `vrEditor.pan`, //! `vrEditor.tilt`, `vrEditor.roll`, `vrEditor.reset`, `vrEditor.open`. +use crate::i18n::tr; use egui::{Rect, RichText}; use serde_json::{Value, json}; @@ -38,7 +39,7 @@ pub fn create_nulls(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let loop_id = egui::Id::new("createNulls.loop"); let mut looping: bool = ui.data(|d| d.get_temp(loop_id)).unwrap_or(false); let ready = app.session.is_enabled("paths.nullsFollowPoints") && !app.session.state.selected_layers.is_empty(); - ui.label(RichText::new("Select a layer with a mask or shape path (or the path property), or Puppet pins.").color(t.text_dim).small()); + ui.label(RichText::new(tr("Select a layer with a mask or shape path (or the path property), or Puppet pins.")).color(t.text_dim).small()); ui.add_space(6.0); let mut acts = vec![]; for (id, label, tip) in [ @@ -53,7 +54,7 @@ pub fn create_nulls(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { acts.push((id, if id == "paths.tracePath" { json!({"loop": looping}) } else { json!({}) })); } } - let r = ui.checkbox(&mut looping, "Loop (Trace Path)"); + let r = ui.checkbox(&mut looping, tr("Loop (Trace Path)")); app.auto.add("createNulls.loop", r.rect, "Loop"); ui.data_mut(|d| d.insert_temp(loop_id, looping)); run(app, &ctx, acts); @@ -66,7 +67,7 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let mut ui = area(ui, rect); let envs = app.session.execute("comp.vr.environments", json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default(); if envs.is_empty() { - ui.label(RichText::new("No VR environments yet. Create one with Composition ▸ VR ▸ Create VR Environment…").color(t.text_faint)); + ui.label(RichText::new(tr("No VR environments yet. Create one with Composition ▸ VR ▸ Create VR Environment…")).color(t.text_faint)); return; } let sel_id = egui::Id::new("vrEditor.selected"); @@ -78,7 +79,7 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { sel = active.and_then(|c| envs.iter().find(|e| owns(e, c))).or(envs.first()).and_then(|e| e["output"].as_u64()).unwrap_or(0); } let mut acts = vec![]; - ui.label(RichText::new("VR Environments").strong()); + ui.label(RichText::new(tr("VR Environments")).strong()); for e in &envs { let out = e["output"].as_u64().unwrap_or(0); let r = ui.selectable_label(out == sel, e["name"].as_str().unwrap_or("")); @@ -90,7 +91,7 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { ui.data_mut(|d| d.insert_temp(sel_id, sel)); let Some(env) = envs.iter().find(|e| e["output"] == sel) else { return }; ui.separator(); - ui.label(RichText::new("360 View Orientation").strong()); + ui.label(RichText::new(tr("360 View Orientation")).strong()); let view: Vec = env["view"].as_array().map(|a| a.iter().filter_map(Value::as_f64).collect()).unwrap_or_default(); let signed = |a: f64| if a > 180.0 { a - 360.0 } else { a }; let (mut tilt, mut pan, mut roll) = @@ -108,17 +109,17 @@ pub fn vr_editor(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { acts.push(("comp.vr.setView", json!({"comp": sel, "pan": pan, "tilt": tilt, "roll": roll}))); } ui.horizontal(|ui| { - let r = ui.button("Reset"); + let r = ui.button(tr("Reset")); app.auto.add("vrEditor.reset", r.rect, "Reset"); if r.clicked() { acts.push(("comp.vr.setView", json!({"comp": sel, "orientation": [0.0, 0.0, 0.0]}))); } - let r = ui.button("Open Output"); + let r = ui.button(tr("Open Output")); app.auto.add("vrEditor.open", r.rect, "Open Output"); if r.clicked() { acts.push(("comp.open", json!({"comp": sel}))); } }); - ui.label(RichText::new("All six face cameras turn together; the scene is not changed.").color(t.text_faint).small()); + ui.label(RichText::new(tr("All six face cameras turn together; the scene is not changed.")).color(t.text_faint).small()); run(app, &ctx, acts); } diff --git a/crates/ui-egui/src/panels/project.rs b/crates/ui-egui/src/panels/project.rs index 7b14cce15..8f249b780 100644 --- a/crates/ui-egui/src/panels/project.rs +++ b/crates/ui-egui/src/panels/project.rs @@ -579,27 +579,27 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } let footage = matches!(it.kind, ItemKind::Footage(_)); resp.context_menu(|ui| { - if ui.button("Delete").clicked() { + if ui.button(crate::i18n::tr("Delete")).clicked() { actions.push(("project.delete".into(), json!({"items": targets(app, *id, selected)}))); ui.close(); } - if matches!(it.kind, ItemKind::Comp(_)) && ui.button("Open Composition").clicked() { + if matches!(it.kind, ItemKind::Comp(_)) && ui.button(crate::i18n::tr("Open Composition")).clicked() { actions.push(("comp.open".into(), json!({"comp": id.0}))); ui.close(); } - if ui.button("Rename").clicked() { + if ui.button(crate::i18n::tr("Rename")).clicked() { ctx.data_mut(|d| d.insert_temp::(edit_id(), (id.0, "name".into(), it.name.clone()))); ui.close(); } - if it.parent.is_some() && ui.button("Move to Project Root").clicked() { + if it.parent.is_some() && ui.button(crate::i18n::tr("Move to Project Root")).clicked() { actions.push(("project.move".into(), json!({"items": [id.0], "folder": null}))); ui.close(); } - if !it.is_folder() && ui.button("Add to Composition").clicked() { + if !it.is_folder() && ui.button(crate::i18n::tr("Add to Composition")).clicked() { actions.push(("layer.addItem".into(), json!({"item": id.0}))); ui.close(); } - if ui.button("New Comp from Selection").clicked() { + if ui.button(crate::i18n::tr("New Comp from Selection")).clicked() { if let Some((w, h)) = it.dimensions() { let d = it.duration().map(|d| d.seconds()).unwrap_or(10.0); actions.push(("comp.new".into(), json!({"name": format!("{} Comp", it.name), "width": w, "height": h, "duration": d}))); @@ -608,7 +608,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { ui.close(); } ui.separator(); - ui.menu_button("Label", |ui| { + ui.menu_button(crate::i18n::tr("Label"), |ui| { for l in Label::ALL { if widgets::label_entry(ui, l, &app.session.prefs.label_name(l), l == it.label, &t).clicked() { actions.push(("project.setLabel".into(), json!({"items": targets(app, *id, selected), "label": label_index(l)}))); @@ -734,9 +734,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { actions.push(("file.cycleBitDepth".into(), json!({}))); } } - dresp.on_hover_text("Project color depth: click to cycle 8/16/32 bpc, Alt-click for Project Settings"); + dresp.on_hover_text(crate::i18n::tr("Project color depth: click to cycle 8/16/32 bpc, Alt-click for Project Settings")); let tr = Rect::from_min_size(pos2(foot.max.x - 30.0, foot.min.y + 3.0), vec2(22.0, 22.0)); - if widgets::icon_button(ui, tr, Icon::Trash, false, &t, egui::Id::new("ptrash")).on_hover_text("Delete selected project items").clicked() + if widgets::icon_button(ui, tr, Icon::Trash, false, &t, egui::Id::new("ptrash")).on_hover_text(crate::i18n::tr("Delete selected project items")).clicked() && !app.session.state.project_selection.is_empty() { actions.push(("project.delete".into(), json!({}))); @@ -812,7 +812,7 @@ fn label_index(l: Label) -> usize { /// The column header's context menu: show or hide the optional columns. fn column_menu(app: &mut EffectcraftApp, resp: &egui::Response) { resp.context_menu(|ui| { - ui.label(egui::RichText::new("Columns").weak()); + ui.label(egui::RichText::new(crate::i18n::tr("Columns")).weak()); for (key, label, _) in COLUMNS { let mut on = app.ui.project_columns.iter().any(|c| c == key); if ui.checkbox(&mut on, label).changed() { diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 990b00689..2dcecc1d4 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -57,7 +57,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let comp = app.session.active_comp_arc(); let layer = comp.as_ref().and_then(|c| app.session.state.selected_layers.first().and_then(|id| c.layer(*id)).cloned()); let (Some(comp), Some(cid), Some(layer)) = (comp, app.session.active_comp_id(), layer) else { - p.text(rect.center(), Align2::CENTER_CENTER, "Select a layer to see its properties", Tokens::ui(12.0), t.text_faint); + p.text(rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Select a layer to see its properties"), Tokens::ui(12.0), t.text_faint); return; }; let project = app.session.project.clone(); @@ -76,7 +76,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { section_header(app, ui, &p, x0, w, y, "Layer Transform", "properties.transform"); let rr = Rect::from_min_size(pos2(x0 + w - 40.0, y), vec2(40.0, ROW_H)); let resp = ui.interact(rr, egui::Id::new("props-reset"), Sense::click()); - p.text(rr.right_center(), Align2::RIGHT_CENTER, "Reset", Tokens::ui(12.0), if resp.hovered() { t.accent_hover } else { t.accent }); + p.text(rr.right_center(), Align2::RIGHT_CENTER, crate::i18n::tr("Reset"), Tokens::ui(12.0), if resp.hovered() { t.accent_hover } else { t.accent }); app.auto.add("properties.transform.reset", rr, "Reset Transform"); if resp.clicked() { for pr in tr.props() { @@ -112,7 +112,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let resp = ui.interact(br, egui::Id::new("props-add-animator"), Sense::click()); p.rect_stroke(br, 4.0, Stroke::new(1.0, if resp.hovered() { t.text_dim } else { t.field_border }), egui::StrokeKind::Inside); icons::paint(&p, Rect::from_center_size(pos2(br.min.x + 14.0, br.center().y), vec2(10.0, 10.0)), Icon::Plus, t.text); - p.text(pos2(br.min.x + 26.0, br.center().y), Align2::LEFT_CENTER, "Add Animator", Tokens::ui(12.0), t.text); + p.text(pos2(br.min.x + 26.0, br.center().y), Align2::LEFT_CENTER, crate::i18n::tr("Add Animator"), Tokens::ui(12.0), t.text); app.auto.add("properties.addAnimator", br, "Add Animator"); let pop = egui::Id::new("props-add-animator-pop"); if resp.clicked() { @@ -132,7 +132,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let pr = Rect::from_min_size(pos2(br.max.x + 8.0, y), vec2(84.0, 24.0)); let presp = ui.interact(pr, egui::Id::new("props-text-presets"), Sense::click()); p.rect_stroke(pr, 4.0, Stroke::new(1.0, if presp.hovered() { t.text_dim } else { t.field_border }), egui::StrokeKind::Inside); - p.text(pr.center(), Align2::CENTER_CENTER, "Presets", Tokens::ui(12.0), t.text); + p.text(pr.center(), Align2::CENTER_CENTER, crate::i18n::tr("Presets"), Tokens::ui(12.0), t.text); app.auto.add("properties.textPresets", pr, "Text Animation Presets"); let ppop = egui::Id::new("props-text-presets-pop"); if presp.clicked() { @@ -362,7 +362,13 @@ fn text_section( p.text(pos2(col2, y + 9.0), Align2::LEFT_CENTER, "A", Tokens::semibold(11.0), t.text_dim); let r = Rect::from_min_size(pos2(col2 + 26.0, y), vec2(40.0, 18.0)); let resp = ui.interact(r, egui::Id::new("props-leading-auto"), Sense::click()); - p.text(pos2(r.min.x + 2.0, r.center().y), Align2::LEFT_CENTER, "Auto", Tokens::ui(12.0), if resp.hovered() { t.accent_hover } else { t.hot_text }); + p.text( + pos2(r.min.x + 2.0, r.center().y), + Align2::LEFT_CENTER, + crate::i18n::tr("Auto"), + Tokens::ui(12.0), + if resp.hovered() { t.accent_hover } else { t.hot_text }, + ); app.auto.add("properties.text.leading", r, "leading"); if resp.clicked() { set(actions, json!({"leading": (doc.size * 1.2).round()})); @@ -480,7 +486,7 @@ fn more_button(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, x let resp = ui.interact(r, egui::Id::new(id), Sense::click()); p.rect_filled(r, 4.0, if resp.hovered() { t.hover } else { t.field_bg }); p.text(pos2(r.min.x + 8.0, r.center().y), Align2::LEFT_CENTER, "···", Tokens::semibold(12.0), t.text); - p.text(pos2(r.min.x + 26.0, r.center().y), Align2::LEFT_CENTER, "More", Tokens::ui(12.0), t.text); + p.text(pos2(r.min.x + 26.0, r.center().y), Align2::LEFT_CENTER, crate::i18n::tr("More"), Tokens::ui(12.0), t.text); app.auto.add(auto, r, "More"); if resp.clicked() { app.show_panel(panel); diff --git a/crates/ui-egui/src/panels/text_panels.rs b/crates/ui-egui/src/panels/text_panels.rs index 06b626733..8e24a0835 100644 --- a/crates/ui-egui/src/panels/text_panels.rs +++ b/crates/ui-egui/src/panels/text_panels.rs @@ -2,6 +2,7 @@ //! Character and Paragraph panels show and change the selected text (creating style runs); //! otherwise they apply to the whole selected text layer. +use crate::i18n::tr; use effectcraft_engine::keyframe::{BaselineOption, Composer, Direction, FigureStyle, FigureWidth, Justify, Kerning, TextDoc}; use effectcraft_engine::render::EvalCtx; use egui::{Align2, Rect, Sense, pos2, vec2}; @@ -232,7 +233,7 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { actions.push(json!({"ligatures": !doc.ligatures})); } app.auto.add("character.ligatures", lr, "Ligatures"); - p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, "Ligatures", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, tr("Ligatures"), Tokens::ui(11.5), t.text_dim); // OpenType: a popup with the font's layout features (like the Character panel's OpenType menu). let or = Rect::from_min_size(pos2(sr.max.x + 6.0, sr.min.y), vec2(78.0, 22.0)); let opop = egui::Id::new("char-opentype-pop"); @@ -264,7 +265,7 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let axes = effectcraft_engine::text::variable::font_axes(face); if !axes.is_empty() { y += 26.0; - p.text(pos2(x0, y + 7.0), Align2::LEFT_CENTER, "Variable Font Axes", Tokens::semibold(11.0), t.text_dim); + p.text(pos2(x0, y + 7.0), Align2::LEFT_CENTER, tr("Variable Font Axes"), Tokens::semibold(11.0), t.text_dim); for (i, a) in axes.iter().enumerate() { let (col, row) = (i % 2, i / 2); let fx = x0 + col as f32 * (w / 2.0); @@ -287,7 +288,7 @@ pub fn character(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { scroll.end(ui, &mut app.auto, "character.scroll", y + 44.0 - top, &t); match &target { None => { - p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, "Select a text layer", Tokens::ui(11.0), t.text_faint); + p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, tr("Select a text layer"), Tokens::ui(11.0), t.text_faint); } Some(tt) if tt.range.is_some() => { let [a, b] = tt.range.unwrap_or_default(); @@ -351,7 +352,7 @@ fn opentype_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui::Id, pos } } ui.separator(); - ui.label(egui::RichText::new("Figures").small().color(t.text_dim)); + ui.label(egui::RichText::new(tr("Figures")).small().color(t.text_dim)); let figs = [ ("default", "Default Figure Style", FigureStyle::Default, FigureWidth::Default), ("tabularLining", "Tabular Lining", FigureStyle::Lining, FigureWidth::Tabular), @@ -368,7 +369,7 @@ fn opentype_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: egui::Id, pos } } ui.separator(); - ui.label(egui::RichText::new("Stylistic Sets").small().color(t.text_dim)); + ui.label(egui::RichText::new(tr("Stylistic Sets")).small().color(t.text_dim)); egui::Grid::new(id.with("sets")).spacing(vec2(3.0, 3.0)).show(ui, |ui| { for n in 1..=20u32 { let key = format!("ss{n:02}"); @@ -536,11 +537,11 @@ pub fn paragraph(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { actions.push(json!({"hangingPunctuation": !doc.hanging_punctuation})); } app.auto.add("paragraph.hangingPunctuation", hr, "Roman Hanging Punctuation"); - p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, "Roman Hanging Punctuation", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0 + 20.0, y + 7.0), Align2::LEFT_CENTER, tr("Roman Hanging Punctuation"), Tokens::ui(11.5), t.text_dim); // (Room for the status line at the bottom.) scroll.end(ui, &mut app.auto, "paragraph.scroll", y + 44.0 - top, &t); if !enabled { - p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, "Select a text layer", Tokens::ui(11.0), t.text_faint); + p.text(pos2(rect.center().x, rect.max.y - 20.0), Align2::CENTER_CENTER, tr("Select a text layer"), Tokens::ui(11.0), t.text_faint); } let Some(tt) = target else { return }; for a in actions { @@ -601,7 +602,7 @@ pub fn align(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let p = ui.painter().with_clip_rect(rect); let x0 = rect.min.x + 10.0; let mut y = rect.min.y + 10.0; - p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, "Align Layers to:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, tr("Align Layers to:"), Tokens::ui(11.5), t.text_dim); let dr = Rect::from_min_size(pos2(x0 + 100.0, y), vec2(110.0, 20.0)); let to_sel = app.ui.align_to_selection; let pid = egui::Id::new("align-to-pop"); @@ -619,7 +620,7 @@ pub fn align(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let mut run: Option<(&str, &str)> = None; for (row, kind) in ["align", "distribute"].into_iter().enumerate() { if row == 1 { - p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Distribute Layers:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Distribute Layers:"), Tokens::ui(11.5), t.text_dim); y += 20.0; } for (i, op) in ops.iter().enumerate() { diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index e1059c567..87335515a 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -1499,11 +1499,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } hresp.context_menu(|ui| { let under: &str = ctx.data(|d| d.get_temp(egui::Id::new("tl-colhdr-under"))).unwrap_or(""); - if !under.is_empty() && under != "name" && ui.button("Hide This").clicked() { + if !under.is_empty() && under != "name" && ui.button(crate::i18n::tr("Hide This")).clicked() { actions.push(("timeline.column".into(), json!({"column": under, "visible": false}))); ui.close(); } - ui.menu_button("Columns", |ui| { + ui.menu_button(crate::i18n::tr("Columns"), |ui| { for (id, label) in COLUMNS { let label = if id == "name" { if app.ui.timeline.source_name { "Source Name" } else { "Layer Name" } } else { label }; let on = column_visible(&app.ui.timeline, id); @@ -1513,7 +1513,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } } }); - if ui.button(if app.ui.timeline.source_name { "Show Layer Name" } else { "Show Source Name" }).clicked() { + if ui.button(if app.ui.timeline.source_name { crate::i18n::tr("Show Layer Name") } else { crate::i18n::tr("Show Source Name") }).clicked() { actions.push(("timeline.sourceName".into(), json!({}))); ui.close(); } @@ -1799,8 +1799,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { // Parent. if vis.parent { let pw_rect = Rect::from_center_size(pos2(cw.parent + 9.0, cy), vec2(15.0, 15.0)); - let pw = - ui.interact(pw_rect, egui::Id::new(("parent-whip", layer.id.0)), Sense::drag()).on_hover_text("Parent pick whip: drag onto a layer"); + let pw = ui + .interact(pw_rect, egui::Id::new(("parent-whip", layer.id.0)), Sense::drag()) + .on_hover_text(crate::i18n::tr("Parent pick whip: drag onto a layer")); icons::paint(&lp, pw_rect.shrink(1.0), Icon::PickWhip, if pw.hovered() || pw.dragged() { t.text } else { t.text_dim }); app.auto.add(&format!("timeline.layer.{}.pickWhip", layer.id.0), pw_rect, "Parent pick whip"); if pw.drag_started() { @@ -1870,7 +1871,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if gr.width() < 2.0 { continue; } - let gresp = ui.interact(gr, egui::Id::new(("ghost", side, layer.id.0)), Sense::drag()).on_hover_text("Drag to slip the source"); + let gresp = ui + .interact(gr, egui::Id::new(("ghost", side, layer.id.0)), Sense::drag()) + .on_hover_text(crate::i18n::tr("Drag to slip the source")); app.auto.add(&format!("timeline.layer.{}.source{}", layer.id.0, side), gr, "Slip"); if gresp.hovered() || gresp.dragged() { ctx.set_cursor_icon(egui::CursorIcon::ResizeHorizontal); @@ -1999,9 +2002,10 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { text_anim_popups(app, ui, &lp, layer, *uid, cw.switches, cy, &mut actions); shape_add_popup(app, ui, &lp, layer, *uid, cw.switches, cy, &mut actions); if *uid == GEOM_BIT | layer.id.0 { - let lr = lp.text(pos2(cw.switches + 6.0, cy), Align2::LEFT_CENTER, "Change Renderer…", Tokens::ui(11.5), t.accent); - let resp = - ui.interact(lr, egui::Id::new(("tl-change-renderer", layer.id.0)), Sense::click()).on_hover_text("Composition Settings ▸ 3D Renderer"); + let lr = lp.text(pos2(cw.switches + 6.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Change Renderer…"), Tokens::ui(11.5), t.accent); + let resp = ui + .interact(lr, egui::Id::new(("tl-change-renderer", layer.id.0)), Sense::click()) + .on_hover_text(crate::i18n::tr("Composition Settings ▸ 3D Renderer")); if resp.hovered() { ctx.set_cursor_icon(egui::CursorIcon::PointingHand); } @@ -2028,7 +2032,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if widgets::checkbox(ui, ir, inverted, &t, egui::Id::new(("minv", uid))).clicked() { actions.push(("layer.setMask".into(), json!({"layer": layer.id.0, "mask": uid, "inverted": !inverted}))); } - lp.text(pos2(ir.max.x + 4.0, cy), Align2::LEFT_CENTER, "Inverted", Tokens::ui(11.0), t.text_dim); + lp.text(pos2(ir.max.x + 4.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Inverted"), Tokens::ui(11.0), t.text_dim); } let gr_rect = Rect::from_min_max(pos2(indent + 8.0, r.min.y), pos2(cw.switches, r.max.y)); let gr = ui.interact(gr_rect, egui::Id::new(("grow", uid)), Sense::click()); @@ -2072,11 +2076,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let Some(ex) = prop.expr.clone() else { continue }; lp.rect_filled(left, 0.0, t.row_alt); let indent = cw.name + 6.0 + 14.0 * row.depth as f32; - lp.text(pos2(indent + 12.0, cy), Align2::LEFT_CENTER, "Expression:", Tokens::ui(11.5), t.text_dim); + lp.text(pos2(indent + 12.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Expression:"), Tokens::ui(11.5), t.text_dim); lp.text(pos2(indent + 82.0, cy), Align2::LEFT_CENTER, &prop.name, Tokens::ui(11.5), t.text); // "=" enable switch and the property pick whip (AE's expression controls). let en_r = Rect::from_center_size(pos2(cw.switches + 10.0, cy), vec2(16.0, 16.0)); - let en = ui.interact(en_r, egui::Id::new(("expr-en", uid)), Sense::click()).on_hover_text("Enable Expression"); + let en = ui.interact(en_r, egui::Id::new(("expr-en", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Enable Expression")); lp.rect_stroke(en_r.shrink(1.0), 2.0, Stroke::new(1.0, t.separator), StrokeKind::Inside); let expr_col = Color32::from_rgb(0xe8, 0x7c, 0x5c); lp.text( @@ -2091,7 +2095,9 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { actions.push(("prop.setExpression".into(), json!({"layer": layer.id.0, "prop": uid, "enabled": !ex.enabled}))); } let pw_rect = Rect::from_center_size(pos2(cw.switches + 30.0, cy), vec2(15.0, 15.0)); - let pw = ui.interact(pw_rect, egui::Id::new(("expr-whip", uid)), Sense::drag()).on_hover_text("Expression pick whip: drag onto a property"); + let pw = ui + .interact(pw_rect, egui::Id::new(("expr-whip", uid)), Sense::drag()) + .on_hover_text(crate::i18n::tr("Expression pick whip: drag onto a property")); icons::paint(&lp, pw_rect.shrink(1.0), Icon::PickWhip, if pw.hovered() || pw.dragged() { t.text } else { t.text_dim }); app.auto.add(&format!("timeline.prop.{uid}.pickWhip"), pw_rect, "Expression pick whip"); if pw.is_pointer_button_down_on() && ui.input(|i| i.pointer.primary_pressed()) { @@ -2127,7 +2133,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } // ⓕ: the Expression Language menu inserts at the cursor. let lang_r = Rect::from_center_size(pos2(cw.switches + 70.0, cy), vec2(16.0, 16.0)); - let lang = ui.interact(lang_r, egui::Id::new(("expr-lang", uid)), Sense::click()).on_hover_text("Expression Language Menu"); + let lang = ui.interact(lang_r, egui::Id::new(("expr-lang", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Expression Language Menu")); lp.circle_stroke(lang_r.center(), 6.5, Stroke::new(1.0, if lang.hovered() { t.text } else { t.text_dim })); lp.text(lang_r.center(), Align2::CENTER_CENTER, "f", Tokens::semibold(10.0), if lang.hovered() { t.text } else { t.text_dim }); app.auto.add(&format!("timeline.prop.{uid}.exprLanguage"), lang_r, "Expression Language Menu"); @@ -2396,7 +2402,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { ] { if lbl == "Select All Keyframes" { // Keyframe colour labels. - ui.menu_button("Label", |ui| { + ui.menu_button(crate::i18n::tr("Label"), |ui| { for (i, l) in effectcraft_engine::color::Label::ALL.into_iter().enumerate() { if widgets::label_entry(ui, l, &app.session.prefs.label_name(l), k.label as usize == i, &t).clicked() { if !ks { @@ -2459,7 +2465,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } for (response, area) in [(&outline_empty, "outline"), (&empty, "graph")] { response.context_menu(|ui| { - let menu = ui.menu_button("New", |ui| { + let menu = ui.menu_button(crate::i18n::tr("New"), |ui| { for (key, label, command, params) in [ ("text", "Text", "layer.newText", json!({"text": "Text"})), ("solid", "Solid…", "layer.newSolid", json!({})), @@ -2478,7 +2484,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } }); app.auto.add(&format!("timeline.context.{area}.new"), menu.response.rect, "New"); - let response = ui.button("Composition Settings…"); + let response = ui.button(crate::i18n::tr("Composition Settings…")); app.auto.add(&format!("timeline.context.{area}.settings"), response.rect, "Composition Settings…"); if response.clicked() { actions.push(("comp.settings".into(), json!({}))); @@ -2697,13 +2703,25 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } let tgl = Rect::from_min_size(pos2(footer.min.x + 10.0, footer.min.y + 3.0), vec2(150.0, 18.0)); let tresp = ui.interact(tgl, egui::Id::new("tl-toggle-modes"), Sense::click()); - p.text(tgl.left_center(), Align2::LEFT_CENTER, "Toggle Switches / Modes", Tokens::ui(11.0), if tresp.hovered() { t.text } else { t.text_dim }); + p.text( + tgl.left_center(), + Align2::LEFT_CENTER, + crate::i18n::tr("Toggle Switches / Modes"), + Tokens::ui(11.0), + if tresp.hovered() { t.text } else { t.text_dim }, + ); app.auto.add("timeline.toggleSwitchesModes", tgl, "Toggle Switches / Modes"); if tresp.clicked() { app.ui.timeline.show_modes = !app.ui.timeline.show_modes; } let ms = app.frames.last_ms.lock().map(|v| *v).unwrap_or(0.0); - p.text(pos2(graph_x0 - 12.0, footer.center().y), Align2::RIGHT_CENTER, format!("Frame Render Time {ms:.0}ms"), Tokens::ui(11.0), t.text_faint); + p.text( + pos2(graph_x0 - 12.0, footer.center().y), + Align2::RIGHT_CENTER, + crate::i18n::tr_args("Frame Render Time {}ms", &[&format!("{ms:.0}")]), + Tokens::ui(11.0), + t.text_faint, + ); // Zoom slider. let zs = Rect::from_min_max(pos2(graph_x0 + 30.0, footer.center().y - 2.0), pos2((graph_x0 + 230.0).min(rect.max.x - 30.0), footer.center().y + 2.0)); icons::paint(&p, Rect::from_center_size(pos2(zs.min.x - 14.0, footer.center().y), vec2(14.0, 14.0)), Icon::MountainSmall, t.text_dim); @@ -2891,7 +2909,7 @@ fn layer_cells( ctx.data_mut(|d| d.insert_temp(edit_id, (el, buf))); } } else { - let resp = ui.interact(cr, egui::Id::new(("tl-comment", lid)), Sense::click()).on_hover_text("Double-click to edit the comment"); + let resp = ui.interact(cr, egui::Id::new(("tl-comment", lid)), Sense::click()).on_hover_text(crate::i18n::tr("Double-click to edit the comment")); lp.with_clip_rect(cr.intersect(lp.clip_rect())).text(pos2(cr.min.x + 2.0, cy), Align2::LEFT_CENTER, &layer.comment, Tokens::ui(11.5), t.text_dim); if resp.double_clicked() { ctx.data_mut(|d| d.insert_temp(edit_id, (lid, layer.comment.clone()))); @@ -2910,7 +2928,7 @@ fn layer_cells( } let cr = Rect::from_min_max(pos2(x + 2.0, r.min.y + 1.0), pos2(x + TIME_W - 2.0, r.max.y - 1.0)); let tc = crate::panels::timecode(&app.session, comp, Tick::from_seconds_f64(secs) - if kind == "duration" { comp.display_start } else { Tick(0) }); - let resp = ui.interact(cr, egui::Id::new(("tl-cell", kind, lid)), Sense::drag()).on_hover_text("Drag to change"); + let resp = ui.interact(cr, egui::Id::new(("tl-cell", kind, lid)), Sense::drag()).on_hover_text(crate::i18n::tr("Drag to change")); lp.text(pos2(cr.min.x + 4.0, cy), Align2::LEFT_CENTER, &tc, Tokens::mono(11.0), if resp.hovered() || resp.dragged() { t.hot_text } else { t.timecode }); app.auto.add(&format!("timeline.layer.{lid}.{kind}"), cr, &tc); if resp.hovered() || resp.dragged() { @@ -2941,7 +2959,7 @@ fn layer_cells( if cw.vis.stretch { let cr = Rect::from_min_max(pos2(cw.stretch + 2.0, r.min.y + 1.0), pos2(cw.stretch + STRETCH_W - 2.0, r.max.y - 1.0)); let label = format!("{:.1}%", layer.stretch * 100.0); - let resp = ui.interact(cr, egui::Id::new(("tl-stretch", lid)), Sense::click()).on_hover_text("Time Stretch…"); + let resp = ui.interact(cr, egui::Id::new(("tl-stretch", lid)), Sense::click()).on_hover_text(crate::i18n::tr("Time Stretch…")); lp.text(pos2(cr.min.x + 4.0, cy), Align2::LEFT_CENTER, &label, Tokens::ui(11.5), if resp.hovered() { t.hot_text } else { t.text }); app.auto.add(&format!("timeline.layer.{lid}.stretch"), cr, &label); if resp.clicked() { @@ -3041,9 +3059,10 @@ fn shape_add_popup( return; } let t = app.tokens; - p.text(pos2(x + 6.0, cy), Align2::LEFT_CENTER, "Add:", Tokens::ui(11.5), t.text_dim); + p.text(pos2(x + 6.0, cy), Align2::LEFT_CENTER, crate::i18n::tr("Add:"), Tokens::ui(11.5), t.text_dim); let br = Rect::from_center_size(pos2(x + 40.0, cy), vec2(16.0, 16.0)); - let resp = ui.interact(br, egui::Id::new(("tl-shapeadd", uid)), Sense::click()).on_hover_text("Add a shape, fill, stroke or path operation"); + let resp = + ui.interact(br, egui::Id::new(("tl-shapeadd", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Add a shape, fill, stroke or path operation")); icons::paint(p, br.shrink(3.0), Icon::ChevronRight, if resp.hovered() { t.text } else { t.text_dim }); app.auto.add(&format!("timeline.group.{uid}.add"), br, "Add:"); let pop = egui::Id::new(("tl-shapeadd-pop", uid)); @@ -3174,7 +3193,7 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S ui.close(); } }; - ui.menu_button("New", |ui| { + ui.menu_button(crate::i18n::tr("New"), |ui| { item(ui, "Text", "layer.newText", json!({"text": "Text"})); item(ui, "Solid…", "app.solidSettings", json!({})); item(ui, "Light…", "layer.newLight", json!({})); @@ -3185,24 +3204,24 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S }); item(ui, "Layer Settings…", "layer.settings", json!({})); ui.separator(); - ui.menu_button("Masks", |ui| { + ui.menu_button(crate::i18n::tr("Masks"), |ui| { item(ui, "New Mask", "layer.addMask", json!({"layer": id})); }); - ui.menu_button("Transform", |ui| { + ui.menu_button(crate::i18n::tr("Transform"), |ui| { item(ui, "Reset", "layer.transform", json!({"layers": layers, "op": "reset"})); item(ui, "Center In View", "layer.transform", json!({"layers": layers, "op": "center"})); item(ui, "Fit to Comp", "layer.transform", json!({"layers": layers, "op": "fit"})); item(ui, "Flip Horizontal", "layer.transform", json!({"layers": layers, "op": "flipH"})); item(ui, "Flip Vertical", "layer.transform", json!({"layers": layers, "op": "flipV"})); }); - ui.menu_button("Time", |ui| { + ui.menu_button(crate::i18n::tr("Time"), |ui| { item(ui, "Enable Time Remapping", "layer.enableTimeRemap", json!({"layers": layers})); item(ui, "Time-Reverse Layer", "layer.timeReverse", json!({"layers": layers})); item(ui, "Time Stretch…", "layer.timeStretch", json!({})); item(ui, "Freeze Frame", "layer.freezeFrame", json!({"layers": layers})); item(ui, "Freeze on Last Frame", "layer.freezeOnLastFrame", json!({"layers": layers})); }); - ui.menu_button("Blending Mode", |ui| { + ui.menu_button(crate::i18n::tr("Blending Mode"), |ui| { widgets::menu_scroll(ui, |ui| { for m in BlendMode::ALL { item(ui, m.label(), "layer.setBlendMode", json!({"layers": layers, "mode": m.label()})); @@ -3212,7 +3231,7 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S } }) }); - ui.menu_button("Arrange", |ui| { + ui.menu_button(crate::i18n::tr("Arrange"), |ui| { item(ui, "Bring Layer to Front", "layer.arrange", json!({"layers": layers, "to": "front"})); item(ui, "Bring Layer Forward", "layer.arrange", json!({"layers": layers, "to": "forward"})); item(ui, "Send Layer Backward", "layer.arrange", json!({"layers": layers, "to": "backward"})); @@ -3304,7 +3323,7 @@ fn value_editor( let linked = linkable && !app.ui.timeline.unlinked.contains(&uid); if linkable { let lr = Rect::from_center_size(pos2(x + 7.0, at.y), vec2(14.0, 14.0)); - let resp = ui.interact(lr, egui::Id::new(("tl-link", uid)), Sense::click()).on_hover_text("Constrain Proportions"); + let resp = ui.interact(lr, egui::Id::new(("tl-link", uid)), Sense::click()).on_hover_text(crate::i18n::tr("Constrain Proportions")); icons::paint(p, lr, Icon::Link, if linked { t.accent } else { t.text_faint }); app.auto.add(&format!("timeline.prop.{uid}.link"), lr, "Constrain Proportions"); if resp.clicked() && !app.ui.timeline.unlinked.remove(&uid) { @@ -3379,14 +3398,20 @@ fn value_editor( } } Value::Path(_) => { - p.text(pos2(x, at.y), Align2::LEFT_CENTER, "Shape…", Tokens::ui(12.0), t.hot_text); + p.text(pos2(x, at.y), Align2::LEFT_CENTER, crate::i18n::tr("Shape…"), Tokens::ui(12.0), t.hot_text); } Value::Text(_) => {} Value::Layer(l) => { - p.text(pos2(x, at.y), Align2::LEFT_CENTER, l.map(|l| format!("Layer {l}")).unwrap_or("None".into()), Tokens::ui(12.0), t.hot_text); + p.text( + pos2(x, at.y), + Align2::LEFT_CENTER, + l.map(|l| crate::i18n::tr_args("Layer {}", &[&l])).unwrap_or(crate::i18n::tr("None").into()), + Tokens::ui(12.0), + t.hot_text, + ); } Value::Gradient(_) => { - p.text(pos2(x, at.y), Align2::LEFT_CENTER, "Edit Gradient…", Tokens::ui(12.0), t.hot_text); + p.text(pos2(x, at.y), Align2::LEFT_CENTER, crate::i18n::tr("Edit Gradient…"), Tokens::ui(12.0), t.hot_text); } Value::Str(s) => { p.text(pos2(x, at.y), Align2::LEFT_CENTER, s.chars().take(30).collect::(), Tokens::ui(12.0), t.text_dim); diff --git a/crates/ui-egui/src/panels/tracker.rs b/crates/ui-egui/src/panels/tracker.rs index b264d68a9..63ab06e80 100644 --- a/crates/ui-egui/src/panels/tracker.rs +++ b/crates/ui-egui/src/panels/tracker.rs @@ -153,7 +153,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { } y += 34.0; let Some(comp) = app.session.active_comp_arc() else { - p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Open a composition to track motion.", Tokens::ui(12.0), t.text_faint); + p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, crate::i18n::tr("Open a composition to track motion."), Tokens::ui(12.0), t.text_faint); return; }; // A selected mask switches the panel to mask tracking. @@ -476,10 +476,10 @@ fn ok_cancel(ui: &mut egui::Ui, app: &mut EffectcraftApp, t: &Tokens, prefix: &s let (mut ok, mut cancel) = (false, false); 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(crate::i18n::tr(" OK ")).color(Color32::WHITE)).fill(t.accent)); app.auto.add(&format!("{prefix}.ok"), r.rect, "OK"); ok = r.clicked(); - let r = ui.button("Cancel"); + let r = ui.button(crate::i18n::tr("Cancel")); app.auto.add(&format!("{prefix}.cancel"), r.rect, "Cancel"); cancel = r.clicked(); }); @@ -491,12 +491,12 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) let (mut ok, mut cancel) = (false, false); super::dialogs::modal(ctx, "Motion Tracker Options", vec2(460.0, 470.0), t, |ui| { ui.horizontal(|ui| { - ui.label("Track Name:"); + ui.label(crate::i18n::tr("Track Name:")); let r = ui.text_edit_singleline(&mut d.name); app.auto.add("dialog.trackOptions.name", r.rect, "Track Name"); }); ui.add_space(8.0); - ui.label(egui::RichText::new("Channel").font(Tokens::medium(12.5))); + ui.label(egui::RichText::new(crate::i18n::tr("Channel")).font(Tokens::medium(12.5))); ui.horizontal(|ui| { for (c, l) in [(TrackChannel::Rgb, "RGB"), (TrackChannel::Luminance, "Luminance"), (TrackChannel::Saturation, "Saturation")] { let r = ui.radio_value(&mut d.opts.channel, c, l); @@ -504,21 +504,21 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) } }); ui.add_space(8.0); - ui.label(egui::RichText::new("Process Before Match").font(Tokens::medium(12.5))); + ui.label(egui::RichText::new(crate::i18n::tr("Process Before Match")).font(Tokens::medium(12.5))); ui.horizontal(|ui| { - let r = ui.checkbox(&mut d.blur_on, "Blur"); + let r = ui.checkbox(&mut d.blur_on, crate::i18n::tr("Blur")); app.auto.add("dialog.trackOptions.blurOn", r.rect, "Blur"); let r = ui.add_enabled(d.blur_on, egui::DragValue::new(&mut d.opts.blur).speed(0.1).range(0.5..=50.0).suffix(" pixels")); app.auto.add("dialog.trackOptions.blur", r.rect, "Blur pixels"); - let r = ui.checkbox(&mut d.opts.enhance, "Enhance"); + let r = ui.checkbox(&mut d.opts.enhance, crate::i18n::tr("Enhance")); app.auto.add("dialog.trackOptions.enhance", r.rect, "Enhance"); }); ui.add_space(8.0); - let r = ui.checkbox(&mut d.opts.subpixel, "Subpixel Positioning"); + let r = ui.checkbox(&mut d.opts.subpixel, crate::i18n::tr("Subpixel Positioning")); app.auto.add("dialog.trackOptions.subpixel", r.rect, "Subpixel Positioning"); - let r = ui.checkbox(&mut d.opts.adapt_every_frame, "Adapt Feature On Every Frame"); + let r = ui.checkbox(&mut d.opts.adapt_every_frame, crate::i18n::tr("Adapt Feature On Every Frame")); app.auto.add("dialog.trackOptions.adaptEveryFrame", r.rect, "Adapt Feature On Every Frame"); - let r = ui.checkbox(&mut d.opts.track_shape, "Follow Feature Rotation and Scale"); + let r = ui.checkbox(&mut d.opts.track_shape, crate::i18n::tr("Follow Feature Rotation and Scale")); app.auto.add("dialog.trackOptions.trackShape", r.rect, "Follow Feature Rotation and Scale"); ui.add_space(8.0); ui.horizontal(|ui| { @@ -538,7 +538,7 @@ pub fn options_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) }) .response; app.auto.add("dialog.trackOptions.action", r.rect, "If Confidence is Below action"); - ui.label("If Confidence is Below"); + ui.label(crate::i18n::tr("If Confidence is Below")); let r = ui.add(egui::DragValue::new(&mut d.opts.threshold).speed(0.5).range(0.0..=100.0).suffix(" %")); app.auto.add("dialog.trackOptions.threshold", r.rect, "Confidence threshold"); }); @@ -578,15 +578,15 @@ pub fn target_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) let layers: Vec<(u64, String)> = app.session.active_comp().map(|c| c.layers.iter().enumerate().map(|(i, l)| (l.id.0, format!("{}. {}", i + 1, l.name))).collect()).unwrap_or_default(); super::dialogs::modal(ctx, "Motion Target", vec2(400.0, 200.0), t, |ui| { - ui.label("Apply Motion To:"); + ui.label(crate::i18n::tr("Apply Motion To:")); ui.horizontal(|ui| { - ui.label("Layer:"); + ui.label(crate::i18n::tr("Layer:")); let cur = d.target.and_then(|id| layers.iter().find(|(l, _)| *l == id)).map(|(_, n)| n.clone()).unwrap_or_else(|| "None".into()); let r = egui::ComboBox::from_id_salt("track-target") .selected_text(cur) .width(240.0) .show_ui(ui, |ui| { - ui.selectable_value(&mut d.target, None, "None"); + ui.selectable_value(&mut d.target, None, crate::i18n::tr("None")); for (id, n) in &layers { ui.selectable_value(&mut d.target, Some(*id), n); } @@ -613,7 +613,7 @@ pub fn apply_dialog(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) { let (mut ok, mut cancel) = (false, false); super::dialogs::modal(ctx, "Motion Tracker Apply Options", vec2(380.0, 170.0), t, |ui| { ui.horizontal(|ui| { - ui.label("Apply Dimensions:"); + ui.label(crate::i18n::tr("Apply Dimensions:")); let labels = ["X and Y", "X only", "Y only"]; let r = egui::ComboBox::from_id_salt("track-apply-dims") .selected_text(labels[d.dims.min(2)]) diff --git a/crates/ui-egui/src/panels/viewer.rs b/crates/ui-egui/src/panels/viewer.rs index 837cfb98d..1e31f53b6 100644 --- a/crates/ui-egui/src/panels/viewer.rs +++ b/crates/ui-egui/src/panels/viewer.rs @@ -454,7 +454,7 @@ fn aux_views( p.rect_stroke(Rect::from_center_size(cr.center(), cr.size() * k), 0.0, Stroke::new(1.0, Color32::from_white_alpha(120)), StrokeKind::Middle); } } - let label = if comp.has_3d() { v.label() } else { "Active Camera" }; + let label = if comp.has_3d() { v.label() } else { crate::i18n::tr("Active Camera") }; p.text(r.left_bottom() + vec2(8.0, -8.0), Align2::LEFT_BOTTOM, label, Tokens::ui(11.0), Color32::from_white_alpha(200)); app.auto.add(&format!("viewer.view.{}", v.id()), r, label); } @@ -511,7 +511,7 @@ fn locked_pane(app: &mut EffectcraftApp, ui: &mut egui::Ui, full: Rect, bg: Colo p.rect_filled(cr, 0.0, Color32::from_rgb((b[0] * 255.0) as u8, (b[1] * 255.0) as u8, (b[2] * 255.0) as u8)); p.image(tex.id(), cr, Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), Color32::WHITE); p.rect_stroke(cr, 0.0, Stroke::new(1.0, Color32::from_black_alpha(160)), StrokeKind::Outside); - let label = format!("Locked: {name} \u{2014} {}", if comp.has_3d() { lv.view.label() } else { "Active Camera" }); + let label = crate::i18n::tr_args("Locked: {} — {}", &[&name, &(if comp.has_3d() { lv.view.label() } else { crate::i18n::tr("Active Camera") })]); p.text(r.left_top() + vec2(8.0, 8.0), Align2::LEFT_TOP, &label, Tokens::ui(11.0), Color32::from_white_alpha(210)); app.auto.add("viewer.locked", r, &label); // Close the locked viewer. @@ -974,7 +974,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { resp.context_menu(|ui| { let hits = ui.data(|d| d.get_temp::>(menu_hits_id)).unwrap_or_default(); if !hits.is_empty() { - ui.menu_button("Select", |ui| { + ui.menu_button(crate::i18n::tr("Select"), |ui| { crate::widgets::menu_scroll(ui, |ui| { for (id, name) in &hits { if ui.button(name).clicked() { @@ -987,7 +987,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { ui.separator(); } if context_layers.is_empty() { - ui.label("No unlocked layer selected"); + ui.label(crate::i18n::tr("No unlocked layer selected")); } else { super::timeline::layer_menu(ui, &context_layers, &mut context_actions); } @@ -1392,7 +1392,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let speed = app.session.state.puppet.record_speed.max(1.0) / 100.0; app.session.set_time(cti + Tick::from_seconds_f64(now / speed)); painter.circle_filled(pos, 6.0, Color32::from_rgb(0xe0, 0x30, 0x30)); - painter.text(pos + vec2(10.0, -10.0), Align2::LEFT_BOTTOM, "Recording", Tokens::ui(11.0), Color32::from_rgb(0xff, 0x60, 0x60)); + painter.text(pos + vec2(10.0, -10.0), Align2::LEFT_BOTTOM, crate::i18n::tr("Recording"), Tokens::ui(11.0), Color32::from_rgb(0xff, 0x60, 0x60)); ui.data_mut(|dd| dd.insert_temp(gid, Gesture::PuppetRecord { layer, pin, others, inv, began, cti, samples })); ui.ctx().request_repaint(); } @@ -1633,7 +1633,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if app.playback.playing && app.playback.waiting { let r = Rect::from_min_size(area.min + vec2(10.0, 10.0), vec2(150.0, 22.0)); painter.rect_filled(r, 11.0, Color32::from_black_alpha(170)); - painter.text(r.center(), Align2::CENTER_CENTER, "Caching frames…", Tokens::ui(11.5), t.cache_green); + painter.text(r.center(), Align2::CENTER_CENTER, crate::i18n::tr("Caching frames…"), Tokens::ui(11.5), t.cache_green); } vt::draw_snap(&ctx, &painter, &map, &ectx); @@ -1917,7 +1917,7 @@ fn empty_state(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { let p = ui.painter(); p.rect_filled(rect, 0.0, t.pasteboard); let c = rect.center(); - p.text(c - vec2(0.0, 40.0), Align2::CENTER_CENTER, "Composition", Tokens::semibold(16.0), t.text); + p.text(c - vec2(0.0, 40.0), Align2::CENTER_CENTER, crate::i18n::tr("Composition"), Tokens::semibold(16.0), t.text); let b1 = Rect::from_center_size(c + vec2(0.0, 0.0), vec2(220.0, 30.0)); if widgets::text_button(ui, b1, "New Composition", true, &t, egui::Id::new("empty-newcomp")).clicked() { crate::panels::dialogs::open_new_comp(app); diff --git a/crates/ui-egui/src/panels/viewer_tools.rs b/crates/ui-egui/src/panels/viewer_tools.rs index 70bb8464e..06b529b30 100644 --- a/crates/ui-egui/src/panels/viewer_tools.rs +++ b/crates/ui-egui/src/panels/viewer_tools.rs @@ -225,8 +225,9 @@ pub(crate) fn rulers(app: &mut EffectcraftApp, ui: &mut egui::Ui, map: &ViewerMa // Zero point: drag the corner, double-click to reset. p.line_segment([corner.center() - vec2(4.0, 0.0), corner.center() + vec2(4.0, 0.0)], tick); p.line_segment([corner.center() - vec2(0.0, 4.0), corner.center() + vec2(0.0, 4.0)], tick); - let cr = - ui.interact(corner, egui::Id::new("vw-ruler-origin"), Sense::click_and_drag()).on_hover_text("Ruler zero point (drag to move, double-click to reset)"); + let cr = ui + .interact(corner, egui::Id::new("vw-ruler-origin"), Sense::click_and_drag()) + .on_hover_text(crate::i18n::tr("Ruler zero point (drag to move, double-click to reset)")); app.auto.add("viewer.ruler.origin", corner, "Ruler zero point"); let fg = ui.ctx().layer_painter(egui::LayerId::new(egui::Order::Foreground, egui::Id::new("vw-ruler-fg"))); if cr.dragged() @@ -247,7 +248,7 @@ pub(crate) fn rulers(app: &mut EffectcraftApp, ui: &mut egui::Ui, map: &ViewerMa // Guides dragged out of the rulers: the top ruler makes horizontal guides, the left one // vertical guides. for (r, vertical, id) in [(top, false, "top"), (left, true, "left")] { - let resp = ui.interact(r, egui::Id::new(("vw-ruler", id)), Sense::drag()).on_hover_text("Drag to create a guide"); + let resp = ui.interact(r, egui::Id::new(("vw-ruler", id)), Sense::drag()).on_hover_text(crate::i18n::tr("Drag to create a guide")); app.auto.add(&format!("viewer.ruler.{id}"), r, if vertical { "Vertical ruler" } else { "Horizontal ruler" }); if resp.hovered() { ui.ctx().set_cursor_icon(if vertical { egui::CursorIcon::ResizeHorizontal } else { egui::CursorIcon::ResizeVertical }); @@ -356,7 +357,7 @@ pub(crate) fn draw_frame(app: &mut EffectcraftApp, ctx: &egui::Context, painter: let snap = showing_snapshot(app, ctx); // Settings ▸ Video ▸ Mirror on Computer Monitor off: playback goes to Video Preview only. if crate::prefs_live::main_viewer_hidden(app) && !snap { - painter.text(comp_rect.center(), Align2::CENTER_CENTER, "Playing on Video Preview", Tokens::ui(13.0), Color32::from_gray(150)); + painter.text(comp_rect.center(), Align2::CENTER_CENTER, crate::i18n::tr("Playing on Video Preview"), Tokens::ui(13.0), Color32::from_gray(150)); return; } if opts.fast_previews == FastPreviews::Wireframe && !snap { @@ -403,7 +404,7 @@ pub(crate) fn draw_frame(app: &mut EffectcraftApp, ctx: &egui::Context, painter: } }; painter.image(tex.id(), comp_rect, uv, Color32::WHITE); - painter.text(comp_rect.left_top() + vec2(6.0, 6.0), Align2::LEFT_TOP, "Snapshot", Tokens::ui(11.0), SNAP_COLOR); + painter.text(comp_rect.left_top() + vec2(6.0, 6.0), Align2::LEFT_TOP, crate::i18n::tr("Snapshot"), Tokens::ui(11.0), SNAP_COLOR); return; } // A GPU frame (the web viewer): drawn straight from its texture; Show Channel / exposure @@ -530,7 +531,10 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, // Magnification. let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(80.0, 20.0)); - if widgets::dropdown(ui, r, &magnification_label(zoom * ppp * 100.0), &t, egui::Id::new("vw-mag")).on_hover_text("Magnification ratio popup").clicked() { + if widgets::dropdown(ui, r, &magnification_label(zoom * ppp * 100.0), &t, egui::Id::new("vw-mag")) + .on_hover_text(crate::i18n::tr("Magnification ratio popup")) + .clicked() + { toggle_popup(ui, "vw-mag-pop"); } app.auto.add("viewer.magnification", r, "Magnification"); @@ -560,7 +564,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, let r = Rect::from_min_size(pos2(x, cy - 10.0), vec2(84.0, 20.0)); let scale = app.viewer_scale(zoom, ppp); if widgets::dropdown(ui, r, &resolution_label(app.ui.viewer.res, scale), &t, egui::Id::new("vw-res")) - .on_hover_text("Resolution/Down Sample Factor Popup") + .on_hover_text(crate::i18n::tr("Resolution/Down Sample Factor Popup")) .clicked() { toggle_popup(ui, "vw-res-pop"); @@ -666,7 +670,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect, .fixed_decimals(1) .custom_formatter(|v, _| if v == 0.0 { "0.0".into() } else { format!("{v:+.1}") }), ); - let resp = resp.on_hover_text("Adjust Exposure (stops)"); + let resp = resp.on_hover_text(crate::i18n::tr("Adjust Exposure (stops)")); app.auto.add("viewer.exposure", er, "Adjust Exposure"); if resp.changed() { let _ = app.session.execute("view.exposure", json!({"stops": ev})); diff --git a/crates/ui-egui/src/panels/viewers.rs b/crates/ui-egui/src/panels/viewers.rs index be01f5b8b..57a3511f5 100644 --- a/crates/ui-egui/src/panels/viewers.rs +++ b/crates/ui-egui/src/panels/viewers.rs @@ -6,6 +6,7 @@ //! the active viewer is locked shows it in an unlocked viewer, or in a new one if every viewer is //! locked. +use crate::i18n::tr; use std::sync::Arc; use effectcraft_engine::project::ItemId; @@ -88,7 +89,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: u32, rect: Rect) { let ctx = ui.ctx().clone(); let p = ui.painter().with_clip_rect(rect); p.rect_filled(rect, 0.0, t.pasteboard); - let resp = ui.interact(rect, egui::Id::new(("passive-viewer", id)), Sense::click()).on_hover_text("Click to make this viewer active"); + let resp = ui.interact(rect, egui::Id::new(("passive-viewer", id)), Sense::click()).on_hover_text(tr("Click to make this viewer active")); app.auto.add(&format!("viewers.{id}"), rect, "Composition viewer (click to make it active)"); match comp_of(app, id).and_then(|cid| Some((cid, app.session.project.comp(cid)?.clone()))) { Some((cid, c)) => { @@ -116,7 +117,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: u32, rect: Rect) { p.rect_stroke(r, 0.0, egui::Stroke::new(1.0, t.separator), egui::StrokeKind::Outside); } None => { - p.text(rect.center(), Align2::CENTER_CENTER, "Click to show the active composition here", Tokens::ui(12.0), t.text_faint); + p.text(rect.center(), Align2::CENTER_CENTER, tr("Click to show the active composition here"), Tokens::ui(12.0), t.text_faint); } } if resp.clicked() {