diff --git a/crates/ui-egui/src/about.rs b/crates/ui-egui/src/about.rs index 611e7d64c..41eb43a7f 100644 --- a/crates/ui-egui/src/about.rs +++ b/crates/ui-egui/src/about.rs @@ -113,7 +113,12 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "DesignCraft")).font(semibold(26.0)).color(t.text_strong), )); - ui.label(egui::RichText::new(format!("Version {}", env!("CARGO_PKG_VERSION"))).size(12.0).color(t.text_dim)); + crate::rtl::label( + ui, + egui::RichText::new(format!("{} {}", crate::i18n::tr(&app.ui.language, "Version"), env!("CARGO_PKG_VERSION"))) + .size(12.0) + .color(t.text_dim), + ); ui.add_space(4.0); ui.label(crate::rtl::widget( ui, diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 85e53d592..dfb5e699b 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -22,7 +22,7 @@ pub fn app_bar(app: &mut DesignApp, ui: &mut egui::Ui) { let mut menus_end = full.min.x; let mut tools_start = full.max.x; ui.horizontal_centered(|ui| { - if icons::button(ui, "home", 24.0, app.session.active().is_none(), "Home").clicked() { + if icons::button(ui, "home", 24.0, app.session.active().is_none(), crate::i18n::tr(&app.ui.language, "Home")).clicked() { app.session_home(); } ui.add_space(6.0); @@ -40,10 +40,18 @@ pub fn app_bar(app: &mut DesignApp, ui: &mut egui::Ui) { let (r, _) = ui.allocate_exact_size(vec2(125.0, 18.0), Sense::click()); ui.painter().rect(r, 1.0, t.input, Stroke::new(1.0, t.field_border), egui::StrokeKind::Inside); icons::paint(ui.painter(), egui::Rect::from_min_size(r.min + vec2(3.0, 2.0), vec2(14.0, 14.0)), "search", t.icon); - ui.painter().text(r.min + vec2(20.0, 9.0), egui::Align2::LEFT_CENTER, "Search", egui::FontId::proportional(11.0), t.text_dim); + crate::rtl::paint( + ui.painter(), + r.min + vec2(20.0, 9.0), + egui::Align2::LEFT_CENTER, + crate::i18n::tr(&app.ui.language, "Search"), + egui::FontId::proportional(11.0), + t.text_dim, + ); ui.add_space(8.0); let current = app.ui.workspace.clone(); - ui.menu_button(egui::RichText::new(format!("{current} ▾")).font(semibold(11.5)).color(t.text), |ui| { + let shown = crate::i18n::workspace_name(&app.ui.language, ¤t); + ui.menu_button(crate::rtl::widget(ui, egui::RichText::new(format!("{shown} ▾")).font(semibold(11.5)).color(t.text)), |ui| { let customs: Vec = app.ui.custom_workspaces.iter().map(|w| w.name.clone()).collect(); for w in ["Essentials", "Advanced", "Book", "Digital Publishing", "Interactive for PDF", "Printing and Proofing", "Typography"] @@ -51,13 +59,20 @@ pub fn app_bar(app: &mut DesignApp, ui: &mut egui::Ui) { .map(str::to_string) .chain(customs.iter().cloned()) { - if ui.selectable_label(w == current, &w).clicked() { + if ui.selectable_label(w == current, crate::rtl::widget(ui, crate::i18n::workspace_name(&app.ui.language, &w))).clicked() + { let _ = app.run("window.workspace", json!({"name": w})); ui.close(); } } ui.separator(); - if ui.button(format!("Reset {current}")).clicked() { + if ui + .button(crate::rtl::widget( + ui, + format!("{} {}", crate::i18n::tr(&app.ui.language, "Reset"), crate::i18n::workspace_name(&app.ui.language, ¤t)), + )) + .clicked() + { let _ = app.run("window.resetWorkspace", json!({})); ui.close(); } @@ -77,7 +92,7 @@ pub fn app_bar(app: &mut DesignApp, ui: &mut egui::Ui) { } }); ui.add_space(6.0); - if icons::button(ui, "share", 22.0, false, "Share").clicked() { + if icons::button(ui, "share", 22.0, false, crate::i18n::tr(&app.ui.language, "Share")).clicked() { app.status("Export a PDF, IDML or package to share — no cloud account needed."); } ui.add_space(8.0); @@ -143,7 +158,9 @@ pub fn control_bar(app: &mut DesignApp, ui: &mut egui::Ui) { fn icon_caption(ui: &mut egui::Ui, icon: &str, tip: &str) { let (r, resp) = ui.allocate_exact_size(vec2(16.0, 16.0), Sense::hover()); icons::paint(ui.painter(), r, icon, Tokens::get(ui.ctx()).icon); - resp.on_hover_text(tip); + resp.on_hover_ui(|ui| { + crate::rtl::label(ui, tip); + }); } fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { @@ -159,20 +176,20 @@ fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { None => (None, None, None, None), }; egui::Grid::new("ctl_xywh").num_columns(4).spacing(vec2(4.0, 4.0)).show(ui, |ui| { - caption(ui, "X:"); + caption(ui, crate::i18n::tr(&app.ui.language, "X:")); if let Some(v) = measure(ui, "cx", x, units, 64.0) { let _ = app.run("transform.set", json!({"x": v})); } - caption(ui, "W:"); + caption(ui, crate::i18n::tr(&app.ui.language, "W:")); if let Some(v) = measure(ui, "cw", w, units, 64.0) { let _ = app.run("transform.set", json!({"width": v})); } ui.end_row(); - caption(ui, "Y:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Y:")); if let Some(v) = measure(ui, "cy", y, units, 64.0) { let _ = app.run("transform.set", json!({"y": v})); } - caption(ui, "H:"); + caption(ui, crate::i18n::tr(&app.ui.language, "H:")); if let Some(v) = measure(ui, "ch", h, units, 64.0) { let _ = app.run("transform.set", json!({"height": v})); } @@ -183,20 +200,20 @@ fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { let tv = if info.is_some() { app.session.execute("transform.info", &json!({})).ok() } else { None }; let tv_get = |k: &str| tv.as_ref().and_then(|v| v[k].as_f64()); egui::Grid::new("ctl_srs").num_columns(4).spacing(vec2(4.0, 4.0)).show(ui, |ui| { - icon_caption(ui, "tool-scale", "Scale X Percentage"); + icon_caption(ui, "tool-scale", crate::i18n::tr(&app.ui.language, "Scale X Percentage")); if let Some(v) = number(ui, "csx", tv_get("scaleX"), "%", 56.0, 1) { let _ = app.run("transform.set", json!({"scaleX": v})); } - icon_caption(ui, "tool-rotate", "Rotation Angle"); + icon_caption(ui, "tool-rotate", crate::i18n::tr(&app.ui.language, "Rotation Angle")); if let Some(v) = number(ui, "crot", tv_get("rotation"), "°", 50.0, 1) { let _ = app.run("transform.set", json!({"rotation": v})); } ui.end_row(); - icon_caption(ui, "tool-scale", "Scale Y Percentage"); + icon_caption(ui, "tool-scale", crate::i18n::tr(&app.ui.language, "Scale Y Percentage")); if let Some(v) = number(ui, "csy", tv_get("scaleY"), "%", 56.0, 1) { let _ = app.run("transform.set", json!({"scaleY": v})); } - icon_caption(ui, "tool-shear", "Shear X Angle"); + icon_caption(ui, "tool-shear", crate::i18n::tr(&app.ui.language, "Shear X Angle")); if let Some(v) = number(ui, "cshr", tv_get("shear"), "°", 50.0, 1) { let _ = app.run("transform.set", json!({"shear": v})); } @@ -206,7 +223,7 @@ fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { // Rotate / flip. ui.vertical(|ui| { ui.horizontal(|ui| { - if icons::button(ui, "tool-rotate", 20.0, false, "Rotate 90° Counterclockwise").clicked() { + if icons::button(ui, "tool-rotate", 20.0, false, crate::i18n::tr(&app.ui.language, "Rotate 90° Counterclockwise")).clicked() { let _ = app.run("transform.rotate", json!({"angle": 90})); } if ui @@ -244,13 +261,13 @@ fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { // Fill / stroke. ui.vertical(|ui| { ui.horizontal(|ui| { - caption(ui, "Fill"); + caption(ui, crate::i18n::tr(&app.ui.language, "Fill")); panels::swatch_picker(app, ui, "ctlfill", info.as_ref().map(|i| i.fill.clone()), |app, name| { let _ = app.run("object.fill", json!({"swatch": name})); }); }); ui.horizontal(|ui| { - caption(ui, "Stroke"); + caption(ui, crate::i18n::tr(&app.ui.language, "Stroke")); panels::swatch_picker(app, ui, "ctlstroke", info.as_ref().map(|i| i.stroke.clone()), |app, name| { let _ = app.run("object.stroke", json!({"swatch": name})); }); @@ -263,13 +280,15 @@ fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { // Opacity, fx. ui.vertical(|ui| { ui.horizontal(|ui| { - caption(ui, "Opacity"); + caption(ui, crate::i18n::tr(&app.ui.language, "Opacity")); if let Some(v) = number(ui, "cop", info.as_ref().map(|i| i.opacity * 100.0), "%", 42.0, 0) { let _ = app.run("object.opacity", json!({"opacity": v / 100.0})); } }); ui.horizontal(|ui| { - if icons::button(ui, "panel-effects", 20.0, info.as_ref().is_some_and(|i| i.shadow), "Drop Shadow").clicked() { + if icons::button(ui, "panel-effects", 20.0, info.as_ref().is_some_and(|i| i.shadow), crate::i18n::tr(&app.ui.language, "Drop Shadow")) + .clicked() + { let _ = app.run("object.dropShadow", json!({})); } }); @@ -325,7 +344,7 @@ fn control_object(app: &mut DesignApp, ui: &mut egui::Ui) { { vsep(ui); ui.vertical(|ui| { - caption(ui, "Columns"); + caption(ui, crate::i18n::tr(&app.ui.language, "Columns")); if let Some(v) = number(ui, "ccols", Some(cols as f64), "", 36.0, 0) { let _ = app.run("object.textFrameOptions", json!({"columns": v.max(1.0) as u64})); } @@ -359,7 +378,7 @@ fn control_text(app: &mut DesignApp, ui: &mut egui::Ui) { if let Some(v) = number(ui, "csize", chars["size"].as_f64(), " pt", 54.0, 2) { let _ = app.run("type.char", json!({"attrs": {"size": v}})); } - caption(ui, "VA"); + caption(ui, crate::i18n::tr(&app.ui.language, "VA")); if let Some(v) = number(ui, "ctrack", chars["tracking"].as_f64(), "", 48.0, 0) { let _ = app.run("type.char", json!({"attrs": {"tracking": v}})); } @@ -372,7 +391,7 @@ fn control_text(app: &mut DesignApp, ui: &mut egui::Ui) { if let Some(v) = number(ui, "clead", lead, " pt", 54.0, 2) { let _ = app.run("type.char", json!({"attrs": {"leading": {"kind": "points", "value": v}}})); } - caption(ui, "IT"); + caption(ui, crate::i18n::tr(&app.ui.language, "IT")); if let Some(v) = number(ui, "chs", chars["hScale"].as_f64().map(|v| v * 100.0), "%", 48.0, 0) { let _ = app.run("type.char", json!({"attrs": {"hScale": v / 100.0}})); } @@ -426,7 +445,7 @@ fn control_text(app: &mut DesignApp, ui: &mut egui::Ui) { panels::para_style_picker(app, ui, &ps, ov > 0, 170.0); let hy = para["hyphenate"].as_bool().unwrap_or(true); let mut h = hy; - if ui.checkbox(&mut h, "Hyphenate").changed() { + if ui.checkbox(&mut h, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Hyphenate"))).changed() { let _ = app.run("type.para", json!({"attrs": {"hyphenate": h}})); } }); @@ -503,10 +522,10 @@ pub fn status_bar(app: &mut DesignApp, ui: &mut egui::Ui) { ui.add_space(8.0); let n = app.session.active().map(|d| d.doc.page_count()).unwrap_or(1); let cur = crate::canvas::current_page(app).unwrap_or(0); - if icons::button(ui, "first", 14.0, false, "First Spread").clicked() { + if icons::button(ui, "first", 14.0, false, crate::i18n::tr(&app.ui.language, "First Spread")).clicked() { crate::canvas::go_to_page(app, 0); } - if icons::button(ui, "prev", 14.0, false, "Previous Spread").clicked() { + if icons::button(ui, "prev", 14.0, false, crate::i18n::tr(&app.ui.language, "Previous Spread")).clicked() { crate::canvas::go_to_page(app, cur.saturating_sub(1)); } let name = app.session.page_label(cur); @@ -525,10 +544,10 @@ pub fn status_bar(app: &mut DesignApp, ui: &mut egui::Ui) { } }); }); - if icons::button(ui, "next", 14.0, false, "Next Spread").clicked() { + if icons::button(ui, "next", 14.0, false, crate::i18n::tr(&app.ui.language, "Next Spread")).clicked() { crate::canvas::go_to_page(app, (cur + 1).min(n - 1)); } - if icons::button(ui, "last", 14.0, false, "Last Spread").clicked() { + if icons::button(ui, "last", 14.0, false, crate::i18n::tr(&app.ui.language, "Last Spread")).clicked() { crate::canvas::go_to_page(app, n - 1); } ui.add_space(12.0); @@ -542,11 +561,12 @@ pub fn status_bar(app: &mut DesignApp, ui: &mut egui::Ui) { 4.0, if errors == 0 { Color32::from_rgb(60, 200, 90) } else { Color32::from_rgb(235, 50, 50) }, ); - ui.label( + crate::rtl::label( + ui, egui::RichText::new(if errors == 0 { - "No errors ▾".to_string() + format!("{} ▾", crate::i18n::tr(&app.ui.language, "No errors")) } else { - format!("{errors} error{} ▾", if errors == 1 { "" } else { "s" }) + format!("{errors} {} ▾", crate::i18n::tr(&app.ui.language, if errors == 1 { "error" } else { "errors" })) }) .font(small.clone()), ); diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index d79499d5d..bfa83d4a9 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -107,7 +107,7 @@ fn text_field(ui: &mut egui::Ui, d: &mut Dialog, key: &str, w: f32) { /// new keys (Esc cancels, Backspace clears). /// Edit › Spelling › User Dictionary: added words and hyphenation exceptions (applied at once). fn user_dictionary(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { - ui.label(egui::RichText::new("Hyphenation Exceptions").font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Hyphenation Exceptions")).font(semibold(12.0))); ui.label(crate::rtl::widget( ui, egui::RichText::new(crate::i18n::tr( @@ -136,7 +136,7 @@ fn user_dictionary(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { let w = w.as_str().unwrap_or("").to_string(); ui.horizontal(|ui| { ui.label(&w); - if ui.small_button("Remove").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Remove"))).clicked() { let _ = app.run("hyphenation.removeException", json!({"word": w})); } }); @@ -146,7 +146,11 @@ fn user_dictionary(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { fn keyboard_shortcuts(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { let mut q = d.s("query"); - ui.add(egui::TextEdit::singleline(&mut q).hint_text("Search commands").desired_width(f32::INFINITY)); + ui.add( + egui::TextEdit::singleline(&mut q) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Search commands"))) + .desired_width(f32::INFINITY), + ); d.fields.insert("query".into(), json!(q)); let recording = d.s("recording"); // Capture the next key press for the command being recorded. @@ -188,7 +192,7 @@ fn keyboard_shortcuts(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { egui::ScrollArea::vertical().max_height(360.0).show(ui, |ui| { egui::Grid::new("shortcuts").num_columns(3).striped(true).spacing([12.0, 4.0]).show(ui, |ui| { for (id, label, menu) in &rows { - ui.label(label.trim_end_matches('…')) + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label.trim_end_matches('…'))) .on_hover_text(format!("{id}{}", if menu.is_empty() { String::new() } else { format!(" ({menu})") })); let cur = crate::menus::shortcut_of(app, id).map(|s| crate::menus::shortcut_text(&s)).unwrap_or_else(|| "—".into()); let text = if recording == *id { "Press keys…".to_string() } else { cur }; @@ -197,7 +201,7 @@ fn keyboard_shortcuts(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { d.fields.insert("message".into(), json!("")); } if app.ui.shortcuts.contains_key(id) { - if ui.small_button("Default").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Default"))).clicked() { let _ = app.run("window.setShortcut", json!({"id": id, "shortcut": null})); } } else { @@ -303,7 +307,7 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { .width(110.0) .show_ui(ui, |ui| { for (v, l) in [("print", "Print"), ("web", "Web"), ("mobile", "Mobile")] { - if ui.selectable_label(cur == v, l).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { d.fields.insert("intent".into(), json!(v)); } } @@ -319,22 +323,22 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Start Page #:")); text_field(ui, d, "startPage", 60.0); ui.end_row(); - ui.label("Binding:"); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Binding:")); let rtl = d.s("binding") == "rightToLeft"; - egui::ComboBox::from_id_salt("ds_binding").selected_text(if rtl { "Right to Left" } else { "Left to Right" }).width(110.0).show_ui( - ui, - |ui| { + egui::ComboBox::from_id_salt("ds_binding") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, if rtl { "Right to Left" } else { "Left to Right" }))) + .width(110.0) + .show_ui(ui, |ui| { for (v, l) in [("leftToRight", "Left to Right"), ("rightToLeft", "Right to Left")] { - if ui.selectable_label((v == "rightToLeft") == rtl, l).clicked() { + if ui.selectable_label((v == "rightToLeft") == rtl, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { d.fields.insert("binding".into(), json!(v)); } } - }, - ); + }); ui.end_row(); }); ui.add_space(8.0); - ui.label(egui::RichText::new("Page Size").font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Page Size")).font(semibold(12.0))); let (w, h) = (d.m("width").unwrap_or(612.0), d.m("height").unwrap_or(792.0)); let preset = designcraft_doc::build::PRESETS.iter().find(|p| (p.width - w).abs() < 0.5 && (p.height - h).abs() < 0.5).map_or("Custom", |p| p.name); @@ -351,7 +355,9 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Orientation:")); ui.horizontal(|ui| { for (portrait, l) in [(true, "Portrait"), (false, "Landscape")] { - if ui.selectable_label((h >= w) == portrait, l).clicked() && (h >= w) != portrait { + if ui.selectable_label((h >= w) == portrait, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() + && (h >= w) != portrait + { let (fw, fh) = (d.s("width"), d.s("height")); d.fields.insert("width".into(), json!(fh)); d.fields.insert("height".into(), json!(fw)); @@ -370,11 +376,11 @@ fn document_setup(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { egui::Grid::new("ds_bleed").num_columns(5).spacing([8.0, 6.0]).show(ui, |ui| { ui.label(""); for e in ["Top", "Bottom", "Inside", "Outside"] { - ui.label(e); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, e)); } ui.end_row(); for (k, l) in [("bleed", "Bleed:"), ("slug", "Slug:")] { - ui.label(l); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, l)); for e in ["Top", "Bottom", "Inside", "Outside"] { text_field(ui, d, &format!("{k}{e}"), 60.0); } @@ -431,7 +437,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.vertical(|ui| { ui.set_width(150.0); for (id, label) in sections { - if ui.selectable_label(cur == *id, *label).clicked() { + if ui.selectable_label(cur == *id, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { d.fields.insert("section".into(), json!(id)); } } @@ -457,7 +463,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { } } "spelling" => { - ui.label(egui::RichText::new("Dynamic Spelling").font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Dynamic Spelling")).font(semibold(12.0))); check(ui, d, "dynamicSpelling", crate::i18n::tr(&app.ui.language, "Enable Dynamic Spelling (underline misspelled words)")); } "autocorrect" => { @@ -542,7 +548,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Position")); ui.end_row(); for (k, l) in [("superscript", "Superscript:"), ("subscript", "Subscript:")] { - ui.label(l); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, l)); for f in ["Size", "Position"] { ui.horizontal(|ui| { text_field(ui, d, &format!("adv.{k}{f}"), 50.0); @@ -562,11 +568,17 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "On Screen:")); let rich = d.b("richBlack"); egui::ComboBox::from_id_salt("pref_black") - .selected_text(if rich { "Display All Blacks as Rich Black" } else { "Display All Blacks Accurately" }) + .selected_text(crate::rtl::widget( + ui, + crate::i18n::tr( + &app.ui.language, + if rich { "Display All Blacks as Rich Black" } else { "Display All Blacks Accurately" }, + ), + )) .width(240.0) .show_ui(ui, |ui| { for (v, l) in [(false, "Display All Blacks Accurately"), (true, "Display All Blacks as Rich Black")] { - if ui.selectable_label(v == rich, l).clicked() { + if ui.selectable_label(v == rich, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { d.fields.insert("richBlack".into(), json!(v)); } } @@ -576,11 +588,17 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Printing / Exporting:")); let rich = d.b("richBlackOutput"); egui::ComboBox::from_id_salt("pref_black_out") - .selected_text(if rich { "Output All Blacks as Rich Black" } else { "Output All Blacks Accurately" }) + .selected_text(crate::rtl::widget( + ui, + crate::i18n::tr( + &app.ui.language, + if rich { "Output All Blacks as Rich Black" } else { "Output All Blacks Accurately" }, + ), + )) .width(240.0) .show_ui(ui, |ui| { for (v, l) in [(false, "Output All Blacks Accurately"), (true, "Output All Blacks as Rich Black")] { - if ui.selectable_label(v == rich, l).clicked() { + if ui.selectable_label(v == rich, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { d.fields.insert("richBlackOutput".into(), json!(v)); } } @@ -596,7 +614,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { } } "interface" => { - ui.label(egui::RichText::new("UI Scaling").font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "UI Scaling")).font(semibold(12.0))); ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "UI Size:")); let mut v = d.n("uiScale").unwrap_or(100.0); @@ -616,16 +634,18 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Relative To:")); let cur = d.s("bg.relativeTo"); - egui::ComboBox::from_id_salt("bg_rel").selected_text(if cur == "topMargin" { "Top Margin" } else { "Top of Page" }).show_ui( - ui, - |ui| { + egui::ComboBox::from_id_salt("bg_rel") + .selected_text(crate::rtl::widget( + ui, + crate::i18n::tr(&app.ui.language, if cur == "topMargin" { "Top Margin" } else { "Top of Page" }), + )) + .show_ui(ui, |ui| { for (v, l) in [("topOfPage", "Top of Page"), ("topMargin", "Top Margin")] { - if ui.selectable_label(cur == v, l).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { d.fields.insert("bg.relativeTo".into(), json!(v)); } } - }, - ); + }); ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Increment Every:")); text_field(ui, d, "bg.increment", 80.0); @@ -656,10 +676,10 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { check(ui, d, "grid.inBack", crate::i18n::tr(&app.ui.language, "Grids in Back")); } "guides" => { - ui.label(egui::RichText::new("Color").font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Color")).font(semibold(12.0))); egui::Grid::new("pref_guides").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { for (k, l) in [("marginColor", "Margins:"), ("columnColor", "Columns:"), ("bleedColor", "Bleed:"), ("slugColor", "Slug:")] { - ui.label(l); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, l)); color_field(ui, d, k); ui.end_row(); } @@ -703,7 +723,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { }) .show_ui(ui, |ui| { for (v, l) in [("fast", "Fast"), ("typical", "Typical"), ("high", "High Quality")] { - if ui.selectable_label(cur == v, l).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { d.fields.insert("displayQuality".into(), json!(v)); } } @@ -714,15 +734,21 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Ruler Units")).font(semibold(12.0))); egui::Grid::new("pref_units").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { for (key, label) in [("horizontalUnits", "Horizontal:"), ("verticalUnits", "Vertical:")] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let cur: Unit = d.fields.get(key).and_then(|v| serde_json::from_value(v.clone()).ok()).unwrap_or_default(); - egui::ComboBox::from_id_salt(key).selected_text(cur.label()).width(140.0).show_ui(ui, |ui| { - for u in Unit::ALL { - if ui.selectable_label(u == cur, u.label()).clicked() { - d.fields.insert(key.into(), json!(u)); + egui::ComboBox::from_id_salt(key) + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, cur.label()))) + .width(140.0) + .show_ui(ui, |ui| { + for u in Unit::ALL { + if ui + .selectable_label(u == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, u.label()))) + .clicked() + { + d.fields.insert(key.into(), json!(u)); + } } - } - }); + }); ui.end_row(); } }); @@ -740,7 +766,7 @@ fn preferences(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "When Scaling")).font(semibold(12.0))); check(ui, d, "scaleStrokes", crate::i18n::tr(&app.ui.language, "Include Stroke Weight")); ui.add_space(6.0); - ui.label(egui::RichText::new("Transform").font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Transform")).font(semibold(12.0))); check(ui, d, "dimensionsIncludeStroke", crate::i18n::tr(&app.ui.language, "Dimensions Include Stroke Weight")); check(ui, d, "transformationsAreTotals", crate::i18n::tr(&app.ui.language, "Transformations are Totals")); ui.add_space(6.0); @@ -906,7 +932,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { "findChange" => { ui.horizontal(|ui| { for (label, grep) in [("Text", false), ("GREP", true)] { - if ui.selectable_label(!d.b("objectMode") && d.b("grep") == grep, label).clicked() { + if ui.selectable_label(!d.b("objectMode") && d.b("grep") == grep, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { d.fields.insert("grep".into(), json!(grep)); d.fields.insert("objectMode".into(), json!(false)); } @@ -920,10 +946,10 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { let swatches: Vec = app.session.active().map(|st| st.doc.swatches.iter().filter(|w| !w.hidden).map(|w| w.name.clone()).collect()).unwrap_or_default(); egui::Grid::new("fco").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { for (key, label, any) in [("objFill", "Find fill:", "(any)"), ("objChangeFill", "Change fill to:", "(unchanged)")] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let cur = d.s(key); egui::ComboBox::from_id_salt(key).selected_text(if cur.is_empty() { any } else { cur.as_str() }).width(180.0).show_ui(ui, |ui| { - if ui.selectable_label(cur.is_empty(), any).clicked() { + if ui.selectable_label(cur.is_empty(), crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, any))).clicked() { d.fields.insert(key.into(), json!("")); } for w in &swatches { @@ -938,7 +964,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { let cur = d.s("objKind"); egui::ComboBox::from_id_salt("objKind").selected_text(if cur.is_empty() { "(any)" } else { cur.as_str() }).show_ui(ui, |ui| { for v in ["", "text", "graphic", "shape", "line", "group"] { - if ui.selectable_label(cur == v, if v.is_empty() { "(any)" } else { v }).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, if v.is_empty() { "(any)" } else { v }))).clicked() { d.fields.insert("objKind".into(), json!(v)); } } @@ -975,9 +1001,9 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Search:")); let cur = d.s("scope"); - egui::ComboBox::from_id_salt("fcscope").selected_text(&cur).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("fcscope").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &cur))).show_ui(ui, |ui| { for v in ["document", "story", "selection"] { - if ui.selectable_label(cur == v, v).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, v))).clicked() { d.fields.insert("scope".into(), json!(v)); } } @@ -1030,7 +1056,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { "pdfImport" => { ui.label(egui::RichText::new(d.s("path")).size(11.0)); ui.horizontal(|ui| { - ui.label(format!("Page (1–{}):", d.n("pages").unwrap_or(1.0))); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Page (1–{count}):").replace("{count}", &d.n("pages").unwrap_or(1.0).to_string())); text_field(ui, &mut d, "page", 60.0); }); ui.horizontal(|ui| { @@ -1043,7 +1069,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { "media" => crate::i18n::tr(&app.ui.language, "Media"), _ => crate::i18n::tr(&app.ui.language, "Crop"), }; - egui::ComboBox::from_id_salt("pdf_crop").selected_text(label(&cur)).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("pdf_crop").selected_text(crate::rtl::widget(ui, label(&cur))).show_ui(ui, |ui| { for v in ["crop", "trim", "bleed", "art", "media"] { if ui.selectable_label(label(&cur) == label(v), label(v)).clicked() { d.fields.insert("crop".into(), json!(v)); @@ -1069,7 +1095,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { let profiles = d.fields.get("profiles").and_then(Value::as_array).cloned().unwrap_or_default(); egui::Grid::new("color_settings").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { for (key, label, kind) in [("rgb", "RGB:", "rgb"), ("cmyk", "CMYK:", "cmyk")] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let cur = d.s(key); egui::ComboBox::from_id_salt(("ws", key)).selected_text(&cur).width(260.0).show_ui(ui, |ui| { for pr in profiles.iter().filter(|p| p["kind"] == kind) { @@ -1084,10 +1110,10 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Intent:")); let cur = d.s("intent"); egui::ComboBox::from_id_salt("ws_intent") - .selected_text(designcraft_color::cms::Intent::parse(&cur).map_or("", |i| i.label())) + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, designcraft_color::cms::Intent::parse(&cur).map_or("", |i| i.label())))) .show_ui(ui, |ui| { for i in designcraft_color::cms::Intent::ALL { - if ui.selectable_label(i.id() == cur, i.label()).clicked() { + if ui.selectable_label(i.id() == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, i.label()))).clicked() { d.fields.insert("intent".into(), json!(i.id())); } } @@ -1116,7 +1142,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { for (menu, label) in crate::menus::all_menu_items().into_iter().filter(|(m, l)| q.is_empty() || m.to_lowercase().contains(&q) || l.to_lowercase().contains(&q)) { let key = crate::menus::menu_key(&menu, &label); let mut visible = !app.ui.hidden_menu_items.contains(&key); - if ui.checkbox(&mut visible, format!("{menu} › {label}")).changed() { + if ui.checkbox(&mut visible, crate::rtl::widget(ui, format!("{} › {}", crate::i18n::tr(&app.ui.language, &menu), crate::i18n::tr(&app.ui.language, &label)))).changed() { let _ = app.run("window.hideMenuItem", json!({"item": key, "hidden": !visible})); } } @@ -1128,7 +1154,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { if !d.b("removeStyles") { crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Style Name Conflicts")).font(semibold(12.0))); let conflicts: Vec = d.fields.get("conflicts").and_then(Value::as_array).map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_string)).collect()).unwrap_or_default(); - ui.label(if conflicts.is_empty() { "No conflicts".to_string() } else { format!("{} conflict(s): {}", conflicts.len(), conflicts.join(", ")) }); + ui.label(if conflicts.is_empty() { crate::i18n::tr(&app.ui.language, "No conflicts").to_string() } else { format!("{} conflict(s): {}", conflicts.len(), conflicts.join(", ")) }); let cur = d.s("styleConflicts"); ui.horizontal(|ui| { for (v, l) in [("useExisting", "Use Document Style Definition"), ("redefine", "Redefine Document Style"), ("autoRename", "Auto Rename")] { @@ -1146,7 +1172,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { ui.label(w); let mapped = d.fields.get("map").and_then(|m| m.get(w)).and_then(Value::as_str).unwrap_or("").to_string(); let shown = if mapped.is_empty() { "(import)".to_string() } else { mapped.clone() }; - egui::ComboBox::from_id_salt(("imp_map", w)).selected_text(shown).width(170.0).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt(("imp_map", w)).selected_text(crate::rtl::widget(ui, &shown)).width(170.0).show_ui(ui, |ui| { if ui.selectable_label(mapped.is_empty(),crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "(import)"))).clicked() && let Some(m) = d.fields.get_mut("map").and_then(Value::as_object_mut) { @@ -1187,9 +1213,9 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { }; ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Fitting:")); - egui::ComboBox::from_id_salt("ff_fit").selected_text(label(&cur)).width(200.0).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("ff_fit").selected_text(crate::rtl::widget(ui, label(&cur))).width(200.0).show_ui(ui, |ui| { for v in ["none", "fillProportionally", "fitProportionally", "fitContentToFrame", "centerContent"] { - if ui.selectable_label(cur == v, label(v)).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, label(v))).clicked() { d.fields.insert("fitting".into(), json!(v)); } } @@ -1220,12 +1246,12 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Crop Amount")).font(semibold(12.0))); egui::Grid::new("ff_crop").num_columns(4).spacing([8.0, 6.0]).show(ui, |ui| { for (k, l) in [("cropTop", "Top:"), ("cropLeft", "Left:")] { - ui.label(l); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, l)); text_field(ui, &mut d, k, 70.0); } ui.end_row(); for (k, l) in [("cropBottom", "Bottom:"), ("cropRight", "Right:")] { - ui.label(l); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, l)); text_field(ui, &mut d, k, 70.0); } ui.end_row(); @@ -1242,9 +1268,9 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { "vcard" => crate::i18n::tr(&app.ui.language, "Business Card"), _ => crate::i18n::tr(&app.ui.language, "Web Hyperlink"), }; - egui::ComboBox::from_id_salt("qr_type").selected_text(label(&cur)).width(160.0).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("qr_type").selected_text(crate::rtl::widget(ui, label(&cur))).width(160.0).show_ui(ui, |ui| { for v in ["url", "text", "sms", "email", "vcard"] { - if ui.selectable_label(cur == v, label(v)).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, label(v))).clicked() { d.fields.insert("type".into(), json!(v)); } } @@ -1259,7 +1285,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { }; egui::Grid::new("qr_fields").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { for (k, l) in fields { - ui.label(*l); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, l)); text_field(ui, &mut d, k, 260.0); ui.end_row(); } @@ -1290,10 +1316,10 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { } "ruby" => { ui.horizontal(|ui| { - ui.label("Ruby:"); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Ruby:")); text_field(ui, &mut d, "text", 200.0); }); - ui.label(egui::RichText::new("Set over the selected text; empty removes it.").size(11.0)); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Set over the selected text; empty removes it.")).size(11.0)); } "insertTable" => { crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Table Dimensions")).font(semibold(12.0))); @@ -1323,9 +1349,9 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Vertical justification")); let cur = d.s("verticalJustification"); - egui::ComboBox::from_id_salt("vj").selected_text(&cur).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("vj").selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &cur))).show_ui(ui, |ui| { for v in ["top", "center", "bottom", "justify"] { - if ui.selectable_label(cur == v, v).clicked() { + if ui.selectable_label(cur == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, v))).clicked() { d.fields.insert("verticalJustification".into(), json!(v)); } } @@ -1710,7 +1736,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo ("color", "Character Color"), ("export", "Export Tagging"), ] { - if ui.selectable_label(d.s("section") == id, label).clicked() { + if ui.selectable_label(d.s("section") == id, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { d.fields.insert("section".into(), json!(id)); } } @@ -1728,13 +1754,16 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Tag:")); let cur = d.s("x.tag"); - egui::ComboBox::from_id_salt("export_tag").selected_text(&cur).show_ui(ui, |ui| { - for t in ["[Automatic]", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre", "li", "figcaption", "aside", "div"] { - if ui.selectable_label(cur == t, t).clicked() { - d.fields.insert("x.tag".into(), json!(t)); + egui::ComboBox::from_id_salt("export_tag") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &cur))) + .show_ui(ui, |ui| { + for t in ["[Automatic]", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre", "li", "figcaption", "aside", "div"] + { + if ui.selectable_label(cur == t, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, t))).clicked() { + d.fields.insert("x.tag".into(), json!(t)); + } } - } - }); + }); }); ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Class:")); @@ -1749,42 +1778,51 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo for (i, ns) in list.iter_mut().enumerate() { ui.horizontal(|ui| { let style = ns["style"].as_str().unwrap_or("[None]").to_string(); - egui::ComboBox::from_id_salt(("ns_style", i)).selected_text(&style).width(110.0).show_ui(ui, |ui| { - for c in &cnames { - if ui.selectable_label(*c == style, c).clicked() { - ns["style"] = json!(c); - changed = true; + egui::ComboBox::from_id_salt(("ns_style", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &style))) + .width(110.0) + .show_ui(ui, |ui| { + for c in &cnames { + if ui + .selectable_label(*c == style, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, c))) + .clicked() + { + ns["style"] = json!(c); + changed = true; + } } - } - }); + }); let through = ns["through"].as_bool().unwrap_or(true); - egui::ComboBox::from_id_salt(("ns_thr", i)).selected_text(if through { "through" } else { "up to" }).width(70.0).show_ui( - ui, - |ui| { + egui::ComboBox::from_id_salt(("ns_thr", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, if through { "through" } else { "up to" }))) + .width(70.0) + .show_ui(ui, |ui| { for (v, l) in [(true, "through"), (false, "up to")] { - if ui.selectable_label(v == through, l).clicked() { + if ui.selectable_label(v == through, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { ns["through"] = json!(v); changed = true; } } - }, - ); + }); let mut n = ns["count"].as_u64().unwrap_or(1) as u32; if ui.add(egui::DragValue::new(&mut n).range(1..=999)).changed() { ns["count"] = json!(n); changed = true; } let kind = ns["until"]["kind"].as_str().unwrap_or("words").to_string(); - egui::ComboBox::from_id_salt(("ns_until", i)).selected_text(&kind).width(100.0).show_ui(ui, |ui| { - for k in - ["sentences", "words", "characters", "letters", "digits", "tab", "forcedLineBreak", "emSpace", "enSpace", "chars"] - { - if ui.selectable_label(k == kind, k).clicked() { - ns["until"] = if k == "chars" { json!({"kind": "chars", "chars": ":"}) } else { json!({"kind": k}) }; - changed = true; + egui::ComboBox::from_id_salt(("ns_until", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &kind))) + .width(100.0) + .show_ui(ui, |ui| { + for k in + ["sentences", "words", "characters", "letters", "digits", "tab", "forcedLineBreak", "emSpace", "enSpace", "chars"] + { + if ui.selectable_label(k == kind, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, k))).clicked() { + ns["until"] = if k == "chars" { json!({"kind": "chars", "chars": ":"}) } else { json!({"kind": k}) }; + changed = true; + } } - } - }); + }); if kind == "chars" { let mut c = ns["until"]["chars"].as_str().unwrap_or("").to_string(); if ui.add(egui::TextEdit::singleline(&mut c).desired_width(40.0)).changed() { @@ -1822,21 +1860,27 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo for (i, l) in lines.iter_mut().enumerate() { ui.horizontal(|ui| { let style = l["style"].as_str().unwrap_or("").to_string(); - egui::ComboBox::from_id_salt(("nls_style", i)).selected_text(&style).width(130.0).show_ui(ui, |ui| { - for c in &cnames { - if ui.selectable_label(*c == style, c).clicked() { - l["style"] = json!(c); - changed = true; + egui::ComboBox::from_id_salt(("nls_style", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &style))) + .width(130.0) + .show_ui(ui, |ui| { + for c in &cnames { + if ui + .selectable_label(*c == style, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, c))) + .clicked() + { + l["style"] = json!(c); + changed = true; + } } - } - }); + }); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "for")); let mut n = l["lines"].as_u64().unwrap_or(1) as u32; if ui.add(egui::DragValue::new(&mut n).range(1..=999)).changed() { l["lines"] = json!(n); changed = true; } - ui.label(if n == 1 { "line" } else { "lines" }); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, if n == 1 { "line" } else { "lines" })); if ui .small_button("×") .on_hover_ui(|ui| { @@ -1869,14 +1913,20 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Apply Style:")); let style = g["style"].as_str().unwrap_or("").to_string(); - egui::ComboBox::from_id_salt(("gs_style", i)).selected_text(&style).width(110.0).show_ui(ui, |ui| { - for c in &cnames { - if ui.selectable_label(*c == style, c).clicked() { - g["style"] = json!(c); - changed = true; + egui::ComboBox::from_id_salt(("gs_style", i)) + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &style))) + .width(110.0) + .show_ui(ui, |ui| { + for c in &cnames { + if ui + .selectable_label(*c == style, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, c))) + .clicked() + { + g["style"] = json!(c); + changed = true; + } } - } - }); + }); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "To Text:")); let mut pat = g["pattern"].as_str().unwrap_or("").to_string(); let bad = regex_ok(&pat).is_err(); @@ -1935,7 +1985,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo }); ui.end_row(); for (label, key, suffix) in [("Size:", "size", " pt"), ("Tracking:", "tracking", "")] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let v = cur(d, &format!("c.{key}"), &cv[key]).as_f64(); if let Some(n) = crate::widgets::number(ui, &format!("ps{key}"), v, suffix, 80.0, 2) { d.fields.insert(format!("c.{key}"), json!(n)); @@ -1957,13 +2007,16 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo egui::Grid::new("psi").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Alignment:")); let a: designcraft_doc::Align = serde_json::from_value(cur(d, "p.align", &pv["align"])).unwrap_or_default(); - egui::ComboBox::from_id_salt("psalign").selected_text(a.label()).width(240.0).show_ui(ui, |ui| { - for al in designcraft_doc::Align::ALL { - if ui.selectable_label(al == a, al.label()).clicked() { - d.fields.insert("p.align".into(), serde_json::to_value(al).unwrap_or_default()); + egui::ComboBox::from_id_salt("psalign") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, a.label()))) + .width(240.0) + .show_ui(ui, |ui| { + for al in designcraft_doc::Align::ALL { + if ui.selectable_label(al == a, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, al.label()))).clicked() { + d.fields.insert("p.align".into(), serde_json::to_value(al).unwrap_or_default()); + } } - } - }); + }); ui.end_row(); for (label, key) in [ ("Left Indent:", "leftIndent"), @@ -1972,7 +2025,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo ("Space Before:", "spaceBefore"), ("Space After:", "spaceAfter"), ] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let v = cur(d, &format!("p.{key}"), &pv[key]).as_f64(); if let Some(n) = crate::widgets::measure(ui, &format!("ps{key}"), v, units, 80.0) { d.fields.insert(format!("p.{key}"), json!(n)); @@ -1993,7 +2046,7 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo ("Before Last:", "hyphBeforeLast"), ("Hyphen Limit:", "hyphLimit"), ] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); let v = cur(d, &format!("p.{key}"), &pv[key]).as_f64(); if let Some(n) = crate::widgets::number(ui, &format!("ps{key}"), v, "", 60.0, 0) { d.fields.insert(format!("p.{key}"), json!(n.max(0.0) as u64)); @@ -2010,11 +2063,11 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Maximum")); ui.end_row(); for (label, base) in [("Word Spacing:", "wordSpace"), ("Letter Spacing:", "letterSpace"), ("Glyph Scaling:", "glyphScale")] { - crate::rtl::label(ui, label); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, label)); for suffix in ["Min", "Desired", "Max"] { let key = format!("{base}{suffix}"); let v = cur(d, &format!("p.{key}"), &pv[key.as_str()]).as_f64().map(|x| x * 100.0); - if let Some(n) = crate::widgets::number(ui, &format!("ps{key}"), v, "%", 60.0, 0) { + if let Some(n) = ui.scope(|ui| crate::widgets::number(ui, &format!("ps{key}"), v, "%", 60.0, 0)).inner { d.fields.insert(format!("p.{key}"), json!(n / 100.0)); } } @@ -2029,9 +2082,9 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo d.fields.insert("p.composer".into(), json!("singleLine")); } ui.end_row(); - ui.label("Insert Kashidas:"); + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Insert Kashidas:")); let mut k = cur(d, "p.kashidas", &pv["kashidas"]).as_bool().unwrap_or(true); - if ui.checkbox(&mut k, "In justified Arabic text").changed() { + if ui.checkbox(&mut k, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "In justified Arabic text"))).changed() { d.fields.insert("p.kashidas".into(), json!(k)); } ui.end_row(); @@ -2072,19 +2125,29 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo .map(str::to_string) .or(style.based_on.clone()) .unwrap_or_else(|| "[No Paragraph Style]".into()); - egui::ComboBox::from_id_salt("psbased").selected_text(&based).width(220.0).show_ui(ui, |ui| { - for n in &names { - if ui.selectable_label(*n == based, n).clicked() { - d.fields.insert("basedOn".into(), json!(n)); + egui::ComboBox::from_id_salt("psbased") + .selected_text(crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, &based))) + .width(220.0) + .show_ui(ui, |ui| { + for n in &names { + if ui.selectable_label(*n == based, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, n))).clicked() { + d.fields.insert("basedOn".into(), json!(n)); + } } - } - }); + }); ui.end_row(); }); ui.add_space(8.0); - ui.label( - egui::RichText::new(format!("{} {} {:.1} pt · {}", cp.font_family, cp.font_style, cp.size, pp.align.label())) - .color(crate::theme::Tokens::get(ui.ctx()).text_dim), + crate::rtl::label( + ui, + egui::RichText::new(format!( + "{} {} {:.1} pt · {}", + cp.font_family, + cp.font_style, + cp.size, + crate::i18n::tr(&app.ui.language, pp.align.label()) + )) + .color(crate::theme::Tokens::get(ui.ctx()).text_dim), ); } }); @@ -2094,15 +2157,21 @@ fn paragraph_style_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialo fn combo(ui: &mut egui::Ui, d: &mut Dialog, key: &str, opts: &[(&str, &str)]) { let cur = d.s(key); let shown = opts.iter().find(|o| o.0 == cur).map_or(cur.as_str(), |o| o.1).to_string(); - egui::ComboBox::from_id_salt(key).selected_text(shown).width(170.0).show_ui(ui, |ui| { + egui::ComboBox::from_id_salt(key).selected_text(crate::rtl::widget(ui, &shown)).width(170.0).show_ui(ui, |ui| { for (v, label) in opts { - if ui.selectable_label(cur == *v, *label).clicked() { + if ui.selectable_label(cur == *v, crate::rtl::widget(ui, *label)).clicked() { d.fields.insert(key.into(), json!(v)); } } }); } +/// Translate enum captions without changing serialized values or user-defined names. +fn translated_combo(lang: &str, ui: &mut egui::Ui, d: &mut Dialog, key: &str, opts: &[(&str, &str)]) { + let opts: Vec<_> = opts.iter().map(|(value, label)| (*value, crate::i18n::tr(lang, label))).collect(); + combo(ui, d, key, &opts); +} + /// Style name picker (paragraph or character styles of the active document). fn style_combo(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, key: &str, character: bool) { let names: Vec = app @@ -2116,9 +2185,9 @@ fn style_combo(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, key: &str, ch } }) .unwrap_or_default(); - let mut opts: Vec<(&str, &str)> = names.iter().map(|n| (n.as_str(), n.as_str())).collect(); + let mut opts: Vec<(&str, &str)> = names.iter().map(|n| (n.as_str(), crate::i18n::style_name(&app.ui.language, n))).collect(); if character && !names.iter().any(|n| n == designcraft_doc::NO_CHAR_STYLE) { - opts.insert(0, (designcraft_doc::NO_CHAR_STYLE, designcraft_doc::NO_CHAR_STYLE)); + opts.insert(0, (designcraft_doc::NO_CHAR_STYLE, crate::i18n::style_name(&app.ui.language, designcraft_doc::NO_CHAR_STYLE))); } combo(ui, d, key, &opts); } @@ -2127,7 +2196,7 @@ fn style_combo(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog, key: &str, ch fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.horizontal(|ui| { for (tab, label) in [("numbering", "Numbering and Formatting"), ("layout", "Layout")] { - if ui.selectable_label(d.s("tab") == tab, label).clicked() { + if ui.selectable_label(d.s("tab") == tab, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { d.fields.insert("tab".into(), json!(tab)); } } @@ -2135,7 +2204,7 @@ fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { ui.separator(); let head = |ui: &mut egui::Ui, t: &str| { ui.add_space(4.0); - ui.label(egui::RichText::new(t).font(semibold(12.0))); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, t)).font(semibold(12.0))); }; if d.s("tab") == "layout" { head(ui, "Spacing Options"); @@ -2150,7 +2219,8 @@ fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { head(ui, "First Baseline"); egui::Grid::new("fn_fb").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Offset:")); - combo( + translated_combo( + &app.ui.language, ui, d, "firstBaseline", @@ -2186,7 +2256,8 @@ fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { head(ui, "Numbering"); egui::Grid::new("fn_num").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Style:")); - combo( + translated_combo( + &app.ui.language, ui, d, "style", @@ -2205,10 +2276,22 @@ fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { text_field(ui, d, "startAt", 60.0); ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Restart Numbering Every:")); - combo(ui, d, "restart", &[("never", "Never (continuous)"), ("page", "Page"), ("spread", "Spread"), ("section", "Section")]); + translated_combo( + &app.ui.language, + ui, + d, + "restart", + &[("never", "Never (continuous)"), ("page", "Page"), ("spread", "Spread"), ("section", "Section")], + ); ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Show Prefix/Suffix in:")); - combo(ui, d, "affixIn", &[("none", "None"), ("reference", "Footnote Reference"), ("text", "Footnote Text"), ("both", "Both")]); + translated_combo( + &app.ui.language, + ui, + d, + "affixIn", + &[("none", "None"), ("reference", "Footnote Reference"), ("text", "Footnote Text"), ("both", "Both")], + ); ui.end_row(); crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Prefix:")); text_field(ui, d, "prefix", 60.0); @@ -2220,7 +2303,8 @@ fn footnote_options(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { head(ui, "Footnote Reference Number in Text"); egui::Grid::new("fn_ref").num_columns(2).spacing([8.0, 6.0]).show(ui, |ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Position:")); - combo( + translated_combo( + &app.ui.language, ui, d, "refPosition", @@ -2345,7 +2429,8 @@ fn command_form(app: &crate::DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { check(ui, d, &f.key, ""); } else if !f.choices.is_empty() { let mut opts: Vec<(&str, &str)> = vec![("", "—")]; - opts.extend(f.choices.iter().map(|c| (c.as_str(), c.as_str()))); + let labels: Vec<_> = f.choices.iter().map(|c| humanize(c)).collect(); + opts.extend(f.choices.iter().zip(&labels).map(|(value, label)| (value.as_str(), crate::i18n::tr(&app.ui.language, label)))); combo(ui, d, &f.key, &opts); } else { let mut s = d.s(&f.key); @@ -2499,7 +2584,12 @@ fn find_font(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { } let fonts = d.fields.get("_fonts").and_then(Value::as_array).cloned().unwrap_or_default(); let missing = fonts.iter().filter(|f| f["missing"] == true || f["styleMissing"] == true).count(); - ui.label(format!("Fonts in Document: {} Missing: {missing}", fonts.len())); + crate::rtl::label( + ui, + crate::i18n::tr(&app.ui.language, "Fonts in Document: {count} Missing: {missing}") + .replace("{count}", &fonts.len().to_string()) + .replace("{missing}", &missing.to_string()), + ); egui::ScrollArea::vertical().id_salt("ff_list").max_height(180.0).show(ui, |ui| { for f in &fonts { let (fam, st) = (f["family"].as_str().unwrap_or(""), f["style"].as_str().unwrap_or("")); diff --git a/crates/ui-egui/src/dock.rs b/crates/ui-egui/src/dock.rs index de44f15cd..9b4355708 100644 --- a/crates/ui-egui/src/dock.rs +++ b/crates/ui-egui/src/dock.rs @@ -132,9 +132,11 @@ pub fn flyout(app: &mut DesignApp, ctx: &egui::Context) { egui::Area::new(egui::Id::new("panel_flyout")).order(egui::Order::Foreground).fixed_pos(pos).show(ctx, |ui| { egui::Frame::popup(ui.style()).fill(t.panel).inner_margin(egui::Margin::same(0)).show(ui, |ui| { ui.set_width(256.0); + // A previous shorter flyout must not constrain the next panel's scroll viewport. + ui.set_max_height((screen.max.y - pos.y - 12.0).max(80.0)); let (strip, _) = ui.allocate_exact_size(vec2(256.0, 26.0), Sense::hover()); ui.painter().rect_filled(strip, 0.0, t.panel_darker); - ui.painter().text(strip.min + vec2(10.0, 13.0), egui::Align2::LEFT_CENTER, label, semibold(12.0), t.text_strong); + crate::rtl::paint(ui.painter(), strip.min + vec2(10.0, 13.0), egui::Align2::LEFT_CENTER, &label, semibold(12.0), t.text_strong); let close = egui::Rect::from_min_size(egui::pos2(strip.max.x - 22.0, strip.min.y + 4.0), vec2(18.0, 18.0)); if ui.interact(close, ui.id().with("flyclose"), Sense::click()).clicked() { open = false; diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 00de3d7ec..0d755865c 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -204,7 +204,7 @@ const TABLE: &[(&str, [&str; 5])] = &[ ("Color Settings", ["Color Settings", "Color Settings", "Color Settings", "Color Settings", "颜色设置"]), ("Color:", ["Color:", "Color:", "Color:", "Color:", "颜色:"]), ("Columns", ["Columns", "Columns", "Columns", "Columns", "栏"]), - ("Composer:", ["Composer:", "Composer:", "Composer:", "Composer:", "书写器:"]), + ("Composer:", ["Composer:", "Composer:", "Composer:", "Composer:", "排版器:"]), ("Copies:", ["Copies:", "Copies:", "Copies:", "Copies:", "份数:"]), ("Count", ["Count", "Count", "Count", "Count", "数量"]), ("Create", ["Create", "Create", "Create", "Create", "创建"]), @@ -362,7 +362,7 @@ const TABLE: &[(&str, [&str; 5])] = &[ ("Orientation:", ["Orientation:", "Orientation:", "Orientation:", "Orientation:", "方向:"]), ("Outside", ["Outside", "Outside", "Outside", "Outside", "外侧"]), ("Pages:", ["Pages:", "Pages:", "Pages:", "Pages:", "页面:"]), - ("Paragraph Composer", ["Paragraph Composer", "Paragraph Composer", "Paragraph Composer", "Paragraph Composer", "段落书写器"]), + ("Paragraph Composer", ["Paragraph Composer", "Paragraph Composer", "Paragraph Composer", "Paragraph Composer", "段落排版器"]), ( "Paragraph Style Options", ["Paragraph Style Options", "Paragraph Style Options", "Paragraph Style Options", "Paragraph Style Options", "段落样式选项"], @@ -442,7 +442,7 @@ const TABLE: &[(&str, [&str; 5])] = &[ ), ("Show Tag Markers", ["Show Tag Markers", "Show Tag Markers", "Show Tag Markers", "Show Tag Markers", "显示标记的框架"]), ("Single-line", ["Single-line", "Single-line", "Single-line", "Single-line", "单行"]), - ("Single-line Composer", ["Single-line Composer", "Single-line Composer", "Single-line Composer", "Single-line Composer", "单行书写器"]), + ("Single-line Composer", ["Single-line Composer", "Single-line Composer", "Single-line Composer", "Single-line Composer", "单行排版器"]), ("Size:", ["Size:", "Size:", "Size:", "Size:", "大小:"]), ( "Space Between Footnotes:", @@ -1786,6 +1786,816 @@ const TABLE: &[(&str, [&str; 5])] = &[ ["⌥-click clears overrides", "⌥-click clears overrides", "⌥-click clears overrides", "⌥-click clears overrides", "⌥-单击清除覆盖"], ), ("⚠ out of date", ["⚠ out of date", "⚠ out of date", "⚠ out of date", "⚠ out of date", "⚠ 已过期"]), + ("General", ["General", "General", "General", "General", "常规"]), + ( + "Basic Character Formats", + ["Basic Character Formats", "Basic Character Formats", "Basic Character Formats", "Basic Character Formats", "基本字符格式"], + ), + ("Indents and Spacing", ["Indents and Spacing", "Indents and Spacing", "Indents and Spacing", "Indents and Spacing", "缩进和间距"]), + ("Hyphenation", ["Hyphenation", "Hyphenation", "Hyphenation", "Hyphenation", "断字"]), + ("Justification", ["Justification", "Justification", "Justification", "Justification", "两端对齐"]), + ( + "Drop Caps and Nested Styles", + [ + "Drop Caps and Nested Styles", + "Drop Caps and Nested Styles", + "Drop Caps and Nested Styles", + "Drop Caps and Nested Styles", + "首字下沉和嵌套样式", + ], + ), + ("GREP Style", ["GREP Style", "GREP Style", "GREP Style", "GREP Style", "GREP 样式"]), + ("Character Color", ["Character Color", "Character Color", "Character Color", "Character Color", "字符颜色"]), + ( + "Numbering and Formatting", + ["Numbering and Formatting", "Numbering and Formatting", "Numbering and Formatting", "Numbering and Formatting", "编号和格式"], + ), + ("Spacing Options", ["Spacing Options", "Spacing Options", "Spacing Options", "Spacing Options", "间距选项"]), + ("First Baseline", ["First Baseline", "First Baseline", "First Baseline", "First Baseline", "首行基线"]), + ("Rule Above", ["Rule Above", "Rule Above", "Rule Above", "Rule Above", "上方横线"]), + ("Numbering", ["Numbering", "Numbering", "Numbering", "Numbering", "编号"]), + ( + "Footnote Reference Number in Text", + [ + "Footnote Reference Number in Text", + "Footnote Reference Number in Text", + "Footnote Reference Number in Text", + "Footnote Reference Number in Text", + "正文中的脚注引用编号", + ], + ), + ("Footnote Formatting", ["Footnote Formatting", "Footnote Formatting", "Footnote Formatting", "Footnote Formatting", "脚注格式"]), + ( + "Span Footnotes Across Columns", + [ + "Span Footnotes Across Columns", + "Span Footnotes Across Columns", + "Span Footnotes Across Columns", + "Span Footnotes Across Columns", + "脚注跨栏", + ], + ), + ("Rule On", ["Rule On", "Rule On", "Rule On", "Rule On", "启用横线"]), + ("Ascent", ["Ascent", "Ascent", "Ascent", "Ascent", "升部"]), + ("Cap Height", ["Cap Height", "Cap Height", "Cap Height", "Cap Height", "大写字母高度"]), + ("Leading", ["Leading", "Leading", "Leading", "Leading", "行距"]), + ("x Height", ["x Height", "x Height", "x Height", "x Height", "x 字高"]), + ("Fixed", ["Fixed", "Fixed", "Fixed", "Fixed", "固定"]), + ("Never (continuous)", ["Never (continuous)", "Never (continuous)", "Never (continuous)", "Never (continuous)", "从不(连续编号)"]), + ("Page", ["Page", "Page", "Page", "Page", "页面"]), + ("Spread", ["Spread", "Spread", "Spread", "Spread", "跨页"]), + ("Section", ["Section", "Section", "Section", "Section", "章节"]), + ("Footnote Reference", ["Footnote Reference", "Footnote Reference", "Footnote Reference", "Footnote Reference", "脚注引用"]), + ("Footnote Text", ["Footnote Text", "Footnote Text", "Footnote Text", "Footnote Text", "脚注文本"]), + ("Both", ["Both", "Both", "Both", "Both", "两者"]), + ("Apply Superscript", ["Apply Superscript", "Apply Superscript", "Apply Superscript", "Apply Superscript", "应用上标"]), + ("Apply Subscript", ["Apply Subscript", "Apply Subscript", "Apply Subscript", "Apply Subscript", "应用下标"]), + ("Apply Normal", ["Apply Normal", "Apply Normal", "Apply Normal", "Apply Normal", "应用正常位置"]), + ("OpenType Superscript", ["OpenType Superscript", "OpenType Superscript", "OpenType Superscript", "OpenType Superscript", "OpenType 上标"]), + ("through", ["through", "through", "through", "through", "直到(包含)"]), + ("up to", ["up to", "up to", "up to", "up to", "直到(不包含)"]), + ("line", ["line", "line", "line", "line", "行"]), + ("lines", ["lines", "lines", "lines", "lines", "行"]), + ("sentences", ["sentences", "sentences", "sentences", "sentences", "句子"]), + ("words", ["words", "words", "words", "words", "单词"]), + ("characters", ["characters", "characters", "characters", "characters", "字符"]), + ("letters", ["letters", "letters", "letters", "letters", "字母"]), + ("digits", ["digits", "digits", "digits", "digits", "数字"]), + ("tab", ["tab", "tab", "tab", "tab", "制表符"]), + ("forcedLineBreak", ["forcedLineBreak", "forcedLineBreak", "forcedLineBreak", "forcedLineBreak", "强制换行符"]), + ("emSpace", ["emSpace", "emSpace", "emSpace", "emSpace", "全角空格"]), + ("enSpace", ["enSpace", "enSpace", "enSpace", "enSpace", "半角空格"]), + ("chars", ["chars", "chars", "chars", "chars", "指定字符"]), + ("[Automatic]", ["[Automatic]", "[Automatic]", "[Automatic]", "[Automatic]", "[自动]"]), + ("Tracking:", ["Tracking:", "Tracking:", "Tracking:", "Tracking:", "字距:"]), + ("Right Indent:", ["Right Indent:", "Right Indent:", "Right Indent:", "Right Indent:", "右缩进:"]), + ("First Line Indent:", ["First Line Indent:", "First Line Indent:", "First Line Indent:", "First Line Indent:", "首行缩进:"]), + ("Space Before:", ["Space Before:", "Space Before:", "Space Before:", "Space Before:", "段前间距:"]), + ("Space After:", ["Space After:", "Space After:", "Space After:", "Space After:", "段后间距:"]), + ("Words with at Least:", ["Words with at Least:", "Words with at Least:", "Words with at Least:", "Words with at Least:", "最少单词长度:"]), + ("After First:", ["After First:", "After First:", "After First:", "After First:", "前部最少保留:"]), + ("Before Last:", ["Before Last:", "Before Last:", "Before Last:", "Before Last:", "后部最少保留:"]), + ("Hyphen Limit:", ["Hyphen Limit:", "Hyphen Limit:", "Hyphen Limit:", "Hyphen Limit:", "连续断字上限:"]), + ("Word Spacing:", ["Word Spacing:", "Word Spacing:", "Word Spacing:", "Word Spacing:", "单词间距:"]), + ("Letter Spacing:", ["Letter Spacing:", "Letter Spacing:", "Letter Spacing:", "Letter Spacing:", "字母间距:"]), + ("Glyph Scaling:", ["Glyph Scaling:", "Glyph Scaling:", "Glyph Scaling:", "Glyph Scaling:", "字形缩放:"]), + ("Insert Kashidas:", ["Insert Kashidas:", "Insert Kashidas:", "Insert Kashidas:", "Insert Kashidas:", "插入延长线:"]), + ( + "In justified Arabic text", + [ + "In justified Arabic text", + "In justified Arabic text", + "In justified Arabic text", + "In justified Arabic text", + "在两端对齐的阿拉伯语文本中", + ], + ), + ("Binding:", ["Binding:", "Binding:", "Binding:", "Binding:", "装订方向:"]), + ("Ruby:", ["Ruby:", "Ruby:", "Ruby:", "Ruby:", "注音:"]), + ( + "Set over the selected text; empty removes it.", + [ + "Set over the selected text; empty removes it.", + "Set over the selected text; empty removes it.", + "Set over the selected text; empty removes it.", + "Set over the selected text; empty removes it.", + "在所选文本上方添加注音;留空可移除。", + ], + ), + ("[Basic Paragraph]", ["[Basic Paragraph]", "[Basic Paragraph]", "[Basic Paragraph]", "[Basic Paragraph]", "[基本段落]"]), + ("[No Paragraph Style]", ["[No Paragraph Style]", "[No Paragraph Style]", "[No Paragraph Style]", "[No Paragraph Style]", "[无段落样式]"]), + ("Align left", ["Align left", "Align left", "Align left", "Align left", "左对齐"]), + ("Align center", ["Align center", "Align center", "Align center", "Align center", "居中对齐"]), + ("Align right", ["Align right", "Align right", "Align right", "Align right", "右对齐"]), + ( + "Justify with last line aligned left", + [ + "Justify with last line aligned left", + "Justify with last line aligned left", + "Justify with last line aligned left", + "Justify with last line aligned left", + "两端对齐,末行左对齐", + ], + ), + ( + "Justify with last line aligned center", + [ + "Justify with last line aligned center", + "Justify with last line aligned center", + "Justify with last line aligned center", + "Justify with last line aligned center", + "两端对齐,末行居中", + ], + ), + ( + "Justify with last line aligned right", + [ + "Justify with last line aligned right", + "Justify with last line aligned right", + "Justify with last line aligned right", + "Justify with last line aligned right", + "两端对齐,末行右对齐", + ], + ), + ("Justify all lines", ["Justify all lines", "Justify all lines", "Justify all lines", "Justify all lines", "所有行两端对齐"]), + ("Align towards spine", ["Align towards spine", "Align towards spine", "Align towards spine", "Align towards spine", "朝向书脊对齐"]), + ("Align away from spine", ["Align away from spine", "Align away from spine", "Align away from spine", "Align away from spine", "背向书脊对齐"]), + ( + "Create New Style from Selection", + [ + "Create New Style from Selection", + "Create New Style from Selection", + "Create New Style from Selection", + "Create New Style from Selection", + "从所选内容创建新样式", + ], + ), + ("No text selected", ["No text selected", "No text selected", "No text selected", "No text selected", "未选择文本"]), + ("Search", ["Search", "Search", "Search", "Search", "搜索"]), + ("Essentials", ["Essentials", "Essentials", "Essentials", "Essentials", "基本功能"]), + ("Advanced", ["Advanced", "Advanced", "Advanced", "Advanced", "高级"]), + ("Digital Publishing", ["Digital Publishing", "Digital Publishing", "Digital Publishing", "Digital Publishing", "数字出版"]), + ("Interactive for PDF", ["Interactive for PDF", "Interactive for PDF", "Interactive for PDF", "Interactive for PDF", "PDF 交互"]), + ("Printing and Proofing", ["Printing and Proofing", "Printing and Proofing", "Printing and Proofing", "Printing and Proofing", "打印和校样"]), + ("Typography", ["Typography", "Typography", "Typography", "Typography", "文字排版"]), + ("Rulers & Grids", ["Rulers & Grids", "Rulers & Grids", "Rulers & Grids", "Rulers & Grids", "标尺和网格"]), + ("No errors", ["No errors", "No errors", "No errors", "No errors", "无错误"]), + ("error", ["error", "error", "error", "error", "个错误"]), + ("errors", ["errors", "errors", "errors", "errors", "个错误"]), + ("Reset", ["Reset", "Reset", "Reset", "Reset", "重置"]), + ("Right to Left", ["Right to Left", "Right to Left", "Right to Left", "Right to Left", "从右向左"]), + ("Left to Right", ["Left to Right", "Left to Right", "Left to Right", "Left to Right", "从左向右"]), + ("Portrait", ["Portrait", "Portrait", "Portrait", "Portrait", "纵向"]), + ("Landscape", ["Landscape", "Landscape", "Landscape", "Landscape", "横向"]), + ( + "Display All Blacks as Rich Black", + [ + "Display All Blacks as Rich Black", + "Display All Blacks as Rich Black", + "Display All Blacks as Rich Black", + "Display All Blacks as Rich Black", + "所有黑色显示为浓黑", + ], + ), + ( + "Display All Blacks Accurately", + [ + "Display All Blacks Accurately", + "Display All Blacks Accurately", + "Display All Blacks Accurately", + "Display All Blacks Accurately", + "准确显示所有黑色", + ], + ), + ( + "Output All Blacks as Rich Black", + [ + "Output All Blacks as Rich Black", + "Output All Blacks as Rich Black", + "Output All Blacks as Rich Black", + "Output All Blacks as Rich Black", + "所有黑色输出为浓黑", + ], + ), + ( + "Output All Blacks Accurately", + [ + "Output All Blacks Accurately", + "Output All Blacks Accurately", + "Output All Blacks Accurately", + "Output All Blacks Accurately", + "准确输出所有黑色", + ], + ), + ("Top Margin", ["Top Margin", "Top Margin", "Top Margin", "Top Margin", "上边距"]), + ("Top of Page", ["Top of Page", "Top of Page", "Top of Page", "Top of Page", "页面顶部"]), + ( + "Paragraph Styles panel", + ["Paragraph Styles panel", "Paragraph Styles panel", "Paragraph Styles panel", "Paragraph Styles panel", "段落样式面板"], + ), + ( + "Character Styles panel", + ["Character Styles panel", "Character Styles panel", "Character Styles panel", "Character Styles panel", "字符样式面板"], + ), + ("Create new style", ["Create new style", "Create new style", "Create new style", "Create new style", "创建新样式"]), + ("Clear overrides", ["Clear overrides", "Clear overrides", "Clear overrides", "Clear overrides", "清除覆盖"]), + ("Apply", ["Apply", "Apply", "Apply", "Apply", "应用"]), + ("Accept", ["Accept", "Accept", "Accept", "Accept", "接受"]), + ("Reject", ["Reject", "Reject", "Reject", "Reject", "拒绝"]), + ("Add Selection", ["Add Selection", "Add Selection", "Add Selection", "Add Selection", "添加所选内容"]), + ("Align Stroke:", ["Align Stroke:", "Align Stroke:", "Align Stroke:", "Align Stroke:", "描边对齐:"]), + ("Angle", ["Angle", "Angle", "Angle", "Angle", "角度"]), + ("Angle:", ["Angle:", "Angle:", "Angle:", "Angle:", "角度:"]), + ("Baseline", ["Baseline", "Baseline", "Baseline", "Baseline", "基线"]), + ("Cap:", ["Cap:", "Cap:", "Cap:", "Cap:", "端点:"]), + ("Cell Text", ["Cell Text", "Cell Text", "Cell Text", "Cell Text", "单元格文本"]), + ("Cell Type", ["Cell Type", "Cell Type", "Cell Type", "Cell Type", "单元格类型"]), + ("Composer", ["Composer", "Composer", "Composer", "Composer", "排版器"]), + ("Convert to Text", ["Convert to Text", "Convert to Text", "Convert to Text", "Convert to Text", "转换为文本"]), + ("Create New Layer", ["Create New Layer", "Create New Layer", "Create New Layer", "Create New Layer", "创建新图层"]), + ("Delete DTD", ["Delete DTD", "Delete DTD", "Delete DTD", "Delete DTD", "删除 DTD"]), + ("Digits", ["Digits", "Digits", "Digits", "Digits", "数字"]), + ("Direction", ["Direction", "Direction", "Direction", "Direction", "方向"]), + ("Distance", ["Distance", "Distance", "Distance", "Distance", "距离"]), + ("Distribute ↔", ["Distribute ↔", "Distribute ↔", "Distribute ↔", "Distribute ↔", "水平分布"]), + ("Distribute ↕", ["Distribute ↕", "Distribute ↕", "Distribute ↕", "Distribute ↕", "垂直分布"]), + ("Effects: Drop Shadow", ["Effects: Drop Shadow", "Effects: Drop Shadow", "Effects: Drop Shadow", "Effects: Drop Shadow", "效果:投影"]), + ("Favorites", ["Favorites", "Favorites", "Favorites", "Favorites", "收藏"]), + ("First Spread", ["First Spread", "First Spread", "First Spread", "First Spread", "第一个跨页"]), + ("Gap Color:", ["Gap Color:", "Gap Color:", "Gap Color:", "Gap Color:", "间隙颜色:"]), + ("Graphic…", ["Graphic…", "Graphic…", "Graphic…", "Graphic…", "图形…"]), + ("Home", ["Home", "Home", "Home", "Home", "主页"]), + ("Join:", ["Join:", "Join:", "Join:", "Join:", "连接:"]), + ("Language", ["Language", "Language", "Language", "Language", "语言"]), + ("Last Spread", ["Last Spread", "Last Spread", "Last Spread", "Last Spread", "最后一个跨页"]), + ("Load DTD…", ["Load DTD…", "Load DTD…", "Load DTD…", "Load DTD…", "载入 DTD…"]), + ("Location:", ["Location:", "Location:", "Location:", "Location:", "位置:"]), + ("Lock Guides", ["Lock Guides", "Lock Guides", "Lock Guides", "Lock Guides", "锁定参考线"]), + ("Miter Limit:", ["Miter Limit:", "Miter Limit:", "Miter Limit:", "Miter Limit:", "斜接限制:"]), + ( + "New Article from Selection", + [ + "New Article from Selection", + "New Article from Selection", + "New Article from Selection", + "New Article from Selection", + "从所选内容新建文章", + ], + ), + ("New Note at Cursor", ["New Note at Cursor", "New Note at Cursor", "New Note at Cursor", "New Note at Cursor", "在光标处新建附注"]), + ("New Swatch", ["New Swatch", "New Swatch", "New Swatch", "New Swatch", "新建色板"]), + ("New condition name", ["New condition name", "New condition name", "New condition name", "New condition name", "新条件名称"]), + ("New group…", ["New group…", "New group…", "New group…", "New group…", "新建组…"]), + ("New tag", ["New tag", "New tag", "New tag", "New tag", "新建标签"]), + ("Next Spread", ["Next Spread", "Next Spread", "Next Spread", "Next Spread", "下一个跨页"]), + ("Note text", ["Note text", "Note text", "Note text", "Note text", "附注文本"]), + ("Offset", ["Offset", "Offset", "Offset", "Offset", "偏移"]), + ("Offsets", ["Offsets", "Offsets", "Offsets", "Offsets", "偏移量"]), + ("Paragraph alignment", ["Paragraph alignment", "Paragraph alignment", "Paragraph alignment", "Paragraph alignment", "段落对齐"]), + ("Previous Spread", ["Previous Spread", "Previous Spread", "Previous Spread", "Previous Spread", "上一个跨页"]), + ("Release to Objects", ["Release to Objects", "Release to Objects", "Release to Objects", "Release to Objects", "释放为对象"]), + ("Remove", ["Remove", "Remove", "Remove", "Remove", "移除"]), + ( + "Rotate 90° Counterclockwise", + [ + "Rotate 90° Counterclockwise", + "Rotate 90° Counterclockwise", + "Rotate 90° Counterclockwise", + "Rotate 90° Counterclockwise", + "逆时针旋转 90°", + ], + ), + ("Rotation Angle", ["Rotation Angle", "Rotation Angle", "Rotation Angle", "Rotation Angle", "旋转角度"]), + ("Rows", ["Rows", "Rows", "Rows", "Rows", "行数"]), + ("Run", ["Run", "Run", "Run", "Run", "运行"]), + ("Scale X Percentage", ["Scale X Percentage", "Scale X Percentage", "Scale X Percentage", "Scale X Percentage", "水平缩放百分比"]), + ("Scale Y Percentage", ["Scale Y Percentage", "Scale Y Percentage", "Scale Y Percentage", "Scale Y Percentage", "垂直缩放百分比"]), + ("Search commands", ["Search commands", "Search commands", "Search commands", "Search commands", "搜索命令"]), + ("Search fonts", ["Search fonts", "Search fonts", "Search fonts", "Search fonts", "搜索字体"]), + ( + "Search styles and commands…", + [ + "Search styles and commands…", + "Search styles and commands…", + "Search styles and commands…", + "Search styles and commands…", + "搜索样式和命令…", + ], + ), + ( + "Search: character or U+code", + [ + "Search: character or U+code", + "Search: character or U+code", + "Search: character or U+code", + "Search: character or U+code", + "搜索字符或 U+ 编码", + ], + ), + ("Share", ["Share", "Share", "Share", "Share", "分享"]), + ("Shear X Angle", ["Shear X Angle", "Shear X Angle", "Shear X Angle", "Shear X Angle", "水平倾斜角度"]), + ("Show Baseline Grid", ["Show Baseline Grid", "Show Baseline Grid", "Show Baseline Grid", "Show Baseline Grid", "显示基线网格"]), + ("Show Document Grid", ["Show Document Grid", "Show Document Grid", "Show Document Grid", "Show Document Grid", "显示文档网格"]), + ("Show Guides", ["Show Guides", "Show Guides", "Show Guides", "Show Guides", "显示参考线"]), + ("Smart Guides", ["Smart Guides", "Smart Guides", "Smart Guides", "Smart Guides", "智能参考线"]), + ("Stop Color:", ["Stop Color:", "Stop Color:", "Stop Color:", "Stop Color:", "色标颜色:"]), + ("Style", ["Style", "Style", "Style", "Style", "样式"]), + ("Styles panel", ["Styles panel", "Styles panel", "Styles panel", "Styles panel", "样式面板"]), + ("Text", ["Text", "Text", "Text", "Text", "文本"]), + ( + "The structure is valid.", + ["The structure is valid.", "The structure is valid.", "The structure is valid.", "The structure is valid.", "结构有效。"], + ), + ("Toggle double column", ["Toggle double column", "Toggle double column", "Toggle double column", "Toggle double column", "切换双列布局"]), + ("Tracking", ["Tracking", "Tracking", "Tracking", "Tracking", "字距"]), + ("Untag Selection", ["Untag Selection", "Untag Selection", "Untag Selection", "Untag Selection", "移除所选内容的标签"]), + ("Validate", ["Validate", "Validate", "Validate", "Validate", "验证"]), + ("Weight", ["Weight", "Weight", "Weight", "Weight", "粗细"]), + ("Subtract", ["Subtract", "Subtract", "Subtract", "Subtract", "减去"]), + ("Intersect", ["Intersect", "Intersect", "Intersect", "Intersect", "交集"]), + ("Exclude Overlap", ["Exclude Overlap", "Exclude Overlap", "Exclude Overlap", "Exclude Overlap", "排除重叠"]), + ("Minus Back", ["Minus Back", "Minus Back", "Minus Back", "Minus Back", "减去后方对象"]), + ("Align left edges", ["Align left edges", "Align left edges", "Align left edges", "Align left edges", "左边缘对齐"]), + ( + "Align horizontal centers", + ["Align horizontal centers", "Align horizontal centers", "Align horizontal centers", "Align horizontal centers", "水平居中对齐"], + ), + ("Align right edges", ["Align right edges", "Align right edges", "Align right edges", "Align right edges", "右边缘对齐"]), + ("Align top edges", ["Align top edges", "Align top edges", "Align top edges", "Align top edges", "顶边缘对齐"]), + ( + "Align vertical centers", + ["Align vertical centers", "Align vertical centers", "Align vertical centers", "Align vertical centers", "垂直居中对齐"], + ), + ("Align bottom edges", ["Align bottom edges", "Align bottom edges", "Align bottom edges", "Align bottom edges", "底边缘对齐"]), + ("Align to Selection", ["Align to Selection", "Align to Selection", "Align to Selection", "Align to Selection", "对齐到所选对象"]), + ("Align to Key Object", ["Align to Key Object", "Align to Key Object", "Align to Key Object", "Align to Key Object", "对齐到关键对象"]), + ("Align to Margins", ["Align to Margins", "Align to Margins", "Align to Margins", "Align to Margins", "对齐到边距"]), + ("Align to Page", ["Align to Page", "Align to Page", "Align to Page", "Align to Page", "对齐到页面"]), + ("Align to Spread", ["Align to Spread", "Align to Spread", "Align to Spread", "Align to Spread", "对齐到跨页"]), + ("No Text Wrap", ["No Text Wrap", "No Text Wrap", "No Text Wrap", "No Text Wrap", "无文本绕排"]), + ( + "Wrap Around Bounding Box", + ["Wrap Around Bounding Box", "Wrap Around Bounding Box", "Wrap Around Bounding Box", "Wrap Around Bounding Box", "绕排定界框"], + ), + ( + "Wrap Around Object Shape", + ["Wrap Around Object Shape", "Wrap Around Object Shape", "Wrap Around Object Shape", "Wrap Around Object Shape", "绕排对象形状"], + ), + ("Jump Object", ["Jump Object", "Jump Object", "Jump Object", "Jump Object", "跳过对象"]), + ("Jump to Next Column", ["Jump to Next Column", "Jump to Next Column", "Jump to Next Column", "Jump to Next Column", "跳至下一栏"]), + ("Bulleted List", ["Bulleted List", "Bulleted List", "Bulleted List", "Bulleted List", "项目符号列表"]), + ("Numbered List", ["Numbered List", "Numbered List", "Numbered List", "Numbered List", "编号列表"]), + ("UPPERCASE", ["UPPERCASE", "UPPERCASE", "UPPERCASE", "UPPERCASE", "大写"]), + ("Small Caps", ["Small Caps", "Small Caps", "Small Caps", "Small Caps", "小型大写字母"]), + ("Normal", ["Normal", "Normal", "Normal", "Normal", "正常"]), + ("Butt", ["Butt", "Butt", "Butt", "Butt", "平头"]), + ("Round", ["Round", "Round", "Round", "Round", "圆形"]), + ("Projecting", ["Projecting", "Projecting", "Projecting", "Projecting", "突出"]), + ("Miter", ["Miter", "Miter", "Miter", "Miter", "斜接"]), + ("Bevel", ["Bevel", "Bevel", "Bevel", "Bevel", "斜角"]), + ("Center", ["Center", "Center", "Center", "Center", "居中"]), + ("Start/End:", ["Start/End:", "Start/End:", "Start/End:", "Start/End:", "起点/终点:"]), + ("Left", ["Left", "Left", "Left", "Left", "左"]), + ("Right", ["Right", "Right", "Right", "Right", "右"]), + ("First", ["First", "First", "First", "First", "首行"]), + ("Last", ["Last", "Last", "Last", "Last", "末行"]), + ("Before", ["Before", "Before", "Before", "Before", "段前"]), + ("After", ["After", "After", "After", "After", "段后"]), + ("Shading", ["Shading", "Shading", "Shading", "Shading", "底纹"]), + ("Border", ["Border", "Border", "Border", "Border", "边框"]), + ("Overprint Fill", ["Overprint Fill", "Overprint Fill", "Overprint Fill", "Overprint Fill", "叠印填色"]), + ("Overprint Stroke", ["Overprint Stroke", "Overprint Stroke", "Overprint Stroke", "Overprint Stroke", "叠印描边"]), + ("Overprint Gap", ["Overprint Gap", "Overprint Gap", "Overprint Gap", "Overprint Gap", "叠印间隙"]), + ("Nonprinting", ["Nonprinting", "Nonprinting", "Nonprinting", "Nonprinting", "不打印"]), + ("At Least", ["At Least", "At Least", "At Least", "At Least", "至少"]), + ("Exactly", ["Exactly", "Exactly", "Exactly", "Exactly", "固定"]), + ("Justify", ["Justify", "Justify", "Justify", "Justify", "两端对齐"]), + ("Underline Options", ["Underline Options", "Underline Options", "Underline Options", "Underline Options", "下划线选项"]), + ("Strikethrough Options", ["Strikethrough Options", "Strikethrough Options", "Strikethrough Options", "Strikethrough Options", "删除线选项"]), + ( + "Discretionary Ligatures", + ["Discretionary Ligatures", "Discretionary Ligatures", "Discretionary Ligatures", "Discretionary Ligatures", "任意连字"], + ), + ("Fractions", ["Fractions", "Fractions", "Fractions", "Fractions", "分数"]), + ("Ordinal", ["Ordinal", "Ordinal", "Ordinal", "Ordinal", "序数"]), + ("Swash", ["Swash", "Swash", "Swash", "Swash", "花体"]), + ("Titling Alternates", ["Titling Alternates", "Titling Alternates", "Titling Alternates", "Titling Alternates", "标题替代字形"]), + ("Contextual Alternates", ["Contextual Alternates", "Contextual Alternates", "Contextual Alternates", "Contextual Alternates", "上下文替代字形"]), + ("Slashed Zero", ["Slashed Zero", "Slashed Zero", "Slashed Zero", "Slashed Zero", "斜线零"]), + ("Tabular Lining", ["Tabular Lining", "Tabular Lining", "Tabular Lining", "Tabular Lining", "等宽齐线数字"]), + ("Proportional Oldstyle", ["Proportional Oldstyle", "Proportional Oldstyle", "Proportional Oldstyle", "Proportional Oldstyle", "比例旧式数字"]), + ("Proportional Lining", ["Proportional Lining", "Proportional Lining", "Proportional Lining", "Proportional Lining", "比例齐线数字"]), + ("Tabular Oldstyle", ["Tabular Oldstyle", "Tabular Oldstyle", "Tabular Oldstyle", "Tabular Oldstyle", "等宽旧式数字"]), + ("Default Figure Style", ["Default Figure Style", "Default Figure Style", "Default Figure Style", "Default Figure Style", "默认数字样式"]), + ("Not a button", ["Not a button", "Not a button", "Not a button", "Not a button", "非按钮"]), + ("Go To Next Page", ["Go To Next Page", "Go To Next Page", "Go To Next Page", "Go To Next Page", "转到下一页"]), + ("Go To Previous Page", ["Go To Previous Page", "Go To Previous Page", "Go To Previous Page", "Go To Previous Page", "转到上一页"]), + ("Go To First Page", ["Go To First Page", "Go To First Page", "Go To First Page", "Go To First Page", "转到第一页"]), + ("Go To Last Page", ["Go To Last Page", "Go To Last Page", "Go To Last Page", "Go To Last Page", "转到最后一页"]), + ("Go To Page", ["Go To Page", "Go To Page", "Go To Page", "Go To Page", "转到页面"]), + ("Go To URL", ["Go To URL", "Go To URL", "Go To URL", "Go To URL", "转到网址"]), + ("Not a form field", ["Not a form field", "Not a form field", "Not a form field", "Not a form field", "非表单域"]), + ("Text Field", ["Text Field", "Text Field", "Text Field", "Text Field", "文本域"]), + ("Check Box", ["Check Box", "Check Box", "Check Box", "Check Box", "复选框"]), + ("Radio Button", ["Radio Button", "Radio Button", "Radio Button", "Radio Button", "单选按钮"]), + ("Combo Box", ["Combo Box", "Combo Box", "Combo Box", "Combo Box", "组合框"]), + ("List Box", ["List Box", "List Box", "List Box", "List Box", "列表框"]), + ("Signature", ["Signature", "Signature", "Signature", "Signature", "签名"]), + ("Resize width", ["Resize width", "Resize width", "Resize width", "Resize width", "调整宽度"]), + ("Resize height", ["Resize height", "Resize height", "Resize height", "Resize height", "调整高度"]), + ("Pin top", ["Pin top", "Pin top", "Pin top", "Pin top", "固定顶部"]), + ("Pin bottom", ["Pin bottom", "Pin bottom", "Pin bottom", "Pin bottom", "固定底部"]), + ("Pin left", ["Pin left", "Pin left", "Pin left", "Pin left", "固定左侧"]), + ("Pin right", ["Pin right", "Pin right", "Pin right", "Pin right", "固定右侧"]), + ("Inward", ["Inward", "Inward", "Inward", "Inward", "向内"]), + ("Horizontal", ["Horizontal", "Horizontal", "Horizontal", "Horizontal", "水平"]), + ("Vertical", ["Vertical", "Vertical", "Vertical", "Vertical", "垂直"]), + ("Metrics", ["Metrics", "Metrics", "Metrics", "Metrics", "度量"]), + ("Optical", ["Optical", "Optical", "Optical", "Optical", "视觉"]), + ("Applies to fill", ["Applies to fill", "Applies to fill", "Applies to fill", "Applies to fill", "应用于填色"]), + ("Applies to text", ["Applies to text", "Applies to text", "Applies to text", "Applies to text", "应用于文本"]), + ("Gray", ["Gray", "Gray", "Gray", "Gray", "灰度"]), + ("Linear", ["Linear", "Linear", "Linear", "Linear", "线性"]), + ("Radial", ["Radial", "Radial", "Radial", "Radial", "径向"]), + ("Arabic", ["Arabic", "Arabic", "Arabic", "Arabic", "阿拉伯语"]), + ("Beveled Rectangle", ["Beveled Rectangle", "Beveled Rectangle", "Beveled Rectangle", "Beveled Rectangle", "斜角矩形"]), + ("Chinese", ["Chinese", "Chinese", "Chinese", "Chinese", "中文"]), + ( + "Constrain proportions for width and height", + [ + "Constrain proportions for width and height", + "Constrain proportions for width and height", + "Constrain proportions for width and height", + "Constrain proportions for width and height", + "约束宽高比例", + ], + ), + ("Corner", ["Corner", "Corner", "Corner", "Corner", "角"]), + ("Custom", ["Custom", "Custom", "Custom", "Custom", "自定义"]), + ("Czech", ["Czech", "Czech", "Czech", "Czech", "捷克语"]), + ("Danish", ["Danish", "Danish", "Danish", "Danish", "丹麦语"]), + ("Dashed", ["Dashed", "Dashed", "Dashed", "Dashed", "虚线"]), + ("Dotted", ["Dotted", "Dotted", "Dotted", "Dotted", "点线"]), + ("Dutch: 2005 Reform", ["Dutch: 2005 Reform", "Dutch: 2005 Reform", "Dutch: 2005 Reform", "Dutch: 2005 Reform", "荷兰语:2005 年拼写改革"]), + ("Embedded", ["Embedded", "Embedded", "Embedded", "Embedded", "已嵌入"]), + ("English: Canadian", ["English: Canadian", "English: Canadian", "English: Canadian", "English: Canadian", "英语:加拿大"]), + ("English: UK", ["English: UK", "English: UK", "English: UK", "English: UK", "英语:英国"]), + ("English: USA", ["English: USA", "English: USA", "English: USA", "English: USA", "英语:美国"]), + ("Farsi", ["Farsi", "Farsi", "Farsi", "Farsi", "波斯语"]), + ("Finnish", ["Finnish", "Finnish", "Finnish", "Finnish", "芬兰语"]), + ("Fit Frame to Content", ["Fit Frame to Content", "Fit Frame to Content", "Fit Frame to Content", "Fit Frame to Content", "框架适合内容"]), + ("French", ["French", "French", "French", "French", "法语"]), + ("French: Canadian", ["French: Canadian", "French: Canadian", "French: Canadian", "French: Canadian", "法语:加拿大"]), + ("German: 2006 Reform", ["German: 2006 Reform", "German: 2006 Reform", "German: 2006 Reform", "German: 2006 Reform", "德语:2006 年拼写改革"]), + ( + "German: Swiss 2006 Reform", + [ + "German: Swiss 2006 Reform", + "German: Swiss 2006 Reform", + "German: Swiss 2006 Reform", + "German: Swiss 2006 Reform", + "德语:瑞士 2006 年拼写改革", + ], + ), + ("Greek", ["Greek", "Greek", "Greek", "Greek", "希腊语"]), + ("Hebrew", ["Hebrew", "Hebrew", "Hebrew", "Hebrew", "希伯来语"]), + ("Hindi", ["Hindi", "Hindi", "Hindi", "Hindi", "印地语"]), + ("Hungarian", ["Hungarian", "Hungarian", "Hungarian", "Hungarian", "匈牙利语"]), + ( + "Inverse Rounded Rectangle", + ["Inverse Rounded Rectangle", "Inverse Rounded Rectangle", "Inverse Rounded Rectangle", "Inverse Rounded Rectangle", "内凹圆角矩形"], + ), + ("Italian", ["Italian", "Italian", "Italian", "Italian", "意大利语"]), + ("Japanese", ["Japanese", "Japanese", "Japanese", "Japanese", "日语"]), + ("Korean", ["Korean", "Korean", "Korean", "Korean", "韩语"]), + ( + "Make all settings the same", + ["Make all settings the same", "Make all settings the same", "Make all settings the same", "Make all settings the same", "统一所有设置"], + ), + ("Native", ["Native", "Native", "Native", "Native", "本地数字"]), + ("Norwegian: Bokmål", ["Norwegian: Bokmål", "Norwegian: Bokmål", "Norwegian: Bokmål", "Norwegian: Bokmål", "挪威语:书面语"]), + ("Orthogonal Line", ["Orthogonal Line", "Orthogonal Line", "Orthogonal Line", "Orthogonal Line", "正交线"]), + ("Polish", ["Polish", "Polish", "Polish", "Polish", "波兰语"]), + ("Portuguese", ["Portuguese", "Portuguese", "Portuguese", "Portuguese", "葡萄牙语"]), + ("Portuguese: Brazilian", ["Portuguese: Brazilian", "Portuguese: Brazilian", "Portuguese: Brazilian", "Portuguese: Brazilian", "葡萄牙语:巴西"]), + ("Rounded Rectangle", ["Rounded Rectangle", "Rounded Rectangle", "Rounded Rectangle", "Rounded Rectangle", "圆角矩形"]), + ("Russian", ["Russian", "Russian", "Russian", "Russian", "俄语"]), + ("Solid", ["Solid", "Solid", "Solid", "Solid", "实线"]), + ("Spanish", ["Spanish", "Spanish", "Spanish", "Spanish", "西班牙语"]), + ("Story Editor", ["Story Editor", "Story Editor", "Story Editor", "Story Editor", "文章编辑器"]), + ("Swedish", ["Swedish", "Swedish", "Swedish", "Swedish", "瑞典语"]), + ("Thick - Thick", ["Thick - Thick", "Thick - Thick", "Thick - Thick", "Thick - Thick", "粗线-粗线"]), + ("Thick - Thin", ["Thick - Thin", "Thick - Thin", "Thick - Thin", "Thick - Thin", "粗线-细线"]), + ("Thin - Thick", ["Thin - Thick", "Thin - Thick", "Thin - Thick", "Thin - Thick", "细线-粗线"]), + ("Thin - Thin", ["Thin - Thin", "Thin - Thin", "Thin - Thin", "Thin - Thin", "细线-细线"]), + ("Thin - Thick - Thin", ["Thin - Thick - Thin", "Thin - Thick - Thin", "Thin - Thick - Thin", "Thin - Thick - Thin", "细线-粗线-细线"]), + ("Thick - Thin - Thick", ["Thick - Thin - Thick", "Thick - Thin - Thick", "Thick - Thin - Thick", "Thick - Thin - Thick", "粗线-细线-粗线"]), + ("Wavy", ["Wavy", "Wavy", "Wavy", "Wavy", "波浪线"]), + ("Straight Hash", ["Straight Hash", "Straight Hash", "Straight Hash", "Straight Hash", "直斜线"]), + ("Stripes", ["Stripes", "Stripes", "Stripes", "Stripes", "条纹"]), + ("Triangle", ["Triangle", "Triangle", "Triangle", "Triangle", "三角形"]), + ("Turkish", ["Turkish", "Turkish", "Turkish", "Turkish", "土耳其语"]), + ("Ukrainian", ["Ukrainian", "Ukrainian", "Ukrainian", "Ukrainian", "乌克兰语"]), + ("Cell Style", ["Cell Style", "Cell Style", "Cell Style", "Cell Style", "单元格样式"]), + ("Table Style", ["Table Style", "Table Style", "Table Style", "Table Style", "表格样式"]), + ("Col ←", ["Col ←", "Col ←", "Col ←", "Col ←", "左侧插入列"]), + ("Col →", ["Col →", "Col →", "Col →", "Col →", "右侧插入列"]), + ("Row ↑", ["Row ↑", "Row ↑", "Row ↑", "Row ↑", "上方插入行"]), + ("Row ↓", ["Row ↓", "Row ↓", "Row ↓", "Row ↓", "下方插入行"]), + ("Hide Others", ["Hide Others", "Hide Others", "Hide Others", "Hide Others", "隐藏其他图层"]), + ("Lock Others", ["Lock Others", "Lock Others", "Lock Others", "Lock Others", "锁定其他图层"]), + ("Show All Layers", ["Show All Layers", "Show All Layers", "Show All Layers", "Show All Layers", "显示所有图层"]), + ("Unlock", ["Unlock", "Unlock", "Unlock", "Unlock", "解锁"]), + ("Unlock All Layers", ["Unlock All Layers", "Unlock All Layers", "Unlock All Layers", "Unlock All Layers", "解锁所有图层"]), + ("Added", ["Added", "Added", "Added", "Added", "已添加"]), + ("Deleted", ["Deleted", "Deleted", "Deleted", "Deleted", "已删除"]), + ("Guide-based", ["Guide-based", "Guide-based", "Guide-based", "Guide-based", "基于参考线"]), + ("Loop", ["Loop", "Loop", "Loop", "Loop", "循环播放"]), + ("Object-based", ["Object-based", "Object-based", "Object-based", "Object-based", "基于对象"]), + ("Off", ["Off", "Off", "Off", "Off", "关闭"]), + ("Play on Page Load", ["Play on Page Load", "Play on Page Load", "Play on Page Load", "Play on Page Load", "页面载入时播放"]), + ("Re-center", ["Re-center", "Re-center", "Re-center", "Re-center", "重新居中"]), + ("Show Controls", ["Show Controls", "Show Controls", "Show Controls", "Show Controls", "显示控件"]), + ("Signature Field", ["Signature Field", "Signature Field", "Signature Field", "Signature Field", "签名域"]), + ( + "Select text to apply a condition", + [ + "Select text to apply a condition", + "Select text to apply a condition", + "Select text to apply a condition", + "Select text to apply a condition", + "选择文本以应用条件", + ], + ), + ("Blinds", ["Blinds", "Blinds", "Blinds", "Blinds", "百叶窗"]), + ("Box", ["Box", "Box", "Box", "Box", "方框"]), + ("Comb", ["Comb", "Comb", "Comb", "Comb", "梳状"]), + ("Cover", ["Cover", "Cover", "Cover", "Cover", "覆盖"]), + ("Dissolve", ["Dissolve", "Dissolve", "Dissolve", "Dissolve", "溶解"]), + ("Fade", ["Fade", "Fade", "Fade", "Fade", "淡入淡出"]), + ("Push", ["Push", "Push", "Push", "Push", "推入"]), + ("Split", ["Split", "Split", "Split", "Split", "分割"]), + ("Uncover", ["Uncover", "Uncover", "Uncover", "Uncover", "揭开"]), + ("Wipe", ["Wipe", "Wipe", "Wipe", "Wipe", "擦除"]), + ("Simple", ["Simple", "Simple", "Simple", "Simple", "简单"]), + ("Simple - Wide", ["Simple - Wide", "Simple - Wide", "Simple - Wide", "Simple - Wide", "简单-宽"]), + ("Triangle - Wide", ["Triangle - Wide", "Triangle - Wide", "Triangle - Wide", "Triangle - Wide", "三角形-宽"]), + ("Barbed", ["Barbed", "Barbed", "Barbed", "Barbed", "倒钩"]), + ("Curved", ["Curved", "Curved", "Curved", "Curved", "曲线"]), + ("Circle", ["Circle", "Circle", "Circle", "Circle", "圆形"]), + ("Circle - Solid", ["Circle - Solid", "Circle - Solid", "Circle - Solid", "Circle - Solid", "实心圆"]), + ("Square", ["Square", "Square", "Square", "Square", "正方形"]), + ("Square - Solid", ["Square - Solid", "Square - Solid", "Square - Solid", "Square - Solid", "实心正方形"]), + ("Bar", ["Bar", "Bar", "Bar", "Bar", "短线"]), + ("Fit Text", ["Fit Text", "Fit Text", "Fit Text", "Fit Text", "适合文本"]), + ("Preview (W)", ["Preview (W)", "Preview (W)", "Preview (W)", "Preview (W)", "预览 (W)"]), + ("Normal (W)", ["Normal (W)", "Normal (W)", "Normal (W)", "Normal (W)", "正常 (W)"]), + ("objects", ["objects", "objects", "objects", "objects", "个对象"]), + ("pages", ["pages", "pages", "pages", "pages", "页"]), + ("stories", ["stories", "stories", "stories", "stories", "篇文章"]), + ("items", ["items", "items", "items", "items", "个项目"]), + ("warnings", ["warnings", "warnings", "warnings", "warnings", "个警告"]), + ("Render", ["Render", "Render", "Render", "Render", "渲染"]), + ("Set", ["Set", "Set", "Set", "Set", "样式集"]), + ("unused", ["unused", "unused", "unused", "unused", "未使用"]), + ("⚠ Missing", ["⚠ Missing", "⚠ Missing", "⚠ Missing", "⚠ Missing", "⚠ 缺失"]), + ("▲ Modified", ["▲ Modified", "▲ Modified", "▲ Modified", "▲ Modified", "▲ 已修改"]), + ("(Text Color)", ["(Text Color)", "(Text Color)", "(Text Color)", "(Text Color)", "(文本颜色)"]), + ( + "{count} glyphs — click to insert at the text cursor", + [ + "{count} glyphs — click to insert at the text cursor", + "{count} glyphs — click to insert at the text cursor", + "{count} glyphs — click to insert at the text cursor", + "{count} glyphs — click to insert at the text cursor", + "{count} 个字形 — 单击插入到文本光标处", + ], + ), + ( + "{pages} Pages in {spreads} Spreads", + [ + "{pages} Pages in {spreads} Spreads", + "{pages} Pages in {spreads} Spreads", + "{pages} Pages in {spreads} Spreads", + "{pages} Pages in {spreads} Spreads", + "共 {pages} 页,{spreads} 个跨页", + ], + ), + (" (auto)", [" (auto)", " (auto)", " (auto)", " (auto)", "(自动)"]), + ( + "Absolute Numbering (instead of Section Numbering)", + [ + "Absolute Numbering (instead of Section Numbering)", + "Absolute Numbering (instead of Section Numbering)", + "Absolute Numbering (instead of Section Numbering)", + "Absolute Numbering (instead of Section Numbering)", + "绝对页码(而非章节页码)", + ], + ), + ( + "Add and remove pages as the primary text frame's story grows and shrinks", + [ + "Add and remove pages as the primary text frame's story grows and shrinks", + "Add and remove pages as the primary text frame's story grows and shrinks", + "Add and remove pages as the primary text frame's story grows and shrinks", + "Add and remove pages as the primary text frame's story grows and shrinks", + "随主文本框架内容增减自动添加或删除页面", + ], + ), + ( + "Added Text (highlighted)", + ["Added Text (highlighted)", "Added Text (highlighted)", "Added Text (highlighted)", "Added Text (highlighted)", "新增文本(高亮)"], + ), + ( + "Adjust Layout (objects follow the new page size)", + [ + "Adjust Layout (objects follow the new page size)", + "Adjust Layout (objects follow the new page size)", + "Adjust Layout (objects follow the new page size)", + "Adjust Layout (objects follow the new page size)", + "调整版面(对象随页面尺寸调整)", + ], + ), + ("Auto-Fit", ["Auto-Fit", "Auto-Fit", "Auto-Fit", "Auto-Fit", "自动适合"]), + ("Case sensitive", ["Case sensitive", "Case sensitive", "Case sensitive", "Case sensitive", "区分大小写"]), + ( + "Custom Tracking/Kerning", + ["Custom Tracking/Kerning", "Custom Tracking/Kerning", "Custom Tracking/Kerning", "Custom Tracking/Kerning", "自定义字距/字偶距"], + ), + ( + "Dimensions Include Stroke Weight", + [ + "Dimensions Include Stroke Weight", + "Dimensions Include Stroke Weight", + "Dimensions Include Stroke Weight", + "Dimensions Include Stroke Weight", + "尺寸包含描边粗细", + ], + ), + ("Enable Autocorrect", ["Enable Autocorrect", "Enable Autocorrect", "Enable Autocorrect", "Enable Autocorrect", "启用自动更正"]), + ( + "Enable Dynamic Spelling (underline misspelled words)", + [ + "Enable Dynamic Spelling (underline misspelled words)", + "Enable Dynamic Spelling (underline misspelled words)", + "Enable Dynamic Spelling (underline misspelled words)", + "Enable Dynamic Spelling (underline misspelled words)", + "启用动态拼写检查(在拼写错误下加线)", + ], + ), + ("Grids in Back", ["Grids in Back", "Grids in Back", "Grids in Back", "Grids in Back", "网格置于后方"]), + ("H&J Violations", ["H&J Violations", "H&J Violations", "H&J Violations", "H&J Violations", "断字与对齐违规"]), + ("Include Bleed", ["Include Bleed", "Include Bleed", "Include Bleed", "Include Bleed", "包含出血"]), + ("Include Stroke Weight", ["Include Stroke Weight", "Include Stroke Weight", "Include Stroke Weight", "Include Stroke Weight", "包含描边粗细"]), + ("Join our Discord", ["Join our Discord", "Join our Discord", "Join our Discord", "Join our Discord", "加入我们的 Discord 社区"]), + ("Keep Violations", ["Keep Violations", "Keep Violations", "Keep Violations", "Keep Violations", "保持选项违规"]), + ( + "Overprint [Black] Swatch at 100%", + [ + "Overprint [Black] Swatch at 100%", + "Overprint [Black] Swatch at 100%", + "Overprint [Black] Swatch at 100%", + "Overprint [Black] Swatch at 100%", + "100% 黑色色板叠印", + ], + ), + ("Printer's Marks", ["Printer's Marks", "Printer's Marks", "Printer's Marks", "Printer's Marks", "打印标记"]), + ( + "Remove Styles and Formatting from Text and Tables", + [ + "Remove Styles and Formatting from Text and Tables", + "Remove Styles and Formatting from Text and Tables", + "Remove Styles and Formatting from Text and Tables", + "Remove Styles and Formatting from Text and Tables", + "移除文本和表格中的样式与格式", + ], + ), + ("Ruby", ["Ruby", "Ruby", "Ruby", "Ruby", "注音"]), + ( + "Show Note Anchors in Layout View", + [ + "Show Note Anchors in Layout View", + "Show Note Anchors in Layout View", + "Show Note Anchors in Layout View", + "Show Note Anchors in Layout View", + "在版面视图显示附注锚点", + ], + ), + ("Spreads", ["Spreads", "Spreads", "Spreads", "Spreads", "跨页"]), + ("Substituted Fonts", ["Substituted Fonts", "Substituted Fonts", "Substituted Fonts", "Substituted Fonts", "替换的字体"]), + ( + "Transformations are Totals", + ["Transformations are Totals", "Transformations are Totals", "Transformations are Totals", "Transformations are Totals", "显示累计变换值"], + ), + ( + "Use Black Point Compensation", + [ + "Use Black Point Compensation", + "Use Black Point Compensation", + "Use Black Point Compensation", + "Use Black Point Compensation", + "使用黑场补偿", + ], + ), + ( + "Use Typographer's Quotes", + ["Use Typographer's Quotes", "Use Typographer's Quotes", "Use Typographer's Quotes", "Use Typographer's Quotes", "使用排版引号"], + ), + ("Whole word", ["Whole word", "Whole word", "Whole word", "Whole word", "全字匹配"]), + ("Interface", ["Interface", "Interface", "Interface", "Interface", "界面"]), + ("Advanced Type", ["Advanced Type", "Advanced Type", "Advanced Type", "Advanced Type", "高级文字"]), + ("Composition", ["Composition", "Composition", "Composition", "Composition", "排版"]), + ("Units & Increments", ["Units & Increments", "Units & Increments", "Units & Increments", "Units & Increments", "单位与增量"]), + ("Grids", ["Grids", "Grids", "Grids", "Grids", "网格"]), + ("Guides & Pasteboard", ["Guides & Pasteboard", "Guides & Pasteboard", "Guides & Pasteboard", "Guides & Pasteboard", "参考线与粘贴板"]), + ("Dictionary", ["Dictionary", "Dictionary", "Dictionary", "Dictionary", "词典"]), + ("Autocorrect", ["Autocorrect", "Autocorrect", "Autocorrect", "Autocorrect", "自动更正"]), + ("Story Editor Display", ["Story Editor Display", "Story Editor Display", "Story Editor Display", "Story Editor Display", "文章编辑器显示"]), + ("File Handling", ["File Handling", "File Handling", "File Handling", "File Handling", "文件处理"]), + ( + "Formatting affects container (J)", + [ + "Formatting affects container (J)", + "Formatting affects container (J)", + "Formatting affects container (J)", + "Formatting affects container (J)", + "格式应用于容器 (J)", + ], + ), + ( + "Formatting affects text (J)", + [ + "Formatting affects text (J)", + "Formatting affects text (J)", + "Formatting affects text (J)", + "Formatting affects text (J)", + "格式应用于文字 (J)", + ], + ), + ("Apply None", ["Apply None", "Apply None", "Apply None", "Apply None", "应用无色"]), + ("Height Only", ["Height Only", "Height Only", "Height Only", "Height Only", "仅高度"]), + ("Width Only", ["Width Only", "Width Only", "Width Only", "Width Only", "仅宽度"]), + ("Height and Width", ["Height and Width", "Height and Width", "Height and Width", "Height and Width", "高度和宽度"]), + ("Height only", ["Height only", "Height only", "Height only", "Height only", "仅高度"]), + ("Width only", ["Width only", "Width only", "Width only", "Width only", "仅宽度"]), + ("Height and width", ["Height and width", "Height and width", "Height and width", "Height and width", "高度和宽度"]), + ("Version", ["Version", "Version", "Version", "Version", "版本"]), + ( + "{count} characters don't fit", + [ + "{count} characters don't fit", + "{count} characters don't fit", + "{count} characters don't fit", + "{count} characters don't fit", + "有 {count} 个字符溢出", + ], + ), + ("document", ["document", "document", "document", "document", "文档"]), + ("story", ["story", "story", "story", "story", "文章"]), + ("selection", ["selection", "selection", "selection", "selection", "选区"]), + ("top", ["top", "top", "top", "top", "顶部"]), + ("center", ["center", "center", "center", "center", "居中"]), + ("bottom", ["bottom", "bottom", "bottom", "bottom", "底部"]), + ("justify", ["justify", "justify", "justify", "justify", "两端对齐"]), + ("text", ["text", "text", "text", "text", "文字"]), + ("graphic", ["graphic", "graphic", "graphic", "graphic", "图形"]), + ("unassigned", ["unassigned", "unassigned", "unassigned", "unassigned", "未指定"]), + ("Perceptual", ["Perceptual", "Perceptual", "Perceptual", "Perceptual", "可感知"]), + ("Relative Colorimetric", ["Relative Colorimetric", "Relative Colorimetric", "Relative Colorimetric", "Relative Colorimetric", "相对比色"]), + ("Saturation", ["Saturation", "Saturation", "Saturation", "Saturation", "饱和度"]), + ("Absolute Colorimetric", ["Absolute Colorimetric", "Absolute Colorimetric", "Absolute Colorimetric", "Absolute Colorimetric", "绝对比色"]), + ("No conflicts", ["No conflicts", "No conflicts", "No conflicts", "No conflicts", "没有冲突"]), + ("Page (1–{count}):", ["Page (1–{count}):", "Page (1–{count}):", "Page (1–{count}):", "Page (1–{count}):", "页码 (1–{count}):"]), + ( + "Fonts in Document: {count} Missing: {missing}", + [ + "Fonts in Document: {count} Missing: {missing}", + "Fonts in Document: {count} Missing: {missing}", + "Fonts in Document: {count} Missing: {missing}", + "Fonts in Document: {count} Missing: {missing}", + "文档中的字体:{count} 缺失:{missing}", + ], + ), ]; /// Interface direction; independent of document binding and paragraph direction. @@ -1816,6 +2626,22 @@ pub fn tr<'a>(lang: &str, s: &'a str) -> &'a str { } } +/// Localize reserved built-in style names only; user-defined names are document data. +pub fn style_name<'a>(lang: &str, name: &'a str) -> &'a str { + match name { + designcraft_doc::NO_PARA_STYLE | designcraft_doc::BASIC_PARAGRAPH | designcraft_doc::NO_CHAR_STYLE => tr(lang, name), + _ => name, + } +} + +/// Built-in workspace captions; custom workspace names remain unchanged. +pub fn workspace_name<'a>(lang: &str, name: &'a str) -> &'a str { + match name { + "Essentials" | "Advanced" | "Book" | "Digital Publishing" | "Interactive for PDF" | "Printing and Proofing" | "Typography" => tr(lang, name), + _ => name, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ui-egui/src/i18n/ar.rs b/crates/ui-egui/src/i18n/ar.rs index 0abfc06da..03f97a62b 100644 --- a/crates/ui-egui/src/i18n/ar.rs +++ b/crates/ui-egui/src/i18n/ar.rs @@ -273,4 +273,740 @@ pub(super) const TABLE: &[(&str, &str)] = &[ ("Options", "خيارات"), ("More Options", "خيارات إضافية"), ("Dialog", "مربع حوار"), + ("General", "عام"), + ("Basic Character Formats", "تنسيقات الأحرف الأساسية"), + ("Indents and Spacing", "المسافات البادئة والتباعد"), + ("Justification", "الضبط"), + ("Drop Caps and Nested Styles", "الأحرف الاستهلالية والأنماط المتداخلة"), + ("GREP Style", "نمط GREP"), + ("Character Color", "لون الأحرف"), + ("Export Tagging", "وسوم التصدير"), + ("Numbering and Formatting", "الترقيم والتنسيق"), + ("Spacing Options", "خيارات التباعد"), + ("First Baseline", "خط الأساس الأول"), + ("Rule Above", "خط علوي"), + ("Numbering", "الترقيم"), + ("Footnote Reference Number in Text", "رقم مرجع الحاشية في النص"), + ("Footnote Formatting", "تنسيق الحواشي"), + ("Span Footnotes Across Columns", "امتداد الحواشي عبر الأعمدة"), + ("Rule On", "إظهار الخط"), + ("Ascent", "ارتفاع الحروف الصاعدة"), + ("Cap Height", "ارتفاع الأحرف الكبيرة"), + ("x Height", "ارتفاع حرف x"), + ("Fixed", "ثابت"), + ("Never (continuous)", "أبدًا (ترقيم متصل)"), + ("Spread", "صفحتان متقابلتان"), + ("Section", "قسم"), + ("Footnote Reference", "مرجع الحاشية"), + ("Footnote Text", "نص الحاشية"), + ("Both", "كلاهما"), + ("Apply Superscript", "تطبيق مرتفع"), + ("Apply Subscript", "تطبيق منخفض"), + ("Apply Normal", "تطبيق عادي"), + ("OpenType Superscript", "مرتفع OpenType"), + ("through", "حتى شاملًا"), + ("up to", "حتى باستثناء"), + ("line", "سطر"), + ("lines", "أسطر"), + ("sentences", "جمل"), + ("words", "كلمات"), + ("characters", "محارف"), + ("letters", "حروف"), + ("digits", "أرقام"), + ("tab", "جدولة"), + ("forcedLineBreak", "فاصل سطر إجباري"), + ("emSpace", "مسافة em"), + ("enSpace", "مسافة en"), + ("chars", "محارف محددة"), + ("[Automatic]", "[تلقائي]"), + ("Size:", "الحجم:"), + ("Tracking:", "التباعد:"), + ("Left Indent:", "المسافة البادئة اليسرى:"), + ("Right Indent:", "المسافة البادئة اليمنى:"), + ("First Line Indent:", "المسافة البادئة للسطر الأول:"), + ("Space Before:", "المسافة قبل الفقرة:"), + ("Space After:", "المسافة بعد الفقرة:"), + ("Words with at Least:", "الحد الأدنى لطول الكلمة:"), + ("After First:", "الأحرف قبل الواصلة:"), + ("Before Last:", "الأحرف بعد الواصلة:"), + ("Hyphen Limit:", "حد الأسطر المتتالية بواصلة:"), + ("Word Spacing:", "تباعد الكلمات:"), + ("Letter Spacing:", "تباعد الحروف:"), + ("Glyph Scaling:", "تحجيم الحروف:"), + ("Insert Kashidas:", "إدراج الكشيدة:"), + ("In justified Arabic text", "في النص العربي المضبوط"), + ("Minimum Space Before First Footnote:", "أقل مسافة قبل الحاشية الأولى:"), + ("Space Between Footnotes:", "المسافة بين الحواشي:"), + ("Offset:", "الإزاحة:"), + ("Min:", "الحد الأدنى:"), + ("Weight:", "السماكة:"), + ("Color:", "اللون:"), + ("Width:", "العرض:"), + ("Style:", "النمط:"), + ("Start at:", "بدء الترقيم من:"), + ("Restart Numbering Every:", "إعادة بدء الترقيم كل:"), + ("Show Prefix/Suffix in:", "إظهار البادئة واللاحقة في:"), + ("Prefix:", "البادئة:"), + ("Suffix:", "اللاحقة:"), + ("Position:", "الموضع:"), + ("Character Style:", "نمط الأحرف:"), + ("Paragraph Style:", "نمط الفقرة:"), + ("Separator:", "الفاصل:"), + ("EPUB and HTML", "EPUB وHTML"), + ("Tag:", "الوسم:"), + ("Class:", "الفئة:"), + ("Nested Styles", "الأنماط المتداخلة"), + ("Nested Line Styles", "أنماط الأسطر المتداخلة"), + ("New Nested Style", "نمط متداخل جديد"), + ("New Line Style", "نمط سطر جديد"), + ("GREP Styles", "أنماط GREP"), + ("Apply Style:", "تطبيق النمط:"), + ("To Text:", "على النص:"), + ("New GREP Style", "نمط GREP جديد"), + ("Font Family:", "عائلة الخط:"), + ("Font Style:", "نمط الخط:"), + ("Leading:", "تباعد الأسطر:"), + ("Alignment:", "المحاذاة:"), + ("Hyphenate", "إضافة واصلات"), + ("Minimum", "الحد الأدنى"), + ("Desired", "المطلوب"), + ("Maximum", "الحد الأقصى"), + ("Composer:", "منضّد النص:"), + ("Paragraph Composer", "منضّد الفقرات"), + ("Single-line Composer", "منضّد السطر الواحد"), + ("Style Name:", "اسم النمط:"), + ("Based On:", "استنادًا إلى:"), + ("for", "لعدد"), + ("Hyphenation Exceptions", "استثناءات الواصلة"), + ("Binding:", "اتجاه التجليد:"), + ("Dynamic Spelling", "التدقيق الإملائي التلقائي"), + ("UI Scaling", "تحجيم الواجهة"), + ("Ruby:", "روبي:"), + ("Set over the selected text; empty removes it.", "تُضاف فوق النص المحدد؛ اتركها فارغة للإزالة."), + ("Document Footnote Options…", "خيارات حواشي المستند…"), + ("Insert Footnote", "إدراج حاشية"), + ("Go to Footnote Reference", "الانتقال إلى مرجع الحاشية"), + ("Footnotes", "الحواشي"), + ("Paragraph Style Options…", "خيارات نمط الفقرة…"), + ("New Paragraph Style…", "نمط فقرة جديد…"), + ("New Character Style…", "نمط أحرف جديد…"), + ("[Basic Paragraph]", "[فقرة أساسية]"), + ("[No Paragraph Style]", "[بلا نمط فقرة]"), + ("Align left", "محاذاة إلى اليسار"), + ("Align center", "محاذاة إلى الوسط"), + ("Align right", "محاذاة إلى اليمين"), + ("Justify with last line aligned left", "ضبط مع محاذاة السطر الأخير إلى اليسار"), + ("Justify with last line aligned center", "ضبط مع توسيط السطر الأخير"), + ("Justify with last line aligned right", "ضبط مع محاذاة السطر الأخير إلى اليمين"), + ("Justify all lines", "ضبط جميع الأسطر"), + ("Align towards spine", "محاذاة نحو الكعب"), + ("Align away from spine", "محاذاة بعيدًا عن الكعب"), + ("Create New Style from Selection", "إنشاء نمط جديد من التحديد"), + ("No text selected", "لم يتم تحديد نص"), + ("Essentials", "الأساسيات"), + ("Advanced", "متقدم"), + ("Book", "كتاب"), + ("Digital Publishing", "النشر الرقمي"), + ("Interactive for PDF", "التفاعل في PDF"), + ("Printing and Proofing", "الطباعة والتدقيق"), + ("Typography", "تنضيد النصوص"), + ("No errors", "لا توجد أخطاء"), + ("error", "خطأ"), + ("errors", "أخطاء"), + ("Display All Blacks as Rich Black", "عرض كل درجات الأسود كأسود غني"), + ("Display All Blacks Accurately", "عرض كل درجات الأسود بدقة"), + ("Output All Blacks as Rich Black", "إخراج كل درجات الأسود كأسود غني"), + ("Output All Blacks Accurately", "إخراج كل درجات الأسود بدقة"), + ("Top Margin", "الهامش العلوي"), + ("Top of Page", "أعلى الصفحة"), + ("Paragraph Styles panel", "لوحة أنماط الفقرات"), + ("Character Styles panel", "لوحة أنماط الأحرف"), + ("Create new style", "إنشاء نمط جديد"), + ("Clear overrides", "مسح التجاوزات"), + ("Accept", "قبول"), + ("Reject", "رفض"), + ("Add Selection", "إضافة التحديد"), + ("Add the selection", "إضافة التحديد"), + ("Add to Swatches", "إضافة إلى الحوامل"), + ("Align Stroke:", "محاذاة الحد:"), + ("All Caps", "أحرف كبيرة"), + ("Angle", "الزاوية"), + ("Angle:", "الزاوية:"), + ("Baseline", "خط الأساس"), + ("Cap:", "نهاية الخط:"), + ("Cell Text", "نص الخلية"), + ("Cell Type", "نوع الخلية"), + ("Column Width", "عرض العمود"), + ("Composer", "منضّد النص"), + ("Convert Selection to Multi-State Object", "تحويل التحديد إلى كائن متعدد الحالات"), + ("Convert to Text", "تحويل إلى نص"), + ("Create New Layer", "إنشاء طبقة جديدة"), + ("Default", "افتراضي"), + ("Delete DTD", "حذف DTD"), + ("Delete Layer", "حذف الطبقة"), + ("Delete Page", "حذف الصفحة"), + ("Digits", "الأرقام"), + ("Distance", "المسافة"), + ("Distribute ↔", "توزيع أفقي"), + ("Distribute ↕", "توزيع عمودي"), + ("Drop Shadow", "ظل ساقط"), + ("Effects: Drop Shadow", "التأثيرات: ظل ساقط"), + ("Favorites", "المفضلة"), + ("First Spread", "أول صفحتين متقابلتين"), + ("Frame Fitting", "ملاءمة الإطار"), + ("Gap Color:", "لون الفجوة:"), + ("Go To", "انتقال إلى"), + ("Graphic…", "رسم…"), + ("Home", "الرئيسية"), + ("Hyperlink from the selected text or frames", "إنشاء رابط من النص أو الإطارات المحددة"), + ("Include when exporting", "تضمين عند التصدير"), + ("Join:", "وصلة الخط:"), + ("Last Spread", "آخر صفحتين متقابلتين"), + ("Linked Stories", "النصوص المرتبطة"), + ("Load DTD…", "تحميل DTD…"), + ("Location:", "الموضع:"), + ("Miter Limit:", "حد الوصلة الحادة:"), + ("Move", "نقل"), + ("New Article from Selection", "مقال جديد من التحديد"), + ("New Note at Cursor", "ملاحظة جديدة عند المؤشر"), + ("New condition name", "اسم الشرط الجديد"), + ("New group…", "مجموعة جديدة…"), + ("New tag", "وسم جديد"), + ("Next Spread", "الصفحتان التاليتان"), + ("Note text", "نص الملاحظة"), + ("Offset", "الإزاحة"), + ("Offsets", "الإزاحات"), + ("Open", "فتح"), + ("Paragraph alignment", "محاذاة الفقرة"), + ("Pick a file in the folder holding the moved graphics", "اختر ملفًا من المجلد الذي يحتوي على الرسومات المنقولة"), + ("Place", "إدراج"), + ("Previous Spread", "الصفحتان السابقتان"), + ("Release to Objects", "تحرير إلى كائنات"), + ("Reverse the gradient", "عكس التدرج"), + ("Rotate 90° Counterclockwise", "تدوير 90° عكس عقارب الساعة"), + ("Rotation Angle", "زاوية الدوران"), + ("Row Height", "ارتفاع الصف"), + ("Run", "تشغيل"), + ("Scale X Percentage", "نسبة التحجيم الأفقي"), + ("Scale Y Percentage", "نسبة التحجيم العمودي"), + ("Search commands", "البحث عن الأوامر"), + ("Search fonts", "البحث عن الخطوط"), + ("Search styles and commands…", "البحث عن الأنماط والأوامر…"), + ("Search: character or U+code", "البحث بحرف أو رمز U+"), + ("Separated by commas", "مفصولة بفواصل"), + ("Share", "مشاركة"), + ("Shear X Angle", "زاوية الميل الأفقي"), + ("Show / hide text with this condition", "إظهار أو إخفاء النص بهذا الشرط"), + ("Show Favorites Only", "إظهار المفضلة فقط"), + ("Show in the text", "إظهار في النص"), + ("Spreads of up to 10 pages", "مجموعات تصل إلى 10 صفحات"), + ("Stop Color:", "لون نقطة التدرج:"), + ("Strikethrough", "يتوسطه خط"), + ("Structure", "البنية"), + ("Style", "النمط"), + ("Style source", "مصدر الأنماط"), + ("Styles panel", "لوحة الأنماط"), + ("Superscript", "مرتفع"), + ("Tag the selection", "إضافة وسم للتحديد"), + ("The structure is valid.", "البنية صالحة."), + ("Toggle double column", "تبديل التخطيط بعمودين"), + ("Type:", "النوع:"), + ("Underline", "تسطير"), + ("Untag Selection", "إزالة الوسم عن التحديد"), + ("Update", "تحديث"), + ("Validate", "تحقق"), + ("Weight", "السماكة"), + ("Subtract", "طرح"), + ("Intersect", "تقاطع"), + ("Exclude Overlap", "استبعاد التداخل"), + ("Minus Back", "طرح الخلفية"), + ("Make Compound", "إنشاء مسار مركب"), + ("Release", "تحرير"), + ("Paths", "مسارات"), + ("Align left edges", "محاذاة الحواف اليسرى"), + ("Align horizontal centers", "محاذاة المراكز أفقيًا"), + ("Align right edges", "محاذاة الحواف اليمنى"), + ("Align top edges", "محاذاة الحواف العليا"), + ("Align vertical centers", "محاذاة المراكز عموديًا"), + ("Align bottom edges", "محاذاة الحواف السفلى"), + ("Align to Selection", "محاذاة إلى التحديد"), + ("Align to Key Object", "محاذاة إلى الكائن الرئيسي"), + ("Align to Margins", "محاذاة إلى الهوامش"), + ("Align to Page", "محاذاة إلى الصفحة"), + ("Align to Spread", "محاذاة إلى الصفحات المتقابلة"), + ("No Text Wrap", "بلا التفاف للنص"), + ("Wrap Around Bounding Box", "التفاف حول المربع المحيط"), + ("Wrap Around Object Shape", "التفاف حول شكل الكائن"), + ("Jump Object", "تجاوز الكائن"), + ("Jump to Next Column", "انتقال إلى العمود التالي"), + ("Bulleted List", "قائمة نقطية"), + ("Numbered List", "قائمة مرقمة"), + ("UPPERCASE", "أحرف كبيرة"), + ("Small Caps", "أحرف كبيرة صغيرة الحجم"), + ("Butt", "مسطحة"), + ("Round", "مستديرة"), + ("Projecting", "بارزة"), + ("Miter", "حادة"), + ("Bevel", "مشطوفة"), + ("Center", "وسط"), + ("Start/End:", "البداية/النهاية:"), + ("First", "الأول"), + ("Last", "الأخير"), + ("Before", "قبل"), + ("After", "بعد"), + ("Shading", "تظليل"), + ("Border", "إطار"), + ("Overprint Fill", "طباعة التعبئة فوقيًا"), + ("Overprint Stroke", "طباعة الحد فوقيًا"), + ("Overprint Gap", "طباعة الفجوة فوقيًا"), + ("Nonprinting", "غير قابل للطباعة"), + ("At Least", "على الأقل"), + ("Exactly", "بالضبط"), + ("Justify", "ضبط"), + ("Underline Options", "خيارات التسطير"), + ("Strikethrough Options", "خيارات الخط المتوسط"), + ("Discretionary Ligatures", "روابط اختيارية"), + ("Fractions", "كسور"), + ("Ordinal", "ترتيبي"), + ("Swash", "زخرفي"), + ("Titling Alternates", "بدائل العناوين"), + ("Contextual Alternates", "بدائل سياقية"), + ("Slashed Zero", "صفر مشطوب"), + ("Tabular Lining", "أرقام منتظمة ثابتة العرض"), + ("Proportional Oldstyle", "أرقام قديمة متناسبة"), + ("Proportional Lining", "أرقام منتظمة متناسبة"), + ("Tabular Oldstyle", "أرقام قديمة ثابتة العرض"), + ("Default Figure Style", "نمط الأرقام الافتراضي"), + ("Not a button", "ليس زرًا"), + ("Go To Next Page", "انتقال إلى الصفحة التالية"), + ("Go To Previous Page", "انتقال إلى الصفحة السابقة"), + ("Go To First Page", "انتقال إلى الصفحة الأولى"), + ("Go To Last Page", "انتقال إلى الصفحة الأخيرة"), + ("Go To Page", "انتقال إلى صفحة"), + ("Go To URL", "انتقال إلى عنوان URL"), + ("Not a form field", "ليس حقل نموذج"), + ("Text Field", "حقل نص"), + ("Check Box", "خانة اختيار"), + ("Radio Button", "زر اختيار"), + ("Combo Box", "مربع تحرير وسرد"), + ("List Box", "مربع قائمة"), + ("Signature", "توقيع"), + ("Resize width", "تغيير العرض"), + ("Resize height", "تغيير الارتفاع"), + ("Pin top", "تثبيت الأعلى"), + ("Pin bottom", "تثبيت الأسفل"), + ("Pin left", "تثبيت اليسار"), + ("Pin right", "تثبيت اليمين"), + ("Inward", "إلى الداخل"), + ("Metrics", "مقاييس"), + ("Optical", "بصري"), + ("Applies to fill", "يُطبّق على التعبئة"), + ("Applies to text", "يُطبّق على النص"), + ("Gray", "رمادي"), + ("Linear", "خطي"), + ("Radial", "شعاعي"), + ("Arabic", "العربية"), + ("Beveled Rectangle", "مستطيل مشطوف"), + ("Chinese", "الصينية"), + ("Constrain proportions for width and height", "تقييد نسب العرض والارتفاع"), + ("Custom", "مخصص"), + ("Czech", "التشيكية"), + ("Danish", "الدنماركية"), + ("Dashed", "متقطع"), + ("Dotted", "منقط"), + ("Dutch: 2005 Reform", "الهولندية: إصلاح 2005"), + ("Embedded", "مضمّن"), + ("English: Canadian", "الإنجليزية: كندا"), + ("English: UK", "الإنجليزية: المملكة المتحدة"), + ("English: USA", "الإنجليزية: الولايات المتحدة"), + ("Farsi", "الفارسية"), + ("Finnish", "الفنلندية"), + ("French", "الفرنسية"), + ("French: Canadian", "الفرنسية: كندا"), + ("German: 2006 Reform", "الألمانية: إصلاح 2006"), + ("German: Swiss 2006 Reform", "الألمانية: الإصلاح السويسري 2006"), + ("Greek", "اليونانية"), + ("Hebrew", "العبرية"), + ("Hindi", "الهندية"), + ("Hungarian", "المجرية"), + ("Inverse Rounded Rectangle", "مستطيل بزوايا دائرية معكوسة"), + ("Italian", "الإيطالية"), + ("Japanese", "اليابانية"), + ("Korean", "الكورية"), + ("Make all settings the same", "توحيد جميع الإعدادات"), + ("Native", "أرقام محلية"), + ("Norwegian: Bokmål", "النرويجية: بوكمول"), + ("Orthogonal Line", "خط متعامد"), + ("Polish", "البولندية"), + ("Portuguese", "البرتغالية"), + ("Portuguese: Brazilian", "البرتغالية: البرازيل"), + ("Rounded Rectangle", "مستطيل مستدير الزوايا"), + ("Russian", "الروسية"), + ("Solid", "متصل"), + ("Spanish", "الإسبانية"), + ("Swedish", "السويدية"), + ("Thick - Thick", "سميك - سميك"), + ("Thick - Thin", "سميك - رفيع"), + ("Thin - Thick", "رفيع - سميك"), + ("Thin - Thin", "رفيع - رفيع"), + ("Thin - Thick - Thin", "رفيع - سميك - رفيع"), + ("Thick - Thin - Thick", "سميك - رفيع - سميك"), + ("Wavy", "متموج"), + ("Straight Hash", "شرطات مائلة"), + ("Stripes", "خطوط"), + ("Triangle", "مثلث"), + ("Turkish", "التركية"), + ("Ukrainian", "الأوكرانية"), + ("Cell Style", "نمط الخلية"), + ("Table Style", "نمط الجدول"), + ("Col ←", "إدراج عمود يسارًا"), + ("Col →", "إدراج عمود يمينًا"), + ("Row ↑", "إدراج صف أعلى"), + ("Row ↓", "إدراج صف أسفل"), + ("Hide Others", "إخفاء الطبقات الأخرى"), + ("Lock Others", "قفل الطبقات الأخرى"), + ("Show All Layers", "إظهار كل الطبقات"), + ("Unlock", "إلغاء القفل"), + ("Unlock All Layers", "إلغاء قفل كل الطبقات"), + ("Added", "مضاف"), + ("Deleted", "محذوف"), + ("Guide-based", "استنادًا إلى الأدلة"), + ("Loop", "تكرار"), + ("Object-based", "استنادًا إلى الكائنات"), + ("Play on Page Load", "تشغيل عند تحميل الصفحة"), + ("Re-center", "إعادة التوسيط"), + ("Show Controls", "إظهار عناصر التحكم"), + ("Signature Field", "حقل توقيع"), + ("Select text to apply a condition", "حدد نصًا لتطبيق شرط"), + ("Blinds", "ستائر"), + ("Box", "مربع"), + ("Comb", "مشط"), + ("Cover", "تغطية"), + ("Dissolve", "تلاشي متناثر"), + ("Fade", "تلاشي"), + ("Push", "دفع"), + ("Split", "انقسام"), + ("Uncover", "كشف"), + ("Wipe", "مسح"), + ("Simple", "بسيط"), + ("Simple - Wide", "بسيط - عريض"), + ("Triangle - Wide", "مثلث - عريض"), + ("Barbed", "مسنن"), + ("Curved", "منحني"), + ("Circle", "دائرة"), + ("Circle - Solid", "دائرة مصمتة"), + ("Square", "مربع"), + ("Square - Solid", "مربع مصمت"), + ("Bar", "شرطة"), + ("Hide", "إخفاء"), + ("Show", "إظهار"), + ("Select", "تحديد"), + ("Fit Text", "ملاءمة النص"), + ("Preview (W)", "معاينة (W)"), + ("Normal (W)", "عادي (W)"), + ("Conveyor", "ناقل المحتوى"), + ("objects", "كائنات"), + ("pages", "صفحات"), + ("stories", "نصوص"), + ("items", "عناصر"), + ("warnings", "تحذيرات"), + ("Render", "تصيير"), + ("Set", "مجموعة"), + ("unused", "غير مستخدم"), + ("⚠ Missing", "⚠ مفقود"), + ("▲ Modified", "▲ معدّل"), + ("(Text Color)", "(لون النص)"), + ("{count} glyphs — click to insert at the text cursor", "عدد المحارف: {count} — انقر للإدراج عند مؤشر النص"), + ("{pages} Pages in {spreads} Spreads", "عدد الصفحات: {pages}، المجموعات: {spreads}"), + ("Text Frame", "إطار نص"), + ("Text Style", "نمط النص"), + (" (auto)", " (تلقائي)"), + ("Absolute Numbering (instead of Section Numbering)", "ترقيم مطلق بدلًا من ترقيم الأقسام"), + ("Add and remove pages as the primary text frame's story grows and shrinks", "إضافة الصفحات وحذفها مع زيادة نص الإطار الرئيسي أو نقصانه"), + ("Added Text (highlighted)", "النص المضاف (مميز)"), + ("Adjust Layout (objects follow the new page size)", "ضبط التخطيط وفق حجم الصفحة الجديد"), + ("Auto-Fit", "ملاءمة تلقائية"), + ("Case sensitive", "مراعاة حالة الأحرف"), + ("Custom Tracking/Kerning", "تباعد أحرف وأزواج مخصص"), + ("Dimensions Include Stroke Weight", "تضمين سماكة الحد في الأبعاد"), + ("Enable Autocorrect", "تمكين التصحيح التلقائي"), + ("Enable Dynamic Spelling (underline misspelled words)", "تمكين التدقيق الإملائي التلقائي (تسطير الأخطاء)"), + ("Grids in Back", "الشبكات في الخلف"), + ("H&J Violations", "مخالفات الواصلة والضبط"), + ("Include Bleed", "تضمين النزف"), + ("Include Stroke Weight", "تضمين سماكة الحد"), + ("Keep Violations", "مخالفات خيارات إبقاء النص"), + ("Overprint [Black] Swatch at 100%", "طباعة حامل الأسود بنسبة 100% فوقيًا"), + ("Printer's Marks", "علامات الطباعة"), + ("Remove Styles and Formatting from Text and Tables", "إزالة الأنماط والتنسيق من النص والجداول"), + ("Ruby", "روبي"), + ("Show Note Anchors in Layout View", "إظهار مراسي الملاحظات في عرض التخطيط"), + ("Spreads", "صفحات متقابلة"), + ("Substituted Fonts", "الخطوط المستبدلة"), + ("Transformations are Totals", "عرض قيم التحويل الإجمالية"), + ("Use Black Point Compensation", "استخدام تعويض النقطة السوداء"), + ("Use Typographer's Quotes", "استخدام علامات الاقتباس الطباعية"), + ("Whole word", "كلمة كاملة"), + (" Close ", " إغلاق "), + ("(Top Level)", "(المستوى الأعلى)"), + ("(import)", "(استيراد)"), + ("Accept All", "قبول الكل"), + ("Add Document…", "إضافة مستند…"), + ("Add to Previous Spread", "إضافة إلى الصفحات المتقابلة السابقة"), + ("Align From:", "المحاذاة من:"), + ("Align to baseline grid", "محاذاة إلى شبكة خطوط الأساس"), + ("All Spots to Process", "تحويل كل الألوان الموضعية إلى ألوان معالجة"), + ("Allow Spread to Shuffle", "السماح بإعادة ترتيب الصفحات المتقابلة"), + ("Alternating Row Fills", "تعبئة الصفوف بالتناوب"), + ("Apply Gradient", "تطبيق تدرج"), + ("Apply None (/)", "تطبيق بلا لون (/)"), + ("Apply Parent", "تطبيق الصفحة الأصلية"), + ("Apply to All Spreads", "تطبيق على كل الصفحات المتقابلة"), + ("Apply to Stroke", "تطبيق على الحد"), + ("Art", "حدود الرسم"), + ("Baseline Grid", "شبكة خطوط الأساس"), + ("Bleed and Slug", "النزف ومنطقة المعلومات"), + ("Body Rows", "صفوف المتن"), + ("Break Link to Style", "فك الارتباط بالنمط"), + ("Bullets and Numbering", "التعداد النقطي والرقمي"), + ("Business Card", "بطاقة عمل"), + ("Buttons act in interactive PDF export.", "تعمل الأزرار عند التصدير إلى PDF تفاعلي."), + ("Change to:", "التغيير إلى:"), + ("Character Settings", "إعدادات الأحرف"), + ("Characters", "أحرف"), + ("Checked by default", "محدد افتراضيًا"), + ("Choices", "الخيارات"), + ("Choose Image…", "اختيار صورة…"), + ("Choose a link.", "اختر رابطًا."), + ("Content Fitting", "ملاءمة المحتوى"), + ("Convert Shape", "تحويل الشكل"), + ("Copies:", "عدد النسخ:"), + ("Count", "عدّ"), + ("Create", "إنشاء"), + ("Create Alternate Layout", "إنشاء تخطيط بديل"), + ("Create Table…", "إنشاء جدول…"), + ("Crop", "قص"), + ("Crop Amount", "مقدار القص"), + ("Crop to:", "القص إلى:"), + ("Cross-Reference Format", "تنسيق المرجع المتقاطع"), + ("Cursor Key:", "مفتاح المؤشر:"), + ("Default Fill and Stroke (D)", "التعبئة والحد الافتراضيان (D)"), + ("Default View:", "العرض الافتراضي:"), + ("Delete Column", "حذف العمود"), + ("Delete Condition", "حذف الشرط"), + ("Delete Row", "حذف الصف"), + ("Delete Swatch", "حذف حامل اللون"), + ("Delete Unused Layers", "حذف الطبقات غير المستخدمة"), + ("Delete Workspace", "حذف مساحة العمل"), + ("Document Grid", "شبكة المستند"), + ("Document Recovery Data", "بيانات استرداد المستند"), + ("Duplicate Spread", "تكرار الصفحات المتقابلة"), + ("Email", "بريد إلكتروني"), + ("Embed", "تضمين"), + ("Fast", "سريع"), + ("Fill (X)", "تعبئة (X)"), + ("Fill with Placeholder Text", "ملء بنص مؤقت"), + ("Find All", "بحث عن الكل"), + ("Find what:", "البحث عن:"), + ("Fit frame height to text", "ملاءمة ارتفاع الإطار للنص"), + ("Fitting:", "الملاءمة:"), + ("Flip Horizontal", "قلب أفقي"), + ("Flip Vertical", "قلب عمودي"), + ("Font Size:", "حجم الخط:"), + ("Form Field", "حقل نموذج"), + ("Format:", "التنسيق:"), + ("Height:", "الارتفاع:"), + ("Hide Bar", "إخفاء الشريط"), + ("High Quality", "جودة عالية"), + ("Highlight", "تمييز"), + ("Horizontal Gridline Every:", "تباعد خطوط الشبكة الأفقية:"), + ("Horizontal Margins:", "الهوامش الأفقية:"), + ("Import XML…", "استيراد XML…"), + ("Increment Every:", "مقدار الزيادة:"), + ("Ink Manager", "مدير الأحبار"), + ("Insert Column", "إدراج عمود"), + ("Insert Page After", "إدراج صفحة بعدها"), + ("Insert Row", "إدراج صف"), + ("Inset spacing", "التباعد الداخلي"), + ("Intent:", "الغرض:"), + ("Invert", "عكس"), + ("Keyboard Increments", "زيادات لوحة المفاتيح"), + ("Kind:", "النوع:"), + ("Link To:", "الربط إلى:"), + ("Liquid guides", "أدلة التخطيط المرن"), + ("Load Profile…", "تحميل ملف تعريف…"), + ("Load Swatches…", "تحميل حوامل الألوان…"), + ("Load…", "تحميل…"), + ("Media", "وسائط"), + ("Merge into Active Layer", "دمج في الطبقة النشطة"), + ("Misspelled word → Correction (one per line):", "كلمة خاطئة ← تصحيح (واحد في كل سطر):"), + ("Mobile", "جهاز محمول"), + ("More ArtCraft apps", "المزيد من تطبيقات ArtCraft"), + ("Move Selection Here", "نقل التحديد إلى هنا"), + ("Move to Color Group", "نقل إلى مجموعة ألوان"), + ("Move to Group", "نقل إلى مجموعة"), + ("Multiline", "متعدد الأسطر"), + ("Multiple Objects", "كائنات متعددة"), + ("Name:", "الاسم:"), + ("New Bookmark for This Page", "إشارة مرجعية جديدة لهذه الصفحة"), + ("New Book…", "كتاب جديد…"), + ("New Color Group", "مجموعة ألوان جديدة"), + ("New Color Group with Swatch", "مجموعة ألوان جديدة من الحامل"), + ("New Library…", "مكتبة جديدة…"), + ("New Workspace…", "مساحة عمل جديدة…"), + ("No articles: exports follow page order.", "لا توجد مقالات؛ يتبع التصدير ترتيب الصفحات."), + ("No book open.", "لا يوجد كتاب مفتوح."), + ("No bookmarks. They become the PDF outline.", "لا توجد إشارات مرجعية. تظهر في مخطط PDF."), + ("No changes.", "لا توجد تغييرات."), + ("No conditions. Create one, then select text and click it to apply.", "لا توجد شروط. أنشئ شرطًا ثم حدد النص وانقر عليه لتطبيقه."), + ("No document.", "لا يوجد مستند."), + ("No hyperlinks. Select text or a frame, type a URL and click New.", "لا توجد روابط. حدد نصًا أو إطارًا وأدخل عنوان URL ثم انقر على جديد."), + ("No library open.", "لا توجد مكتبة مفتوحة."), + ("No notes in this document.", "لا توجد ملاحظات في هذا المستند."), + ("No placed graphics.", "لا توجد رسومات مدرجة."), + ("No scripts. Add one below.", "لا توجد نصوص برمجية. أضف واحدًا أدناه."), + ("No spot inks", "لا توجد أحبار موضعية"), + ("No text anchors in this document.", "لا توجد مراسي نصية في هذا المستند."), + ("Number of Pages:", "عدد الصفحات:"), + ("Number of Sides:", "عدد الأضلاع:"), + ("Number of columns", "عدد الأعمدة"), + ("OK changes all: text and paragraph/character styles.", "تطبّق الموافقة التغييرات على النص وأنماط الفقرات والأحرف."), + ("OVERSET", "نص فائض"), + ("On Release", "عند الإفلات"), + ("On Screen:", "على الشاشة:"), + ("Open source. No telemetry.", "مفتوح المصدر. بلا تتبع للاستخدام."), + ("Options for Black on RGB and Grayscale Devices", "خيارات الأسود لأجهزة RGB والتدرج الرمادي"), + ("Orientation:", "الاتجاه:"), + ("Page Numbering", "ترقيم الصفحات"), + ("Page · PPI", "صفحة · PPI"), + ("Pages:", "الصفحات:"), + ("Part of the ArtCraft suite of open creative tools.", "جزء من مجموعة ArtCraft للأدوات الإبداعية المفتوحة."), + ("Pasteboard Options", "خيارات مساحة اللصق"), + ("Path", "مسار"), + ("Place the insertion point in a table, or create one.", "ضع نقطة الإدراج داخل جدول أو أنشئ جدولًا."), + ("Plain Text", "نص عادي"), + ("Plays in EPUB export; print and PDF show the poster.", "يعمل في تصدير EPUB؛ وتعرض الطباعة وPDF صورة الغلاف."), + ("Poster", "صورة الغلاف"), + ("Printer:", "الطابعة:"), + ("Printing / Exporting:", "الطباعة / التصدير:"), + ("Properties Panel", "لوحة الخصائص"), + ("Recently Used", "المستخدمة مؤخرًا"), + ("Reject All", "رفض الكل"), + ("Relative To:", "بالنسبة إلى:"), + ("Relink to Folder…", "إعادة الربط بمجلد…"), + ("Relink…", "إعادة الربط…"), + ("Remove Conditions from Selection", "إزالة الشروط عن التحديد"), + ("Repeat Header Rows", "تكرار صفوف الرأس"), + ("Replace With", "استبدال بـ"), + ("Required", "مطلوب"), + ("Reset All to Defaults", "إعادة تعيين الكل إلى الافتراضي"), + ("Reverse", "عكس"), + ("Right-click: stroke, groups", "نقرة يمنى: الحد والمجموعات"), + ("Rotate 90° Clockwise", "تدوير 90° مع عقارب الساعة"), + ("Ruler Units", "وحدات المسطرة"), + ("Save Swatches for Exchange…", "حفظ الحوامل للتبادل…"), + ("Save recovery data every:", "حفظ بيانات الاسترداد كل:"), + ("Saves which bars show and where the panels are.", "يحفظ الأشرطة الظاهرة ومواضع اللوحات."), + ("Search:", "البحث:"), + ( + "Select a placed video or sound (File › Place a .mp4, .mov, .mp3, .wav …).", + "حدد فيديو أو صوتًا مدرجًا (ملف › إدراج ملف .mp4 أو .mov أو .mp3 أو .wav …).", + ), + ("Select an object to color its fill or stroke.", "حدد كائنًا لتلوين تعبئته أو حده."), + ("Select an object to give its fill a gradient.", "حدد كائنًا لإضافة تدرج إلى تعبئته."), + ("Select an object to make it a button.", "حدد كائنًا لتحويله إلى زر."), + ("Select an object.", "حدد كائنًا."), + ("Select text or a text frame.", "حدد نصًا أو إطار نص."), + ("Select two or more objects (or a group) to make a multi-state object.", "حدد كائنين أو أكثر (أو مجموعة) لإنشاء كائن متعدد الحالات."), + ("Show Tag Markers", "إظهار علامات الوسوم"), + ("Single-line", "سطر واحد"), + ("Smart Text Reflow", "إعادة تدفق النص الذكية"), + ("Speed", "السرعة"), + ("Star Inset:", "تقعر النجمة:"), + ("Start Page #:", "رقم صفحة البداية:"), + ("Start:", "البداية:"), + ("Status", "الحالة"), + ("Stroke (X)", "حد (X)"), + ("Style Mapping", "مطابقة الأنماط"), + ("Style Name Conflicts", "تعارض أسماء الأنماط"), + ("Stylistic Sets", "المجموعات الأسلوبية"), + ("Subdivisions:", "التقسيمات الفرعية:"), + ("Swap Fill and Stroke (Shift+X)", "تبديل التعبئة والحد (Shift+X)"), + ("Synchronize", "مزامنة"), + ("System default", "افتراضي النظام"), + ("Table Dimensions", "أبعاد الجدول"), + ("Text Display Options", "خيارات عرض النص"), + ("Text Message", "رسالة نصية"), + ("The fill isn't a gradient.", "التعبئة ليست تدرجًا."), + ("The library is empty. Select objects and Add Selection.", "المكتبة فارغة. حدد كائنات ثم أضف التحديد."), + ("This PDF has no layers.", "ملف PDF هذا لا يحتوي على طبقات."), + ("Track Changes in All Stories", "تتبع التغييرات في جميع النصوص"), + ("Transitions play in interactive PDF (full-screen mode).", "تعمل الانتقالات في PDF التفاعلي بوضع ملء الشاشة."), + ("Trim", "حدود التشذيب"), + ("Type Options", "خيارات النص"), + ("Typical", "نموذجي"), + ("UI Size:", "حجم الواجهة:"), + ("Ungroup Color Group", "فك مجموعة الألوان"), + ("Unmerge", "إلغاء الدمج"), + ("Update Numbering", "تحديث الترقيم"), + ("Use Global Light", "استخدام الإضاءة العامة"), + ("User Dictionary (this document)", "قاموس المستخدم (هذا المستند)"), + ("Variable Font", "خط متغير"), + ("Vertical Gridline Every:", "تباعد خطوط الشبكة العمودية:"), + ("Vertical Margins:", "الهوامش العمودية:"), + ("Vertical justification", "المحاذاة العمودية"), + ("View Threshold:", "حد العرض:"), + ("Web", "ويب"), + ("Web Hyperlink", "رابط ويب"), + ("When Scaling", "عند التحجيم"), + ("Words, one per line:", "الكلمات، واحدة في كل سطر:"), + ("[Basic] (working) ▾", "[أساسي] (قيد العمل) ▾"), + ("[Black] Overprint", "طباعة [الأسود] فوقيًا"), + ("[No Group]", "[بلا مجموعة]"), + ("[None] — choose a colour on the ramp.", "[بلا] — اختر لونًا على شريط التدرج."), + ("⌥-click clears overrides", "النقر مع ⌥ يمسح التجاوزات"), + ("⚠ out of date", "⚠ غير محدّث"), + ("Interface", "الواجهة"), + ("Advanced Type", "إعدادات النص المتقدمة"), + ("Composition", "تنضيد النص"), + ("Units & Increments", "الوحدات والزيادات"), + ("Grids", "الشبكات"), + ("Guides & Pasteboard", "الأدلة ولوحة اللصق"), + ("Dictionary", "القاموس"), + ("Spelling", "التدقيق الإملائي"), + ("Autocorrect", "التصحيح التلقائي"), + ("Track Changes", "تعقب التغييرات"), + ("Story Editor Display", "عرض محرر النص"), + ("Display Performance", "أداء العرض"), + ("Appearance of Black", "مظهر الأسود"), + ("File Handling", "معالجة الملفات"), + ("Formatting affects container (J)", "تطبيق التنسيق على الحاوية (J)"), + ("Formatting affects text (J)", "تطبيق التنسيق على النص (J)"), + ("Apply Color", "تطبيق اللون"), + ("Apply None", "إزالة اللون"), + ("Height Only", "الارتفاع فقط"), + ("Width Only", "العرض فقط"), + ("Height and Width", "الارتفاع والعرض"), + ("Height only", "الارتفاع فقط"), + ("Width only", "العرض فقط"), + ("Height and width", "الارتفاع والعرض"), + ("Version", "الإصدار"), + ("{count} characters don't fit", "عدد الأحرف الزائدة: {count}"), + ("document", "المستند"), + ("story", "النص"), + ("selection", "التحديد"), + ("top", "أعلى"), + ("center", "وسط"), + ("bottom", "أسفل"), + ("justify", "ضبط"), + ("text", "نص"), + ("graphic", "رسم"), + ("unassigned", "غير معين"), + ("Perceptual", "إدراكي"), + ("Relative Colorimetric", "قياس لوني نسبي"), + ("Saturation", "التشبع"), + ("Absolute Colorimetric", "قياس لوني مطلق"), + ("No conflicts", "لا توجد تعارضات"), + ("Page (1–{count}):", "الصفحة (1–{count}):"), + ("Fonts in Document: {count} Missing: {missing}", "الخطوط في المستند: {count} المفقودة: {missing}"), ]; diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index e15e53caa..c7b9f1928 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1902,7 +1902,11 @@ pub fn palette(app: &mut DesignApp, ctx: &egui::Context) { let mut run: Option<(String, Value)> = None; egui::Modal::new(egui::Id::new("palette")).show(ctx, |ui| { ui.set_width(480.0); - let r = ui.add(egui::TextEdit::singleline(&mut q).hint_text("Search styles and commands…").desired_width(f32::INFINITY)); + let r = ui.add( + egui::TextEdit::singleline(&mut q) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Search styles and commands…"))) + .desired_width(f32::INFINITY), + ); r.request_focus(); let items = quick_apply_items(&app.session, &q); egui::ScrollArea::vertical().max_height(340.0).show(ui, |ui| { diff --git a/crates/ui-egui/src/panels/conditions.rs b/crates/ui-egui/src/panels/conditions.rs index 6048250e9..6cb3cd3e5 100644 --- a/crates/ui-egui/src/panels/conditions.rs +++ b/crates/ui-egui/src/panels/conditions.rs @@ -28,7 +28,9 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let (row, _) = ui.allocate_exact_size(vec2(ui.available_width(), 22.0), Sense::hover()); // Visibility (eye). let eye = egui::Rect::from_min_size(row.min + vec2(2.0, 3.0), vec2(16.0, 16.0)); - let er = ui.interact(eye, ui.id().with(("cond_eye", &c.name)), Sense::click()).on_hover_text("Show / hide text with this condition"); + let er = ui.interact(eye, ui.id().with(("cond_eye", &c.name)), Sense::click()).on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Show / hide text with this condition")); + }); if c.visible { crate::icons::paint(ui.painter(), eye, "eye", t.text_dim); } @@ -73,7 +75,11 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let id = egui::Id::new("new_condition_name"); let mut name: String = ui.data(|d| d.get_temp(id)).unwrap_or_default(); ui.horizontal(|ui| { - ui.add(egui::TextEdit::singleline(&mut name).hint_text("New condition name").desired_width(150.0)); + ui.add( + egui::TextEdit::singleline(&mut name) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New condition name"))) + .desired_width(150.0), + ); if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New"))).clicked() && !name.trim().is_empty() { match app.run("condition.new", json!({"name": name.trim()})) { Ok(_) => name.clear(), diff --git a/crates/ui-egui/src/panels/glyphs.rs b/crates/ui-egui/src/panels/glyphs.rs index 3a9ffdc31..b950dcca0 100644 --- a/crates/ui-egui/src/panels/glyphs.rs +++ b/crates/ui-egui/src/panels/glyphs.rs @@ -50,7 +50,11 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { } }); }); - ui.add(egui::TextEdit::singleline(&mut st.query).hint_text("Search: character or U+code").desired_width(f32::INFINITY)); + ui.add( + egui::TextEdit::singleline(&mut st.query) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Search: character or U+code"))) + .desired_width(f32::INFINITY), + ); let face = db.face(&st.family, &st.style); let q = st.query.trim().to_string(); let code = q.strip_prefix("U+").or_else(|| q.strip_prefix("u+")).and_then(|h| u32::from_str_radix(h, 16).ok()); @@ -129,6 +133,13 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { Err(e) => app.status(format!("Glyphs: {e}")), } } - ui.label(egui::RichText::new(format!("{} glyphs — click to insert at the text cursor", chars.len())).size(10.5).color(t.text_dim)); + crate::rtl::label( + ui, + egui::RichText::new( + crate::i18n::tr(&app.ui.language, "{count} glyphs — click to insert at the text cursor").replace("{count}", &chars.len().to_string()), + ) + .size(10.5) + .color(t.text_dim), + ); ui.data_mut(|d| d.insert_temp(id, st)); } diff --git a/crates/ui-egui/src/panels/interactive.rs b/crates/ui-egui/src/panels/interactive.rs index 66b249df2..85927927e 100644 --- a/crates/ui-egui/src/panels/interactive.rs +++ b/crates/ui-egui/src/panels/interactive.rs @@ -25,7 +25,13 @@ pub fn hyperlinks(app: &mut DesignApp, ui: &mut egui::Ui) { let has_target = app.session.active().is_some_and(|d| d.selection.text.is_some_and(|t| !t.is_caret()) || !d.selection.items.is_empty()); ui.horizontal(|ui| { ui.add(egui::TextEdit::singleline(&mut url).desired_width(170.0)); - if ui.add_enabled(has_target, egui::Button::new("New")).on_hover_text("Hyperlink from the selected text or frames").clicked() { + if ui + .add_enabled(has_target, egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New")))) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Hyperlink from the selected text or frames")); + }) + .clicked() + { let p = if let Some(m) = url.strip_prefix("mailto:") { json!({"email": m}) } else { json!({"url": url}) }; if let Err(e) = app.run("hyperlink.create", p) { app.status(format!("Hyperlinks: {e}")); @@ -36,12 +42,15 @@ pub fn hyperlinks(app: &mut DesignApp, ui: &mut egui::Ui) { ui.separator(); let list = app.session.execute("hyperlink.list", &json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default(); if list.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "No hyperlinks. Select text or a frame, type a URL and click New.")) - .size(11.0) - .color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "No hyperlinks. Select text or a frame, type a URL and click New.")) + .size(11.0) + .color(t.text_dim), + ), + ); } for h in list { let hid = h["id"].clone(); @@ -51,7 +60,7 @@ pub fn hyperlinks(app: &mut DesignApp, ui: &mut egui::Ui) { let _ = app.run("hyperlink.goToSource", json!({"id": hid})); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Delete").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete"))).clicked() { let _ = app.run("hyperlink.delete", json!({"id": hid})); } }); @@ -68,10 +77,13 @@ pub fn bookmarks(app: &mut DesignApp, ui: &mut egui::Ui) { ui.separator(); let list = app.session.execute("bookmark.list", &json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default(); if list.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "No bookmarks. They become the PDF outline.")).size(11.0).color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "No bookmarks. They become the PDF outline.")).size(11.0).color(t.text_dim), + ), + ); } for (i, b) in list.iter().enumerate() { let page = b["page"].as_u64().unwrap_or(0) as usize; @@ -81,7 +93,7 @@ pub fn bookmarks(app: &mut DesignApp, ui: &mut egui::Ui) { crate::canvas::go_to_page(app, page); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Delete").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete"))).clicked() { let _ = app.run("bookmark.delete", json!({"index": i})); } }); @@ -93,31 +105,51 @@ pub fn bookmarks(app: &mut DesignApp, ui: &mut egui::Ui) { pub fn articles(app: &mut DesignApp, ui: &mut egui::Ui) { let t = Tokens::get(ui.ctx()); let has_sel = app.session.active().is_some_and(|d| !d.selection.items.is_empty()); - if ui.add_enabled(has_sel, egui::Button::new("New Article from Selection")).clicked() { + if ui.add_enabled(has_sel, egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New Article from Selection")))).clicked() { let _ = app.run("article.new", json!({})); } ui.separator(); let list = app.session.execute("article.list", &json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default(); if list.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "No articles: exports follow page order.")).size(11.0).color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "No articles: exports follow page order.")).size(11.0).color(t.text_dim), + ), + ); } for a in list { let name = a["name"].as_str().unwrap_or("").to_string(); let mut export = a["export"].as_bool().unwrap_or(true); ui.horizontal(|ui| { - if ui.checkbox(&mut export, "").on_hover_text("Include when exporting").changed() { + if ui + .checkbox(&mut export, "") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Include when exporting")); + }) + .changed() + { let _ = app.run("article.options", json!({"name": name, "export": export})); } - ui.label(egui::RichText::new(&name).strong()); - ui.label(egui::RichText::new(format!("{} objects", a["items"].as_array().map_or(0, Vec::len))).size(10.5).color(t.text_dim)); + crate::rtl::label(ui, egui::RichText::new(&name).strong()); + crate::rtl::label( + ui, + egui::RichText::new(format!("{} {}", a["items"].as_array().map_or(0, Vec::len), crate::i18n::tr(&app.ui.language, "objects"))) + .size(10.5) + .color(t.text_dim), + ); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Delete").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete"))).clicked() { let _ = app.run("article.delete", json!({"name": name})); } - if ui.add_enabled(has_sel, egui::Button::new("Add").small()).on_hover_text("Add the selection").clicked() { + if ui + .add_enabled(has_sel, egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Add"))).small()) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Add the selection")); + }) + .clicked() + { let _ = app.run("article.add", json!({"name": name})); } }); @@ -136,7 +168,11 @@ pub fn tags(app: &mut DesignApp, ui: &mut egui::Ui) { let id = egui::Id::new("new_tag_name"); let mut name: String = ui.data(|d| d.get_temp(id)).unwrap_or_default(); ui.horizontal(|ui| { - ui.add(egui::TextEdit::singleline(&mut name).hint_text("New tag").desired_width(140.0)); + ui.add( + egui::TextEdit::singleline(&mut name) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New tag"))) + .desired_width(140.0), + ); if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New"))).clicked() && !name.trim().is_empty() { match app.run("xml.newTag", json!({"name": name.trim()})) { Ok(_) => name.clear(), @@ -153,17 +189,23 @@ pub fn tags(app: &mut DesignApp, ui: &mut egui::Ui) { if c.len() == 3 { ui.painter().rect_filled(r, 2.0, egui::Color32::from_rgb(c[0], c[1], c[2])); } - if ui.add_enabled(has_sel || text_sel, egui::Button::new(&n).frame(false)).on_hover_text("Tag the selection").clicked() { + if ui + .add_enabled(has_sel || text_sel, egui::Button::new(&n).frame(false)) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Tag the selection")); + }) + .clicked() + { let _ = if text_sel { app.run("xml.tagText", json!({"tag": n})) } else { app.run("xml.tag", json!({"tag": n})) }; } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Delete").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete"))).clicked() { let _ = app.run("xml.deleteTag", json!({"name": n})); } }); }); } - if (has_sel || text_sel) && ui.small_button("Untag Selection").clicked() { + if (has_sel || text_sel) && ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Untag Selection"))).clicked() { let _ = if text_sel { app.run("xml.tagText", json!({"tag": null})) } else { app.run("xml.tag", json!({"tag": null})) }; } let mut markers = app.ui.tag_markers; @@ -171,9 +213,9 @@ pub fn tags(app: &mut DesignApp, ui: &mut egui::Ui) { let _ = app.run("view.tagMarkers", json!({"on": markers})); } ui.separator(); - ui.label(egui::RichText::new("Structure").strong()); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Structure")).strong()); let st = app.session.execute("xml.structure", &json!({})).unwrap_or_default(); - ui.label(egui::RichText::new(st["root"].as_str().unwrap_or("Root")).size(11.0).color(t.text_dim)); + crate::rtl::label(ui, egui::RichText::new(st["root"].as_str().unwrap_or("Root")).size(11.0).color(t.text_dim)); for el in st["elements"].as_array().cloned().unwrap_or_default() { let text = el["text"].as_str().unwrap_or("").chars().take(40).collect::(); let label = format!(" <{}> {}", el["tag"].as_str().unwrap_or(""), text); @@ -185,7 +227,7 @@ pub fn tags(app: &mut DesignApp, ui: &mut egui::Ui) { let vid = egui::Id::new("dtd_problems"); let mut problems: Option> = ui.data(|d| d.get_temp(vid)); ui.horizontal(|ui| { - if ui.button("Load DTD…").clicked() + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Load DTD…"))).clicked() && let Some(path) = app.services.pick_open.as_mut().and_then(|f| f("dtd")) { match app.run("xml.loadDtd", json!({"path": path})) { @@ -194,7 +236,7 @@ pub fn tags(app: &mut DesignApp, ui: &mut egui::Ui) { } } let has_dtd = info["dtd"].as_bool().unwrap_or(false); - if ui.add_enabled(has_dtd, egui::Button::new("Validate")).clicked() + if ui.add_enabled(has_dtd, egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Validate")))).clicked() && let Ok(v) = app.session.execute("xml.validate", &json!({})) { problems = Some( @@ -206,18 +248,18 @@ pub fn tags(app: &mut DesignApp, ui: &mut egui::Ui) { .collect(), ); } - if has_dtd && ui.small_button("Delete DTD").clicked() { + if has_dtd && ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete DTD"))).clicked() { let _ = app.run("xml.deleteDtd", json!({})); problems = None; } }); match &problems { Some(list) if list.is_empty() => { - ui.label(egui::RichText::new("The structure is valid.").size(11.0).color(t.text_dim)); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "The structure is valid.")).size(11.0).color(t.text_dim)); } Some(list) => { for p in list { - ui.label(egui::RichText::new(format!("⚠ {p}")).size(11.0)); + crate::rtl::label(ui, egui::RichText::new(format!("⚠ {p}")).size(11.0)); } } None => {} @@ -251,13 +293,21 @@ pub fn states(app: &mut DesignApp, ui: &mut egui::Ui) { let info = app.session.execute("states.list", &json!({})).ok(); let list = info.as_ref().and_then(|i| i["states"].as_array().cloned()).unwrap_or_default(); if list.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select two or more objects (or a group) to make a multi-state object.")) - .size(11.0) - .color(t.text_dim), - )); - if ui.add_enabled(n > 0, egui::Button::new("Convert Selection to Multi-State Object")).clicked() + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select two or more objects (or a group) to make a multi-state object.")) + .size(11.0) + .color(t.text_dim), + ), + ); + if ui + .add_enabled( + n > 0, + egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Convert Selection to Multi-State Object"))), + ) + .clicked() && let Err(e) = app.run("states.create", json!({})) { app.status(format!("Object States: {e}")); @@ -271,7 +321,7 @@ pub fn states(app: &mut DesignApp, ui: &mut egui::Ui) { } } ui.separator(); - if ui.small_button("Release to Objects").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Release to Objects"))).clicked() { let _ = app.run("states.release", json!({})); } } @@ -280,10 +330,13 @@ pub fn states(app: &mut DesignApp, ui: &mut egui::Ui) { pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { let t = Tokens::get(ui.ctx()); let Some(id) = app.session.active().and_then(|d| d.selection.items.first().copied()) else { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select an object to make it a button.")).size(11.0).color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select an object to make it a button.")).size(11.0).color(t.text_dim), + ), + ); return; }; let cur = app.session.active().and_then(|d| d.doc.item(id)).and_then(|it| it.button.clone()); @@ -309,13 +362,15 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { let mut pick = None; ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "On Release")); - egui::ComboBox::from_id_salt("button_action").selected_text(labels.iter().find(|l| l.0 == kind).map_or("", |l| l.1)).show_ui(ui, |ui| { - for (k, l) in labels { - if ui.selectable_label(k == kind, l).clicked() { - pick = Some(k); + egui::ComboBox::from_id_salt("button_action") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, labels.iter().find(|l| l.0 == kind).map_or("", |l| l.1)))) + .show_ui(ui, |ui| { + for (k, l) in labels { + if ui.selectable_label(k == kind, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { + pick = Some(k); + } } - } - }); + }); }); let mut params = None; if let Some(k) = pick { @@ -345,10 +400,13 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { { app.status(format!("Buttons: {e}")); } - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "Buttons act in interactive PDF export.")).size(10.5).color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "Buttons act in interactive PDF export.")).size(10.5).color(t.text_dim), + ), + ); // Form field. ui.separator(); let ff = app.session.active().and_then(|d| d.doc.item(id)).and_then(|it| it.form_field.clone()); @@ -364,16 +422,18 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { let cur = ff.as_ref().map(|f| f.kind); ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Form Field")); - egui::ComboBox::from_id_salt("form_kind").selected_text(kinds.iter().find(|k| k.0 == cur).map_or("", |k| k.1)).show_ui(ui, |ui| { - for (k, l) in kinds { - if ui.selectable_label(k == cur, l).clicked() { - let _ = match k { - Some(k) => app.run("form.set", json!({"kind": k})), - None => app.run("form.clear", json!({})), - }; + egui::ComboBox::from_id_salt("form_kind") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, kinds.iter().find(|k| k.0 == cur).map_or("", |k| k.1)))) + .show_ui(ui, |ui| { + for (k, l) in kinds { + if ui.selectable_label(k == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { + let _ = match k { + Some(k) => app.run("form.set", json!({"kind": k})), + None => app.run("form.clear", json!({})), + }; + } } - } - }); + }); }); if let Some(f) = ff { let key = egui::Id::new(("form_edit", id.0)); @@ -398,7 +458,12 @@ pub fn buttons(app: &mut DesignApp, ui: &mut egui::Ui) { if matches!(f.kind, K::ComboBox | K::ListBox) { ui.horizontal(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Choices")); - commit |= ui.text_edit_singleline(&mut opts).on_hover_text("Separated by commas").lost_focus(); + commit |= ui + .text_edit_singleline(&mut opts) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Separated by commas")); + }) + .lost_focus(); }); } let mut req = f.required; @@ -433,15 +498,29 @@ pub fn liquid(app: &mut DesignApp, ui: &mut egui::Ui) { let Some((si, pi)) = d.page_loc(page) else { return }; let pg = d.spreads[si].pages[pi].clone(); use designcraft_doc::LiquidRule as L; - ui.label(egui::RichText::new(format!("Page {}", d.page_name(page))).strong()); + crate::rtl::label(ui, egui::RichText::new(format!("{} {}", crate::i18n::tr(&app.ui.language, "Page"), d.page_name(page))).strong()); let mut rule = pg.liquid; - egui::ComboBox::from_id_salt("liquid_rule").selected_text(format!("{rule:?}")).show_ui(ui, |ui| { - for (r, label) in - [(L::Off, "Off"), (L::Scale, "Scale"), (L::ReCenter, "Re-center"), (L::GuideBased, "Guide-based"), (L::ObjectBased, "Object-based")] - { - ui.selectable_value(&mut rule, r, label); - } - }); + egui::ComboBox::from_id_salt("liquid_rule") + .selected_text(crate::rtl::widget( + ui, + crate::i18n::tr( + &app.ui.language, + match rule { + L::Off => "Off", + L::Scale => "Scale", + L::ReCenter => "Re-center", + L::GuideBased => "Guide-based", + L::ObjectBased => "Object-based", + }, + ), + )) + .show_ui(ui, |ui| { + for (r, label) in + [(L::Off, "Off"), (L::Scale, "Scale"), (L::ReCenter, "Re-center"), (L::GuideBased, "Guide-based"), (L::ObjectBased, "Object-based")] + { + ui.selectable_value(&mut rule, r, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))); + } + }); if rule != pg.liquid { let _ = app.run("liquid.pageRule", json!({"rule": rule, "pages": [page + 1]})); } @@ -456,7 +535,7 @@ pub fn liquid(app: &mut DesignApp, ui: &mut egui::Ui) { let labels = ["Resize width", "Resize height", "Pin top", "Pin bottom", "Pin left", "Pin right"]; ui.horizontal_wrapped(|ui| { for ((on, key), label) in v.iter_mut().zip(keys).zip(labels) { - if ui.checkbox(on, label).changed() { + if ui.checkbox(on, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).changed() { let _ = app.run("liquid.object", json!({key: *on})); } } @@ -495,18 +574,21 @@ pub fn liquid(app: &mut DesignApp, ui: &mut egui::Ui) { pub fn media(app: &mut DesignApp, ui: &mut egui::Ui) { let t = Tokens::get(ui.ctx()); let Ok(m) = app.session.execute("media.get", &json!({})) else { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select a placed video or sound (File › Place a .mp4, .mov, .mp3, .wav …).")) - .size(11.0) - .color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "Select a placed video or sound (File › Place a .mp4, .mov, .mp3, .wav …).")) + .size(11.0) + .color(t.text_dim), + ), + ); return; }; - ui.label(egui::RichText::new(format!("{} — {}", m["kind"].as_str().unwrap_or(""), m["name"].as_str().unwrap_or(""))).strong()); + crate::rtl::label(ui, egui::RichText::new(format!("{} — {}", m["kind"].as_str().unwrap_or(""), m["name"].as_str().unwrap_or(""))).strong()); for (key, label) in [("playOnPageLoad", "Play on Page Load"), ("loop", "Loop"), ("controls", "Show Controls")] { let mut on = m[key].as_bool().unwrap_or(false); - if ui.checkbox(&mut on, label).changed() { + if ui.checkbox(&mut on, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).changed() { let _ = app.run("media.options", json!({key: on})); } } @@ -518,14 +600,19 @@ pub fn media(app: &mut DesignApp, ui: &mut egui::Ui) { { app.status(format!("Media: {e}")); } - if !m["poster"].is_null() && ui.small_button("None").clicked() { + if !m["poster"].is_null() && ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "None"))).clicked() { let _ = app.run("media.options", json!({"poster": null})); } }); - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "Plays in EPUB export; print and PDF show the poster.")).size(10.5).color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "Plays in EPUB export; print and PDF show the poster.")) + .size(10.5) + .color(t.text_dim), + ), + ); } /// Page Transitions panel: the current spread's transition for interactive PDF. @@ -536,14 +623,16 @@ pub fn transitions(app: &mut DesignApp, ui: &mut egui::Ui) { let Some((si, _)) = d.page_loc(page) else { return }; let cur = d.spreads[si].pages.first().and_then(|p| p.transition); use designcraft_doc::TransitionKind as K; - ui.label(egui::RichText::new(format!("Spread {}", si + 1)).strong()); + crate::rtl::label(ui, egui::RichText::new(format!("{} {}", crate::i18n::tr(&app.ui.language, "Spread"), si + 1)).strong()); let mut kind = cur.map(|c| c.kind); - egui::ComboBox::from_id_salt("transition_kind").selected_text(kind.map_or("None", K::label)).show_ui(ui, |ui| { - ui.selectable_value(&mut kind, None, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "None"))); - for k in K::ALL { - ui.selectable_value(&mut kind, Some(k), k.label()); - } - }); + egui::ComboBox::from_id_salt("transition_kind") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, kind.map_or("None", K::label)))) + .show_ui(ui, |ui| { + ui.selectable_value(&mut kind, None, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "None"))); + for k in K::ALL { + ui.selectable_value(&mut kind, Some(k), crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, k.label()))); + } + }); let mut duration = cur.map_or(1.0, |c| c.duration); let mut horizontal = cur.is_some_and(|c| c.horizontal); let mut changed = kind != cur.map(|c| c.kind); @@ -553,7 +642,12 @@ pub fn transitions(app: &mut DesignApp, ui: &mut egui::Ui) { changed |= ui.add(egui::DragValue::new(&mut duration).range(0.1..=10.0).speed(0.1).suffix(" s")).changed(); }); if matches!(kind, Some(K::Blinds | K::Box | K::Split)) { - changed |= ui.checkbox(&mut horizontal, if kind == Some(K::Box) { "Inward" } else { "Horizontal" }).changed(); + changed |= ui + .checkbox( + &mut horizontal, + crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, if kind == Some(K::Box) { "Inward" } else { "Horizontal" })), + ) + .changed(); } } let params = |all: bool| { @@ -570,12 +664,15 @@ pub fn transitions(app: &mut DesignApp, ui: &mut egui::Ui) { if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Apply to All Spreads"))).clicked() { let _ = app.run("page.transition", params(true)); } - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "Transitions play in interactive PDF (full-screen mode).")) - .size(10.5) - .color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "Transitions play in interactive PDF (full-screen mode).")) + .size(10.5) + .color(t.text_dim), + ), + ); } /// Track Changes panel: tracking on/off and each change with Accept / Reject. @@ -598,18 +695,28 @@ pub fn track_changes(app: &mut DesignApp, ui: &mut egui::Ui) { let kind = c["kind"].as_str().unwrap_or(""); let text: String = c["text"].as_str().unwrap_or("").chars().take(40).collect(); ui.horizontal(|ui| { - let label = - egui::RichText::new(format!("{} “{}”", if kind == "inserted" { "Added" } else { "Deleted" }, text.replace('\n', "¶"))).size(11.0); + let label = egui::RichText::new(format!( + "{} “{}”", + crate::i18n::tr(&app.ui.language, if kind == "inserted" { "Added" } else { "Deleted" }), + text.replace('\n', "¶") + )) + .size(11.0); let label = if kind == "deleted" { label.strikethrough() } else { label }; - if ui.selectable_label(false, label).on_hover_text("Show in the text").clicked() { + if ui + .selectable_label(false, label) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Show in the text")); + }) + .clicked() + { let end = c["end"].clone(); let _ = app.run("text.select", json!({"story": story, "anchor": start, "focus": end})); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Reject").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Reject"))).clicked() { let _ = app.run("changes.reject", json!({"story": story, "start": start})); } - if ui.small_button("Accept").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Accept"))).clicked() { let _ = app.run("changes.accept", json!({"story": story, "start": start})); } }); diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 41120c256..a9c842116 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -49,7 +49,15 @@ fn c32(c: [u8; 3]) -> Color32 { } /// Eye and lock cells (with column rules). Returns (eye clicked, lock clicked). -fn eye_lock(ui: &mut Ui, row: Rect, visible: bool, locked: bool, dim: bool, key: impl std::hash::Hash + std::fmt::Debug + Copy) -> (bool, bool) { +fn eye_lock( + ui: &mut Ui, + language: &str, + row: Rect, + visible: bool, + locked: bool, + dim: bool, + key: impl std::hash::Hash + std::fmt::Debug + Copy, +) -> (bool, bool) { let t = Tokens::get(ui.ctx()); let eye = Rect::from_min_size(row.min, vec2(COL_W, ROW_H)); let lock = Rect::from_min_size(row.min + vec2(COL_W, 0.0), vec2(COL_W, ROW_H)); @@ -63,13 +71,25 @@ fn eye_lock(ui: &mut Ui, row: Rect, visible: bool, locked: bool, dim: bool, key: if locked { icons::paint(ui.painter(), Rect::from_center_size(lock.center(), vec2(13.0, 13.0)), "lock", col); } - let e = ui.interact(eye, ui.id().with(("eye", key)), Sense::click()).on_hover_text(if visible { "Hide" } else { "Show" }); - let l = ui.interact(lock, ui.id().with(("lock", key)), Sense::click()).on_hover_text(if locked { "Unlock" } else { "Lock" }); + let e = ui.interact(eye, ui.id().with(("eye", key)), Sense::click()).on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(language, if visible { "Hide" } else { "Show" })); + }); + let l = ui.interact(lock, ui.id().with(("lock", key)), Sense::click()).on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(language, if locked { "Unlock" } else { "Lock" })); + }); (e.clicked(), l.clicked()) } /// The selection square at the right of a row: filled = selected; hollow on hover. -fn selection_square(ui: &mut Ui, row: Rect, color: Color32, selected: bool, small: bool, key: impl std::hash::Hash + std::fmt::Debug) -> bool { +fn selection_square( + ui: &mut Ui, + language: &str, + row: Rect, + color: Color32, + selected: bool, + small: bool, + key: impl std::hash::Hash + std::fmt::Debug, +) -> bool { let s = if small { 7.0 } else { 8.0 }; let r = Rect::from_center_size(pos2(row.max.x - 12.0, row.center().y), vec2(s, s)); let resp = ui.interact(r.expand(4.0), ui.id().with(("selsq", key)), Sense::click()); @@ -78,7 +98,10 @@ fn selection_square(ui: &mut Ui, row: Rect, color: Color32, selected: bool, smal } else if resp.hovered() { ui.painter().rect_stroke(r, 0.0, Stroke::new(1.0, color), StrokeKind::Inside); } - resp.on_hover_text("Select").clicked() + resp.on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(language, "Select")); + }) + .clicked() } pub fn show(app: &mut DesignApp, ui: &mut Ui) { @@ -103,7 +126,7 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) { } else if resp.hovered() { ui.painter().rect_filled(row, 0.0, t.hover); } - let (eye, lock) = eye_lock(ui, row, l.visible, l.locked, false, ("layer", l.id.0)); + let (eye, lock) = eye_lock(ui, &app.ui.language, row, l.visible, l.locked, false, ("layer", l.id.0)); // Disclosure triangle. let tri = Rect::from_min_size(row.min + vec2(2.0 * COL_W + 2.0, 0.0), vec2(14.0, ROW_H)); let c = tri.center(); @@ -127,7 +150,7 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) { if l.id == active { icons::paint(ui.painter(), Rect::from_center_size(pos2(row.max.x - 32.0, row.center().y), vec2(13.0, 13.0)), "tool-pen", t.icon); } - let sq = selection_square(ui, row, lc, sel_layers.contains(&l.id), false, ("layer", l.id.0)); + let sq = selection_square(ui, &app.ui.language, row, lc, sel_layers.contains(&l.id), false, ("layer", l.id.0)); if eye { let _ = app.run("layer.set", json!({"id": l.id.0, "visible": !l.visible})); } else if lock { @@ -163,7 +186,7 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) { ("Show All Layers", json!({"id": l.id.0, "show": true})), ("Unlock All Layers", json!({"id": l.id.0, "unlock": true})), ] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("layer.others", params); ui.close(); } @@ -182,7 +205,7 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) { if resp.hovered() { ui.painter().rect_filled(row, 0.0, t.hover); } - let (eye, lock) = eye_lock(ui, row, !it.hidden, it.locked, !l.visible, ("item", it.id.0)); + let (eye, lock) = eye_lock(ui, &app.ui.language, row, !it.hidden, it.locked, !l.visible, ("item", it.id.0)); let name = item_label(&doc, it); let clip = Rect::from_min_max(row.min, pos2(row.max.x - 26.0, row.max.y)); ui.painter().with_clip_rect(clip).text( @@ -192,7 +215,7 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) { egui::FontId::proportional(11.0), if it.hidden { t.text_dim } else { t.text }, ); - let sq = selection_square(ui, row, lc, is_sel, true, ("item", it.id.0)); + let sq = selection_square(ui, &app.ui.language, row, lc, is_sel, true, ("item", it.id.0)); if eye { let _ = app.run("object.setFlags", json!({"ids": [it.id.0], "hidden": !it.hidden})); } else if lock { @@ -217,10 +240,10 @@ pub fn show(app: &mut DesignApp, ui: &mut Ui) { .color(t.text_dim), ); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if icons::button(ui, "trash", 20.0, false, "Delete Layer").clicked() { + if icons::button(ui, "trash", 20.0, false, crate::i18n::tr(&app.ui.language, "Delete Layer")).clicked() { let _ = app.run("layer.delete", json!({"id": active.0})); } - if icons::button(ui, "plus", 20.0, false, "Create New Layer").clicked() { + if icons::button(ui, "plus", 20.0, false, crate::i18n::tr(&app.ui.language, "Create New Layer")).clicked() { let _ = app.run("layer.new", json!({})); } }); diff --git a/crates/ui-egui/src/panels/library.rs b/crates/ui-egui/src/panels/library.rs index affa5b224..ccaa595dd 100644 --- a/crates/ui-egui/src/panels/library.rs +++ b/crates/ui-egui/src/panels/library.rs @@ -10,20 +10,23 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let t = Tokens::get(ui.ctx()); // Content Collector conveyor. let conveyor = app.session.conveyor.iter().map(|c| c.0.clone()).collect::>(); - ui.label(egui::RichText::new(format!("Conveyor ({})", conveyor.len())).strong()); + crate::rtl::label(ui, egui::RichText::new(format!("{} ({})", crate::i18n::tr(&app.ui.language, "Conveyor"), conveyor.len())).strong()); if conveyor.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr( - &app.ui.language, - "Collect objects with the Content Collector (B); place them with the Content Placer.", - )) - .size(10.5) - .color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr( + &app.ui.language, + "Collect objects with the Content Collector (B); place them with the Content Placer.", + )) + .size(10.5) + .color(t.text_dim), + ), + ); } else { - ui.label(egui::RichText::new(conveyor.join(", ")).size(10.5)); - if ui.small_button("Clear").clicked() { + crate::rtl::label(ui, egui::RichText::new(conveyor.join(", ")).size(10.5)); + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Clear"))).clicked() { let _ = app.run("conveyor.clear", json!({})); } } @@ -45,7 +48,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { return; }; let has_sel = app.session.active().is_some_and(|d| !d.selection.items.is_empty()); - if ui.add_enabled(has_sel, egui::Button::new("Add Selection")).clicked() + if ui.add_enabled(has_sel, egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Add Selection")))).clicked() && let Err(e) = app.run("library.add", json!({})) { app.status(format!("Library: {e}")); @@ -53,22 +56,25 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { ui.separator(); let items = items.as_array().cloned().unwrap_or_default(); if items.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "The library is empty. Select objects and Add Selection.")) - .size(11.0) - .color(t.text_dim), - )); + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "The library is empty. Select objects and Add Selection.")) + .size(11.0) + .color(t.text_dim), + ), + ); } for it in items { let i = it["index"].clone(); ui.horizontal(|ui| { - ui.label(it["name"].as_str().unwrap_or("")); + crate::rtl::label(ui, it["name"].as_str().unwrap_or("")); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Delete").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete"))).clicked() { let _ = app.run("library.remove", json!({"index": i})); } - if ui.small_button("Place").clicked() + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Place"))).clicked() && let Err(e) = app.run("library.place", json!({"index": i})) { app.status(format!("Library: {e}")); @@ -107,15 +113,27 @@ pub fn book(app: &mut DesignApp, ui: &mut egui::Ui) { let name = std::path::Path::new(&path).file_stem().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); let (first, pages) = (d["firstPage"].as_u64().unwrap_or(1), d["pages"].as_u64().unwrap_or(0)); ui.horizontal(|ui| { - if ui.selectable_label(i == source, if i == source { "◆" } else { "◇" }).on_hover_text("Style source").clicked() { + if ui + .selectable_label(i == source, if i == source { "◆" } else { "◇" }) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Style source")); + }) + .clicked() + { let _ = app.run("book.styleSource", json!({"index": i})); } - if ui.selectable_label(false, &name).on_hover_text("Open").clicked() { + if ui + .selectable_label(false, &name) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Open")); + }) + .clicked() + { let _ = app.run("file.open", json!({"path": path})); } - ui.label(egui::RichText::new(format!("{first}–{}", first + pages.saturating_sub(1))).size(10.5).color(t.text_dim)); + crate::rtl::label(ui, egui::RichText::new(format!("{first}–{}", first + pages.saturating_sub(1))).size(10.5).color(t.text_dim)); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.small_button("Remove").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Remove"))).clicked() { let _ = app.run("book.remove", json!({"index": i})); } }); @@ -150,10 +168,10 @@ pub fn scripts(app: &mut DesignApp, ui: &mut egui::Ui) { let sel_id = egui::Id::new("scripts_sel"); let mut sel: usize = ui.data(|d| d.get_temp(sel_id)).unwrap_or(0); if app.ui.scripts.is_empty() { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "No scripts. Add one below.")).size(11.0).color(t.text_dim), - )); + crate::rtl::widget(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "No scripts. Add one below.")).size(11.0).color(t.text_dim)), + ); } let mut run: Option = None; for (i, (name, _)) in app.ui.scripts.iter().enumerate() { @@ -167,7 +185,7 @@ pub fn scripts(app: &mut DesignApp, ui: &mut egui::Ui) { } ui.separator(); ui.horizontal(|ui| { - if ui.add_enabled(!app.ui.scripts.is_empty(), egui::Button::new("Run")).clicked() { + if ui.add_enabled(!app.ui.scripts.is_empty(), egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Run")))).clicked() { run = Some(sel); } if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New"))).clicked() { @@ -187,7 +205,11 @@ pub fn scripts(app: &mut DesignApp, ui: &mut egui::Ui) { Err(e) => app.status(format!("Scripts: {e}")), } } - if ui.add_enabled(!app.ui.scripts.is_empty(), egui::Button::new("Delete")).clicked() && sel < app.ui.scripts.len() { + if ui + .add_enabled(!app.ui.scripts.is_empty(), egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete")))) + .clicked() + && sel < app.ui.scripts.len() + { app.ui.scripts.remove(sel); sel = sel.saturating_sub(1); } diff --git a/crates/ui-egui/src/panels/mod.rs b/crates/ui-egui/src/panels/mod.rs index 7beb995c5..0ef125619 100644 --- a/crates/ui-egui/src/panels/mod.rs +++ b/crates/ui-egui/src/panels/mod.rs @@ -201,8 +201,14 @@ pub fn font_family_picker(app: &mut DesignApp, ui: &mut egui::Ui, current: &str, |ui| { ui.set_min_width(300.0); ui.horizontal(|ui| { - ui.add(egui::TextEdit::singleline(&mut query).hint_text("Search fonts").desired_width(200.0)); - ui.toggle_value(&mut only_favs, "Favorites").on_hover_text("Show Favorites Only"); + ui.add( + egui::TextEdit::singleline(&mut query) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Search fonts"))) + .desired_width(200.0), + ); + ui.toggle_value(&mut only_favs, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Favorites"))).on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Show Favorites Only")); + }); }); let q = query.to_lowercase(); let ppp = ui.ctx().pixels_per_point(); @@ -285,10 +291,11 @@ pub fn font_style_picker(app: &mut DesignApp, ui: &mut egui::Ui, family: &str, c pub fn para_style_picker(app: &mut DesignApp, ui: &mut egui::Ui, current: &str, overridden: bool, width: f32) { let names: Vec = app.session.active().map(|d| d.doc.styles.paragraph.iter().map(|p| p.name.clone()).collect()).unwrap_or_default(); let mut pick = None; - let label = if overridden { format!("{current}+") } else { current.to_string() }; - egui::ComboBox::from_id_salt("para_style").selected_text(label).width(width).show_ui(ui, |ui| { + let shown = crate::i18n::style_name(&app.ui.language, current); + let label = if overridden { format!("{shown}+") } else { shown.to_string() }; + egui::ComboBox::from_id_salt("para_style").selected_text(crate::rtl::widget(ui, label)).width(width).show_ui(ui, |ui| { for n in names.iter().filter(|n| *n != designcraft_doc::NO_PARA_STYLE) { - if ui.selectable_label(n == current, n).clicked() { + if ui.selectable_label(n == current, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, n))).clicked() { pick = Some(n.clone()); } } diff --git a/crates/ui-egui/src/panels/notes.rs b/crates/ui-egui/src/panels/notes.rs index 4206d4105..5139a090d 100644 --- a/crates/ui-egui/src/panels/notes.rs +++ b/crates/ui-egui/src/panels/notes.rs @@ -13,8 +13,19 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let has_text = app.session.active().is_some_and(|d| d.selection.text.is_some()); let id = egui::Id::new("new_note_text"); let mut draft: String = ui.data(|d| d.get_temp(id)).unwrap_or_default(); - ui.add(egui::TextEdit::multiline(&mut draft).hint_text("Note text").desired_rows(2).desired_width(f32::INFINITY)); - if ui.add_enabled(has_text && !draft.trim().is_empty(), egui::Button::new("New Note at Cursor")).clicked() { + ui.add( + egui::TextEdit::multiline(&mut draft) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Note text"))) + .desired_rows(2) + .desired_width(f32::INFINITY), + ); + if ui + .add_enabled( + has_text && !draft.trim().is_empty(), + egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New Note at Cursor"))), + ) + .clicked() + { match app.run("note.new", json!({"text": draft.trim()})) { Ok(_) => draft.clear(), Err(e) => app.status(format!("Notes: {e}")), @@ -37,13 +48,13 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { } ui.label(n["text"].as_str().unwrap_or("")); ui.horizontal(|ui| { - if ui.small_button("Go To").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Go To"))).clicked() { let _ = app.run("text.select", json!({"story": story, "anchor": at, "focus": at + 3})); } - if ui.small_button("Convert to Text").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Convert to Text"))).clicked() { let _ = app.run("note.convertToText", json!({"story": story, "id": nid})); } - if ui.small_button("Delete").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Delete"))).clicked() { let _ = app.run("note.delete", json!({"story": story, "id": nid})); } }); diff --git a/crates/ui-egui/src/panels/pages.rs b/crates/ui-egui/src/panels/pages.rs index 5f29855bd..6019e7883 100644 --- a/crates/ui-egui/src/panels/pages.rs +++ b/crates/ui-egui/src/panels/pages.rs @@ -186,12 +186,21 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { ui.add_space(6.0); ui.spacing_mut().item_spacing.y = 4.0; ui.horizontal(|ui| { - ui.label(egui::RichText::new(format!("{} Pages in {} Spreads", doc.page_count(), doc.spreads.len())).size(11.0).color(t.text_dim)); + crate::rtl::label( + ui, + egui::RichText::new( + crate::i18n::tr(&app.ui.language, "{pages} Pages in {spreads} Spreads") + .replace("{pages}", &doc.page_count().to_string()) + .replace("{spreads}", &doc.spreads.len().to_string()), + ) + .size(11.0) + .color(t.text_dim), + ); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if icons::button(ui, "trash", 20.0, false, "Delete Page").clicked() { + if icons::button(ui, "trash", 20.0, false, crate::i18n::tr(&app.ui.language, "Delete Page")).clicked() { let _ = app.run("layout.pages.delete", json!({"pages": [cur]})); } - if icons::button(ui, "plus", 20.0, false, "Insert Page").clicked() { + if icons::button(ui, "plus", 20.0, false, crate::i18n::tr(&app.ui.language, "Insert Page")).clicked() { let _ = app.run("layout.pages.insert", json!({"after": cur, "count": 1})); } }); @@ -223,7 +232,9 @@ fn page_menu(app: &mut DesignApp, ui: &mut egui::Ui, doc: &designcraft_doc::Docu if si > 0 && ui .button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Add to Previous Spread"))) - .on_hover_text("Spreads of up to 10 pages") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Spreads of up to 10 pages")); + }) .clicked() { let _ = app.run("layout.pages.toSpread", json!({"page": abs + 1, "spread": si - 1})); @@ -240,7 +251,7 @@ fn page_menu(app: &mut DesignApp, ui: &mut egui::Ui, doc: &designcraft_doc::Docu ("layout.removeOverrides", "Remove All Local Overrides"), ("layout.detachAll", "Detach All Objects from Parent"), ] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run(id, json!({"page": abs})); ui.close(); } diff --git a/crates/ui-egui/src/panels/properties.rs b/crates/ui-egui/src/panels/properties.rs index 1a61f1808..bf3f0b3af 100644 --- a/crates/ui-egui/src/panels/properties.rs +++ b/crates/ui-egui/src/panels/properties.rs @@ -137,7 +137,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { ("fit-frame", "fitFrameToContent", "Fit Frame to Content"), ("fit-center", "centerContent", "Center Content"), ] { - if icon_toggle(ui, icon, false, tip).clicked() { + if icon_toggle(ui, icon, false, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run("object.fit", json!({"mode": mode})); } } @@ -197,10 +197,10 @@ fn document_sections(app: &mut DesignApp, ui: &mut Ui) { let mut orient = None; place(ui, sub(b, 126.0, 0.0, 52.0, FIELD_H), |ui| { ui.spacing_mut().item_spacing.x = 4.0; - if icon_toggle(ui, "orient-portrait", !landscape, "Portrait").clicked() && landscape { + if icon_toggle(ui, "orient-portrait", !landscape, crate::i18n::tr(&app.ui.language, "Portrait")).clicked() && landscape { orient = Some(()); } - if icon_toggle(ui, "orient-landscape", landscape, "Landscape").clicked() && !landscape { + if icon_toggle(ui, "orient-landscape", landscape, crate::i18n::tr(&app.ui.language, "Landscape")).clicked() && !landscape { orient = Some(()); } }); @@ -258,7 +258,9 @@ fn document_sections(app: &mut DesignApp, ui: &mut Ui) { for (k, (icon, tip, v, x, y)) in fields.into_iter().enumerate() { let ir = sub(b, x, y + 2.0, 17.0, 17.0); icons::paint(ui.painter(), ir, icon, t.icon); - ui.interact(ir, ui.id().with(("micon", k)), Sense::hover()).on_hover_text(tip); + ui.interact(ir, ui.id().with(("micon", k)), Sense::hover()).on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, tip)); + }); let nv = place(ui, sub(b, x + 20.0, y, 73.0, FIELD_H), |ui| { NumField::measure(tip, Some(v), u).width(73.0).spinner().range(0.0, 8000.0).show(ui) }); @@ -326,13 +328,13 @@ fn document_sections(app: &mut DesignApp, ui: &mut Ui) { let (mut rulers, mut bl, mut dg) = (app.ui.rulers, app.ui.baseline_grid, app.ui.document_grid); section_row(ui, crate::i18n::tr(&app.ui.language, "Rulers & Grids"), |ui| { ui.spacing_mut().item_spacing.x = 14.0; - if icon_toggle(ui, "grid-document", dg, "Show Document Grid").clicked() { + if icon_toggle(ui, "grid-document", dg, crate::i18n::tr(&app.ui.language, "Show Document Grid")).clicked() { dg = !dg; } - if icon_toggle(ui, "grid-baseline", bl, "Show Baseline Grid").clicked() { + if icon_toggle(ui, "grid-baseline", bl, crate::i18n::tr(&app.ui.language, "Show Baseline Grid")).clicked() { bl = !bl; } - if icon_toggle(ui, "rulers", rulers, "Show Rulers").clicked() { + if icon_toggle(ui, "rulers", rulers, crate::i18n::tr(&app.ui.language, "Show Rulers")).clicked() { rulers = !rulers; } }); @@ -347,13 +349,13 @@ fn document_sections(app: &mut DesignApp, ui: &mut Ui) { let (mut guides, mut locked, mut smart) = (app.ui.guides, app.ui.guides_locked, app.ui.smart_guides); section_row(ui, crate::i18n::tr(&app.ui.language, "Guides"), |ui| { ui.spacing_mut().item_spacing.x = 14.0; - if icon_toggle(ui, "guides-smart", smart, "Smart Guides").clicked() { + if icon_toggle(ui, "guides-smart", smart, crate::i18n::tr(&app.ui.language, "Smart Guides")).clicked() { smart = !smart; } - if icon_toggle(ui, "guides-lock", locked, "Lock Guides").clicked() { + if icon_toggle(ui, "guides-lock", locked, crate::i18n::tr(&app.ui.language, "Lock Guides")).clicked() { locked = !locked; } - if icon_toggle(ui, "guides-show", guides, "Show Guides").clicked() { + if icon_toggle(ui, "guides-show", guides, crate::i18n::tr(&app.ui.language, "Show Guides")).clicked() { guides = !guides; } }); @@ -457,7 +459,7 @@ fn transform_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { set.insert("ref".into(), json!(rp)); let _ = app.run("transform.set", Value::Object(set)); } - if more_options(ui).clicked() { + if more_options(ui, &app.ui.language).clicked() { app.ui.open_panel = Some("info".into()); } } @@ -477,7 +479,7 @@ fn stroke_type_dropdown(app: &mut DesignApp, ui: &mut Ui, cur: &str, w: f32) { egui::Popup::menu(&resp).show(|ui| { ui.set_min_width(140.0); for (label, kind) in TYPES { - if ui.selectable_label(*kind == cur, *label).clicked() { + if ui.selectable_label(*kind == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let ty = if *kind == "dashed" { json!({"kind": "dashed", "pattern": [12.0, 4.0]}) } else { json!({"kind": kind}) }; let _ = app.run("object.stroke", json!({"type": ty})); ui.close(); @@ -573,7 +575,14 @@ fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { swatch_menu(app, &resp, &doc, &i.fill, |app, n| { let _ = app.run("object.fill", json!({"swatch": n})); }); - ui.painter().text(b.min + vec2(34.0, FIELD_H / 2.0), egui::Align2::LEFT_CENTER, "Fill", egui::FontId::proportional(11.5), t.text); + crate::rtl::paint( + ui.painter(), + b.min + vec2(34.0, FIELD_H / 2.0), + egui::Align2::LEFT_CENTER, + crate::i18n::tr(&app.ui.language, "Fill"), + egui::FontId::proportional(11.5), + t.text, + ); ui.add_space(4.0); // Stroke. let b = block(ui, FIELD_H); @@ -585,7 +594,7 @@ fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { swatch_menu(app, &resp, &doc, &i.stroke, |app, n| { let _ = app.run("object.stroke", json!({"swatch": n})); }); - if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, "Stroke", 56.0)).clicked() { + if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, crate::i18n::tr(&app.ui.language, "Stroke"), 56.0)).clicked() { app.ui.open_panel = Some("stroke".into()); } const WEIGHTS: &[f64] = &[0.0, 0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 20.0, 30.0, 40.0, 50.0, 100.0]; @@ -600,7 +609,7 @@ fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { // Corner. let b = block(ui, FIELD_H); icons::paint(ui.painter(), sub(b, 3.0, 1.0, 19.0, 19.0), "corner", t.accent); - if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, "Corner", 56.0)).clicked() { + if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, crate::i18n::tr(&app.ui.language, "Corner"), 56.0)).clicked() { app.ui.dialog = Some(crate::dialogs::Dialog::new("frameSize", json!({}))); } let (shape, size) = i.corner; @@ -615,7 +624,7 @@ fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { egui::Popup::menu(&resp).show(|ui| { ui.set_min_width(140.0); for s in CornerShape::ALL { - if ui.selectable_label(s == shape, s.label()).clicked() { + if ui.selectable_label(s == shape, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, s.label()))).clicked() { let _ = app.run("object.cornerOptions", json!({"shape": s, "size": if size > 0.0 { size } else { 12.0 }})); ui.close(); } @@ -626,7 +635,7 @@ fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { // Opacity. let b = block(ui, FIELD_H); icons::paint(ui.painter(), sub(b, 3.0, 1.0, 19.0, 19.0), "opacity", t.icon); - if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, "Opacity", 56.0)).clicked() { + if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, crate::i18n::tr(&app.ui.language, "Opacity"), 56.0)).clicked() { app.ui.open_panel = Some("effects".into()); } if let Some(v) = place(ui, sub(b, 96.0, 0.0, 51.0, FIELD_H), |ui| { @@ -644,10 +653,10 @@ fn appearance_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { ui.add_space(6.0); ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 10.0; - if icon_toggle(ui, "frame-dashed", false, "Frame Fitting").clicked() { + if icon_toggle(ui, "frame-dashed", false, crate::i18n::tr(&app.ui.language, "Frame Fitting")).clicked() { app.ui.open_panel = Some("textWrap".into()); } - if icon_toggle(ui, "fx", i.shadow, "Effects: Drop Shadow").clicked() { + if icon_toggle(ui, "fx", i.shadow, crate::i18n::tr(&app.ui.language, "Effects: Drop Shadow")).clicked() { let _ = app.run("object.dropShadow", json!({"on": !i.shadow})); } }); @@ -692,7 +701,7 @@ fn align_row(app: &mut DesignApp, ui: &mut Ui) { ("Align to Page", "page"), ("Align to Spread", "spread"), ] { - if ui.selectable_label(app.ui.align_to == id, label).clicked() { + if ui.selectable_label(app.ui.align_to == id, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { app.ui.align_to = id.into(); ui.close(); } @@ -708,7 +717,7 @@ fn align_row(app: &mut DesignApp, ui: &mut Ui) { ("vcenter", "objalign-vcenter", "Align vertical centers"), ("bottom", "objalign-bottom", "Align bottom edges"), ] { - if icon_toggle(ui, icon, false, tip).clicked() { + if icon_toggle(ui, icon, false, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run("object.align", json!({"edge": edge, "to": to})); } } @@ -725,7 +734,7 @@ fn wrap_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { ("wrap-jump", "jumpObject", "Jump Object"), ("wrap-next", "jumpToNextColumn", "Jump to Next Column"), ] { - if icon_toggle(ui, icon, i.wrap == mode, tip).clicked() { + if icon_toggle(ui, icon, i.wrap == mode, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run("object.textWrap", json!({"mode": mode})); } } @@ -737,7 +746,7 @@ fn wrap_section(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { let _ = app.run("object.textWrap", json!({"mode": i.wrap, "invert": inv})); } }); - if more_options(ui).clicked() { + if more_options(ui, &app.ui.language).clicked() { app.ui.open_panel = Some("textWrap".into()); } } @@ -750,7 +759,7 @@ fn arrange_menu(app: &mut DesignApp, resp: &egui::Response) { ("Send Backward", "object.sendBackward"), ("Send to Back", "object.sendToBack"), ] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run(cmd, json!({})); ui.close(); } @@ -771,7 +780,7 @@ fn convert_shape_menu(app: &mut DesignApp, resp: &egui::Response) { ("Line", "line"), ("Orthogonal Line", "orthogonalLine"), ] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { if let Err(e) = app.run("object.convertShape", json!({"to": to})) { app.status(format!("Convert Shape: {e}")); } @@ -806,7 +815,7 @@ fn quick_actions_object(app: &mut DesignApp, ui: &mut Ui, i: &SelInfo) { ui.spacing_mut().item_spacing.x = 10.0; let r = outline_button(ui, crate::i18n::tr(&app.ui.language, "Arrange"), hw); arrange_menu(app, &r); - if outline_button(ui, if i.count > 1 { "Group" } else { "Lock" }, hw).clicked() { + if outline_button(ui, crate::i18n::tr(&app.ui.language, if i.count > 1 { "Group" } else { "Lock" }), hw).clicked() { let _ = app.run(if i.count > 1 { "object.group" } else { "object.lock" }, json!({})); } }); @@ -829,7 +838,12 @@ fn text_style_section(app: &mut DesignApp, ui: &mut Ui) { let Some(a) = text_attrs(app) else { return }; let tab = app.ui.text_style_tab; let fw = full_width(ui); - if let Some(k) = widgets::segmented(ui, &["Paragraph Styles", "Character Styles"], tab as usize, fw) { + if let Some(k) = widgets::segmented( + ui, + &[crate::i18n::tr(&app.ui.language, "Paragraph Styles"), crate::i18n::tr(&app.ui.language, "Character Styles")], + tab as usize, + fw, + ) { app.ui.text_style_tab = k as u8; } ui.add_space(5.0); @@ -845,13 +859,15 @@ fn text_style_section(app: &mut DesignApp, ui: &mut Ui) { } }; let ov = if tab == 0 { a["paraOverrides"].as_u64().unwrap_or(0) + a["charOverrides"].as_u64().unwrap_or(0) } else { 0 }; - let label = if ov > 0 { format!("{cur}+") } else { cur.clone() }; + let shown = crate::i18n::style_name(&app.ui.language, &cur); + let label = if ov > 0 { format!("{shown}+") } else { shown.to_string() }; let resp = widgets::dropdown_h(ui, "", fw, 34.0); // "Ag" swatch + style name. let ag = Rect::from_min_size(resp.rect.min + vec2(6.0, 6.0), vec2(22.0, 22.0)); ui.painter().rect_filled(ag, 1.0, egui::Color32::WHITE); ui.painter().text(ag.center(), egui::Align2::CENTER_CENTER, "Ag", egui::FontId::new(13.0, egui::FontFamily::Proportional), egui::Color32::BLACK); - ui.painter().with_clip_rect(resp.rect.shrink2(vec2(20.0, 0.0))).text( + crate::rtl::paint( + &ui.painter().with_clip_rect(resp.rect.shrink2(vec2(20.0, 0.0))), pos2(ag.max.x + 8.0, resp.rect.center().y), egui::Align2::LEFT_CENTER, &label, @@ -863,7 +879,7 @@ fn text_style_section(app: &mut DesignApp, ui: &mut Ui) { ui.set_min_width(fw); egui::ScrollArea::vertical().max_height(320.0).show(ui, |ui| { for n in &names { - if ui.selectable_label(*n == cur, n).clicked() { + if ui.selectable_label(*n == cur, crate::rtl::widget(ui, crate::i18n::style_name(&app.ui.language, n))).clicked() { pick = Some(n.clone()); ui.close(); } @@ -880,20 +896,32 @@ fn text_style_section(app: &mut DesignApp, ui: &mut Ui) { ui.add_space(4.0); ui.horizontal(|ui| { ui.spacing_mut().item_spacing.x = 8.0; - if icon_toggle(ui, "pilcrow-menu", false, if tab == 0 { "Paragraph Styles panel" } else { "Character Styles panel" }).clicked() { + if icon_toggle( + ui, + "pilcrow-menu", + false, + crate::i18n::tr(&app.ui.language, if tab == 0 { "Paragraph Styles panel" } else { "Character Styles panel" }), + ) + .clicked() + { app.ui.open_panel = Some(if tab == 0 { "paragraphStyles" } else { "characterStyles" }.into()); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.spacing_mut().item_spacing.x = 12.0; ui.add_space(4.0); - if icon_toggle(ui, "style-new", false, "Create new style").clicked() { + if icon_toggle(ui, "style-new", false, crate::i18n::tr(&app.ui.language, crate::i18n::tr(&app.ui.language, "Create new style"))).clicked() + { let cmd = if tab == 0 { "style.paragraph.create" } else { "style.character.create" }; let _ = app.run(cmd, json!({})); } - if icon_toggle(ui, "style-clear", false, "Clear overrides").clicked() && tab == 0 && !cur.is_empty() { + if icon_toggle(ui, "style-clear", false, crate::i18n::tr(&app.ui.language, crate::i18n::tr(&app.ui.language, "Clear overrides"))) + .clicked() + && tab == 0 + && !cur.is_empty() + { let _ = app.run("style.paragraph.apply", json!({"name": cur, "clearOverrides": true})); } - if icon_toggle(ui, "style-load", false, "Styles panel").clicked() { + if icon_toggle(ui, "style-load", false, crate::i18n::tr(&app.ui.language, "Styles panel")).clicked() { app.ui.open_panel = Some(if tab == 0 { "paragraphStyles" } else { "characterStyles" }.into()); } }); @@ -926,9 +954,16 @@ fn text_appearance_section(app: &mut DesignApp, ui: &mut Ui) { let _ = app.run("type.char", json!({"attrs": {key: n}})); }); if k == 0 { - ui.painter().text(b.min + vec2(34.0, FIELD_H / 2.0), egui::Align2::LEFT_CENTER, label, egui::FontId::proportional(11.5), t.text); + crate::rtl::paint( + ui.painter(), + b.min + vec2(34.0, FIELD_H / 2.0), + egui::Align2::LEFT_CENTER, + crate::i18n::tr(&app.ui.language, label), + egui::FontId::proportional(11.5), + t.text, + ); } else { - if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, label, 56.0)).clicked() { + if place(ui, sub(b, 34.0, 0.0, 56.0, FIELD_H), |ui| link_label(ui, crate::i18n::tr(&app.ui.language, label), 56.0)).clicked() { app.ui.open_panel = Some("stroke".into()); } const WEIGHTS: &[f64] = &[0.0, 0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0]; @@ -1019,10 +1054,10 @@ fn character_section(app: &mut DesignApp, ui: &mut Ui) { _ => "Metrics".to_string(), }; place(ui, sub(b, 24.0, 26.0, half - 24.0, FIELD_H), |ui| { - let resp = widgets::dropdown(ui, &kern_label, half - 24.0); + let resp = widgets::dropdown(ui, crate::i18n::tr(&app.ui.language, &kern_label), half - 24.0); egui::Popup::menu(&resp).show(|ui| { for (label, v) in [("Metrics", json!({"kind": "metrics"})), ("Optical", json!({"kind": "optical"})), ("0", json!({"kind": "none"}))] { - if ui.selectable_label(kern_label == label, label).clicked() { + if ui.selectable_label(kern_label == label, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("type.char", json!({"attrs": {"kerning": v}})); ui.close(); } @@ -1036,7 +1071,7 @@ fn character_section(app: &mut DesignApp, ui: &mut Ui) { }) { let _ = app.run("type.char", json!({"attrs": {"tracking": v}})); } - if more_options(ui).clicked() { + if more_options(ui, &app.ui.language).clicked() { app.ui.open_panel = Some("character".into()); } } @@ -1068,7 +1103,7 @@ fn paragraph_section(app: &mut DesignApp, ui: &mut Ui) { } } }); - if more_options(ui).clicked() { + if more_options(ui, &app.ui.language).clicked() { app.ui.open_panel = Some("paragraph".into()); } } @@ -1080,7 +1115,7 @@ fn bullets_section(app: &mut DesignApp, ui: &mut Ui) { ui.spacing_mut().item_spacing.x = 10.0; for (icon, kind, tip) in [("bullets", "bullets", "Bulleted List"), ("numbering", "numbers", "Numbered List")] { let on = lt == kind; - if icon_toggle(ui, icon, on, tip).clicked() { + if icon_toggle(ui, icon, on, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run("type.para", json!({"attrs": {"listType": if on { "none" } else { kind }}})); } } @@ -1101,7 +1136,7 @@ fn quick_actions_text(app: &mut DesignApp, ui: &mut Ui) { let r = outline_button(ui, crate::i18n::tr(&app.ui.language, "Change Case"), hw); egui::Popup::menu(&r).show(|ui| { for (label, v) in [("UPPERCASE", "allCaps"), ("Small Caps", "smallCaps"), ("Normal", "normal")] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("type.char", json!({"attrs": {"capitalization": v}})); ui.close(); } @@ -1184,82 +1219,112 @@ pub fn character_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let c = a["chars"].clone(); let fam = c["fontFamily"].as_str().unwrap_or("").to_string(); let sty = c["fontStyle"].as_str().unwrap_or("").to_string(); - ui.label(egui::RichText::new("Character").strong()); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Character")).strong()); super::font_family_picker(app, ui, &fam, 220.0); super::font_style_picker(app, ui, &fam, &sty, 220.0); variable_font_axes(app, ui, &fam, &sty); // Language: hyphenation, spelling and typographer's quotes follow it. let lang = c["language"].as_str().unwrap_or("English: USA").to_string(); ui.horizontal(|ui| { - caption(ui, "Language"); - egui::ComboBox::from_id_salt("char_language").selected_text(&lang).width(170.0).show_ui(ui, |ui| { - for l in LANGUAGES { - if ui.selectable_label(*l == lang, *l).clicked() { - let _ = app.run("type.char", json!({"attrs": {"language": l}})); + caption(ui, crate::i18n::tr(&app.ui.language, "Language")); + egui::ComboBox::from_id_salt("char_language") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, &lang))) + .width(170.0) + .show_ui(ui, |ui| { + for l in LANGUAGES { + if ui.selectable_label(*l == lang, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { + let _ = app.run("type.char", json!({"attrs": {"language": l}})); + } } - } - }); + }); }); // Digits (World-Ready): shown for right-to-left paragraphs or once set. let digits = c["digits"].as_str().unwrap_or("default").to_string(); if digits != "default" || a["para"]["direction"].as_str() == Some("rightToLeft") { ui.horizontal(|ui| { - caption(ui, "Digits"); + caption(ui, crate::i18n::tr(&app.ui.language, "Digits")); let opts = [("default", "Default"), ("arabic", "Arabic"), ("hindi", "Hindi"), ("farsi", "Farsi"), ("native", "Native")]; let cur = opts.iter().find(|o| o.0 == digits).map_or("Default", |o| o.1); - egui::ComboBox::from_id_salt("char_digits").selected_text(cur).width(120.0).show_ui(ui, |ui| { - for (k, l) in opts { - if ui.selectable_label(k == digits, l).clicked() { - let _ = app.run("type.char", json!({"attrs": {"digits": k}})); + egui::ComboBox::from_id_salt("char_digits") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, cur))) + .width(120.0) + .show_ui(ui, |ui| { + for (k, l) in opts { + if ui.selectable_label(k == digits, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, l))).clicked() { + let _ = app.run("type.char", json!({"attrs": {"digits": k}})); + } } - } - }); + }); }); } egui::Grid::new("chargrid").num_columns(4).spacing(vec2(6.0, 4.0)).show(ui, |ui| { - caption(ui, "Size"); + caption(ui, crate::i18n::tr(&app.ui.language, "Size")); if let Some(v) = number(ui, "pcs", c["size"].as_f64(), " pt", 60.0, 2) { let _ = app.run("type.char", json!({"attrs": {"size": v}})); } - caption(ui, "Leading"); + caption(ui, crate::i18n::tr(&app.ui.language, "Leading")); let auto = c["leading"]["kind"].as_str() != Some("points"); let lv = if auto { c["size"].as_f64().map(|s| s * a["para"]["autoLeading"].as_f64().unwrap_or(1.2)) } else { c["leading"]["value"].as_f64() }; - if let Some(v) = number(ui, "pcl", lv, if auto { " (auto)" } else { " pt" }, 70.0, 2) { + if let Some(v) = number(ui, "pcl", lv, if auto { crate::i18n::tr(&app.ui.language, " (auto)") } else { " pt" }, 70.0, 2) { let _ = app.run("type.char", json!({"attrs": {"leading": {"kind": "points", "value": v}}})); } ui.end_row(); - caption(ui, "Tracking"); + caption(ui, crate::i18n::tr(&app.ui.language, "Tracking")); if let Some(v) = number(ui, "pct", c["tracking"].as_f64(), "", 60.0, 0) { let _ = app.run("type.char", json!({"attrs": {"tracking": v}})); } - caption(ui, "Baseline"); + caption(ui, crate::i18n::tr(&app.ui.language, "Baseline")); if let Some(v) = number(ui, "pcb", c["baselineShift"].as_f64(), " pt", 70.0, 2) { let _ = app.run("type.char", json!({"attrs": {"baselineShift": v}})); } ui.end_row(); }); ui.horizontal(|ui| { - caption(ui, "Color"); + caption(ui, crate::i18n::tr(&app.ui.language, "Color")); super::swatch_picker(app, ui, "tfill", c["fill"].as_str().map(str::to_string), |app, n| { let _ = app.run("type.char", json!({"attrs": {"fill": n}})); }); - ui.label(c["fill"].as_str().unwrap_or("")); + crate::rtl::label(ui, c["fill"].as_str().unwrap_or("")); }); ui.horizontal(|ui| { let caps = c["capitalization"].as_str() == Some("allCaps"); - if ui.selectable_label(caps, "TT").on_hover_text("All Caps").clicked() { + if ui + .selectable_label(caps, "TT") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "All Caps")); + }) + .clicked() + { let _ = app.run("type.char", json!({"attrs": {"capitalization": if caps { "normal" } else { "allCaps" }}})); } let ul = c["underline"].as_bool().unwrap_or(false); - if ui.selectable_label(ul, "U").on_hover_text("Underline").clicked() { + if ui + .selectable_label(ul, "U") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Underline")); + }) + .clicked() + { let _ = app.run("type.char", json!({"attrs": {"underline": !ul}})); } let st = c["strikethrough"].as_bool().unwrap_or(false); - if ui.selectable_label(st, "S").on_hover_text("Strikethrough").clicked() { + if ui + .selectable_label(st, "S") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Strikethrough")); + }) + .clicked() + { let _ = app.run("type.char", json!({"attrs": {"strikethrough": !st}})); } let sup = c["position"].as_str() == Some("superscript"); - if ui.selectable_label(sup, "T¹").on_hover_text("Superscript").clicked() { + if ui + .selectable_label(sup, "T¹") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Superscript")); + }) + .clicked() + { let _ = app.run("type.char", json!({"attrs": {"position": if sup { "normal" } else { "superscript" }}})); } let features: Vec = @@ -1271,16 +1336,16 @@ pub fn character_panel(app: &mut DesignApp, ui: &mut egui::Ui) { if !c[key].as_bool().unwrap_or(false) { continue; } - egui::CollapsingHeader::new(title).id_salt(key).show(ui, |ui| { + egui::CollapsingHeader::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, title))).id_salt(key).show(ui, |ui| { let size = c["size"].as_f64().unwrap_or(12.0); let auto_off = if key == "underline" { size * 0.12 } else { size * 0.3 }; egui::Grid::new((key, "opts")).num_columns(4).spacing(vec2(6.0, 4.0)).show(ui, |ui| { - caption(ui, "Weight"); + caption(ui, crate::i18n::tr(&app.ui.language, "Weight")); let w = c[format!("{key}Weight")].as_f64().unwrap_or(size / 14.0); if let Some(v) = number(ui, &format!("{key}w"), Some(w), " pt", 60.0, 2) { let _ = app.run("type.char", json!({"attrs": {format!("{key}Weight"): v.max(0.0)}})); } - caption(ui, "Offset"); + caption(ui, crate::i18n::tr(&app.ui.language, "Offset")); let o = c[format!("{key}Offset")].as_f64().unwrap_or(auto_off); if let Some(v) = number(ui, &format!("{key}o"), Some(o), " pt", 60.0, 2) { let _ = app.run("type.char", json!({"attrs": {format!("{key}Offset"): v}})); @@ -1288,12 +1353,12 @@ pub fn character_panel(app: &mut DesignApp, ui: &mut egui::Ui) { ui.end_row(); }); ui.horizontal(|ui| { - caption(ui, "Color"); + caption(ui, crate::i18n::tr(&app.ui.language, "Color")); let cur = c[format!("{key}Color")].as_str().filter(|s| !s.is_empty()).map(str::to_string); super::swatch_picker(app, ui, &format!("{key}c"), cur.clone(), |app, n| { let _ = app.run("type.char", json!({"attrs": {format!("{key}Color"): n}})); }); - ui.label(cur.unwrap_or_else(|| "(Text Color)".into())); + crate::rtl::label(ui, cur.unwrap_or_else(|| crate::i18n::tr(&app.ui.language, "(Text Color)").into())); }); }); } @@ -1305,7 +1370,7 @@ pub fn open_type_menu(app: &mut DesignApp, ui: &mut egui::Ui, features: &[String ui.set_min_width(220.0); for (tag, label, _) in otf::TOGGLES { let mut on = otf::is_on(features, tag); - if ui.checkbox(&mut on, *label).clicked() { + if ui.checkbox(&mut on, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("type.openType", json!({"feature": tag, "on": on})); ui.close(); } @@ -1323,7 +1388,7 @@ pub fn open_type_menu(app: &mut DesignApp, ui: &mut egui::Ui, features: &[String let mask = otf::stylistic_sets(features); for n in 1..=20u32 { let mut on = mask & (1 << (n - 1)) != 0; - if ui.checkbox(&mut on, format!("Set {n}")).clicked() { + if ui.checkbox(&mut on, crate::rtl::widget(ui, format!("{} {n}", crate::i18n::tr(&app.ui.language, "Set")))).clicked() { let sets: Vec = (1..=20).filter(|k| if *k == n { on } else { mask & (1 << (k - 1)) != 0 }).collect(); let _ = app.run("type.openType", json!({"stylisticSets": sets})); } @@ -1338,7 +1403,7 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { }; let u = units(app); let p = a["para"].clone(); - ui.label(egui::RichText::new("Paragraph").strong()); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Paragraph")).strong()); let cur: Align = serde_json::from_value(p["align"].clone()).unwrap_or_default(); ui.horizontal(|ui| { for (al, icon) in [ @@ -1356,9 +1421,9 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { // Paragraph Direction (World-Ready): switching also mirrors left/right alignment. let rtl = p["direction"] == "rightToLeft"; ui.horizontal(|ui| { - caption(ui, "Direction"); + caption(ui, crate::i18n::tr(&app.ui.language, "Direction")); for (on, label, dir) in [(!rtl, "Left to Right", "leftToRight"), (rtl, "Right to Left", "rightToLeft")] { - if ui.selectable_label(on, label).clicked() && !on { + if ui.selectable_label(on, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() && !on { let align = match cur { Align::Left => Align::Right, Align::Right => Align::Left, @@ -1377,11 +1442,11 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { .into_iter() .enumerate() { - caption(ui, la); + caption(ui, crate::i18n::tr(&app.ui.language, la)); if let Some(v) = measure(ui, &format!("pp{row}a"), p[ka].as_f64(), u, 64.0) { let _ = app.run("type.para", json!({"attrs": {ka: v}})); } - caption(ui, lb); + caption(ui, crate::i18n::tr(&app.ui.language, lb)); if let Some(v) = measure(ui, &format!("pp{row}b"), p[kb].as_f64(), u, 64.0) { let _ = app.run("type.para", json!({"attrs": {kb: v}})); } @@ -1399,7 +1464,7 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { } }); ui.horizontal(|ui| { - caption(ui, "Composer"); + caption(ui, crate::i18n::tr(&app.ui.language, "Composer")); let single = p["composer"].as_str() == Some("singleLine"); if ui.selectable_label(!single, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Paragraph"))).clicked() { let _ = app.run("type.para", json!({"attrs": {"composer": "paragraph"}})); @@ -1414,7 +1479,7 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { for (on_key, color_key, label) in [("shadingOn", "shadingColor", "Shading"), ("borderOn", "borderColor", "Border")] { ui.horizontal(|ui| { let mut on = p[on_key].as_bool().unwrap_or(false); - if ui.checkbox(&mut on, label).changed() { + if ui.checkbox(&mut on, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).changed() { let _ = app.run("type.para", json!({ on_key: on })); } if on { @@ -1439,7 +1504,7 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { } if p["shadingOn"].as_bool().unwrap_or(false) || p["borderOn"].as_bool().unwrap_or(false) { ui.horizontal(|ui| { - caption(ui, "Offsets"); + caption(ui, crate::i18n::tr(&app.ui.language, "Offsets")); let o = p["borderOffsets"][0].as_f64().or(p["shadingOffsets"][0].as_f64()); if let Some(v) = number(ui, "pbo", o, " pt", 50.0, 2) { let o = [v; 4]; @@ -1450,7 +1515,7 @@ pub fn paragraph_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let ps = a["paragraphStyle"].as_str().unwrap_or("").to_string(); let ov = a["paraOverrides"].as_u64().unwrap_or(0) + a["charOverrides"].as_u64().unwrap_or(0); ui.horizontal(|ui| { - caption(ui, "Style"); + caption(ui, crate::i18n::tr(&app.ui.language, "Style")); super::para_style_picker(app, ui, &ps, ov > 0, 190.0); }); } @@ -1466,102 +1531,111 @@ pub fn stroke_panel(app: &mut DesignApp, ui: &mut egui::Ui) { }; let t = Tokens::get(ui.ctx()); egui::Grid::new("stroke_panel").num_columns(2).spacing(vec2(8.0, 6.0)).show(ui, |ui| { - caption(ui, "Weight:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Weight:")); if let Some(v) = number(ui, "sw", Some(i.stroke_weight), " pt", 60.0, 3) { let _ = app.run("object.stroke", json!({"weight": v})); } ui.end_row(); - caption(ui, "Cap:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Cap:")); ui.horizontal(|ui| { for (label, v, cur) in [("Butt", "butt", Cap::Butt), ("Round", "round", Cap::Round), ("Projecting", "projecting", Cap::Projecting)] { - if ui.selectable_label(st.cap == cur, label).clicked() { + if ui.selectable_label(st.cap == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("object.stroke", json!({"cap": v})); } } }); ui.end_row(); - caption(ui, "Join:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Join:")); ui.horizontal(|ui| { for (label, v, cur) in [("Miter", "miter", Join::Miter), ("Round", "round", Join::Round), ("Bevel", "bevel", Join::Bevel)] { - if ui.selectable_label(st.join == cur, label).clicked() { + if ui.selectable_label(st.join == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("object.stroke", json!({"join": v})); } } }); ui.end_row(); - caption(ui, "Miter Limit:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Miter Limit:")); if let Some(v) = number(ui, "smiter", Some(st.miter_limit), " x", 60.0, 0) { let _ = app.run("object.stroke", json!({"miterLimit": v})); } ui.end_row(); - caption(ui, "Align Stroke:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Align Stroke:")); ui.horizontal(|ui| { for (label, a, cur) in [("Center", "center", StrokeAlign::Center), ("Inside", "inside", StrokeAlign::Inside), ("Outside", "outside", StrokeAlign::Outside)] { - if ui.selectable_label(st.align == cur, label).clicked() { + if ui.selectable_label(st.align == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("object.stroke", json!({"align": a})); } } }); ui.end_row(); - caption(ui, "Type:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Type:")); let custom: Vec = app.session.active().map(|d| d.doc.stroke_styles.iter().map(|x| x.name.clone()).collect()).unwrap_or_default(); let shown = match &st.kind { StrokeType::Style { name } => name.clone(), k => k.label().to_string(), }; - egui::ComboBox::from_id_salt("stype").selected_text(&shown).width(140.0).show_ui(ui, |ui| { - let built_in = [ - "solid", - "dashed", - "dotted", - "thickThin", - "thinThick", - "thinThin", - "thickThick", - "thinThickThin", - "thickThinThick", - "wavy", - "hashed", - ]; - for k in built_in { - let ty = if k == "dashed" { json!({"kind": k, "pattern": [12.0, 4.0]}) } else { json!({"kind": k}) }; - let label = serde_json::from_value::(ty.clone()).map(|k| k.label()).unwrap_or(""); - if ui.selectable_label(shown == label, label).clicked() { - let _ = app.run("object.stroke", json!({"type": ty})); + egui::ComboBox::from_id_salt("stype") + .selected_text(crate::rtl::widget( + ui, + if matches!(st.kind, StrokeType::Style { .. }) { &shown } else { crate::i18n::tr(&app.ui.language, &shown) }, + )) + .width(140.0) + .show_ui(ui, |ui| { + let built_in = [ + "solid", + "dashed", + "dotted", + "thickThin", + "thinThick", + "thinThin", + "thickThick", + "thinThickThin", + "thickThinThick", + "wavy", + "hashed", + ]; + for k in built_in { + let ty = if k == "dashed" { json!({"kind": k, "pattern": [12.0, 4.0]}) } else { json!({"kind": k}) }; + let label = serde_json::from_value::(ty.clone()).map(|k| k.label()).unwrap_or(""); + if ui.selectable_label(shown == label, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { + let _ = app.run("object.stroke", json!({"type": ty})); + } } - } - if !custom.is_empty() { - ui.separator(); - } - for n in &custom { - if ui.selectable_label(shown == *n, n).clicked() { - let _ = app.run("object.stroke", json!({"type": {"kind": "style", "name": n}})); + if !custom.is_empty() { + ui.separator(); } - } - }); - ui.end_row(); - for (key, label, cur, flip) in [("start", "Start/End:", st.start, true), ("end", "", st.end, false)] { - caption(ui, label); - egui::ComboBox::from_id_salt(key).selected_text(cur.label()).width(140.0).show_ui(ui, |ui| { - for a in Arrowhead::ALL { - ui.horizontal(|ui| { - arrow_preview(ui, a, flip, t.text_strong); - if ui.selectable_label(a == cur, a.label()).clicked() { - let _ = app.run("object.stroke", json!({key: a})); - } - }); + for n in &custom { + if ui.selectable_label(shown == *n, n).clicked() { + let _ = app.run("object.stroke", json!({"type": {"kind": "style", "name": n}})); + } } }); + ui.end_row(); + for (key, label, cur, flip) in [("start", "Start/End:", st.start, true), ("end", "", st.end, false)] { + caption(ui, crate::i18n::tr(&app.ui.language, label)); + egui::ComboBox::from_id_salt(key) + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, cur.label()))) + .width(140.0) + .show_ui(ui, |ui| { + for a in Arrowhead::ALL { + ui.horizontal(|ui| { + arrow_preview(ui, a, flip, t.text_strong); + if ui.selectable_label(a == cur, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, a.label()))).clicked() { + let _ = app.run("object.stroke", json!({key: a})); + } + }); + } + }); ui.end_row(); } - caption(ui, "Gap Color:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Gap Color:")); super::swatch_picker(app, ui, "sgap", Some(st.gap_swatch.clone()), |app, n| { let _ = app.run("object.stroke", json!({"gapSwatch": n})); }); ui.end_row(); - caption(ui, "Color:"); + caption(ui, crate::i18n::tr(&app.ui.language, "Color:")); super::swatch_picker(app, ui, "spick", Some(i.stroke.clone()), |app, n| { let _ = app.run("object.stroke", json!({"swatch": n})); }); @@ -1621,14 +1695,14 @@ pub fn wrap_panel(app: &mut DesignApp, ui: &mut egui::Ui) { ("wrap-next", "jumpToNextColumn", "Jump to Next Column"), ] { let on = info.as_ref().is_some_and(|i| i.wrap == mode); - if icons::button(ui, icon, 26.0, on, tip).clicked() { + if icons::button(ui, icon, 26.0, on, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run("object.textWrap", json!({"mode": mode})); } } }); let u = units(app); ui.horizontal(|ui| { - caption(ui, "Offset"); + caption(ui, crate::i18n::tr(&app.ui.language, "Offset")); if let Some(v) = measure(ui, "wo", Some(0.0), u, 60.0) { let _ = app.run("object.textWrap", json!({"mode": info.as_ref().map(|i| i.wrap).unwrap_or("boundingBox"), "offset": v})); } @@ -1637,12 +1711,12 @@ pub fn wrap_panel(app: &mut DesignApp, ui: &mut egui::Ui) { /// Pathfinder panel: combine the selected shapes; make or release compound paths. pub fn pathfinder_panel(app: &mut DesignApp, ui: &mut egui::Ui) { - ui.label(egui::RichText::new("Pathfinder").strong()); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Pathfinder")).strong()); ui.horizontal_wrapped(|ui| { for (label, op) in [("Add", "add"), ("Subtract", "subtract"), ("Intersect", "intersect"), ("Exclude Overlap", "exclude"), ("Minus Back", "minusBack")] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("object.pathfinder", json!({"op": op})); } } @@ -1678,7 +1752,9 @@ pub fn align_panel(app: &mut DesignApp, ui: &mut egui::Ui) { "vcenter" => "↕", _ => "⤓", }) - .on_hover_text(tip) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, tip)); + }) .clicked() { let _ = app.run("object.align", json!({"edge": edge})); @@ -1686,10 +1762,10 @@ pub fn align_panel(app: &mut DesignApp, ui: &mut egui::Ui) { } }); ui.horizontal(|ui| { - if ui.small_button("Distribute ↔").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Distribute ↔"))).clicked() { let _ = app.run("object.distribute", json!({"axis": "horizontal"})); } - if ui.small_button("Distribute ↕").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Distribute ↕"))).clicked() { let _ = app.run("object.distribute", json!({"axis": "vertical"})); } }); @@ -1701,17 +1777,20 @@ pub fn links_panel(app: &mut DesignApp, ui: &mut egui::Ui) { // Linked stories (Place and Link). let stories = app.session.execute("story.links", &json!({})).ok().and_then(|v| v.as_array().cloned()).unwrap_or_default(); if !stories.is_empty() { - ui.label(egui::RichText::new("Linked Stories").strong()); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Linked Stories")).strong()); for l in &stories { let out = l["outOfDate"].as_bool().unwrap_or(false); ui.horizontal(|ui| { - ui.label(format!("Story {} ← story {}", l["story"], l["parent"])); + crate::rtl::label(ui, format!("Story {} ← story {}", l["story"], l["parent"])); if out { - ui.label(crate::rtl::widget( + crate::rtl::label( ui, - egui::RichText::new(crate::i18n::tr(&app.ui.language, "⚠ out of date")).color(egui::Color32::from_rgb(230, 160, 40)), - )); - if ui.small_button("Update").clicked() { + crate::rtl::widget( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, "⚠ out of date")).color(egui::Color32::from_rgb(230, 160, 40)), + ), + ); + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Update"))).clicked() { let _ = app.run("story.updateLink", json!({"story": l["story"]})); } } @@ -1741,7 +1820,7 @@ pub fn links_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let sel_key = egui::Id::new("links_panel_sel"); let mut chosen: Option = ui.data(|d| d.get_temp(sel_key)); egui::Grid::new("links_grid").num_columns(3).striped(true).spacing([10.0, 4.0]).show(ui, |ui| { - ui.label(egui::RichText::new("Name").color(t.text_dim).size(11.0)); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Name")).color(t.text_dim).size(11.0)); crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Status")).color(t.text_dim).size(11.0)); crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "Page · PPI")).color(t.text_dim).size(11.0)); ui.end_row(); @@ -1757,7 +1836,7 @@ pub fn links_panel(app: &mut DesignApp, ui: &mut egui::Ui) { "embedded" => ("Embedded", t.text_dim), _ => ("OK", t.text_dim), }; - ui.label(egui::RichText::new(txt).color(col).size(11.0)); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, txt)).color(col).size(11.0)); let uses = r["uses"].as_array().cloned().unwrap_or_default(); let info = match uses.first() { Some(u) => { @@ -1766,11 +1845,14 @@ pub fn links_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let more = if uses.len() > 1 { format!(" (+{})", uses.len() - 1) } else { String::new() }; format!("{page}{ppi}{more}") } - None => "unused".into(), + None => crate::i18n::tr(&app.ui.language, "unused").into(), }; // Low effective resolution for print is flagged like the Preflight check. let low = uses.first().and_then(|u| u["ppi"].as_f64()).is_some_and(|p| p < 150.0); - ui.label(egui::RichText::new(info).size(11.0).color(if low { egui::Color32::from_rgb(0xe8, 0xb3, 0x2c) } else { t.text_dim })); + crate::rtl::label( + ui, + egui::RichText::new(info).size(11.0).color(if low { egui::Color32::from_rgb(0xe8, 0xb3, 0x2c) } else { t.text_dim }), + ); ui.end_row(); } }); @@ -1802,7 +1884,9 @@ pub fn links_panel(app: &mut DesignApp, ui: &mut egui::Ui) { // Choose any file in the folder: missing links are looked up there by name. if ui .button(crate::i18n::tr(&app.ui.language, "Relink to Folder…")) - .on_hover_text("Pick a file in the folder holding the moved graphics") + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Pick a file in the folder holding the moved graphics")); + }) .clicked() && let Some(path) = app.services.pick_open.as_mut().and_then(|pick| pick("relink")) && let Some(dir) = std::path::Path::new(&path).parent() @@ -1821,17 +1905,34 @@ pub fn info_panel(app: &mut DesignApp, ui: &mut egui::Ui) { match sel_info(app) { Some(i) => { let f = |v: f64| designcraft_geom::format_measure(v, u); - ui.label(format!("X: {} Y: {}", f(i.page_rect.x0), f(i.page_rect.y0))); - ui.label(format!("W: {} H: {}", f(i.page_rect.width()), f(i.page_rect.height()))); - ui.label(format!("Fill: {} Stroke: {}", i.fill, i.stroke)); + crate::rtl::label(ui, format!("X: {} Y: {}", f(i.page_rect.x0), f(i.page_rect.y0))); + crate::rtl::label(ui, format!("W: {} H: {}", f(i.page_rect.width()), f(i.page_rect.height()))); + crate::rtl::label( + ui, + format!("{}: {} {}: {}", crate::i18n::tr(&app.ui.language, "Fill"), i.fill, crate::i18n::tr(&app.ui.language, "Stroke"), i.stroke), + ); } None => { if let Some(st) = app.session.active() { - ui.label(format!("{} pages · {} stories · {} items", st.doc.page_count(), st.doc.stories.len(), st.doc.all_items().len())); + crate::rtl::label( + ui, + format!( + "{} {} · {} {} · {} {}", + st.doc.page_count(), + crate::i18n::tr(&app.ui.language, "pages"), + st.doc.stories.len(), + crate::i18n::tr(&app.ui.language, "stories"), + st.doc.all_items().len(), + crate::i18n::tr(&app.ui.language, "items") + ), + ); } } } - ui.label(egui::RichText::new(format!("Render {:.1} ms", app.perf.render_ms)).color(t.text_dim)); + crate::rtl::label( + ui, + egui::RichText::new(format!("{} {:.1} ms", crate::i18n::tr(&app.ui.language, "Render"), app.perf.render_ms)).color(t.text_dim), + ); } pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) { @@ -1860,14 +1961,17 @@ pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) { B::Luminosity, ]; ui.horizontal(|ui| { - egui::ComboBox::from_id_salt("blend").selected_text(it.blend.label()).width(130.0).show_ui(ui, |ui| { - for m in MODES { - if ui.selectable_label(m == it.blend, m.label()).clicked() { - let _ = app.run("object.opacity", json!({"opacity": it.opacity, "blend": m})); + egui::ComboBox::from_id_salt("blend") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, it.blend.label()))) + .width(130.0) + .show_ui(ui, |ui| { + for m in MODES { + if ui.selectable_label(m == it.blend, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, m.label()))).clicked() { + let _ = app.run("object.opacity", json!({"opacity": it.opacity, "blend": m})); + } } - } - }); - caption(ui, "Opacity"); + }); + caption(ui, crate::i18n::tr(&app.ui.language, "Opacity")); if let Some(v) = number(ui, "eop", Some(it.opacity as f64 * 100.0), "%", 46.0, 0) { let _ = app.run("object.opacity", json!({"opacity": (v / 100.0).clamp(0.0, 1.0)})); } @@ -1880,11 +1984,11 @@ pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) { } if ds.on { egui::Grid::new("dsg").num_columns(4).spacing(vec2(6.0, 4.0)).show(ui, |ui| { - caption(ui, "Distance"); + caption(ui, crate::i18n::tr(&app.ui.language, "Distance")); if let Some(v) = number(ui, "dsd", Some(ds.distance), " pt", 56.0, 1) { let _ = app.run("object.dropShadow", json!({"on": true, "distance": v})); } - caption(ui, "Angle"); + caption(ui, crate::i18n::tr(&app.ui.language, "Angle")); let global = app.session.active().map_or(120.0, |st| st.doc.settings.global_light); if let Some(v) = number(ui, "dsa", Some(if ds.global_light { global } else { ds.angle }), "°", 50.0, 0) { // With Use Global Light on, the angle is the document's (every such shadow moves). @@ -1895,19 +1999,19 @@ pub fn effects_panel(app: &mut DesignApp, ui: &mut egui::Ui) { }; } ui.end_row(); - ui.label(""); - ui.label(""); + crate::rtl::label(ui, ""); + crate::rtl::label(ui, ""); caption(ui, ""); let mut gl = ds.global_light; if ui.checkbox(&mut gl, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Use Global Light"))).changed() { let _ = app.run("object.dropShadow", json!({"on": true, "globalLight": gl})); } ui.end_row(); - caption(ui, "Opacity"); + caption(ui, crate::i18n::tr(&app.ui.language, "Opacity")); if let Some(v) = number(ui, "dso", Some(ds.opacity as f64 * 100.0), "%", 56.0, 0) { let _ = app.run("object.dropShadow", json!({"on": true, "opacity": v / 100.0})); } - caption(ui, "Size"); + caption(ui, crate::i18n::tr(&app.ui.language, "Size")); if let Some(v) = number(ui, "dss", Some(ds.size), " pt", 50.0, 1) { let _ = app.run("object.dropShadow", json!({"on": true, "size": v})); } @@ -1927,7 +2031,19 @@ pub fn preflight_panel(app: &mut DesignApp, ui: &mut egui::Ui) { 4.5, if errors == 0 { egui::Color32::from_rgb(60, 200, 90) } else { egui::Color32::from_rgb(235, 50, 50) }, ); - ui.label(if issues.is_empty() { "No errors".to_string() } else { format!("{errors} error(s), {} warning(s)", issues.len() - errors) }); + crate::rtl::label( + ui, + if issues.is_empty() { + crate::i18n::tr(&app.ui.language, "No errors").to_string() + } else { + format!( + "{errors} {}, {} {}", + crate::i18n::tr(&app.ui.language, "errors"), + issues.len() - errors, + crate::i18n::tr(&app.ui.language, "warnings") + ) + }, + ); }); divider(ui); for i in issues { @@ -1935,7 +2051,7 @@ pub fn preflight_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let text = format!("{} {}", if i.severity == "error" { "⛔" } else { "⚠" }, i.message); let resp = ui.add(egui::Button::new(egui::RichText::new(text).size(11.0)).frame(false)); if let Some(pg) = &page { - ui.label(egui::RichText::new(format!(" page {pg}")).size(10.0).color(t.text_dim)); + crate::rtl::label(ui, egui::RichText::new(format!(" page {pg}")).size(10.0).color(t.text_dim)); } if resp.clicked() { if let Some(id) = i.item { @@ -1963,7 +2079,7 @@ pub fn attributes_panel(app: &mut DesignApp, ui: &mut egui::Ui) { egui::Grid::new("attributes_panel").num_columns(2).spacing(vec2(16.0, 6.0)).show(ui, |ui| { for (i, (key, label, on)) in rows.into_iter().enumerate() { let mut v = on; - if ui.checkbox(&mut v, label).changed() { + if ui.checkbox(&mut v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).changed() { let _ = app.run("object.attributes", json!({ key: v })); } if i % 2 == 1 { diff --git a/crates/ui-egui/src/panels/styles.rs b/crates/ui-egui/src/panels/styles.rs index 3035afe5b..c3412095e 100644 --- a/crates/ui-egui/src/panels/styles.rs +++ b/crates/ui-egui/src/panels/styles.rs @@ -17,10 +17,11 @@ fn list(app: &mut DesignApp, ui: &mut egui::Ui, para: bool) { let attrs = super::text_attrs(app); let current = attrs.as_ref().and_then(|a| a[if para { "paragraphStyle" } else { "characterStyle" }].as_str().map(str::to_string)); let overrides = attrs.as_ref().map(|a| a[if para { "paraOverrides" } else { "charOverrides" }].as_u64().unwrap_or(0)).unwrap_or(0); - ui.label( + crate::rtl::label( + ui, egui::RichText::new(match ¤t { - Some(c) => format!("{c}{}", if overrides > 0 { "+" } else { "" }), - None => "No text selected".into(), + Some(c) => format!("{}{}", crate::i18n::style_name(&app.ui.language, c), if overrides > 0 { "+" } else { "" }), + None => crate::i18n::tr(&app.ui.language, "No text selected").into(), }) .size(11.0) .color(t.text_dim), @@ -52,7 +53,15 @@ fn list(app: &mut DesignApp, ui: &mut egui::Ui, para: bool) { egui::RichText::new(crate::i18n::tr(&app.ui.language, "⌥-click clears overrides")).size(10.5).color(t.text_disabled), )); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if icons::button(ui, "plus", 20.0, false, "Create New Style from Selection").clicked() { + if icons::button( + ui, + "plus", + 20.0, + false, + crate::i18n::tr(&app.ui.language, crate::i18n::tr(&app.ui.language, "Create New Style from Selection")), + ) + .clicked() + { let cmd = if para { "style.paragraph.create" } else { "style.character.create" }; let base = if para { "Paragraph Style 1" } else { "Character Style 1" }; let _ = app.run(cmd, json!({"name": base, "fromSelection": true})); @@ -79,7 +88,14 @@ fn style_row(app: &mut DesignApp, ui: &mut egui::Ui, para: bool, n: &str, curren ui.painter().rect_filled(row, 0.0, t.hover); } let shown = n.rsplit('/').next().unwrap_or(n); - ui.painter().text(row.min + vec2(8.0, 11.0), egui::Align2::LEFT_CENTER, shown, egui::FontId::proportional(12.5), t.text); + crate::rtl::paint( + ui.painter(), + row.min + vec2(8.0, 11.0), + egui::Align2::LEFT_CENTER, + if n.contains('/') { shown } else { crate::i18n::style_name(&app.ui.language, shown) }, + egui::FontId::proportional(12.5), + t.text, + ); if resp.double_clicked() && para { app.ui.dialog = Some(crate::dialogs::Dialog::new("paragraphStyleOptions", json!({"name": n}))); } else if resp.clicked() { @@ -89,7 +105,17 @@ fn style_row(app: &mut DesignApp, ui: &mut egui::Ui, para: bool, n: &str, curren } let kind = if para { "paragraph" } else { "character" }; resp.context_menu(|ui| { - if ui.button(format!("Apply \"{shown}\"")).clicked() { + if ui + .button(crate::rtl::widget( + ui, + format!( + "{} \"{}\"", + crate::i18n::tr(&app.ui.language, "Apply"), + if n.contains('/') { shown } else { crate::i18n::style_name(&app.ui.language, shown) } + ), + )) + .clicked() + { let cmd = if para { "style.paragraph.apply" } else { "style.character.apply" }; let _ = app.run(cmd, json!({"name": n})); ui.close(); @@ -113,7 +139,11 @@ fn style_row(app: &mut DesignApp, ui: &mut egui::Ui, para: bool, n: &str, curren ui.separator(); let id = egui::Id::new(("new_style_group", para)); let mut name: String = ui.data(|d| d.get_temp(id)).unwrap_or_default(); - let r = ui.add(egui::TextEdit::singleline(&mut name).hint_text("New group…").desired_width(140.0)); + let r = ui.add( + egui::TextEdit::singleline(&mut name) + .hint_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "New group…"))) + .desired_width(140.0), + ); if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) && !name.trim().is_empty() { let _ = app.run("style.group", json!({"kind": kind, "names": [n], "group": name.trim()})); name.clear(); diff --git a/crates/ui-egui/src/panels/swatches.rs b/crates/ui-egui/src/panels/swatches.rs index 5bd482ffd..d80b72883 100644 --- a/crates/ui-egui/src/panels/swatches.rs +++ b/crates/ui-egui/src/panels/swatches.rs @@ -14,7 +14,12 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let text = st.selection.text.is_some(); let fill_cur = super::sel_info(app).map(|i| i.fill); ui.horizontal(|ui| { - ui.label(egui::RichText::new(if text { "Applies to text" } else { "Applies to fill" }).size(11.0).color(t.text_dim)); + crate::rtl::label( + ui, + egui::RichText::new(crate::i18n::tr(&app.ui.language, if text { "Applies to text" } else { "Applies to fill" })) + .size(11.0) + .color(t.text_dim), + ); }); // Unnamed colours (mixed in the Color panel) aren't listed until Add to Swatches; swatches in // colour groups are listed under their folder. @@ -65,7 +70,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { egui::RichText::new(crate::i18n::tr(&app.ui.language, "Right-click: stroke, groups")).size(10.5).color(t.text_disabled), )); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if icons::button(ui, "plus", 20.0, false, "New Swatch").clicked() { + if icons::button(ui, "plus", 20.0, false, crate::i18n::tr(&app.ui.language, "New Swatch")).clicked() { let _ = app.run("swatch.create", json!({"color": {"c": 0, "m": 50, "y": 100, "k": 0}})); } ui.menu_button("☰", |ui| { @@ -308,7 +313,7 @@ pub fn color_panel(app: &mut DesignApp, ui: &mut egui::Ui) { spectrum(app, ui, target); ui.horizontal(|ui| { let unnamed = sw.as_ref().is_some_and(|w| w.hidden); - if ui.add_enabled(unnamed, egui::Button::new("Add to Swatches")).clicked() { + if ui.add_enabled(unnamed, egui::Button::new(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Add to Swatches")))).clicked() { let _ = app.run("swatch.addToSwatches", json!({"target": target})); } }); @@ -413,15 +418,24 @@ pub fn gradient_panel(app: &mut DesignApp, ui: &mut egui::Ui) { }; let radial = g.kind == designcraft_color::GradientKind::Radial; ui.horizontal(|ui| { - crate::widgets::caption(ui, "Type:"); - egui::ComboBox::from_id_salt("grad_type").selected_text(g.kind.label()).width(100.0).show_ui(ui, |ui| { - for (k, v) in [("Linear", "linear"), ("Radial", "radial")] { - if ui.selectable_label(g.kind.label() == k, k).clicked() { - let _ = app.run("object.gradient", json!({"kind": v})); + crate::widgets::caption(ui, crate::i18n::tr(&app.ui.language, "Type:")); + egui::ComboBox::from_id_salt("grad_type") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, g.kind.label()))) + .width(100.0) + .show_ui(ui, |ui| { + for (k, v) in [("Linear", "linear"), ("Radial", "radial")] { + if ui.selectable_label(g.kind.label() == k, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, k))).clicked() { + let _ = app.run("object.gradient", json!({"kind": v})); + } } - } - }); - if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Reverse"))).on_hover_text("Reverse the gradient").clicked() { + }); + if ui + .button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Reverse"))) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Reverse the gradient")); + }) + .clicked() + { let _ = app.run("object.gradient", json!({"reverse": true})); } }); @@ -429,7 +443,7 @@ pub fn gradient_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let drag_id = egui::Id::new("grad_drag"); let mut sel: usize = ui.data(|d| d.get_temp(sel_id)).unwrap_or(0).min(g.stops.len().saturating_sub(1)); ui.horizontal(|ui| { - crate::widgets::caption(ui, "Location:"); + crate::widgets::caption(ui, crate::i18n::tr(&app.ui.language, "Location:")); let loc = g.stops.get(sel).map(|s| s.offset as f64 * 100.0); if let Some(v) = crate::widgets::number(ui, "grad_loc", loc, " %", 54.0, 1) { let mut stops = stops_json(&g); @@ -437,7 +451,7 @@ pub fn gradient_panel(app: &mut DesignApp, ui: &mut egui::Ui) { let _ = app.run("object.gradient", json!({"stops": stops})); } if !radial { - crate::widgets::caption(ui, "Angle:"); + crate::widgets::caption(ui, crate::i18n::tr(&app.ui.language, "Angle:")); let angle = fill.gradient_vector.map(|[x0, y0, x1, y1]| (-(y1 - y0)).atan2(x1 - x0).to_degrees()).or(fill.gradient_angle).unwrap_or(0.0); if let Some(v) = crate::widgets::number(ui, "grad_angle", Some(angle), "°", 54.0, 1) { let _ = app.run("object.gradient", json!({"angle": v})); @@ -546,7 +560,7 @@ pub fn gradient_panel(app: &mut DesignApp, ui: &mut egui::Ui) { // The selected stop's colour. if let Some(s) = g.stops.get(sel) { ui.horizontal(|ui| { - crate::widgets::caption(ui, "Stop Color:"); + crate::widgets::caption(ui, crate::i18n::tr(&app.ui.language, "Stop Color:")); let [r, gg, bb] = s.color.to_rgb(); let mut rgb = [(r * 255.0) as u8, (gg * 255.0) as u8, (bb * 255.0) as u8]; if egui::color_picker::color_edit_button_srgb(ui, &mut rgb).changed() { diff --git a/crates/ui-egui/src/panels/table.rs b/crates/ui-egui/src/panels/table.rs index e91800e39..3f2d871ff 100644 --- a/crates/ui-egui/src/panels/table.rs +++ b/crates/ui-egui/src/panels/table.rs @@ -32,19 +32,19 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let _ = app.run(id, p); }; ui.horizontal(|ui| { - caption(ui, "Cell Type"); - if ui.small_button("Graphic…").clicked() + caption(ui, crate::i18n::tr(&app.ui.language, "Cell Type")); + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Graphic…"))).clicked() && let Err(e) = app.run("app.graphicCell", json!({})) { app.status(format!("Table: {e}")); } - if ui.small_button("Text").clicked() { + if ui.small_button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, "Text"))).clicked() { let _ = app.run("table.textCell", json!({})); } }); // Move the target row / column (InDesign drags them). ui.horizontal(|ui| { - caption(ui, "Move"); + caption(ui, crate::i18n::tr(&app.ui.language, "Move")); let (nr, nc) = (rows.len(), cols.len()); for (label, id, from, to, ok) in [ ("Row ↑", "table.moveRow", r0, r0.saturating_sub(1), r0 > 0), @@ -68,7 +68,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { let ccur = cell["style"].as_str().filter(|s| !s.is_empty()).unwrap_or(designcraft_doc::NO_CELL_STYLE).to_string(); for (label, kind, names, cur) in [("Table Style", "table", tnames, tcur), ("Cell Style", "cell", cnames, ccur)] { ui.horizontal(|ui| { - caption(ui, label); + caption(ui, crate::i18n::tr(&app.ui.language, label)); egui::ComboBox::from_id_salt(("tp_style", kind)).selected_text(&cur).width(130.0).show_ui(ui, |ui| { for n in &names { if ui.selectable_label(*n == cur, n).clicked() { @@ -87,7 +87,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { } } egui::Grid::new("table_panel").num_columns(4).spacing([6.0, 6.0]).show(ui, |ui| { - caption(ui, "Rows"); + caption(ui, crate::i18n::tr(&app.ui.language, "Rows")); if let Some(v) = number(ui, "tp_rows", Some(rows.len() as f64), "", 52.0, 0) { let n = v.round().max(1.0) as usize; if n > rows.len() { @@ -100,7 +100,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { run(app, "table.deleteRow", json!({"table": tid, "rows": [n, rows.len() - 1]})); } } - caption(ui, "Columns"); + caption(ui, crate::i18n::tr(&app.ui.language, "Columns")); if let Some(v) = number(ui, "tp_cols", Some(cols.len() as f64), "", 52.0, 0) { let n = v.round().max(1.0) as usize; if n > cols.len() { @@ -116,19 +116,23 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { ui.end_row(); let row = rows.get(r0).cloned().unwrap_or(Value::Null); let exactly = row["mode"] == "exactly"; - caption(ui, "Row Height"); - egui::ComboBox::from_id_salt("tp_rowmode").selected_text(if exactly { "Exactly" } else { "At Least" }).width(70.0).show_ui(ui, |ui| { - for (label, mode) in [("At Least", "atLeast"), ("Exactly", "exactly")] { - if ui.selectable_label((mode == "exactly") == exactly, label).clicked() { - run(app, "table.setRowHeight", json!({"mode": mode})); + caption(ui, crate::i18n::tr(&app.ui.language, "Row Height")); + egui::ComboBox::from_id_salt("tp_rowmode") + .selected_text(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, if exactly { "Exactly" } else { "At Least" }))) + .width(70.0) + .show_ui(ui, |ui| { + for (label, mode) in [("At Least", "atLeast"), ("Exactly", "exactly")] { + if ui.selectable_label((mode == "exactly") == exactly, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() + { + run(app, "table.setRowHeight", json!({"mode": mode})); + } } - } - }); + }); if let Some(h) = measure(ui, "tp_rowh", row["height"].as_f64(), unit, 64.0) { run(app, "table.setRowHeight", json!({"height": h})); } ui.end_row(); - caption(ui, "Column Width"); + caption(ui, crate::i18n::tr(&app.ui.language, "Column Width")); ui.label(""); if let Some(w) = measure(ui, "tp_colw", cols.get(c0).and_then(|c| c["width"].as_f64()), unit, 64.0) { run(app, "table.setColumnWidth", json!({"width": w})); @@ -136,10 +140,10 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { ui.end_row(); }); divider(ui); - caption(ui, "Cell Text"); + caption(ui, crate::i18n::tr(&app.ui.language, "Cell Text")); ui.horizontal(|ui| { for (label, vj) in [("Top", "top"), ("Center", "center"), ("Bottom", "bottom"), ("Justify", "justify")] { - if ui.selectable_label(cell["vj"] == vj, label).clicked() { + if ui.selectable_label(cell["vj"] == vj, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { run(app, "table.setCell", json!({"vj": vj})); } } @@ -149,7 +153,7 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { egui::Grid::new("table_insets").num_columns(4).spacing([6.0, 6.0]).show(ui, |ui| { let labels = ["Top", "Left", "Bottom", "Right"]; for (i, l) in labels.iter().enumerate() { - caption(ui, l); + caption(ui, crate::i18n::tr(&app.ui.language, l)); if let Some(v) = measure(ui, &format!("tp_inset{i}"), insets.get(i).copied(), unit, 56.0) { let mut n = insets.clone(); n.resize(4, 4.0); @@ -197,11 +201,11 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { run(app, "table.options", json!({"repeatHeader": rep})); } ui.horizontal(|ui| { - caption(ui, "Header Rows"); + caption(ui, crate::i18n::tr(&app.ui.language, "Header Rows")); if let Some(v) = number(ui, "tp_hdr", info["headerRows"].as_f64(), "", 40.0, 0) { run(app, "table.options", json!({"headerRows": v.max(0.0) as u64})); } - caption(ui, "Footer Rows"); + caption(ui, crate::i18n::tr(&app.ui.language, "Footer Rows")); if let Some(v) = number(ui, "tp_ftr", info["footerRows"].as_f64(), "", 40.0, 0) { run(app, "table.options", json!({"footerRows": v.max(0.0) as u64})); } diff --git a/crates/ui-egui/src/story_editor.rs b/crates/ui-egui/src/story_editor.rs index 92d97f43d..5b093ab41 100644 --- a/crates/ui-egui/src/story_editor.rs +++ b/crates/ui-egui/src/story_editor.rs @@ -36,7 +36,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { for (i, r) in story.para_ranges().iter().enumerate() { let lines = story.text[r.clone()].len() / 60 + 1; let style = &story.paras[i].style; - ui.label(egui::RichText::new(style).size(11.0).color(t.text_dim)); + crate::rtl::label(ui, egui::RichText::new(crate::i18n::style_name(&app.ui.language, style)).size(11.0).color(t.text_dim)); for _ in 1..lines { ui.label(egui::RichText::new(" ").size(11.0)); } @@ -70,9 +70,13 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context) { ui, egui::RichText::new(crate::i18n::tr(&app.ui.language, "OVERSET")).color(egui::Color32::from_rgb(230, 40, 40)).strong(), )); - ui.label( - egui::RichText::new(format!("{} characters don't fit", story.text[o.min(story.text.len())..].chars().count())) - .color(t.text_dim), + crate::rtl::label( + ui, + egui::RichText::new( + crate::i18n::tr(&app.ui.language, "{count} characters don't fit") + .replace("{count}", &story.text[o.min(story.text.len())..].chars().count().to_string()), + ) + .color(t.text_dim), ); }); } diff --git a/crates/ui-egui/src/taskbar.rs b/crates/ui-egui/src/taskbar.rs index 83e2bdbf3..6ea91ed3b 100644 --- a/crates/ui-egui/src/taskbar.rs +++ b/crates/ui-egui/src/taskbar.rs @@ -61,7 +61,7 @@ pub fn show(app: &mut DesignApp, ctx: &egui::Context, sel: Rect, canvas: Rect) { fn split_button(ui: &mut Ui, icon: &str, label: &str) -> (egui::Response, egui::Response) { let t = Tokens::get(ui.ctx()); - let g = ui.painter().layout_no_wrap(label.to_string(), egui::FontId::proportional(11.0), t.text); + let g = crate::rtl::plain(ui.ctx(), label, egui::FontId::proportional(11.0), t.text); let w = 26.0 + g.size().x + 8.0 + widgets::SEG_W; let (r, _) = ui.allocate_exact_size(vec2(w, FIELD_H), Sense::hover()); let main = Rect::from_min_max(r.min, pos2(r.max.x - widgets::SEG_W, r.max.y)); @@ -84,7 +84,7 @@ fn split_button(ui: &mut Ui, icon: &str, label: &str) -> (egui::Response, egui:: fn text_controls(app: &mut DesignApp, ui: &mut Ui, in_text: bool) { let t = Tokens::get(ui.ctx()); if !in_text { - let (main, seg) = split_button(ui, "fit-text", "Fit Text"); + let (main, seg) = split_button(ui, "fit-text", crate::i18n::tr(&app.ui.language, "Fit Text")); if main .on_hover_ui(|ui| { crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, "Fit frame height to text")); @@ -95,7 +95,7 @@ fn text_controls(app: &mut DesignApp, ui: &mut Ui, in_text: bool) { } egui::Popup::menu(&seg).show(|ui| { for (label, v) in [("Off", "off"), ("Height Only", "heightOnly"), ("Width Only", "widthOnly"), ("Height and Width", "heightAndWidth")] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("object.textFrameOptions", json!({"autoSize": v})); ui.close(); } @@ -155,7 +155,7 @@ fn text_controls(app: &mut DesignApp, ui: &mut Ui, in_text: bool) { "leftJustified" | "centerJustified" | "rightJustified" | "fullyJustified" => "palign-justify-left", _ => "palign-left", }; - let resp = widgets::icon_toggle(ui, icon, false, "Paragraph alignment"); + let resp = widgets::icon_toggle(ui, icon, false, crate::i18n::tr(&app.ui.language, "Paragraph alignment")); egui::Popup::menu(&resp).show(|ui| { for (label, v) in [ ("Align Left", "left"), @@ -164,13 +164,13 @@ fn text_controls(app: &mut DesignApp, ui: &mut Ui, in_text: bool) { ("Justify", "leftJustified"), ("Justify All", "fullyJustified"), ] { - if ui.selectable_label(al == v, label).clicked() { + if ui.selectable_label(al == v, crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("type.para", json!({"attrs": {"align": v}})); ui.close(); } } }); - if widgets::icon_toggle(ui, "frame-options", false, "Text Frame Options").clicked() { + if widgets::icon_toggle(ui, "frame-options", false, crate::i18n::tr(&app.ui.language, "Text Frame Options")).clicked() { app.ui.dialog = Some(crate::dialogs::Dialog::new("textFrameOptions", json!({}))); } } @@ -185,7 +185,7 @@ fn object_controls(app: &mut DesignApp, ui: &mut Ui) { ("fit-frame", "fitFrameToContent", "Fit Frame to Content"), ("fit-center", "centerContent", "Center Content"), ] { - if widgets::icon_toggle(ui, icon, false, tip).clicked() { + if widgets::icon_toggle(ui, icon, false, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run("object.fit", json!({"mode": mode})); } } @@ -221,11 +221,11 @@ fn object_controls(app: &mut DesignApp, ui: &mut Ui) { } sep(ui); for (cmd, icon, tip) in [("object.bringToFront", "objalign-top", "Bring to Front"), ("object.sendToBack", "objalign-bottom", "Send to Back")] { - if widgets::icon_toggle(ui, icon, false, tip).clicked() { + if widgets::icon_toggle(ui, icon, false, crate::i18n::tr(&app.ui.language, tip)).clicked() { let _ = app.run(cmd, json!({})); } } - if i.count > 1 && widgets::outline_button(ui, "Group", 52.0).clicked() { + if i.count > 1 && widgets::outline_button(ui, crate::i18n::tr(&app.ui.language, "Group"), 52.0).clicked() { let _ = app.run("object.group", json!({})); } } diff --git a/crates/ui-egui/src/toolbar.rs b/crates/ui-egui/src/toolbar.rs index 1b8844a01..4f110df4d 100644 --- a/crates/ui-egui/src/toolbar.rs +++ b/crates/ui-egui/src/toolbar.rs @@ -24,8 +24,14 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui) { // Collapse chevrons. ui.horizontal(|ui| { ui.add_space(width / 2.0 - 8.0); - if icons::button(ui, if cols == 1 { "double-chevron-right" } else { "double-chevron-left" }, 16.0, false, "Toggle double column") - .clicked() + if icons::button( + ui, + if cols == 1 { "double-chevron-right" } else { "double-chevron-left" }, + 16.0, + false, + crate::i18n::tr(&app.ui.language, "Toggle double column"), + ) + .clicked() { app.ui.tools_double_column = !app.ui.tools_double_column; } @@ -266,7 +272,13 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) { ui.painter().rect(r, 1.5, t.well, Stroke::new(1.0, t.well_rim), StrokeKind::Inside); } icons::paint(ui.painter(), r.shrink(1.5), icon, if on { t.text_strong } else { t.icon }); - if ui.interact(r, ui.id().with(("proxy_fmt", k)), Sense::click()).on_hover_text(tip).clicked() { + if ui + .interact(r, ui.id().with(("proxy_fmt", k)), Sense::click()) + .on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, tip)); + }) + .clicked() + { ui.data_mut(|d| d.insert_temp(text_id, k == 1)); } } @@ -287,7 +299,7 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) { } egui::Popup::context_menu(&resp).show(|ui| { for (label, sw) in [("Apply Color", "[Black]"), ("Apply None", "[None]")] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run(target, json!({"swatch": sw})); ui.close(); } @@ -297,7 +309,7 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) { separator(ui, &t); // Utility (frame options). ui.vertical_centered(|ui| { - if tool_button(ui, "frame-options", false, "Text Frame Options").clicked() { + if tool_button(ui, "frame-options", false, crate::i18n::tr(&app.ui.language, "Text Frame Options")).clicked() { app.ui.dialog = Some(crate::dialogs::Dialog::new("textFrameOptions", json!({}))); } }); @@ -315,13 +327,15 @@ fn fill_stroke_proxy(app: &mut DesignApp, ui: &mut egui::Ui, width: f32) { t.text_strong, ); flyout_triangle(ui.painter(), well, t.icon); - let resp = resp.on_hover_text(if preview { "Preview (W)" } else { "Normal (W)" }); + let resp = resp.on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(&app.ui.language, if preview { "Preview (W)" } else { "Normal (W)" })); + }); if resp.clicked() { let _ = app.run("view.togglePreview", json!({})); } egui::Popup::context_menu(&resp).show(|ui| { for (label, mode) in [("Normal", "normal"), ("Preview", "preview"), ("Bleed", "bleed"), ("Slug", "slug"), ("Presentation", "presentation")] { - if ui.button(label).clicked() { + if ui.button(crate::rtl::widget(ui, crate::i18n::tr(&app.ui.language, label))).clicked() { let _ = app.run("view.screenMode", json!({"mode": mode})); ui.close(); } @@ -356,5 +370,7 @@ fn tool_button(ui: &mut egui::Ui, icon: &str, active: bool, tip: &str) -> egui:: ui.painter().rect_filled(egui::Rect::from_center_size(r.center(), egui::vec2(28.0, 20.0)), 2.0, t.hover); } icons::paint(ui.painter(), egui::Rect::from_center_size(r.center(), egui::vec2(17.0, 17.0)), icon, if active { t.text_strong } else { t.icon }); - resp.on_hover_text(tip) + resp.on_hover_ui(|ui| { + crate::rtl::label(ui, tip); + }) } diff --git a/crates/ui-egui/src/widgets.rs b/crates/ui-egui/src/widgets.rs index 80578ff43..8e6b72167 100644 --- a/crates/ui-egui/src/widgets.rs +++ b/crates/ui-egui/src/widgets.rs @@ -359,7 +359,7 @@ pub fn link_label(ui: &mut Ui, text: &str, width: f32) -> Response { } /// `•••` more-options, right-aligned on its own row at the bottom of a section. -pub fn more_options(ui: &mut Ui) -> Response { +pub fn more_options(ui: &mut Ui, language: &str) -> Response { let t = Tokens::get(ui.ctx()); let (row, _) = ui.allocate_exact_size(vec2(ui.available_width(), 14.0), Sense::hover()); let r = Rect::from_min_max(pos2(row.max.x - 26.0, row.min.y), pos2(row.max.x - 4.0, row.max.y)); @@ -367,7 +367,9 @@ pub fn more_options(ui: &mut Ui) -> Response { for i in 0..3 { ui.painter().circle_filled(pos2(r.min.x + 4.0 + i as f32 * 7.0, r.center().y), 1.9, if resp.hovered() { t.text_strong } else { t.icon }); } - resp.on_hover_text("More Options") + resp.on_hover_ui(|ui| { + crate::rtl::label(ui, crate::i18n::tr(language, "More Options")); + }) } /// A small dim label (field captions like `X:` or `W:`). @@ -514,7 +516,13 @@ pub fn icon_toggle_sized(ui: &mut Ui, icon: &str, on: bool, tip: &str, size: egu } let s = (size.y - 4.0).min(17.0); crate::icons::paint(ui.painter(), Rect::from_center_size(r.center(), vec2(s, s)), icon, if on { t.text_strong } else { t.icon }); - if tip.is_empty() { resp } else { resp.on_hover_text(tip) } + if tip.is_empty() { + resp + } else { + resp.on_hover_ui(|ui| { + crate::rtl::label(ui, tip); + }) + } } /// An InDesign checkbox: 12 pt rounded box, white check, label. @@ -552,7 +560,7 @@ pub fn segmented(ui: &mut Ui, labels: &[&str], active: usize, width: f32) -> Opt } else if resp.hovered() { ui.painter().rect_filled(sr, 0.0, t.hover); } - ui.painter().text(sr.center(), egui::Align2::CENTER_CENTER, *l, label_font(), if i == active { t.accent } else { t.text }); + crate::rtl::paint(ui.painter(), sr.center(), egui::Align2::CENTER_CENTER, l, label_font(), if i == active { t.accent } else { t.text }); if i > 0 { ui.painter().line_segment([sr.left_top(), sr.left_bottom()], Stroke::new(1.0, t.field_border)); }