Files
figcraft/docs/translating.md
T
bflatastic 159de3ce43 M9: Japanese and Korean draft catalogs and CJK UI fonts
- **Japanese (`ja`) and Korean (`ko`) draft catalogs** (`crates/ui-egui/locales/ja.toml`, `ko.toml`): every UI string (1,351 keys), command label (519) and menu wording (29), machine-drafted from our English (Japanese: concise, no desu/masu endings, full-width punctuation where Japanese UI text uses it; Korean: no hamnida endings, instructions in -seyo). Placeholders identical to English, `|context` keys translated per context, plural entries give only `other`. Marked `status = "draft - needs native review"` and registered as drafts (not in `COMPLETE`), like fr/de/pt-BR.
- **Plurals:** `plural_one` returns false for ja and ko (always "other").
- **Language picker and detection:** Preferences > Language lists the two languages by their own names; System picks `ja*` / `ko*` from the OS language list; `pref.language` doc lists `ja|ko` (engine spec and docs/mcp.md).
- **CJK UI fonts** (`crates/ui-egui/src/cjk.rs`): egui's fonts and Inter have no CJK glyphs. After the existing system font scan, the desktop app works out which CJK scripts (kana, Hangul, Han) the UI shows: the UI language's, the language picker's while Preferences is open, typed/IME text, and the open file's names and comments (re-read when the tab's revision changes, not mid-drag, and never again once all three scripts have a face). For each needed script it appends one face to egui's fallback list, the UI language's script first: Windows Yu Gothic UI / Meiryo / Malgun Gothic / Microsoft YaHei, macOS Hiragino Sans / Apple SD Gothic Neo / PingFang SC, Linux/BSD Noto Sans CJK, Source Han Sans, Nanum, WenQuanYi; the craft-fonts build's embedded BIZ UDPGothic is tried first (free). Han without kana outside a Japanese/Korean UI reads as Chinese (Chinese faces first). English users load no CJK face. No font files are added to the repo.
- **Read once, share the bytes:** new `FontDb::find_one` reads only the one face a fallback needs instead of every file of the family (Yu Gothic UI is 4 files of ~14 MB); `theme::font_data` hands egui the font database's shared bytes instead of copying them (also used for the existing Hebrew/Arabic fallbacks). The scan's existing 64 MB per-file cap applies. Any failure degrades to the previous behaviour (tofu at worst), never a panic.
- **Web:** no system fonts there. `crates/text/build.rs` now embeds craft-fonts' Noto Sans CJK into the wasm build when `FIGCRAFT_EMBED_CJK` is set (it covers kana, Hangul and Han; +16 MB) and reruns when that variable changes. Without it, `cjk::can_show` keeps ja/ko out of the picker and out of System detection, so Japanese and Korean browser users keep the English UI instead of boxes. Nothing is fetched from the network.
- **Docs:** localization-parity (rows, CJK UI fonts section, totals, revision), translating (catalog list, CJK conventions, fonts, plural note), target-app-parity, ROADMAP, gaps (gap 5), roadmap (focus 4), mcp.md. Localization 30% -> 35% (graded low: unreviewed drafts), 40-65 h -> 32-55 h; ready for real work ~52% -> ~53% (52.9 -> 53.1 by the dimension table); full parity ~284-554 h -> ~276-544 h; beta ~162-292 h -> ~155-280 h.
2026-10-12 04:41:28 +07:00

82 lines
5.3 KiB
Markdown

# Translating FigCraft
FigCraft's interface strings live in one TOML catalog per language in
[`crates/ui-egui/locales/`](../crates/ui-egui/locales). The build bakes every catalog into the
binary (desktop and web); nothing is read at run time. Preferences ▸ Language picks the language
and the interface switches at once. System takes the first of the OS's preferred languages that has
a catalog (the browser's language on the web): a regional tag reads its language's catalog
(`fr-CA` → `fr`), and a language with only a regional catalog reads that one (`pt-PT` → `pt-BR`).
`pref.language {language}` does the same from the CLI, MCP and the control channel.
The catalogs: English (`en`, the source), and Spanish (`es`), French (`fr`), German (`de`),
Brazilian Portuguese (`pt-BR`), Japanese (`ja`) and Korean (`ko`), machine-drafted and waiting for
native speakers' review.
## The catalogs
- **`en.toml`** lists every key. A key is the English text as the code writes it, so the code stays
readable: `tr("Fill")`, `trf("Delete {name}?", &[("name", &name)])`, `trn("{n} layers", count, &[])`.
Values equal their keys except plurals, which give both forms:
`"{n} layers" = { one = "{n} layer", other = "{n} layers" }`.
- **`es.toml`** and other languages translate the same keys under `[ui]`, plus command labels by
command id under `[commands]` (the Actions palette, the shortcuts panel and the menus) and, under
`[menu]`, the few menu items the main menu words differently from the command (the Select matching
submenu's "Fill" for "Select same fill"). Command ids, the CLI, MCP and control-channel output stay
English.
- `[meta] name` is the language's own name (the picker shows it untranslated); `[meta] status` marks
a draft (the Preferences window says so).
A key a catalog lacks shows its English text, so a partial catalog is safe to ship.
Typography follows each language: French puts a no-break space (`\u00A0` in the TOML) before
`:` `;` `?` `!`, inside « » and before `%`; German uses „ “ and formal address (Sie); Brazilian
Portuguese uses Brazilian words (arquivo, salvar, tela, você), not European ones. Japanese is
concise, without です/ます endings, with full-width ()「」:、。 where Japanese UI text has them;
Korean labels end without 합니다 (instructions in -세요). Neither has plural forms.
CJK text needs a CJK font, which egui and Inter lack: the desktop app adds an installed one when
something on screen needs it (see `crates/ui-egui/src/cjk.rs`). The web build has no system fonts,
so Japanese and Korean appear there only in a build with craft-fonts and `FIGCRAFT_EMBED_CJK=1`
(Noto Sans CJK, +16 MB); otherwise they are left out of the picker. A new CJK or other non-Latin
language also needs its script in `cjk::language`.
## Adding a language
1. Copy `crates/ui-egui/locales/es.toml` to `<code>.toml` (a BCP 47 code: `fr`, `pt-BR`, `ja`) and
set `[meta] name` and `status = "draft — needs native review"`.
2. Translate every value; keep the keys. Keep `{placeholders}` exactly, "…" where English has it,
brand names and keyboard glyphs. English labels of commands: `figcraft-cli commands`.
3. Plurals: give `one` only if the language has a singular form (Japanese, Korean and Chinese give
only `other`, and `plural_one` returns false for them). `trn` picks `one` for a count of 1, and
also for 0 in French and Brazilian Portuguese (keep `{n}` in their `one` forms); a language with other rules needs its rule in
`plural_one` in [`crates/ui-egui/src/i18n.rs`](../crates/ui-egui/src/i18n.rs).
4. To require full coverage, add the code to `COMPLETE` in
[`crates/ui-egui/src/i18n_tests.rs`](../crates/ui-egui/src/i18n_tests.rs). Every later PR that
adds a string then has to translate it into each complete language, so only Spanish is there:
French, German, Brazilian Portuguese, Japanese and Korean are complete today but registered as
drafts, and a
string added later shows English in them until it is translated. To list what a draft lacks,
add its code to `COMPLETE` locally and run the check.
5. Run the check and look at the panels in the app (longer words must fit the right panel):
cargo test -p figcraft-ui-egui i18n
Dropdowns, the right panel's two-column labels and the home cards cut a label that doesn't fit
with "…" and show it in full on hover; elsewhere, look for clipped or wrapped text.
The check fails on keys the code uses that `en.toml` lacks (it prints the lines to paste), keys no
code uses, unknown command ids, placeholders that don't match English, and, for complete languages,
anything untranslated. Machine-drafted catalogs stay marked as drafts until a native speaker has
reviewed them.
## Adding strings in code
Wrap every string a user sees or a screen reader hears: `tr` for plain text, `trf` for `{named}`
placeholders, `trn` for counts, `tr_noop` for literals in `const` tables that are looked up later
with `tr(label)`. The key must be a string literal as the first argument (the check reads the
source). Don't wrap ids, command ids, JSON, log messages, file extensions or document content. Then
add the key to `en.toml` and to each complete language (Spanish); the drafts may follow later.
Labels from the engine and document crates (tool names, blend modes, easing curves…) are looked up
at display with `tr(x.label())`; they are listed in `dynamic_labels` in `i18n_tests.rs`.