在 Vue 2 中配置国际化,您可以使用 Vue I18n 插件。Vue I18n 是 Vue.js 官方推荐的国际化插件,它可以帮助您轻松地实现多语言支持。
安装 vue-i18n
项目根目录下打开终端或命令行工具,运行以下命令来安装相关依赖包:
npm install vue-i18n@8.27.1 --save
使用
1. 在 src/components
目录中新增一个名为 i18n
的目录,并添加以下3个文件:
src/components/i18n/locales/en/index.js
英语语言包:
export default {
'Language': 'English'
}
src/components/i18n/locales/zh-CN/index.js
中文语言包:
export default {
'Language': '中文'
}
src/components/i18n/index.js
语言包入口文件:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 各个国家的key
const localeKeys = ['en', 'zh-CN']
// 各个国家语言包
const messages = {}
for (const key of localeKeys) {
messages[key] = require(`./locales/${key}/index.js`).default
}
export default new VueI18n({
locale: 'en',
messages,
silentTranslationWarn: true // 忽略翻译警告
})
2. 打开 src/main.js
文件,挂载到Vue实例:
import i18n from './components/i18n'
new Vue({
i18n
})
修改后的代码如下:
import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import router from './router'
import store from './store'
import i18n from './components/i18n'
import '@css/index.less'
// 禁用生产环境提示
Vue.config.productionTip = false
// Element挂载到Vue
Vue.$message = Element.Message
Vue.use(Element)
new Vue({
router,
store,
i18n,
render: (h) => h(App)
}).$mount('#app')
3. 在页面、js代码中使用:
一旦将 VueI18n 实例挂载到 Vue 实例上,在 Vue 组件中直接使用 $t 方法,可以通过指定键(key)来获取对应语言的翻译文本。这个键可以是简单的字符串,也可以是一个对象,用于支持更复杂的翻译需求。
以下是使用 $t 方法获取翻译文本的示例:
<template>
<div>
<p>{{ $t('Language') }}</p>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.$t('Language'))
}
}
</script>
Element 语言包
完成上述步骤后,我们在使用 Element 组件时可能会遇到一个问题:虽然我们成功切换了自定义的语言,在应用中自定义的文本已经被正确翻译,但是 Element UI 组件的显示内容并没有随之切换。
这是因为 Element UI 组件库本身并不直接集成 vue-i18n 插件,因此它并不会自动根据我们设置的语言环境来翻译组件的显示文本。
解决这个问题的办法是,我们需要手动为 Element UI 组件进行国际化配置和翻译。
以下是解决方案的基本步骤:
1. 创建 Element 语言包文件
在 src/components/i18n
目录下添加一个名为 element
的目录,并在目录中添加以下两个文件:
en.js
// 英语
exports.default = {
el: {
colorpicker: {
confirm: 'OK',
clear: 'Clear'
},
datepicker: {
now: 'Now',
today: 'Today',
cancel: 'Cancel',
clear: 'Clear',
confirm: 'OK',
selectDate: 'Select date',
selectTime: 'Select time',
startDate: 'Start Date',
startTime: 'Start Time',
endDate: 'End Date',
endTime: 'End Time',
prevYear: 'Previous Year',
nextYear: 'Next Year',
prevMonth: 'Previous Month',
nextMonth: 'Next Month',
year: '',
month1: 'January',
month2: 'February',
month3: 'March',
month4: 'April',
month5: 'May',
month6: 'June',
month7: 'July',
month8: 'August',
month9: 'September',
month10: 'October',
month11: 'November',
month12: 'December',
week: 'week',
weeks: {
sun: 'Sun',
mon: 'Mon',
tue: 'Tue',
wed: 'Wed',
thu: 'Thu',
fri: 'Fri',
sat: 'Sat'
},
months: {
jan: 'Jan',
feb: 'Feb',
mar: 'Mar',
apr: 'Apr',
may: 'May',
jun: 'Jun',
jul: 'Jul',
aug: 'Aug',
sep: 'Sep',
oct: 'Oct',
nov: 'Nov',
dec: 'Dec'
}
},
select: {
loading: 'Loading',
noMatch: 'No matching data',
noData: 'No data',
placeholder: 'Select'
},
cascader: {
noMatch: 'No matching data',
loading: 'Loading',
placeholder: 'Select',
noData: 'No data'
},
pagination: {
goto: 'Go to',
pagesize: '/page',
total: 'Total {total}',
pageClassifier: ''
},
messagebox: {
title: 'Message',
confirm: 'OK',
cancel: 'Cancel',
error: 'Illegal input'
},
upload: {
deleteTip: 'press delete to remove',
delete: 'Delete',
preview: 'Preview',
continue: 'Continue'
},
table: {
emptyText: 'No Data',
confirmFilter: 'Confirm',
resetFilter: 'Reset',
clearFilter: 'All',
sumText: 'Sum'
},
tree: {
emptyText: 'No Data'
},
transfer: {
noMatch: 'No matching data',
noData: 'No data',
titles: ['List 1', 'List 2'], // to be translated
filterPlaceholder: 'Enter keyword', // to be translated
noCheckedFormat: '{total} items', // to be translated
hasCheckedFormat: '{checked}/{total} checked' // to be translated
},
image: {
error: 'FAILED'
},
pageHeader: {
title: 'Back' // to be translated
},
popconfirm: {
confirmButtonText: 'Yes',
cancelButtonText: 'No'
},
empty: {
description: 'No Data'
}
}
}
zh-CN.js
// 中文
exports.default = {
el: {
colorpicker: {
confirm: '确定',
clear: '清空'
},
datepicker: {
now: '此刻',
today: '今天',
cancel: '取消',
clear: '清空',
confirm: '确定',
selectDate: '选择日期',
selectTime: '选择时间',
startDate: '开始日期',
startTime: '开始时间',
endDate: '结束日期',
endTime: '结束时间',
prevYear: '前一年',
nextYear: '后一年',
prevMonth: '上个月',
nextMonth: '下个月',
year: '年',
month1: '1 月',
month2: '2 月',
month3: '3 月',
month4: '4 月',
month5: '5 月',
month6: '6 月',
month7: '7 月',
month8: '8 月',
month9: '9 月',
month10: '10 月',
month11: '11 月',
month12: '12 月',
// week: '周次',
weeks: {
sun: '日',
mon: '一',
tue: '二',
wed: '三',
thu: '四',
fri: '五',
sat: '六'
},
months: {
jan: '一月',
feb: '二月',
mar: '三月',
apr: '四月',
may: '五月',
jun: '六月',
jul: '七月',
aug: '八月',
sep: '九月',
oct: '十月',
nov: '十一月',
dec: '十二月'
}
},
select: {
loading: '加载中',
noMatch: '无匹配数据',
noData: '无数据',
placeholder: '请选择'
},
cascader: {
noMatch: '无匹配数据',
loading: '加载中',
placeholder: '请选择',
noData: '暂无数据'
},
pagination: {
goto: '前往',
pagesize: '条/页',
total: '共 {total} 条',
pageClassifier: '页'
},
messagebox: {
title: '提示',
confirm: '确定',
cancel: '取消',
error: '输入的数据不合法!'
},
upload: {
deleteTip: '按 delete 键可删除',
delete: '删除',
preview: '查看图片',
continue: '继续上传'
},
table: {
emptyText: '暂无数据',
confirmFilter: '筛选',
resetFilter: '重置',
clearFilter: '全部',
sumText: '合计'
},
tree: {
emptyText: '暂无数据'
},
transfer: {
noMatch: '无匹配数据',
noData: '无数据',
titles: ['列表 1', '列表 2'],
filterPlaceholder: '请输入搜索内容',
noCheckedFormat: '共 {total} 项',
hasCheckedFormat: '已选 {checked}/{total} 项'
},
image: {
error: '加载失败'
},
pageHeader: {
title: '返回'
},
popconfirm: {
confirmButtonText: '确定',
cancelButtonText: '取消'
},
empty: {
description: '暂无数据'
}
}
}
以上两个文件来源于 Element官方。在当前项目中,将Element语言包放到本地的目的是为了便于后期对语言包进行修改拓展。
如果不想在本地创建这两个文件,也可以通过引入的方式直接获取到对应的文件,例如:import elementLang from 'element-ui/src/locale/lang/en'
。
2. 在语言包入口文件引入 Element 语言包
在 src/components/i18n/index.js
文件中找到以下代码块:
// 各个国家语言包
const messages = {}
for (const key of localeKeys) {
messages[key] = require(`./locales/${key}/index.js`).default
}
将其修改为:
// 各个国家语言包
const messages = {}
for (const key of localeKeys) {
const langObj = require(`./locales/${key}/index.js`).default
const langElement = require(`./element/${key}`)
messages[key] = {
...langObj,
...langElement ? langElement.default : {}
}
}
修改后的代码如下:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 各个国家的key
const localeKeys = ['en', 'zh-CN']
// 各个国家语言包
const messages = {}
for (const key of localeKeys) {
const langObj = require(`./locales/${key}/index.js`).default
const langElement = require(`./element/${key}`)
messages[key] = {
...langObj,
...langElement ? langElement.default : {}
}
}
export default new VueI18n({
locale: 'en',
messages,
silentTranslationWarn: true // 忽略翻译警告
})
通过这种方式,Element语言包与我们自定义的语言包便合并在一起了。
3. 挂载 Element 到 Vue 实例中时将语言包注入
在 src/main.js
中找到以下代码块
Vue.use(Element)
将其修改为:
Vue.use(Element, {
i18n: (key, value) => i18n.t(key, value)
})
修改后的代码如下:
import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import App from './App.vue'
import router from './router'
import store from './store'
import i18n from './components/i18n'
import '@css/index.less'
// 禁用生产环境提示
Vue.config.productionTip = false
// Element挂载到Vue
Vue.$message = Element.Message
Vue.use(Element, {
i18n: (key, value) => i18n.t(key, value)
})
new Vue({
router,
store,
i18n,
render: (h) => h(App)
}).$mount('#app')
完成后 Element 相关组件就能正常的显示翻译后的内容了。
切换组件的封装
在我们的UI组件中,我们使用了 Element 中的下拉菜单组件。如果想了解更多关于下拉菜单组件的用法,请访问 Dropdown下拉菜单文档地址 查看详细文档内容。
新建组件文件
在src/components/i18n
目录中,新建一个名为change-language.vue
的文件,并添加以下内容:
<template>
<el-dropdown @command="handle">
<span class="el-dropdown-link">
{{$t('Language')}}<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, index) of list" :key="index" :command="item.key">{{item.name}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
export default {
name: 'change-language',
data() {
return {
list: [
{ key: 'en', name: 'English' }, // 英语
{ key: 'zh-CN', name: '中文' } // 中文
]
}
},
methods: {
handle(value) {
}
}
}
</script>
<style scoped lang="less">
</style>
然后,我们可以找一个Vue文件,在其中使用这个组件进行观察。使用方式如下(以Vue单文件组件为例):
- 引入组件
import ChangeLanguage from '@/components/i18n/change-language'
- 注册组件
components: {
ChangeLanguage
}
- 使用
<ChangeLanguage />
这样一个基本的语言包下拉单组件样式就完成了,之后还需要对组件进行功能上的开发。
切换语言
在组件handle
函数代码中添加代码,如下:
handle(value) {
this.$i18n.locale = value
}
通过将value
赋给this.$i18n.locale
,我们可以动态地切换当前语言为下拉菜单中所选中的语言。
现在虽然基本的切换功能已经完成,但是当我们在网页中切换语言包并刷新页面后,之前选择的语言包不会被保留,而是重新加载页面时返回到默认的语言包。这是因为在刷新页面时,浏览器会重新加载整个应用程序,并重置Vue实例的状态,包括设置的语言包。这时候我们就需要做切换数据的持久化处理,来保证我们切换后的内容显示不会出错。
数据持久化
数据持久化的方式有很多种,这里我们采用了浏览器的本地存储 LocalStorage
来实现。
在组件handle
函数代码中添加代码,如下:
handle(value) {
this.$i18n.locale = value
localStorage.setItem('change-language', value)
}
找到 src/components/i18n/index.js
文件中以下代码块:
export default new VueI18n({
locale: 'en',
messages,
silentTranslationWarn: true // 忽略翻译警告
})
将其中 locale
写死的值 'en'
改为 localStorage
获取的值,如下:
export default new VueI18n({
locale: localStorage.getItem('change-language') || 'zh-CN',
messages,
silentTranslationWarn: true // 忽略翻译警告
})
上述代码中,语言包的key
值会先从本地存储中获取,如果获取不到则设置为默认值'zh-CN'
。
这样,我们切换语言包再刷新页面后,仍然可以正确地显示之前选择的语言包。
后端语言包
在一般情况下,仅仅使用前端的语言包是无法涵盖整个系统的翻译需求的。例如,后端接口返回的菜单、下拉列表的数据等。针对这种情况,我们可以采取一种方案,即在切换语言后调用location.reload()
方法刷新页面以重新获取后端的翻译数据。
使用这种方案时,我们需要与后端约定好国际化的翻译key
的格式,并在语言切换时通过前端与后端接口进行数据交互。前端可以将当前选择的语言作为参数传递给后端,后端将返回相应语言的翻译数据。然后在前端刷新页面时,通过重新加载页面来获取更新后的翻译内容。
请注意,使用location.reload()
方法会导致整个页面重新加载,这可能会对用户的体验产生一定影响,特别是在数据量较大或网络较慢的情况下。因此,在实施这种方案之前,请确保综合考虑用户体验和性能方面的因素。
总结来说,为了在整个系统中实现翻译需求,我们可以通过与后端接口协作,传递语言选择,并在需要时重新加载页面来获取最新的翻译内容。这样就能够在前后端协同工作下,正确显示经过翻译的内容。
在组件handle
函数中添加 location.reload()
:
handle(value) {
this.$i18n.locale = value
localStorage.setItem('change-language', value)
location.reload()
}
小小的优化一下
上述步骤完成,我们的组件整体功能就已经开发完毕了,但是代码中有一部部分内容可以复用简化。
组件代码中的以下部分:
list: [
{ key: 'en', name: 'English' }, // 英语
{ key: 'zh-CN', name: '中文' } // 中文
]
src/components/i18n/index.js
代码中的以下部分:
// 各个国家的key
const localeKeys = ['en', 'zh-CN']
以上两部分代码对比,就能够发现他们有两个相同点:都是数组、内容中都有一样的值。
这样我们就能将组件代码中的内容移动到 src/components/i18n/index.js
文件中,避免后期维护需要新增国家时,还要同时维护两个文件的列表了。
根据以下步骤进行优化:
- 找到
src/components/i18n/index.js
文件中以下内容:
// 各个国家的key
const localeKeys = ['en', 'zh-CN']
// 各个国家语言包
const messages = {}
for (const key of localeKeys) {
const langObj = require(`./locales/${key}/index.js`).default
const langElement = require(`./element/${key}`)
messages[key] = {
...langObj,
...langElement ? langElement.default : {}
}
}
- 将其修改为:
// 各个国家的key
export const localeKeys = [
{ key: 'en', name: 'English' }, // 英语
{ key: 'zh-CN', name: '中文' } // 中文
]
// 各个国家语言包
const messages = {}
for (const item of localeKeys) {
const key = item.key
const langObj = require(`./locales/${key}/index.js`).default
const langElement = require(`./element/${key}`)
messages[key] = {
...langObj,
...langElement ? langElement.default : {}
}
}
- 在组件中引入
src/components/i18n/index.js
文件中的localeKeys
:
import { localeKeys } from './index'
- 将
localeKeys
赋值给list
:
return {
list: localeKeys
}
这样,这个小小的优化就完成了。
完整代码
src/components/i18n/change-language.vue
组件代码:
<!--
@Descripttion 国际化语言切换
@version 1.0.0
@Author Bell
@ 使用
引入组件
import ChangeLanguage from '@/components/i18n/change-language'
注册组件
components: {
ChangeLanguage
}
使用
<ChangeLanguage />
-->
<template>
<el-dropdown @command="handle">
<span class="el-dropdown-link">
{{$t('Language')}}<i class="el-icon-caret-bottom el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, index) of list" :key="index" :command="item.key">{{item.name}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
<script>
import { localeKeys } from './index'
export default {
name: 'change-language',
data() {
return {
list: localeKeys
}
},
methods: {
handle(value) {
this.$i18n.locale = value
localStorage.setItem('change-language', value)
location.reload()
}
}
}
</script>
<style scoped lang="less">
</style>
src/components/i18n/index.js
国际化入口文件:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
// 各个国家的key
export const localeKeys = [
{ key: 'en', name: 'English' }, // 英语
{ key: 'zh-CN', name: '中文' } // 中文
]
// 各个国家语言包
const messages = {}
for (const item of localeKeys) {
const key = item.key
const langObj = require(`./locales/${key}/index.js`).default
const langElement = require(`./element/${key}`)
messages[key] = {
...langObj,
...langElement ? langElement.default : {}
}
}
export default new VueI18n({
locale: localStorage.getItem('change-language') || 'zh-CN',
messages,
silentTranslationWarn: true // 忽略翻译警告
})
框架搭建整体流程
-
第一步 Vue2 使用 Vue 脚手架 Vue CLI 搭建一个 Vue.js 前端项目框架
-
第二步 Vue2 vue.config.js 基础配置,路径别名alias
-
第三步 Vue2 vue.config.js 集成 Less 配置 sourceMap+全局变量
-
第四步 Vue2 配置ESLint
-
第五步 Vue2 vue.config.js 使用image-minimizer-webpack-plugin配置图片压缩
-
第六步 Vue2 集成全家桶 vue-router vuex axios 和 element-ui
-
第七步 Webpack 配置多环境和全局变量 cross-env 和 webpack.DefinePlugin