{T}

语言切换与基础国际化接入

概述

国际化不是"把文案翻译一遍"这么简单。后台项目里,业务按钮、标题是一层,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: falsefallbackLocale 必须配置,避免某个 key 缺失时直接显示空白路径。

ts
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() 解构 tlocale,修改 locale.value 即可响应式切换。Element Plus 文案则通过 ElConfigProvider 包裹应用并动态传入 locale 对象联动:

Vue SFC
<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 组件,核心不只是切换一个值,而是联动四件事:

ts
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 后,组件只负责触发,不关心底层如何加载:

Vue SFC
<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,没接组件库 localeElConfigProvider 包裹并动态传 locale
某些文案切换后显示空白没配 fallbackLocale提前设置兜底语言
刷新后语言又变回默认没做持久化写入 localStorage,启动优先读取
useI18n() 报错createI18n 没设 legacy: false显式 legacy: false 用组合式 API
想翻译整本文档用错了方案长文档改用翻译平台或内容仓库流水线

延伸阅读