Skip to content

feat: add seven-language interface localization (next-intl) - #36

Open
tuifeisec wants to merge 1 commit into
LuxAlgo:mainfrom
tuifeisec:feat/i18n-seven-languages
Open

tuifeisec wants to merge 1 commit into
LuxAlgo:mainfrom
tuifeisec:feat/i18n-seven-languages

Conversation

@tuifeisec

Copy link
Copy Markdown

What & why

Complete interface localization in seven languages — English (default), Simplified Chinese, Japanese, Korean, Traditional Chinese, Spanish, and French — covering all 16 pages, shared components, charts, error messages, import diagnostics, AI responses, voice dictation, and the PDF/PNG review export.

Self-hosted users outside English-speaking markets currently get a fully English UI. This makes the whole journal usable in their language while leaving every machine-facing contract untouched.

Design

  • next-intl 4.14.9 (MIT) with the App Router request config. The locale rides in a NEXT_LOCALE cookie instead of URL prefixes, so existing URLs, bookmarks, and filter query params are unaffected. First visit uses the default (en); no Accept-Language sniffing.
  • Messages live in apps/web/messages/<locale>/<namespace>.json — 28 namespaces × 7 locales (~2,000 keys per locale), merged per request and injected through a single NextIntlClientProvider in the root layout. html lang and metadata follow the cookie. The switcher lives in Settings → General (single entry point, product decision).
  • Human-readable formatting (money, numbers, dates, durations, weekday/month names, currency display names) renders through Intl with the active locale. Storage and machine formats stay exactly as they were.
  • Server responses keep their English { error } text as the stable machine identifier and gain an optional code (plus optional params) so clients can localize without regex-matching English prose. Status codes and auth behavior are unchanged; the existing English-text matching in lib/ai-feedback.ts still works as before.
  • Importers expose an optional diagnostics[] ({ code, params }) alongside the existing English warnings/errors; detection and rejection rules are untouched.
  • AI ask/recap/critique requests carry the verified interface locale and the prompt requires the reply in that language (recap's "I / Keep / Fix" structure is requested per language). AI-import schema and field semantics are unchanged.
  • Dictation defaults to the interface locale's BCP 47 speech tag, with an in-control override.
  • PDF/PNG review export picks the font per locale: the existing Noto Sans for Latin locales plus bundled Noto Sans SC/TC/JP/KR (all SIL OFL 1.1, license files included). The per-character missing-glyph check is kept and runs against the selected font.

What does NOT change

  • CSV/JSON export shape, headers, value precision (byte-compared across locales in tests)
  • Import parsing rules, decimal/timezone interpretation, importer column keywords
  • URL paths and query parameter keys/values, localStorage keys, DB schema
  • Broker/brand names, symbols, account names, user notes — never translated
  • Auth: /api/locale is the only addition to the public path list, and it only writes the language cookie

Dependencies

next-intl@4.14.9, date-fns@^4.4.0, @formatjs/icu-messageformat-parser@^3.5.20 — all MIT, all pass scripts/check-licenses.mjs. Four Noto Sans CJK fonts (SIL OFL 1.1) with license files under apps/web/public/fonts/.

Testing

  • Vitest: 532 → 689 tests across 77 files, all green. jsdom component tests render through a locale-controlled provider (apps/web/tests/helpers/i18n.tsx); nothing depends on the developer machine's language or timezone.
  • New permanent gates: pnpm check:messages (key sets, ICU parsing, parameter parity, and plural categories across all 7 locales) and a rich-text contract test that scans every t.rich() call site for matching tags in every locale.
  • All CI gates pass locally (format:check, typecheck, test, license gate, package + app builds).
  • Manual follow-ups tracked in docs/i18n.md §19: real-model AI reply language, real-browser speech-recognition support, and a narrow-viewport visual pass.

Review guide

Suggested reading order:

  1. apps/web/src/i18n/config.ts and apps/web/src/i18n/request.ts — locale enum, cookie resolution, per-request message merge (paths are built only from the validated enum)
  2. docs/i18n.md — the behavior contract: switching rules, error strategy, machine-format boundaries, seven-language glossary
  3. apps/web/src/server/api.ts + apps/web/src/middleware.ts — the only protocol deltas are the optional code/params fields and the public /api/locale route (~40 lines)
  4. scripts/check-messages.mjs + apps/web/tests/i18n-rich-tags.test.ts — the two gates
  5. Everything else: message files are pure data, and page diffs are mechanical useTranslations conversions

docs/i18n.md is the long-term maintenance entry point — adding an 8th language is a config + catalog + glossary change with no business-code edits.

Cookie-driven locale (NEXT_LOCALE, en default) across all pages, shared
components, charts, error messages, import diagnostics, AI replies,
dictation, and the PDF/PNG review export. Machine contracts (CSV/JSON
exports, import parsing, URL/query params, DB schema, auth) are unchanged;
server errors gain an optional stable `code` field and importers an
optional `diagnostics[]` so clients can localize without parsing English
text. Includes per-locale fonts (Noto Sans SC/TC/JP/KR, SIL OFL 1.1), a
seven-locale message-integrity gate, and a rich-text tag contract test.
Behavior contract and maintenance guide: docs/i18n.md.
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.

1 participant