语言切换与基础国际化接入
概述
国际化不是"把文案翻译一遍"这么简单。后台项目里,业务按钮、标题是一层,Element Plus 分页器、日期面板、空状态等内置文案是另一层,两者必须纳入同一条切换链路。本文先讲最小接入(vue-i18n + ElConfigProvider),再讲如何把切换能力收口成一个 LocaleSelect 组件,并厘清"哪些内容该进 i18n、哪些不该进"。
学习目标
- 理解国际化的两层结构:业务文案(vue-i18n)与组件库内置文案(Element Plus locale)
- 能用
createI18n初始化实例,并用ElConfigProvider让组件库文案联动 - 把语言切换封装成
LocaleSelect统一入口,联动缓存、页面语义与组件库 locale - 区分 i18n 的适用边界:固定 UI 文案适合,长文档 / 动态内容不适合
- 养成
fallbackLocale必配、key 不用整句中文当主键的习惯
一、国际化是两层,不是一层
在后台系统里,需要翻译的内容天然分成两类:
- 业务文案:按钮、标题、提示语、表单标签——由
vue-i18n管理。 - 组件库内置文案:分页器、日期组件、空状态、确认弹窗——由 Element Plus 自己的
locale方案管理。
只接 vue-i18n 会出现"业务文案切了,但分页器还是旧语言"的半中半英现象。真正完成基础接入,是两条链路同时接上:vue-i18n 负责你写的文案,Element Plus locale 负责组件内置文案,两者用同一个语言状态驱动。
二、最小接入:createI18n + ElConfigProvider
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:统一切换入口与完整闭环
语言切换不要散落在每个页面各写一套。把它沉淀成头部工具区的 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
i18n 适合组件级、页面级、固定且结构稳定的短文案;以下场景不适合硬塞进语言包:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 按钮、菜单、表单、提示 | vue-i18n | 文本短、结构稳定 |
| Element Plus 内置文案 | 组件库 locale | 官方已提供语言包 |
| 官网、文档站、博客 | 多语言站点 + 翻译平台 | 内容长、更新频繁 |
| 日志、评论、接口返回 | 运行时翻译 API + 缓存 | 文本动态生成 |
另外两条纪律:不要用整句中文当 key 长期维护(app.title 比 首页标题 更稳定);长文档塞进前端语言包会让仓库膨胀、审校困难。
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 业务文案切了,分页器还是旧语言 | 只接了 vue-i18n,没接组件库 locale | 用 ElConfigProvider 包裹并动态传 locale |
| 某些文案切换后显示空白 | 没配 fallbackLocale | 提前设置兜底语言 |
| 刷新后语言又变回默认 | 没做持久化 | 写入 localStorage,启动优先读取 |
useI18n() 报错 | createI18n 没设 legacy: false | 显式 legacy: false 用组合式 API |
| 想翻译整本文档 | 用错了方案 | 长文档改用翻译平台或内容仓库流水线 |
延伸阅读
- 上一篇:主题切换与全屏控制 — 通用功能组件模块收尾
- 下一篇:国际化工程化与构建优化 — 语言包模块化、懒加载与构建优化
- 相关:自研组件库 — 组件库模块总览
- 相关:vue-i18n · Element Plus 国际化 · MDN Intl