国际化
唯一入口
src/lib/i18n.ts 是全部文案的唯一来源,同时导出中文资源供测试扫描:
ts
export const zhResources = {
confirm: '确定',
cancel: '取消',
total: '共 {{count}} 条',
// …
}
// en 的类型是 typeof zhResources:少一个 key 或多一个 key 都会编译失败
const en: typeof zhResources = {
confirm: 'Confirm',
cancel: 'Cancel',
total: '{{count}} items',
}1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
en: typeof zhResources 这条类型约束是刻意的:中英文结构必须一致,翻译漏项会在 pnpm typecheck 阶段暴露, 而不是等到运行时显示 key。
在组件里使用
tsx
import { useTranslation } from 'react-i18next'
export function Toolbar() {
const { t } = useTranslation()
return <Button>{t('confirm')}</Button>
}1
2
3
4
5
6
2
3
4
5
6
带变量:
tsx
t('total', { count: rows.length }) // 共 12 条
t('icons.copied', { name: 'Search' }) // 已复制 Search1
2
2
哪些文案可以不走语言包
| 场景 | 做法 |
|---|---|
| 菜单项 | label 可以写 i18n key,也可以先写中文(未登记的 key 会原样显示) |
| 组件内部固定文案 | 必须进语言包(例如表格的"密度""恢复默认") |
| 组件总览与文档里的示例文案 | 直接写中文,属于演示内容 |
| 业务页面文案 | 进语言包,或按业务域拆模块再合并进 resources |
语言切换
- 存储键:
storageKey('locale'),取值zh-CN/en。 - 切换时同步更新
<html lang>。 - 入口在顶栏「语言」快捷操作,以及侧栏底部用户菜单里的设置弹窗。
新增一种语言
- 在
src/lib/i18n.ts里加语言包对象,类型写成typeof zhResources,先把缺项补成中文占位。 - 在
resources里登记语言包。 - 在语言切换菜单的选项里加上该语言(
PreferencesMenu)。 resolveLocale逻辑里接受新的存储值(当前只区分en与其它)。- 跑
pnpm test:i18n-keys.test.ts会扫描代码里的字面量 key,确认没有"用了但没登记"的文案。
为什么要扫描 key
漏登记 key 的表现是页面上直接显示 sample.saved 这样的原始字符串,肉眼评审很难覆盖全站。 src/test/unit/i18n-keys.test.ts 用正则收集源码里所有 t('a.b') 形式的字面量,断言它们都存在于 zhResources, 新增文案时忘记登记会直接让单测失败。
bash
pnpm test -- i18n-keys1
动态拼接的 key(例如 t('sample.metric' + key))不在这条扫描范围内,需要人工确认;能写死就写死。