由于项目后台系统主要服务于新加坡、马来西亚等海外自提站业务,国际化(i18n)配置成为了必不可少的基础设施。在基于 Vue3 技术栈的开发中,我们需要处理的语言切换内容主要包含两个维度:
- 业务文案:开发者自行定义的文本内容,通过
vue-i18n-next 进行管理。
- 组件库文案:Naive UI 组件内部的提示语(如分页、日期选择器等),通过
n-config-provider 进行全局配置。
本文将详细介绍如何在 Vite + Vue3 + TypeScript + Pinia + Naive UI 的架构下,优雅地实现国际化配置。
一、配置 vue-i18n-next
1. 安装依赖
在 Vue3 环境中,我们需要安装 vue-i18n 的 v9 版本:
npm install vue-i18n@9
2. 初始化 i18n 实例
创建 src\lang\index.ts 文件,使用 createI18n 初始化实例。这里我们需要特别注意 legacy 模式的配置以及语言包的按需加载。
// src\lang\index.ts
import { createI18n } from 'vue-i18n'
import { LANG_VALUE } from '@/common/enum'
import zhHans from './zh-Hans'
import en from './en'
const i18n = createI18n({
legacy: false, // 必须设置为 false 以此支持 Composition API
locale: getLanguage(), // 获取初始化语言
messages: {
[LANG_VALUE.Zh]: zhHans,
[LANG_VALUE.En]: en
}
})
export default i18n
配置项深度解析:
// src\lang\index.ts
import { localCache } from '@/utils'
export function getLanguage() {
const chooseLanguage = localCache.getItem(LANGUAGE)
if (chooseLanguage) return chooseLanguage
// 如果没有缓存记录,则根据浏览器语言自动匹配
const language = navigator.language.toLowerCase()
const locales = [LANG_VALUE.En, LANG_VALUE.Zh]
for (const locale of locales) {
if (language.indexOf(locale) > -1) {
return locale
}
}
return LANG_VALUE.Zh
}
为了保持类型安全和代码维护性,建议使用 TypeScript 的枚举(enum)来管理语言常量:
// src\common\enum.ts
export enum LANG_VALUE {
En = 'en',
Zh = 'zh-Hans'
}
中文语言包示例:
// src\lang\zh-Hans.ts
export default {
baoguochuku: '包裹出库',
sousuo: '搜索'
}
英文语言包示例:
// src\lang\en.ts
export default {
baoguochuku: 'Outbound',
sousuo: 'Search'
}
3. 全局注册
在 main.ts 中引入并挂载 i18n 实例:
// src\main.ts
import i18n from './lang/index'
app.use(i18n)
二、在业务代码中使用
1. 在 Vue 组件中使用
Script Setup 环境
在 script setup 中,我们需要从 vue-i18n 导入 useI18n 钩子。
注意:在定义响应式数据(如路由菜单配置)时,如果直接赋值 t('key'),切换语言时内容不会更新。必须使用 Getter 函数形式 () => t('key') 来保持响应性。
<!-- src\components\Navigation\Navigation.vue -->
<script lang="ts" setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
// 错误写法:label: t(route.meta.title)
// 正确写法:label: () => t(route.meta.title)
const menuOptions: MenuOption[] = modulesRoutes.map(route => ({
label: () => t(route.meta.title),
// ...
}))
</script>
Template 模板环境
在模板中可以直接使用 $t,这得益于 globalInjection: true 的默认配置。
<template>
<n-button>
<slot name="submitBtnText">{{ $t('sousuo') }}</slot>
</n-button>
</template>
如果将 globalInjection 设为 false,模板中的 $t 调用将会报错。

2. 在非 Vue 文件中使用(如 TS 文件)
在纯 TypeScript 文件(如 API 拦截器、工具函数)中,无法使用 hooks,需要直接引入 i18n 实例,通过 i18n.global.t() 调用。
// src\service\request\index.ts
import i18n from '@/lang'
if (!axios.isCancel(error)) {
dialog.error({
title: i18n.global.t('tishi'),
// ...
})
}
三、配置 Naive UI 组件国际化
Naive UI 组件默认语言为英语。要切换为中文,需在根组件 App.vue 中使用 n-config-provider 组件,并动态绑定 locale(语言)和 date-locale(日期语言)。
1. Pinia 状态管理
我们将语言状态托管在 Pinia 中,以便全局管理和联动。
// src\stores\app.ts
import { defineStore } from 'pinia'
import { zhCN, dateZhCN } from 'naive-ui'
import { LANG_VALUE } from '@/common/enum'
import { getLanguage } from '@/lang'
interface IAppState {
language: string
// ...
}
const useAppStore = defineStore('app', {
state: (): IAppState => ({
language: getLanguage(),
// ...
}),
getters: {
// 根据当前语言状态返回对应的 Naive UI 语言包
locale(state) {
switch (state.language) {
case LANG_VALUE.En:
return null // Naive UI 默认是英文,传 null 即可
case LANG_VALUE.Zh:
return zhCN
default:
break
}
},
dateLocale(state) {
switch (state.language) {
case LANG_VALUE.En:
return null
case LANG_VALUE.Zh:
return dateZhCN
default:
break
}
}
},
// ...
})
export default useAppStore
2. 全局注入配置
在 App.vue 中,通过 storeToRefs 保持响应性,将 Pinia 计算出的 locale 对象传递给配置组件。
<!-- src\App.vue -->
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import useAppStore from '@/stores/app'
const appStore = useAppStore()
const { locale, dateLocale } = storeToRefs(appStore)
</script>
<template>
<n-config-provider
:locale="locale"
:date-locale="dateLocale"
>
<RouterView />
</n-config-provider>
</template>
四、开发语言切换控件
为了让用户方便地切换语言,我们结合 Naive UI 的 Popselect(弹出选择)和 Icon 组件封装一个切换控件。
1. 控件 UI 实现
<!-- src\components\LangSelect\LangSelect.vue -->
<template>
<div class="lang-select">
<n-popselect
v-model:value="value"
:options="options"
trigger="click"
@update:value="handleUpdateValue"
>
<n-icon>
<Language />
</n-icon>
</n-popselect>
</div>
</template>
<script lang="ts" setup>
import { Language } from '@vicons/ionicons5' // 引入图标
// ... 逻辑代码见下文
</script>
2. 逻辑处理
在逻辑层,我们需要处理以下几件事:
- 初始化时从 Pinia 获取当前语言回显。
- 切换时同步更新 Pinia、i18n 实例以及本地缓存。
<script lang="ts" setup>
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import useAppStore from '@/stores/app'
import { LANG_VALUE } from '@/common/enum'
import { setLocale } from '@/lang'
import type { SelectOption, SelectGroupOption } from 'naive-ui'
const ENGLISH = 'English'
const ZHONG_WEN = '中文'
const appStore = useAppStore()
const { language } = storeToRefs(appStore)
// 计算回显值
function getValue() {
switch (language.value) {
case LANG_VALUE.En: return ENGLISH
case LANG_VALUE.Zh: return ZHONG_WEN
default: return ZHONG_WEN
}
}
const value = ref<string | null>(getValue())
const options: Array<SelectOption | SelectGroupOption> = [
{ label: ENGLISH, value: ENGLISH },
{ label: ZHONG_WEN, value: ZHONG_WEN }
]
// 处理切换事件
function handleUpdateValue(val: string) {
let lang = LANG_VALUE.Zh
if (val === ENGLISH) {
lang = LANG_VALUE.En
}
// 调用统一的设置方法
setLocale(lang)
}
</script>
3. 统一设置方法
在 src\lang\index.ts 中封装 setLocale 方法,确保状态变更的原子性:
// src\lang\index.ts
export function setLocale(lang: langType) {
const appStore = useAppStore()
const { language } = storeToRefs(appStore)
i18n.global.locale.value = lang // 1. 修改 i18n 实例语言
language.value = lang // 2. 修改 Pinia 状态
localCache.setItem(LANGUAGE, lang) // 3. 持久化到缓存
}
五、消除控制台警告
在配置完成后,浏览器控制台可能会出现关于 vue-i18n 的 Feature Flags 警告。

这是因为我们使用了 ESM 构建版本,需要显式定义特性标志。在 vite.config.ts 中添加 define 配置即可解决:
export default defineConfig({
// ...
define: {
__VUE_I18N_FULL_INSTALL__: true,
__VUE_I18N_LEGACY_API__: false, // 关闭 Legacy API 支持,减少打包体积
__INTLIFY_PROD_DEVTOOLS__: false
}
})
六、最终效果
经过上述配置,我们不仅实现了业务文案的动态切换,也完美兼容了 UI 组件库的国际化。
