Internationalization
Setup
- Library:
@nuxtjs/i18nv10.2.x - Strategy:
no_prefix— no locale code in URL path - Default locale:
en_GB - Browser detection: disabled — locale is always set manually
Supported Locales
| Code | Language | File |
|---|---|---|
en_GB | English (UK) | en_GB.json |
en_US | English (US) | en_US.json |
es_ES | Spanish | es_ES.json |
ar_SA | Arabic | ar_SA.json |
de_DE | German | de_DE.json |
fr_FR | French | fr_FR.json |
ru_RU | Russian | ru_RU.json |
pt_PT | Portuguese | pt_PT.json |
pl_PL | Polish | pl_PL.json |
sv_SE | Swedish | sv_SE.json |
en_PS | Pseudo English (testing) | en_PS.json |
Translation files live in i18n/locales/ and are ~130–145 KB each.
Translation Key Convention
Keys use an @ prefix by project convention (not enforced by the library):
@common.somethingWentWrong
@common.languages.en.label
@common.prereleaseTitle
@toasts.questionnaire.failedToFetch
@toasts.questionnaire.created
@stores.permission.toast.folderPermissionCreated.title
@confirms.deleteItem.title
@components.designer.addSectionNested keys follow a domain.subdomain.key pattern. The @ prefix is purely a project-level naming convention.
Usage in Components and Stores
const { t } = useI18n();
t("@common.somethingWentWrong");
t("@common.languages.en.label");Auto-imported by Nuxt — no import statement needed.
Locale Initialization
app/plugins/i18n.client.ts runs once on client mount:
1. Read localStorage["user-language"] (stored as JSON: {"value": "en_GB"})
2. If found and different from current locale → $i18n.setLocale(savedLocale)
3. If not found → read navigator.language, convert "en-GB" → "en_GB"
4. Fall back to en_GB if no match foundLocale Persistence
The user's chosen locale is stored in localStorage["user-language"] as:
{ "value": "en_GB" }useLocalStorage("user-language", { value: "en_GB" }) provides a reactive ref bound to this key. useUserStore reads this at initialization and sets appConfig.language_code, calendar_code, and date_locale.
RTL Support
app/layouts/default.vue computes isRTL based on the current locale and sets the HTML dir attribute:
const rtlLanguages = [
"ar_SA",
"ar",
"he_IL",
"he",
"fa_IR",
"fa",
"ur_PK",
"ur",
];
const isRTL = computed(() => rtlLanguages.includes(currentLocale.value));
useHead({
htmlAttrs: { lang: currentLocale, dir: isRTL.value ? "rtl" : "ltr" },
});HTML in Translations
strictMessage: false in nuxt.config.ts allows HTML markup inside translation strings. Use v-html-safe (from vue-html-secure) when rendering these to prevent XSS.
Pseudo-locale
en_PS.json is generated by running:
npm run pseudo-localeThe pseudo-locale wraps all strings with decorators (e.g. [Ëñglïsh]) to visually identify:
- Hardcoded strings that bypass translation
- Layout issues with expanded text
Questionnaire Content vs UI Language
These are separate concerns:
| Concern | Controller | Scope |
|---|---|---|
| UI language | @nuxtjs/i18n | All labels, buttons, toasts, error messages |
| Questionnaire content language | useDesignerStore.current_language | Question text, section titles in the designer |
Questionnaire content language codes come from useProviderStore (fetched from the backend) and are stored in payload.definitions.translations. They are not the same as the @nuxtjs/i18n locale codes.