# 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`](localization-zh-hans.md) and [`localization-ja.md`](localization-ja.md). ## Adding a language 1. **Translate the two catalogs.** Copy `locales/ja.json` and `locales/ja-formats.json` to `locales/.json` and `locales/-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::tests` fails on a missing, extra or empty entry. 2. **Add the table entry** in `crates/ui-egui/src/i18n.rs`: ```rust 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.rs` picks up `-formats.json` by 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. 3. **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](https://github.com/storytold/craft-fonts), never here; a missing face is added there, not worked around in this repository. 4. Run `cargo xtask ci` (it includes `fmt`, `clippy`, the tests, `parity` and 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: ```rust 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 ```sh 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: ```sh 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.