From c8ce333a74c14af603ce4f001f562243d19c1ba0 Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:35:04 +0200 Subject: [PATCH 1/5] i18n: Czech interface language (cs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Czech table with all 266 interface labels (the same key set as Korean, Russian, Simplified Chinese and Brazilian Portuguese) and register `cs` in Language: name "Čeština", code `cs`, system tags `cs-*` / `cs_*`, appended to the end of the switcher. The language command help, its error text and the snapshot example usage list `cs`. Like the other ribbon tables, it covers the ribbon, sheet tabs and widgets; dialogs, menus and messages stay English. Formulas stay canonical English: Czech uses the English formula locale like every non-Spanish language. Translated from the meaning of each English label (clean-room, not from another product's localization), with AI assistance. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/examples/snapshot.rs | 2 +- crates/ui-egui/src/control.rs | 2 +- crates/ui-egui/src/i18n.rs | 8 +- crates/ui-egui/src/i18n/cs.rs | 273 ++++++++++++++++++++++++++++ crates/ui-egui/src/lib.rs | 2 +- 5 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 crates/ui-egui/src/i18n/cs.rs diff --git a/crates/ui-egui/examples/snapshot.rs b/crates/ui-egui/examples/snapshot.rs index 93359f0..0a481b2 100644 --- a/crates/ui-egui/examples/snapshot.rs +++ b/crates/ui-egui/examples/snapshot.rs @@ -1,7 +1,7 @@ //! Renders the whole GridCraft window offscreen (wgpu, no window) to a PNG. //! //! `cargo run --release -p gridcraft-ui-egui --example snapshot -- [--sample sales] [--in file.xlsx] -//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] [--language en|zh|ja|ko|ru|pt] +//! [--size 1440x900] [--scale 2] [--cmd 'id={json}']... [--tab Insert] [--dark] [--language en|zh|ja|ko|ru|pt|cs] //! [--dialog 'name={json}'] out.png` use gridcraft_engine::Session; diff --git a/crates/ui-egui/src/control.rs b/crates/ui-egui/src/control.rs index e6da0f0..f2a1e05 100644 --- a/crates/ui-egui/src/control.rs +++ b/crates/ui-egui/src/control.rs @@ -174,7 +174,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) - ("view.theme", "Display Theme", ""), ("view.zoom100", "100%", ""), ("ui.dialog", "Open Dialog", ""), - ("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"} (alias: code)"), + ("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"|\"cs\"} (alias: code)"), ("app.language.english", "Interface Language: English", ""), ("app.language.japanese", "Interface Language: Japanese", ""), ] { diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 1e7b212..2ff8399 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; +mod cs; mod ko; mod pt; mod ru; @@ -26,10 +27,11 @@ pub enum Language { Ru, #[serde(rename = "pt")] PtBr, + Cs, } impl Language { - pub const ALL: [Self; 7] = [Self::En, Self::Es, Self::Zh, Self::Ja, Self::Ko, Self::Ru, Self::PtBr]; + pub const ALL: [Self; 8] = [Self::En, Self::Es, Self::Zh, Self::Ja, Self::Ko, Self::Ru, Self::PtBr, Self::Cs]; /// The language's own name, shown in the switcher. pub fn name(self) -> &'static str { @@ -41,6 +43,7 @@ impl Language { Self::Ko => "한국어", Self::Ru => "Русский", Self::PtBr => "Português (Brasil)", + Self::Cs => "Čeština", } } @@ -54,6 +57,7 @@ impl Language { Self::Ko => "ko", Self::Ru => "ru", Self::PtBr => "pt", + Self::Cs => "cs", } } @@ -70,6 +74,7 @@ impl Language { "ko" => Some(Self::Ko), "ru" => Some(Self::Ru), "pt" => Some(Self::PtBr), + "cs" => Some(Self::Cs), _ => None, } } @@ -103,6 +108,7 @@ impl Language { Self::Ko => ko::translate(text), Self::Ru => ru::translate(text), Self::PtBr => pt::translate(text), + Self::Cs => cs::translate(text), }; translated.unwrap_or_else(|| text.rsplit_once('|').map_or(text, |(_, label)| label)) } diff --git a/crates/ui-egui/src/i18n/cs.rs b/crates/ui-egui/src/i18n/cs.rs new file mode 100644 index 0000000..1f54793 --- /dev/null +++ b/crates/ui-egui/src/i18n/cs.rs @@ -0,0 +1,273 @@ +//! Czech translations for the interface strings in i18n.rs. + +pub(super) fn translate(text: &str) -> Option<&'static str> { + Some(match text { + "Paste" => "Vložit", + "Paste Values" => "Vložit hodnoty", + "Paste Formulas" => "Vložit vzorce", + "Paste Formatting" => "Vložit formátování", + "Transpose" => "Transponovat", + "Paste Link" => "Vložit propojení", + "Paste Special…" => "Vložit jinak…", + "Cut (⌘X)" => "Vyjmout (⌘X)", + "Copy (⌘C)" => "Kopírovat (⌘C)", + "Format Painter (double-click to keep it on)" => "Štětec formátu (dvojklikem zůstane zapnutý)", + "Bold (⌘B)" => "Tučné (⌘B)", + "Italic (⌘I)" => "Kurzíva (⌘I)", + "Underline (⌘U)" => "Podtržení (⌘U)", + "Double Underline" => "Dvojité podtržení", + "Strikethrough" => "Přeškrtnutí", + "Underline" => "Podtržení", + "Increase Font Size" => "Zvětšit písmo", + "Decrease Font Size" => "Zmenšit písmo", + "Wrap Text" => "Zalamovat text", + "Merge & Center" => "Sloučit a vycentrovat", + "Merge Cells" => "Sloučit buňky", + "Merge Across" => "Sloučit po řádcích", + "Unmerge Cells" => "Zrušit sloučení buněk", + "Fill Color" => "Barva výplně", + "Font Color" => "Barva písma", + "Borders" => "Ohraničení", + "Top\nAlign" => "Zarovnat\nnahoru", + "Middle\nAlign" => "Zarovnat\ndoprostřed", + "Bottom\nAlign" => "Zarovnat\ndolů", + "Left" => "Doleva", + "Right" => "Doprava", + "Center" => "Zarovnat na střed", + "Horizontal" => "Vodorovně", + "Vertical Text" => "Svislý text", + "Decrease Indent" => "Zmenšit odsazení", + "Increase Indent" => "Zvětšit odsazení", + "Orientation" => "Orientace textu", + "Page Layout|Orientation" => "Orientace", + "Angle Counterclockwise" => "Natočit proti směru hodin", + "Angle Clockwise" => "Natočit po směru hodin", + "Rotate Text Up" => "Otočit text nahoru", + "Rotate Text Down" => "Otočit text dolů", + "Accounting Number Format" => "Účetnický formát čísla", + "Percent Style" => "Styl procent", + "Comma Style" => "Styl s oddělovačem tisíců", + "Increase Decimal" => "Přidat desetinné místo", + "Decrease Decimal" => "Odebrat desetinné místo", + "More Accounting Formats…" => "Další účetnické formáty…", + "Conditional\nFormatting" => "Podmíněné\nformátování", + "Conditional Formatting" => "Podmíněné formátování", + "Format\nas Table" => "Formátovat\njako tabulku", + "Cell\nStyles" => "Styly\nbuněk", + "Format Cells…" => "Formát buněk…", + "Format Cell Alignment…" => "Zarovnání buněk…", + "Format\nPane" => "Podokno\nformátu", + "Format" => "Formát", + "Insert" => "Vložit", + "Insert Cells…" => "Vložit buňky…", + "Delete" => "Odstranit", + "Delete Cells…" => "Odstranit buňky…", + "Insert Sheet Rows" => "Vložit řádky listu", + "Insert Sheet Columns" => "Vložit sloupce listu", + "Delete Sheet Rows" => "Odstranit řádky listu", + "Delete Sheet Columns" => "Odstranit sloupce listu", + "Row Height…" => "Výška řádku…", + "AutoFit Row Height" => "Přizpůsobit výšku řádku", + "Column Width…" => "Šířka sloupce…", + "AutoFit Column Width" => "Přizpůsobit šířku sloupce", + "Default Width…" => "Výchozí šířka…", + "Hide Rows" => "Skrýt řádky", + "Unhide Rows" => "Zobrazit řádky", + "Hide Columns" => "Skrýt sloupce", + "Unhide Columns" => "Zobrazit sloupce", + "Lock Cell" => "Zamknout buňku", + "Clear" => "Vymazat", + "Clear All" => "Vymazat vše", + "Clear Formats" => "Vymazat formáty", + "Clear Contents" => "Vymazat obsah", + "Clear Comments and Notes" => "Vymazat komentáře a poznámky", + "Clear Hyperlinks" => "Vymazat hypertextové odkazy", + "AutoSum" => "Automatický\nsoučet", + "AutoSum (⌘⇧T)" => "Automatický součet (⌘⇧T)", + "Flash\nFill" => "Dynamické\ndoplňování", + "Flash Fill" => "Dynamické doplňování", + "Fill" => "Vyplnit", + "Clear Print Area" => "Vymazat oblast tisku", + "Find &\nSelect" => "Najít a\nvybrat", + "Find…" => "Najít…", + "Replace…" => "Nahradit…", + "Go To…" => "Přejít na…", + "Go To Special…" => "Přejít na jinak…", + "PivotTable" => "Kontingenční\ntabulka", + "Recommended\nCharts" => "Doporučené\ngrafy", + "Table" => "Tabulka", + "Pictures" => "Obrázky", + "Shapes" => "Obrazce", + "Icons" => "Ikony", + "Text\nBox" => "Textové\npole", + "Comment" => "Komentář", + "New\nComment" => "Nový\nkomentář", + "New Note" => "Nová poznámka", + "Show/Hide Note" => "Zobrazit/skrýt poznámku", + "Header &\nFooter" => "Záhlaví a\nzápatí", + "Text to\nColumns" => "Text do\nsloupců", + "Link" => "Odkaz", + "Symbol" => "Symbol", + "Sparklines" => "Minigrafy", + "Header & Footer" => "Záhlaví a zápatí", + "Draw with ink" => "Kreslit od ruky", + "Eraser" => "Guma", + "Ink to\nShape" => "Rukopis\nna obrazec", + "Select\nObjects" => "Vybrat\nobjekty", + "Themes" => "Motivy", + "Margins" => "Okraje", + "Size" => "Velikost", + "Print\nArea" => "Oblast\ntisku", + "Set Print Area" => "Nastavit oblast tisku", + "Breaks" => "Konce", + "Insert Page Break" => "Vložit konec stránky", + "Remove Page Break" => "Odebrat konec stránky", + "Reset All Page Breaks" => "Obnovit všechny konce stránek", + "Print\nTitles" => "Tisk\nnázvů", + "Narrow" => "Úzké", + "Wide" => "Široké", + "Normal" => "Normální", + "Portrait" => "Na výšku", + "Landscape" => "Na šířku", + "Custom Margins…" => "Vlastní okraje…", + "Insert\nFunction" => "Vložit\nfunkci", + "Name\nManager" => "Správce\nnázvů", + "Define Name" => "Definovat název", + "Use in Formula" => "Použít ve vzorci", + "Create from Selection" => "Vytvořit z výběru", + "Trace Precedents" => "Sledovat předchůdce", + "Trace Dependents" => "Sledovat následníky", + "Remove Arrows" => "Odebrat šipky", + "Show Formulas" => "Zobrazit vzorce", + "Error Checking" => "Kontrola chyb", + "Evaluate Formula" => "Vyhodnotit vzorec", + "Watch Window" => "Okno sledování", + "Calculation\nOptions" => "Možnosti\nvýpočtu", + "Calculate Now" => "Přepočítat", + "Calculate Sheet" => "Přepočítat list", + "Automatic" => "Automaticky", + "Automatic Except for Data Tables" => "Automaticky kromě tabulek dat", + "Manual" => "Ručně", + "Sort &\nFilter" => "Seřadit a\nfiltrovat", + "Sort" => "Seřadit", + "Sort A to Z" => "Seřadit od A do Z", + "Sort Z to A" => "Seřadit od Z do A", + "Custom Sort…" => "Vlastní řazení…", + "Filter" => "Filtr", + "Reapply" => "Použít znovu", + "Get Data\n(Text/CSV)" => "Načíst data\n(text/CSV)", + "Remove\nDuplicates" => "Odebrat\nduplicity", + "Data\nValidation" => "Ověření\ndat", + "Data Validation" => "Ověření dat", + "Data Validation…" => "Ověření dat…", + "Circle Invalid Data" => "Zakroužkovat neplatná data", + "Clear Validation" => "Vymazat ověření", + "Group" => "Seskupit", + "Ungroup" => "Zrušit seskupení", + "Subtotal" => "Mezisoučet", + "What-If\nAnalysis" => "Citlivostní\nanalýza", + "Data Table…" => "Tabulka dat…", + "Scenario Manager…" => "Správce scénářů…", + "Goal Seek…" => "Hledat řešení…", + "Spelling" => "Pravopis", + "Check\nAccessibility" => "Zkontrolovat\npřístupnost", + "Threaded Comments" => "Komentáře ve vláknech", + "Show\nComments" => "Zobrazit\nkomentáře", + "Protect\nWorkbook" => "Zamknout\nsešit", + "Unprotect\nSheet" => "Odemknout\nlist", + "Protect Sheet…" => "Zamknout list…", + "Comments" => "Komentáře", + "Notes" => "Poznámky", + "Page Break\nPreview" => "Náhled\nkonců stránek", + "Page\nLayout" => "Rozložení\nstránky", + "Formula Bar" => "Řádek vzorců", + "Gridlines" => "Mřížka", + "Headings" => "Záhlaví", + "Zoom" => "Lupa", + "100%" => "100 %", + "Zoom to\nSelection" => "Přiblížit\nna výběr", + "Freeze\nPanes" => "Ukotvit\npříčky", + "Freeze Panes" => "Ukotvit příčky", + "Freeze Top Row" => "Ukotvit horní řádek", + "Freeze First Column" => "Ukotvit první sloupec", + "Unfreeze Panes" => "Uvolnit příčky", + "Dark Mode" => "Tmavý režim", + "Display theme" => "Vzhled", + "System" => "Podle systému", + "Light" => "Světlý", + "Dark" => "Tmavý", + "Interface language" => "Jazyk rozhraní", + "Command\nPalette" => "Paleta\npříkazů", + "Agent\nControl" => "Řízení\nagenty", + "Action\nJournal" => "Deník\nakcí", + "About\nGridCraft" => "O aplikaci\nGridCraft", + "Share" => "Sdílet", + "Add Chart\nElement" => "Přidat prvek\ngrafu", + "Change\nChart Type" => "Změnit\ntyp grafu", + "Switch\nRow/Column" => "Prohodit\nřádky/sloupce", + "Data Labels: None" => "Popisky dat: žádné", + "Data Labels: Show" => "Popisky dat: zobrazit", + "Legend: None" => "Legenda: žádná", + "Legend: Bottom" => "Legenda: dole", + "Legend: Top" => "Legenda: nahoře", + "Legend: Right" => "Legenda: vpravo", + "Gridlines: None" => "Mřížka: žádná", + "Gridlines: Show" => "Mřížka: zobrazit", + "Chart\nTitle" => "Název\ngrafu", + "Delete\nChart" => "Odstranit\ngraf", + "Convert\nto Range" => "Převést\nna rozsah", + "Table\nStyles" => "Styly\ntabulky", + "Top 10 Items" => "Prvních 10 položek", + "Top 10%" => "Prvních 10 %", + "Bottom 10 Items" => "Posledních 10 položek", + "Bottom 10%" => "Posledních 10 %", + "Above Average" => "Nad průměrem", + "Below Average" => "Pod průměrem", + "Clear Rules from Entire Sheet" => "Vymazat pravidla z celého listu", + "Clear Rules from Selected Cells" => "Vymazat pravidla z vybraných buněk", + "Constants" => "Konstanty", + "Data Bars" => "Datové pruhy", + "Color Scales" => "Barevné škály", + "Icon Sets" => "Sady ikon", + "Home" => "Domů", + "Draw" => "Kreslení", + "Page Layout" => "Rozložení stránky", + "Formulas" => "Vzorce", + "Data" => "Data", + "Review" => "Revize", + "View" => "Zobrazení", + "Automate" => "Automatizace", + "Table Design" => "Návrh tabulky", + "Chart Design" => "Návrh grafu", + "Ready" => "Připraveno", + "Enter" => "Zadávání", + "Edit" => "Úpravy", + "Point" => "Výběr odkazu", + "Calculate" => "Přepočítat", + "Select destination and press Enter or choose Paste" => "Vyberte cíl a stiskněte Enter nebo zvolte Vložit", + "General" => "Obecný", + "Number" => "Číslo", + "Currency" => "Měna", + "Accounting" => "Účetnický", + "Date" => "Datum", + "Time" => "Čas", + "Percentage" => "Procenta", + "Fraction" => "Zlomek", + "Scientific" => "Vědecký", + "Text" => "Text", + "Special" => "Speciální", + "Custom" => "Vlastní", + "Short Date" => "Krátké datum", + "Long Date" => "Dlouhé datum", + "More Number Formats…" => "Další formáty čísel…", + "Financial" => "Finanční", + "Logical" => "Logické", + "Date &\nTime" => "Datum a\nčas", + "Lookup &\nReference" => "Vyhledávání\na odkazy", + "Math &\nTrig" => "Matematika a\ntrigonometrie", + "More\nFunctions" => "Další\nfunkce", + "Sheet Options|View" => "Zobrazit", + "Print" => "Tisknout", + _ => return None, + }) +} diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 1225eec..99a9960 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -328,7 +328,7 @@ impl SheetApp { self.ui.language = l; Ok(json!({"language": l})) } - None => Err(format!("unknown language {code:?} (use \"en\", \"es\", \"zh\", \"ja\", \"ko\", \"ru\" or \"pt\")")), + None => Err(format!("unknown language {code:?} (use \"en\", \"es\", \"zh\", \"ja\", \"ko\", \"ru\", \"pt\" or \"cs\")")), } } "app.language.english" => { From f44b31e15443507e2a03d54cf741428d5f0d900c Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:36:33 +0200 Subject: [PATCH 2/5] i18n: cover Czech in the interface-language tests - The catalog-completeness test requires a non-empty Czech label for every key of the reference (Japanese) table. - Czech context keys: `Page Layout|Orientation` and `Orientation` get different labels; unknown text such as `Sheet1!A1` passes through. - `cs-CZ` / `cs_CZ.UTF-8` system tags and `parse("cs")` resolve to Czech. - The ribbon renders after switching to Czech, and the choice persists in UiState. - Czech keeps canonical formula entry (`=SUM(1.5,2.25)`), and `=SUMA(1;2)` is not accepted. - The UI font draws every Czech accented letter, upper and lower case, without tofu; Czech shares the default Han font order. Czech spells "Data" as English does, so the Czech checks probe "Formulas" -> "Vzorce", where an English fallback would be caught. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n.rs | 10 ++++++++++ crates/ui-egui/src/theme.rs | 6 ++++-- crates/ui-egui/tests/formula_locale.rs | 16 ++++++++++++++++ crates/ui-egui/tests/interaction.rs | 7 +++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/i18n.rs b/crates/ui-egui/src/i18n.rs index 2ff8399..c55d9c8 100644 --- a/crates/ui-egui/src/i18n.rs +++ b/crates/ui-egui/src/i18n.rs @@ -440,6 +440,7 @@ mod tests { assert!(ko::translate(english).is_some_and(|text| !text.is_empty()), "missing Korean: {english}"); assert!(ru::translate(english).is_some_and(|text| !text.is_empty()), "missing Russian: {english}"); assert!(pt::translate(english).is_some_and(|text| !text.is_empty()), "missing Portuguese: {english}"); + assert!(cs::translate(english).is_some_and(|text| !text.is_empty()), "missing Czech: {english}"); } assert_eq!(Language::Ja.tr("Data"), "データ"); assert_eq!(Language::Zh.tr("Data"), "数据"); @@ -452,10 +453,16 @@ mod tests { assert_eq!(Language::Zh.tr("Sheet1!A1"), "Sheet1!A1"); assert_eq!(Language::Ko.tr("Sheet1!A1"), "Sheet1!A1"); assert_eq!(Language::Ru.tr("Sheet1!A1"), "Sheet1!A1"); + // Czech "Data" is spelled as in English, so probe it with a label that differs. + assert_eq!(Language::Cs.tr("Data"), "Data"); + assert_eq!(Language::Cs.tr("Formulas"), "Vzorce"); + assert_eq!(Language::Cs.tr("Sheet1!A1"), "Sheet1!A1"); // A `context|label` key shows only its label in English and splits the translation. assert_eq!(Language::En.tr("Page Layout|Orientation"), "Orientation"); assert_eq!(Language::Ja.tr("Page Layout|Orientation"), "印刷の向き"); assert_eq!(Language::Ja.tr("Orientation"), "方向"); + assert_eq!(Language::Cs.tr("Page Layout|Orientation"), "Orientace"); + assert_eq!(Language::Cs.tr("Orientation"), "Orientace textu"); assert_eq!(Language::parse("xx"), None); } @@ -486,6 +493,9 @@ mod tests { assert_eq!(Language::from_tag("pt-BR"), Some(Language::PtBr)); assert_eq!(Language::parse("pt"), Some(Language::PtBr)); assert_eq!(Language::parse("pt-br"), Some(Language::PtBr)); + assert_eq!(Language::from_tag("cs-CZ"), Some(Language::Cs)); + assert_eq!(Language::from_tag("cs_CZ.UTF-8"), Some(Language::Cs)); + assert_eq!(Language::parse("cs"), Some(Language::Cs)); } #[test] diff --git a/crates/ui-egui/src/theme.rs b/crates/ui-egui/src/theme.rs index 219e2d2..25b9ca8 100644 --- a/crates/ui-egui/src/theme.rs +++ b/crates/ui-egui/src/theme.rs @@ -587,7 +587,7 @@ mod tests { assert_eq!(keys(HanOrder::Japanese), ["sys-cjk-han-ja", "sys-cjk-han-zh", "sys-cjk-hangul", "sys-thai", "sys-hebrew"]); assert_eq!(keys(HanOrder::Chinese), ["sys-cjk-han-zh", "sys-cjk-han-ja", "sys-cjk-hangul", "sys-thai", "sys-hebrew"]); assert_eq!(HanOrder::of(Language::Ja), HanOrder::Japanese); - for l in [Language::En, Language::Zh, Language::Ko, Language::Ru, Language::PtBr] { + for l in [Language::En, Language::Zh, Language::Ko, Language::Ru, Language::PtBr, Language::Cs] { assert_eq!(HanOrder::of(l), HanOrder::Chinese, "{l:?} shares the default order (no font rebuild)"); } } @@ -613,13 +613,15 @@ mod tests { // Real render check, per script: the proportional family (what cells and the UI use) must // resolve a sample string to a real glyph, not the tofu replacement char. Skips a script // whose face is absent (a minimal machine) and fails if a face is present but not wired in. - let cases: [(&str, Vec<(String, u32)>, &str); 6] = [ + let cases: [(&str, Vec<(String, u32)>, &str); 7] = [ ("sys-cjk-han-ja", cjk_han_japanese(), "カテゴリ"), ("sys-cjk-han-zh", cjk_han_simplified(), "页面布局 客户端配置"), ("sys-cjk-hangul", cjk_hangul(), "한글"), ("sys-thai", thai(), "ทดสอบ"), ("sys-hebrew", hebrew(), "עברית"), ("ui", candidates(UI), "Русский"), + // Czech interface labels: every Czech accented letter, in both cases. + ("ui", candidates(UI), "Příliš žluťoučký kůň úpěl ďábelské ódy ŘŠČŽÝÁÍÉÚŮĚŤĎŇÓ"), ]; for language in [Language::En, Language::Ja] { let ctx = egui::Context::default(); diff --git a/crates/ui-egui/tests/formula_locale.rs b/crates/ui-egui/tests/formula_locale.rs index a92ab13..6ce2208 100644 --- a/crates/ui-egui/tests/formula_locale.rs +++ b/crates/ui-egui/tests/formula_locale.rs @@ -185,3 +185,19 @@ fn portuguese_interface_retains_its_canonical_formula_fallback() { assert_eq!(restored.language, Language::PtBr); assert_eq!(Language::Es.tr("Page Layout|Orientation"), "Orientation"); } + +#[test] +fn czech_interface_retains_its_canonical_formula_fallback() { + // The Czech table translates interface labels only: formulas keep canonical English names, + // `,` separators and `.` decimals until a Czech formula locale exists. + let mut app = app(); + app.run("app.language.set", json!({"code":"cs-CZ"})).unwrap(); + assert_eq!(app.ui.language, Language::Cs); + assert_eq!(app.ui.language.tr("Formulas"), "Vzorce"); + assert!(enter(&mut app, "=SUM(1.5,2.25)")); + app.begin_edit(None, true); + assert_eq!(app.editor.as_ref().unwrap().text, "=SUM(1.5,2.25)"); + assert!(!enter(&mut app, "=SUMA(1;2)")); + let restored: UiState = serde_json::from_str(&serde_json::to_string(&app.ui).unwrap()).unwrap(); + assert_eq!(restored.language, Language::Cs); +} diff --git a/crates/ui-egui/tests/interaction.rs b/crates/ui-egui/tests/interaction.rs index 84f20a6..1b71c82 100644 --- a/crates/ui-egui/tests/interaction.rs +++ b/crates/ui-egui/tests/interaction.rs @@ -209,6 +209,13 @@ fn language_switch_translates_the_ribbon_and_persists() { let restored: gridcraft_ui_egui::UiState = serde_json::from_str(&serde_json::to_string(&h.state().ui).unwrap()).unwrap(); assert_eq!(restored.language, language); } + // Czech spells "Data" as English does, so the Czech table is checked on a label that differs. + h.state_mut().run("app.language.set", json!({"language": "cs"})).unwrap(); + h.run_steps(2); + assert_eq!(h.state().ui.language, Language::Cs); + assert_eq!(Language::Cs.tr("Formulas"), "Vzorce"); + let restored: gridcraft_ui_egui::UiState = serde_json::from_str(&serde_json::to_string(&h.state().ui).unwrap()).unwrap(); + assert_eq!(restored.language, Language::Cs); h.state_mut().run("app.language.set", json!({"language": "en"})).unwrap(); assert_eq!(h.state().ui.language, Language::En); } From 509afaf4cfef5e2b903e772ca12aaabe2e329855 Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:36:55 +0200 Subject: [PATCH 3/5] docs: list Czech (cs) in the localization status Czech joins Russian under "other shipped languages" in ROADMAP.md and docs/localization-parity.md: 266 ribbon labels (~12%), menus only, formulas still en-US. Timestamp lines and revision history updated. Kept separate from the catalog so it can be dropped if the docs are refreshed upstream. Co-Authored-By: Claude Opus 5.5 --- ROADMAP.md | 5 +++-- docs/localization-parity.md | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 1189fd7..8817691 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,7 +2,7 @@ **Stage: alpha** · next: beta, ~25 points of "ready for real work" and ~160–240 h away -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-10 · **Change:** major (full re-measure against Excel for Mac 16.113.4; restructured to the craftrules progress-docs standard) · **Target:** Microsoft Excel (Microsoft 365) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Czech ribbon table listed under other shipped languages) · **Target:** Microsoft Excel (Microsoft 365) GridCraft aims at full Microsoft Excel parity, and at being better in places: faster, open (XLSX is the native format), fully scriptable by agents (MCP, control channel, CLI), and on the web. @@ -110,7 +110,7 @@ is in PR #163. Detail: [localization-parity.md](docs/localization-parity.md). | Korean | ko | menus only | ~12% | | Vietnamese | vi | none | 0% | -Other shipped languages: 1 (Russian, menus only, ~12%). +Other shipped languages: 2 (Russian and Czech, menus only, ~12% each). ## Upcoming @@ -142,6 +142,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in | Date | Change | Summary | |---|---|---| +| 2026-10-11 | minor | Czech (cs) listed under other shipped languages: ribbon only, ~12% | | 2026-10-10 | minor | Readiness-by-audience table with hours to ~95% per audience; full number unchanged (additive weighted sum) | | 2026-10-10 | minor | Added Mainstream practitioner (~50%) and Essentials user (~63%); languages updated for the 10-10 merges (ribbon in ja, zh, ko, pt-BR, ru); localization 5%→10%; full number re-checked (51.6, unchanged) | | 2026-10-10 | minor | Checked against the alpha gate (six core workflows pass): stays alpha | diff --git a/docs/localization-parity.md b/docs/localization-parity.md index 3a8a991..17a5efc 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-10 · **Change:** minor (re-measured after the 10-10 language and font-fallback merges) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Czech ribbon table added) · **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, @@ -12,7 +12,7 @@ right-to-left sheets and full complex-script support. Part of - **A first catalog exists, for the ribbon only.** `crates/ui-egui/src/i18n.rs` (merged 10-10 in #31, #104, #82) adds an interface-language preference that defaults to the system language (closes #20) and a View-tab switcher, with English-keyed lookup tables: Japanese 268 labels, - Simplified Chinese, Korean, Russian and Brazilian Portuguese 266 each. The tables are used by + Simplified Chinese, Korean, Russian, Brazilian Portuguese and Czech 266 each. The tables are used by the ribbon, sheet tabs and widgets (`ribbon.rs`, `tabs.rs`, `widgets.rs`); dialogs, menus, context menus, task panes, status messages, command labels and function descriptions are still English literals. Untranslated labels fall back to English. @@ -51,7 +51,8 @@ separators, number/date formats, machine translation with agent review, and scri | Korean | ko | 266 (~12%) | no | Hangul via the CJK fallback; IME unverified | no | menus only | 6–10 h | | Vietnamese | vi | 0 (0%) | no | Latin with stacked diacritics: unverified | no | none | 6–10 h | -Other shipped languages: Russian (ru), 266 labels (~12%), menus only. +Other shipped languages: Russian (ru) and Czech (cs), 266 labels each (~12%), menus only. Czech +formulas stay en-US (no Czech function names, `;` separator or decimal comma yet). **Total:** catalog work 10–20 h + languages 92–144 h ≈ **100–165 h**. Parallelizes almost fully once the catalog covers dialogs and commands. **Needs a human:** native-speaker review of every @@ -73,5 +74,6 @@ language (none so far) and of localized function names. | Date | Change | Summary | |---|---|---| +| 2026-10-11 | minor | Czech (cs) ribbon table: 266 labels (~12%), menus only | | 2026-10-10 | minor | Re-measured after #31, #104, #82, #38 merged: ribbon translated into ja, zh, ko, pt-BR and ru (266–268 labels, ~12%), system-language default, CJK/Thai/Hebrew font fallback; ~10% ready, 100–165 h | | 2026-10-10 | major | Created: English only, no catalog, ~2,200 strings, script-support gaps, open language PRs | From 5f346dc3ccdc5682627a8b7609628bd8e25e3cca Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:47:02 +0200 Subject: [PATCH 4/5] i18n (cs): terminology review fixes An independent review of the table found two meaning errors: "Enter" is both the status-bar mode and the tooltip of the confirm button next to the formula bar, so "Zadat" fits both places better than "Zadavani"; "Convert to Range" uses "oblast" (as "Oblast tisku" does), not "rozsah". Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/i18n/cs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/i18n/cs.rs b/crates/ui-egui/src/i18n/cs.rs index 1f54793..5b56b20 100644 --- a/crates/ui-egui/src/i18n/cs.rs +++ b/crates/ui-egui/src/i18n/cs.rs @@ -215,7 +215,7 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Gridlines: Show" => "Mřížka: zobrazit", "Chart\nTitle" => "Název\ngrafu", "Delete\nChart" => "Odstranit\ngraf", - "Convert\nto Range" => "Převést\nna rozsah", + "Convert\nto Range" => "Převést\nna oblast", "Table\nStyles" => "Styly\ntabulky", "Top 10 Items" => "Prvních 10 položek", "Top 10%" => "Prvních 10 %", @@ -240,7 +240,7 @@ pub(super) fn translate(text: &str) -> Option<&'static str> { "Table Design" => "Návrh tabulky", "Chart Design" => "Návrh grafu", "Ready" => "Připraveno", - "Enter" => "Zadávání", + "Enter" => "Zadat", "Edit" => "Úpravy", "Point" => "Výběr odkazu", "Calculate" => "Přepočítat", From a9e743ee6d667be60a326a7b099ad826eda5b7af Mon Sep 17 00:00:00 2001 From: Losminek <301412321+Losminek@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:47:13 +0200 Subject: [PATCH 5/5] contributors: add my own people.toml entry Own entry only, as AGENTS.md describes: credited by the name on my public GitHub profile (sync_github_name), otherwise by @username. Co-Authored-By: Claude Opus 5.5 --- contributors/people.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contributors/people.toml b/contributors/people.toml index 78683ae..8086cf1 100644 --- a/contributors/people.toml +++ b/contributors/people.toml @@ -28,3 +28,6 @@ sync_github_name = false [people.edurbs] sync_github_name = true + +[people.Losminek] +sync_github_name = true