国际化
加一句「保存成功」要动两个文件:中文词典落一个键,英文词典落一个键。视图里从头到尾只有 t('common.saveOk'),一个汉字都不写死。换语言于是变成换词典,而每句文案从此都欠着两笔债。
怎么接起来的
vue-i18n 靠内核包的 locales/index.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.ts 和 locales/en-US.ts。没按组件拆,也没做按需加载。
这两个文件在包里,应用改不到,也不必改。自己的文案放到应用的 src/locales/ext/<locale>/<模块>.ts,main.ts 里 createSmartAdmin({ locales: import.meta.glob('./locales/ext/*/*.ts', { eager: true }) }) 把它们交给内核,registerLocales 按命名空间深合并进词典。文件名就是顶层命名空间,改写某条内置文案也只动写到的那一个键。详见模板里该目录的 README。
App.vue 把 Naive UI 自己的 locale 与 app 的语言保持同步,前者驱动的是日期选择器、分页这类内置文案:
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,驱动它内部组件的文案。
加一个翻译键
两个文案文件按功能命名空间分组:common、app、settings、login、module、menu、user、config、dict、org、file、notice、role、log、profile、error、recycle 等等。两个真实例子:
// zh-CN.ts
user: {
title: '用户管理',
account: '账号',
deleteConfirm: '确定删除用户「{name}」?',
}
// en-US.ts
user: {
title: 'Users',
account: 'Account',
deleteConfirm: 'Delete user "{name}"?',
}加键步骤:
- 找准命名空间(确实是新功能才新开顶级命名空间)。
- 中英两份同时加,同一个点分路径,插值占位符(
{name}、{count}…)也要对上。内核的键加在包里的zh-CN.ts和en-US.ts,应用的键加在ext/zh-CN/<模块>.ts和ext/en-US/<模块>.ts。 - 组件内用
t('命名空间.键');非 setup 上下文(如utils/里的函数)用导出的t,应用从smart-admin-web导入它。
前端规范 把这条定成硬规矩:视图内所有可见文本走 t('...'),禁止硬编码中文、英文字面量。
运行时切换语言
语言是一条持久化的 UI 偏好,落在 stores/app.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.value。naiveLocale 和 naiveDateLocale 这两个计算属性也从同一个 app.locale 派生。所以切一次语言,vue-i18n 的文案和 Naive UI 内置文案会在同一次渲染里一起变。
后端错误码怎么落成 error.* 里的文案,这页不讲。那套映射是数字码 → msgKey → 翻译键,它和响应契约绑在一起,见对接后端。