Files
everything-is-suitable/docs/superpowers/plans/2026-04-28-uniapp-i18n/plan.md
T
张翔 a39b4388f3 chore: 添加文档、资源与测试输出
- assets/ 静态资源
- docs/superpowers/ 技能文档
- dogfood-output/ 测试验证输出
2026-04-29 21:17:10 +08:00

432 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UniApp 主应用国际化(i18n)实现计划
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
**目标:** 为 UniApp 主应用引入 vue-i18n 国际化体系,支持 9 种语言,将所有硬编码中文 UI 文本替换为 `$t()` / `t()` 调用。
**架构:** 集中式翻译文件方案,所有翻译键值对按语言分文件存放于 `src/locales/`,通过 vue-i18n 实例注入 Vue 应用。领域术语保留中文原词不纳入翻译体系。
**技术栈:** vue-i18n@9, Vue 3 Composition API, UniApp, Vitest
**设计规格:** `docs/superpowers/specs/2026-04-28-uniapp-i18n-design.md`
---
## 整体架构流程
```mermaid
graph TD
A[用户启动 App] --> B{localStorage 有 preferred-locale?}
B -->|是| C[读取存储的 locale]
B -->|否| D{读取系统语言}
D -->|匹配支持语言| E[使用系统语言]
D -->|不匹配| F[回退 zh-CN]
C --> G[应用 vue-i18n locale]
E --> G
F --> G
G --> H[渲染页面 - $t 替换硬编码文本]
H --> I{用户切换系统语言?}
I -->|是| J[重启 App 重新检测]
I -->|否| H
```
## 组件改造流程
```mermaid
graph LR
subgraph 改造前
A1[模板硬编码中文] --> B1[Script 硬编码中文]
B1 --> C1[pages.json 硬编码中文]
end
subgraph 改造后
A2["模板: {{ $t('key') }}"] --> B2["Script: const { t } = useI18n(); t('key')"]
B2 --> C2["onShow: uni.setNavigationBarTitle"]
end
A1 -.->|替换| A2
B1 -.->|替换| B2
C1 -.->|运行时覆盖| C2
```
## Phase 依赖关系
```mermaid
graph TD
P1["Phase 1: 基础设施<br/>安装 vue-i18n + 创建核心模块"] --> P2["Phase 2: 翻译文件<br/>创建 9 种语言翻译"]
P2 --> P3["Phase 3: 页面改造<br/>3 个页面 + pages.json"]
P2 --> P4["Phase 4: 组件改造<br/>10 个组件"]
P3 --> P5["Phase 5: 测试与验证<br/>完整性 + 组件 + E2E"]
P4 --> P5
```
---
## 文件结构
### 新建文件
| 文件路径 | 职责 |
|---------|------|
| `src/locales/index.ts` | vue-i18n 实例创建、语言检测/切换/持久化 |
| `src/locales/zh-CN.ts` | 简体中文翻译(主翻译源) |
| `src/locales/zh-TW.ts` | 繁體中文翻译 |
| `src/locales/en.ts` | English 翻译 |
| `src/locales/ja.ts` | 日本語翻译 |
| `src/locales/ko.ts` | 한국어 翻译 |
| `src/locales/de.ts` | Deutsch 翻译 |
| `src/locales/fr.ts` | Français 翻译 |
| `src/locales/es.ts` | Español 翻译 |
| `src/locales/pt.ts` | Português 翻译 |
| `src/locales/__tests__/completeness.test.ts` | 翻译完整性校验测试 |
| `src/locales/__tests__/interpolation.test.ts` | 插值格式校验测试 |
### 修改文件
| 文件路径 | 改动说明 |
|---------|---------|
| `package.json` | 添加 vue-i18n 依赖 |
| `src/main.ts` | 挂载 i18n 实例 |
| `src/pages/almanac-search/index.vue` | 替换硬编码中文为 `$t()` / `t()` |
| `src/pages/ziwei/index.vue` | 替换硬编码中文为 `$t()` / `t()` |
| `src/pages/fortune/index.vue` | 替换硬编码中文为 `$t()` / `t()` |
| `src/components/BottomNavigation/BottomNavigation.vue` | 替换硬编码中文 |
| `src/components/EmptyState/index.vue` | 移除中文默认值 |
| `src/components/SearchConditionPanel/index.vue` | 替换硬编码中文 |
| `src/components/SearchConditionItem/index.vue` | 替换硬编码中文(领域术语保留) |
| `src/components/SearchResultList/index.vue` | 替换硬编码中文 |
| `src/components/SearchResultCard/index.vue` | 替换硬编码中文 |
| `src/components/SortSwitcher/index.vue` | 替换硬编码中文 |
| `src/components/ExportPanel/index.vue` | 替换硬编码中文 |
| `src/components/SearchHistoryPanel/index.vue` | 替换硬编码中文 |
| `src/components/TemplatePanel/index.vue` | 替换硬编码中文 |
---
## Phase 1:基础设施 — 安装 vue-i18n + 创建核心模块
**文件:**
- 修改:`package.json`
- 创建:`src/locales/index.ts`
- 创建:`src/locales/zh-CN.ts`
- 修改:`src/main.ts`
### 任务 1.1:安装 vue-i18n 依赖
- [ ] **步骤 1:安装 vue-i18n**
运行:`cd everything-is-suitable-uniapp && npm install vue-i18n@9`
预期:`package.json` 中出现 `"vue-i18n": "^9.x.x"` 依赖
- [ ] **步骤 2:验证安装**
运行:`cd everything-is-suitable-uniapp && node -e "const v = require('vue-i18n/package.json'); console.log(v.version)"`
预期:输出 `9.x.x` 版本号
- [ ] **步骤 3Commit**
`cd everything-is-suitable-uniapp && git add package.json package-lock.json && git commit -m "chore: 安装 vue-i18n@9 依赖"`
### 任务 1.2:创建 zh-CN 主翻译源文件
- [ ] **步骤 1:创建 `src/locales/zh-CN.ts`** — 包含 common/nav/almanac/ziwei/fortune/search/template/export/history 共 9 个命名空间,约 80 个翻译键
- [ ] **步骤 2Commit**
`cd everything-is-suitable-uniapp && git add src/locales/zh-CN.ts && git commit -m "feat(i18n): 创建简体中文主翻译源 zh-CN.ts"`
### 任务 1.3:创建 locales/index.ts 核心模块
- [ ] **步骤 1:创建 `src/locales/index.ts`** — 包含 createI18n 实例、detectLocale 语言检测、setLocale 切换持久化、getLocale 获取当前语言、SUPPORTED_LOCALES 常量、localeNames 映射
- [ ] **步骤 2Commit**
`cd everything-is-suitable-uniapp && git add src/locales/index.ts && git commit -m "feat(i18n): 创建 vue-i18n 实例与语言检测/切换核心模块"`
### 任务 1.4:改造 main.ts 挂载 i18n
- [ ] **步骤 1:修改 `src/main.ts`** — 添加 `import { i18n } from './locales'``app.use(i18n)`
- [ ] **步骤 2:验证构建无报错**
运行:`cd everything-is-suitable-uniapp && npx vite build --mode development 2>&1 | head -20`
预期:构建成功,无模块解析错误
- [ ] **步骤 3Commit**
`cd everything-is-suitable-uniapp && git add src/main.ts && git commit -m "feat(i18n): 在 main.ts 中挂载 vue-i18n 实例"`
---
## Phase 2:翻译文件 — 创建 8 种非中文翻译
**依赖:** Phase 1
每个任务结构相同:创建翻译文件 → Commit。翻译键结构必须与 zh-CN.ts 完全一致。
### 任务 2.1:创建繁體中文翻译 zh-TW.ts
- [ ] **步骤 1:创建 `src/locales/zh-TW.ts`** — 简转繁,保持相同键结构
- [ ] **步骤 2Commit**
### 任务 2.2:创建英文翻译 en.ts
- [ ] **步骤 1:创建 `src/locales/en.ts`** — 领域术语保留拼音(如 Zi Wei Dou Shu
- [ ] **步骤 2Commit**
### 任务 2.3:创建日文翻译 ja.ts
- [ ] **步骤 1:创建 `src/locales/ja.ts`** — 命理术语保留汉字(如 紫微斗数、三方四正)
- [ ] **步骤 2Commit**
### 任务 2.4:创建韩文翻译 ko.ts
- [ ] **步骤 1:创建 `src/locales/ko.ts`** — 命理术语保留汉字(如 자미두수)
- [ ] **步骤 2Commit**
### 任务 2.5:创建德文翻译 de.ts
- [ ] **步骤 1:创建 `src/locales/de.ts`** — 领域术语保留拼音
- [ ] **步骤 2Commit**
### 任务 2.6:创建法文翻译 fr.ts
- [ ] **步骤 1:创建 `src/locales/fr.ts`** — 领域术语保留拼音
- [ ] **步骤 2Commit**
### 任务 2.7:创建西班牙文翻译 es.ts
- [ ] **步骤 1:创建 `src/locales/es.ts`** — 领域术语保留拼音
- [ ] **步骤 2Commit**
### 任务 2.8:创建葡萄牙文翻译 pt.ts
- [ ] **步骤 1:创建 `src/locales/pt.ts`** — 领域术语保留拼音
- [ ] **步骤 2Commit**
---
## Phase 3:页面改造 — 3 个页面 + pages.json
**依赖:** Phase 1, Phase 2
```mermaid
graph TD
P3A["任务 3.1: almanac-search"] --> P3D["任务 3.4: pages.json 确认"]
P3B["任务 3.2: ziwei"] --> P3D
P3C["任务 3.3: fortune"] --> P3D
```
### 任务 3.1:改造黄历搜索页 almanac-search/index.vue
**文件:** 修改 `src/pages/almanac-search/index.vue`
- [ ] **步骤 1:在 `<script setup>` 中添加 i18n 导入**`import { useI18n } from 'vue-i18n'` + `const { t } = useI18n()`
- [ ] **步骤 2:替换模板硬编码中文**`黄历搜索``{{ $t('almanac.pageTitle') }}``搜索中...``{{ $t('almanac.searching') }}``重试``{{ $t('common.retry') }}`
- [ ] **步骤 3:替换 script 硬编码中文**`error.value = t('almanac.keywordHint')``error.value = t('almanac.noResult')``error.value = t('search.addConditionHint')``error.value = t('almanac.searchFailed')`
- [ ] **步骤 4:添加 onShow 动态设置导航栏标题**`import { onShow } from '@dcloudio/uni-app'` + `onShow(() => { uni.setNavigationBarTitle({ title: t('almanac.pageTitle') }) })`
- [ ] **步骤 5:验证构建**
- [ ] **步骤 6Commit**
### 任务 3.2:改造紫微斗数页 ziwei/index.vue
**文件:** 修改 `src/pages/ziwei/index.vue`
- [ ] **步骤 1:添加 i18n 导入**
- [ ] **步骤 2:替换模板硬编码中文**`出生信息``{{ $t('ziwei.birthInfo') }}``出生日期``{{ $t('ziwei.birthDate') }}``请选择``{{ $t('ziwei.pleaseSelect') }}``出生时辰``{{ $t('ziwei.birthHour') }}``性别``{{ $t('ziwei.gender') }}``男``{{ $t('common.male') }}``女``{{ $t('common.female') }}``排盘中...``{{ $t('ziwei.generating') }}``排盘``{{ $t('ziwei.generate') }}``紫微斗数盘``{{ $t('ziwei.chartTitle') }}``三方四正分析``{{ $t('ziwei.sanFangAnalysis') }}``三方得分``{{ $t('ziwei.sanFangScore') }}``四正得分``{{ $t('ziwei.siZhengScore') }}``综合评价``{{ $t('ziwei.overallEval') }}``盘面概述``{{ $t('ziwei.summary') }}``分``{{ $t('ziwei.scoreUnit') }}`
- [ ] **步骤 3:添加 onShow 动态设置导航栏标题**
- [ ] **步骤 4:验证构建**
- [ ] **步骤 5Commit**
### 任务 3.3:改造运势分析页 fortune/index.vue
**文件:** 修改 `src/pages/fortune/index.vue`
- [ ] **步骤 1:添加 i18n 导入**
- [ ] **步骤 2:替换模板硬编码中文**`日运``{{ $t('fortune.daily') }}``月运``{{ $t('fortune.monthly') }}``选择日期``{{ $t('fortune.selectDate') }}``选择月份``{{ $t('fortune.selectMonth') }}``综合运势:``{{ $t('fortune.overallFortune') }}``分``{{ $t('ziwei.scoreUnit') }}``事业:``{{ $t('fortune.career') }}``财运:``{{ $t('fortune.wealth') }}``感情:``{{ $t('fortune.relationship') }}``健康:``{{ $t('fortune.health') }}``幸运色:``{{ $t('fortune.luckyColor') }}``幸运数字:``{{ $t('fortune.luckyNumber') }}``幸运方位:``{{ $t('fortune.luckyDirection') }}``请先在紫微斗数页面完成排盘``{{ $t('fortune.noChart') }}`
- [ ] **步骤 3:添加 onShow 动态设置导航栏标题**
- [ ] **步骤 4:验证构建**
- [ ] **步骤 5Commit**
### 任务 3.4pages.json 保持中文初始值
**文件:** `src/pages.json`(无需修改)
`pages.json` 中的 `navigationBarTitleText` 保持现有中文值作为首次渲染前的兜底,运行时由各页面 `onShow` 生命周期动态覆盖。
---
## Phase 4:组件改造 — 10 个组件
**依赖:** Phase 1, Phase 2
```mermaid
graph TD
C1["BottomNavigation"] --> CV["验证构建"]
C2["EmptyState"] --> CV
C3["SearchConditionPanel"] --> CV
C4["SearchConditionItem"] --> CV
C5["SearchResultList"] --> CV
C6["SearchResultCard"] --> CV
C7["SortSwitcher"] --> CV
C8["ExportPanel"] --> CV
C9["SearchHistoryPanel"] --> CV
C10["TemplatePanel"] --> CV
```
### 任务 4.1:改造 BottomNavigation
**文件:** 修改 `src/components/BottomNavigation/BottomNavigation.vue`
- [ ] **步骤 1:添加 i18n 导入**`import { useI18n } from 'vue-i18n'` + `const { t } = useI18n()`
- [ ] **步骤 2:将 tabs 数组中的 label 改为 computed**`label: t('nav.almanac')``label: t('nav.ziwei')``label: t('nav.fortune')`
- [ ] **步骤 3Commit**
### 任务 4.2:改造 EmptyState
**文件:** 修改 `src/components/EmptyState/index.vue`
- [ ] **步骤 1:移除 withDefaults 中的中文默认值** — 移除 `title``description``actionText` 的默认值,由调用方通过 `$t()` 传入
- [ ] **步骤 2Commit**
### 任务 4.3:改造 SearchConditionPanel
**文件:** 修改 `src/components/SearchConditionPanel/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`搜索条件``{{ $t('search.conditions') }}``添加条件``{{ $t('search.addCondition') }}``请添加搜索条件``{{ $t('search.addConditionEmpty') }}``搜索天数``{{ $t('search.days') }}``天``{{ $t('search.dayUnit') }}``搜索``{{ $t('common.search') }}`
- [ ] **步骤 2Commit**
### 任务 4.4:改造 SearchConditionItem
**文件:** 修改 `src/components/SearchConditionItem/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`事项``{{ $t('search.items') }}``逻辑运算符``{{ $t('search.operator') }}``排除条件``{{ $t('search.excludeCondition') }}`
- [ ] **步骤 2:将 typeOptions 和 operatorOptions 改为 computed**`{ label: t('search.typeSuitable'), value: 'suitable' }``{ label: t('search.typeUnsuitable'), value: 'unsuitable' }``{ label: t('search.operatorAnd'), value: 'and' }``{ label: t('search.operatorOr'), value: 'or' }`
- [ ] **步骤 3Commit** — 注意:`suitableActivities``avoidActivities` 数组保持中文硬编码(领域术语策略)
### 任务 4.5:改造 SearchResultList
**文件:** 修改 `src/components/SearchResultList/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`搜索结果``{{ $t('search.searchResult') }}``共{results.length}条结果``{{ $t('search.resultCount', { count: results.length }) }}``加载中...``{{ $t('common.loading') }}`
- [ ] **步骤 2Commit**
### 任务 4.6:改造 SearchResultCard
**文件:** 修改 `src/components/SearchResultCard/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`宜``{{ $t('search.typeSuitable') }}``忌``{{ $t('search.typeUnsuitable') }}``匹配度:``{{ $t('search.matchCount', { count: result.matchCount }) }}`
- [ ] **步骤 2Commit**
### 任务 4.7:改造 SortSwitcher
**文件:** 修改 `src/components/SortSwitcher/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`排序``{{ $t('search.sort') }}``日期``{{ $t('search.sortByDate') }}``匹配度``{{ $t('search.sortByMatch') }}`
- [ ] **步骤 2Commit**
### 任务 4.8:改造 ExportPanel
**文件:** 修改 `src/components/ExportPanel/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`导出搜索结果``{{ $t('export.title') }}``选择导出格式``{{ $t('export.selectFormat') }}``选择导出内容``{{ $t('export.selectContent') }}``取消``{{ $t('common.cancel') }}``导出``{{ $t('common.confirm') }}`
- [ ] **步骤 2:将 contentOptions 改为 computed**`{ label: t('export.date'), value: 'date' }``{ label: t('export.lunarDate'), value: 'lunarDate' }``{ label: t('export.weekday'), value: 'weekday' }``{ label: t('export.matchedItems'), value: 'matchedItems' }``{ label: t('export.matchCount'), value: 'matchCount' }`
- [ ] **步骤 3Commit**
### 任务 4.9:改造 SearchHistoryPanel
**文件:** 修改 `src/components/SearchHistoryPanel/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`搜索历史``{{ $t('history.title') }}``清除历史``{{ $t('history.clear') }}``确认清除``{{ $t('history.clearConfirm') }}``确定要清除所有搜索历史吗?``{{ $t('history.clearConfirmMsg') }}``暂无搜索历史``{{ $t('history.empty') }}``执行搜索后,搜索条件将显示在这里``{{ $t('history.emptyDesc') }}`
- [ ] **步骤 2:将 formatTime 函数中的中文改为 t() 调用**`刚刚``t('history.justNow')``小时前``t('history.hoursAgo', { count })``天前``t('history.daysAgo', { count })`
- [ ] **步骤 3Commit**
### 任务 4.10:改造 TemplatePanel
**文件:** 修改 `src/components/TemplatePanel/index.vue`
- [ ] **步骤 1:添加 i18n 导入并替换硬编码中文**`搜索模板``{{ $t('template.title') }}`、搜索框占位 → `{{ $t('template.searchPlaceholder') }}``暂无模板``{{ $t('template.empty') }}``没有找到符合条件的模板``{{ $t('template.emptyDesc') }}``全部``{{ $t('template.all') }}`
- [ ] **步骤 2Commit**
---
## Phase 5:测试与验证
**依赖:** Phase 3, Phase 4
```mermaid
graph TD
T1["任务 5.1: 翻译完整性测试"] --> T4["任务 5.4: 全量构建验证"]
T2["任务 5.2: 插值格式测试"] --> T4
T3["任务 5.3: 组件 i18n 集成测试"] --> T4
T4 --> T5["任务 5.5: 运行全量测试"]
T5 --> T6["任务 5.6: 最终 Commit"]
```
### 任务 5.1:创建翻译完整性测试
**文件:** 创建 `src/locales/__tests__/completeness.test.ts`
- [ ] **步骤 1:编写测试** — 校验所有 9 种语言的翻译键与 zh-CN 完全一致(嵌套 key 逐层比对)
- [ ] **步骤 2:运行测试验证通过**
运行:`cd everything-is-suitable-uniapp && npx vitest run src/locales/__tests__/completeness.test.ts`
预期:PASS
- [ ] **步骤 3Commit**
### 任务 5.2:创建插值格式测试
**文件:** 创建 `src/locales/__tests__/interpolation.test.ts`
- [ ] **步骤 1:编写测试** — 校验所有语言的插值变量名一致(如 `{count}` 在所有语言中均存在)
- [ ] **步骤 2:运行测试验证通过**
- [ ] **步骤 3Commit**
### 任务 5.3:创建组件 i18n 集成测试
**文件:** 修改现有组件测试文件
- [ ] **步骤 1:在 vitest 配置中添加 vue-i18n 插件** — 确保 `@vue/test-utils``mount` 能正确注入 i18n
- [ ] **步骤 2:为 BottomNavigation 编写 i18n 测试** — 验证渲染文本来自翻译键
- [ ] **步骤 3:运行测试验证通过**
- [ ] **步骤 4Commit**
### 任务 5.4:全量构建验证
- [ ] **步骤 1:运行 vite build**
运行:`cd everything-is-suitable-uniapp && npx vite build 2>&1 | tail -10`
预期:构建成功,无错误
- [ ] **步骤 2:运行 H5 开发服务器验证页面渲染**
运行:`cd everything-is-suitable-uniapp && npm run dev:h5`
预期:页面正常渲染,导航栏标题为中文(默认 locale),底部导航显示中文
### 任务 5.5:运行全量测试
- [ ] **步骤 1:运行所有测试**
运行:`cd everything-is-suitable-uniapp && npx vitest run`
预期:所有测试通过
### 任务 5.6:最终 Commit
- [ ] **步骤 1Commit**
`cd everything-is-suitable-uniapp && git add -A && git commit -m "feat(i18n): 完成 UniApp 国际化改造 — 9 种语言支持"`
---
## 自检清单
1. **规格覆盖度:** 设计规格中的 9 种语言 ✅、集中式翻译文件 ✅、vue-i18n ✅、领域术语保留 ✅、pages.json 运行时覆盖 ✅、3 个页面改造 ✅、10 个组件改造 ✅、翻译完整性测试 ✅
2. **占位符扫描:** 无 TODO/待定/后续实现 ✅
3. **类型一致性:** 所有翻译键名在 zh-CN.ts 定义,其他语言文件保持一致 ✅