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

LayerFilesRole
Locale JSONsrc/locales/{en,zh}/{common,auth,settings,showcase}.jsonEnglish and Simplified Chinese copy, grouped by namespace
Resource registrysrc/lib/i18n/resources.tsLanguages, namespaces, imports, fallback, and locale resolution
React integrationsrc/lib/i18n/index.tsInitializes i18next with react-i18next and exports changeLanguage()
Preferencesrc/lib/settings/schema.ts and store.tsValidates and persists system, en, or zh
Native labelssrc/lib/menu/labels.tsResolves 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:

  1. en or zh is applied directly.
  2. system is resolved from navigator.language.
  3. A locale such as zh-CN is reduced to its supported primary tag, zh.
  4. 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.json and src/locales/zh/showcase.json.
  • Remove their imports and resource entries from resources.ts.
  • Remove showcase from I18N_NAMESPACES.
  • Remove remaining showcase: consumers and tests.
rg "showcase:" src
pnpm lint:check && pnpm typecheck && pnpm test

On this page