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

10 KiB
Raw Blame History

UniApp 主应用国际化(i18n)设计规格

概述

everything-is-suitable-uniapp 引入 vue-i18n 国际化体系,支持 9 种语言,与落地页语言覆盖对齐。领域术语(天干地支、星曜名、宫位名等)保留中文原词,仅翻译 UI 文本。

决策记录

决策项 选择 理由
语言范围 9 种(zh-CN, zh-TW, en, ja, ko, de, fr, es, pt 与落地页对齐
领域术语策略 保留中文原词 命理术语缺乏行业标准翻译,多数命理 App 的做法
i18n 库 vue-i18n Vue 生态最成熟,功能完善
架构方案 集中式翻译文件(方案 A 项目规模适中,集中式便于审查覆盖率
语言切换 跟随系统语言,不提供 App 内切换 UI 与落地页行为一致

目录结构

everything-is-suitable-uniapp/src/
├── locales/
│   ├── index.ts          # vue-i18n 实例创建、语言检测/切换/持久化
│   ├── zh-CN.ts          # 简体中文(主翻译源)
│   ├── zh-TW.ts          # 繁體中文
│   ├── en.ts             # English
│   ├── ja.ts             # 日本語
│   ├── ko.ts             # 한국어
│   ├── de.ts             # Deutsch
│   ├── fr.ts             # Français
│   ├── es.ts             # Español
│   └── pt.ts             # Português
├── main.ts               # 挂载 i18n 实例
└── ...

locales/index.ts 核心职责

  1. 创建 vue-i18n 实例,注入所有 9 种语言的翻译
  2. 语言检测优先级:uni.getStorageSync('preferred-locale')uni.getSystemInfoSync().language → 默认 zh-CN
  3. 暴露切换函数 setLocale(locale),同步更新 uni.setStorageSync + vue-i18n 的 locale
  4. 暴露当前语言 getLocale()
  5. 回退机制:目标语言缺失某 key 时,回退到 zh-CN

main.ts 改造

import { createSSRApp } from 'vue'
import App from './App.vue'
import { i18n } from './locales'

export function createApp() {
  const app = createSSRApp(App)
  app.use(i18n)
  return { app }
}

翻译键命名空间

按功能模块划分,扁平 key 结构(与落地页 i18n.js 一致):

命名空间 覆盖范围 示例 key
nav.* 底部导航栏 nav.almanacnav.ziweinav.fortune
almanac.* 黄历搜索页及相关组件 almanac.pageTitlealmanac.searching
ziwei.* 紫微斗数页 ziwei.birthInfoziwei.generating
fortune.* 运势分析页 fortune.dailyfortune.monthly
search.* 搜索相关组件 search.conditionssearch.addCondition
export.* 导出面板 export.titleexport.format
history.* 搜索历史 history.titlehistory.clear
template.* 搜索模板 template.titletemplate.search
common.* 通用文本 common.retrycommon.cancel

领域术语策略

保留中文原词,不纳入翻译体系:

  • enums.ts 中的 HeavenlyStem.name()EarthlyBranch.name()MajorStar.name()PalaceType.name() 等保持不变
  • SearchConditionItem 中的黄历活动列表(suitableActivitiesavoidActivities)保持中文硬编码
  • templateService.ts 中的模板名/描述/分类保持中文
  • 这些领域数据未来如需国际化,可作为独立增量迭代

组件改造模式

模式 1:模板中的静态文本

<!-- 改造前 -->
<text class="section-title">出生信息</text>

<!-- 改造后 -->
<text class="section-title">{{ $t('ziwei.birthInfo') }}</text>

模式 2:动态拼接文本(插值)

<!-- 改造前 -->
<Typography variant="caption">共{{ results.length }}条结果</Typography>

<!-- 改造后 -->
<Typography variant="caption">{{ $t('search.resultCount', { count: results.length }) }}</Typography>

翻译文件:

// zh-CN
'search.resultCount': '共{count}条结果',
// en
'search.resultCount': '{count} results',

模式 3Script 中的中文文本

// 改造前
error.value = '请至少添加一个搜索条件'

// 改造后
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
error.value = t('search.addConditionHint')

pages.json 国际化

navigationBarTitleText 不支持 vue-i18n 动态翻译,改用运行时设置。pages.json 中保留中文初始值(作为首次渲染前的兜底),由 onShow 生命周期动态覆盖为当前语言文本:

{
  "pages": [
    {
      "path": "pages/almanac-search/index",
      "style": { "navigationBarTitleText": "黄历搜索" }
    }
  ]
}

各页面 onShow 生命周期中动态设置:

import { useI18n } from 'vue-i18n'
const { t } = useI18n()

onShow(() => {
  uni.setNavigationBarTitle({ title: t('almanac.pageTitle') })
})

EmptyState 组件默认值处理

移除中文默认值,由调用方显式传入翻译文本:

// 改造前
withDefaults(defineProps<Props>(), {
  title: '暂无数据',
  description: '没有找到相关结果',
  actionText: '重新搜索',
})

// 改造后
withDefaults(defineProps<Props>(), {
  icon: 'empty',
  showAction: true,
  size: 'medium'
})

调用方:

<EmptyState
  :title="$t('common.noData')"
  :description="$t('common.noResult')"
  :actionText="$t('common.retry')"
  :showAction="hasSearched"
  @action="performSearch"
/>

翻译键全量清单(约 80 条)

common.retry              = 重试
common.cancel             = 取消
common.confirm            = 确认
common.loading            = 加载中...
common.noData             = 暂无数据
common.noResult           = 没有找到相关结果
common.search             = 搜索
common.male               = 男
common.female             = 女

nav.almanac               = 黄历
nav.ziwei                 = 紫微
nav.fortune               = 运势

almanac.pageTitle         = 黄历搜索
almanac.searching         = 搜索中...
almanac.searchFailed      = 搜索失败,请稍后重试
almanac.noResult          = 没有找到符合条件的结果
almanac.keywordHint       = 请输入搜索关键词

ziwei.pageTitle           = 紫微斗数
ziwei.birthInfo           = 出生信息
ziwei.birthDate           = 出生日期
ziwei.birthHour           = 出生时辰
ziwei.gender              = 性别
ziwei.pleaseSelect        = 请选择
ziwei.generating          = 排盘中...
ziwei.generate            = 排盘
ziwei.chartTitle          = 紫微斗数盘
ziwei.sanFangAnalysis     = 三方四正分析
ziwei.sanFangScore        = 三方得分
ziwei.siZhengScore        = 四正得分
ziwei.overallEval         = 综合评价
ziwei.summary             = 盘面概述
ziwei.scoreUnit           = 分

fortune.pageTitle         = 运势分析
fortune.daily             = 日运
fortune.monthly           = 月运
fortune.selectDate        = 选择日期
fortune.selectMonth       = 选择月份
fortune.overallFortune    = 综合运势
fortune.career            = 事业
fortune.wealth            = 财运
fortune.relationship      = 感情
fortune.health            = 健康
fortune.luckyColor        = 幸运色
fortune.luckyNumber       = 幸运数字
fortune.luckyDirection    = 幸运方位
fortune.noChart           = 请先在紫微斗数页面完成排盘

search.conditions         = 搜索条件
search.addCondition       = 添加条件
search.addConditionHint   = 请至少添加一个搜索条件
search.addConditionEmpty  = 请添加搜索条件
search.days               = 搜索天数
search.dayUnit            = 天
search.searchResult       = 搜索结果
search.resultCount        = 共{count}条结果
search.matchCount         = 匹配度: {count}
search.sort               = 排序
search.sortByDate         = 日期
search.sortByMatch        = 匹配度
search.typeSuitable       = 宜
search.typeUnsuitable     = 忌
search.operatorAnd        = 与(AND
search.operatorOr         = 或(OR
search.excludeCondition   = 排除条件
search.items              = 事项
search.operator           = 逻辑运算符

template.title            = 搜索模板
template.searchPlaceholder= 搜索模板
template.empty            = 暂无模板
template.emptyDesc        = 没有找到符合条件的模板
template.all              = 全部

export.title              = 导出搜索结果
export.selectFormat       = 选择导出格式
export.selectContent      = 选择导出内容
export.date               = 日期
export.lunarDate          = 农历日期
export.weekday            = 星期
export.matchedItems       = 匹配事项
export.matchCount         = 匹配度

history.title             = 搜索历史
history.clear             = 清除历史
history.clearConfirm      = 确认清除
history.clearConfirmMsg   = 确定要清除所有搜索历史吗?
history.empty             = 暂无搜索历史
history.emptyDesc         = 执行搜索后,搜索条件将显示在这里
history.justNow           = 刚刚
history.hoursAgo          = {count}小时前
history.daysAgo           = {count}天前

测试策略

1. 单元测试 — 翻译完整性校验

新增 locales/__tests__/completeness.test.ts

  • 校验所有 8 种非中文语言的翻译文件,key 集合必须与 zh-CN 相等
  • 发现缺失 key 时测试失败,输出具体缺失的 key 列表

2. 单元测试 — 插值格式校验

  • 校验所有使用插值({count} 等)的 key,在每种语言中插值占位符名称一致

3. 组件测试 — 翻译渲染验证

对关键组件(BottomNavigation、SearchConditionPanel、EmptyState)编写测试:

  • 切换 locale 后,验证渲染文本对应正确语言
  • 验证插值参数正确替换

4. E2E 测试 — 语言切换端到端验证

  • 在 H5 模式下模拟不同 navigator.language,验证页面标题和关键文本正确切换
  • 验证 preferred-locale 持久化后重启仍生效

不在范围内

  • 领域术语的国际化(天干地支、星曜名、宫位名、黄历活动名)
  • templateService 中模板名/描述/分类的国际化
  • App 内语言切换 UI
  • 落地页 i18n.js 的改动(已独立支持 9 种语言)