Skip to content

Split eager English i18n shell#4014

Open
koala73 wants to merge 1 commit into
mainfrom
codex/issue-3535-i18n-split
Open

Split eager English i18n shell#4014
koala73 wants to merge 1 commit into
mainfrom
codex/issue-3535-i18n-split

Conversation

@koala73
Copy link
Copy Markdown
Owner

@koala73 koala73 commented Jun 1, 2026

Summary

  • Replace the static full en.json import in src/services/i18n.ts with an eager en.shell.json subset.
  • Keep full en.json lazy-loadable through the locale glob and preload it after English startup; non-English startup still loads the requested locale plus full English fallback before switching languages.
  • Add a focused regression test for the English shell split and required eager translation surface.

Issue

Fixes #3535.

Validation

  • node --test tests/i18n-english-shell.test.mjs passed.
  • node --test tests/country-brief-i18n.test.mjs passed.
  • npm run typecheck passed.
  • git diff --check passed.
  • npm run build passed.
  • Pre-push hook passed: typecheck, API typecheck, boundary checks, edge bundle check, full unit suite (10331 tests, 0 failures), edge function tests, markdown lint, MDX lint, pro-test bundle freshness, version sync.

Bundle evidence

  • src/locales/en.json: 135,820 bytes; new eager src/locales/en.shell.json: 48,175 bytes.
  • Build emitted full English separately as dist/assets/en-WYH83nI7.js (110.14 kB, gzip 39.50 kB).
  • dist/index.html modulepreloads i18n-qlunRAMb.js, not en-*.js.
  • Full-only string probe (Country Facts) is present only in dist/assets/en-WYH83nI7.js, not entry/i18n/panels chunks.

Remaining risk

  • The shell subset is conservative and still broad enough for eager chrome/panel strings; future eager translations need to be covered by tests/i18n-english-shell.test.mjs or moved out of first-paint paths.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldmonitor Ready Ready Preview, Comment Jun 1, 2026 11:14am

Request Review

@koala73 koala73 changed the title [codex] Split eager English i18n shell Split eager English i18n shell Jun 1, 2026
@koala73 koala73 marked this pull request as ready for review June 1, 2026 10:11
@koala73 koala73 force-pushed the codex/issue-3535-i18n-split branch from a0a4a9a to 24efaca Compare June 1, 2026 10:13
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

This PR splits the monolithic en.json (~136 KB) eager import into a curated en.shell.json (~48 KB) subset for first-paint, deferring the full English dictionary to a lazy load. English users now get the shell bundle synchronously and the full bundle preloaded fire-and-forget; non-English users continue to await both their locale and the full English fallback before initI18n resolves.

  • src/locales/en.shell.json: New 794-line subset containing all shell/chrome-level strings (header, panels, common, auth, mcp, widgets, etc.) plus all component tooltips; values are verified by test to be identical to en.json.
  • src/services/i18n.ts: Replaces the static en.json import, updates the locale glob exclusion, removes the pre-seeded loadedLanguages.add('en'), and introduces preloadEnglishTranslation() as a non-blocking post-init kick for English users.
  • tests/i18n-english-shell.test.mjs: New regression guard checking that the static import was removed, the shell stays under 50 KB, and all required eager-chrome keys exist in the shell with values matching en.json.

Confidence Score: 4/5

Safe to merge; the shell split is well-validated by the new test and the non-English path is unchanged and fully awaited.

The logic change is clean and the core non-English path is unaffected. The two concerns are both non-blocking: the fire-and-forget preload for English leaves a brief window where non-shell keys could show as missing with no automatic re-render, and the test's non-recursive directory scan may not cover panel subcomponents. Neither is a confirmed breakage given the shell is designed to cover all first-paint strings and the build evidence checks out.

tests/i18n-english-shell.test.mjs — the eager-chrome file scan is non-recursive and may not cover all panel component files if any live in subdirectories.

Important Files Changed

Filename Overview
src/locales/en.shell.json New eager-load English subset (~48 KB) covering shell, header, panels, common, connectivity, premium, auth, mcp, widgets, countryBrief, contextMenu, components, modals, and popups. Values are verified by the new test to match en.json exactly.
src/services/i18n.ts Replaces static en.json import with en.shell.json; English users now get the shell eagerly and full English preloaded fire-and-forget. Non-English users still await both their locale and full English before returning. Fire-and-forget preload is intentional but means non-shell keys on English paths may briefly be absent until the preload resolves, and no re-render is triggered automatically when addResourceBundle completes.
tests/i18n-english-shell.test.mjs New regression test validating that en.json is no longer statically imported, the shell stays under 50 KB, and shell key values match en.json. Uses readdirSync (non-recursive) for eager chrome files, so components in subdirectories of src/components/ are not checked.

Sequence Diagram

sequenceDiagram
    participant App
    participant initI18n
    participant i18next
    participant localeModules

    App->>initI18n: initI18n()
    initI18n->>i18next: "init({ resources: { en: shellTranslation } })"
    i18next-->>initI18n: initialized (shell bundle only)

    alt English detected
        initI18n->>initI18n: preloadEnglishTranslation() [fire-and-forget]
        initI18n-->>App: returns (shell active)
        Note over App,localeModules: Components render with shell keys only
        initI18n->>localeModules: import('../locales/en.json') [async]
        localeModules-->>initI18n: full English dictionary
        initI18n->>i18next: addResourceBundle('en', fullDict, deep, overwrite)
        Note over i18next: Full English merged in, no re-render triggered
    else Non-English detected
        initI18n->>localeModules: "import('../locales/${lang}.json') [await]"
        localeModules-->>initI18n: locale translation
        initI18n->>localeModules: import('../locales/en.json') [await]
        localeModules-->>initI18n: full English (for fallback)
        initI18n->>i18next: changeLanguage(detectedLang)
        initI18n-->>App: returns (both bundles active)
    end
Loading

Comments Outside Diff (1)

  1. tests/i18n-english-shell.test.mjs, line 917-920 (link)

    P2 Non-recursive directory scan may miss panel subcomponents

    readdirSync(COMPONENTS_DIR) only lists files directly inside src/components/ — it does not descend into subdirectories. If any panel subcomponent or helper file lives in a nested directory and calls t() with a key not present in the shell, neither the test nor the type-checker would catch the gap. The component would silently show a missing-key string for English users during the preload window, with no automatic re-render once the full bundle loads.

Reviews (1): Last reviewed commit: "fix(i18n): split eager English shell" | Re-trigger Greptile

Comment thread src/services/i18n.ts
Comment on lines +70 to +75
function preloadEnglishTranslation(): void {
if (loadedLanguages.has('en')) return;
void ensureLanguageLoaded('en').catch((error) => {
console.warn('Failed to preload full English locale', error);
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fire-and-forget preload leaves a stale-UI window for non-shell keys

preloadEnglishTranslation() is not awaited, so initI18n() returns while en.json is still in-flight. Any class-based component that renders between initI18n returning and the preload completing, and that calls t() with a key present in en.json but absent from en.shell.json, will receive the key name (missing translation). Since i18next.addResourceBundle does not emit a languageChanged event, there is no automatic mechanism to re-render those components once the full bundle lands — the stale value persists until the next independent re-render triggered by data or user interaction. The shell is designed to cover all first-paint strings, so this is largely mitigated by the test, but any gap in shell key coverage would be invisible at runtime.

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.

131KB en.json statically imported by i18n — split into shell + lazy subsets

1 participant