Documentation

Translations (i18n / Weblate)

The frontend (the personal-agent-org/frontend repo, 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

In the frontend repo (personal-agent-org/frontend, SPA at the repo root):

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/i18n in quasar.config.ts).
  • src/boot/i18n.ts pins MessageSchema = typeof messages['en-US'], so the English file defines the TypeScript shape - a missing key in en-US is a type error.

Day-to-day: adding or changing UI strings

  1. Add/edit the key in en-US/index.json only.

  2. Reference it in components via useI18n()t('your.key').

  3. Use named placeholders, e.g. "stepOf": "Step {n} of {total}" (setup.stepOf).

  4. Run the parity check:

    just i18n          # or: node scripts/i18n_check.mjs
    

    It flattens each locale to dotted leaf paths and fails (exit 1) on missing keys, extra keys, or mismatched {named} placeholder sets against the English source. It is part of just check and runs in CI (.github/workflows/ci.yml, step “i18n parity check”). The pre-commit hooks (.pre-commit-config.yaml) only cover formatting and conventional-commit messages, so run it yourself before pushing.

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 flag python-brace-format validates that translations keep the same set of placeholders; scripts/i18n_check.mjs enforces the same locally.
  • Counts are interpolated with a named argument, not CLDR plurals: the message embeds the count placeholder (e.g. "chunks": "{n} chunks", files.chunks) and the caller passes t('files.chunks', { n }). The source uses no vue-i18n pipe-plural (a | b) strings, so there are no | segments for translators to preserve.

Adding a new language

  1. In Weblate, “Start new translation” for the web component and pick the locale. Weblate creates src/i18n/<locale>/index.json and opens a PR.
  2. After merge, wire the locale into the app:
    • src/i18n/index.ts — import and register the new JSON.
    • src/stores/appearance.ts — extend UiLanguage, navLang() and resolvedLocale() (these currently special-case only de/en).

Weblate setup

Translations are managed in Weblate. The repo carries no .weblate / wlc config file; the component is configured entirely server-side:

Setting Value
File format JSON nested structure file
File mask src/i18n/*/index.json
Monolingual base language file 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).

Browser extension (WebExtension i18n)

The browser extension lives in its own repository (personal-agent-org/browser-extension) and carries its own WebExtension i18n (_locales/*/messages.json) plus a Weblate component there — see that repo’s README. It is no longer part of this repository’s translation setup.