Translations (i18n / Weblate)¶
The frontend (apps/web, Quasar/Vue 3) is internationalized with
vue-i18n. English (en-US) is the source of
truth; every other locale is a translation managed in
Weblate. English is also the default display locale and
fallback (src/boot/i18n.ts); for the system language setting the UI maps the
browser language to a bundled locale and falls back to English
(src/stores/appearance.ts).
Where the messages live¶
apps/web/src/i18n/
├── index.ts # imports the locale JSON, builds the messages map
├── en-US/index.json # SOURCE — edit English strings here
└── de-DE/index.json # translation (managed by Weblate)
- Format: nested JSON, one file per locale. Keys are dot-addressed in code
(
t('chat.placeholder')). The files are pre-compiled at build time by@intlify/unplugin-vue-i18n(include: ./src/i18ninquasar.config.ts). src/boot/i18n.tspinsMessageSchema = typeof messages['en-US'], so the English file defines the TypeScript shape — a missing key inen-USis a type error.
Day-to-day: adding or changing UI strings¶
- Add/edit the key in
en-US/index.jsononly. - Reference it in components via
useI18n()→t('your.key'). - Use named placeholders, e.g.
"stepOf": "Step {n} of {total}". - Run the parity check (also enforced by the pre-commit hook and CI):
It fails on missing/extra keys and on {placeholder} mismatches against the
English source.
Do not translate into de-DE (or any other locale) by hand in normal
development — Weblate owns those files and will overwrite manual edits. New keys
you add to en-US show up in Weblate as untranslated strings for translators to
fill in. (German strings that already exist stay as-is.)
Conventions Weblate relies on¶
- Placeholders use vue-i18n's single-brace named form
{name}. The Weblate component flagpython-brace-formatvalidates that translations keep the same set of placeholders. - Plurals use vue-i18n's pipe form (
"no items | one item | {count} items"). Weblate stores these as a single string (the pipes are not split into CLDR plural categories), so translators must preserve the|segments.
Adding a new language¶
- In Weblate, "Start new translation" for the
webcomponent and pick the locale. Weblate createsapps/web/src/i18n/<locale>/index.jsonand opens a PR. - After merge, wire the locale into the app:
src/i18n/index.ts— import and register the new JSON.src/stores/appearance.ts— extendUiLanguage,navLang()andresolvedLocale()(these currently special-case onlyde/en).
Weblate setup (Hosted Weblate, libre)¶
Translations are hosted on Hosted Weblate under
the libre plan (free for open projects). Repo-side config lives in .weblate
(for the wlc CLI); the component itself is configured server-side:
| Setting | Value |
|---|---|
| File format | JSON nested structure file |
| File mask | apps/web/src/i18n/*/index.json |
| Monolingual base language file | apps/web/src/i18n/en-US/index.json |
| Source / template language | English (en) |
| Edit base file | off (English source is edited in code) |
| Translation flags | python-brace-format |
Git integration: enable GitHub pull requests with push branch weblate —
Weblate commits translations to that branch and opens PRs against main for
review (it never pushes to main directly). Connect the repo via the Weblate
GitHub App (or an SSH deploy key + webhook).