国际化不是"把文案翻译一遍"这么简单。后台项目里,业务按钮、标题是一层,Element Plus 分页器、日期面板、空状态等内置文案是另一层,两者必须纳入同一条切换链路。本文先讲最小接入(vue-i18n + ElConfigProvider),再讲如何把切换能力收口成一个 LocaleSelect 组件,并厘清"哪些内容该进 i18n、哪些不该进"。
createI18n 初始化实例,并用 ElConfigProvider 让组件库文案联动LocaleSelect 统一入口,联动缓存、页面语义与组件库 localefallbackLocale 必配、key 不用整句中文当主键的习惯在后台系统里,需要翻译的内容天然分成两类:
vue-i18n 管理。locale 方案管理。只接 vue-i18n 会出现"业务文案切了,但分页器还是旧语言"的半中半英现象。真正完成基础接入,是两条链路同时接上:vue-i18n 负责你写的文案,Element Plus locale 负责组件内置文案,两者用同一个语言状态驱动。
Vue 3 项目推荐组合式 API 写法,关键是 legacy: false;fallbackLocale 必须配置,避免某个 key 缺失时直接显示空白路径。
import { createI18n } from 'vue-i18n'
export const i18n = createI18n({
legacy: false,
locale: 'zh-CN',
fallbackLocale: 'en-US',
messages: {
'zh-CN': { hello: '你好' },
'en-US': { hello: 'Hello' }
}
})组件侧用 useI18n() 解构 t 与 locale,修改 locale.value 即可响应式切换。Element Plus 文案则通过 ElConfigProvider 包裹应用并动态传入 locale 对象联动:
<template>
<el-config-provider :locale="elementLocale">
<h1>{{ t('hello') }}</h1>
</el-config-provider>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
const { locale } = useI18n()
const elementLocale = computed(() => (locale.value === 'zh-CN' ? zhCn : en))
</script>注意:useI18n() 必须在 <script setup> 顶层调用,写在条件分支或异步回调里会报错。
语言切换不要散落在每个页面各写一套。把它沉淀成头部工具区的 LocaleSelect 组件,核心不只是切换一个值,而是联动四件事:
function setAppLocale(locale: 'zh-CN' | 'en-US') {
i18n.global.locale.value = locale // 1. 业务文案
elementLocale.value = locale // 2. 组件库文案(经 computed 映射)
localStorage.setItem('app-locale', locale) // 3. 持久化
document.documentElement.lang = locale // 4. 页面语义同步
}四个职责封装成 setAppLocale 后,组件只负责触发,不关心底层如何加载:
<template>
<el-select v-model="currentLocale" size="small" style="width: 132px">
<el-option label="简体中文" value="zh-CN" />
<el-option label="English" value="en-US" />
</el-select>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { setAppLocale, type AppLocale } from '@/locales'
const { locale } = useI18n()
const currentLocale = computed<AppLocale>({
get: () => locale.value as AppLocale,
set: (value) => setAppLocale(value)
})
</script>默认语言策略:resolveDefaultLocale() 优先读 localStorage,没有缓存再回退到 navigator.language 兜底,最终以用户主动选择为准。
i18n 适合组件级、页面级、固定且结构稳定的短文案;以下场景不适合硬塞进语言包:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 按钮、菜单、表单、提示 | vue-i18n | 文本短、结构稳定 |
| Element Plus 内置文案 | 组件库 locale | 官方已提供语言包 |
| 官网、文档站、博客 | 多语言站点 + 翻译平台 | 内容长、更新频繁 |
| 日志、评论、接口返回 | 运行时翻译 API + 缓存 | 文本动态生成 |
另外两条纪律:不要用整句中文当 key 长期维护(app.title 比 首页标题 更稳定);长文档塞进前端语言包会让仓库膨胀、审校困难。
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 业务文案切了,分页器还是旧语言 | 只接了 vue-i18n,没接组件库 locale | 用 ElConfigProvider 包裹并动态传 locale |
| 某些文案切换后显示空白 | 没配 fallbackLocale | 提前设置兜底语言 |
| 刷新后语言又变回默认 | 没做持久化 | 写入 localStorage,启动优先读取 |
useI18n() 报错 | createI18n 没设 legacy: false | 显式 legacy: false 用组合式 API |
| 想翻译整本文档 | 用错了方案 | 长文档改用翻译平台或内容仓库流水线 |