Merge pull request #490 from cjcwwww/i18n/panel-helper-strings

This commit is contained in:
Claude committed 2026-10-10 12:53:18 +00:00
commit 5b7c314317
10 files changed
+197 -39

No files matched your search

+149 -4
View File
@@ -129,7 +129,7 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[
("Casts Shadows", "投射阴影"),
("Change Renderer…", "更改渲染器…"),
("Changing a shortcut creates a custom preset; the default stays as it is.", "修改快捷键会创建自定义预设;默认预设保持不变。"),
("Channel", "色版"),
("Channel", "通道"),
("Channel:", "通道:"),
("Chapter:", "章节:"),
("Choose...", "选择..."),
@@ -285,7 +285,7 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[
("More ArtCraft apps", "更多 ArtCraft 应用"),
("More tutorials", "更多教程"),
("Motion Blur", "运动模糊"),
("Motion Blur: {}", "动态模糊:{}"),
("Motion Blur: {}", "运动模糊:{}"),
("Motion Sketch: drag to record", "动态草图:拖动以记录"),
("Move Down", "下移"),
("Move Up", "上移"),
@@ -490,6 +490,64 @@ const SIMPLIFIED_CHINESE: &[(&str, &str)] = &[
("⌘/Ctrl-drag a pin to record its motion", "⌘/Ctrl 拖动定位点可记录其运动"),
("▣ {}", "▣ {}"),
("⚠ {} is used by:", "⚠ {} 已被以下项目使用:"),
// Strings a panel used to hand to a helper as a bare literal: the helper draws them (the
// viewer's toolbar tooltips and popup items, the solid dialog's checkbox), so the lookup has
// to reach the catalog from inside the helper. Kept at the end so the catalogs stay parallel.
("3D Reference Axes", "3D 参考轴"),
("Affect all layers that use this solid", "影响使用该纯色的所有图层"),
("Choose grid and guide options", "选择网格和参考线选项"),
("Colorize", "着色"),
("Custom", "自定义"),
("Custom...", "自定义…"),
("Fit", "适合"),
("Fit up to 100%", "适合(最大 100%)"),
("Full", "完整"),
("Half", "一半"),
("Only this layer uses this solid.", "仅此图层使用该纯色。"),
("Proportional Grid", "比例网格"),
("Quarter", "四分之一"),
("Region of Interest", "目标区域"),
("Reset Exposure", "重置曝光"),
("Show Channel and Color Management Settings", "显示通道和色彩管理设置"),
("Show Snapshot (hold)", "显示快照(按住)"),
("Take Snapshot", "拍摄快照"),
("Third", "三分之一"),
("Toggle Mask and Shape Path Visibility", "切换蒙版与形状路径可见性"),
("Toggle Transparency Grid", "切换透明网格"),
// Technical terms and proper nouns: the project settings, the import and export forms, the
// viewer's channel and Fast Previews popups, the keyframe dialogs and the About window. The
// terms that are the same in Chinese (Display P3, HDTV (Rec. 709), RGB, Alpha, H.264, ProRes…)
// need no row: a missing row falls back to the English source, which is the decision.
("16 bits per channel", "每通道 16 位"),
("32 bits per channel (float)", "每通道 32 位(浮点)"),
("8 bits per channel", "每通道 8 位"),
("Adaptive Resolution", "自适应分辨率"),
("Adobe-style built-in", "Adobe 风格内置"),
("Blue", "蓝色"),
("Current Settings", "当前设置"),
("Disable Per-character 3D", "停用逐字 3D 化"),
("Draft", "草图"),
("Enable Per-character 3D", "启用逐字 3D 化"),
("Fast Draft", "快速草图"),
("Fast Previews (Draft 3D, Faster Effects)", "快速预览(3D 草图、更快效果)"),
("Green", "绿色"),
("Import Image Sequence", "导入图像序列"),
("Import PDF / Illustrator File", "导入 PDF / Illustrator 文件"),
("Import Photoshop File", "导入 Photoshop 文件"),
("Interpret Unlabeled Alpha As", "将未标记的 Alpha 解释为"),
("Join the ArtCraft Discord", "加入 ArtCraft Discord"),
("Mercury GPU Acceleration ({})", "Mercury GPU 加速({})"),
("Mercury Software Only", "仅 Mercury 软件"),
("OCIO (built-in ACES config)", "OCIO(内置 ACES 配置)"),
("Off (Final Quality)", "关闭(最终质量)"),
("OpenGL (compatibility)", "OpenGL(兼容)"),
("Opus Bitrate: {} kbps", "Opus 比特率:{} kbps"),
("RGB Straight", "RGB 直接"),
("Red", "红色"),
("Tone map", "色调映射"),
("Use In-Window Menu Bar on macOS", "在 macOS 上使用窗口内菜单栏"),
("Wireframe", "线框"),
("sRGB IEC61966-2.1 (default)", "sRGB IEC61966-2.1(默认)"),
];
const TRADITIONAL_CHINESE: &[(&str, &str)] = &[
@@ -907,6 +965,61 @@ const TRADITIONAL_CHINESE: &[(&str, &str)] = &[
("⌘/Ctrl-drag a pin to record its motion", "⌘/Ctrl 拖曳釘選點可記錄其運動"),
("▣ {}", "▣ {}"),
("⚠ {} is used by:", "⚠ {} 已被以下專案使用:"),
// The same rows as the Simplified catalog (the tests require both lists to match). The
// Traditional wording is the translator's call and is left as the English source for now.
("3D Reference Axes", "3D Reference Axes"),
("Affect all layers that use this solid", "Affect all layers that use this solid"),
("Choose grid and guide options", "Choose grid and guide options"),
("Colorize", "Colorize"),
("Custom", "Custom"),
("Custom...", "Custom..."),
("Fit", "Fit"),
("Fit up to 100%", "Fit up to 100%"),
("Full", "Full"),
("Half", "Half"),
("Only this layer uses this solid.", "Only this layer uses this solid."),
("Proportional Grid", "Proportional Grid"),
("Quarter", "Quarter"),
("Region of Interest", "Region of Interest"),
("Reset Exposure", "Reset Exposure"),
("Show Channel and Color Management Settings", "Show Channel and Color Management Settings"),
("Show Snapshot (hold)", "Show Snapshot (hold)"),
("Take Snapshot", "Take Snapshot"),
("Third", "Third"),
("Toggle Mask and Shape Path Visibility", "Toggle Mask and Shape Path Visibility"),
("Toggle Transparency Grid", "Toggle Transparency Grid"),
// The same rows as the Simplified catalog (the tests require both lists to match). The
// Traditional wording is the translator's call and stays the English source until then.
("16 bits per channel", "16 bits per channel"),
("32 bits per channel (float)", "32 bits per channel (float)"),
("8 bits per channel", "8 bits per channel"),
("Adaptive Resolution", "Adaptive Resolution"),
("Adobe-style built-in", "Adobe-style built-in"),
("Blue", "Blue"),
("Current Settings", "Current Settings"),
("Disable Per-character 3D", "Disable Per-character 3D"),
("Draft", "Draft"),
("Enable Per-character 3D", "Enable Per-character 3D"),
("Fast Draft", "Fast Draft"),
("Fast Previews (Draft 3D, Faster Effects)", "Fast Previews (Draft 3D, Faster Effects)"),
("Green", "Green"),
("Import Image Sequence", "Import Image Sequence"),
("Import PDF / Illustrator File", "Import PDF / Illustrator File"),
("Import Photoshop File", "Import Photoshop File"),
("Interpret Unlabeled Alpha As", "Interpret Unlabeled Alpha As"),
("Join the ArtCraft Discord", "Join the ArtCraft Discord"),
("Mercury GPU Acceleration ({})", "Mercury GPU Acceleration ({})"),
("Mercury Software Only", "Mercury Software Only"),
("OCIO (built-in ACES config)", "OCIO (built-in ACES config)"),
("Off (Final Quality)", "Off (Final Quality)"),
("OpenGL (compatibility)", "OpenGL (compatibility)"),
("Opus Bitrate: {} kbps", "Opus Bitrate: {} kbps"),
("RGB Straight", "RGB Straight"),
("Red", "Red"),
("Tone map", "Tone map"),
("Use In-Window Menu Bar on macOS", "Use In-Window Menu Bar on macOS"),
("Wireframe", "Wireframe"),
("sRGB IEC61966-2.1 (default)", "sRGB IEC61966-2.1 (default)"),
];
#[cfg(test)]
@@ -965,6 +1078,12 @@ mod tests {
("panels/viewer.rs", include_str!("../panels/viewer.rs")),
("panels/viewer_tools.rs", include_str!("../panels/viewer_tools.rs")),
("panels/viewers.rs", include_str!("../panels/viewers.rs")),
// Text the interface draws but does not own: the settings pages and the viewer's channel
// and Fast Previews lists come from the engine as data, so their source strings live here.
// Both files pass the literal scan below unchanged (they hold no drawn literals).
("engine settings pages", include_str!("../../../engine/src/prefs.rs")),
("engine viewer channels", include_str!("../../../engine/src/viewer.rs")),
("engine viewer commands", include_str!("../../../engine/src/commands/viewer_cmds.rs")),
];
/// Call shapes that hand a string literal straight to the user.
@@ -976,6 +1095,7 @@ mod tests {
".heading(\"",
".monospace(\"",
".on_hover_text(\"",
".on_disabled_hover_text(\"",
".hint_text(\"",
".text(\"",
".prefix(\"",
@@ -1039,19 +1159,44 @@ mod tests {
}
/// Every row is asked for by a converted panel: a leftover row means the panel went back to its
/// English literal.
/// English literal. A string a helper draws for the panel (a toolbar tooltip, a popup item, a
/// checkbox label) is looked up inside that helper, so its English source only appears as a
/// literal there - `helper_labels_are_translated` keeps those helpers honest.
#[test]
fn every_row_is_used_by_a_converted_panel() {
for (en, _) in catalogs() {
let plain = format!("tr({en:?})");
let formatted = format!("tr_args({en:?}");
let bare = format!("{en:?}");
assert!(
CONVERTED.iter().any(|(_, src)| src.contains(&plain) || src.contains(&formatted)),
CONVERTED.iter().any(|(_, src)| src.contains(&plain) || src.contains(&formatted) || src.contains(&bare)),
"{en:?} is in the catalog but no converted panel asks for it"
);
}
}
/// A label that a helper takes as an argument is drawn by the helper, so the helper has to ask
/// the catalog: `Checkbox::new` and `RadioButton::new` are not among the scanned call shapes
/// (the label is their second argument), which let the solid dialog's checkbox ship in English.
#[test]
fn helper_labels_are_translated() {
for (path, source) in CONVERTED {
for marker in ["Checkbox::new(", "RadioButton::new("] {
let mut from = 0;
while let Some(at) = source[from..].find(marker) {
let at = from + at;
let rest = &source[at + marker.len()..];
let line = &rest[..rest.find('\n').unwrap_or(rest.len())];
if let Some(q) = line.find('"') {
let before = line[..q].trim_end();
assert!(before.ends_with("tr("), "{path}: the `{marker}` label `{}` is not in the catalog", &line[q..]);
}
from = at + marker.len();
}
}
}
}
/// A newly hard-coded interface string fails here instead of shipping in English to every
/// language. It is a heuristic - it looks at the call shapes the panels use - and it ignores
/// one-character literals, which are punctuation or a key name rather than a sentence.
+5 -3
View File
@@ -340,11 +340,13 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr
Tab::Renderer => {
ui.horizontal(|ui| {
ui.label(tr("Renderer:"));
let r =
egui::ComboBox::from_id_salt("comp-renderer").selected_text(if d.advanced_3d { "Advanced 3D" } else { "Classic 3D" }).show_ui(ui, |ui| {
let r = egui::ComboBox::from_id_salt("comp-renderer").selected_text(tr(if d.advanced_3d { "Advanced 3D" } else { "Classic 3D" })).show_ui(
ui,
|ui| {
ui.selectable_value(&mut d.advanced_3d, false, tr("Classic 3D"));
ui.selectable_value(&mut d.advanced_3d, true, tr("Advanced 3D"));
});
},
);
auto.add("dialog.comp.renderer", r.response.rect, "Renderer");
});
}
+5 -4
View File
@@ -158,7 +158,8 @@ pub(crate) fn modal(ctx: &egui::Context, title: &str, size: egui::Vec2, t: &Toke
.show(ctx, |ui| {
ui.set_width(size.x - 36.0);
ui.set_min_height(size.y - 36.0);
ui.label(egui::RichText::new(title).font(Tokens::semibold(15.0)).color(t.tab_text_active));
// The id stays keyed on the untranslated source, so switching language cannot move it.
ui.label(egui::RichText::new(tr(title)).font(Tokens::semibold(15.0)).color(t.tab_text_active));
ui.add_space(10.0);
egui::ScrollArea::both().max_height((available.y - 84.0).max(40.0)).auto_shrink([false, true]).show(ui, body);
});
@@ -291,7 +292,7 @@ fn about_main(app: &mut EffectcraftApp, ui: &mut egui::Ui, t: &Tokens, cmd: &mut
);
let foreground = if discord { Color32::WHITE } else { t.text };
icons::paint(p, Rect::from_center_size(pos2(r.min.x + 18.0, r.center().y), vec2(15.0, 15.0)), icon, foreground);
p.text(pos2(r.min.x + 36.0, r.center().y), Align2::LEFT_CENTER, label, Tokens::medium(12.5), foreground);
p.text(pos2(r.min.x + 36.0, r.center().y), Align2::LEFT_CENTER, tr(label), Tokens::medium(12.5), foreground);
p.text(
pos2(r.max.x - 12.0, r.center().y),
Align2::RIGHT_CENTER,
@@ -418,8 +419,8 @@ fn solid(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
if editing.is_some() {
ui.add_space(8.0);
let r = ui
.add_enabled(shared, egui::Checkbox::new(&mut affect_all, "Affect all layers that use this solid"))
.on_disabled_hover_text("Only this layer uses this solid.");
.add_enabled(shared, egui::Checkbox::new(&mut affect_all, tr("Affect all layers that use this solid")))
.on_disabled_hover_text(tr("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(tr("This change will create a new solid for this layer.")).color(t.text_dim).size(11.0));
+10 -8
View File
@@ -4,7 +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 crate::i18n::{tr, tr_args};
use egui::{Color32, vec2};
use serde_json::{Map, Value, json};
@@ -487,9 +487,11 @@ pub fn open_form(app: &mut EffectcraftApp, id: &str, p: &Value) -> bool {
}
"file.projectSettings" if p.as_object().is_none_or(|m| m.is_empty()) => {
let st = &s.project.settings;
// The catalogue row is `Mercury GPU Acceleration ({})`, so the value goes through
// tr_args: wrapping the assembled string in tr() would look up the backend's name.
let gpu_label = match &s.accel {
Some(a) => format!("Mercury GPU Acceleration ({})", a.name()),
None => "Mercury GPU Acceleration (no GPU: software)".to_string(),
Some(a) => tr_args("Mercury GPU Acceleration ({})", &[&a.name()]),
None => tr("Mercury GPU Acceleration (no GPU: software)").to_string(),
};
let depth = match st.bit_depth.label() {
l if l.starts_with("16") => 1,
@@ -765,7 +767,7 @@ pub fn show_form(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
super::dialogs::modal(ctx, &f.title.clone(), vec2(480.0, h), t, |ui| {
egui::Grid::new("form-grid").num_columns(2).spacing([14.0, 10.0]).show(ui, |ui| {
for (i, fl) in f.fields.iter_mut().enumerate() {
ui.label(&fl.label);
ui.label(tr(&fl.label));
let r = match &mut fl.kind {
FieldKind::Number { value, speed } => {
let dec = if *speed < 0.01 { 5 } else { 3 };
@@ -801,7 +803,7 @@ pub fn show_form(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
FieldKind::Note(text) => {
ui.scope(|ui| {
ui.set_width(220.0);
ui.add(egui::Label::new(egui::RichText::new(text.as_str()).small()).wrap())
ui.add(egui::Label::new(egui::RichText::new(tr(text.as_str())).small()).wrap())
})
.inner
.rect
@@ -810,11 +812,11 @@ pub fn show_form(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
FieldKind::Choice { options, sel } => {
let cur = options.get(*sel).map(|o| o.0.clone()).unwrap_or_default();
egui::ComboBox::from_id_salt(("form", fl.key.as_str()))
.selected_text(cur)
.selected_text(tr(&cur))
.width(240.0)
.show_ui(ui, |ui| {
for (i, (l, _)) in options.iter().enumerate() {
ui.selectable_value(sel, i, l);
ui.selectable_value(sel, i, tr(l));
}
})
.response
@@ -888,7 +890,7 @@ pub fn show_info(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
let (title, body) = app.dialog_state.info.clone();
let mut close = false;
super::dialogs::modal(ctx, &title, vec2(440.0, 160.0), t, |ui| {
ui.label(body);
ui.label(tr(&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(tr(" OK ")).color(Color32::WHITE)).fill(t.accent)).clicked() {
+3 -2
View File
@@ -190,9 +190,10 @@ pub fn velocity(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
fn combo(ui: &mut egui::Ui, app: &mut EffectcraftApp, id: &str, sel: &mut usize, opts: &[&str], enabled: bool) {
ui.add_enabled_ui(enabled, |ui| {
let r = egui::ComboBox::from_id_salt(id).width(200.0).selected_text(opts[*sel]).show_ui(ui, |ui| {
// The options are the English source strings; the automation id and label stay English.
let r = egui::ComboBox::from_id_salt(id).width(200.0).selected_text(tr(opts[*sel])).show_ui(ui, |ui| {
for (i, o) in opts.iter().enumerate() {
ui.selectable_value(sel, i, *o);
ui.selectable_value(sel, i, tr(o));
}
});
app.auto.add(id, r.response.rect, id);
+3 -1
View File
@@ -15,6 +15,7 @@ use egui::{Align2, Color32, Rect, Sense, Stroke, pos2, vec2};
use serde_json::json;
use crate::dock::PanelKind;
use crate::i18n::tr;
use crate::icons::{self, Icon};
use crate::theme::Tokens;
use crate::{EffectcraftApp, widgets};
@@ -120,7 +121,8 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
let per_char = layer.props.prop("text/perChar3d").is_some_and(|q| q.value.as_bool());
let entries = animator_entries(per_char);
let labels: Vec<String> = entries.iter().map(|(l, _)| l.clone()).collect();
// The entries are the English source strings; the menu draws them translated.
let labels: Vec<String> = entries.iter().map(|(l, _)| tr(l).to_string()).collect();
if let Some(i) = widgets::popup_menu(ui, pop, br.left_bottom(), &labels, None) {
match entries[i].1 {
"" => {}
+1 -1
View File
@@ -183,7 +183,7 @@ fn output_menu(it: &RenderQueueItem, available: &[OutputFormat], templates: &Ren
}
v.push(("-".into(), Value::Null));
for kbps in [24u32, 32, 64, 96, 128, 192, 256] {
v.push((mark(o.opus_bitrate_kbps == kbps, &format!("Opus Bitrate: {kbps} kbps")), json!({"audioBitrate": kbps})));
v.push((mark(o.opus_bitrate_kbps == kbps, &tr_args("Opus Bitrate: {} kbps", &[&kbps])), json!({"audioBitrate": kbps})));
}
for (a, k) in [(OpusApplication::Audio, "audio"), (OpusApplication::Voip, "voice")] {
v.push((mark(o.opus_application == a, &tr_args("Opus Tuning: {}", &[&(a.label())])), json!({"opusApplication": k})));
+9 -9
View File
@@ -50,14 +50,14 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
match *item {
Item::Section(title) => {
ui.add_space(6.0);
ui.label(RichText::new(title).font(Tokens::semibold(12.5)).color(t.tab_text_active));
ui.label(RichText::new(tr(title)).font(Tokens::semibold(12.5)).color(t.tab_text_active));
ui.separator();
}
Item::Note(text) => {
ui.label(RichText::new(text).color(t.text_dim));
ui.label(RichText::new(tr(text)).color(t.text_dim));
}
Item::Button { label, command, params } => {
let r = ui.button(label);
let r = ui.button(tr(label));
reg(app, &format!("settings.button.{}", label.trim_end_matches('.')), &r, label);
if r.clicked() {
acts.push(Act::Run(command.into(), serde_json::from_str(params).unwrap_or(json!({}))));
@@ -96,7 +96,7 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
match kind {
Kind::Bool => {
let mut b = v.as_bool().unwrap_or(false);
let r = ui.checkbox(&mut b, label);
let r = ui.checkbox(&mut b, tr(label));
if r.changed() {
acts.push(Act::Set(key.into(), json!(b)));
}
@@ -104,7 +104,7 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
}
_ => {
ui.horizontal(|ui| {
ui.add_sized(vec2(label_w, 18.0), egui::Label::new(format!("{label}:")).truncate());
ui.add_sized(vec2(label_w, 18.0), egui::Label::new(format!("{}:", tr(label))).truncate());
let r = match kind {
Kind::Int(lo, hi, unit) => {
let mut x = v.as_i64().unwrap_or(0);
@@ -146,11 +146,11 @@ fn page_ui(app: &mut EffectcraftApp, ui: &mut egui::Ui, page: &Page, cur: &Value
let curv = v.as_str().map_or_else(|| v.to_string(), str::to_string);
let shown = opts.iter().find(|o| o.1 == curv).map(|o| o.0).unwrap_or(curv.as_str()).to_string();
egui::ComboBox::from_id_salt(("settings", key))
.selected_text(shown)
.selected_text(tr(&shown))
.width(220.0)
.show_ui(ui, |ui| {
for (l, val) in opts {
if ui.selectable_label(*val == curv, *l).clicked() {
if ui.selectable_label(*val == curv, tr(l)).clicked() {
acts.push(Act::Set(key.into(), json!(val)));
}
}
@@ -449,7 +449,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
ui.painter().text(
r.left_center() + vec2(10.0, 0.0),
egui::Align2::LEFT_CENTER,
p.title,
tr(p.title),
Tokens::ui(12.5),
if sel { Color32::WHITE } else { t.text },
);
@@ -466,7 +466,7 @@ pub fn show(app: &mut EffectcraftApp, ctx: &egui::Context, t: &Tokens) {
// dialog scrolled sideways a little (#284).
ui.set_width(ui.available_width());
let p = &all[idx];
ui.label(RichText::new(p.title).font(Tokens::semibold(14.0)).color(t.tab_text_active));
ui.label(RichText::new(tr(p.title)).font(Tokens::semibold(14.0)).color(t.tab_text_active));
ui.add_space(6.0);
egui::ScrollArea::vertical().max_height(body_h - 30.0).auto_shrink([false, false]).show(ui, |ui| {
page_ui(app, ui, p, &cur, t, &mut acts);
+2 -1
View File
@@ -14,6 +14,7 @@ use effectcraft_engine::time::Tick;
use egui::{Align2, Color32, Pos2, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
use serde_json::json;
use crate::i18n::tr;
use crate::icons::{self, Icon};
use crate::theme::Tokens;
use crate::{EffectcraftApp, widgets};
@@ -3280,7 +3281,7 @@ fn text_anim_popups(
let mut opts: Vec<String> = Vec::new();
let mut acts: Vec<(String, serde_json::Value)> = Vec::new();
if key == "animate" {
opts.push(if per_char { "Disable Per-character 3D" } else { "Enable Per-character 3D" }.into());
opts.push(tr(if per_char { "Disable Per-character 3D" } else { "Enable Per-character 3D" }).to_string());
acts.push(("layer.enablePerChar3D".into(), json!({"layer": layer.id.0, "enabled": !per_char})));
opts.push("-".into());
acts.push((String::new(), json!(null)));
+10 -6
View File
@@ -486,7 +486,9 @@ fn popup(app: &mut EffectcraftApp, ui: &mut egui::Ui, id: &str, anchor: Rect, it
ui.separator();
continue;
}
let r = ui.selectable_label(*on, label.as_str());
// The item is the English source: the drawn text is looked up in the catalog, while
// the automation label stays English for scripts and tests.
let r = ui.selectable_label(*on, crate::i18n::tr(label));
app.auto.add(&format!("viewer.{auto}.{i}"), r.rect, label);
if r.clicked() {
chosen = Some(i);
@@ -520,10 +522,10 @@ pub(crate) fn resolution_label(res: Resolution, scale: f64) -> String {
} else {
"Quarter"
};
format!("({r})")
format!("({})", crate::i18n::tr(r))
}
Resolution::Custom(n) => format!("Custom ({n})"),
r => r.label().to_string(),
Resolution::Custom(n) => format!("{} ({n})", crate::i18n::tr("Custom")),
r => crate::i18n::tr(r.label()).to_string(),
}
}
@@ -602,7 +604,9 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
let tog = |ui: &mut egui::Ui, auto: &mut crate::automation::Registry, x: &mut f32, icon: Icon, on: bool, id: &str, tip: &str| -> (bool, Rect) {
let r = Rect::from_min_size(pos2(*x, cy - 11.0), vec2(22.0, 22.0));
let resp = widgets::icon_button(ui, r, icon, on, &t, egui::Id::new(("vw-btn", id))).on_hover_text(tip);
// The tip is the English source: the tooltip is looked up in the catalog, while the
// automation label stays English for scripts and tests.
let resp = widgets::icon_button(ui, r, icon, on, &t, egui::Id::new(("vw-btn", id))).on_hover_text(crate::i18n::tr(tip));
auto.add(&format!("viewer.{id}"), r, tip);
*x += 24.0;
(resp.clicked(), r)
@@ -632,7 +636,7 @@ pub(crate) fn bottom_bar(app: &mut EffectcraftApp, ui: &mut egui::Ui, bar: Rect,
}
let v = &app.ui.viewer;
let items = vec![
("Title/Action Safe".to_string(), v.safe_margins),
("Title/action safe".to_string(), v.safe_margins),
("Proportional Grid".to_string(), v.proportional_grid),
("Grid".to_string(), v.grid),
("Guides".to_string(), v.guides),