From 9c1369186ffb175782c32e0ecc2419d5b6a071b8 Mon Sep 17 00:00:00 2001 From: ryan Date: Fri, 24 Jul 2026 16:08:45 +0800 Subject: [PATCH] docs(i18n): add frontend bilingual i18n design spec Capture the approved next-intl non-routing approach for zh-CN/en, locale resolution, static-export constraints, and phase-1 core-path scope. --- .../specs/2026-07-24-frontend-i18n-design.md | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-24-frontend-i18n-design.md diff --git a/docs/superpowers/specs/2026-07-24-frontend-i18n-design.md b/docs/superpowers/specs/2026-07-24-frontend-i18n-design.md new file mode 100644 index 00000000..e2f9c2c4 --- /dev/null +++ b/docs/superpowers/specs/2026-07-24-frontend-i18n-design.md @@ -0,0 +1,253 @@ +# Frontend i18n Design + +Date: 2026-07-24 +Status: Approved for implementation planning +Scope: Frontend UI only + +## 1. Goals + +Add bilingual UI support for Wavelet frontend: + +- Languages: `zh-CN` and `en` +- Default locale: `zh-CN` +- Locale resolution: explicit user choice → browser language → default +- Phase 1: infrastructure + core paths only (layout / auth / settings) +- Must remain compatible with `NEXT_STANDALONE_EXPORT` static export + +### Non-goals (Phase 1) + +- Backend API error / message localization +- Email / push notification localization +- URL locale prefixes (`/en/...`, `/zh-CN/...`) and SEO hreflang +- Full translation of all admin business pages + +## 2. Context + +Current state: + +- Root layout hardcodes `lang='zh-CN'` +- UI copy is mostly Chinese string literals across many TSX files +- Date formatting often hardcodes `zh-CN` / `date-fns/locale` `zhCN` +- No i18n library is installed +- Frontend supports both normal Next rewrites mode and static export embed mode + +## 3. Approach + +Use **next-intl in non-routing / provider mode**. + +Why this approach: + +- Mature App Router integration and clear `useTranslations` API +- ICU message format ready when needed +- Avoids locale-prefixed routing, which conflicts with static-export simplicity and current route structure +- Cookie + browser detection matches product preference without SEO path requirements + +Rejected alternatives: + +- Fully custom Context + JSON: lower dependency cost, but reimplements interpolation/plurals/type safety poorly +- `i18next` + `react-i18next`: powerful, but heavier and less natural for this Next App Router setup + +## 4. Architecture + +``` +RootLayout + html lang={locale} + ThemeProvider + CustomThemeProvider + AppQueryProvider + NextIntlClientProvider(locale, messages) + existing User / Notification / Bell providers + pages + components +``` + +### Key files + +| Path | Responsibility | +| --- | --- | +| `frontend/i18n/config.ts` | Supported locales, default locale, cookie name, normalize helpers | +| `frontend/i18n/request.ts` | `getRequestConfig` for server-side locale/messages resolution when not in export mode | +| `frontend/i18n/client.ts` | Client helpers to read/write locale preference | +| `frontend/messages/zh-CN.json` | Chinese messages | +| `frontend/messages/en.json` | English messages | +| `frontend/components/common/language-switcher.tsx` (or under `layout/`) | Language switch UI | +| `frontend/lib/i18n-format.ts` (optional location under `i18n/`) | Locale-aware date/number formatting helpers | + +### Runtime flow + +1. Resolve locale: cookie `NEXT_LOCALE` → browser languages → `zh-CN` +2. Load `messages/{locale}.json` +3. Provide locale + messages through `NextIntlClientProvider` +4. Components call `useTranslations('')` +5. Language switcher writes cookie and refreshes locale/messages +6. Update `document.documentElement.lang` + +## 5. Locale Resolution + +Supported locales: `zh-CN`, `en` + +Normalization: + +- `zh`, `zh-CN`, `zh-Hans*` → `zh-CN` +- `en`, `en-US`, `en-GB`, other `en-*` → `en` +- anything else → `zh-CN` + +Priority: + +1. User explicit choice stored in cookie `NEXT_LOCALE` +2. Browser language (`Accept-Language` on server, `navigator.languages` on client) +3. Default `zh-CN` + +Invalid cookie values are normalized to a supported locale and may be rewritten to a valid value. + +## 6. Static Export Compatibility + +Constraints: + +- No locale-segment routes +- No middleware-based locale rewriting required for correctness +- `build:embed` (`NEXT_STANDALONE_EXPORT=true`) must continue to work + +Behavior: + +- **Normal SSR/dev**: resolve locale on server when possible to reduce first-paint language flash +- **Static export**: ship both message catalogs; resolve on client from cookie/browser; accept a brief default-language flash similar to theme hydration, using existing `suppressHydrationWarning` patterns where needed + +## 7. Message Organization + +Single catalog files with nested namespaces: + +```json +{ + "common": { + "save": "保存", + "cancel": "取消", + "loading": "加载中..." + }, + "layout": { + "nav": { + "home": "首页", + "myFiles": "我的文件" + }, + "userMenu": { + "settings": "设置", + "logout": "退出登录" + } + }, + "auth": { + "login": { + "title": "登录", + "submit": "登录" + } + }, + "settings": { + "appearance": { + "language": "语言", + "languageDesc": "选择界面显示语言" + } + } +} +``` + +Conventions: + +- Keys use camelCase and hierarchical grouping +- Prefer complete phrases as values; avoid assembling sentences in components +- Use ICU only when needed (`{name}`, plural forms) +- Backend `error_msg` values are shown as-is in Phase 1 +- Frontend-owned toast / validation copy is translated + +Both locale files must keep the same key tree. A key-alignment check script is recommended. + +## 8. Language Switcher UX + +Placement: + +- Header toolbar near theme controls +- Appearance settings page as an explicit preference row + +UI labels for language options use native names and do not themselves translate: + +- `中文` +- `English` + +On change: + +1. Persist `NEXT_LOCALE` +2. Apply new locale/messages (via refresh or controlled provider update) +3. Sync `document.documentElement.lang` +4. Preserve unrelated UI state where practical (theme, auth session, sidebar collapse) + +## 9. Phase 1 Migration Scope + +### In scope + +- Install and wire `next-intl` +- Message catalogs for core namespaces +- Locale resolution + persistence +- `LanguageSwitcher` +- Translate: + - layout shell: sidebar nav/user menu, header accessible labels / titles + - auth: login / register / OTP labels, buttons, validation messages + - settings: appearance (including language preference), profile, security, notifications, access-token visible copy +- Replace date/number hardcoding only where touched by the above paths +- Ensure `html lang` reflects active locale + +### Out of scope + +- Remaining admin pages and deep business modules +- Backend localization +- Route prefixing / SEO alternate links + +Unmigrated pages may remain Chinese hard-coded; mixed-language UI is acceptable during incremental rollout. + +## 10. Formatting Helpers + +Introduce locale-aware helpers for dates/numbers used by migrated surfaces, e.g.: + +- `formatDateTime(value, locale)` +- `formatNumber(value, locale)` + +`date-fns` locale objects should follow active locale (`zhCN` / `enUS`) when a migrated component uses them. + +## 11. Error Handling & Fallbacks + +| Case | Behavior | +| --- | --- | +| Missing message key | Dev warning; do not crash; show key or fallback language value | +| Unsupported cookie locale | Normalize to supported locale / default | +| Partial migration | Keep hard-coded Chinese on unmigrated screens | +| Backend error strings | Display raw `error_msg` | + +## 12. Testing & Acceptance + +Manual: + +1. No cookie + browser Chinese → Chinese UI +2. No cookie + browser English → English UI +3. Manual switch to English survives refresh +4. Manual switch back to Chinese survives refresh +5. Core paths (layout/auth/settings) have no major residual hard-coded Chinese UI copy +6. `pnpm build` and `pnpm build:embed` both succeed +7. Language switch does not break theme, session, or sidebar state + +Automated (recommended): + +- Unit tests for `normalizeLocale` / resolution priority +- Script or test asserting `zh-CN.json` and `en.json` key parity + +## 13. Rollout Plan (high level) + +1. Add i18n infrastructure and empty/core message files +2. Mount provider and language switcher +3. Migrate layout shell copy +4. Migrate auth copy +5. Migrate settings copy + appearance language control +6. Verify SSR and static-export builds +7. Document how later pages should adopt `useTranslations` + +## 14. Open Implementation Notes + +- Prefer cookie name `NEXT_LOCALE` unless an existing project cookie convention conflicts during implementation +- Prefer minimal surface-area integration with next-intl; avoid introducing locale-based routing APIs that break static export +- Keep `internal/util` and backend packages untouched +- After implementation, follow repo frontend conventions and existing provider composition style