19 KiB
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
整体架构流程
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
组件改造流程
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 依赖关系
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 版本号
- 步骤 3:Commit
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 个翻译键 -
步骤 2:Commit
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 映射 -
步骤 2:Commit
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
预期:构建成功,无模块解析错误
- 步骤 3:Commit
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— 简转繁,保持相同键结构 - 步骤 2:Commit
任务 2.2:创建英文翻译 en.ts
- 步骤 1:创建
src/locales/en.ts— 领域术语保留拼音(如 Zi Wei Dou Shu) - 步骤 2:Commit
任务 2.3:创建日文翻译 ja.ts
- 步骤 1:创建
src/locales/ja.ts— 命理术语保留汉字(如 紫微斗数、三方四正) - 步骤 2:Commit
任务 2.4:创建韩文翻译 ko.ts
- 步骤 1:创建
src/locales/ko.ts— 命理术语保留汉字(如 자미두수) - 步骤 2:Commit
任务 2.5:创建德文翻译 de.ts
- 步骤 1:创建
src/locales/de.ts— 领域术语保留拼音 - 步骤 2:Commit
任务 2.6:创建法文翻译 fr.ts
- 步骤 1:创建
src/locales/fr.ts— 领域术语保留拼音 - 步骤 2:Commit
任务 2.7:创建西班牙文翻译 es.ts
- 步骤 1:创建
src/locales/es.ts— 领域术语保留拼音 - 步骤 2:Commit
任务 2.8:创建葡萄牙文翻译 pt.ts
- 步骤 1:创建
src/locales/pt.ts— 领域术语保留拼音 - 步骤 2:Commit
Phase 3:页面改造 — 3 个页面 + pages.json
依赖: Phase 1, Phase 2
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:验证构建
- 步骤 6:Commit
任务 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:验证构建
- 步骤 5:Commit
任务 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:验证构建
- 步骤 5:Commit
任务 3.4:pages.json 保持中文初始值
文件: src/pages.json(无需修改)
pages.json 中的 navigationBarTitleText 保持现有中文值作为首次渲染前的兜底,运行时由各页面 onShow 生命周期动态覆盖。
Phase 4:组件改造 — 10 个组件
依赖: Phase 1, Phase 2
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') - 步骤 3:Commit
任务 4.2:改造 EmptyState
文件: 修改 src/components/EmptyState/index.vue
- 步骤 1:移除 withDefaults 中的中文默认值 — 移除
title、description、actionText的默认值,由调用方通过$t()传入 - 步骤 2:Commit
任务 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') }} - 步骤 2:Commit
任务 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' } - 步骤 3:Commit — 注意:
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') }} - 步骤 2:Commit
任务 4.6:改造 SearchResultCard
文件: 修改 src/components/SearchResultCard/index.vue
- 步骤 1:添加 i18n 导入并替换硬编码中文 —
宜→{{ $t('search.typeSuitable') }}、忌→{{ $t('search.typeUnsuitable') }}、匹配度:→{{ $t('search.matchCount', { count: result.matchCount }) }} - 步骤 2:Commit
任务 4.7:改造 SortSwitcher
文件: 修改 src/components/SortSwitcher/index.vue
- 步骤 1:添加 i18n 导入并替换硬编码中文 —
排序→{{ $t('search.sort') }}、日期→{{ $t('search.sortByDate') }}、匹配度→{{ $t('search.sortByMatch') }} - 步骤 2:Commit
任务 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' } - 步骤 3:Commit
任务 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 }) - 步骤 3:Commit
任务 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') }} - 步骤 2:Commit
Phase 5:测试与验证
依赖: Phase 3, Phase 4
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
- 步骤 3:Commit
任务 5.2:创建插值格式测试
文件: 创建 src/locales/__tests__/interpolation.test.ts
- 步骤 1:编写测试 — 校验所有语言的插值变量名一致(如
{count}在所有语言中均存在) - 步骤 2:运行测试验证通过
- 步骤 3:Commit
任务 5.3:创建组件 i18n 集成测试
文件: 修改现有组件测试文件
- 步骤 1:在 vitest 配置中添加 vue-i18n 插件 — 确保
@vue/test-utils的mount能正确注入 i18n - 步骤 2:为 BottomNavigation 编写 i18n 测试 — 验证渲染文本来自翻译键
- 步骤 3:运行测试验证通过
- 步骤 4:Commit
任务 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
- 步骤 1:Commit
cd everything-is-suitable-uniapp && git add -A && git commit -m "feat(i18n): 完成 UniApp 国际化改造 — 9 种语言支持"
自检清单
- 规格覆盖度: 设计规格中的 9 种语言 ✅、集中式翻译文件 ✅、vue-i18n ✅、领域术语保留 ✅、pages.json 运行时覆盖 ✅、3 个页面改造 ✅、10 个组件改造 ✅、翻译完整性测试 ✅
- 占位符扫描: 无 TODO/待定/后续实现 ✅
- 类型一致性: 所有翻译键名在 zh-CN.ts 定义,其他语言文件保持一致 ✅