Skip to content

refactor(i18n): share i18next across Electron processes - #1529

Merged
ewen-poch merged 4 commits into
aipoch:mainfrom
roxi3906:codex/refactor/main-process-i18next
Aug 25, 2026
Merged

refactor(i18n): share i18next across Electron processes#1529
ewen-poch merged 4 commits into
aipoch:mainfrom
roxi3906:codex/refactor/main-process-i18next

Conversation

@roxi3906

@roxi3906 roxi3906 commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

The renderer used i18next with complete locale catalogs, while Electron main separately maintained native-message maps, manual interpolation, and a Notebook-specific plural branch. Catalog authorship was also split physically between renderer files and shared namespace files.

Architecture

Before:

  • renderer owned a global i18next setup and one flat catalog per locale
  • main owned handwritten message maps, replaceAll interpolation, and Intl.PluralRules key switching
  • catalog authorship was split between renderer and main

After:

  • src/shared/i18n owns process-neutral instance initialization, direct-English fallback, interpolation, plural behavior, resource assembly, and six authoritative locale files
  • each locale has exactly one JSON file with flat common, native, and renderer namespace objects
  • main creates its own i18next instance and named-imports only common + native
  • renderer creates its own instance, named-imports only common + renderer, and adds initReactI18next only in its adapter
  • LocalePreferenceOwner remains main's locale authority, so startup, tray, and dialog copy works before renderer creation and changes immediately with the preference
  • NativeTranslator remains the small main-process call interface; no renderer import or translation IPC is introduced

Catalog ownership

  • common: copy whose meaning and reviewed translation are identical in both processes
  • native: tray, Electron dialog, picker, export, and Notebook-conflict copy
  • renderer: React UI copy, including context-specific meanings that must not be shared merely because the English key matches
  • all six locales have one authoritative JSON file under src/shared/i18n/locales; namespace keys are pairwise disjoint in every locale
  • cross-process workflow labels have one common owner; distinct meanings use explicit context such as Reload_window
  • locale self-names are process-neutral metadata in LOCALE_SELF_NAMES, not dormant translation entries
  • guards span renderer and main call sites, aliases, namespaces, context, interpolation, plurals, orphans, duplicate blocks, glossary, scripts, technical identifiers, and cross-namespace ownership

Conflict resolution

The branch is rebased onto current upstream/main ee88badc.

Plural behavior

  • natural-language English keys remain in use with keySeparator: false and nsSeparator: false
  • the Notebook conflict message uses standard i18next/CLDR selection with defaultValue_one
  • tests cover English 1/2, Russian 1/2/5/21, French representative CLDR categories, and the single-category locales
  • adding a locale requires catalog data only, not a main-process plural branch

Bundle impact

  • i18next is a runtime dependency because electron-vite externalizes it from main
  • the built main translation chunk remains 26,409 bytes (26.41 kB)
  • all 69 out/main JS chunks exclude renderer-only Russian MCP transfer sentinels while retaining native Connector-picker and Notebook sentinels
  • main and renderer use named JSON namespace imports, so main does not indiscriminately bundle renderer catalogs

Verification

  • final affected i18n, native locale, Notebook, Connector/MCP, composer redo, Skill startup, and CI-impact Vitest matrix: 1090/1090 across 14 files
  • catalog guard alone: 684/684, including RED/GREEN namespace ownership plus zh-Hans/ru MCP and OAuth technical-identifier mutations
  • npm run typecheck
  • npm run lint: 0 errors and 0 warnings
  • Prettier check over all changed existing files
  • git diff --check upstream/main...HEAD
  • npm run build
  • built-main bundle scan across 69 chunks
  • built Electron Russian locale/native quit-dialog E2E: 1/1
  • independent Standards and Spec reviews: no remaining Critical, Important, or Minor findings

The Windows global classifier coverage test still exposes an upstream #1695 baseline gap for restricted-runtime-profile.ts; the same gap exists at the fixed base and neither that production file nor the classifier implementation is changed by this PR.

Rollback

Revert the four task commits in reverse order:

  1. a30dfd22 - strict single-owner catalog contracts, technical-term guards, and contextual reload ownership
  2. 1a9fb2be - guard, fallback, E2E, and ownership hardening
  3. 8461c8db - single-file shared locale catalogs
  4. dc093872 - shared i18next architecture and main-process migration

No persisted-settings schema or data migration is involved. This remains an independent PR against main; it does not reuse or modify the merged Russian-localization branch or PR #1443.

Authors

@roxi3906
roxi3906 marked this pull request as ready for review August 21, 2026 08:29
@roxi3906
roxi3906 marked this pull request as draft August 21, 2026 09:05
@roxi3906
roxi3906 force-pushed the codex/refactor/main-process-i18next branch 4 times, most recently from ea87a7a to aae77e6 Compare August 24, 2026 03:33
@roxi3906
roxi3906 marked this pull request as ready for review August 24, 2026 07:27
@roxi3906
roxi3906 force-pushed the codex/refactor/main-process-i18next branch 5 times, most recently from f2480fd to cd0bc18 Compare August 25, 2026 01:50
@roxi3906
roxi3906 force-pushed the codex/refactor/main-process-i18next branch from cd0bc18 to a30dfd2 Compare August 25, 2026 06:54
@ewen-poch
ewen-poch merged commit fe55ce9 into aipoch:main Aug 25, 2026
19 of 22 checks passed
@roxi3906
roxi3906 deleted the codex/refactor/main-process-i18next branch August 25, 2026 08:14
ewen-poch added a commit that referenced this pull request Aug 25, 2026
…#1705)

The Codex restricted-runtime profile copies USERPROFILE when isolating
CODEX_HOME, so a change to that file can affect Windows process
environment. The global classifier coverage test failed on #1529 because
the file was not in the windows_sensitive overlay.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants