Agent reference: i18n / Localization
Deep subsystem reference for AI assistants. Open this when a task touches locale files, the localization workflow, auto-translate, or the language selector. The always-on hard rule (never add hardcoded English) lives in .cursor/rules/use-i18n-no-hardcoded-english.mdc; broader guidelines live in AGENTS.md.
- Framework: i18next + react-i18next; static JSON bundles loaded at startup;
fallbackLng: 'en'. - Locale files:
src/renderer/locales/{en,es,uk,de,zh,pt-BR,fr,it,pl,cs,ja,ru,nl,ko,tr,id}/translation.json— English is source of truth (pnpm run check:i18nreports key count). - Locale persistence:
localekey inapp_settingsSQLite table (canonical) andmesh-client:appSettingslocalStorage (fast startup read); reconciled inApp.tsxon mount. - Reduce motion:
reduceMotionboolean in the sameapp_settings/ localStorage bundle; toggled in App → Appearance (AppPanel.tsx). When true, non-essential UI motion (animated icons, decorative CSS pulses) is suppressed; loading spinners and connection status pulses remain. Does not auto-sync to OSprefers-reduced-motionafter first-run init — see../accessibility-checklist.md. - 24-hour time:
use24HourTimebeside Reduce motion in App → Appearance (timeFormatStore,formatDisplayTime; SQLiteapp_settings+mesh-client:appSettingslocalStorage). When on, chat/diagnostics clocks force 24-hour; when off, follow system locale. - Adding strings: add to
src/renderer/locales/en/translation.json, uset('your.key')in components;check:i18nenforces all call sites resolve to English keys and fails on unused English keys (no statict(), registered dynamic prefix, quoted literal insrc/, ortabs.*fromTAB_SLOT_IDS). - Removing strings: delete the key from
en/translation.jsonand runpnpm run i18n:prune-unused -- --writeto drop it from every locale (or remove manually).check:i18nblocks orphaned English keys. - Auto-translate:
pnpm run i18n:auto-translateuses MyMemory (default) or LibreTranslate (LIBRETRANSLATE_URL). With git, the default run only fills keys that are new in English vsHEADand still missing from each locale (pre-commit uses this). Usepnpm run i18n:auto-translate --allorI18N_TRANSLATE_ALL=1to backfill every key missing from a locale vs English. Use--audit(orI18N_AUDIT=1) to additionally retranslate any key whose locale value is still identical to English (i.e. never actually translated). Existing translated entries are never overwritten. MyMemory sends contactinfo@coloradomesh.orgby default for the 50 k words/day quota; override withMYMEMORY_EMAILif needed. - Key check:
pnpm run check:i18n— hard fails on missing English keys and unused English keys; warns (does not fail) on incomplete locale coverage so rate-limit gaps don't block commits. Also runs locale quality rules viascripts/check-i18n-quality.mjs(mojibake,meshtastic://spacing, false friends, boot-sequence transport labels, Reticulum hub/stack wording, RRC slash-command token preservation and room false friends, Repeaters CLI danger confirm action text,repeatersPanel.cliMultiHopHintauto-ping semantics). Unused-key detection lives inscripts/i18n-unused-keys.mjs;pnpm run check:i18n:branchskips the unused pass and only runs quality rules on keys new/changed vsHEAD. - Language selector:
src/renderer/components/LanguageSelector.tsx— globe-icon dropdown in the header; callsi18n.changeLanguage()+mergeAppSetting('locale', ...)+electronAPI.appSettings.set('locale', ...).