# UI localization (i18n) The LeafWiki **application UI** (buttons, labels, navigation, dialogs, settings, system messages) is translatable. Wiki **page content** is not affected by this — it is always shown exactly as written. This document explains the setup and the exact steps to **add a new language**. Related issue: [#789](https://github.com/perber/leafwiki/issues/789). ## How it works - The frontend uses [`i18next`](https://www.i18next.com/) via `react-i18next`. - Strings live in **per-feature namespace catalogs**: `ui/leafwiki-ui/src/locales//.json` (for example `en/editor.json`, `en/settings.json`). There are 19 namespace files today. - `ui/leafwiki-ui/src/lib/i18n.ts` **globs** `src/locales/*/*.json` at build time. A new language folder is picked up automatically — there is no list of languages to register in the frontend, and the language switcher in *Settings → Account → Preferences* is generated from what it finds. - **English (`en/`) is the fallback language.** Any key missing in another language falls back to English at runtime. - `ui/leafwiki-ui/src/lib/i18n.test.ts` enforces **key parity**: every namespace file must exist for every language with an *identical* set of keys. The test suite fails on any missing or extra key. - The name shown for a language in the switcher comes from that language's own `common.json` → `language.selfName` (e.g. `"Deutsch"`, `"Español"`). Components read strings with `useTranslation('')`; code outside React uses `i18next.t(key, { ns: '' })`. ### Shipped languages | Code | Language | Notes | |------|----------|-------| | `en` | English | default / fallback | | `de` | Deutsch | | | `es` | Español | | ## Add a new language Example: adding French (`fr`). ### 1. Create the locale folder Copy the full English set as a starting point and translate every value. Keep every key exactly as in English — do not rename, add, or drop keys. ```bash cd ui/leafwiki-ui/src/locales cp -r en fr ``` Then translate the values in `fr/*.json`. In `fr/common.json`, set the language's own name: ```json "language": { "selfName": "Français" } ``` Leaving a value in English is acceptable as an interim state (English is the fallback anyway), but the **key must be present** in every file or the parity test fails. ### 2. Extend the backend language allow-list `internal/usersettings/language.go` has an allow-list that user-settings validation checks when a user saves their language choice. The frontend discovers the locale by glob, so the language appears in the switcher immediately — but **without this step, saving the choice fails validation** and the UI snaps back with an error (this is exactly what happened for Spanish in [#1526](https://github.com/perber/leafwiki/pull/1526)). ```go var allowedLanguages = map[string]bool{ DefaultLanguage: true, "de": true, "es": true, "fr": true, // add this } ``` Update the expectations in `internal/usersettings/language_test.go` to include the new code. ### 3. Wire up date/time formatting `ui/leafwiki-ui/src/lib/dateLocale.ts` keeps `date-fns` (relative times like "3 days ago") and `Intl.DateTimeFormat` in sync with the UI language. It has two small maps that must gain an entry, otherwise dates stay English for the new language: ```ts import { de, enUS, fr, type Locale } from 'date-fns/locale' const DATE_FNS_LOCALES: Record = { en: enUS, de, fr, // add this } const BCP47_TAGS: Record = { en: 'en-US', de: 'de-DE', fr: 'fr-FR', // add this } ``` ### 4. Run the checks ```bash # frontend: key parity + language discovery cd ui/leafwiki-ui npm run test npm run format # backend: allow-list + user-settings validation cd ../.. go test ./internal/usersettings/... ``` ### 5. (Optional) make it the server default Operators can point new users at the language with `--default-language=fr` / `LEAFWIKI_DEFAULT_LANGUAGE=fr`. Once the locale folder ships, this value is picked up automatically; an unknown value is ignored with a console warning. ## Update or add strings in an existing language 1. Never hardcode a user-facing string in React/TS — always go through a key. 2. Add or change the key in `ui/leafwiki-ui/src/locales/en/.json` first, then make the **same** change in every other `ui/leafwiki-ui/src/locales//.json`. 3. A brand-new namespace only needs `en/.json` plus a matching file under every other language folder — `i18n.ts` globs them, there is nothing else to register. 4. `npm run test` in `ui/leafwiki-ui` confirms parity still holds.