Internationalization

i18next in the renderer and main process, with English and Simplified Chinese.

The template uses one i18next resource bundle in both the React renderer and the Electron main process. That keeps page text, native menus, tray labels, command palette items, and settings copy in the same translation system.

What ships

LayerFilesRole
Locale JSONapp/locales/{en,zh}/{common,auth,settings,showcase}.jsonTranslation copy grouped by namespace.
Resource registryapp/lib/i18n/resources.tsImports locale JSON, declares supported languages, namespaces, and fallback language.
Renderer i18napp/lib/i18n/index.tsInitializes i18next with react-i18next and exposes changeLanguage.
Bootstrapapp/lib/i18n/bootstrap.tsReads the persisted language setting before React mounts.
Main-process i18nlib/main/i18n.tsCreates a separate i18next instance for native menu and tray text.

English (en) and Simplified Chinese (zh) ship by default. common is the default namespace; settings and auth own feature-specific copy.

showcase is marked [template-demo]. Delete that namespace with the Showcase demo layer once your product pages replace it.

Boot and runtime flow

At renderer startup, app/renderer.tsx calls bootstrapI18n() before mounting React:

  1. Read settings.locale over Conveyor.
  2. If it is en or zh, use that language.
  3. If it is system, ask the main process for app.getLocale().
  4. Resolve unsupported system languages back to en.
  5. Initialize renderer i18n with the effective language.

The current bootstrap persists a resolved system choice back to settings, so future launches are deterministic.

Runtime language changes happen in Settings → General. GeneralSection updates the settings store, calls renderer changeLanguage(effective), then calls app.setLocale(effective) over Conveyor. The main handler calls setMainLanguage() and runs onLocaleChanged, which rebuilds the native menu and refreshes localized tray labels.

Adding a locale

Add locale files

Create one JSON file per namespace:

app/locales/fr/common.json
app/locales/fr/auth.json
app/locales/fr/settings.json
app/locales/fr/showcase.json

Omit showcase.json if you have already deleted the demo layer.

Register the language

Import the files in app/lib/i18n/resources.ts, add the language code to SUPPORTED_LANGUAGES, and add the new resource object under resources.

Widen the settings schemas

Add the language code to localeSettingSchema and supportedLanguageSchema in lib/conveyor/schemas/app-schema.ts. Update any TypeScript unions that still list only en | zh.

Add Settings labels

Add settings:general.language.fr in every locale so the General language select can display the new option.

Verify both processes

Switch to the locale in Settings → General. Confirm React text updates, the native menu labels update, and tray menu labels update without restarting.

Translation keys

Use namespaced keys where a component does not live in the default namespace:

const { t } = useTranslation('settings')

return <h1>{t('appearance.title')}</h1>

For cross-namespace calls, include the namespace prefix:

i18next.t('auth:toasts.signedOut')

Native menus and tray code use t() from lib/main/i18n.ts, but the keys come from the same resource bundle. Menu labels live mostly in common:menu.*.

Removing Showcase translations

When you remove the demo layer:

  • Delete app/locales/en/showcase.json and app/locales/zh/showcase.json.
  • Remove the showcase imports in app/lib/i18n/resources.ts.
  • Remove showcase from I18N_NAMESPACES and from each resources language.
  • Remove any remaining showcase: keys from routes, palette sources, or tests.

Run:

rg "showcase:" app lib
pnpm lint:check && pnpm typecheck && pnpm test

On this page