From 5564856febf2c2cb437e4f8dae286250781ad805 Mon Sep 17 00:00:00 2001 From: Marco Arnulfo Date: Tue, 13 Oct 2026 02:41:49 +0200 Subject: [PATCH] PivotTables: the PivotTable Fields pane follows the interface language (fixes #858) The pane's labels, field menus and buttons were hardcoded English strings that never went through i18n, so switching to Spanish left the whole pane in English. They now go through Language::tr, with Spanish entries in the catalog. The Summarize by / Show as items are keyed as whole labels so each one can be translated. --- crates/ui-egui/src/i18n/es.rs | 30 +++++++++++++++++++ crates/ui-egui/src/pivot_pane.rs | 46 +++++++++++++++++------------ crates/ui-egui/tests/interaction.rs | 20 +++++++++++++ 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/crates/ui-egui/src/i18n/es.rs b/crates/ui-egui/src/i18n/es.rs index da41bd7..b7fec16 100644 --- a/crates/ui-egui/src/i18n/es.rs +++ b/crates/ui-egui/src/i18n/es.rs @@ -393,6 +393,36 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Dark" => "Oscuro", "Sheet Options|View" => "Ver", "Print" => "Imprimir", + "PivotTable Fields" => "Campos de tabla dinámica", + "Choose fields to add to report:" => "Seleccione los campos para agregar al informe:", + "Use a field's menu to move it between areas:" => "Use el menú de un campo para moverlo entre áreas:", + "Filters" => "Filtros", + "Columns" => "Columnas", + "Rows" => "Filas", + "Values" => "Valores", + "Move to Row Labels" => "Mover a rótulos de fila", + "Move to Column Labels" => "Mover a rótulos de columna", + "Move to Report Filter" => "Mover al filtro de informe", + "Move to Values" => "Mover a valores", + "Summarize by Sum" => "Resumir por suma", + "Summarize by Count" => "Resumir por cuenta", + "Summarize by Average" => "Resumir por promedio", + "Summarize by Max" => "Resumir por máximo", + "Summarize by Min" => "Resumir por mínimo", + "Show as No Calculation" => "Mostrar como sin cálculo", + "Show as % of Grand Total" => "Mostrar como % del total general", + "Show as Running Total" => "Mostrar como total acumulado", + "Show as Rank" => "Mostrar como clasificación", + "Group by Years" => "Agrupar por años", + "Group by Quarters" => "Agrupar por trimestres", + "Group by Months" => "Agrupar por meses", + "Remove Field" => "Quitar campo", + "Refresh" => "Actualizar", + "Calculated Field…" => "Campo calculado…", + "Report Layout" => "Diseño de informe", + "Compact" => "Compacto", + "Outline" => "Esquema", + "Tabular" => "Tabular", _ => return None, }) } diff --git a/crates/ui-egui/src/pivot_pane.rs b/crates/ui-egui/src/pivot_pane.rs index 14d69db..5ea46b0 100644 --- a/crates/ui-egui/src/pivot_pane.rs +++ b/crates/ui-egui/src/pivot_pane.rs @@ -6,6 +6,7 @@ use egui::{Color32, Ui, vec2}; use serde_json::{Value as Json, json}; use crate::SheetApp; +use crate::i18n; use crate::theme::{self, Tokens}; /// Whether the active cell is inside a pivot report (so the pane should show). @@ -19,13 +20,14 @@ pub fn active_pivot(app: &SheetApp) -> Option { pub fn show(app: &mut SheetApp, ui: &mut Ui) { let Some(name) = active_pivot(app) else { return }; let t = Tokens::get(ui.ctx()); + let lang = i18n::current(ui.ctx()); let info = match app.session.run("pivot.fields", json!({"pivot": name})) { Ok(v) => v, Err(_) => return, }; egui::Panel::right("pivot_pane").default_size(280.0).resizable(true).frame(egui::Frame::NONE.fill(t.ribbon).inner_margin(10)).show(ui, |ui| { - ui.label(egui::RichText::new("PivotTable Fields").font(theme::ui_bold(15.0))); - ui.label(egui::RichText::new("Choose fields to add to report:").small().color(t.text_dim)); + ui.label(egui::RichText::new(lang.tr("PivotTable Fields")).font(theme::ui_bold(15.0))); + ui.label(egui::RichText::new(lang.tr("Choose fields to add to report:")).small().color(t.text_dim)); ui.add_space(4.0); let fields = info["fields"].as_array().cloned().unwrap_or_default(); egui::ScrollArea::vertical().max_height(220.0).id_salt("pv_fields").show(ui, |ui| { @@ -52,7 +54,7 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) { } }); ui.separator(); - ui.label(egui::RichText::new("Use a field's menu to move it between areas:").small().color(t.text_dim)); + ui.label(egui::RichText::new(lang.tr("Use a field's menu to move it between areas:")).small().color(t.text_dim)); let area = |key: &str| -> Vec { info["pivot"][key] .as_array() @@ -67,7 +69,7 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) { ui.columns(2, |cols| { for (i, (key, label)) in [("filters", "Filters"), ("columns", "Columns"), ("rows", "Rows"), ("values", "Values")].iter().enumerate() { let ui = &mut cols[i % 2]; - ui.label(egui::RichText::new(*label).strong()); + ui.label(egui::RichText::new(lang.tr(label)).strong()); egui::Frame::NONE.fill(t.window).corner_radius(4.0).inner_margin(4.0).show(ui, |ui| { ui.set_min_size(vec2(ui.available_width(), 60.0)); for f in area(key) { @@ -83,31 +85,37 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) { ("filters", "Move to Report Filter"), ("values", "Move to Values"), ] { - if to != *key && ui.button(l).clicked() { + if to != *key && ui.button(lang.tr(l)).clicked() { app.run_or_alert("pivot.moveField", json!({"pivot": name, "field": f, "from": key, "to": to})); } } if *key == "values" { ui.separator(); - for (func, l) in [("sum", "Sum"), ("count", "Count"), ("average", "Average"), ("max", "Max"), ("min", "Min")] { - if ui.button(format!("Summarize by {l}")).clicked() { + for (func, l) in [ + ("sum", "Summarize by Sum"), + ("count", "Summarize by Count"), + ("average", "Summarize by Average"), + ("max", "Summarize by Max"), + ("min", "Summarize by Min"), + ] { + if ui.button(lang.tr(l)).clicked() { app.run_or_alert("pivot.valueSettings", json!({"pivot": name, "field": f, "func": func})); } } for (sa, l) in [ - ("normal", "No Calculation"), - ("percentOfGrandTotal", "% of Grand Total"), - ("runningTotal", "Running Total"), - ("rank", "Rank"), + ("normal", "Show as No Calculation"), + ("percentOfGrandTotal", "Show as % of Grand Total"), + ("runningTotal", "Show as Running Total"), + ("rank", "Show as Rank"), ] { - if ui.button(format!("Show as {l}")).clicked() { + if ui.button(lang.tr(l)).clicked() { app.run_or_alert("pivot.valueSettings", json!({"pivot": name, "field": f, "showAs": sa})); } } } else { ui.separator(); for (o, l) in [("asc", "Sort A to Z"), ("desc", "Sort Z to A")] { - if ui.button(l).clicked() { + if ui.button(lang.tr(l)).clicked() { app.run_or_alert("pivot.sort", json!({"pivot": name, "field": f, "order": o})); } } @@ -117,13 +125,13 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) { ("months", "Group by Months"), ("none", "Ungroup"), ] { - if ui.button(l).clicked() { + if ui.button(lang.tr(l)).clicked() { app.run_or_alert("pivot.group", json!({"pivot": name, "field": f, "by": g})); } } } ui.separator(); - if ui.button("Remove Field").clicked() { + if ui.button(lang.tr("Remove Field")).clicked() { app.run_or_alert("pivot.removeField", json!({"pivot": name, "field": f, "area": key})); } }); @@ -133,15 +141,15 @@ pub fn show(app: &mut SheetApp, ui: &mut Ui) { }); ui.separator(); ui.horizontal(|ui| { - if ui.button("Refresh").clicked() { + if ui.button(lang.tr("Refresh")).clicked() { app.run_or_alert("pivot.refresh", json!({"pivot": name})); } - if ui.button("Calculated Field…").clicked() { + if ui.button(lang.tr("Calculated Field…")).clicked() { app.run_or_alert("pivot.calculatedField", json!({"pivot": name})); } - egui::ComboBox::from_id_salt("pv_layout").selected_text("Report Layout").show_ui(ui, |ui| { + egui::ComboBox::from_id_salt("pv_layout").selected_text(lang.tr("Report Layout")).show_ui(ui, |ui| { for (l, v) in [("Compact", "compact"), ("Outline", "outline"), ("Tabular", "tabular")] { - if ui.button(l).clicked() { + if ui.button(lang.tr(l)).clicked() { app.run_or_alert("pivot.layout", json!({"pivot": name, "layout": v})); } } diff --git a/crates/ui-egui/tests/interaction.rs b/crates/ui-egui/tests/interaction.rs index 7093933..c7d7288 100644 --- a/crates/ui-egui/tests/interaction.rs +++ b/crates/ui-egui/tests/interaction.rs @@ -279,6 +279,26 @@ fn language_switch_translates_the_ribbon_and_persists() { assert_eq!(h.state().ui.language, Language::En); } +#[test] +fn pivot_fields_pane_follows_the_interface_language() { + // #858: the PivotTable Fields pane and its field menus stayed in English after switching to Spanish. + let mut h = harness(blank()); + h.state_mut().run("range.setValues", json!({"range": "A1:B3", "values": [["Region", "Sales"], ["North", 10], ["South", 20]]})).unwrap(); + h.state_mut().run("insert.pivotTable", json!({"source": "Sheet1!$A$1:$B$3", "rows": ["Region"]})).unwrap(); + h.state_mut().run("app.language.set", json!({"language": "es"})).unwrap(); + h.run_steps(4); + for label in ["Campos de tabla dinámica", "Filas", "Valores", "Actualizar", "Campo calculado…"] { + assert!(h.query_by_label(label).is_some(), "the pane shows `{label}`"); + } + assert!(h.query_by_label("PivotTable Fields").is_none()); + h.get_by_label("Region ▾").click(); + h.run_steps(2); + for label in ["Mover a rótulos de columna", "Agrupar por años", "Quitar campo"] { + assert!(h.query_by_label(label).is_some(), "the field menu shows `{label}`"); + } + assert!(h.query_by_label("Remove Field").is_none()); +} + #[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