Merge pull request #335 from Losminek/cs-pr

i18n: Czech interface language (cs)

# Conflicts:
#	ROADMAP.md
#	contributors/people.toml
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/theme.rs
#	docs/localization-parity.md
This commit is contained in:
Brandon Thomas 2026-10-11 12:10:06 -04:00
commit 0305d2a666
11 changed files with 325 additions and 6 deletions

View File

@ -170,6 +170,7 @@ Ranked; estimates in Opus 5.5 agent-hours. Detail and the full beta-gate list in
| 2026-10-11 | minor | Progress log: XLSX threaded comments round trip (#300) |
| 2026-10-11 | minor | Catalog 258 → 259 / 290: Allow Edit Ranges (#307) |
| 2026-10-11 | minor | Worksheet functions 505 / 523 (FORECAST.ETS family); progress log row |
| 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 |

View File

@ -42,3 +42,6 @@ real_name = "Alexander Alber"
[people.webdevtodayjason]
display_name = "Jason Brashear"
sync_github_name = false
[people.Losminek]
sync_github_name = true

View File

@ -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;

View File

@ -175,7 +175,7 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
("view.interfaceScale", "Interface Size", "{percent: 75–200 | \"auto\"} (100 = auto; clamped)"),
("ui.dialog", "Open Dialog", ""),
("ui.backstage", "File (Backstage view)", "{page?: home|new|open|info|saveAs|print|export|options|close}"),
("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"|\"de\"} (alias: code)"),
("app.language.set", "Interface Language", "{language: \"en\"|\"zh\"|\"ja\"|\"ko\"|\"ru\"|\"pt\"|\"de\"|\"cs\"} (alias: code)"),
("app.language.english", "Interface Language: English", ""),
("app.language.japanese", "Interface Language: Japanese", ""),
] {

View File

@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
mod de;
mod es;
mod cs;
mod ko;
mod pt;
mod ru;
@ -29,10 +30,11 @@ pub enum Language {
#[serde(rename = "pt")]
PtBr,
De,
Cs,
}
impl Language {
pub const ALL: [Self; 8] = [Self::En, Self::Es, Self::Zh, Self::Ja, Self::Ko, Self::Ru, Self::PtBr, Self::De];
pub const ALL: [Self; 9] = [Self::En, Self::Es, Self::Zh, Self::Ja, Self::Ko, Self::Ru, Self::PtBr, Self::De, Self::Cs];
/// The language's own name, shown in the switcher.
pub fn name(self) -> &'static str {
@ -45,6 +47,7 @@ impl Language {
Self::Ru => "Русский",
Self::PtBr => "Português (Brasil)",
Self::De => "Deutsch",
Self::Cs => "Čeština",
}
}
@ -59,6 +62,7 @@ impl Language {
Self::Ru => "ru",
Self::PtBr => "pt",
Self::De => "de",
Self::Cs => "cs",
}
}
@ -76,6 +80,7 @@ impl Language {
"ru" => Some(Self::Ru),
"pt" => Some(Self::PtBr),
"de" => Some(Self::De),
"cs" => Some(Self::Cs),
_ => None,
}
}
@ -111,6 +116,7 @@ impl Language {
Self::Ru => ru::translate(text),
Self::PtBr => pt::translate(text),
Self::De => de::translate(text),
Self::Cs => cs::translate(text),
};
translated.unwrap_or_else(|| text.rsplit_once('|').map_or(text, |(_, label)| label))
}
@ -519,6 +525,7 @@ mod tests {
assert!(pt::translate(english).is_some_and(|text| !text.is_empty()), "missing Portuguese: {english}");
assert!(es::translate(english).is_some_and(|text| !text.is_empty()), "missing Spanish: {english}");
assert!(de::translate(english).is_some_and(|text| !text.is_empty()), "missing German: {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"), "数据");
@ -536,10 +543,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);
}
@ -572,6 +585,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]

View File

@ -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 oblast",
"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" => "Zadat",
"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,
})
}

View File

@ -389,7 +389,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" => {

View File

@ -621,7 +621,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, Language::De] {
for l in [Language::En, Language::Zh, Language::Ko, Language::Ru, Language::PtBr, Language::De, Language::Cs] {
assert_eq!(HanOrder::of(l), HanOrder::Chinese, "{l:?} shares the default order (no font rebuild)");
}
}
@ -647,13 +647,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();

View File

@ -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"), "Orientación");
}
#[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);
}

View File

@ -268,6 +268,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);
}

View File

@ -79,5 +79,6 @@ language (none so far) and of localized function names.
|---|---|---|
| 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) |
| 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 |