i18n: add complete Vietnamese localization

This commit is contained in:
Trần Nhật Duy 2026-10-13 00:04:10 +07:00 committed by bflatastic
parent 22e06db73d
commit ec8dc0c2da
9 changed files with 4320 additions and 27 deletions

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, Korean, Traditional and Simplified Chinese, Spanish, French, Hungarian, Italian, Russian and Ukrainian (and Czech and Brazilian Portuguese in the menus);
speaks English, German, Japanese, Korean, Vietnamese, 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.

View File

@ -35,7 +35,7 @@ blocking it ([Alpha gate](docs/roadmap.md#alpha-gate)).
| Audience | Ready % | Opus 5.5 agent hours to ~95% (one agent) | With 4–6 agents | Work that dominates |
|---|---:|---:|---:|---|
| **Full Illustrator** (ready for real work; decides the stage) | **~62%** (57–67%) | **~340–560 h** | ~95–160 h | the mainstream work below, plus 3D (50–85 h), raster effects (23–39 h), advanced type (20–28 h), generative AI (30–60 h), localization (60–100 h) and ecosystem |
| **Full Illustrator** (ready for real work; decides the stage) | **~62%** (57–67%) | **~340–560 h** | ~95–160 h | the mainstream work below, plus 3D (50–85 h), raster effects (23–39 h), advanced type (20–28 h), generative AI (30–60 h), localization (55–90 h) and ecosystem |
| **Mainstream illustrator** | **~65%** (60–70%) | **~230–380 h** | ~65–110 h | the interaction-fidelity pass (60–90 h), hardening and QA (30–50 h), stability (20–35 h), performance budgets (15–25 h), `.ai` exchange (10–20 h), the remaining depth of the everyday areas |
| **Essentials user** | **~75%** (70–80%) | **~60–105 h** | ~25–45 h | launch and stability on Windows (20–35 h), in-app help and onboarding (8–15 h), basic QA (10–15 h), Shape Builder regions (4–8 h), UI polish |
@ -52,7 +52,7 @@ everything is measured from our code against Illustrator's public documentation.
| UI/UX fidelity | 55% | 70–110 | [ui-parity.md](docs/ui-parity.md) |
| File formats | 75% | 15–30 | [file-format-parity.md](docs/file-format-parity.md) |
| Hardware (GPU, pen, touch, displays) | 35% | 25–45 | [hardware-parity.md](docs/hardware-parity.md) |
| Localization (12 key languages) | 63% (measured) | 60–100 | [localization-parity.md](docs/localization-parity.md) |
| Localization (12 key languages) | 71% (measured) | 55–90 | [localization-parity.md](docs/localization-parity.md) |
| Performance | ~60% | 15–25 | [gaps.md › G15](docs/gaps.md#g15-performance-budgets-and-a-gpu-renderer) |
| Stability | ~60% | 30–50 | [gaps.md › G10](docs/gaps.md#g10-launch-and-stability-on-real-machines) |
| Platforms | 80% | 15–25 | [gaps.md › G8](docs/gaps.md#g8-hardening-at-scale) |
@ -107,7 +107,7 @@ Weights, evidence and the missing items per area: [target-app-parity.md](docs/ta
| Japanese | `ja` | full | 100% |
| German | `de` | full | 98% |
| Korean | `ko` | full | 100% |
| Vietnamese | `vi` | none | 0% |
| Vietnamese | `vi` | full | 100% |
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).

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; 14] = [
pub static LANGUAGES: [LangInfo; 15] = [
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`).
@ -91,6 +91,11 @@ pub static LANGUAGES: [LangInfo; 14] = [
// English as Japanese; a count takes no plural, so plurals have one form. Hangul is drawn with an
// installed system font (craft-fonts has no Korean UI face yet); every `ko-*` locale resolves here.
LangInfo { code: "ko", name: "한국어", source: include_str!("ko.tsv"), plural: plural_none, complete_menus: true, catalog: OnceLock::new() },
// Vietnamese: complete menus and messages, with one plural form.
// Every `vi-*` locale resolves here.
LangInfo {
code: "vi", name: "Tiếng Việt", source: include_str!("vi.tsv"), plural: plural_none, complete_menus: true, catalog: OnceLock::new()
},
// Czech: the whole interface (every menu string and `tl!` literal) and the status and error
// messages, with one/few/other plurals, keeping the same names in English as Spanish.
LangInfo { code: "cs", name: "Čeština", source: include_str!("cs.tsv"), plural: plural_czech, complete_menus: true, catalog: OnceLock::new() },

View File

@ -32,6 +32,9 @@ fn tags_map_to_languages() {
for tag in ["ko", "ko-KR", "ko_KR.UTF-8", "KO-kr"] {
assert_eq!(lang_from_tag(tag), Some(ko()), "{tag}");
}
for tag in ["vi", "vi-VN", "vi_VN.UTF-8", "VI-vn"] {
assert_eq!(lang_from_tag(tag), Some(vi()), "{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()));
@ -449,7 +452,7 @@ fn cs() -> Lang {
}
/// Languages whose catalogs leave [`MENU_KEEP_AS_IS`] in English.
const KEEPS_MENU_NAMES: [&str; 11] = ["cs", "de", "es", "fr", "hu", "it", "ja", "ko", "pt-br", "ru", "uk"];
const KEEPS_MENU_NAMES: [&str; 12] = ["cs", "de", "es", "fr", "hu", "it", "ja", "ko", "pt-br", "ru", "uk", "vi"];
/// 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).
@ -662,6 +665,63 @@ fn korean_reads_as_korean() {
assert_eq!(message(ko(), nested), "명령 `object.group`은(는) 지금 사용할 수 없습니다: 선택된 항목이 없습니다");
}
fn vi() -> Lang {
Lang::from_code("vi").expect("vi registered")
}
/// Vietnamese has one plural form and covers the complete interface and messages.
#[test]
fn vietnamese_reads_as_vietnamese() {
assert_eq!(vi().name(), "Tiếng Việt");
assert!(vi().complete_menus());
assert_eq!(Lang::from_pref("vi"), vi());
assert_eq!(Lang::from_pref("VI"), vi());
for (en, want) in [
("Artboard Tool", "Công cụ Bảng vẽ"),
("Swatches", "Mẫu màu"),
("Stroke", "Nét"),
("Fill", "Tô"),
("Direct Selection Tool", "Công cụ Chọn trực tiếp"),
("Save As…", "Lưu thành…"),
("Undo", "Hoàn tác"),
("Close", "Đóng"),
] {
assert_eq!(tr(vi(), en), want);
}
for n in [0, 1, 3, 21] {
assert_eq!(trn(vi(), n, "{n} Layer", "{n} Layers"), format!("{n} lớp"));
}
let nested = "command `object.group` is not available right now: nothing selected";
assert_eq!(message(vi(), nested), "lệnh `object.group` hiện không khả dụng: chưa chọn gì");
}
/// Ambiguous English terms keep their intended Vietnamese meaning across UI contexts.
#[test]
fn vietnamese_domain_terms_are_consistent() {
for (en, want) in [
("All Scripts", "Tất cả hệ chữ"),
("Pointillize", "Hiệu ứng chấm"),
("Object Highlighting", "Đánh dấu đối tượng"),
("Color Theme", "Bộ màu"),
("Color Themes", "Bộ màu"),
("Asset Export", "Xuất asset"),
("Envelope Distort", "Biến dạng Envelope"),
("Move Spine Point", "Di chuyển điểm trục"),
("Dashed Line", "Đường nét đứt"),
("Eyedropper Tool", "Công cụ Ống hút màu"),
("Character Alignment", "Căn chỉnh ký tự"),
] {
assert_eq!(tr(vi(), en), want, "{en}");
}
assert_eq!(tr(vi(), "Appearance"), "Diện mạo");
assert_eq!(tr_ctx(vi(), "theme", "Appearance"), "Giao diện");
assert_eq!(message(vi(), "no saved themes"), "chưa có bộ màu nào được lưu");
assert_eq!(message(vi(), r#"choice must be "merge" or "add", not `foo`"#), r#"lựa chọn phải là "merge" hoặc "add", không phải `foo`"#);
assert_eq!(message(vi(), "select a blend"), "hãy chọn một đối tượng hòa trộn");
assert_eq!(message(vi(), "select a repeat"), "hãy chọn một đối tượng lặp");
assert_eq!(message(vi(), "Replace Spine: select a blend and a path"), "Thay thế trục: chọn một đối tượng hòa trộn và một đường dẫn");
}
fn uk() -> Lang {
Lang::from_code("uk").expect("uk registered")
}
@ -1103,7 +1163,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", "hu", "it", "ja", "ko", "ru", "uk"];
const COMPLETE_MESSAGES: &[&str] = &["cs", "de", "es", "fr", "hu", "it", "ja", "ko", "ru", "uk", "vi"];
/// 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"];

File diff suppressed because it is too large Load Diff

View File

@ -171,7 +171,7 @@ always use the English ids and labels, so agents and scripts never see translate
Languages shipped: English (`en`, the source), Traditional Chinese (`zh-hant`, complete, in the vocabulary used
in Taiwan; `zh-TW`, `zh-HK`, `zh-MO` and `zh-Hant-*` locales all resolve to it), Simplified Chinese (`zh-hans`,
complete, in the vocabulary used in mainland China; `zh-CN`, `zh-SG`, `zh-Hans-*` and a bare `zh` resolve to it,
so the two scripts never mix), Japanese (`ja`, complete), Korean (`ko`, complete; every `ko-*` locale resolves to it; a count takes no plural, and Hangul is drawn with an installed system font), German (`de`, complete; `de-DE`, `de-AT`, `de-CH` and every
so the two scripts never mix), Japanese (`ja`, complete), Korean (`ko`, complete; every `ko-*` locale resolves to it; a count takes no plural, and Hangul is drawn with an installed system font), Vietnamese (`vi`, complete; every `vi-*` locale resolves to it; a count takes no plural, so plurals have one form), German (`de`, complete; `de-DE`, `de-AT`, `de-CH` and every
other `de-*` locale resolve to it), Spanish (`es`, complete, in neutral
international Spanish; every `es-*` locale such as `es-ES`, `es-MX`, `es-AR` or `es-419` resolves to it), French (`fr`,
complete; `fr-FR`, `fr-BE`, `fr-CA`, `fr-CH` and every other `fr-*` locale resolve to it), Italian (`it`,
@ -206,10 +206,9 @@ 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, Hungarian, Italian, Japanese, Russian and Ukrainian cover every
placeholders are translated in turn (the reason after `: {e}` is often a message too). Czech, German, Spanish, French, Hungarian, Italian, Japanese, Korean, Russian, Ukrainian and Vietnamese 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`, 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_MESSAGES`): a new `Err("…")`, `Other(…)`, `#[error(…)]` or `status(…)` message needs a row in each of those catalogs (`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

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (Czech: 128 strings added since the catalog was completed are translated, #1461; scores and estimates unchanged; Korean catalog added, complete under the tests; 12-language score 54% → 63%; Hungarian catalog added; 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-13 · **Change:** minor (Vietnamese catalog added, complete under the tests; 12-language score 63% → 71%; Czech: 128 strings added since the catalog was completed are translated, #1461; scores and estimates unchanged; Korean catalog added, complete under the tests; 12-language score 54% → 63%; 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
@ -34,10 +35,10 @@ Chinese by `imachus`). None is recorded as reviewed by a native speaker; the cat
| Japanese | `ja` | 4,004 / 4,020 (99.6%) | all | CJK UI font as above; Japanese IME; vertical type partial; Japanese crop marks and composition settings | no | full | — (review) |
| German | `de` | 3,952 / 4,020 (98%; tests hold menus, literals and messages complete) | all (602 of 604 message rows; landed 2026-10-10) | Latin | contributor-written, not reviewed | full | — (review) |
| Korean | `ko` | 4,151 rows: menus, `tl!` literals and messages complete (tests); one plural form | all | Hangul from an installed system font on the desktop (Apple SD Gothic Neo, Malgun Gothic, Noto Sans CJK KR…); the web build has no Korean UI font yet (craft-fonts); IME path exists | no (AI-drafted) | full | — (review; web font) |
| Vietnamese | `vi` | 0 | none | Latin with stacked diacritics (precomposed forms render) | no | none | 5–8 |
| Vietnamese | `vi` | 4,213 rows: menus, `tl!` literals and messages complete (tests); one plural form; 3,548 plain + 13 `@plural` + 6 contextual + 646 `@msg` | all | Latin with Vietnamese diacritics (precomposed forms; glyph tests pass) | contributor is a Vietnamese speaker; AI-assisted draft, further native review welcome | full | — (review) |
**12-language score: ~63%** (equal weights: 100 + 85 + 100 + 0 + 0 + 100 + 68 + 0 + 100 + 98 + 100 + 0, over 12).
**To `full` for all twelve: ~60–100 h**, most of it Arabic (right-to-left interface) and Hindi (interface
**12-language score: ~71%** (equal weights: 100 + 85 + 100 + 0 + 0 + 100 + 68 + 0 + 100 + 98 + 100 + 100, over 12).
**To `full` for all twelve: ~55–90 h**, most of it Arabic (right-to-left interface) and Hindi (interface
shaping). Each new Latin-script catalog is ~4,000 rows; the existing ones were written in a few agent hours each
plus review. Native-speaker review is human work and isn't in the hours.
@ -52,7 +53,7 @@ plus review. Native-speaker review is human work and isn't in the hours.
| 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 |
Thirteen non-English catalogs in all. Illustrator, as its public documentation lists, ships around two dozen
Fourteen non-English catalogs in all. Illustrator, as its public documentation lists, ships around two dozen
interface languages (including Polish, Dutch, Turkish, the Nordic languages, Hungarian, and Middle East
editions with Arabic and Hebrew); it doesn't ship Hindi, Indonesian or Vietnamese.
@ -79,7 +80,9 @@ font for the web build.
| Date | Change | Summary |
|---|---|---|
| 2026-10-12 | minor | Vietnamese (`vi`) catalog added, complete under the tests (menus, literals, messages; one plural form); 12-language score 63% → 71%, hours 60–100 → 55–90 |
| 2026-10-13 | minor | Czech: 128 more strings translated (tool names, PDF and Image Trace presets, pie graph options and others added since the catalog was completed; #1461); the entry count in the table is the 2026-10-11 measurement; native review still pending; scores and estimates unchanged |
)
| 2026-10-12 | minor | Korean (`ko`) catalog added, complete under the tests (menus, literals, messages); 12-language score 54% → 63%, hours 70–110 → 60–100 |
| 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 |

File diff suppressed because one or more lines are too long