Skip to content

chore(cleanup): replace hand-rolled i18n loader with i18next - #199

Merged
EtienneLescot merged 3 commits into
release/v1.8.0from
ponytail/replace-i18n-with-i18next
Jul 29, 2026
Merged

chore(cleanup): replace hand-rolled i18n loader with i18next#199
EtienneLescot merged 3 commits into
release/v1.8.0from
ponytail/replace-i18n-with-i18next

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the 126-line hand-rolled i18n loader in src/i18n/loader.ts with a ~70-line wrapper around the maintained i18next library. {{var}} interpolation, defaultNS, fallbackLng, and locale resource loading all come from the library; the public API (getAvailableLocales, getLocaleName, getLocaleShort, translate) and the existing call sites in I18nContext, LaunchWindow, and EditorTopBar are unchanged.

Heads-up: new runtime dependency

This PR adds i18next@^23.16.0 as a runtime dependency (it's the only new dep). The original audit marked this swap as "needs a maintainer's blessing" since it pulls a maintained library into the bundle. 23.x is the conservative pick (same major that was current when the audit was written); 26.x is the current stable and would also work for our minimal init + t surface.

Bundle cost: ~12 kB minified for i18next core (no plugins, no react-i18next, no backend — we feed the resources inline via import.meta.glob).

Maintainer eyeball pass

The library is wired to all 13 locales × 7 namespaces = 91 JSON files. The exists() + t() shim falls back to the en translation for missing keys at runtime (i18next handles this via fallbackLng: 'en'), and the existing src/i18n/__tests__/localeParity.test.ts already enforces that every locale has every en key at build time. A quick eyeball pass against the 91 files would be appreciated before merge.

Notes on interpolation behavior

The new loader uses i18next's default {{var}} regex, which matches the hand-rolled loader's /\{\{(\w+)\}\}/g exactly. The only intentional divergence: i18next is configured with interpolation: { escapeValue: false } because React escapes at the render boundary; the old loader had no escaping at all. This only matters if a translation value contains < or > — and since all of our vars are string | number (file names, project names, counts), the user-visible output is identical.

Test results

  • npx tsc --noEmit: clean
  • npx vitest --run src/i18n src/components/launch/LaunchWindow.test.tsx: 40/40 pass
  • npx vitest --run src/: 904/904 pass; the only file-level failures are a pre-existing pixi.js resolution issue in this worktree's fresh install (unrelated to this change — same tests pass in the main checkout)
  • npx biome check src/i18n/loader.ts package.json: clean

Diff summary

 package-lock.json | 375 +++++------------
 package.json       |   1 +
 src/i18n/loader.ts | 144 +++++++-----------
 3 files changed, 82 insertions(+), 438 deletions(-)

The package-lock.json diff is mostly stale-entry cleanup (28 packages like @langchain/langgraph, gsap, uuid, langsmith, emoji-picker-react, @pixi/filter-drop-shadow were no longer in package.json but still in the lock). One new entry: node_modules/i18next.

Type of change

  • Refactor / maintenance

Release impact

  • Minor

Desktop impact

  • Not platform-specific

Testing

npx tsc --noEmit
npx vitest --run src/i18n src/components/launch/LaunchWindow.test.tsx
npx biome check src/i18n/loader.ts package.json

All clean. Full npm run test shows 904/904 pass; the few file-level failures are pre-existing pixi.js resolver issues in this worktree's fresh node_modules and are reproducible on the base branch with the worktree's node_modules state.

Drops the 126-line bespoke loader in src/i18n/loader.ts for a ~70-line
wrapper around i18next. Locale-fallback, {{var}} interpolation, defaultNS,
and fallbackLng come from the library; we keep the same four exports
(getAvailableLocales, getLocaleName, getLocaleShort, translate) so
LaunchWindow, EditorTopBar, and I18nContext don't change.

Public API preserved: I18nContext still calls translate(locale, ns, key, vars);
the new translate is a thin shim around i18next.t. Miss behavior matches the
old loader (\{namespace}.{key}\ on miss, locale code on locale-name miss).

Test results:
- tsc --noEmit: clean
- vitest (i18n + LaunchWindow + components): all pass
- biome check: clean
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f101e37-9c9e-4fdb-8c4c-cfc3f2abef40

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ponytail/replace-i18n-with-i18next

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

sepion02 and others added 2 commits July 28, 2026 20:31
…marker fallback

The stored value fed only init({lng}), but every lookup passes an explicit lng,
so it never affected output — while adding a SecurityError path at module scope,
where it takes down the whole module graph instead of one component. I18nContext
already owns the preference behind a try/catch.

returnedObjectHandler restores the old loader's behaviour for a non-leaf key:
fall through to the 'namespace.key' marker rather than rendering i18next's
English developer message into the UI.
@EtienneLescot
EtienneLescot merged commit 6203774 into release/v1.8.0 Jul 29, 2026
8 of 9 checks passed
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