diff --git a/docs/superpowers/specs/2026-05-26-i18n-design.md b/docs/superpowers/specs/2026-05-26-i18n-design.md new file mode 100644 index 0000000..cbf2055 --- /dev/null +++ b/docs/superpowers/specs/2026-05-26-i18n-design.md @@ -0,0 +1,162 @@ +# Internationalization Design + +## Status +Accepted by user on 2026-05-26. + +## Context +The project is a Vue 3 + Vite H5 application using Pinia, Vue Router, Vant, and Tailwind CSS. The current page structure is small, with `home` and `quick` feature folders under `src/pages`. The project needs internationalization that stays readable and maintainable as features grow. + +The first supported locales are: + +- Simplified Chinese: `zh-CN` +- English: `en-US` +- Thai: `th-TH` + +## Goals +- Keep translation files organized by feature module. +- Avoid hard-coded user-facing copy in Vue components. +- Provide one place for locale detection, persistence, switching, and fallback behavior. +- Keep the first implementation simple enough for the current H5 app. +- Make missing or inconsistent translation keys easy to detect. + +## Decision +Use `vue-i18n` and organize messages by feature module. Each feature owns the same message shape for every supported locale. Shared copy belongs in a `common` module. + +Recommended structure: + +```text +src/i18n/ + index.ts + locales.ts + storage.ts + types.ts + modules/ + common/ + zh-CN.ts + en-US.ts + th-TH.ts + index.ts + home/ + zh-CN.ts + en-US.ts + th-TH.ts + index.ts + quick/ + zh-CN.ts + en-US.ts + th-TH.ts + index.ts +``` + +The aggregated message object should be shaped by module: + +```ts +{ + common: { ... }, + home: { ... }, + quick: { ... } +} +``` + +Components should reference copy with module-prefixed keys: + +```ts +t("common.actions.confirm") +t("home.title") +t("quick.form.submit") +``` + +## Locale Model +Define supported locales in one place: + +```ts +export const supportedLocales = ["zh-CN", "en-US", "th-TH"] as const; +export type SupportedLocale = (typeof supportedLocales)[number]; +export const defaultLocale: SupportedLocale = "zh-CN"; +``` + +This keeps locale handling type-safe and prevents typo-prone string usage across the app. + +## Initial Locale Resolution +Initial locale resolution should follow this priority: + +1. User-selected locale stored in `localStorage`. +2. Browser language, matching `zh`, `en`, or `th`. +3. Default locale `zh-CN`. + +Use a stable storage key: + +```ts +nianxx.locale +``` + +Invalid or unsupported locale values must fall back to `zh-CN`. + +## Locale Switching +Expose a single `setLocale(locale)` function from the i18n module. It should: + +- Validate that the locale is supported. +- Update the `vue-i18n` global locale. +- Persist the locale to `localStorage`. +- Update `document.documentElement.lang`. +- Synchronize Vant's built-in locale package. + +Language state should not be manually duplicated in feature components. + +## Vant Integration +Because the app uses Vant, locale changes must also update Vant's internal copy. The i18n infrastructure should map app locales to Vant locale packs: + +- `zh-CN` -> Vant Chinese locale +- `en-US` -> Vant English locale +- `th-TH` -> Vant Thai locale + +This should happen during app startup and every manual locale switch. + +## Fallback and Error Behavior +- `zh-CN` is the fallback locale. +- Unknown locale input resolves to `zh-CN`. +- `localStorage` failures should not block app startup. +- If locale switching receives an unsupported locale, keep the current locale. +- Missing current-locale messages should fall back to `zh-CN`. + +## Module Ownership Rules +- Put shared buttons, state labels, and common errors in `common`. +- Put feature-specific page headings, form labels, placeholders, and business copy in the owning feature module. +- Keep the same key structure across `zh-CN`, `en-US`, and `th-TH`. +- Add a new module when a new feature directory appears under `src/pages`. +- Do not place unrelated feature copy in `common` just to avoid creating a module. + +## Testing Strategy +Add a lightweight Node test for i18n infrastructure: + +- Supported locale list includes `zh-CN`, `en-US`, and `th-TH`. +- Invalid locale values fall back to `zh-CN`. +- Stored locale takes priority over browser language. +- Browser language maps `zh`, `en`, and `th` to the supported locale codes. +- Translation key structure is consistent across all supported locales for each module. + +This test protects maintainability by catching missing English or Thai keys when new Chinese copy is added. + +## Alternatives Considered + +### Language-Centered Files +Example: `src/locales/zh-CN.ts`, `src/locales/en-US.ts`, `src/locales/th-TH.ts`. + +This is simple for very small apps, but files grow quickly and make feature ownership unclear. It was rejected because the project explicitly needs feature-module structure. + +### Route-Level Lazy Loading +Each page could lazy-load its own messages when the route is entered. + +This can reduce initial bundle size for large applications, but it adds async timing, fallback, and loading complexity. It is not necessary for the first H5 i18n foundation. + +## Implementation Scope +The first implementation should include: + +- Add `vue-i18n`. +- Add the `src/i18n` structure. +- Register i18n in `src/main.ts`. +- Add `common`, `home`, and `quick` message modules for all three locales. +- Add locale resolution, persistence, switching, and Vant synchronization helpers. +- Add tests for locale resolution and key consistency. + +The first implementation should not add a visible language switcher unless requested separately.