Skip to content

国际化

加一句「保存成功」要动两个文件:中文词典落一个键,英文词典落一个键。视图里从头到尾只有 t('common.saveOk'),一个汉字都不写死。换语言于是变成换词典,而每句文案从此都欠着两笔债。

怎么接起来的

vue-i18n 靠内核包的 locales/index.ts 接进来:

ts
export const i18n = createI18n({
  legacy: false,
  locale: 'zh-CN',
  fallbackLocale: 'en-US',
  messages: {
    'zh-CN': zhCN as Messages,
    'en-US': enUS as Messages,
  },
})

/** 供非 setup 上下文(工具函数)使用的翻译器。 */
export const t = i18n.global.t

跑的是 Composition API 模式,legacy: false。默认语言 zh-CN。当前语言缺了哪个键,就回退到 en-US。比如 zh-CN.ts 漏加了某个键,界面不会报错也不会露出键名,而是原样显示 en-US.ts 里那句英文。内置文案就两个文件,locales/zh-CN.tslocales/en-US.ts。没按组件拆,也没做按需加载。

这两个文件在包里,应用改不到,也不必改。自己的文案放到应用的 src/locales/ext/<locale>/<模块>.tsmain.tscreateSmartAdmin({ locales: import.meta.glob('./locales/ext/*/*.ts', { eager: true }) }) 把它们交给内核,registerLocales 按命名空间深合并进词典。文件名就是顶层命名空间,改写某条内置文案也只动写到的那一个键。详见模板里该目录的 README

App.vue 把 Naive UI 自己的 locale 与 app 的语言保持同步,前者驱动的是日期选择器、分页这类内置文案:

ts
const naiveLocale = computed(() => (app.locale === 'en-US' ? enUS : zhCN))
const naiveDateLocale = computed(() => (app.locale === 'en-US' ? dateEnUS : dateZhCN))

watch(
  () => app.locale,
  (l) => {
    i18n.global.locale.value = l
  },
  { immediate: true },
)

所以有两套语言状态,都从同一个源联动,那个源就是 app.locale。一套是 vue-i18n 的 i18n.global.locale,驱动 t()。另一套是 Naive UI 的 n-config-provider :locale/:date-locale,驱动它内部组件的文案。

加一个翻译键

两个文案文件按功能命名空间分组:commonappsettingsloginmodulemenuuserconfigdictorgfilenoticerolelogprofileerrorrecycle 等等。两个真实例子:

ts
// zh-CN.ts
user: {
  title: '用户管理',
  account: '账号',
  deleteConfirm: '确定删除用户「{name}」?',
}

// en-US.ts
user: {
  title: 'Users',
  account: 'Account',
  deleteConfirm: 'Delete user "{name}"?',
}

加键步骤:

  1. 找准命名空间(确实是新功能才新开顶级命名空间)。
  2. 中英两份同时加,同一个点分路径,插值占位符({name}{count} …)也要对上。内核的键加在包里的 zh-CN.ts en-US.ts,应用的键加在 ext/zh-CN/<模块>.ts ext/en-US/<模块>.ts
  3. 组件内用 t('命名空间.键');非 setup 上下文(如 utils/ 里的函数)用导出的 t,应用从 smart-admin-web 导入它。

前端规范 把这条定成硬规矩:视图内所有可见文本走 t('...'),禁止硬编码中文、英文字面量。

运行时切换语言

语言是一条持久化的 UI 偏好,落在 stores/app.ts 里:

ts
export type Locale = 'zh-CN' | 'en-US'

state: () => ({ ...DEFAULTS, collapsed: false, locale: 'zh-CN' as Locale }),

actions: {
  setLocale(l: Locale) {
    this.locale = l
  },
}

调用 useAppStore().setLocale('en-US') 就能更新 app.locale。整个 store 经 Pinia 持久化插件存进 localStorage,键名是 app,所以刷新后语言选择还在。App.vue 里那个 watch(() => app.locale, ...) 随后把新值推进 i18n.global.locale.valuenaiveLocalenaiveDateLocale 这两个计算属性也从同一个 app.locale 派生。所以切一次语言,vue-i18n 的文案和 Naive UI 内置文案会在同一次渲染里一起变。

后端错误码怎么落成 error.* 里的文案,这页不讲。那套映射是数字码 → msgKey → 翻译键,它和响应契约绑在一起,见对接后端

基于 Apache License 2.0 开源