mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
i18n: Czech one/few/other plurals
Czech counts take three integer forms: one (1), few (2-4) and other (0 and 5+). Since #257 catalogs can supply `few`, so `plural()` gets a Czech rule and the 38 plural entries in cs.toml now read naturally ("1 vrstva", "3 vrstvy", "5 vrstev") instead of the count-neutral "Vrstvy: {n}" that #469 used for everything but 1. Test czech_integer_plurals covers the rule (1; 2-4; 0, 5, 11, 12, 21, 22, 100, 1 000 000), a real key through counted(), and that every Czech plural entry has one and few. Docs no longer say Czech has only one/other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
97e422226f
commit
4d9cde8fee
@@ -7,7 +7,7 @@ status = "draft — needs native review"
|
||||
[ui]
|
||||
"% of all commits" = "% všech commitů"
|
||||
"(no longer published)" = "(již nepublikováno)"
|
||||
"({n} warnings: some entries were skipped)" = { one = "({n} varování: některé položky byly přeskočeny)", other = "(Varování: {n}; některé položky byly přeskočeny)" }
|
||||
"({n} warnings: some entries were skipped)" = { one = "({n} varování: některé položky byly přeskočeny)", few = "({n} varování: některé položky byly přeskočeny)", other = "({n} varování: některé položky byly přeskočeny)" }
|
||||
"+ Create variable" = "+ Vytvořit proměnnou"
|
||||
".fig (experimental)" = ".fig (experimentální)"
|
||||
"1…9, 0" = "1…9, 0"
|
||||
@@ -455,11 +455,11 @@ status = "draft — needs native review"
|
||||
"Export settings" = "Nastavení exportu"
|
||||
"Export variables" = "Exportovat proměnné"
|
||||
"Export {name}" = "Exportovat {name}"
|
||||
"Export {n} layers" = { one = "Exportovat {n} vrstvu", other = "Exportovat vrstvy: {n}" }
|
||||
"Export {n} layers" = { one = "Exportovat {n} vrstvu", few = "Exportovat {n} vrstvy", other = "Exportovat {n} vrstev" }
|
||||
"Exported {file}" = "Exportován soubor {file}"
|
||||
"Exported {name}" = "Exportováno: {name}"
|
||||
"Exported {name}; {n} kinds of content were left out" = { one = "Exportováno: {name}; vynechán {n} druh obsahu", other = "Exportováno: {name}; vynechané druhy obsahu: {n}" }
|
||||
"Exported {n} files" = { one = "Exportován {n} soubor", other = "Exportované soubory: {n}" }
|
||||
"Exported {name}; {n} kinds of content were left out" = { one = "Exportováno: {name}; vynechán {n} druh obsahu", few = "Exportováno: {name}; vynechány {n} druhy obsahu", other = "Exportováno: {name}; vynecháno {n} druhů obsahu" }
|
||||
"Exported {n} files" = { one = "Exportován {n} soubor", few = "Exportovány {n} soubory", other = "Exportováno {n} souborů" }
|
||||
"Export…" = "Exportovat…"
|
||||
"Expose properties to instances" = "Zpřístupnit vlastnosti instancím"
|
||||
"Exposure" = "Expozice"
|
||||
@@ -477,7 +477,7 @@ status = "draft — needs native review"
|
||||
"File not found" = "Soubor nenalezen"
|
||||
"File published" = "Soubor publikován"
|
||||
"Files aren't end-to-end encrypted: the server's operator can read them." = "Soubory nemají koncové šifrování: provozovatel serveru je může číst."
|
||||
"Files nobody opens for {n} days are deleted." = { one = "Soubory, které nikdo neotevře déle než {n} den, se smažou.", other = "Soubory, které nikdo neotevře déle než {n} d, se smažou." }
|
||||
"Files nobody opens for {n} days are deleted." = { one = "Soubory, které nikdo neotevře déle než {n} den, se smažou.", few = "Soubory, které nikdo neotevře déle než {n} dny, se smažou.", other = "Soubory, které nikdo neotevře déle než {n} dní, se smažou." }
|
||||
"Fill" = "Výplň"
|
||||
"Fill (fr)" = "Vyplnit (fr)"
|
||||
"Fill container" = "Vyplnit kontejner"
|
||||
@@ -601,7 +601,7 @@ status = "draft — needs native review"
|
||||
"Import plugin from manifest…" = "Importovat plugin z manifestu…"
|
||||
"Import variables" = "Importovat proměnné"
|
||||
"Imported {name}" = "Importováno: {name}"
|
||||
"Imported {n} new variables, updated {updated}" = { one = "Importována {n} nová proměnná, aktualizováno: {updated}", other = "Nově importované proměnné: {n}, aktualizované: {updated}" }
|
||||
"Imported {n} new variables, updated {updated}" = { one = "Importována {n} nová proměnná, aktualizováno: {updated}", few = "Importovány {n} nové proměnné, aktualizováno: {updated}", other = "Importováno {n} nových proměnných, aktualizováno: {updated}" }
|
||||
"Import…" = "Importovat…"
|
||||
"In an instance, this comes from the main component" = "V instanci se toto přebírá z hlavní komponenty"
|
||||
"In the browser you can join sessions the desktop app hosts and files on a FigCraft server. A page opened over https reaches other computers only through wss:// links (a server behind TLS)." = "V prohlížeči se můžete připojit k relacím hostovaným desktopovou aplikací a k souborům na serveru FigCraft. Stránka otevřená přes https může přistupovat k jiným počítačům pouze přes odkazy wss:// (server za TLS)."
|
||||
@@ -1013,7 +1013,7 @@ status = "draft — needs native review"
|
||||
"Recent" = "Nedávné"
|
||||
"Recent files" = "Nedávné soubory"
|
||||
"Recents" = "Nedávné"
|
||||
"Recovered {n} unsaved files after the app quit unexpectedly: save them to keep them" = { one = "Po neočekávaném ukončení aplikace obnoven {n} neuložený soubor: uložte ho pro zachování", other = "Po neočekávaném ukončení aplikace obnoveny neuložené soubory (počet: {n}): uložte je pro zachování" }
|
||||
"Recovered {n} unsaved files after the app quit unexpectedly: save them to keep them" = { one = "Po neočekávaném ukončení aplikace obnoven {n} neuložený soubor: uložte ho pro zachování", few = "Po neočekávaném ukončení aplikace obnoveny {n} neuložené soubory: uložte je pro zachování", other = "Po neočekávaném ukončení aplikace obnoveno {n} neuložených souborů: uložte je pro zachování" }
|
||||
"Rectangle" = "Obdélník"
|
||||
"Rectangles" = "Obdélníky"
|
||||
"Red" = "Červená"
|
||||
@@ -1287,8 +1287,8 @@ status = "draft — needs native review"
|
||||
"Swap to {name}" = "Vyměnit za {name}"
|
||||
"Swap width and height" = "Prohodit šířku a výšku"
|
||||
"Swapped instance" = "Instance vyměněna"
|
||||
"Swapped {n} assets" = { one = "Vyměněn {n} zdroj", other = "Vyměněné zdroje: {n}" }
|
||||
"Swapped {n} assets; {missed} had no match and still use the old library" = { one = "Vyměněn {n} zdroj; bez shody: {missed}, nadále používají původní knihovnu", other = "Vyměněné zdroje: {n}; bez shody: {missed}, nadále používají původní knihovnu" }
|
||||
"Swapped {n} assets" = { one = "Vyměněn {n} zdroj", few = "Vyměněny {n} zdroje", other = "Vyměněno {n} zdrojů" }
|
||||
"Swapped {n} assets; {missed} had no match and still use the old library" = { one = "Vyměněn {n} zdroj; bez shody: {missed}, nadále používají původní knihovnu", few = "Vyměněny {n} zdroje; bez shody: {missed}, nadále používají původní knihovnu", other = "Vyměněno {n} zdrojů; bez shody: {missed}, nadále používají původní knihovnu" }
|
||||
"Sweep" = "Rozsah"
|
||||
"System" = "Systém"
|
||||
"Tab / Shift+Tab" = "Tab / Shift+Tab"
|
||||
@@ -1479,7 +1479,7 @@ status = "draft — needs native review"
|
||||
"Zoom to selection" = "Přiblížit na výběr"
|
||||
"Zoom tool: click to zoom in, drag an area" = "Nástroj přiblížení: kliknutím přiblížíte, tažením vyberete oblast"
|
||||
"Zoom tool: zoom out" = "Nástroj přiblížení: oddálit"
|
||||
"and {n} more" = { one = "a {n} další", other = "a další (počet: {n})" }
|
||||
"and {n} more" = { one = "a {n} další", few = "a {n} další", other = "a {n} dalších" }
|
||||
"bleed marks" = "značky spadávky"
|
||||
"by {author}" = "autor: {author}"
|
||||
"call any command its access allows" = "volat libovolný příkaz povolený jeho oprávněními"
|
||||
@@ -1531,35 +1531,35 @@ status = "draft — needs native review"
|
||||
"{name}, in a live session" = "{name}, v živé relaci"
|
||||
"{name}, online, open only to you" = "{name}, online, přístupný pouze vám"
|
||||
"{name}, {kind}, {w} by {h}, selected" = "{name}, {kind}, {w} × {h}, vybráno"
|
||||
"{n} added" = { one = "Přidáno: {n}", other = "Přidáno: {n}" }
|
||||
"{n} autosave versions" = { one = "{n} automaticky uložená verze", other = "Automaticky uložené verze: {n}" }
|
||||
"{n} changed" = { one = "Změněno: {n}", other = "Změněno: {n}" }
|
||||
"{n} columns" = { one = "{n} sloupec", other = "Sloupce: {n}" }
|
||||
"{n} components" = { one = "{n} komponenta", other = "Komponenty: {n}" }
|
||||
"{n} components and styles have newer versions" = { one = "{n} komponenta nebo styl má novější verzi", other = "Komponenty a styly s novější verzí: {n}" }
|
||||
"{n} conflicts to resolve" = { one = "{n} konflikt k vyřešení", other = "Konflikty k vyřešení: {n}" }
|
||||
"{n} days ago" = { one = "Před {n} dnem", other = "Před {n} dny" }
|
||||
"{n} files" = { one = "{n} soubor", other = "Soubory: {n}" }
|
||||
"{n} hours ago" = { one = "Před {n} hodinou", other = "Před {n} hodinami" }
|
||||
"{n} instances" = { one = "{n} instance", other = "Instance: {n}" }
|
||||
"{n} layers" = { one = "{n} vrstva", other = "Vrstvy: {n}" }
|
||||
"{n} layers as one file" = { one = "{n} vrstva jako jeden soubor", other = "Vrstvy jako jeden soubor: {n}" }
|
||||
"{n} layers selected" = { one = "Vybrána {n} vrstva", other = "Vybrané vrstvy: {n}" }
|
||||
"{n} layers · {parts}" = { one = "{n} vrstva · {parts}", other = "Vrstvy: {n} · {parts}" }
|
||||
"{n} matches" = { one = "{n} shoda", other = "Shody: {n}" }
|
||||
"{n} minutes ago" = { one = "Před {n} minutou", other = "Před {n} minutami" }
|
||||
"{n} months ago" = { one = "Před {n} měsícem", other = "Před {n} měsíci" }
|
||||
"{n} added" = { one = "Přidáno: {n}", few = "Přidáno: {n}", other = "Přidáno: {n}" }
|
||||
"{n} autosave versions" = { one = "{n} automaticky uložená verze", few = "{n} automaticky uložené verze", other = "{n} automaticky uložených verzí" }
|
||||
"{n} changed" = { one = "Změněno: {n}", few = "Změněno: {n}", other = "Změněno: {n}" }
|
||||
"{n} columns" = { one = "{n} sloupec", few = "{n} sloupce", other = "{n} sloupců" }
|
||||
"{n} components" = { one = "{n} komponenta", few = "{n} komponenty", other = "{n} komponent" }
|
||||
"{n} components and styles have newer versions" = { one = "{n} komponenta nebo styl má novější verzi", few = "{n} komponenty nebo styly mají novější verzi", other = "{n} komponent nebo stylů má novější verzi" }
|
||||
"{n} conflicts to resolve" = { one = "{n} konflikt k vyřešení", few = "{n} konflikty k vyřešení", other = "{n} konfliktů k vyřešení" }
|
||||
"{n} days ago" = { one = "Před {n} dnem", few = "Před {n} dny", other = "Před {n} dny" }
|
||||
"{n} files" = { one = "{n} soubor", few = "{n} soubory", other = "{n} souborů" }
|
||||
"{n} hours ago" = { one = "Před {n} hodinou", few = "Před {n} hodinami", other = "Před {n} hodinami" }
|
||||
"{n} instances" = { one = "{n} instance", few = "{n} instance", other = "{n} instancí" }
|
||||
"{n} layers" = { one = "{n} vrstva", few = "{n} vrstvy", other = "{n} vrstev" }
|
||||
"{n} layers as one file" = { one = "{n} vrstva jako jeden soubor", few = "{n} vrstvy jako jeden soubor", other = "{n} vrstev jako jeden soubor" }
|
||||
"{n} layers selected" = { one = "Vybrána {n} vrstva", few = "Vybrány {n} vrstvy", other = "Vybráno {n} vrstev" }
|
||||
"{n} layers · {parts}" = { one = "{n} vrstva · {parts}", few = "{n} vrstvy · {parts}", other = "{n} vrstev · {parts}" }
|
||||
"{n} matches" = { one = "{n} shoda", few = "{n} shody", other = "{n} shod" }
|
||||
"{n} minutes ago" = { one = "Před {n} minutou", few = "Před {n} minutami", other = "Před {n} minutami" }
|
||||
"{n} months ago" = { one = "Před {n} měsícem", few = "Před {n} měsíci", other = "Před {n} měsíci" }
|
||||
"{n} of {total}" = "{n} z {total}"
|
||||
"{n} people joined" = { one = "Připojena {n} osoba", other = "Připojené osoby: {n}" }
|
||||
"{n} preferred components" = { one = "{n} preferovaná komponenta", other = "Preferované komponenty: {n}" }
|
||||
"{n} ready" = { one = "Připraveno: {n}", other = "Připraveno: {n}" }
|
||||
"{n} removed" = { one = "Odebráno: {n}", other = "Odebráno: {n}" }
|
||||
"{n} replies" = { one = "{n} odpověď", other = "Odpovědi: {n}" }
|
||||
"{n} rows" = { one = "{n} řádek", other = "Řádky: {n}" }
|
||||
"{n} styles" = { one = "{n} styl", other = "Styly: {n}" }
|
||||
"{n} variables" = { one = "{n} proměnná", other = "Proměnné: {n}" }
|
||||
"{n} weeks ago" = { one = "Před {n} týdnem", other = "Před {n} týdny" }
|
||||
"{n} years ago" = { one = "Před {n} rokem", other = "Před {n} lety" }
|
||||
"{n} people joined" = { one = "Připojila se {n} osoba", few = "Připojily se {n} osoby", other = "Připojilo se {n} osob" }
|
||||
"{n} preferred components" = { one = "{n} preferovaná komponenta", few = "{n} preferované komponenty", other = "{n} preferovaných komponent" }
|
||||
"{n} ready" = { one = "Připraveno: {n}", few = "Připraveno: {n}", other = "Připraveno: {n}" }
|
||||
"{n} removed" = { one = "Odebráno: {n}", few = "Odebráno: {n}", other = "Odebráno: {n}" }
|
||||
"{n} replies" = { one = "{n} odpověď", few = "{n} odpovědi", other = "{n} odpovědí" }
|
||||
"{n} rows" = { one = "{n} řádek", few = "{n} řádky", other = "{n} řádků" }
|
||||
"{n} styles" = { one = "{n} styl", few = "{n} styly", other = "{n} stylů" }
|
||||
"{n} variables" = { one = "{n} proměnná", few = "{n} proměnné", other = "{n} proměnných" }
|
||||
"{n} weeks ago" = { one = "Před {n} týdnem", few = "Před {n} týdny", other = "Před {n} týdny" }
|
||||
"{n} years ago" = { one = "Před {n} rokem", few = "Před {n} lety", other = "Před {n} lety" }
|
||||
"{n}d" = "{n} d"
|
||||
"{n}h" = "{n} h"
|
||||
"{n}m" = "{n} min"
|
||||
|
||||
@@ -163,6 +163,9 @@ enum Plural {
|
||||
/// CLDR integer rules. Existing one/other catalogs may omit the extra forms.
|
||||
fn plural(code: &str, n: usize) -> Plural {
|
||||
match code {
|
||||
"cs" if n == 1 => Plural::One,
|
||||
"cs" if (2..=4).contains(&n) => Plural::Few,
|
||||
"cs" => Plural::Other,
|
||||
"ru" if n % 10 == 1 && n % 100 != 11 => Plural::One,
|
||||
"ru" if (2..=4).contains(&(n % 10)) && !(12..=14).contains(&(n % 100)) => Plural::Few,
|
||||
"ru" => Plural::Many,
|
||||
@@ -174,7 +177,7 @@ fn plural(code: &str, n: usize) -> Plural {
|
||||
}
|
||||
|
||||
// CLDR's million-count "many" in French, Spanish and Portuguese still reads their existing
|
||||
// "other" wording. Only Russian currently supplies separate few/many forms.
|
||||
// "other" wording. Czech supplies separate few forms; Russian supplies few/many forms.
|
||||
|
||||
/// Whether count `n` takes the "one" form: 0 and 1 in French and Brazilian Portuguese, never in
|
||||
/// Japanese, Korean and Chinese (no plural forms: always "other"), 1 elsewhere (CLDR's integer rules).
|
||||
|
||||
@@ -419,6 +419,26 @@ fn the_key_scanner_reads_rust_literals() {
|
||||
assert_eq!(literals(src), vec!["A \"b\"", "C {n}", "DE"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn czech_integer_plurals() {
|
||||
let cs = LANGS.iter().position(|l| l.code == "cs").unwrap();
|
||||
assert_eq!(plural("cs", 1), Plural::One);
|
||||
assert_eq!(fill(counted(cs, "{n} layers", 1), &[("n", &1)]), "1 vrstva");
|
||||
for n in [2, 3, 4] {
|
||||
assert_eq!(plural("cs", n), Plural::Few, "{n}");
|
||||
assert_eq!(fill(counted(cs, "{n} layers", n), &[("n", &n)]), format!("{n} vrstvy"));
|
||||
}
|
||||
for n in [0, 5, 11, 12, 21, 22, 100, 1_000_000] {
|
||||
assert_eq!(plural("cs", n), Plural::Other, "{n}");
|
||||
assert_eq!(fill(counted(cs, "{n} layers", n), &[("n", &n)]), format!("{n} vrstev"));
|
||||
}
|
||||
for (key, text) in LANGS[cs].ui {
|
||||
if let Text::Plural { one, few, .. } = text {
|
||||
assert!(one.is_some() && few.is_some(), "{key}: missing Czech plural forms");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn russian_language_tags_and_integer_plurals() {
|
||||
let ru = LANGS.iter().position(|l| l.code == "ru").unwrap();
|
||||
|
||||
@@ -21,7 +21,7 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
|
||||
|
||||
- **Layer:** one TOML catalog per language in `crates/ui-egui/locales/`, baked into the binary by
|
||||
the UI crate's build script (desktop and web; nothing read at run time). Keys are the English text
|
||||
(`tr("Fill")`, `trf` for `{named}` placeholders, `trn` for counts with one/other plural forms), so
|
||||
(`tr("Fill")`, `trf` for `{named}` placeholders, `trn` for counts with one/few/many/other plural forms), so
|
||||
a missing translation shows English, never a raw key. One English text that needs two
|
||||
translations takes a context (`"Fill|sizing"`).
|
||||
- **Strings:** 1,417 UI keys in `en.toml` (36 with plural forms; ~110 are labels from engine and
|
||||
@@ -55,9 +55,10 @@ supported by both apps (CJK, Arabic and Devanagari shaping, IME); Figma has no v
|
||||
until someone translates it. Every draft translates every string as of 2026-10-12 (checked by
|
||||
adding each to `COMPLETE` locally and running the check).
|
||||
- **Not yet localized:** messages the engine writes (errors, a few toasts, Dev Mode property values
|
||||
such as "inside"), number formatting (decimal points stay `.`), and plural forms beyond one/other
|
||||
(French and Brazilian Portuguese take the singular for 0 as well; Japanese, Korean and Chinese
|
||||
have one form; CLDR's "many" for millions reads as the plural).
|
||||
such as "inside"), number formatting (decimal points stay `.`), and decimal plural rules
|
||||
(`trn` takes integers: Czech uses one/few/other and Russian one/few/many; French and Brazilian
|
||||
Portuguese take the singular for 0 as well; Japanese, Korean and Chinese have one form;
|
||||
CLDR's "many" for millions in French, Spanish and Portuguese reads as the plural).
|
||||
- **Script support in documents:** Unicode bidi with Arabic shaping and mirrored brackets in text
|
||||
layers; right-to-left list markers and indents; right-to-left names in the layers panel, tabs
|
||||
and canvas labels (visual order, joined Arabic); inline IME composition with the candidate window
|
||||
|
||||
+3
-3
@@ -80,9 +80,9 @@ language also needs its script in `cjk::language`.
|
||||
`few` for 2–4, 22–24… except 12–14; and `many` for the other integers, including 0. Keep `{n}`
|
||||
in all Russian forms. Add new rules to `plural` in
|
||||
[`crates/ui-egui/src/i18n.rs`](../crates/ui-egui/src/i18n.rs). A missing form reads `other`;
|
||||
a missing key uses the fallback catalog's own plural rule. Czech has no rule of its own yet, so it
|
||||
picks `one` for 1 and `other` for everything else: its `other` forms are count-neutral for 0,
|
||||
2–4 and 5+ (e.g. `Vrstvy: {n}`).
|
||||
a missing key uses the fallback catalog's own plural rule. Czech uses `one` for 1, `few` for
|
||||
2–4 and `other` for 0 and 5+ (e.g. `{n} vrstva`, `{n} vrstvy`, `{n} vrstev`). Keep `{n}` in all
|
||||
three forms. Czech's CLDR `many` applies only to decimals; `trn` takes integer counts.
|
||||
4. To require full coverage, add the code to `COMPLETE` in
|
||||
[`crates/ui-egui/src/i18n_tests.rs`](../crates/ui-egui/src/i18n_tests.rs). Every later PR that
|
||||
adds a string then has to translate it into each complete language, so only Spanish is there:
|
||||
|
||||
Reference in New Issue
Block a user