Skip to content

refactor(i18n): migrate to i18next framework#331

Open
cosarah wants to merge 12 commits intomainfrom
feat/i18next
Open

refactor(i18n): migrate to i18next framework#331
cosarah wants to merge 12 commits intomainfrom
feat/i18next

Conversation

@cosarah
Copy link
Copy Markdown
Collaborator

@cosarah cosarah commented Mar 30, 2026

Summary

Migrate the hand-rolled i18n system to i18next + react-i18next. Adding a new language now only requires dropping a JSON file in lib/i18n/locales/ — zero changes to existing code.

Related Issues

Closes #327

Changes

  • Add i18next, react-i18next, i18next-resources-to-backend dependencies
  • Replace 5 TS translation modules (chat, common, generation, settings, stage) with 2 JSON locale files (zh-CN.json, en-US.json)
  • Rewrite lib/i18n/index.ts as a thin wrapper around i18n.t()
  • Rewrite use-i18n hook to delegate to useTranslation(); external API (locale, setLocale, t) unchanged — all 59+ consumer files need no changes
  • Auto-discover locale files via dynamic import() — bundler scans locales/ at build time
  • Defer language detection to useEffect after hydration (fixes SSR mismatch)
  • Remove hardcoded locale validation; rely on i18next's built-in fallback mechanism
  • Use i18next interpolation for greeting (greetingWithName / greetingDefault) to support natural phrasing across languages
  • Widen Locale type from 'zh-CN' | 'en-US' to string for extensibility

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or build changes

Verification

Steps to reproduce / test

  1. Start dev server (pnpm dev)
  2. Verify all UI text renders correctly in both Chinese and English
  3. Toggle language via the header language selector — all text should switch
  4. Check browser with English locale — should auto-detect and switch to English on first visit
  5. Verify greeting shows "Hi there" (no nickname) / "Hi, Alice" (with nickname) in English mode

What you personally verified

  • Full page navigation in both languages
  • Language persistence across page refresh (localStorage)
  • SSR hydration — no mismatch warning in console
  • Browser language auto-detection on first visit

Evidence

  • CI passes (pnpm format && pnpm lint --fix && npx tsc --noEmit)
  • Manually tested locally
  • Screenshots / recordings attached (if UI changes)

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have added/updated documentation as needed
  • My changes do not introduce new warnings

cosarah and others added 12 commits March 30, 2026 15:28
Replace hand-rolled i18n with i18next + react-i18next so that adding a
new language only requires dropping a JSON file in lib/i18n/locales/.

- Add i18next, react-i18next, i18next-browser-languagedetector deps
- Generate zh-CN.json / en-US.json from existing TS translation modules
- Rewrite lib/i18n/index.ts as a thin wrapper around i18n.t()
- Rewrite use-i18n hook to delegate to useTranslation(); external API
  (locale, setLocale, t) is unchanged so consumers need no changes
- SSR-safe: LanguageDetector only loaded on client side

Closes #327

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace string concatenation (greeting + displayName) with two
i18next keys: greetingWithName (with {{name}} interpolation) and
greetingDefault (standalone, no name).

This lets each locale choose natural phrasing independently:
- zh-CN: "嗨,同学" / "嗨,Alice"
- en-US: "Hi there" / "Hi, Alice"
- Future locales can avoid gender issues by choosing genderless defaults

Also widen the t() type signature to accept interpolation options.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded zh-CN/en-US imports with i18next-resources-to-backend
and dynamic import(`./locales/${language}.json`). Bundler scans the
locales/ directory at build time, so adding a new language now requires
only dropping a JSON file — zero changes to existing code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LanguageDetector ran during i18next init(), detecting browser language
before React hydrated — server rendered zh-CN while client switched to
en-US immediately, causing a hydration mismatch.

Fix: remove i18next-browser-languagedetector; init with a fixed lng
(zh-CN) so server and client agree on the first render. Language
detection is now done in I18nProvider's useEffect after hydration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace manual locale validation and startsWith('zh') prefix matching
with i18next's built-in fallback mechanism. Now changeLanguage() is
called with navigator.language directly — if the exact locale has no
JSON file, i18next automatically falls back to fallbackLng.

Also widen Locale type from union to string so adding new languages
doesn't require modifying types.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add lib/i18n/course-languages.ts with curated language list
  (zh-CN, zh-TW, en-US, ja, ko, fr, de, es, pt, ru, ar) including
  native labels and English prompt names
- Course language defaults to UI locale on first visit; once user
  explicitly picks a language, that choice persists across sessions
- Replace toggle button with dropdown selector showing native labels
- Widen language types from 'zh-CN'|'en-US' to string throughout
- Fix hardcoded language ternaries in LLM prompt injection:
  - prompt-builder.ts: use getCourseLanguagePromptName()
  - classroom-generation.ts: remove normalizeLanguage() that forced
    all non-English to zh-CN
  - PBL system prompt, agent templates, generate-pbl: append language
    instruction for non-zh/en languages
  - quiz-grade API: add language suffix for grading feedback

Closes #327

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
quiz-view was passing the UI locale to the grading API, causing AI
feedback to follow the student's answer language instead of the course
language. Now reads stage.language from the store.

Also strengthen the grading prompt: explicitly instruct the LLM to
write comments in the course language regardless of the student's
input language.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverts f7bd6bd and 5bb5133 — these are independent features that
should go into a separate branch/PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These files are replaced by lib/i18n/locales/*.json and are no longer
imported anywhere in the codebase.

Removed: chat.ts, common.ts, generation.ts, settings.ts, stage.ts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

[Feature]: Migrate i18n to i18next framework

1 participant