2026-10-07 21:06:33 +08:00
|
|
|
|
# UI localisation and live language switching
|
|
|
|
|
|
|
|
|
|
|
|
PhotoCraft uses the same Rust catalog implementation in the native egui shell and the
|
|
|
|
|
|
WebAssembly build. English source strings are lookup keys. UTF-8 TSV catalogs supply plain,
|
|
|
|
|
|
contextual, command-ID and plural translations; missing entries fall back to English.
|
|
|
|
|
|
|
|
|
|
|
|
## Supported languages
|
|
|
|
|
|
|
|
|
|
|
|
| Language | Preference code | Integer plural forms |
|
|
|
|
|
|
|---|---|---|
|
|
|
|
|
|
| English | `en` | one / other |
|
|
|
|
|
|
| 简体中文 | `zh-hans` | one form |
|
|
|
|
|
|
| 日本語 | `ja` | one form |
|
|
|
|
|
|
| 한국어 | `ko` | one form |
|
|
|
|
|
|
| Русский | `ru` | one / few / many |
|
2026-10-09 18:18:31 +02:00
|
|
|
|
| Українська | `uk` | one / few / many (teens use many) |
|
2026-10-07 21:06:33 +08:00
|
|
|
|
| Français | `fr` | singular for 0 and 1 / plural for 2+ |
|
|
|
|
|
|
| 繁體中文 | `zh-hant` | one form |
|
|
|
|
|
|
| Español | `es` | one / other |
|
|
|
|
|
|
| Čeština | `cs` | one / few / other |
|
2026-10-07 21:48:02 +02:00
|
|
|
|
| Deutsch | `de` | one / other |
|
2026-10-07 21:06:33 +08:00
|
|
|
|
| Bahasa Indonesia | `id` | one form |
|
2026-10-07 17:11:38 -03:00
|
|
|
|
| Português (Brasil) | `pt-br` | singular for 0 and 1 / plural for 2+ |
|
2026-10-08 01:13:01 +02:00
|
|
|
|
| Italiano | `it` | one / other |
|
2026-10-08 22:38:28 +02:00
|
|
|
|
| Polski | `pl` | one / few (2–4, not 12–14) / many |
|
2026-10-09 07:31:00 +03:00
|
|
|
|
| Ελληνικά | `el` | one / other |
|
2026-10-09 18:41:23 +02:00
|
|
|
|
| Nederlands | `nl` | one / other |
|
2026-10-12 17:07:17 +02:00
|
|
|
|
| Magyar | `hu` | one form |
|
2026-10-12 14:05:18 +07:00
|
|
|
|
| Tiếng Việt | `vi` | one form |
|
2026-10-13 04:50:20 +02:00
|
|
|
|
| Srpski | `sr` | one / few / many (teens use many) |
|
2026-10-17 15:49:19 +05:30
|
|
|
|
| हिन्दी | `hi` | one / other |
|
2026-10-07 21:06:33 +08:00
|
|
|
|
|
2026-10-12 14:05:18 +07:00
|
|
|
|
Languages registered with `complete_menus` cover the current menu labels, `tl!` literals, blend
|
2026-10-23 19:07:23 +00:00
|
|
|
|
mode names, and generated preference labels. A string a catalog lacks shows in English:
|
|
|
|
|
|
`cargo xtask i18n-coverage` lists the missing strings per language, and `cargo xtask ci` prints
|
|
|
|
|
|
that list as its last step without failing. Vietnamese (`vi`) is
|
2026-10-12 14:05:18 +07:00
|
|
|
|
selectable, resolves regional tags such as `vi-VN`, and covers all current stable localization
|
|
|
|
|
|
keys. `complete_menus` remains false while generated/dynamic long-tail labels are still under
|
|
|
|
|
|
review. This does not include every engine error or status message: those remain English, as do
|
|
|
|
|
|
automation command IDs and user-provided document, layer and preset names.
|
2026-10-07 21:06:33 +08:00
|
|
|
|
|
|
|
|
|
|
## Switch without restarting
|
|
|
|
|
|
|
|
|
|
|
|
Open **Edit > Preferences > Interface > Language** and choose a language. The Preferences
|
|
|
|
|
|
dialog previews its title, controls and buttons in the chosen language. **Cancel** discards
|
|
|
|
|
|
the preview, **Apply** commits it without closing the dialog, and **OK** commits and closes.
|
|
|
|
|
|
The rest of the interface follows the committed preference on its next draw. Existing
|
|
|
|
|
|
documents, undo history and tools remain available throughout a language change.
|
|
|
|
|
|
|
|
|
|
|
|
The existing preference store saves the selection for future launches. `auto` follows the
|
|
|
|
|
|
native system locale; an unsupported code follows the same fallback. Regional tags such as
|
2026-10-12 14:05:18 +07:00
|
|
|
|
`fr-CA`, `ko-KR`, `vi-VN` and `zh-CN` resolve to the corresponding registered catalog. Traditional and
|
2026-10-07 17:11:38 -03:00
|
|
|
|
Simplified Chinese remain distinct. Every Portuguese locale (`pt`, `pt-BR`, `pt-PT`) uses the
|
2026-10-13 04:50:20 +02:00
|
|
|
|
Brazilian Portuguese catalog. The Serbian catalog is written in Latin script (Ekavian); every
|
|
|
|
|
|
Serbian locale (`sr-RS`, `sr-Latn-RS`, `sr_RS@latin`, and Cyrillic `sr-Cyrl-RS`) resolves to it.
|
2026-10-07 21:06:33 +08:00
|
|
|
|
|
2026-10-08 04:52:55 +08:00
|
|
|
|
### First launch and system language
|
|
|
|
|
|
|
|
|
|
|
|
With no saved preference, `interface.language` defaults to `auto`, so the first rendered
|
|
|
|
|
|
window uses the first supported system UI language. An older preferences file without this
|
|
|
|
|
|
field also uses Auto. If detection fails or none of the preferred languages is registered,
|
|
|
|
|
|
the UI uses English (`en`). A saved manual language choice takes precedence on later launches;
|
|
|
|
|
|
startup never overwrites it or writes the detected language into the preference file.
|
|
|
|
|
|
|
|
|
|
|
|
The native-only, pinned `sys-locale` dependency provides safe Rust access to the platform APIs:
|
|
|
|
|
|
|
|
|
|
|
|
- **Windows:** `GetUserPreferredUILanguages`, in the user's preferred order. This uses UI
|
|
|
|
|
|
languages rather than the Region setting for dates and numbers; they can differ.
|
|
|
|
|
|
- **macOS:** `CFLocaleCopyPreferredLanguages`, in preferred order, without starting `defaults`
|
|
|
|
|
|
or depending on a particular `.plist` representation.
|
|
|
|
|
|
- **Linux/BSD:** the platform's standard locale environment, including `LANGUAGE` lists.
|
|
|
|
|
|
|
|
|
|
|
|
`PHOTOCRAFT_LOCALE` is an optional single-tag override for reproducible launches; an unsupported
|
|
|
|
|
|
override falls back to English. Empty overrides use the system. Unix locale variables do not
|
|
|
|
|
|
override Windows/macOS UI-language preferences. Tags are read once per process (at most 64,
|
|
|
|
|
|
128 bytes each) and matched against the supported language registry. Changes to the
|
|
|
|
|
|
OS language list itself take effect at the next launch. The web build keeps its existing
|
|
|
|
|
|
English Auto fallback until browser-locale detection is implemented.
|
|
|
|
|
|
|
|
|
|
|
|
API implementation and licensing: [sys-locale](https://github.com/1Password/sys-locale).
|
|
|
|
|
|
|
2026-10-07 21:06:33 +08:00
|
|
|
|
Agents use the existing `prefs.set` command through CLI, MCP or the desktop control channel:
|
|
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{"method":"engine.execute","params":{"command":"prefs.set","params":{"path":"interface.language","value":"ko"}}}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Control requests are processed before synchronising the drawing language, so a request
|
|
|
|
|
|
updates the labels in the same rendered frame. Drawing language is local to the UI thread;
|
|
|
|
|
|
a scoped preview restores the previous language even if drawing unwinds. Menu and dialog
|
|
|
|
|
|
identity continues to use command IDs, independent of translated labels.
|
|
|
|
|
|
|
|
|
|
|
|
This is hot switching among the bundled catalogs. Catalog files are embedded with
|
|
|
|
|
|
`include_str!`; editing a TSV file requires a rebuild. External catalog watching and
|
|
|
|
|
|
file-resource hot reload are not implemented.
|
|
|
|
|
|
|
2026-10-11 10:06:18 -04:00
|
|
|
|
## Right-to-left languages
|
|
|
|
|
|
|
|
|
|
|
|
Arabic, Hebrew, Persian, Urdu and the other right-to-left languages listed in
|
|
|
|
|
|
`i18n::dir_for_code` mirror the window's chrome when chosen: the menu bar starts at the right
|
|
|
|
|
|
with File, the toolbox docks on the right and the panel dock and its rail on the left, and the
|
|
|
|
|
|
title, options and status bars read from the right (`crates/ui-egui/src/direction.rs`). The
|
|
|
|
|
|
direction follows the language code, so a catalog needs no extra setting.
|
|
|
|
|
|
|
|
|
|
|
|
Not mirrored yet: panel interiors, dialogs, menu rows (shortcuts stay on the right), submenus
|
|
|
|
|
|
(they open to the right), document tabs and the canvas. Before an Arabic catalog reads
|
|
|
|
|
|
correctly, the UI also needs an Arabic fallback font (the bundled UI fonts have no Arabic
|
|
|
|
|
|
glyphs) and bidirectional word order: egui 0.36 shapes each right-to-left word with HarfRust,
|
|
|
|
|
|
so letters join, but lays a line's words out left to right, and its caret indexing is off in
|
|
|
|
|
|
right-to-left text (emilk/egui#8576, #8577). Document text is laid out by the text engine.
|
|
|
|
|
|
|
|
|
|
|
|
No right-to-left catalog ships yet. To check the mirrored layout with any language, launch with
|
|
|
|
|
|
`PHOTOCRAFT_UI_DIRECTION=rtl` (or `ltr` to force left-to-right); it is read once per process
|
|
|
|
|
|
and has no effect on the web build:
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
PHOTOCRAFT_UI_DIRECTION=rtl cargo run -p photocraft-ui-egui --example snapshot -- --out rtl.png
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-10-07 21:06:33 +08:00
|
|
|
|
## Fonts
|
|
|
|
|
|
|
|
|
|
|
|
The native lazy CJK font loader prioritises the selected Japanese, Korean or Chinese script.
|
|
|
|
|
|
Switching languages rebuilds the font definitions and resets the existing loader, so fonts
|
|
|
|
|
|
removed from the definitions can be registered again. Idle frames keep their font cache.
|
|
|
|
|
|
Other languages retain the system's CJK fallback preference for document names and text.
|
|
|
|
|
|
|
|
|
|
|
|
Font files are not added to this repository. Native rendering uses installed system fonts
|
|
|
|
|
|
and the optional craft-fonts build input; see [Fonts](development.md#fonts-craft-fonts).
|
|
|
|
|
|
The web build can select every catalog, but CJK glyph delivery and automatic browser-locale
|
|
|
|
|
|
detection remain separate outstanding work. Switching catalogs does not supply missing fonts.
|
|
|
|
|
|
|
|
|
|
|
|
## Add or maintain a catalog
|
|
|
|
|
|
|
|
|
|
|
|
1. Add `<code>.tsv` under `crates/ui-egui/src/i18n`, following the header in `ja.tsv`.
|
|
|
|
|
|
2. Register its code, native name and plural rule in `i18n::LANGUAGES`.
|
|
|
|
|
|
3. Translate the meaning of the English source keys. Preserve `{name}` placeholders, escaped
|
|
|
|
|
|
characters and the trailing `…` on dialog commands. Use `@id` to disambiguate a command
|
|
|
|
|
|
label and `@plural` for count-sensitive text. Keep product names such as PhotoCraft.
|
2026-10-23 19:07:23 +00:00
|
|
|
|
4. Set `complete_menus` once `cargo xtask i18n-coverage` lists nothing missing for the language. Add source and license
|
2026-10-07 21:06:33 +08:00
|
|
|
|
attribution for translation assets, then run the catalog and shell tests.
|
|
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
|
cargo test -p photocraft-ui-egui --locked
|
|
|
|
|
|
cargo clippy -p photocraft-ui-egui --all-targets --locked -- -D warnings
|
|
|
|
|
|
cargo xtask i18n-coverage
|
|
|
|
|
|
cargo xtask layers
|
|
|
|
|
|
cargo xtask wasm
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Render representative dialogs with the offscreen `snapshot` example, including a sequence
|
|
|
|
|
|
of language changes in one process. Inspect the PNGs for missing glyphs and truncated labels.
|
|
|
|
|
|
Translations are original work or reuse the open PhotoCraft contributions credited in
|
|
|
|
|
|
[ATTRIBUTION.md](../ATTRIBUTION.md); no proprietary localisation resources are used.
|