支持语言
应对业务需求,未来有出海的打算。现前端需实现国际化切换【简体中文、繁体、英文、日文】,目前暂定这几种,依据后续业务需求变化。
功能点
1、默认语言
方案一:根据用户识别(非中国匹配英文),默认国际化设置。
方案二:默认设置为中文。
方案三:获取浏览器设置。
在前端实现国际化时,若需在未请求或手动切换语言时自动设置默认语言,可通过以下步骤实现,结合浏览器语言检测与国际化库配置:
a、获取浏览器语言偏好
通过JavaScript读取浏览器的语言设置,通常从navigator.language或navigator.languages获取:
const browserLang = navigator.language || navigator.userLanguage; // 获取浏览器首选语言(如 'zh-CN')
此方法依赖浏览器发送的Accept-Language HTTP头,可覆盖用户系统设置
b、配置国际化库默认语言
以vue-i18n为例,在初始化时动态设置locale:
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import zh from './locales/zh.json';
const i18n = createI18n({
locale: browserLang.split('-')[0], // 取语言部分(如 'zh-CN' → 'zh')
fallbackLocale: 'en', // 回退语言
messages: { en, zh }
});
// 若检测到语言无资源文件,强制回退
if (!i18n.global.availableLocales.includes(i18n.locale)) {
i18n.global.locale = 'en';
}
此逻辑确保即使浏览器语言配置异常,应用仍能正常运行。
c、动态加载语言资源
通过异步请求加载对应语言文件,避免阻塞页面渲染:
async function loadLocaleMessages(locale) {
try {
const messages = await import(`@/locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages.default);
} catch (error) {
console.warn(`语言文件加载失败: ${locale}`);
}
}
// 初始化时加载
loadLocaleMessages(i18n.global.locale);
此方法支持懒加载,优化首屏加载速度。
d、处理多语言混合场景
对于复杂场景(如用户手动修改系统语言),可结合localStorage持久化用户选择:
// 尝试读取用户历史选择
const savedLang = localStorage.getItem('appLanguage');
if (savedLang) {
i18n.global.locale = savedLang;
loadLocaleMessages(savedLang);
}
// 监听语言切换事件
i18n.global.onLocaleChanged((newLocale) => {
localStorage.setItem('appLanguage', newLocale);
});
此方案兼顾用户体验与数据持久化。
e、SEO优化建议
在HTML中添加lang属性,确保搜索引擎正确识别页面语言:
<html :lang="$i18n.locale">
<!-- 页面内容 -->
</html>
同时使用hreflang标签声明多语言版本。
2、国际化页面配置生效
为可自己到系统中配置国际化,赋权修改【暂不采用】
缺点:配置即生效国际化,样式不可控、需要做好文案的精简控制
优点:不用发版
3、前端定制国际化
目前方案
缺点:每次修改国际化,需要发版
优点:美观、样式兼容性问题少
待确认:登录页选择后,都是一套语言,还是说在内部登录后也能修改,依据业务需求
4、待办
国际化需求需整改几个问题
问题一:图片上带文字的设计不适配,需要给底图,前端写文字的方案才能做国际化,或者说需要给出对应语言的设计图片。
问题二:第三方插件的国际化存在不适配,需要考虑第三方插件适配。
问题三:后端接口返回的下拉数据如何做国际化,前端或后端都可以做映射。
接入国际化前端步骤
引入国际化语言 vue-i18n 方案。
全局设置 i18n 文件夹,区分中文、英文、日文,繁体可拓展其他;页面国际化统一放入 i18n 目录下,每次添加前先查找有无、再新增。
1、安装
npm install vue-i18n
2、配置
在src目录下,创建 i18n 文件夹,含 lang 和 pages 文件夹及 index.js
重点解读 index.js 文件配置
import { createI18n } from 'vue-i18n'
import pinia from '@/store/createPinia'
import { storeToRefs } from 'pinia'
import { useThemeConfig } from '@/store/modules/themeConfig'
// import { info } from '@/api/admin/i18n'; // TODO 请求配置来的国际化
// TODO 不使用分包,直接引入本地的国际化
// import enUS from './lang/en'
// import zhCN from './lang/zh-CN'
// import zhTW from './lang/zh-TW'
// import JA from './lang/ja'
/**
* 说明:
* 须在 views 下新建文件夹(建议 `要国际化界面目录` 与 `i18n 目录` 相同,方便查找),
* 注意国际化定义的字段,不要与原有的定义字段相同。
* 1、/src/i18n/lang 下的 js 为框架的国际化内容
* 2、/src/i18n/pages 下的 js 为各界面的国际化内容
*/
// element plus 自带国际化
import enLocale from 'element-plus/es/locale/lang/en'
import zhcnLocale from 'element-plus/es/locale/lang/zh-cn'
import zhtwLocale from 'element-plus/es/locale/lang/zh-tw'
import jaLocale from 'element-plus/es/locale/lang/ja'
const element = {
'en': enLocale,
'zh-CN': zhcnLocale,
'zh-TW': zhtwLocale,
'ja': jaLocale
}
// 定义变量内容
const messages = {}
// 语言包文件分包
const itemize = {
'en': [], // 英文
'zh-CN': [], // 简体中文
'zh-TW': [], // 繁体
'ja': [] // 日文
}
const modules = import.meta.glob('./**/*.js', { eager: true })
const pages = import.meta.glob('./../../**/**/**/i18n/*.js', { eager: true })
const regexp = /(\S+)/(\S+).js/
// 对自动引入的 modules 进行分类 en、zh-cn、zh-tw
for (const path in modules) {
const key = path.match(regexp)
if (itemize[key[2]]) {
itemize[key[2]].push(modules[path].default)
} else {
itemize[key[2]] = modules[path]
}
}
for (const path in pages) {
const key = path.match(regexp)
if (itemize[key[2]]) {
itemize[key[2]].push(pages[path].default)
} else {
itemize[key[2]] = pages[path]
}
}
// 合并数组对象(非标准数组对象,数组中对象的每项 key、value 都不同)
const mergeArrObj = (list, key) => {
let obj = {}
list[key].forEach(item => obj = Object.assign({}, obj, item))
return obj
}
for (const key in itemize) {
messages[key] = {
name: key,
el: element[key].el,
...mergeArrObj(itemize, key)
}
}
// 读取 pinia 默认语言
const themeConfigStore = useThemeConfig(pinia)
const { themeConfig } = storeToRefs(themeConfigStore)
const locale = themeConfig.value.globalI18n || 'zh-CN'
// 导出语言国际化
export const i18n = createI18n({
legacy: false, // 是否使用传统API模式,设为false指定使用Composition API模式(vue3设为false)
silentTranslationWarn: true, // 为每个组件注入全局属性和函数
missingWarn: false, // 抑制本地化失败警告
silentFallbackWarn: true, // 抑制回退失败警告
fallbackWarn: false, // 回退警告
locale: locale, // 语言
fallbackLocale: zhcnLocale.name, // 回退语言
messages
// messages: {
// 'en': enUS,
// 'zh-CN': zhCN,
// 'zh-TW': zhTW,
// 'ja': JA
// },// 语言
})
export function useCustomerI18n(app) {
app.use(i18n)
}
// TODO 获取用户配置的语言包
// await fetchI18n();
// // 远程获取i18n
// async function fetchI18n() {
// if (__MOCK_MODE__ != 'mock') {
// const infoI18n = await info();
// const messageLocal:any = {};
// const itemizeLocal = { en: [] as any[], 'zh-cn': [] as any[] };
// itemizeLocal['zh-CN'].push(...infoI18n.data.data['zh-CN']);
// itemizeLocal.en.push(...infoI18n.data.data.en);
// for (const key in itemizeLocal) {
// messageLocal[key] = {
// name: key,
// ...mergeArrObj(itemizeLocal, key),
// };
// }
// i18n.global.mergeLocaleMessage('zh-CN', messageLocal['zh-CN']);
// i18n.global.mergeLocaleMessage('zh-TW', messageLocal['zh-TW']);
// i18n.global.mergeLocaleMessage('en', messageLocal['en']);
// i18n.global.mergeLocaleMessage('ja',messageLocal['ja']);
// i18n.global.locale.value = themeConfig.value.globalI18n;
// }
// }
也可以直接在 views 文件夹下的某个页面中直接配置。
方式为直接创建 i18n 目录,然后新建语言配置文件
3、使用
在 main.js 中挂载 i18n
在 App.vue 文件中,增加国际化注入 locale
4、详细说明
创建的切换国际化语言的组件
引用组件位置:
使用方式上图可以查看
<template>
<div class="lang-i18n">
<el-dropdown placement="top-end">
<el-icon><Compass /></el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="item in langOptions" @click="changeLang(item)">{{ item.label }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
{{ langVal }}
</div>
</template>
<script setup>
import { Compass } from '@element-plus/icons-vue'
import { useI18n } from 'vue-i18n'
import { useThemeConfig } from '@/store/modules/themeConfig'
const { messages, locale } = useI18n()
const useThemeConfigStore = useThemeConfig()
const langVal = ref(locale.value)
const langOptions = ref([
{ key: 'zh-CN', label: '简体中文' },
{ key: 'zh-TW', label: '繁体' },
{ key: 'en', label: '英文 ' },
{ key: 'ja', label: '日文' }
])
const changeLang = (item) => {
// 设置locale语言
langVal.value = item.label
locale.value = item.key
useThemeConfigStore.SET_LANG(item.key)
}
</script>
<style lang="scss" scoped>
.lang-i18n {
float: right;
position: relative;
top: -2rem;
right: 2rem;
height: 3.6875rem;
line-height: 1rem;
}
</style>
详细使用可参考指南:kazupon.github.io/vue-i18n/zh…
以上网站有说明具体使用及复杂场景。
【提效】扩展插件
vite-auto-i18n-plugin
自动翻译插件。节约前期如果需要人工一条一条去翻译的工作时间。
特点:
- 一键翻译
- 无需改动业务代码
- 支持新增语言自动补全配置
- 支持翻译多国语言
npm install vite-auto-i18n-plugin --save-dev
配置
vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vitePluginAutoI18n, { YoudaoTranslator } from 'vite-auto-i18n-plugin'
import path from 'path'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vitePluginAutoI18n({
targetLangList: ['zh-cn', 'zh-TW', 'en', 'ja'],
translator: new YoudaoTranslator({
appId: 'xxxxxx', // yangsoso 有道翻译的 appId 和 appKey
appKey: 'xxxxxx',
}),
excludedPath: ['node_modules'], // 排除第三方库
generate: true, // 生成词条配置文件
output: true, // 生成翻译文件
}),
],
resolve: {
alias: {
'~': path.resolve(__dirname, './'),
'@': path.resolve(__dirname, './src'),
},
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json', '.vue'],
},
build: {
outDir: 'gh-digital-energy-commonui', //输出文件名
lib: {
entry: path.resolve(__dirname, './src/components/index.js'), //指定组件编译入口文件
name: 'index',
fileName: 'index',
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external: ['vue'],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue',
},
},
},
},
})
有道翻译申请的一个appId 和 appKey ai.youdao.com/console/#/a…
翻译器配置(可选)
1. 使用谷歌翻译(默认)
translator: new GoogleTranslator({
proxyOption: { // 代理配置(可选)
host: '127.0.0.1',
port: 8899,
headers: { 'User-Agent': 'Node' }
}
})
2. 使用有道翻译(推荐)
translator: new YoudaoTranslator({
appId: 'your_app_id',
appKey: 'your_app_key'
})
main.js
import '../lang/index' // 必须放在首位
import { createApp } from 'vue'
import App from './App.vue'
// 样式文件导入
import './assets/style/index.scss'
import ElementComponent from './components/index.js' //导入各个组件
import { useCustomerI18n } from '@/i18n/index'
import { useElementPlus } from '@/plugins/element-plus'
import './assets/style/global.scss' // 引入全局样式
//
// 渲染页面
function renderApp() {
const app = createApp(App)
ElementComponent(app)
useElementPlus(app)
useCustomerI18n(app)
app.mount('#app')
}
renderApp()
配置项
| 参数 | 类型 | 必选 | 默认值 | 描述 |
|---|---|---|---|---|
| translateKey | string | ❌ | $t | 插件转换后切换语言的默认函数 |
| excludedCall | string[] | ❌ | ["$i8n", "require", "$$i8n", "console.log", "$t"] | 标记不会翻译的调用函数 |
| excludedPattern | RegExp[] | ❌ | [/.\w+$/] | 标记不会翻译的字符串 |
| excludedPath | RegExp[] | ❌ | [] | 不翻译指定目录下文件 |
| includePath | RegExp[] | ❌ | [/src//] | 翻译指定目录下文件 |
| globalPath | string | ❌ | ./lang | 翻译配置文件生成位置 |
| distPath | string | ✅ | '' | 打包后生成文件的位置 比如 ./dist/assets(用于将翻译配置注入打包文件) |
| distKey | string | ✅ | '' | 打包后生成文件的主文件名称,比如index.xxx 默认是index(用于将翻译配置注入打包文件) |
| namespace | string | ✅ | '' | 线上区分当前项目间的翻译配置 |
| originLang | string | ❌ | 'zh-cn' | 源语言(基于该语言翻译成其他语言) |
| targetLangList | string[] | ❌ | ['en'] | 目标语言(原始语言将被翻译成的语言类型,接受一个数组,支持多种语言)支持语言类型(langFile) |
| buildToDist | Boolean | ❌ | false | 是否将翻译配置打包到主包中 |
解释buildToDist:
在vite环境中执行插件后会生成翻译配置文件。但是如果您直接构建它,项目会先生成翻译配置文件。但翻译配置文件不会立即打包到主包中,您可能需要再次打包。
因此,提供了buildToDist选项,当创建翻译配置文件时,它将主动将翻译配置文件打包进主包,缺陷是您的打包文件可能有两份翻译配置文件
翻译文件生成
- 插件会自动生成
lang/index.js和对应语言的 JSON 文件(如zh-CN.json),需先执行npm run build完整构建 - 检查生成的 JSON 文件是否包含所有翻译键值对,键名应为哈希值(如
"0"),确保相同文本生成相同键
查看生产的翻译结果,进入lang\index.json,支持重新设置翻译结果,以设置后的为准。
看起来有些翻译,还是有一些偏差。看项目业务是否需要很专业的翻译,个人觉得需要高精度的翻译,可以利用插件首次给出结构,但是后期再校验一遍,节约前期投入能力,针对想快速给国际化的项目比较友好。
验证翻译
在切换语言的地方切换语言验证。
// 切换语言
const changeLanguage = (lang) => {
locale.value = lang
localStorage.setItem('lang', lang) // 保存语言偏好
window.location.reload()
}
查看效果,成功。
deepseek还给出了一些专门的测试插件。可自行安装验证。
推荐测试工具
| 工具 | 适用场景 | 优势 |
|---|---|---|
| Cypress | 端到端测试 | 支持多语言切换自动化验证 |
| BrowserSync | 实时刷新与多设备同步测试 | 自动刷新页面,提升测试效率 |
| i18n-check | 翻译文件完整性检查 | 自动检测缺失键值对和重复键 |
总结
项目业务是否需要很专业的翻译,个人觉得需要高精度的翻译,可以利用插件首次给出结构,但是后期再校验一遍,节约前期投入能力,针对想快速给国际化的项目比较友好。
-
首次构建白屏
- 原因:插件解析代码生成翻译文件时阻塞构建流程。
- 解决方案:先执行
npm run build生成完整文件,再启动开发服务器
-
翻译键冲突
- 现象:不同文本生成相同哈希键导致内容错乱。
- 解决方案:手动修改 JSON 文件中的键名,确保唯一性
-
第三方组件翻译缺失
-
现象:UI 库(如 Element Plus)的文本未被翻译。
-
解决方案:
- 在
vite.config.js中配置excludedPath: ['node_modules']排除第三方库 - 手动提取组件文本并添加到翻译文件。
- 在
-
该文档仅用于学习分享。不涉及商业用途。