4.2 KiB
Localization
The interface ships in the language it is written in (English) plus every language in the table in
crates/ui-egui/src/i18n.rs. Today that is English, Simplified Chinese (zh-hans) and Japanese
(ja). This file is the reference for adding or maintaining a language; the per-language notes
are in localization-zh-hans.md and localization-ja.md.
Adding a language
-
Translate the two catalogs. Copy
locales/ja.jsonandlocales/ja-formats.jsontolocales/<code>.jsonandlocales/<code>-formats.json, then translate the values. Keys are the English source strings and must stay byte-for-byte identical —cargo test -p lightcraft-ui-egui i18n::testsfails on a missing, extra or empty entry. -
Add the table entry in
crates/ui-egui/src/i18n.rs:language_table! { En, "en"; ZhHans, "zh-hans", "简体中文", "Hans", include_str!("../locales/zh-hans.json"); Ja, "ja", "日本語", "Jpan", include_str!("../locales/ja.json"); }The fields are the BCP-47 code (also the settings-file value), the endonym shown in the Language menu, the ISO 15924 script (
Latn,Jpan,Hans,Hant,Kore…), and the embedded catalog.build.rspicks up<code>-formats.jsonby name, so the macro and the format lookup follow automatically — nothing else in the UI needs to change. The menus, the settings row, the keyboard-shortcut sheet and the control channel all read the table. -
Make sure the glyphs exist. The script drives which craft-fonts faces the UI installs: a language whose script has no face in the build shows boxes (the test says so instead of failing). Fonts live in storytold/craft-fonts, never here; a missing face is added there, not worked around in this repository.
-
Run
cargo xtask ci(it includesfmt,clippy, the tests,parityand the wasm check).
How a message is found
tr("Exposure") looks the English label up in the active language's catalog and returns the source
text unchanged when there is no entry; tr_format! handles text with runtime values. Both are
presentation-only: command ids, file names and user-entered metadata are never translated.
tr_format! is generated by crates/ui-egui/build.rs. It formats the English source at the call
site, which is what gives format! a literal to check against that call site's own arguments, and
it also names those values so a translation can place them:
tr_format!("Added {n} photo{} to “{}”", if n == 1 { "" } else { "s" }, album, n = n)
- A translation refers to a value by name exactly as the English source spells it (
{n}), or by position as_1,_2, … in call-site order. Reordering is fine:"已将 {n} 张照片添加到“{_2}”". - The English plural suffix (
"s"/"") is simply not referenced in languages without plurals. - Every translation is placeholder-checked at compile time: an unknown name, or a spec that does not
match its value, fails the build rather than printing
{}at runtime. - A source string with no translation falls back to the English text, values included.
Testing and looking at it
cargo test -p lightcraft-ui-egui i18n::tests # catalogs, keys, placeholders, commands
CRAFT_FONTS_DIR=../craft-fonts cargo test -p lightcraft-ui-egui i18n::tests # + glyph coverage
The glyph tests are skipped without CRAFT_FONTS_DIR (there are no CJK faces to check then).
To look at a language, render it headless:
LIGHTCRAFT_LANGUAGE=zh-hans lightcraft-cli snapshot --demo --script tour.jsonl -o out.png --size 1600x1000
or, with the app running (--control 7980), run the language command
(engine.execute {"command": "app.language.zh-hans"}) and take a screenshot.
Language codes and settings
The settings file stores the BCP-47 code, never the Rust variant name, so a language can be renamed
in code without invalidating anyone's saved preference. LIGHTCRAFT_LANGUAGE accepts what a system
locale looks like (zh_Hans, zh-CN, en_US, ja_JP.UTF-8) and falls back to the base language
when a region has no dedicated entry.