i18n contribution

Add a translation — locale whitelists, key parity rules, and the no-duplicate-keys invariant.

Goal

Add or fix a translation in the GENIE.AI web UI, mobile app, or Keycloak login pages without breaking the locale whitelist or the no-duplicate-keys invariant.

Three translation surfaces

SurfaceWhereFile pattern
Web (Vue 3 SPA)components/gov-chat-frontend/src/i18n/locales/<lang>.jsone file per locale
Mobile (Flutter)mobile/genie_ai_mobile/lib/src/localization/app_en.arb (source) + Flutter-generated app_localizations_<lang>.dartARB (English only) + generated Dart
Keycloak email themeconfigs/keycloak/themes/genie/email/messages/messages_<lang>.propertiesJava properties
English is the source of truth. The nameEN field of every translatable resource (service categories, services) is what the RAG pipeline retrieves against. Do not translate nameEN.

Locale availability (whitelist)

SurfaceKnobDefault
WebVUE_APP_AVAILABLE_LOCALES (comma-separated codes, e.g. en,es)All locales shipped
MobileKeycloakConfig.supportedLocaleCodes per flavorAll locales shipped
KeycloakKEYCLOAK_SUPPORTED_LOCALES (JSON array, e.g. ["en","es"])Curated default

Setting a whitelist hides locales from the picker but does not delete the source files.

See Restrict active locales for the canonical deployer reference.

Translation-key parity rules

When you add a new key, every active locale must include the same key to avoid partial-translation fallbacks at runtime:

  1. Add the key to locales/en.js (English) with the canonical wording.
  2. Add the same key to every other active locale file. Use the source language’s structure as the source of truth.
  3. Do not add keys only to a subset of locales — missing keys fall back to English and silently break locale coverage.

No-duplicate-keys invariant

Translation files are merged at runtime via require.context('./locales', ...) followed by localeContext.keys().forEach(...) in components/gov-chat-frontend/src/i18n/index.js. Duplicate keys within a locale file cause silent overrides and corrupt the UI. Review each locale file manually for duplicates before committing — git grep -n "<key>" across locales/<lang>.js to spot accidental repeats.

Service / category translations

Service categories live in the serviceCategories ArangoDB collection. Category translations go in the serviceCategoryTranslations collection with key pattern <categoryKey>_<LANGCODE> where LANGCODE is the source locale uppercased (e.g. catKey_ES, catKey_FR). Service translations go in the separate serviceTranslations collection with key pattern <serviceKey>_<LANGCODE> (e.g. serviceKey_ES). Each translation document has serviceCategoryId or serviceId, languageCode (UPPERCASE), and translation (the localized text). The RAG pipeline only ever reads nameEN.

Recent key changes

  • 2026-09-22 — weatherAuthRequired added (UX copy for the weather prompt that surfaces when the connected weather provider is missing an API key). weatherError removed (dead key, no callers). Contributors verifying key parity should confirm the current key list against the locale files, not the docs — the docs only describe recent additions and removals, not the full enumerated set.

Workflow

git checkout -b i18n/<handle>/<locale>-<key-or-area>
# edit components/gov-chat-frontend/src/i18n/locales/<locale>.js
# manually verify no duplicate keys (git grep "<key>" locales/<locale>.js)
npm run lint                          # Vue / JS lint
git add components/gov-chat-frontend/src/i18n/locales/<locale>.js
git commit -m "i18n(<locale>): add <key> — <one-line why>"
git push -u origin i18n/<handle>/<locale>-<key-or-area>
glab mr create --target-branch main

Best practices for translators

  • Match the tone of the source locale — formal for government contexts, clear and direct for user-facing copy.
  • Don’t translate proper nouns (Keycloak, OPEA, GENIE.AI, ArangoDB).
  • Don’t translate placeholder values ({count}, {name}).
  • Keep the line length under 100 chars where possible (Vue template wrapping).
  • Test in the UI — switch the locale picker and walk through the affected screen.

Troubleshooting

SymptomLikely causeFix
UI shows a fallback English stringMissing key in another localeAdd the key to every active locale file
Key silently overriddenDuplicate key in the same locale fileSearch the file for the key, rename one
Mobile picker doesn’t show your localeWhitelist excludes itAdd the code to KeycloakConfig.supportedLocaleCodes