Translations
How translations work in Zephyr and how to add or improve them.
Stack
Zephyr uses Inlang Paraglide for i18n. It's a build-time message compiler - translations are JSON files; the build step turns them into typed JS functions, so missed strings become a build error rather than a silent runtime issue.
File layout
messages/
├── en.json # English (source of truth)
├── fr.json
├── es-ES.json
├── pt-BR.json
├── zh-CN.json
├── ru.json
└── ar.jsonEach file is a flat JSON dict where keys are message IDs and values are the translation:
{
"randomizer_runtime_download": "Download & install",
"randomizer_runtime_installFailed": "Runtime install failed"
}Keys use snake_case with a topic prefix (randomizer_, mods_, prefs_, etc.). They're stable - once shipped, a key is never renamed (only deprecated and replaced).
Adding a new language
Pick the BCP-47 code for your language (
itfor Italian,defor German,jafor Japanese, etc.).Copy
messages/en.jsontomessages/<your-code>.json.Translate the values. Keys stay identical, including the
language_namekey (set its value to the autonym for your language - it's what shows up in Zephyr's language dropdown).Add the locale to
project.inlang/settings.json:json{ "locales": ["en", "fr", "es-ES", "pt-BR", "zh-CN", "ru", "ar", "<your-code>"] }Open a PR.
You don't need to translate everything before submitting. Missing keys fall back to the base locale at compile time.
Improving an existing translation
Just edit the .json file and PR. For multi-string changes, run the app and pick your language from Settings -> Language to verify nothing broke.
Style guide
- Tone: same register as English - direct, no exclamation marks unless the original has one, no marketing fluff.
- Capitalization: follow the target language's conventions (sentence case for French, e.g.), not English title case.
- Numbers and dates: use the target language's format.
1,000.50in English;1 000,50in French. - Length: try to stay close to the English string's length so layouts don't break. If a translation is necessarily longer, it's fine - the UI is mostly fluid - but flag it in the PR so we can sanity-check.
- Mod-author content: don't translate it. Mod names, descriptions, configs, etc. are passed through verbatim.
Plurals
Paraglide handles plurals via a count parameter. Don't translate plural forms by hand - use the existing key structure:
{
"mods_count_one": "{count} mod",
"mods_count_other": "{count} mods"
}Languages with more than two plural forms (Russian, Arabic, etc.) use the standard CLDR forms (zero, one, two, few, many, other). Add only the forms your language needs.
RTL languages
Arabic is the only RTL language currently. The ar.json translations follow the same pattern as everything else - the layout direction flip is handled by the app, not by the translations.
Machine translation
Machine translation is available as a starting point for new keys, never for shipped strings:
- Add the new key to
en.jsonwith the canonical text. - Run
pnpm machine-translate- this calls the Inlang CLI to fill missing keys in other locales with machine output. - Native speakers review and correct the machine output via PR before the next release.
Testing
pnpm tauri devIn the running app, swap locales via Settings -> Language for instant comparison.
Credits
If you contribute a translation, mention yourself in the PR description.