Skip to content

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:i18n reports key count).
  • Locale persistence: locale key in app_settings SQLite table (canonical) and mesh-client:appSettings localStorage (fast startup read); reconciled in App.tsx on mount.
  • Reduce motion: reduceMotion boolean in the same app_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 OS prefers-reduced-motion after first-run init — see ../accessibility-checklist.md.
  • 24-hour time: use24HourTime beside Reduce motion in App → Appearance (timeFormatStore, formatDisplayTime; SQLite app_settings + mesh-client:appSettings localStorage). When on, chat/diagnostics clocks force 24-hour; when off, follow system locale.
  • Adding strings: add to src/renderer/locales/en/translation.json, use t('your.key') in components; check:i18n enforces all call sites resolve to English keys and fails on unused English keys (no static t(), registered dynamic prefix, quoted literal in src/, or tabs.* from TAB_SLOT_IDS).
  • Removing strings: delete the key from en/translation.json and run pnpm run i18n:prune-unused -- --write to drop it from every locale (or remove manually). check:i18n blocks orphaned English keys.
  • Auto-translate: pnpm run i18n:auto-translate uses MyMemory (default) or LibreTranslate (LIBRETRANSLATE_URL). With git, the default run only fills keys that are new in English vs HEAD and still missing from each locale (pre-commit uses this). Use pnpm run i18n:auto-translate --all or I18N_TRANSLATE_ALL=1 to backfill every key missing from a locale vs English. Use --audit (or I18N_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 contact info@coloradomesh.org by default for the 50 k words/day quota; override with MYMEMORY_EMAIL if 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 via scripts/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.cliMultiHopHint auto-ping semantics). Unused-key detection lives in scripts/i18n-unused-keys.mjs; pnpm run check:i18n:branch skips the unused pass and only runs quality rules on keys new/changed vs HEAD.
  • Language selector: src/renderer/components/LanguageSelector.tsx — globe-icon dropdown in the header; calls i18n.changeLanguage() + mergeAppSetting('locale', ...) + electronAPI.appSettings.set('locale', ...).