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
| Layer | Files | Role |
|---|---|---|
| Locale JSON | app/locales/{en,zh}/{common,auth,settings,showcase}.json | Translation copy grouped by namespace. |
| Resource registry | app/lib/i18n/resources.ts | Imports locale JSON, declares supported languages, namespaces, and fallback language. |
| Renderer i18n | app/lib/i18n/index.ts | Initializes i18next with react-i18next and exposes changeLanguage. |
| Bootstrap | app/lib/i18n/bootstrap.ts | Reads the persisted language setting before React mounts. |
| Main-process i18n | lib/main/i18n.ts | Creates 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:
- Read
settings.localeover Conveyor. - If it is
enorzh, use that language. - If it is
system, ask the main process forapp.getLocale(). - Resolve unsupported system languages back to
en. - 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.jsonOmit 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.jsonandapp/locales/zh/showcase.json. - Remove the showcase imports in
app/lib/i18n/resources.ts. - Remove
showcasefromI18N_NAMESPACESand from eachresourceslanguage. - Remove any remaining
showcase:keys from routes, palette sources, or tests.
Run:
rg "showcase:" app lib
pnpm lint:check && pnpm typecheck && pnpm test