From f1015e1e6ba2985ead9877fecb4b4d738583bdbd Mon Sep 17 00:00:00 2001 From: Vedran Dobos <19259909+vedran-dobos@users.noreply.github.com> Date: Mon, 19 Oct 2026 01:25:51 +0200 Subject: [PATCH 1/2] i18n: French and Serbian Options headings without the context prefix (#1891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fr.rs and sr.rs translated three `context|label` keys with the context kept ("Options|Général", "Opcije|Opšte", "Options de la feuille|Affichage" ...). `Language::tr` shows a found translation as it is, so File › Options section headings and the Sheet Options "View" checkboxes showed the prefix. The values now hold only the label. Tests: `translations_never_show_the_context_prefix` (no catalog shows a `|` for any key) and `translated_options_headings_show_no_context_prefix` (File › Options in French and Serbian); both fail on main. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n.rs | 18 ++++++++++++++++++ crates/ui-egui/src/i18n/fr.rs | 6 +++--- crates/ui-egui/src/i18n/sr.rs | 6 +++--- crates/ui-egui/tests/file_menu.rs | 20 ++++++++++++++++++++ docs/localization-parity.md | 3 ++- 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 061698df..cd9c842e 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -647,6 +647,24 @@ mod tests { assert_eq!(Language::parse("xx"), None); } + #[test] + fn translations_never_show_the_context_prefix() { + // `tr` returns a found translation as it is, so a catalog that keeps `context|` in its + // value shows it on screen (File › Options read "Options|Général" in French). + for language in Language::ALL { + for (english, _) in JAPANESE { + let shown = language.tr(english); + assert!(!shown.contains('|'), "{} shows a context prefix for {english}: {shown}", language.code()); + } + } + assert_eq!(Language::Fr.tr("Options|General"), "Général"); + assert_eq!(Language::Fr.tr("Options|Save"), "Enregistrer"); + assert_eq!(Language::Fr.tr("Sheet Options|View"), "Affichage"); + assert_eq!(Language::Sr.tr("Options|General"), "Opšte"); + assert_eq!(Language::Sr.tr("Options|Save"), "Sačuvaj"); + assert_eq!(Language::Sr.tr("Sheet Options|View"), "Prikaz"); + } + #[test] fn unknown_saved_language_keeps_the_other_preferences() { let ui: crate::UiState = serde_json::from_str(r#"{"dark": true, "language": "xx"}"#).unwrap(); diff --git a/crates/ui-egui/src/i18n/fr.rs b/crates/ui-egui/src/i18n/fr.rs index 5d20ab0c..82027e84 100644 --- a/crates/ui-egui/src/i18n/fr.rs +++ b/crates/ui-egui/src/i18n/fr.rs @@ -305,7 +305,7 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Lookup &\nReference" => "Recherche et\nréférence", "Math &\nTrig" => "Maths et\ntrigonométrie", "More\nFunctions" => "Autres\nfonctions", - "Sheet Options|View" => "Options de la feuille|Affichage", + "Sheet Options|View" => "Affichage", "Print" => "Impression", "Click to add data" => "Cliquez pour ajouter des données", "Add header" => "Ajouter un en-tête", @@ -402,8 +402,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Save the active sheet as comma-separated text." => "Enregistrer la feuille active sous forme de texte séparé par des points-virgules.", "Web Page (.html)" => "Page web (.html)", "Save the workbook as a web page." => "Enregistrer le classeur en tant que page web.", - "Options|General" => "Options|Général", - "Options|Save" => "Options|Enregistrer", + "Options|General" => "Général", + "Options|Save" => "Enregistrer", "Show formula bar" => "Afficher la barre de formule", "Enable AutoComplete for cell values" => "Activer la saisie semi-automatique des valeurs de cellule", "Save .xlsx workbooks automatically after every change" => "Enregistrer automatiquement les classeurs .xlsx après chaque modification", diff --git a/crates/ui-egui/src/i18n/sr.rs b/crates/ui-egui/src/i18n/sr.rs index cbf18d4c..85bd2da7 100644 --- a/crates/ui-egui/src/i18n/sr.rs +++ b/crates/ui-egui/src/i18n/sr.rs @@ -297,7 +297,7 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Lookup &\nReference" => "Pretraga i\nreference", "Math &\nTrig" => "Matematika i\ntrigonometrija", "More\nFunctions" => "Još\nfunkcija", - "Sheet Options|View" => "Opcije lista|Prikaz", + "Sheet Options|View" => "Prikaz", "Print" => "Štampaj", "Click to add data" => "Kliknite da dodate podatke", "Add header" => "Dodaj zaglavlje", @@ -394,8 +394,8 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Save the active sheet as comma-separated text." => "Sačuvaj aktivni list kao tekst odvojen zapetama.", "Web Page (.html)" => "Veb stranica (.html)", "Save the workbook as a web page." => "Sačuvaj radnu svesku kao veb stranicu.", - "Options|General" => "Opcije|Opšte", - "Options|Save" => "Opcije|Sačuvaj", + "Options|General" => "Opšte", + "Options|Save" => "Sačuvaj", "Show formula bar" => "Prikaži traku formule", "Enable AutoComplete for cell values" => "Omogući automatsko dovršavanje vrednosti ćelija", "Save .xlsx workbooks automatically after every change" => "Automatski sačuvaj .xlsx radne sveske posle svake izmene", diff --git a/crates/ui-egui/tests/file_menu.rs b/crates/ui-egui/tests/file_menu.rs index 691eef36..d4aa13c2 100644 --- a/crates/ui-egui/tests/file_menu.rs +++ b/crates/ui-egui/tests/file_menu.rs @@ -132,6 +132,26 @@ fn file_options_opens_the_options_page_and_pages_are_reachable_by_command() { assert!(h.state().backstage.is_none()); } +#[test] +fn translated_options_headings_show_no_context_prefix() { + for (code, headings, prefixed) in [ + ("fr", ["Général", "Enregistrer"], ["Options|Général", "Options|Enregistrer"]), + ("sr", ["Opšte", "Sačuvaj"], ["Opcije|Opšte", "Opcije|Sačuvaj"]), + ] { + let mut h = harness(Services::default()); + h.state_mut().run("app.language.set", serde_json::json!({"language": code})).unwrap(); + h.state_mut().run("file.options", serde_json::json!({})).unwrap(); + h.run_steps(3); + assert_eq!(page(&h), Some("options")); + for heading in headings { + assert!(h.query_all_by_label(heading).next().is_some(), "{code}: no {heading} heading"); + } + for label in prefixed { + assert!(h.query_by_label(label).is_none(), "{code}: {label} shows its context"); + } + } +} + #[test] fn export_as_pdf_is_delivered_as_a_download() { let files = Arc::new(Mutex::new(Vec::<(String, Vec)>::new())); diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 09197ff6..91d37aa2 100644 --- a/docs/localization-parity.md +++ b/docs/localization-parity.md @@ -1,6 +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-19 · **Change:** minor (French and Serbian `context|label` translations no longer show the context) · **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 packs cover 100+), with localized function names, argument separators, number and date formats, @@ -78,6 +78,7 @@ language (none so far) and of localized function names. | Date | Change | Summary | |---|---|---| +| 2026-10-19 | minor | French and Serbian kept the context in three `context\|label` translations ("Options\|Général", "Opcije\|Opšte" for `Options\|General`; also `Options\|Save`, `Sheet Options\|View`). `tr` shows a found translation as it is, so File › Options headings and the Sheet Options "View" checkboxes showed the prefix. Fixed; a test checks that no catalog shows a `\|` | | 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 | From adcb460a63552d5fd6d134b7d69adf8a1050b23b Mon Sep 17 00:00:00 2001 From: Vedran Dobos <19259909+vedran-dobos@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:54:50 +0200 Subject: [PATCH 2/2] i18n: the test also checks Page Layout's Sheet Options labels (#1891) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From CodeRabbit's review of #1892: the fix also strips the prefix from "Sheet Options|View" (the Gridlines and Headings checkboxes), but the test only rendered File › Options. It now opens Page Layout in French and Serbian too and checks that the checkboxes read "Affichage" / "Prikaz", not "Options de la feuille|Affichage" / "Opcije lista|Prikaz". It fails with the old catalog values. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/tests/file_menu.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/tests/file_menu.rs b/crates/ui-egui/tests/file_menu.rs index d4aa13c2..6cce9cdb 100644 --- a/crates/ui-egui/tests/file_menu.rs +++ b/crates/ui-egui/tests/file_menu.rs @@ -134,9 +134,9 @@ fn file_options_opens_the_options_page_and_pages_are_reachable_by_command() { #[test] fn translated_options_headings_show_no_context_prefix() { - for (code, headings, prefixed) in [ - ("fr", ["Général", "Enregistrer"], ["Options|Général", "Options|Enregistrer"]), - ("sr", ["Opšte", "Sačuvaj"], ["Opcije|Opšte", "Opcije|Sačuvaj"]), + for (code, headings, prefixed, (view, view_prefixed)) in [ + ("fr", ["Général", "Enregistrer"], ["Options|Général", "Options|Enregistrer"], ("Affichage", "Options de la feuille|Affichage")), + ("sr", ["Opšte", "Sačuvaj"], ["Opcije|Opšte", "Opcije|Sačuvaj"], ("Prikaz", "Opcije lista|Prikaz")), ] { let mut h = harness(Services::default()); h.state_mut().run("app.language.set", serde_json::json!({"language": code})).unwrap(); @@ -149,6 +149,13 @@ fn translated_options_headings_show_no_context_prefix() { for label in prefixed { assert!(h.query_by_label(label).is_none(), "{code}: {label} shows its context"); } + // Page Layout › Sheet Options: the Gridlines and Headings "View" checkboxes had the same + // prefix. The View tab may share the word, so count at least the two checkboxes. + h.state_mut().run("ui.backstage", serde_json::json!({"page": "close"})).unwrap(); + h.state_mut().ui.ribbon_tab = "Page Layout".into(); + h.run_steps(3); + assert!(h.query_by_label(view_prefixed).is_none(), "{code}: {view_prefixed} shows its context"); + assert!(h.query_all_by_label(view).count() >= 2, "{code}: the Sheet Options checkboxes don't read {view}"); } }