mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 19:17:02 -06:00
Follow-up to #1100, from #1623: - The 40 messages of pl-formats.json still in English are translated (mask effect presets, slideshow templates and export, Lightroom catalog import, keywording, people, panorama merge, the crash message), and two where only the arrow had been changed. - Polish has three plural forms (1 zdjęcie, 2 zdjęcia, 5 zdjęć). 25 messages put the number before a genitive plural, which reads wrong for 2–4, 22–24… ("Usuń 2 zdjęć"); they now put it after the noun ("Usuń zdjęcia (2)", "Zdjęcia: 22"), right for every number. `{n} {noun}` reads `{noun}: {n}` and its noun pairs share one form. `{} failed` names a task, not a count. - Dates read day.month.year (Niedziela, 20.09.2026; 09.2026), not "20 9 2026". - Test polish_switches_formats_dates_and_reads_every_count: menu and control channel, persistence, pl-* tags, dates and time, counts 0–111, user names kept verbatim, Polish glyphs in the UI faces. - docs/localization-pl.md (Polish notes), and the Polish row and links in localization.md, localization-parity.md and parity-checklist.md. The language buttons already wrap on main, so that part of #1623 is not needed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33
113 lines
7.0 KiB
Markdown
113 lines
7.0 KiB
Markdown
# 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`), Traditional
|
|
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`) and Polish (`pl`). 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), [`localization-zh-hant.md`](localization-zh-hant.md),
|
|
[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), [`localization-es.md`](localization-es.md),
|
|
[`localization-de.md`](localization-de.md), [`localization-ru.md`](localization-ru.md),
|
|
[`localization-cs.md`](localization-cs.md) and [`localization-pl.md`](localization-pl.md).
|
|
|
|
## Adding a language
|
|
|
|
1. **Translate the two catalogs.** Copy `locales/ja.json` and `locales/ja-formats.json` to
|
|
`locales/<code>.json` and `locales/<code>-formats.json`, then translate the values. Keys are the
|
|
English source strings and must stay byte-for-byte identical. The plain catalog may grow at its
|
|
own pace: a message it lacks shows in English, and `cargo test -p lightcraft-ui-egui i18n::tests
|
|
-- --nocapture` lists the gaps (it fails on an empty entry or mismatched placeholders). The
|
|
formats catalog must carry every message (the build fails otherwise; a message copied in English,
|
|
value equal to its key, is an untranslated placeholder until someone translates it), including the date
|
|
patterns (`{year}`, `{month} {year}`, `{weekday}, {day} {month} {year}`…) that date headings and
|
|
capture times use in every language but English; weekday names go in the plain catalog.
|
|
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");
|
|
ZhHant, "zh-hant", "繁體中文(台灣)", "Hant", include_str!("../locales/zh-hant.json");
|
|
Ja, "ja", "日本語", "Jpan", include_str!("../locales/ja.json");
|
|
PtBr, "pt-br", "Português (Brasil)", "Latn", include_str!("../locales/pt-br.json");
|
|
De, "de", "Deutsch", "Latn", include_str!("../locales/de.json");
|
|
Ru, "ru", "Русский", "Cyrl", include_str!("../locales/ru.json");
|
|
Uk, "uk", "Українська", "Cyrl", include_str!("../locales/uk.json");
|
|
Cs, "cs", "Čeština", "Latn", include_str!("../locales/cs.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 `<code>-formats.json` by name, so the macro and the format lookup
|
|
follow automatically. Add the corresponding `app.language.<name>` entry to
|
|
`LANGUAGE_COMMANDS` and `language_from_command` in `src/menus.rs`, and to the
|
|
`language_commands_cover_every_language` test. Settings read the locale table; menus and
|
|
the control channel share the language-command mapping.
|
|
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.
|
|
|
|
A few messages take a **context**, for a word whose shared translation reads wrongly in one place.
|
|
The catalog may then carry `"<context>|<English>"`, which wins there; without it the plain message
|
|
is used, so only the languages that need it add one. Today the one context is `smart album value`:
|
|
the Yes / No of a yes/no rule (Has Edits, Cropped…). Japanese uses it because はい / いいえ answer a
|
|
question rather than describe a photo (`"smart album value|Yes": "あり"`, `"smart album value|No":
|
|
"なし"`); every other language uses its plain Yes / No. A contextual key that names no real value
|
|
fails the i18n tests.
|
|
|
|
`tr_format!` is generated by `crates/ui-egui/build.rs`: one arm per English message, which formats
|
|
the active language's translation with `format!` (English is the fallback arm). Every catalog's
|
|
format string is therefore checked by Rust against the call site's own arguments:
|
|
|
|
```rust
|
|
tr_format!("Added {n} photo{} to “{}”", if n == 1 { "" } else { "s" }, album, n = n)
|
|
```
|
|
|
|
- A translation is an ordinary Rust format string over the same arguments. Refer to named values by
|
|
name (`{n}`) and to positional ones by index when the word order changes (`{0}`, `{1}`):
|
|
`"已将 {n} 张照片添加到“{1}”{0:.0}"`. Keep the source's spec (`{:.1}`, `{d:+}`, `{:.0}%`).
|
|
- `format!` rejects an unused argument, so a value a language does not need — the English plural
|
|
suffix (`"s"` / `""`) — is consumed as `{:.0}` (a string at precision 0 prints nothing).
|
|
- Every formats catalog carries the same messages (`build.rs` fails the build otherwise), and a
|
|
`tr_format!` call whose message is in no catalog does not compile: add it to every
|
|
`*-formats.json` file.
|
|
|
|
## 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.simplifiedChinese"}`) 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.
|
|
|
|
## Release notes
|
|
|
|
Help ▸ What's New shows `docs/whats-new.md`, and each of its lines (a heading, or one wrapped line of a
|
|
bullet, without the `- ` or `### `) is looked up like any message, so a catalog can translate the notes
|
|
line by line (German does). A line that is re-wrapped or edited no longer matches its translation and
|
|
shows in English until the catalog is updated; untranslated lines are reported by
|
|
`display_label_gaps_are_reported`, never failed.
|