# Internationalization (i18n) Pod localizes its own UI chrome with [`rust-i18n`](https://crates.io/crates/rust-i18n). Translation strings live in TOML files under `assets/locales/`. The framework is wired in `src/main.rs` (the `i18n!` macro) and the active locale is set once at startup from the configured language. ## How it is wired - `src/main.rs` declares `#[macro_use] extern crate rust_i18n;` (exposing the `t!` macro crate-wide) and calls `rust_i18n::i18n!("assets/locales", fallback = "en");` at the crate root. - `src/i18n.rs` exposes `i18n::set_locale(language: Language)`, which maps the `Language` enum to its rust-i18n locale string via `Language::esi_code()` (the enum codes — `en`, `de`, `en-us`, `es`, `fr`, `ja`, `ko`, `ru`, `zh` — are exactly the locale strings rust-i18n expects). - `app::boot()` calls `i18n::set_locale(accessibility.language())` once at startup, alongside the existing high-contrast and scale application. A language change applies at the next restart (see ADR-0041); nothing swaps the locale live mid-session. ## Locale file layout — one file per locale Each locale is a single file, `assets/locales/.toml`, holding every feature's strings for that locale. rust-i18n globs `assets/locales/**/*.{toml,yml,yaml}` and reads the locale from the filename stem, so `assets/locales/en.toml` is the `en` locale, `assets/locales/fr.toml` is `fr`, and so on: ```text assets/locales/ en.toml # English baseline (the fallback locale) en-us.toml # exact copy of en, US spelling de.toml # German fr.toml # French es.toml ru.toml ja.toml ko.toml zh.toml ``` Within a file, group keys under their feature/area namespace as TOML tables. The distinct namespaces keep features from colliding in the one merged key map per locale (`wallet.*`, `shell.*`, `common.*`, and so on). `assets/locales/en.toml` is the authored baseline. `en-us` is an exact copy of `en` with US spelling; the other seven are machine-translated. The locale-parity check (`mise run lint:i18n`) enforces that every locale carries the same key set with the same `%{...}` placeholders. ## Key-namespace convention Keys are dot-separated and namespaced by their origin: | Pattern | Use for | Example | |-------------------------------|------------------------------------|------------------------------------| | `..` | feature-module UI strings | `wallet.journal.title` | | `config..` | user-facing labels on domain enums | `config.feature.wallet` | | `common.` | shared, cross-feature strings | `common.cancel` | In a TOML fragment, group keys under their namespace as a table so the file stays a small, append-only surface: ```toml [shell.notifications] mark_all_read = "Mark all read" title = "Notifications" ``` `` is the module name (`wallet`, `assets`, `calendar`, `industry`, `mail`, `roster`, `skills`, `settings`, `shell`, `splash`, ...). `` groups within a feature (`journal`, `transactions`, ...). Keep variants and keys alphabetical, matching the project's ordering convention. ## Interpolation convention Use rust-i18n named placeholders — `%{name}` in the locale value, and `name => value` (or `name = value`) when calling `t!`. Never build user-facing strings with `format!`; interpolate through `t!` so word order can change per language. ```toml # assets/locales/shell.en.toml [shell.notifications] footer_unread = "%{count} unread" ``` ```rust // returns a Cow; call .into_owned() / .to_string() when an owned String is needed let label = t!("shell.notifications.footer_unread", count => total); ``` ## Missing-key behavior - A key missing in the active locale resolves to the `en` value (the `fallback = "en"` chain). - A key absent from every locale resolves to the key string itself and never panics. Both are covered by unit tests in `src/i18n.rs`. ## Extracting strings (the per-feature task) 1. Add or extend `assets/locales/.en.toml` with the keys you are introducing, grouped under their namespace table. 2. Replace `text("literal")` with `text(t!(".."))` and `format!(...)` user-facing strings with `t!("...", name => value)`. 3. Leave the non-`en` fragments to the translation pass — author only the English baseline. See `src/app.rs` notification chrome (`shell.notifications.*`) for the reference conversion.