一、下载il8n插件。
目前通过npm install vue-il8n下载的il8n版本是无法支持vue3.0,因此要使用npm install vue-i18n@next 来获取最新的版本。
二、引入组件
适用于vue3 的vuei18文档在这里,目前还没有中文版。如果大家看到的中文版本应该是适用vue2.x的版本。
vue3+以上的vue-i18n文档
vue3+以上的使用方式改变了。可能是为了跟vue3保持一致,所以作者也搞了个createI18n,
所以在入口文件main.js中这么使用。
import { createApp } from 'vue'
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
// something vue-i18n options here ...
})
const app = createApp({
// something vue options here ...
})
app.use(i18n)
app.mount('#app')
注意:
如果以上引入报错的话,请将import { createI18n } from 'vue-i18n'
改一下。
import { createI18n } from 'vue-i18n/index'
在源码中可以找到createI18n 在index.js里面。一般应该不会报错。默认都会找index的。
三、最简单的使用方式
// 1. Ready translated locale messages
// The structure of the locale message is the hierarchical object structure with each locale as the top property
const messages = {
en: {
message: {
hello: 'hello world'
}
},
ja: {
message: {
hello: 'こんにちは、世界'
}
}
}
// 2. Create i18n instance with options
const i18n = VueI18n.createI18n({
locale: 'ja', // set locale
fallbackLocale: 'en', // set fallback locale
messages, // set locale messages
// If you need to specify other options, you can set other options
// ...
})
// 3. Create a vue root instance
const app = Vue.createApp({
// set something options
// ...
})
// 4. Install i18n instance to make the whole app i18n-aware
app.use(i18n)
// 5. Mount
app.mount('#app')
// Now the app has started!
createI18n这个函数最主要的就是需要一个messages 对象。即语言翻译对象。不过以上这么简单的使用方式,显然这么使用肯定不太适合我们大型项目。我们接下来要基于以上继续进行封装。
四、集成element-plus
之前element-plus文档上有兼容vue-i18n 9.x版本的使用文档,目前找不到了。只在别人博客里面找到了部分内容。element-plus兼容vue-i18n 9.x
主要就是兼容element-plus去构建vue-i18n所需要的message
关键代码:
import enLocale from 'element-plus/lib/locale/lang/en'
import zhLocale from 'element-plus/lib/locale/lang/zh-cn'
const messages = {
[enLocale.name]: {
// el 这个属性很关键,一定要保证有这个属性,
el: enLocale.el,
// 定义您自己的字典,但是请不要和 `el` 重复,这样会导致 ElementPlus 内部组件的翻译失效.
message: {
hello: 'hello world',
},
},
[zhLocale.name]: {
el: zhLocale.el,
// 定义您自己的字典,但是请不要和 `el` 重复,这样会导致 ElementPlus 内部组件的翻译失效.
message: {
hello: '你好,世界',
},
},
}
五、基于vue-i18n封装、抽离、将语言文件按模块区分,适应大型项目。减少冲突
先思考一下要实现什么样的效果
1.需要兼容element-plus
2.语言文件按照模块来区分,将语言文件抽离出来,不写在一个文件里面
3.每定义一种语言,不需要再手动在代码中引入语言文件
...奔着这些目的我们来看下面我是如何实现的吧
1.首先先看下我项目里面的目录结构吧
先看看i18n目录下的index
/**
* author: fzs 2021-6-30
*/
import { createI18n } from 'vue-i18n/index'
import { loadLocaleMessages, getLanguage } from './i18n-utils'
export default createI18n({
locale: getLanguage(),
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', // 备用语言
messages: loadLocaleMessages(),
})
这个文件主要就是做 createI18n,一开始我们已经说过了的。只是现在再进一步封装。
主要还是i18n-utils.js
/**
* author: fzs 2021-6-30
*/
import storage from 'lib@/utils/storage'
const LOCALE_KEY = 'locale'
const config = [
{
name: LOCALE_KEY,
type: 'string',
},
]
const windowStorage = new storage('sessionStorage', window.localStorage, config) // 本地存储
// 构建vue-i18n所需 messages
export function loadLocaleMessages() {
try {
const locales = require.context('@/locales/lang', true, /[A-Za-z0-9-_,\s]+\.js$/i)
const messages = {}
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key).default[locale]
}
})
return messages
} catch (error) {
throw new Error(error)
}
}
// 获取所有模块对应的localeName语言对象
export function getLangObjBylocaleName(localeName) {
try {
const locales = require.context('@/locales/modules', true, /[A-Za-z0-9-_,\s]+\.js$/i)
let tempObj = {}
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
if (locale === localeName) {
const obj = locales(key)
if (obj && obj.default) {
tempObj = {
...tempObj,
...obj.default,
}
}
}
}
})
return tempObj
} catch (error) {
throw new Error(error)
}
}
export function getLanguage() {
// 先取用户设置的,如果没有则取系统设置的,最后取环境变量中配置的
const locale = windowStorage.get(LOCALE_KEY)
if (locale) {
return locale
}
return (
navigator.language ||
navigator.userLanguage ||
process.env.VUE_APP_I18N_LOCALE
).toLowerCase()
}
/**
* 获取选中的语言label
* @param list 语言集合
* @returns
*/
export function getDefaultLanguageLabel(list){
try {
return list.filter((o) => o.value === getLanguage())[0].label
} catch (error) {
throw new Error(error)
}
}
// 获取所有语言列表
export function getLanguageLabelList() {
try {
const locales = require.context('@/locales/lang', true, /[A-Za-z0-9-_,\s]+\.js$/i)
const arr = []
locales.keys().forEach((key) => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const label = locales(key).default.language
const value = matched[1]
arr.push({ value: value, label: label })
}
})
return arr
} catch (error) {
throw new Error(error)
}
}
/**
* 设置语言并且刷新页面
* @param {string} lang 语言value
*/
export function setLanguage(lang) {
windowStorage.set(LOCALE_KEY, lang)
// 此方式直接刷新页面,最便捷,但是体验可能不好
window.location.reload(location.href + '?time=' + (new Date().getTime()))
}
我们主要来看loadLocaleMessages函数,此函数就是为了解决已经将语言文件拆分不同的语言文件再统一引入。主要是借助 require.context
这个方法.统一引入所有语言文件。动态构建vue-i18n需要的message
不了解的可以点击了解
打断点让大家看看里面各值,帮助大家理解
@/locales/lang
@是我配置的路径别名,下面贴出我配置的路径别名,希望后面看代码的时候你不会再有路径问题的疑问
接下来我们再看 locales目录里面的内容
lang目录下(兼容element-plus,并构建message):
1.en.js
/**
* author: fzs 2021-6-30
*/
import enLocale from 'element-plus/lib/locale/lang/en'
import {getLangObjBylocaleName} from 'lib@/utils/i18n/i18n-utils'
export default {
language:'English', // 必须
[enLocale.name]: {
// el 这个属性很关键,一定要保证有这个属性,
el: enLocale.el,
// 定义您自己的字典,但是请不要和 `el` 重复,这样会导致 ElementPlus 内部组件的翻译失效.
...getLangObjBylocaleName(enLocale.name)
},
}
getLangObjBylocaleName
这个函数在上面有,干的事就是把不同模块的语言文件内容再合并到同一个对象。看不懂大家打断点,再理解就好了
注意:lang里面的语言文件,名字必须按照文末的命名方式来起名字,因为我在构建message的时候用了文件名称来做key值。具体看 loadLocaleMessages
函数
zh-cn.js
/**
* author: fzs 2021-6-30
*/
import zhLocale from 'element-plus/lib/locale/lang/zh-cn'
import {getLangObjBylocaleName} from 'lib@/utils/i18n/i18n-utils'
export default {
language: '简体中文', // 必须
[zhLocale.name]: {
el: zhLocale.el,
// 定义您自己的字典,但是请不要和 `el` 重复,这样会导致 ElementPlus 内部组件的翻译失效.
...getLangObjBylocaleName(zhLocale.name)
},
}
以后每增加一种文件,则只需要在lang文件夹里面构建类似的内容即可。因为懒,其实这个也可以再进一步封装,不需要每增加一种语言就重复差不多的代码。不过就是需要提前将所有element-plus支持的语言引用先定义好。思路就是构建一个map对象。然后动态引入。
接下来我们看locales/modules各模块的内容
注意几个地方:1、模块名称(即最外层对象的key)一定要是唯一的,比如a模块,那么他所有语言都是在a这个对象里面。层级关系就是这样。因为最终这些不同模块的语言文件都是要合并到一个对象里面的。2、为了避免重复定义所以定义了个公共模块。
接下来看在入口main.js文件中怎么使用
import { createApp } from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
import CSvgIcon from 'lib@/components/c-svg-icon'
import 'assets@/icons' // icon
import 'assets@/styles/transition.less' // transition css
import VerifyEmojiDirective from 'lib@/directives/verify-emoji'
import Errorhandler from 'lib@/utils/errorhandler.js'
import i18n from 'lib@/utils/i18n/index'
import 'lib@/utils/prototype'
import * as echarts from 'echarts'
const app = createApp(App)
// 使用i18n
app.use(i18n)
// 加载element
app.use(ElementPlus, { i18n: i18n.global.t, size: 'small' })
// 全局注册 svg icon组件
app.component('c-svg-icon', CSvgIcon)
// 注册指令
app.use(VerifyEmojiDirective)
// 全局统一错误处理
app.use(Errorhandler)
app.provide('echarts', echarts)
// 初始化等
app.use(store)
app.use(router)
app.mount('#app')
好了,以上就是多语言封装的所有内容了。代码都有。直接复制到项目中就可以跑起来了。
下面贴出使用文档,以及一些说明
约定:
已集成 element-plus 因为用了文件名来做 key,所以语言文件格式命名按照文末语言列表里面的来命名,如果如下列表的语言都不存在,则需要自行添加 element-plus语言表,参考 element-plus 语言文件 <a>https://element-plus.org/#/zh-CN/component/i18n</a>
语言文件定义,为了尽可能适用大型项目,以减少合并冲突问题,已将将语言文件按模块划分。共通的语言文件在common模块中定义
使用者不需要关注如何实现,有多少种语言在对应的模块建对应的语言文件就可以了
语言文件的定义如下:(要注意最外层为唯一的模块名)
export default {
// 最外层定义 a则为模块名称
a:{
test: '这是a模块的翻译英文的'
}
}
使用的时候就是 模块名.xxx
{{$t('a.test')}} 'test' 是a模块语言文件里面的 key
// 在vue2写法中,已经注册全局 $i18n对象, 使用只需要 this.$i18n.t('key')
this.$i18n.t('hello')
// 在setup中使用
<template>
<div>{{t('name')}}</div>
</template>
<script>
import { useI18n } from "vue-i18n";
export default defineComponent({
setup() {
const { t } = useI18n();
return{
t
}
}
})
</script>
// 在setup中的模板中虽然也可以{{$t('hello')}}这么使用,
// 但是比较不推荐咯,毕竟你都return了翻译的函数 t
<template>
<div>{{$t('name')}}</div>
</template>
- 每新增一种语言则需要在src/locales/lang文件夹中增加一种,写法参照里面的en.js
- I18N_FALLBACK_LOCALE 后置语言,即备用语言,什么意思,就是当你的中文语言文件里面并没有定义这个key,
但是还是会被翻译了而没有报错,则有可能你在英文语言文件中定义了,如果都未定义则直接显示key
import { createI18n } from 'vue-i18n/index'
import { loadLocaleMessages, getLanguage } from './i18n-utils'
export default createI18n({
locale: getLanguage(),
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en', // 备用语言
messages: loadLocaleMessages(),
})
单文件使用方式不建议使用,因为有多少种语言你需要在<i18n>标签里面建立多少种语言
<template>
<p>{{ $t('hello') }}</p>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
name: 'HelloI18n'
})
</script>
<i18n>
{
"en": {
"hello": "Hello i18n in SFC!"
}
}
</i18n>
语言列表如下
- 简体中文(zh-cn)
- 英语(en)
- 德语(de)
- 葡萄牙语(pt)
- 西班牙语(es)
- 丹麦语(da)
- 法语(fr)
- 挪威语(nb-no)
- 繁体中文(zh-tw)
- 意大利语(it)
- 韩语(ko)
- 日语(ja)
- 荷兰语(nl)
- 越南语(vi)
- 俄语(ru)
- 土耳其语(tr)
- 巴西葡萄牙语(pt-br )
- 波斯语(fa)
- 泰语(th)
- 印尼语(id)
- 保加利亚语(bg)
- 波兰语(pl)
- 芬兰语(fi)
- 瑞典语(sv)
- 希腊语(el)
- 斯洛伐克语(sk)
- 加泰罗尼亚语(ca)
- 捷克语(cs)
- 乌克兰语(uk)
- 土库曼语(tk)
- 泰米尔语(ta)
- 拉脱维亚语(lv)
- 南非荷兰语(af)
- 爱沙尼亚语(et)
- 斯洛文尼亚语(sl)
- 阿拉伯语(ar)
- 希伯来语(he)
- 立陶宛语(lt)
- 蒙古语(mn)
- 哈萨克斯坦语(kk)
- 匈牙利语(hu)
- 罗马尼亚语(ro)
- 库尔德语(ku)
- 维吾尔语(ug-cn)
- 高棉语(km)
- 塞尔维亚语(sr)
- 巴斯克语(eu)
- 吉尔吉斯语(ky)
- 亚美尼亚语 (hy-am)
- 克罗地亚 (hr)
- 世界语 (eo)
11.19日更新
由于element-plus更新版本之后,本国际化方案需要稍微改造一下才能适用。
element-plus正式版发布更新后只需稍微改动下,主要去掉main.js中的全局引入方式。element-plus提供了一个全局配置组件Config Provider
// 加载element
app.use(ElementPlus, { i18n: i18n.global.t, size: 'small' }) // main.js这种引入方式废弃了。
新版也简单:在根app.vue组件中,如下
<template>
<el-config-provider v-if="show" :locale="locale">
<router-view />
</el-config-provider>
</template>
<script>
import { defineComponent, ref, onBeforeMount } from 'vue'
import { ElConfigProvider } from 'element-plus'
import i18n from 'lib@/utils/i18n/index'
import isLoginControl from 'lib@/compostion-api/is-login-control'
export default defineComponent({
components: {
ElConfigProvider,
},
setup() {
const locale = i18n.global.locale
let show = ref(false)
onBeforeMount(async () => {
await isLoginControl()
show.value = true
})
return {
locale: i18n.global.messages[locale],
show
}
},
})
</script>