Internationalization
i18next-driven translations for the UI, menus, and tray.
The starter uses one i18next instance for React, the native application menu, and the tray menu. Because Tauri builds those native surfaces from TypeScript, there is no second Rust- or native-process translation system to synchronize.
What ships
| Layer | Files | Role |
|---|---|---|
| Locale JSON | src/locales/{en,zh}/{common,auth,settings,showcase}.json | English and Simplified Chinese copy, grouped by namespace |
| Resource registry | src/lib/i18n/resources.ts | Languages, namespaces, imports, fallback, and locale resolution |
| React integration | src/lib/i18n/index.ts | Initializes i18next with react-i18next and exports changeLanguage() |
| Preference | src/lib/settings/schema.ts and store.ts | Validates and persists system, en, or zh |
| Native labels | src/lib/menu/labels.ts | Resolves common:menu.* and common:tray.* through the same instance |
common is the default namespace. settings and auth keep feature copy
separate. showcase is marked [template-demo] and can be deleted with the
removable demo layer.
Startup and language changes
App.tsx initializes English synchronously so the first render always has a
resource bundle. Its startup effect then reads settings.locale:
enorzhis applied directly.systemis resolved fromnavigator.language.- A locale such as
zh-CNis reduced to its supported primary tag,zh. - Missing or unsupported values fall back to English.
The saved default is system. Unlike the unused helper in
src/lib/i18n/bootstrap.ts, the current App.tsx startup path does not replace
that preference with a resolved language.
At runtime, use Settings → General → Language. GeneralSection saves the
preference and calls changeLanguage(). initNativeShell() listens for
i18next's languageChanged event, then reinstalls the application menu and
refreshes the active tray menu. That is why React and native labels change
without restarting.
The surviving Conveyor compatibility call app.setLocale() is a no-op in
this Tauri template. Native localization works because the menu and tray are
JavaScript-built and subscribe to the shared i18next instance.
Use translation keys
Select a namespace in React:
const { t } = useTranslation('settings')
return <h1>{t('appearance.title')}</h1>Use a namespace prefix when translating outside a namespace-bound component:
i18n.t('common:menu.checkForUpdates')Keep every locale structurally aligned. src/lib/menu/labels.ts has English
defaults so a menu can still be constructed before initialization, but those
fallbacks are not a substitute for translating common:menu.* and
common:tray.*.
Add a locale
Add every namespace file
Create src/locales/fr/common.json, auth.json, settings.json, and
showcase.json. Omit showcase.json if the demo layer has already been
removed.
Register the resources
In src/lib/i18n/resources.ts, import the files, add fr to
SUPPORTED_LANGUAGES, and add the fr object to resources.
Widen persisted settings
Add fr to both localeSettingSchema and supportedLanguageSchema in
src/lib/settings/schema.ts. Update any explicit unions or tests that still
list only en | zh.
Name the choice
Add general.language.fr to every locale's settings.json. The General
section builds its choices from SUPPORTED_LANGUAGES, so registration makes
the new option appear automatically.
Verify all surfaces
Switch to French and restart. Check pages, dialogs, toasts, command palette, application menu, and the tray menu. Also test an unsupported system locale to confirm the English fallback.
Remove the Showcase namespace
When the demo layer is gone:
- Delete
src/locales/en/showcase.jsonandsrc/locales/zh/showcase.json. - Remove their imports and resource entries from
resources.ts. - Remove
showcasefromI18N_NAMESPACES. - Remove remaining
showcase:consumers and tests.
rg "showcase:" src
pnpm lint:check && pnpm typecheck && pnpm test