mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-27 20:26:48 -06:00
Merge pull request #788 from brzuchal:format-cells-tr
This commit is contained in:
@@ -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(),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) |
|
||||
|
||||
Reference in New Issue
Block a user