panels: dialogs, settings and forms read their text from the catalog

The dialogs, Settings pages, unsaved-work and template prompts, the script console and the media
panels now go through `crate::i18n`, so their buttons, field names and hints follow
Settings ▸ General ▸ Language.
This commit is contained in:
FeiLiuEM
2026-10-09 20:17:05 +08:00
parent cceaba0452
commit add2e977f6
15 changed files with 148 additions and 134 deletions
@@ -4,6 +4,7 @@
//! live in `EditorState::content_fill` (`contentFill.set`); the button runs
//! `contentFill.generate` as a background job (Window ▸ Progress).
use crate::i18n::tr;
use egui::{Align2, Rect, pos2, vec2};
use serde_json::json;
@@ -25,7 +26,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(|l| c.layer(*l)).cloned());
// Fill Target.
p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, "Fill Target", Tokens::semibold(12.0), t.text);
p.text(pos2(x0, y + 9.0), Align2::LEFT_CENTER, tr("Fill Target"), Tokens::semibold(12.0), t.text);
y += ROW - 6.0;
let target = match &layer {
Some(l) => {
+4 -3
View File
@@ -5,6 +5,7 @@
//!
//! Automation ids: `dialog.deleteItems.delete`, `dialog.deleteItems.cancel`.
use crate::i18n::tr;
use egui::{Color32, vec2};
use serde_json::Value;
@@ -56,15 +57,15 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
);
ui.label(egui::RichText::new(text).color(t.tab_text_active));
ui.add_space(4.0);
ui.label(egui::RichText::new("You can undo this.").color(t.text_dim).size(11.0));
ui.label(egui::RichText::new(tr("You can undo this.")).color(t.text_dim).size(11.0));
ui.add_space(18.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let r = ui.add(egui::Button::new(egui::RichText::new(" Delete ").color(Color32::WHITE)).fill(t.danger));
let r = ui.add(egui::Button::new(egui::RichText::new(tr(" Delete ")).color(Color32::WHITE)).fill(t.danger));
app.auto.add("dialog.deleteItems.delete", r.rect, "Delete");
if r.clicked() {
answer = Some(true);
}
let r = ui.button("Cancel");
let r = ui.button(tr("Cancel"));
app.auto.add("dialog.deleteItems.cancel", r.rect, "Cancel");
if r.clicked() {
answer = Some(false);
+16 -15
View File
@@ -1,6 +1,7 @@
//! Modal dialogs: About (community links, Contributors and Models credits), New Composition /
//! Composition Settings, Solid Settings and the command palette (Camera/Light Settings live in `dialogs_3d`).
use crate::i18n::{tr, tr_args};
use effectcraft_engine::project::{ItemId, ItemKind, LayerId, LayerSource};
use egui::{Align2, Color32, Rect, Sense, pos2, vec2};
use serde_json::{Value, json};
@@ -232,9 +233,9 @@ fn about(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
}
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.label(egui::RichText::new("MIT OR Apache-2.0 • Fonts: Inter, JetBrains Mono (OFL)").small().color(t.text_faint));
ui.label(egui::RichText::new(tr("MIT OR Apache-2.0 • Fonts: Inter, JetBrains Mono (OFL)")).small().color(t.text_faint));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let r = ui.button("Close");
let r = ui.button(tr("Close"));
app.auto.add("about.close", r.rect, "Close");
if r.clicked() {
close = true;
@@ -265,7 +266,7 @@ fn about_main(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, cmd: &mut
t.text_dim,
);
ui.add_space(12.0);
ui.label("A clean-room, open-source compositor for motion graphics and visual effects: native on macOS, Windows and Linux, and in the browser. Part of the ArtCraft family of creative apps.");
ui.label(tr("A clean-room, open-source compositor for motion graphics and visual effects: native on macOS, Windows and Linux, and in the browser. Part of the ArtCraft family of creative apps."));
ui.add_space(14.0);
let links: [(Icon, &str, &str, &'static str); 4] = [
(Icon::Chat, "Join the ArtCraft Discord", effectcraft_engine::links::DISCORD, "help.discord"),
@@ -349,23 +350,23 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let auto = &mut app.auto;
modal(ctx, "Solid Settings", vec2(480.0, if editing.is_some() { 430.0 } else { 390.0 }), t, |ui| {
ui.horizontal(|ui| {
ui.label("Name:");
ui.label(tr("Name:"));
let r = ui.add(egui::TextEdit::singleline(&mut name).desired_width(320.0));
auto.add("dialog.solid.name", r.rect, "Name");
});
ui.add_space(8.0);
ui.label(egui::RichText::new("Size").strong());
ui.label(egui::RichText::new(tr("Size")).strong());
egui::Grid::new("solid-grid").num_columns(2).spacing([14.0, 8.0]).show(ui, |ui| {
let (ow, oh) = (size[0], size[1]);
ui.label("Width:");
ui.label(tr("Width:"));
ui.horizontal(|ui| {
let r = ui.add(egui::DragValue::new(&mut size[0]).range(1..=30000).suffix(" px"));
auto.add("dialog.solid.width", r.rect, "Width");
let r = ui.checkbox(&mut lock, format!("Lock Aspect Ratio to {}", super::comp_settings::aspect_label(ow as f64, oh as f64)));
let r = ui.checkbox(&mut lock, tr_args("Lock Aspect Ratio to {}", &[&(super::comp_settings::aspect_label(ow as f64, oh as f64))]));
auto.add("dialog.solid.lockAspect", r.rect, "Lock Aspect Ratio");
});
ui.end_row();
ui.label("Height:");
ui.label(tr("Height:"));
let r = ui.add(egui::DragValue::new(&mut size[1]).range(1..=30000).suffix(" px"));
auto.add("dialog.solid.height", r.rect, "Height");
ui.end_row();
@@ -376,7 +377,7 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
size[0] = ((size[1] as f64) * ow as f64 / oh as f64).round().max(1.0) as u32;
}
}
ui.label("Pixel Aspect Ratio:");
ui.label(tr("Pixel Aspect Ratio:"));
let cur = super::comp_settings::PIXEL_ASPECTS
.iter()
.find(|(_, v)| (v - par).abs() < 1e-3)
@@ -403,14 +404,14 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
.color(Color32::GRAY),
);
ui.add_space(4.0);
let r = ui.button("Make Comp Size");
let r = ui.button(tr("Make Comp Size"));
auto.add("dialog.solid.makeCompSize", r.rect, "Make Comp Size");
if r.clicked() {
size = [comp_size.0, comp_size.1];
}
ui.add_space(8.0);
ui.horizontal(|ui| {
ui.label("Color:");
ui.label(tr("Color:"));
let r = crate::widgets::srgb_color_button(ui, &mut color);
auto.add("dialog.solid.color", r.rect, "Color");
});
@@ -421,15 +422,15 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
.on_disabled_hover_text("Only this layer uses this solid.");
auto.add("dialog.solid.affectAll", r.rect, "Affect all layers that use this solid");
if shared && !affect_all {
ui.label(egui::RichText::new("This change will create a new solid for this layer.").color(t.text_dim).size(11.0));
ui.label(egui::RichText::new(tr("This change will create a new solid for this layer.")).color(t.text_dim).size(11.0));
}
}
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));
auto.add("dialog.solid.ok", r.rect, "OK");
ok = r.clicked();
let r = ui.button("Cancel");
let r = ui.button(tr("Cancel"));
auto.add("dialog.solid.cancel", r.rect, "Cancel");
close = r.clicked();
});
@@ -493,7 +494,7 @@ fn palette(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
.take(14)
.collect();
modal(ctx, "Command Palette", vec2(560.0, 470.0), t, |ui| {
let r = ui.add(egui::TextEdit::singleline(&mut q).hint_text("Type a command or effect…").desired_width(f32::INFINITY).font(Tokens::ui(14.0)));
let r = ui.add(egui::TextEdit::singleline(&mut q).hint_text(tr("Type a command or effect…")).desired_width(f32::INFINITY).font(Tokens::ui(14.0)));
r.request_focus();
ui.add_space(8.0);
let sel = app.dialog_state.palette_sel.min(matches.len().saturating_sub(1));
+29 -28
View File
@@ -2,6 +2,7 @@
//! Settings, Layer Settings on a camera or light). OK runs `layer.newCamera` / `layer.newLight`
//! or `layer.cameraSettings` / `layer.lightSettings` with the dialog's values.
use crate::i18n::tr;
use effectcraft_engine::project::{AutoOrient, LayerSource, LightKind};
use effectcraft_engine::render::three_d::camera::{PRESETS, angle_of_view, default_aperture, focal_for_zoom, zoom_for_focal};
use egui::{Color32, vec2};
@@ -144,12 +145,12 @@ pub fn open_light(app: &mut EffectcraftApp, layer: Option<u64>) -> Result<(), St
fn buttons(ui: &mut egui::Ui, t: &Tokens, ok: &mut bool, close: &mut bool) -> (egui::Rect, egui::Rect) {
let mut r = (egui::Rect::NOTHING, egui::Rect::NOTHING);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let b = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent));
let b = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
r.0 = b.rect;
if b.clicked() {
*ok = true;
}
let c = ui.button("Cancel");
let c = ui.button(tr("Cancel"));
r.1 = c.rect;
if c.clicked() {
*close = true;
@@ -165,16 +166,16 @@ pub fn camera(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let mut rects = (egui::Rect::NOTHING, egui::Rect::NOTHING);
super::dialogs::modal(ctx, "Camera Settings", vec2(520.0, 420.0), t, |ui| {
egui::Grid::new("cam-grid").num_columns(2).spacing([14.0, 9.0]).show(ui, |ui| {
ui.label("Type");
ui.label(tr("Type"));
egui::ComboBox::from_id_salt("cam-type").selected_text(if d.two_node { "Two-Node Camera" } else { "One-Node Camera" }).show_ui(ui, |ui| {
ui.selectable_value(&mut d.two_node, true, "Two-Node Camera");
ui.selectable_value(&mut d.two_node, false, "One-Node Camera");
ui.selectable_value(&mut d.two_node, true, tr("Two-Node Camera"));
ui.selectable_value(&mut d.two_node, false, tr("One-Node Camera"));
});
ui.end_row();
ui.label("Name");
ui.label(tr("Name"));
ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(240.0));
ui.end_row();
ui.label("Preset");
ui.label(tr("Preset"));
let focal = focal_for_zoom(w, d.zoom);
let cur = PRESETS.iter().find(|(_, f)| (f - focal).abs() < 1e-6).map(|(n, _)| *n).unwrap_or("Custom");
egui::ComboBox::from_id_salt("cam-preset").selected_text(cur).show_ui(ui, |ui| {
@@ -185,7 +186,7 @@ pub fn camera(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
}
});
ui.end_row();
ui.label("Zoom");
ui.label(tr("Zoom"));
ui.horizontal(|ui| {
ui.add(egui::DragValue::new(&mut d.zoom).range(1.0..=100000.0).speed(1.0).suffix(" px"));
let mut aov = angle_of_view(w, d.zoom);
@@ -198,19 +199,19 @@ pub fn camera(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
}
});
ui.end_row();
ui.label("Depth of Field");
ui.checkbox(&mut d.dof, "Enable Depth of Field");
ui.label(tr("Depth of Field"));
ui.checkbox(&mut d.dof, tr("Enable Depth of Field"));
ui.end_row();
ui.label("Focus Distance");
ui.label(tr("Focus Distance"));
ui.horizontal(|ui| {
ui.add_enabled(!d.lock_to_zoom, egui::DragValue::new(&mut d.focus).range(0.0..=100000.0).suffix(" px"));
ui.checkbox(&mut d.lock_to_zoom, "Lock to Zoom");
ui.checkbox(&mut d.lock_to_zoom, tr("Lock to Zoom"));
});
ui.end_row();
ui.label("Aperture");
ui.label(tr("Aperture"));
ui.add(egui::DragValue::new(&mut d.aperture).range(0.0..=10000.0).speed(0.1).suffix(" px"));
ui.end_row();
ui.label("Blur Level");
ui.label(tr("Blur Level"));
ui.add(egui::DragValue::new(&mut d.blur).range(0.0..=10000.0).suffix(" %"));
ui.end_row();
});
@@ -256,33 +257,33 @@ pub fn light(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let mut rects = (egui::Rect::NOTHING, egui::Rect::NOTHING);
super::dialogs::modal(ctx, "Light Settings", vec2(480.0, 470.0), t, |ui| {
egui::Grid::new("light-grid").num_columns(2).spacing([14.0, 9.0]).show(ui, |ui| {
ui.label("Name");
ui.label(tr("Name"));
ui.add(egui::TextEdit::singleline(&mut d.name).desired_width(220.0));
ui.end_row();
ui.label("Light Type");
ui.label(tr("Light Type"));
egui::ComboBox::from_id_salt("light-type").selected_text(KINDS[d.kind.min(3)].label()).show_ui(ui, |ui| {
for (i, k) in KINDS.iter().enumerate() {
ui.selectable_value(&mut d.kind, i, k.label());
}
});
ui.end_row();
ui.label("Color");
ui.label(tr("Color"));
ui.color_edit_button_rgb(&mut d.color);
ui.end_row();
ui.label("Intensity");
ui.label(tr("Intensity"));
ui.add(egui::DragValue::new(&mut d.intensity).range(-10000.0..=10000.0).suffix(" %"));
ui.end_row();
let kind = KINDS[d.kind.min(3)];
if kind == LightKind::Spot {
ui.label("Cone Angle");
ui.label(tr("Cone Angle"));
ui.add(egui::DragValue::new(&mut d.cone_angle).range(0.0..=180.0).suffix("°"));
ui.end_row();
ui.label("Cone Feather");
ui.label(tr("Cone Feather"));
ui.add(egui::DragValue::new(&mut d.cone_feather).range(0.0..=100.0).suffix(" %"));
ui.end_row();
}
if kind != LightKind::Ambient {
ui.label("Falloff");
ui.label(tr("Falloff"));
egui::ComboBox::from_id_salt("light-falloff").selected_text(FALLOFFS[d.falloff.min(2)]).show_ui(ui, |ui| {
for (i, f) in FALLOFFS.iter().enumerate() {
ui.selectable_value(&mut d.falloff, i, *f);
@@ -290,26 +291,26 @@ pub fn light(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
});
ui.end_row();
if d.falloff > 0 {
ui.label("Radius");
ui.label(tr("Radius"));
ui.add(egui::DragValue::new(&mut d.radius).range(0.0..=100000.0).suffix(" px"));
ui.end_row();
ui.label("Falloff Distance");
ui.label(tr("Falloff Distance"));
ui.add(egui::DragValue::new(&mut d.falloff_distance).range(0.0..=100000.0).suffix(" px"));
ui.end_row();
}
ui.label("Shadows");
ui.checkbox(&mut d.casts_shadows, "Casts Shadows");
ui.label(tr("Shadows"));
ui.checkbox(&mut d.casts_shadows, tr("Casts Shadows"));
ui.end_row();
ui.label("Shadow Darkness");
ui.label(tr("Shadow Darkness"));
ui.add_enabled(d.casts_shadows, egui::DragValue::new(&mut d.shadow_darkness).range(0.0..=100.0).suffix(" %"));
ui.end_row();
ui.label("Shadow Diffusion");
ui.label(tr("Shadow Diffusion"));
ui.add_enabled(d.casts_shadows, egui::DragValue::new(&mut d.shadow_diffusion).range(0.0..=1000.0).suffix(" px"));
ui.end_row();
}
});
ui.add_space(14.0);
ui.label(egui::RichText::new("Note: Shadows are only cast from layers with Cast Shadows enabled.").font(Tokens::ui(11.0)).color(t.text_dim));
ui.label(egui::RichText::new(tr("Note: Shadows are only cast from layers with Cast Shadows enabled.")).font(Tokens::ui(11.0)).color(t.text_dim));
ui.add_space(8.0);
rects = buttons(ui, t, &mut ok, &mut close);
});
+14 -13
View File
@@ -4,6 +4,7 @@
//! Keyboard Shortcuts: `shortcut_editor`). Every form runs an engine command with the collected parameters, so whatever a
//! dialog does an agent can do with one `engine.execute`.
use crate::i18n::tr;
use egui::{Color32, vec2};
use serde_json::{Map, Value, json};
@@ -684,7 +685,7 @@ pub fn show_form(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
FieldKind::Path { value, .. } => {
ui.horizontal(|ui| {
let r = ui.add(egui::TextEdit::singleline(value).desired_width(150.0)).rect;
let b = ui.button("Browse…");
let b = ui.button(tr("Browse…"));
regs.push((format!("form.field.{}.browse", fl.key), b.rect, "Browse…".into()));
if b.clicked() {
browse = Some(i);
@@ -696,7 +697,7 @@ pub fn show_form(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
FieldKind::SavePath(value) => {
ui.horizontal(|ui| {
let r = ui.add(egui::TextEdit::singleline(value).desired_width(150.0)).rect;
let b = ui.button("Browse…");
let b = ui.button(tr("Browse…"));
regs.push((format!("form.field.{}.browse", fl.key), b.rect, "Browse…".into()));
if b.clicked() {
browse = Some(i);
@@ -727,12 +728,12 @@ pub fn show_form(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
});
ui.add_space(14.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let b = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent));
let b = ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent));
regs.push(("form.ok".into(), b.rect, "OK".into()));
if b.clicked() {
ok = true;
}
let c = ui.button("Cancel");
let c = ui.button(tr("Cancel"));
regs.push(("form.cancel".into(), c.rect, "Cancel".into()));
if c.clicked() {
close = true;
@@ -786,7 +787,7 @@ pub fn show_info(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(body);
ui.add_space(16.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent)).clicked() {
if ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)).clicked() {
close = true;
}
});
@@ -800,16 +801,16 @@ pub fn show_view_options(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Toke
let mut close = false;
super::dialogs::modal(ctx, "View Options", vec2(360.0, 330.0), t, |ui| {
let v = &mut app.ui.viewer;
ui.checkbox(&mut v.show_layer_controls, "Layer controls");
ui.checkbox(&mut v.show_masks, "Masks");
ui.checkbox(&mut v.rulers, "Rulers");
ui.checkbox(&mut v.guides, "Guides");
ui.checkbox(&mut v.grid, "Grid");
ui.checkbox(&mut v.safe_margins, "Title/action safe");
ui.checkbox(&mut v.transparency_grid, "Transparency grid");
ui.checkbox(&mut v.show_layer_controls, tr("Layer controls"));
ui.checkbox(&mut v.show_masks, tr("Masks"));
ui.checkbox(&mut v.rulers, tr("Rulers"));
ui.checkbox(&mut v.guides, tr("Guides"));
ui.checkbox(&mut v.grid, tr("Grid"));
ui.checkbox(&mut v.safe_margins, tr("Title/action safe"));
ui.checkbox(&mut v.transparency_grid, tr("Transparency grid"));
ui.add_space(12.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(t.accent)).clicked() {
if ui.add(egui::Button::new(egui::RichText::new(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)).clicked() {
close = true;
}
});
+5 -5
View File
@@ -281,7 +281,7 @@ fn rail_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, rail:
let foot_h = 22.0 + links.len() as f32 * 30.0 + 22.0 + sib_rows * 26.0;
let mut cy = (rail.max.y - 16.0 - foot_h).max(y + 16.0);
p.line_segment([pos2(x0, cy - 10.0), pos2(x0 + w, cy - 10.0)], Stroke::new(1.0, t.separator));
p.text(pos2(x0, cy + 6.0), Align2::LEFT_CENTER, "Community", Tokens::semibold(12.0), t.text_dim);
p.text(pos2(x0, cy + 6.0), Align2::LEFT_CENTER, crate::i18n::tr("Community"), Tokens::semibold(12.0), t.text_dim);
cy += 22.0;
for (icon, label, cmd) in links {
let r = Rect::from_min_size(pos2(x0, cy), vec2(w, 26.0));
@@ -301,7 +301,7 @@ fn rail_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, p: &egui::Painter, rail:
}
cy += 30.0;
}
p.text(pos2(x0, cy + 8.0), Align2::LEFT_CENTER, "More ArtCraft apps", Tokens::ui(11.0), t.text_faint);
p.text(pos2(x0, cy + 8.0), Align2::LEFT_CENTER, crate::i18n::tr("More ArtCraft apps"), Tokens::ui(11.0), t.text_faint);
cy += 20.0;
let sw = (w - 6.0) / 2.0;
for (i, (name, slug)) in sib.iter().enumerate() {
@@ -328,7 +328,7 @@ fn home_page(
let t = app.tokens;
let (x0, w) = (area.min.x, area.width());
let mut y = area.min.y;
p.text(pos2(x0, y + 14.0), Align2::LEFT_CENTER, "Welcome to EffectCraft", Tokens::semibold(26.0), t.tab_text_active);
p.text(pos2(x0, y + 14.0), Align2::LEFT_CENTER, crate::i18n::tr("Welcome to EffectCraft"), Tokens::semibold(26.0), t.tab_text_active);
y += 40.0;
p.text(
pos2(x0, y + 8.0),
@@ -369,7 +369,7 @@ fn home_page(
y += 64.0 + 36.0;
// Recent: heading, filter, clear.
p.text(pos2(x0, y + 12.0), Align2::LEFT_CENTER, "Recent", Tokens::semibold(17.0), t.tab_text_active);
p.text(pos2(x0, y + 12.0), Align2::LEFT_CENTER, crate::i18n::tr("Recent"), Tokens::semibold(17.0), t.tab_text_active);
let entries = recent_entries(&app.session.prefs);
let fid = egui::Id::new("home-filter");
let mut filter: String = ctx.data(|d| d.get_temp(fid)).unwrap_or_default();
@@ -380,7 +380,7 @@ fn home_page(
if !entries.is_empty() {
let cr = Rect::from_min_size(pos2(fr.min.x - 96.0, y + 3.0), vec2(84.0, 20.0));
let resp = ui.interact(cr, egui::Id::new("home-clear-recent"), Sense::click());
p.text(cr.right_center(), Align2::RIGHT_CENTER, "Clear list", Tokens::ui(11.5), if resp.hovered() { t.text } else { t.text_faint });
p.text(cr.right_center(), Align2::RIGHT_CENTER, crate::i18n::tr("Clear list"), Tokens::ui(11.5), if resp.hovered() { t.text } else { t.text_faint });
app.auto.add("home.clearRecent", cr, "Clear Recent Projects");
if resp.clicked() {
actions.push(("file.clearRecent", json!({})));
+14 -13
View File
@@ -1,6 +1,7 @@
//! Animation dialogs: Keyframe Velocity, Keyframe Interpolation and Time Stretch. Each one is
//! pre-filled from the engine (`keys.info`, the layer) and commits through one engine command.
use crate::i18n::tr;
use egui::{Color32, vec2};
use serde_json::{Value, json};
@@ -114,10 +115,10 @@ fn buttons(ui: &mut egui::Ui, app: &mut EffectcraftApp, t: &Tokens, prefix: &str
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(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(tr("Cancel"));
app.auto.add(&format!("{prefix}.cancel"), r.rect, "Cancel");
cancel = r.clicked();
});
@@ -144,13 +145,13 @@ pub fn velocity(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(egui::RichText::new(tag).color(t.text_dim));
let side = if out { "out" } else { "in" };
ui.horizontal(|ui| {
ui.label("Speed:");
ui.label(tr("Speed:"));
let v = if out { &mut d.out_speed[dim] } else { &mut d.in_speed[dim] };
drag(ui, app, &format!("dialog.velocity.{side}Speed.{dim}"), v, 1.0, -1e7..=1e7, "", enabled);
ui.label(egui::RichText::new(&d.units).color(t.text_dim));
});
ui.horizontal(|ui| {
ui.label("Influence:");
ui.label(tr("Influence:"));
let v = if out { &mut d.out_inf[dim] } else { &mut d.in_inf[dim] };
drag(ui, app, &format!("dialog.velocity.{side}Influence.{dim}"), v, 0.2, 0.1..=100.0, " %", enabled);
});
@@ -159,7 +160,7 @@ pub fn velocity(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
});
ui.add_space(8.0);
}
let r = ui.checkbox(&mut d.continuous, "Continuous (Lock Outgoing to Incoming)");
let r = ui.checkbox(&mut d.continuous, tr("Continuous (Lock Outgoing to Incoming)"));
app.auto.add("dialog.velocity.continuous", r.rect, "Continuous");
if d.continuous {
d.out_speed = d.in_speed.clone();
@@ -203,13 +204,13 @@ pub fn interpolation(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens)
let (mut ok, mut cancel) = (false, false);
super::dialogs::modal(ctx, "Keyframe Interpolation", vec2(440.0, 250.0), t, |ui| {
egui::Grid::new("interp-grid").num_columns(2).spacing([14.0, 10.0]).show(ui, |ui| {
ui.label("Temporal Interpolation:");
ui.label(tr("Temporal Interpolation:"));
combo(ui, app, "dialog.interpolation.temporal", &mut d.temporal, &TEMPORAL.map(|x| x.0), true);
ui.end_row();
ui.label("Spatial Interpolation:");
ui.label(tr("Spatial Interpolation:"));
combo(ui, app, "dialog.interpolation.spatial", &mut d.spatial, &SPATIAL.map(|x| x.0), d.has_spatial);
ui.end_row();
ui.label("Roving:");
ui.label(tr("Roving:"));
combo(ui, app, "dialog.interpolation.roving", &mut d.roving, &ROVING, d.has_spatial);
ui.end_row();
});
@@ -245,19 +246,19 @@ pub fn time_stretch(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let mut d = app.dialog_state.stretch.clone();
let (mut ok, mut cancel) = (false, false);
super::dialogs::modal(ctx, "Time Stretch", vec2(420.0, 300.0), t, |ui| {
ui.label(egui::RichText::new("Stretch").font(Tokens::medium(12.5)));
ui.label(egui::RichText::new(tr("Stretch")).font(Tokens::medium(12.5)));
egui::Grid::new("stretch-grid").num_columns(2).spacing([14.0, 8.0]).show(ui, |ui| {
ui.label("Original Duration:");
ui.label(tr("Original Duration:"));
ui.label(format!("{:.2} s", d.orig_duration));
ui.end_row();
ui.label("Stretch Factor:");
ui.label(tr("Stretch Factor:"));
let before = d.percent;
drag(ui, app, "dialog.timeStretch.percent", &mut d.percent, 0.5, -10000.0..=10000.0, " %", true);
if (before - d.percent).abs() > 1e-9 && d.orig_percent.abs() > 1e-9 {
d.duration = d.orig_duration * d.percent.abs() / d.orig_percent.abs();
}
ui.end_row();
ui.label("New Duration:");
ui.label(tr("New Duration:"));
let before = d.duration;
drag(ui, app, "dialog.timeStretch.duration", &mut d.duration, 0.05, 0.01..=100000.0, " s", true);
if (before - d.duration).abs() > 1e-9 && d.orig_duration > 1e-9 {
@@ -266,7 +267,7 @@ pub fn time_stretch(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.end_row();
});
ui.add_space(10.0);
ui.label(egui::RichText::new("Hold In Place").font(Tokens::medium(12.5)));
ui.label(egui::RichText::new(tr("Hold In Place")).font(Tokens::medium(12.5)));
for (i, l) in ["Layer In-point", "Current Frame", "Layer Out-point"].into_iter().enumerate() {
let r = ui.radio_value(&mut d.hold, i, l);
app.auto.add(&format!("dialog.timeStretch.hold.{i}"), r.rect, l);
@@ -4,6 +4,7 @@
//! `layerStyles/<style>/<property>` property, so the viewer previews live; Cancel rolls the
//! edits made in the dialog back, OK keeps them (one undo step per control drag).
use crate::i18n::tr;
use effectcraft_engine::keyframe::Value as KV;
use effectcraft_engine::project::styles::{BLENDING, STYLES};
use effectcraft_engine::project::{Layer, LayerId, ParamUi, Property};
@@ -115,7 +116,7 @@ fn control(ui: &mut egui::Ui, p: &Property, v: &KV) -> Option<Value> {
changed.then(|| json!([rgba.r(), rgba.g(), rgba.b()]))
}
(KV::Gradient(_), _) => {
ui.label(egui::RichText::new("Edit in the Timeline").weak());
ui.label(egui::RichText::new(tr("Edit in the Timeline")).weak());
None
}
_ => {
@@ -176,7 +177,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
app.auto.add("dialog.layerStyle.page", head.rect, &sel);
ui.add_space(6.0);
let Some(g) = styles.as_ref().and_then(|s| s.sub(&sel)) else {
ui.label(egui::RichText::new("Not applied: tick it in the list to add it.").weak());
ui.label(egui::RichText::new(tr("Not applied: tick it in the list to add it.")).weak());
return;
};
egui::ScrollArea::vertical().max_height(400.0).show(ui, |ui| {
@@ -203,12 +204,12 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
});
ui.add_space(8.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let ok = ui.button("OK");
let ok = ui.button(tr("OK"));
app.auto.add("dialog.layerStyle.ok", ok.rect, "OK");
if ok.clicked() {
close = Some(true);
}
let cancel = ui.button("Cancel");
let cancel = ui.button(tr("Cancel"));
app.auto.add("dialog.layerStyle.cancel", cancel.rect, "Cancel");
if cancel.clicked() {
close = Some(false);
+11 -10
View File
@@ -10,6 +10,7 @@
//! 3D Camera Tracker / Roto Brush analyses, Content-Aware Fill, Scene Edit Detection) with a
//! progress bar and a cancel button, and the recently finished ones.
use crate::i18n::tr;
use effectcraft_engine::media_browser as mb;
use effectcraft_engine::project::ItemId;
use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
@@ -66,7 +67,7 @@ pub fn media_browser(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let p = ui.painter().with_clip_rect(rect);
p.rect_filled(rect, 0.0, t.panel_bg);
let Some(b) = app.session.media_browser() else {
p.text(rect.center(), Align2::CENTER_CENTER, "The Media Browser is not available here (use File ▸ Import)", Tokens::ui(12.0), t.text_faint);
p.text(rect.center(), Align2::CENTER_CENTER, tr("The Media Browser is not available here (use File ▸ Import)"), Tokens::ui(12.0), t.text_faint);
app.auto.add("mediaBrowser.unavailable", rect, "Media Browser unavailable");
return;
};
@@ -126,7 +127,7 @@ pub fn media_browser(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
// Left column: favourites.
let side = Rect::from_min_max(pos2(rect.min.x, rect.min.y + 54.0), pos2(rect.min.x + 160.0_f32.min(rect.width() * 0.3), rect.max.y));
p.line_segment([side.right_top(), side.right_bottom()], Stroke::new(1.0, t.separator));
p.text(pos2(side.min.x + 10.0, side.min.y + 10.0), Align2::LEFT_CENTER, "Favorites", Tokens::semibold(11.5), t.text_dim);
p.text(pos2(side.min.x + 10.0, side.min.y + 10.0), Align2::LEFT_CENTER, tr("Favorites"), Tokens::semibold(11.5), t.text_dim);
let mut fy = side.min.y + 22.0;
let mut places: Vec<(String, String)> = b.places();
places.extend(
@@ -208,22 +209,22 @@ pub fn media_browser(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
egui::DragAndDrop::set_payload(&ctx, DragPayload::Files(vec![e.path.clone()]));
}
resp.context_menu(|ui| {
if !e.is_dir && ui.button("Import").clicked() {
if !e.is_dir && ui.button(tr("Import")).clicked() {
actions.push(("mediaBrowser.import", json!({"paths": [e.path]})));
ui.close();
}
if !e.is_dir && ui.button("Import and Add to Composition").clicked() {
if !e.is_dir && ui.button(tr("Import and Add to Composition")).clicked() {
actions.push(("mediaBrowser.import", json!({"paths": [e.path], "addToComp": true})));
ui.close();
}
if e.is_dir && ui.button("Add to Favorites").clicked() {
if e.is_dir && ui.button(tr("Add to Favorites")).clicked() {
actions.push(("mediaBrowser.addFavorite", json!({"path": e.path})));
ui.close();
}
});
}
if entries.is_empty() {
lp.text(list.center(), Align2::CENTER_CENTER, "Empty folder", Tokens::ui(12.0), t.text_faint);
lp.text(list.center(), Align2::CENTER_CENTER, tr("Empty folder"), Tokens::ui(12.0), t.text_faint);
}
scroll.end(ui, &mut app.auto, "mediaBrowser.scroll", entries.len().div_ceil(cols) as f32 * tile.y + 8.0, &t);
if budget == 0 {
@@ -256,7 +257,7 @@ fn comment_field(ui: &mut egui::Ui, r: Rect, id: &str, text: &str) -> Option<Str
let key = egui::Id::new(("meta-comment", id));
let mut buf: String = ui.data(|d| d.get_temp(key)).unwrap_or_else(|| text.to_string());
let mut child = ui.new_child(egui::UiBuilder::new().max_rect(r));
let resp = child.add(egui::TextEdit::multiline(&mut buf).desired_width(r.width()).desired_rows(2).hint_text("Add a comment"));
let resp = child.add(egui::TextEdit::multiline(&mut buf).desired_width(r.width()).desired_rows(2).hint_text(tr("Add a comment")));
if resp.has_focus() || resp.changed() {
ui.data_mut(|d| d.insert_temp(key, buf.clone()));
} else {
@@ -299,7 +300,7 @@ pub fn metadata(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
y += 10.0;
} else {
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Select an item in the Project panel", Tokens::ui(12.0), t.text_faint);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Select an item in the Project panel"), Tokens::ui(12.0), t.text_faint);
y += 28.0;
}
let proj = v["project"].clone();
@@ -332,7 +333,7 @@ pub fn progress(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let x0 = rect.min.x + 12.0;
let w = (rect.width() - 24.0).max(60.0);
if jobs.is_empty() {
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, "No background jobs running", Tokens::ui(12.0), t.text_faint);
p.text(pos2(x0, y + 10.0), Align2::LEFT_CENTER, tr("No background jobs running"), Tokens::ui(12.0), t.text_faint);
y += 28.0;
} else {
ui.ctx().request_repaint_after(std::time::Duration::from_millis(150));
@@ -354,7 +355,7 @@ pub fn progress(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
if !app.session.job_log.is_empty() {
p.line_segment([pos2(x0, y), pos2(x0 + w, y)], Stroke::new(1.0, t.separator));
y += 6.0;
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, "Finished", Tokens::semibold(11.5), t.text_dim);
p.text(pos2(x0, y + 8.0), Align2::LEFT_CENTER, tr("Finished"), Tokens::semibold(11.5), t.text_dim);
y += 20.0;
for r in app.session.job_log.iter().rev().take(20) {
let col = match r.status.as_str() {
+5 -4
View File
@@ -57,6 +57,7 @@ pub mod viewer_tools;
pub mod viewers;
pub mod waveform;
use crate::i18n::tr;
use effectcraft_engine::Session;
use effectcraft_engine::project::Comp;
use effectcraft_engine::time::Tick;
@@ -140,17 +141,17 @@ pub fn panel_menu_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui) {
let area = egui::Area::new(id.with("area")).order(egui::Order::Foreground).fixed_pos(pos).show(ui.ctx(), |ui| {
egui::Frame::popup(ui.style()).show(ui, |ui| {
ui.set_min_width(200.0);
if ui.button("Close Panel").clicked() {
if ui.button(tr("Close Panel")).clicked() {
app.close_panel(panel);
close = true;
}
let floating = app.ui.floating.iter().any(|f| f.panels.contains(&panel));
if !floating && ui.button("Undock Panel").clicked() {
if !floating && ui.button(tr("Undock Panel")).clicked() {
let r = crate::dock_ui::default_float_rect(ui.ctx().content_rect());
app.edit_layout(|l| l.float(panel, r));
close = true;
}
if !floating && ui.button(if app.ui.maximized == Some(panel) { "Restore Panel Size" } else { "Maximize Panel" }).clicked() {
if !floating && ui.button(if app.ui.maximized == Some(panel) { tr("Restore Panel Size") } else { tr("Maximize Panel") }).clicked() {
app.toggle_maximize(panel);
close = true;
}
@@ -168,7 +169,7 @@ pub fn panel_menu_popup(app: &mut EffectcraftApp, ui: &mut egui::Ui) {
close = true;
}
}
if ui.button("Composition Settings…").clicked() {
if ui.button(tr("Composition Settings…")).clicked() {
let _ = dialogs::open_comp_settings(app);
close = true;
}
+4 -3
View File
@@ -2,6 +2,7 @@
//! (`app.project`, comps, layers, properties…). Each run goes through the `script.run` command
//! in the console's persistent context, so variables survive between runs; edits are undoable.
use crate::i18n::tr;
use egui::{Align2, Rect, Sense, pos2, vec2};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
@@ -113,7 +114,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
.font(Tokens::mono(12.0))
.desired_width(in_rect.width())
.desired_rows(4)
.hint_text("Script… (⌘/Ctrl+Enter runs)");
.hint_text(tr("Script… (⌘/Ctrl+Enter runs)"));
let resp = child.add_sized(in_rect.size(), te);
if resp.has_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter) && i.modifiers.command) {
run_now = true;
@@ -125,10 +126,10 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let clear_r = Rect::from_min_size(pos2(bar.max.x - 128.0, bar.min.y + 2.0), vec2(56.0, 20.0));
let run_resp = ui.interact(run_r, egui::Id::new("script-console-run"), Sense::click());
p.rect_filled(run_r, 3.0, if run_resp.hovered() { t.accent_hover } else { t.accent });
p.text(run_r.center(), Align2::CENTER_CENTER, "Run", Tokens::ui(12.0), egui::Color32::WHITE);
p.text(run_r.center(), Align2::CENTER_CENTER, tr("Run"), Tokens::ui(12.0), egui::Color32::WHITE);
let clear_resp = ui.interact(clear_r, egui::Id::new("script-console-clear"), Sense::click());
p.rect_stroke(clear_r, 3.0, egui::Stroke::new(1.0, t.field_border), egui::StrokeKind::Inside);
p.text(clear_r.center(), Align2::CENTER_CENTER, "Clear", Tokens::ui(12.0), t.text);
p.text(clear_r.center(), Align2::CENTER_CENTER, tr("Clear"), Tokens::ui(12.0), t.text);
app.auto.add("scriptConsole.run", run_r, "Run");
app.auto.add("scriptConsole.clear", clear_r, "Clear");
if run_resp.clicked() {
+2 -1
View File
@@ -6,6 +6,7 @@
//! named it). Controls with an `onDraw` handler are painted from their draw list; edit texts and
//! sliders send live `changing` updates (onChanging) while typing / dragging.
use crate::i18n::tr;
use effectcraft_engine::Services;
use effectcraft_engine::scriptui::{DrawOp, ImageRef, PathSeg, ScriptWindow, Widget, WidgetKind, WindowKind, layout};
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, UiBuilder, pos2, vec2};
@@ -410,7 +411,7 @@ pub fn show_windows(app: &mut EffectcraftApp, ctx: &egui::Context) {
pub fn panel(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect, id: u32) {
let t = app.tokens;
let Some(mut w) = app.session.script_ui.window(id).cloned() else {
ui.painter().text(rect.center(), Align2::CENTER_CENTER, "This ScriptUI panel has closed", Tokens::ui(12.0), t.text_faint);
ui.painter().text(rect.center(), Align2::CENTER_CENTER, tr("This ScriptUI panel has closed"), Tokens::ui(12.0), t.text_faint);
return;
};
// Docked panels fill their frame (ScriptUI lays them out at the panel's size).
+31 -29
View File
@@ -7,6 +7,7 @@
//! `settings.labels.<n>.name` / `.color`, `settings.button.<label>`, `settings.ok`,
//! `settings.cancel`, `settings.previous`, `settings.next`.
use crate::i18n::{tr, tr_args};
use effectcraft_engine::prefs::{Item, Kind, Page, pages};
use effectcraft_engine::segment::Task;
use egui::{Color32, RichText, vec2};
@@ -69,10 +70,10 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
let devices: Vec<String> = app.hooks.audio_devices.as_ref().map(|f| f()).unwrap_or_default();
let sel = get(key).as_str().unwrap_or("").to_string();
ui.horizontal(|ui| {
ui.add_sized(vec2(label_w, 18.0), egui::Label::new("Default Output:"));
ui.add_sized(vec2(label_w, 18.0), egui::Label::new(tr("Default Output:")));
let shown = if sel.is_empty() { "System Default".to_string() } else { sel.clone() };
let r = egui::ComboBox::from_id_salt(("settings", key)).selected_text(shown).width(260.0).show_ui(ui, |ui| {
if ui.selectable_label(sel.is_empty(), "System Default").clicked() {
if ui.selectable_label(sel.is_empty(), tr("System Default")).clicked() {
acts.push(Act::Set(key.into(), json!("")));
}
for d in &devices {
@@ -84,7 +85,7 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
reg(app, &format!("settings.{key}"), &r.response, "Default Output");
});
if devices.is_empty() {
ui.label(RichText::new("No device list from this host; the system default output is used.").color(t.text_dim));
ui.label(RichText::new(tr("No device list from this host; the system default output is used.")).color(t.text_dim));
}
}
Item::Setting { key, label, kind, live } => {
@@ -129,9 +130,9 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
}
Kind::Slider(lo, hi) => {
let mut x = v.as_f64().unwrap_or(0.0);
ui.label(RichText::new("Darker").color(t.text_dim));
ui.label(RichText::new(tr("Darker")).color(t.text_dim));
let r = ui.add(egui::Slider::new(&mut x, lo..=hi).show_value(false));
ui.label(RichText::new("Lighter").color(t.text_dim));
ui.label(RichText::new(tr("Lighter")).color(t.text_dim));
if ui.small_button("Default").clicked() {
acts.push(Act::Set(key.into(), json!(0.0)));
}
@@ -163,7 +164,7 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
acts.push(Act::Set(key.into(), json!(s)));
}
if kind == Kind::Path {
let b = ui.button("Choose...");
let b = ui.button(tr("Choose..."));
reg(app, &format!("{id}.choose"), &b, "Choose...");
if b.clicked() {
acts.push(if key.ends_with("Path") { Act::PickProject(key.into()) } else { Act::PickFolder(key.into()) });
@@ -253,7 +254,7 @@ fn models_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, acts: &mut
ui.label(RichText::new(format!("By {by}")).color(t.text_dim));
}
ui.horizontal_wrapped(|ui| {
ui.label(RichText::new(format!("Licence: {}", m["licence"].as_str().unwrap_or_default())).color(t.text_dim));
ui.label(RichText::new(tr_args("Licence: {}", &[&(m["licence"].as_str().unwrap_or_default())])).color(t.text_dim));
if let Some(size) = m["size"].as_u64() {
ui.label(RichText::new(format!("· {}", human_bytes(size))).color(t.text_dim));
}
@@ -271,21 +272,21 @@ fn models_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, acts: &mut
}
ui.horizontal(|ui| {
if !installed && !web {
let r = ui.add_enabled(busy.is_none(), egui::Button::new("Download"));
let r = ui.add_enabled(busy.is_none(), egui::Button::new(tr("Download")));
reg(app, &format!("settings.{prefix}.download.{id}"), &r, "Download");
if r.clicked() {
acts.push(Act::Run(format!("{prefix}.model.download"), json!({"id": id})));
}
}
if !web {
let r = ui.add_enabled(busy.is_none(), egui::Button::new("Install from File…"));
let r = ui.add_enabled(busy.is_none(), egui::Button::new(tr("Install from File…")));
reg(app, &format!("settings.{prefix}.install.{id}"), &r, "Install from File");
if r.clicked() {
acts.push(Act::PickModel(task));
}
}
if installed {
let r = ui.add_enabled(busy.is_none(), egui::Button::new("Remove"));
let r = ui.add_enabled(busy.is_none(), egui::Button::new(tr("Remove")));
reg(app, &format!("settings.{prefix}.remove.{id}"), &r, "Remove");
if r.clicked() {
acts.push(Act::Run(format!("{prefix}.model.remove"), json!({"id": id})));
@@ -305,9 +306,9 @@ fn models_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, acts: &mut
ui.label(RichText::new(e).color(t.danger));
}
if web {
ui.label(RichText::new("Trained models are available in the desktop app; the browser uses the classic engine.").color(t.text_dim));
ui.label(RichText::new(tr("Trained models are available in the desktop app; the browser uses the classic engine.")).color(t.text_dim));
} else if let Some(f) = info["folder"].as_str() {
ui.label(RichText::new(format!("Models folder: {f}")).color(t.text_faint));
ui.label(RichText::new(tr_args("Models folder: {}", &[&f])).color(t.text_faint));
}
}
@@ -318,7 +319,7 @@ fn browser_storage_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, a
let info = host.info();
let u = |v: &Value| v.as_u64().unwrap_or(0);
ui.add_space(6.0);
ui.label(RichText::new("Browser Storage").font(Tokens::semibold(12.5)).color(t.tab_text_active));
ui.label(RichText::new(tr("Browser Storage")).font(Tokens::semibold(12.5)).color(t.tab_text_active));
ui.separator();
let backend = match info["backend"].as_str().unwrap_or("") {
"opfs" => "Origin Private File System",
@@ -327,7 +328,7 @@ fn browser_storage_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, a
"" => "Unknown",
other => other,
};
let r = ui.label(format!("Stored in: {backend}"));
let r = ui.label(tr_args("Stored in: {}", &[&backend]));
reg(app, "settings.storage.backend", &r, backend);
let (usage, quota) = (u(&info["usage"]), u(&info["quota"]));
let frac = if quota > 0 { usage as f32 / quota as f32 } else { 0.0 };
@@ -342,7 +343,8 @@ fn browser_storage_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, a
let f = &info["files"];
let row = |what: &str, k: &str| format!("{what}: {} ({})", u(&f[k]["count"]), human_bytes(u(&f[k]["bytes"])));
ui.label(
RichText::new(format!("{} · {} · {}", row("Projects", "projects"), row("Imported media", "media"), row("Auto-saves", "autoSaves"))).color(t.text_dim),
RichText::new(format!("{} · {} · {}", row(tr("Projects"), "projects"), row(tr("Imported media"), "media"), row(tr("Auto-saves"), "autoSaves")))
.color(t.text_dim),
);
let d = &info["diskCache"];
let dc = if d["enabled"].as_bool().unwrap_or(false) {
@@ -364,7 +366,7 @@ fn browser_storage_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, a
let state = if persisted { "Persistent storage: granted" } else { "Persistent storage: not granted (the browser may clear it when space runs low)" };
ui.label(state);
if !persisted {
let r = ui.button("Request Persistent Storage");
let r = ui.button(tr("Request Persistent Storage"));
reg(app, "settings.storage.persist", &r, "Request Persistent Storage");
if r.clicked() {
acts.push(Act::Run("storage.persist".into(), json!({})));
@@ -387,7 +389,7 @@ fn browser_storage_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, a
}
fn labels_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, cur: &Value, acts: &mut Vec<Act>) {
ui.label("Label Colors and Names:");
ui.label(tr("Label Colors and Names:"));
ui.add_space(4.0);
let labels = cur.get("labels").and_then(Value::as_array).cloned().unwrap_or_default();
egui::Grid::new("settings-labels").num_columns(4).spacing([10.0, 4.0]).show(ui, |ui| {
@@ -411,7 +413,7 @@ fn labels_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, cur: &Value, acts: &mu
}
});
ui.add_space(8.0);
let r = ui.button("Reset Labels");
let r = ui.button(tr("Reset Labels"));
app.auto.add("settings.button.Reset Labels", r.rect, "Reset Labels");
if r.clicked() {
acts.push(Act::Run("prefs.reset".into(), json!({"page": "labels"})));
@@ -474,21 +476,21 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.add_space(8.0);
ui.separator();
ui.horizontal(|ui| {
let r = ui.add_enabled(idx > 0, egui::Button::new("Previous"));
let r = ui.add_enabled(idx > 0, egui::Button::new(tr("Previous")));
app.auto.add("settings.previous", r.rect, "Previous");
if r.clicked() {
page = all[idx - 1].id.to_string();
}
let r = ui.add_enabled(idx + 1 < all.len(), egui::Button::new("Next"));
let r = ui.add_enabled(idx + 1 < all.len(), egui::Button::new(tr("Next")));
app.auto.add("settings.next", r.rect, "Next");
if r.clicked() {
page = all[idx + 1].id.to_string();
}
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("settings.ok", r.rect, "OK");
ok |= r.clicked();
let r = ui.button("Cancel");
let r = ui.button(tr("Cancel"));
app.auto.add("settings.cancel", r.rect, "Cancel");
cancel |= r.clicked();
});
@@ -562,10 +564,10 @@ pub fn recovery(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
};
let mut choice: Option<&str> = None;
super::dialogs::modal(ctx, "Recover Project", vec2(560.0, 230.0), t, |ui| {
ui.label("EffectCraft didn't quit normally last time.");
ui.label(tr("EffectCraft didn't quit normally last time."));
ui.add_space(6.0);
if let Some(p) = &r.project {
ui.label(format!("Project: {p}"));
ui.label(tr_args("Project: {}", &[&p]));
}
if let Some(a) = &r.autosave {
let when = std::fs::metadata(a).and_then(|m| m.modified()).ok().and_then(|m| m.elapsed().ok()).map(|d| {
@@ -578,26 +580,26 @@ pub fn recovery(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
format!("{} hours ago", m / 60)
}
});
ui.label(format!("Latest auto-save: {a}"));
ui.label(tr_args("Latest auto-save: {}", &[&a]));
if let Some(w) = when {
ui.label(RichText::new(format!("Saved {w}")).color(t.text_dim));
ui.label(RichText::new(tr_args("Saved {}", &[&w])).color(t.text_dim));
}
}
ui.add_space(14.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let b = ui.add(egui::Button::new(RichText::new(" Open Auto-Save ").color(Color32::WHITE)).fill(t.accent));
let b = ui.add(egui::Button::new(RichText::new(tr(" Open Auto-Save ")).color(Color32::WHITE)).fill(t.accent));
app.auto.add("recovery.openAutoSave", b.rect, "Open Auto-Save");
if b.clicked() {
choice = Some("autosave");
}
if r.project.is_some() {
let b = ui.button("Open Last Saved Project");
let b = ui.button(tr("Open Last Saved Project"));
app.auto.add("recovery.openProject", b.rect, "Open Last Saved Project");
if b.clicked() {
choice = Some("project");
}
}
let b = ui.button("Don't Recover");
let b = ui.button(tr("Don't Recover"));
app.auto.add("recovery.dismiss", b.rect, "Don't Recover");
if b.clicked() {
choice = Some("none");
+2 -2
View File
@@ -55,7 +55,7 @@ pub fn home_tab(app: &mut EffectcraftApp, ui: &mut egui::Ui, area: Rect) {
let ctx = ui.ctx().clone();
let p = ui.painter().with_clip_rect(area);
let (x, y, w) = (area.min.x, area.min.y, area.width());
p.text(pos2(x, y + 6.0), Align2::LEFT_CENTER, "New from Template", Tokens::semibold(16.0), Color32::WHITE);
p.text(pos2(x, y + 6.0), Align2::LEFT_CENTER, crate::i18n::tr("New from Template"), Tokens::semibold(16.0), Color32::WHITE);
p.text(
pos2(x, y + 26.0),
Align2::LEFT_CENTER,
@@ -134,7 +134,7 @@ pub fn home_tab(app: &mut EffectcraftApp, ui: &mut egui::Ui, area: Rect) {
let mut del_hit = false;
if !tpl.builtin {
let dr = Rect::from_min_size(pos2(tr.max.x - 30.0, tr.min.y + 6.0), vec2(24.0, 24.0));
let dresp = ui.interact(dr, egui::Id::new(("template-del", &tpl.id)), Sense::click()).on_hover_text("Delete this template");
let dresp = ui.interact(dr, egui::Id::new(("template-del", &tpl.id)), Sense::click()).on_hover_text(crate::i18n::tr("Delete this template"));
if resp.hovered() || dresp.hovered() {
p.circle_filled(dr.center(), 12.0, if dresp.hovered() { Color32::from_rgb(0xc0, 0x3a, 0x3a) } else { Color32::from_black_alpha(170) });
icons::paint(&p, dr.shrink(6.0), Icon::Close, Color32::WHITE);
+4 -3
View File
@@ -7,6 +7,7 @@
//! Automation ids: `dialog.unsaved.save`, `dialog.unsaved.dontSave` (Revert: `dialog.unsaved.revert`),
//! `dialog.unsaved.cancel`.
use crate::i18n::tr;
use egui::{Color32, vec2};
use serde_json::{Value, json};
@@ -96,7 +97,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.label(egui::RichText::new(text).color(t.tab_text_active));
if !revert {
ui.add_space(4.0);
ui.label(egui::RichText::new("Your changes will be lost if you don't save them.").color(t.text_dim).size(11.0));
ui.label(egui::RichText::new(tr("Your changes will be lost if you don't save them.")).color(t.text_dim).size(11.0));
}
ui.add_space(18.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
@@ -106,7 +107,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
if r.clicked() {
answer = Some(if revert { Answer::Discard } else { Answer::Save });
}
let r = ui.button("Cancel");
let r = ui.button(tr("Cancel"));
app.auto.add("dialog.unsaved.cancel", r.rect, "Cancel");
if r.clicked() {
answer = Some(Answer::Cancel);
@@ -114,7 +115,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
if !revert {
// Set apart from Save, as is customary.
ui.add_space(ui.available_width() - 90.0);
let r = ui.button("Don't Save");
let r = ui.button(tr("Don't Save"));
app.auto.add("dialog.unsaved.dontSave", r.rect, "Don't Save");
if r.clicked() {
answer = Some(Answer::Discard);