i18n: add Hungarian (hu) UI translation

Add the Hungarian catalog (4,524 rows): every menu string, every `tl!`
literal, the contextual and `@id` rows, the plurals and the status and
error messages, written from the meaning of the English labels in
general Hungarian vector-illustration vocabulary. Conventions are in the
header of `hu.tsv`.

- Register `hu` ("Magyar") in `i18n::LANGUAGES` as a complete language
  with `plural_none` (a count takes the singular); every `hu-*` locale
  resolves to it.
- Tests: Hungarian joins the menu-label, menu-string, `tl!`-literal and
  message coverage checks; `hungarian_reads_as_hungarian` pins the core
  vocabulary, the plural, the `file.close` override and a nested
  message; ő ű Ő Ű are checked in the bundled UI fonts.
- README, ROADMAP, docs/development.md and docs/localization-parity.md
  list Hungarian.
This commit is contained in:
Skaywalker authored and bflatastic committed 2026-10-12 08:18:45 +07:00
1 parent 70e35807e7
commit b9716366bf
8 files changed
+4610 -13

No files matched your search

+1 -1
View File
@@ -217,7 +217,7 @@ doesn't write Affinity files. Current `.af` validation includes 33 pinned files,
and every-board SVG/PDF/PSD export, with fixes for Affinity 3 artboards, source-backed JPEGs and text runs.
[Scope and limits](crates/affinity/README.md); [source audit and remaining gaps](docs/affinity-validation.md).
The interface
speaks English, German, Japanese, Traditional and Simplified Chinese, Spanish, French, Italian, Russian and Ukrainian (and Czech and Brazilian Portuguese in the menus);
speaks English, German, Japanese, Traditional and Simplified Chinese, Spanish, French, Hungarian, Italian, Russian and Ukrainian (and Czech and Brazilian Portuguese in the menus);
[localization-parity.md](docs/localization-parity.md) has the details.
The scores are self-assessed from our code against Illustrator's public documentation;
[target-app-parity.md](docs/target-app-parity.md#how-this-was-measured-2026-10-10) explains how far to trust them.
+1 -1
View File
@@ -109,7 +109,7 @@ Weights, evidence and the missing items per area: [target-app-parity.md](docs/ta
| Korean | `ko` | none | 0% |
| Vietnamese | `vi` | none | 0% |
Also shipped: Traditional Chinese (partial, 80%), Italian, Russian and Ukrainian (full), Czech (catalog complete; render-site gaps reduced; native review pending).
Also shipped: Traditional Chinese (partial, 80%), Hungarian, Italian, Russian and Ukrainian (full), Czech (catalog complete; render-site gaps reduced; native review pending).
No catalog has had a native speaker's review. Details: [localization-parity.md](docs/localization-parity.md).
## Upcoming
+1 -1
View File
@@ -739,7 +739,7 @@ mod tests {
let fr = crate::i18n::Lang::from_code("fr").unwrap();
let shown = crate::i18n::message(fr, "2 font file(s) are now in /Fonts; 1 couldn't be added: a.otf: larger than 256 MB");
assert!(shown.contains("se trouvent maintenant dans /Fonts") && shown.contains("a.otf") && shown.contains("plus de 256 Mo"), "{shown}");
for code in ["de", "es", "fr", "it", "ru"] {
for code in ["de", "es", "fr", "hu", "it", "ru"] {
let lang = crate::i18n::Lang::from_code(code).unwrap();
for s in [
"every search thread stopped on an internal error (please report this bug)",
File diff suppressed because it is too large. Load diff
+5 -1
View File
@@ -82,7 +82,7 @@ fn plural_east_slavic(n: u64) -> usize {
}
/// The registry. English first: it is the fallback and the source language.
pub static LANGUAGES: [LangInfo; 12] = [
pub static LANGUAGES: [LangInfo; 13] = [
LangInfo { code: "en", name: "English", source: "", plural: plural_one_other, complete_menus: false, catalog: OnceLock::new() },
// Japanese: the whole interface (every menu string and `tl!` literal) and the status and error
// messages, keeping the product, workspace and perspective preset names in English (`MENU_KEEP_AS_IS`).
@@ -143,6 +143,10 @@ pub static LANGUAGES: [LangInfo; 12] = [
complete_menus: true,
catalog: OnceLock::new(),
},
// Hungarian: the whole interface and the status and error messages, keeping the same names in
// English as Spanish; a count takes the singular ("3 réteg"), so plurals have one form. Every
// `hu-*` locale (including `hu-HU`) resolves here.
LangInfo { code: "hu", name: "Magyar", source: include_str!("hu.tsv"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() },
// Brazilian Portuguese: every menu label (`menu_catalogs_translate_every_menu_label`), every
// `tl!` literal and the plural messages; left English on purpose are the `MENU_KEEP_AS_IS`
// strings (product name, language names, workspace names, perspective grid presets).
+38 -4
View File
@@ -26,6 +26,9 @@ fn tags_map_to_languages() {
for tag in ["uk", "uk-UA", "uk_UA.UTF-8", "UK-ua", "uk_UA.UTF-8@euro"] {
assert_eq!(lang_from_tag(tag), Some(uk()), "{tag}");
}
for tag in ["hu", "hu-HU", "hu_HU.UTF-8", "HU-hu"] {
assert_eq!(lang_from_tag(tag), Some(hu()), "{tag}");
}
// Traditional Chinese: by region, by script, and with a region after the script.
assert_eq!(lang_from_tag("zh_TW.UTF-8"), Some(ZH()));
assert_eq!(lang_from_tag("zh-TW"), Some(ZH()));
@@ -443,7 +446,7 @@ fn cs() -> Lang {
}
/// Languages whose catalogs leave [`MENU_KEEP_AS_IS`] in English.
const KEEPS_MENU_NAMES: [&str; 9] = ["cs", "de", "es", "fr", "it", "ja", "pt-br", "ru", "uk"];
const KEEPS_MENU_NAMES: [&str; 10] = ["cs", "de", "es", "fr", "hu", "it", "ja", "pt-br", "ru", "uk"];
/// Menu labels the menu-complete catalogs (Czech, German, Spanish, Italian, Japanese, Brazilian Portuguese) show as they are: the product name, a format name, the built-in workspace
/// names and the perspective grid presets (names, shown untranslated wherever else they appear).
@@ -597,6 +600,37 @@ fn ru() -> Lang {
Lang::from_code("ru").expect("ru registered")
}
fn hu() -> Lang {
Lang::from_code("hu").expect("hu registered")
}
/// Hungarian uses its vector-illustration vocabulary, drops the word "tool" from tool names, keeps
/// a count in the singular (one plural form), gives dialog buttons a verb while the File menu's
/// Close keeps its noun through an `@id` row, and translates the reason inside a message too.
#[test]
fn hungarian_reads_as_hungarian() {
for (en, want) in [
("Artboard Tool", "Rajztábla"),
("Swatches", "Színminták"),
("Pathfinder", "Útvonalkezelő"),
("Stroke", "Körvonal"),
("Fill", "Kitöltés"),
("Direct Selection Tool", "Közvetlen kijelölés"),
("Save As…", "Mentés másként…"),
("Undo", "Visszavonás"),
("Close", "Bezár"),
] {
assert_eq!(tr(hu(), en), want);
}
assert_eq!(tr_id(hu(), "file.close", "Close"), "Bezárás");
assert_eq!(tr_id(hu(), "pathfinder.add", "Add"), "Egyesítés");
for n in [0, 1, 3, 21] {
assert_eq!(trn(hu(), n, "{n} Layer", "{n} Layers"), format!("{n} réteg"));
}
let nested = "command `object.group` is not available right now: nothing selected";
assert_eq!(message(hu(), nested), "a parancs jelenleg nem érhető el: `object.group`: nincs kijelölés");
}
fn uk() -> Lang {
Lang::from_code("uk").expect("uk registered")
}
@@ -856,7 +890,7 @@ fn russian_plurals_have_three_forms() {
assert_eq!(forms, [2, 0, 1, 1, 2, 2, 0, 1, 2, 2, 0]);
}
/// Czech, German, Spanish, French and Italian letters (and the punctuation their text uses) come from each family's own first font, not
/// Czech, German, Spanish, French, Italian and Hungarian letters (and the punctuation their text uses) come from each family's own first font, not
/// from a fallback further down the stack. (`has_glyph` can't tell: it counts characters of the
/// face that draws missing glyphs, the first one, as missing.)
#[test]
@@ -871,7 +905,7 @@ fn latin_script_glyphs_are_available_without_system_fonts() {
let first = first.unwrap();
let mut font = fonts.fonts.font(&family);
let chars = font.characters();
for ch in "áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ„“‚‘…–ñÑüÜ¿¡”àèìòùÀÈÌÒÙ«»’âêîôûçëïÿœæÂÊÎÔÛÇËÏŒÆäöÄÖß\u{a0}".chars()
for ch in "áčďéěíňóřšťúůýžÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ„“‚‘…–ñÑüÜ¿¡”àèìòùÀÈÌÒÙ«»’âêîôûçëïÿœæÂÊÎÔÛÇËÏŒÆäöÄÖßőűŐŰ\u{a0}".chars()
{
assert!(chars.get(&ch).is_some_and(|fonts| fonts.contains(&first)), "{first} ({family:?}) has no {ch}");
}
@@ -1023,7 +1057,7 @@ fn select_key_param_error_is_translated() {
}
/// Languages whose catalogs cover every status and error message.
const COMPLETE_MESSAGES: &[&str] = &["cs", "de", "es", "fr", "it", "ja", "ru", "uk"];
const COMPLETE_MESSAGES: &[&str] = &["cs", "de", "es", "fr", "hu", "it", "ja", "ru", "uk"];
/// Crates whose error and status messages reach the status bar.
const MESSAGE_CRATES: &[&str] = &["ui-egui", "engine", "doc", "format", "svg", "pdf", "eps", "text", "plugins", "metafile", "cad", "trace"];
+4 -3
View File
@@ -177,7 +177,8 @@ international Spanish; every `es-*` locale such as `es-ES`, `es-MX`, `es-AR` or
complete; `fr-FR`, `fr-BE`, `fr-CA`, `fr-CH` and every other `fr-*` locale resolve to it), Italian (`it`,
complete; `it-IT`, `it-CH` and every other `it-*` locale resolve to it), Russian (`ru`, complete; every `ru-*`
locale such as `ru-RU`, `ru-BY` or `ru-KZ` resolves to it), Ukrainian (`uk`, complete; `uk-UA`,
`uk_UA.UTF-8` and every other `uk-*` locale resolve to it; integer plurals use one/few/many), Czech (`cs`, every menu label) and Brazilian Portuguese
`uk_UA.UTF-8` and every other `uk-*` locale resolve to it; integer plurals use one/few/many), Hungarian (`hu`, complete;
`hu-HU` and every other `hu-*` locale resolve to it; a count takes the singular, so plurals have one form), Czech (`cs`, every menu label) and Brazilian Portuguese
(`pt-br`, every menu label and every `tl!` literal). Untranslated text falls back to English until its rows are added.
- `tl!("…")` translates a literal into the language the UI is drawn in; `i18n::t(s)` is the same for a
@@ -205,10 +206,10 @@ locale such as `ru-RU`, `ru-BY` or `ru-KZ` resolves to it), Ukrainian (`uk`, com
control channel, MCP and tests read them) and are translated only where the status bar draws them
(`i18n::msg`). `@msg` catalog rows hold a whole message or a template such as
`Couldn't open {name}: {e}`; `{_1}`, `{_2}` … stand for the format string's `{}`, and the values in the
placeholders are translated in turn (the reason after `: {e}` is often a message too). Spanish, French, Italian, Japanese, Russian and Ukrainian cover every
placeholders are translated in turn (the reason after `: {e}` is often a message too). Spanish, French, Hungarian, Italian, Japanese, Russian and Ukrainian cover every
message literal the test scan finds (`complete_languages_translate_every_message`, languages listed in
`COMPLETE_MESSAGES`): a new `Err("…")`, `Other(…)`, `#[error(…)]` or `status(…)` message needs an `es.tsv`,
a `fr.tsv`, an `it.tsv`, a `ja.tsv`, a `ru.tsv` and a `uk.tsv` row (`VECTORCRAFT_I18N_DUMP_MESSAGES=messages.txt cargo test -p vectorcraft-ui-egui
a `fr.tsv`, a `hu.tsv`, an `it.tsv`, a `ja.tsv`, a `ru.tsv` and a `uk.tsv` row (`VECTORCRAFT_I18N_DUMP_MESSAGES=messages.txt cargo test -p vectorcraft-ui-egui
complete_languages_translate_every_message` lists them all). Other languages show messages in English
until they add `@msg` rows.
- Not translated on purpose: names that are user data (layers, swatches, fonts, documents), the tab
+4 -2
View File
@@ -1,6 +1,6 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-11 · **Change:** minor (Czech render-site localization follow-up; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Hungarian catalog added; Czech render-site localization follow-up; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
Per-language status of VectorCraft's interface. How the catalogs work and how to add a language:
[`development.md` › Localisation](development.md#localisation). Part of
@@ -49,9 +49,10 @@ plus review. Native-speaker review is human work and isn't in the hours.
| Italian | `it` | 4,011 / 4,020 | full |
| Russian | `ru` | 4,009 / 4,020 | full |
| Ukrainian | `uk` | 4,009 / 4,020 (one/few/many plurals) | full |
| Hungarian | `hu` | 4,524 rows: menus, `tl!` literals and messages complete (tests); one plural form | full |
| Czech | `cs` | 4,411 catalog entries, including 636 `@msg` rows (measured locally 2026-10-11); menu, literal and message coverage enforced by tests | catalog complete; render-site gaps reduced; native review pending |
Eleven non-English catalogs in all. Illustrator, as its public documentation lists, ships around two dozen
Twelve non-English catalogs in all. Illustrator, as its public documentation lists, ships around two dozen
interface languages (including Korean, Polish, Dutch, Turkish, the Nordic languages, Hungarian, and Middle East
editions with Arabic and Hebrew); it doesn't ship Hindi, Indonesian or Vietnamese.
@@ -78,5 +79,6 @@ font for the web build.
| Date | Change | Summary |
|---|---|---|
| 2026-10-12 | minor | Hungarian (`hu`) catalog added, complete under the tests |
| 2026-10-11 | minor | Czech render-site follow-up: file-dialog filters, diagnostics, preset numbering, contributor tooltips/plurals, gradient grammar and contextual printer mark styles; estimates unchanged |
| 2026-10-10 | major | First per-language table, measured from the catalog row counts against the complete French catalog |