Merge pull request #788 from brzuchal:format-cells-tr

This commit is contained in:
Brandon Thomas
2026-10-13 00:08:21 -04:00
3 changed files with 78 additions and 37 deletions
+55 -36
View File
@@ -939,7 +939,7 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) {
"solver" => 460.0,
_ => 380.0,
};
let window = egui::Window::new(d.title.clone())
let window = egui::Window::new(app.ui.language.tr(&d.title))
.id(egui::Id::new("dialog").with(&d.name))
.collapsible(false)
.resizable(false)
@@ -1271,11 +1271,15 @@ fn paste_special_shortcuts(d: &mut Dialog, ctx: &egui::Context) {
fn ok_cancel(ui: &mut egui::Ui, confirm: &mut bool, open: &mut bool) {
ui.add_space(8.0);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
let ok = ui.add(egui::Button::new(egui::RichText::new(" OK ").color(Color32::WHITE)).fill(crate::theme::Tokens::get(ui.ctx()).accent));
let lang = crate::i18n::current(ui.ctx());
let ok = ui.add(
egui::Button::new(egui::RichText::new(format!(" {} ", lang.tr("OK"))).color(Color32::WHITE))
.fill(crate::theme::Tokens::get(ui.ctx()).accent),
);
if ok.clicked() || ui.input(|i| i.key_pressed(Key::Enter)) {
*confirm = true;
}
if ui.button(" Cancel ").clicked() {
if ui.button(format!(" {} ", lang.tr("Cancel"))).clicked() {
*open = false;
}
});
@@ -1410,9 +1414,20 @@ fn macros(ui: &mut egui::Ui, d: &mut Dialog) {
}
fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm: &mut bool) {
// `d.tab` stays the English name (it is what `open_dialog` and tests pass); only the drawn
// labels are translated. The Fill tab is keyed by context: the Alignment tab's "Fill" is a
// different word in some languages.
let lang = app.ui.language;
ui.horizontal(|ui| {
for tab in ["Number", "Alignment", "Font", "Border", "Fill", "Protection"] {
if ui.selectable_label(d.tab == tab, tab).clicked() {
for (tab, key) in [
("Number", "Number"),
("Alignment", "Alignment"),
("Font", "Font"),
("Border", "Border"),
("Fill", "Format Cells|Fill"),
("Protection", "Protection"),
] {
if ui.selectable_label(d.tab == tab, lang.tr(key)).clicked() {
d.tab = tab.to_string();
}
}
@@ -1424,20 +1439,20 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
"Number" => {
let mut code = st.num_fmt.as_str().to_string();
ui.columns(2, |cols| {
cols[0].label(egui::RichText::new("Category:").strong());
cols[0].label(egui::RichText::new(lang.tr("Category:")).strong());
for name in
["General", "Number", "Currency", "Accounting", "Short Date", "Long Date", "Time", "Percentage", "Fraction", "Scientific", "Text"]
{
let c = gridcraft_engine::cmd::format::format_code_for(name).to_string();
if cols[0].selectable_label(code == c, name).clicked() {
if cols[0].selectable_label(code == c, lang.tr(name)).clicked() {
code = c;
}
}
cols[1].label(egui::RichText::new("Sample").strong());
cols[1].label(egui::RichText::new(lang.tr("Sample")).strong());
let preview = app.session.active().map(|doc| gridcraft_engine::display::format(&sample, &code, &doc.wb).text).unwrap_or_default();
cols[1].label(egui::RichText::new(preview).font(theme::ui_font(15.0)));
cols[1].add_space(8.0);
cols[1].label("Type (custom format code):");
cols[1].label(lang.tr("Type (custom format code):"));
cols[1].text_edit_singleline(&mut code);
for c in [
"0",
@@ -1466,7 +1481,7 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
}
"Alignment" => {
use gridcraft_engine::model::{HAlign, VAlign};
ui.label(egui::RichText::new("Horizontal").strong());
ui.label(egui::RichText::new(lang.tr("Horizontal")).strong());
ui.horizontal_wrapped(|ui| {
for (h, n) in [
(HAlign::General, "General"),
@@ -1478,10 +1493,10 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
(HAlign::CenterAcross, "Center Across Selection"),
(HAlign::Distributed, "Distributed"),
] {
ui.radio_value(&mut st.align.h, h, n);
ui.radio_value(&mut st.align.h, h, lang.tr(n));
}
});
ui.label(egui::RichText::new("Vertical").strong());
ui.label(egui::RichText::new(lang.tr("Vertical")).strong());
ui.horizontal_wrapped(|ui| {
for (v, n) in [
(VAlign::Top, "Top"),
@@ -1490,29 +1505,29 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
(VAlign::Justify, "Justify"),
(VAlign::Distributed, "Distributed"),
] {
ui.radio_value(&mut st.align.v, v, n);
ui.radio_value(&mut st.align.v, v, lang.tr(n));
}
});
let mut indent = st.align.indent as f32;
ui.add(egui::Slider::new(&mut indent, 0.0..=15.0).text("Indent"));
ui.add(egui::Slider::new(&mut indent, 0.0..=15.0).text(lang.tr("Indent")));
st.align.indent = indent as u8;
let mut rot = if st.align.rotation == 255 { 0.0 } else { st.align.rotation as f32 };
ui.add(egui::Slider::new(&mut rot, -90.0..=90.0).text("Orientation (degrees)"));
ui.add(egui::Slider::new(&mut rot, -90.0..=90.0).text(lang.tr("Orientation (degrees)")));
st.align.rotation = rot as i16;
ui.checkbox(&mut st.align.wrap, "Wrap text");
ui.checkbox(&mut st.align.shrink, "Shrink to fit");
ui.checkbox(&mut st.align.wrap, lang.tr("Wrap text"));
ui.checkbox(&mut st.align.shrink, lang.tr("Shrink to fit"));
}
"Font" => {
egui::ComboBox::from_label("Font").selected_text(st.font.name.clone()).show_ui(ui, |ui| {
egui::ComboBox::from_label(lang.tr("Font")).selected_text(st.font.name.clone()).show_ui(ui, |ui| {
for f in crate::fonts::families() {
ui.selectable_value(&mut st.font.name, f.clone(), f.clone());
}
});
ui.add(egui::Slider::new(&mut st.font.size, 6.0..=72.0).text("Size"));
ui.add(egui::Slider::new(&mut st.font.size, 6.0..=72.0).text(lang.tr("Size")));
ui.horizontal(|ui| {
ui.checkbox(&mut st.font.bold, "Bold");
ui.checkbox(&mut st.font.italic, "Italic");
ui.checkbox(&mut st.font.strike, "Strikethrough");
ui.checkbox(&mut st.font.bold, lang.tr("Bold"));
ui.checkbox(&mut st.font.italic, lang.tr("Italic"));
ui.checkbox(&mut st.font.strike, lang.tr("Strikethrough"));
});
use gridcraft_engine::model::{Underline, VertAlign};
ui.horizontal(|ui| {
@@ -1523,17 +1538,17 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
(Underline::SingleAccounting, "Single Accounting"),
(Underline::DoubleAccounting, "Double Accounting"),
] {
ui.radio_value(&mut st.font.underline, u, n);
ui.radio_value(&mut st.font.underline, u, lang.tr(n));
}
});
ui.horizontal(|ui| {
ui.radio_value(&mut st.font.vert, VertAlign::Baseline, "Normal");
ui.radio_value(&mut st.font.vert, VertAlign::Superscript, "Superscript");
ui.radio_value(&mut st.font.vert, VertAlign::Subscript, "Subscript");
ui.radio_value(&mut st.font.vert, VertAlign::Baseline, lang.tr("Normal"));
ui.radio_value(&mut st.font.vert, VertAlign::Superscript, lang.tr("Superscript"));
ui.radio_value(&mut st.font.vert, VertAlign::Subscript, lang.tr("Subscript"));
});
ui.label("Color:");
ui.label(lang.tr("Color:"));
let colors = app.session.active().map(|doc| doc.wb.theme.colors).unwrap_or_default();
if let Some(c) = crate::widgets::color_palette(ui, &colors, "Automatic") {
if let Some(c) = crate::widgets::color_palette(ui, &colors, lang.tr("Automatic")) {
st.font.color =
if c == "none" { gridcraft_engine::model::Color::Auto } else { gridcraft_engine::model::Color::from_hex(&c).unwrap_or_default() };
}
@@ -1542,10 +1557,10 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
use gridcraft_engine::model::{BorderLine, BorderStyle};
let line = BorderLine { style: BorderStyle::Thin, color: gridcraft_engine::model::Color::Auto };
ui.horizontal(|ui| {
if ui.button("None").clicked() {
if ui.button(lang.tr("None")).clicked() {
st.borders = Default::default();
}
if ui.button("Outline").clicked() {
if ui.button(lang.tr("Outline")).clicked() {
st.borders.top = line;
st.borders.bottom = line;
st.borders.left = line;
@@ -1560,6 +1575,7 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
("Diagonal ↘", &mut st.borders.diag_down),
("Diagonal ↗", &mut st.borders.diag_up),
] {
let n = lang.tr(n);
ui.horizontal(|ui| {
ui.label(format!("{n}:"));
for (s, label) in [
@@ -1571,6 +1587,7 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
(BorderStyle::Dotted, "dotted"),
(BorderStyle::Double, "double"),
] {
let label = lang.tr(label);
let response = ui.selectable_label(slot.style == s, label);
response
.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::SelectableLabel, ui.is_enabled(), format!("{n} {label}")));
@@ -1585,8 +1602,8 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
}
"Fill" => {
let colors = app.session.active().map(|doc| doc.wb.theme.colors).unwrap_or_default();
ui.label("Background Color:");
if let Some(c) = crate::widgets::color_palette(ui, &colors, "No Color") {
ui.label(lang.tr("Background Color:"));
if let Some(c) = crate::widgets::color_palette(ui, &colors, lang.tr("No Color")) {
st.fill = if c == "none" {
Default::default()
} else {
@@ -1595,11 +1612,13 @@ fn format_cells(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm:
}
}
"Protection" => {
ui.checkbox(&mut st.protection.locked, "Locked");
ui.checkbox(&mut st.protection.hidden, "Hidden");
ui.checkbox(&mut st.protection.locked, lang.tr("Locked"));
ui.checkbox(&mut st.protection.hidden, lang.tr("Hidden"));
ui.label(
egui::RichText::new("Locking cells or hiding formulas has no effect until you protect the worksheet (Review › Protect Sheet).")
.small(),
egui::RichText::new(
lang.tr("Locking cells or hiding formulas has no effect until you protect the worksheet (Review › Protect Sheet)."),
)
.small(),
);
}
_ => {}
+19
View File
@@ -317,6 +317,25 @@ fn language_switch_translates_the_ribbon_and_persists() {
assert_eq!(h.state().ui.language, Language::En);
}
#[test]
fn format_cells_draws_its_labels_in_the_interface_language() {
let mut h = harness(blank());
h.state_mut().run("app.language.set", json!({"language": "de"})).unwrap();
h.state_mut().open_dialog("formatCells", json!({"tab": "Number"}));
h.run_steps(2);
assert!(h.query_by_label("Wissenschaftlich").is_some(), "the Number tab's categories are translated");
assert!(h.query_by_label("Scientific").is_none(), "no English category is left");
h.get_by_label("Wissenschaftlich").click();
h.run_steps(2);
let style = &h.state().dialog.as_ref().unwrap().values["style"];
assert_eq!(style["num_fmt"], json!("0.00E+00"), "a translated category still applies its code");
h.state_mut().dialog = None;
h.state_mut().run("app.language.set", json!({"language": "en"})).unwrap();
h.state_mut().open_dialog("formatCells", json!({"tab": "Number"}));
h.run_steps(2);
assert!(h.query_by_label("Scientific").is_some(), "English is unchanged");
}
#[test]
fn name_manager_new_and_edit_open_the_name_dialog_and_come_back() {
// #238: New… in the Name Manager did nothing (the manager put itself back over the dialog it
+4 -1
View File
@@ -1,5 +1,6 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Format Cells dialog looks its labels up in the catalog) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Brazilian Portuguese catalog: dialogs, menus and tooltips) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4
Per-language status against Excel, which ships its UI in dozens of languages (Windows language
@@ -14,7 +15,8 @@ right-to-left sheets and full complex-script support. Part of
system language (closes #20) and a View-tab switcher, with English-keyed lookup tables:
Japanese 268 labels, Simplified Chinese, Korean, Russian, Brazilian Portuguese, German and Czech 266
each. The tables are used by
the ribbon, sheet tabs and widgets (`ribbon.rs`, `tabs.rs`, `widgets.rs`); dialogs, menus,
the ribbon, sheet tabs and widgets (`ribbon.rs`, `tabs.rs`, `widgets.rs`), the Format Cells
dialog, every dialog title and the shared OK/Cancel buttons; the other dialogs, menus,
context menus, task panes, status messages, command labels and function descriptions are still
English literals. Untranslated labels fall back to English.
- **About 2,200 user-visible strings** in all (heuristic source scan: ~1,700 UI and command
@@ -77,6 +79,7 @@ language (none so far) and of localized function names.
| Date | Change | Summary |
|---|---|---|
| 2026-10-12 | minor | Format Cells looks up its tabs, labels, options and note through `Language::tr` (Fill tab keyed `Format Cells\|Fill`); dialog titles and OK/Cancel too. Labels already in a table (Number, Currency, Fill…) show translated; the rest fall back to English until the tables add them |
| 2026-10-12 | minor | Brazilian Portuguese catalog (`i18n/pt.rs`) grows from 377 to 923 entries: every key the other catalogs translate, including the dialog strings #669 starts looking up (~42%) |
| 2026-10-11 | minor | German ribbon catalog (`i18n/de.rs`, 266 labels, ~12%); `de` locale tags (de-DE, de-AT, de-CH…) resolve to German. PR #163 (localized German formulas/numbers) still open |
| 2026-10-11 | minor | Sheet Right-to-Left mirrors the grid (#146) |