01:国际化实现原理
先来看一个需求:
我们有一个变量
msg
,但是这个msg
有且只能有两个值:
- hello world
- 你好世界
要求:根据需要切换
msg
的值
这样的一个需求就是 国际化 的需求,那么我们可以通过以下代码来实现这个需求
<script>
// 1. 定义 msg 值的数据源
const messages = {
en: {
msg: 'hello world'
},
zh: {
msg: '你好世界'
}
}
// 2. 定义切换变量
let locale = 'en'
// 3. 定义赋值函数
function t(key) {
return messages[locale][key]
}
// 4. 为 msg 赋值
let msg = t('msg')
console.log(msg);
// 修改 locale, 重新执行 t 方法,获取不同语言环境下的值
</script>
总结:
- 通过一个变量来 控制 语言环境
- 所有语言环境下的数据源要 预先 定义好
- 通过一个方法来获取 当前语言 下 指定属性 的值
- 该值即为国际化下展示值
02:基于 vue-i18n V9 的国际化实现方案分析
在 vue
的项目中,我们不需要手写这么复杂的一些基础代码,可以直接使用 vue-i18n 进行实现(注意:vue3
下需要使用 V 9.x
的 i18n
)
vue-i18n 的使用可以分为四个部分:
- 创建
messages
数据源 - 创建
locale
语言变量 - 初始化
i18n
实例 - 注册
i18n
实例
那么接下来我们就去实现以下:
-
安装
vue-i18n
npm install vue-i18n@next
-
创建
src/i18n/index.js
文件 -
创建
messages
数据源// src/i18n/index.js const messages = { en: { msg: { test: "hello world", }, }, zh: { msg: { test: "你好世界", }, }, };
-
创建
locale
语言变量// src/i18n/index.js const locale = "en";
-
初始化
i18n
实例// src/i18n/index.js import { createI18n } from "vue-i18n"; const i18n = createI18n({ // 使用 Composition API 模式,则需要将其设置为false legacy: false, // 全局注入 $t 函数 globalInjection: true, locale, messages, }); export default i18n
-
把
i18n
注册到vue
实例 在main.js
中导入// i18n (PS:导入放到 APP.vue 导入之前,因为后面我们会在 app.vue 中使用国际化内容) import i18n from "@/i18n"; ... createApp(App).use(store).use(router).use(i18n).mount("#app");
-
在
App.vue
中使用i18n
<template> <h2>{{ $t("msg.test") }}</h2> </template>
-
修改
locale
的值,即可改变展示的内容const locale = "zh";
截止到现在我们已经实现了 i18n
的最基础用法,那么接下来我们就可以在项目中使用 i18n
完成国际化。
项目中完成国际化分成以下几步进行:
- 封装
langSelect
组件用于修改locale
- 导入
el-locale
语言包 - 创建自定义语言包
03:方案落地:封装 langSelect 组件
-
在
constant
中定义常量// 国际化 export const LANG = "language";
-
定义
store/index.js
import { createStore } from "vuex"; import { LANG } from "@/constant"; import getters from "./getters.js"; export default createStore({ state: { language: localStorage.getItem(LANG) || "zh", }, mutations: { setLanguage(state, lang) { // 设置缓存 localStorage.setItem(LANG, lang); // 修改状态 state.language = lang; }, }, actions: {}, modules: {}, getters, });
在getters定义快捷访问
// store/getters const getters = { language: (state) => state.language, }; export default getters;
-
创建
components/LangSelect/index
// 在此之前npm安装element-plus导入 npm i element-plus import "element-plus/dist/index.css"; import ElementPlus from "element-plus"; createApp(App).use(store).use(router).use(i18n).use(ElementPlus).mount("#app");
<template> <el-dropdown trigger="click" class="international" @command="handleSetLanguage" > <div> <el-tooltip content="国际化" :effect="effect"> <el-button>切换</el-button> </el-tooltip> </div> <template #dropdown> <el-dropdown-menu> <el-dropdown-item :disabled="language === 'zh'" command="zh"> 中文 </el-dropdown-item> <el-dropdown-item :disabled="language === 'en'" command="en"> English </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </template> <script setup> import { useI18n } from "vue-i18n"; import { defineProps, computed } from "vue"; import { useStore } from "vuex"; import { ElMessage } from "element-plus"; defineProps({ effect: { type: String, default: "dark", validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ["dark", "light"].includes(value); }, }, }); const store = useStore(); const language = computed(() => store.getters.language); // 切换语言的方法 const i18n = useI18n(); const handleSetLanguage = (lang) => { i18n.locale.value = lang; store.commit("setLanguage", lang); ElMessage.success(`语言成功切换为${lang === "zh" ? "中文" : "英文"}`); }; </script>
-
在 任意组件 例如
App.vue
中导入LangSelect
使用<template> <div> <lang-select /> </div> </template> <script setup> import LangSelect from "@/components/LangSelect"; </script>
04:方案落地:element-plus 国际化处理
截止到目前,我们的国际化内容已经基本功能已经处理完成了。接下来需要处理的就是对应的语言包,有了语言包就可以实现整个项目中的所有国际化处理了。
那么对于语言包来说,我们整个项目中会分成两部分:
element-plus
语言包:用来处理element
组件的国际化功能- 自定义语言包:用来处理 非
element
组件的国际化功能
那么首先我们先来处理 element-plus
语言包:
**按照正常的逻辑,我们是可以通过 element-ui
配合 vue-i18n
来实现国际化功能的,但是目前的 element-plus
尚未提供配合 vue-i18n
实现国际化的方式! **
所以说,我们暂时只能先去做临时处理,等到 element-plus
支持 vue-i18n
功能之后,我们再进行对接实现
那么临时处理我们怎么去做呢?
-
升级
element-plus
到最新版本npm i element-plus
-
接下来实现国际化
-
在
plugins/element
中导入element
的中文、英文语言包:import zhCn from 'element-plus/es/locale/lang/zh-cn' import en from 'element-plus/lib/locale/lang/en'
-
注册
element
时,根据当前语言选择使用哪种语言包import store from '@/store' export default app => { app.use(ElementPlus, { locale: store.getters.language === 'en' ? en : zhCn }) }
05:方案落地:自定义语言包国际化处理
处理完 element
的国际化内容之后,接下来我们来处理 自定义语言包。
自定义语言包我们导出了一个对象,这个对象就是所有的 自定义语言对象
大家可以在网上寻找一些语言包
展开英文语言包
export default { login: { title: 'User Login', loginBtn: 'Login', usernameRule: 'Username is required', passwordRule: 'Password cannot be less than 6 digits', desc: ` Test authority account:Provide three kinds of authority accounts:
1. Super administrator account: super-admin
2. Administrator account: admin
3. Test configurable account: test
The uniform password is: 123456
Import user account:
You can log in with the imported username
The password is unified as: 123456
Note: Import user-discriminatory Chinese and English libraries! ! ! ! ` }, route: { profile: 'Profile', user: 'user', excelImport: 'ExcelImport', userManage: 'EmployeeManage', userInfo: 'UserInfo', roleList: 'RoleList', permissionList: 'PermissionList', article: 'article', articleRanking: 'ArticleRanking', articleCreate: 'ArticleCreate', articleDetail: 'ArticleDetail', articleEditor: 'ArticleEditor' }, toast: { switchLangSuccess: 'Switch Language Success' }, tagsView: { refresh: 'Refresh', closeRight: 'Close Rights', closeOther: 'Close Others' }, theme: { themeColorChange: 'Theme Color Change', themeChange: 'Theme Change' }, universal: { confirm: 'confirm', cancel: 'cancel' }, navBar: { themeChange: 'Theme Modification', headerSearch: 'Page Search', screenfull: 'Full Screen Replacement', lang: 'Globalization', guide: 'Function Guide', home: 'Home', course: 'Course homepage', logout: 'Log out' }, guide: { close: 'close', next: 'next', prev: 'previous', guideTitle: 'guidance', guideDesc: 'Turn on the boot function', hamburgerTitle: 'Hamburger button', hamburgerDesc: 'Open and close the left menu', breadcrumbTitle: 'Bread crumbs', breadcrumbDesc: 'Indicates the current page position', searchTitle: 'search', searchDesc: 'Page link search', fullTitle: 'full screen', fullDesc: 'Page display switching', themeTitle: 'theme', themeDesc: 'Change project theme', langTitle: 'globalization', langDesc: 'Language switch', tagTitle: 'Label', tagDesc: 'Opened page tab', sidebarTitle: 'menu', sidebarDesc: 'Project function menu' }, profile: { muted: '"Vue3 rewrite vue-element-admin, realize the back-end front-end integrated solution" project demonstration', introduce: 'Introduce', projectIntroduction: 'Project Introduction', projectFunction: 'Project Function', feature: 'Feature', chapter: 'Chapter', author: 'Author', name: 'Sunday', job: 'A front-end development program', Introduction: 'A senior technical expert, once worked in a domestic first-line Internet company, and has coordinated multiple large-scale projects with more than tens of millions of users. Committed to researching big front-end technology, he has been invited to participate in domestic front-end technology sharing sessions many times, such as: Google China Technology Sharing Session in 2018.' }, userInfo: { print: 'Print', title: 'Employee information', name: 'name', sex: 'gender', nation: 'nationality', mobile: 'phone number', province: 'Place of residence', date: 'Entry Time', remark: 'Remark', address: 'contact address', experience: 'Experience', major: 'Professional', glory: 'Glory', foot: 'Signature:___________Date:___________' }, uploadExcel: { upload: 'Click upload', drop: 'Drag files here' }, excel: { importExcel: 'excel import', exportExcel: 'excel export', exportZip: 'zip export', name: 'Name', mobile: 'contact details', avatar: 'Avatar', role: 'Role', openTime: 'Opening time', action: 'Operate', show: 'Check', showRole: 'Role', defaultRole: 'Staff', remove: 'delete', removeSuccess: 'Deleted successfully', title: 'Export to excel', placeholder: 'excel file name', defaultName: 'Staff Management Form', close: 'Cancel', confirm: 'Export', importSuccess: ' Employee data imported successfully', dialogTitle1: 'Are you sure you want to delete the user ', dialogTitle2: ' Is it?', roleDialogTitle: 'Configure roles' }, role: { buttonTxt: 'New Role', index: 'Serial number', name: 'name', desc: 'describe', action: 'operate', assignPermissions: 'assign permissions', removeRole: 'Delete role', dialogTitle: 'New role', dialogRole: 'Role Name', dialogDesc: 'Role description', updateRoleSuccess: 'User role updated successfully' }, permission: { name: 'Authority name', mark: 'Authority ID', desc: 'Permission description' }, article: { ranking: 'Ranking', title: 'Title', author: 'Author', publicDate: 'release time', desc: 'brief introduction', action: 'operate', dynamicTitle: 'Dynamic display', show: 'check', remove: 'delete', edit: 'editor', dialogTitle1: 'Are you sure you want to delete the article ', dialogTitle2: ' NS?', removeSuccess: 'Article deleted successfully', titlePlaceholder: 'Please enter the title of the article', markdown: 'Markdown', richText: 'Rich Text', commit: 'commit', createSuccess: 'The article was created successfully', editorSuccess: 'Article modified successfully', sortSuccess: 'Article ranking modified successfully' } }
展开中文语言包
export default { login: { title: '用户登录', loginBtn: '登录', usernameRule: '用户名为必填项', passwordRule: '密码不能少于6位', desc: ` 测试权限账号:提供三种权限账号:
1. 超级管理员账号: super-admin
2. 管理员账号:admin
3. 测试可配置账号:test
密码统一为:123456
导入用户账号:
可使用导入的用户名登录
密码统一为:123456
注意:导入用户区分中英文库!!!! ` }, route: { profile: '个人中心', user: '用户', excelImport: 'Excel导入', userManage: '员工管理', userInfo: '员工信息', roleList: '角色列表', permissionList: '权限列表', article: '文章', articleRanking: '文章排名', articleCreate: '创建文章', articleDetail: '文章详情', articleEditor: '文章编辑' }, toast: { switchLangSuccess: '切换语言成功' }, tagsView: { refresh: '刷新', closeRight: '关闭右侧', closeOther: '关闭其他' }, theme: { themeColorChange: '主题色更换', themeChange: '主题更换' }, universal: { confirm: '确定', cancel: '取消' }, navBar: { themeChange: '主题修改', headerSearch: '页面搜索', screenfull: '全屏替换', lang: '国际化', guide: '功能引导', home: '首页', course: '课程主页', logout: '退出登录' }, guide: { close: '关闭', next: '下一个', prev: '上一个', guideTitle: '引导', guideDesc: '打开引导功能', hamburgerTitle: '汉堡按钮', hamburgerDesc: '打开和关闭左侧菜单', breadcrumbTitle: '面包屑', breadcrumbDesc: '指示当前页面位置', searchTitle: '搜索', searchDesc: '页面链接搜索', fullTitle: '全屏', fullDesc: '页面显示切换', themeTitle: '主题', themeDesc: '更换项目主题', langTitle: '国际化', langDesc: '语言切换', tagTitle: '标签', tagDesc: '已打开页面标签', sidebarTitle: '菜单', sidebarDesc: '项目功能菜单' }, profile: { muted: '《vue3 改写 vue-element-admin,实现后台前端综合解决方案》项目演示', introduce: '介绍', projectIntroduction: '项目介绍', projectFunction: '项目功能', feature: '功能', chapter: '章节', author: '作者', name: 'Sunday', job: '一个前端开发程序猿', Introduction: '高级技术专家,曾就职于国内一线互联网公司,统筹过的多个大型项目用户数已过千万级。致力于研究大前端技术,多次受邀参加国内前端技术分享会,如:2018 年 Google 中国技术分享会。' }, userInfo: { print: '打印', title: '员工信息', name: '姓名', sex: '性别', nation: '民族', mobile: '手机号', province: '居住地', date: '入职时间', remark: '备注', address: '联系地址', experience: '经历', major: '专业', glory: '荣耀', foot: '签字:___________日期:___________' }, uploadExcel: { upload: '点击上传', drop: '将文件拖到此处' }, excel: { importExcel: 'excel 导入', exportExcel: 'excel 导出', exportZip: 'zip 导出', name: '姓名', mobile: '联系方式', avatar: '头像', role: '角色', openTime: '开通时间', action: '操作', show: '查看', showRole: '角色', defaultRole: '员工', remove: '删除', removeSuccess: '删除成功', title: '导出为 excel', placeholder: 'excel 文件名称', defaultName: '员工管理表', close: '取 消', confirm: '导 出', importSuccess: ' 条员工数据导入成功', dialogTitle1: '确定要删除用户 ', dialogTitle2: ' 吗?', roleDialogTitle: '配置角色' }, role: { buttonTxt: '新增角色', index: '序号', name: '名称', desc: '描述', action: '操作', assignPermissions: '分配权限', removeRole: '删除角色', dialogTitle: '新增角色', dialogRole: '角色名称', dialogDesc: '角色描述', updateRoleSuccess: '用户角色更新成功' }, permission: { name: '权限名称', mark: '权限标识', desc: '权限描述' }, article: { ranking: '排名', title: '标题', author: '作者', publicDate: '发布时间', desc: '内容简介', action: '操作', dynamicTitle: '动态展示', show: '查看', remove: '删除', edit: '编辑', dialogTitle1: '确定要删除文章 ', dialogTitle2: ' 吗?', removeSuccess: '文章删除成功', titlePlaceholder: '请输入文章标题', markdown: 'markdown', richText: '富文本', commit: '提交', createSuccess: '文章创建成功', editorSuccess: '文章修改成功', sortSuccess: '文章排名修改成功' } }
-
复制
lang
文件夹到i18n
文件夹中 中 -
在
lang/index.js
中,导入语言包import mZhLocale from "./lang/zh"; import mEnLocale from "./lang/en";
-
在
messages
中注册到语言包const messages = { en: { msg: { ...mEnLocale } }, zh: { msg: { ...mZhLocale } } }
06:方案落地:处理项目国际化内容
在处理好了国际化的语言包之后,接下来我们就可以应用国际化功能到我们的项目需要的组件中,但组件中和js文件中使用导入方式有点不同,举个栗子。
登录页面:
login/index
<template>
<div class="login-container">
<el-form
class="login-form"
:model="loginForm"
:rules="loginRules"
ref="loginFromRef"
>
<div class="title-container">
<h3 class="title">{{ $t("msg.login.title") }}</h3>
<lang-select class="lang-select"></lang-select>
</div>
<el-form-item prop="username">
<el-input
placeholder="username"
name="username"
type="text"
v-model="loginForm.username"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
:type="passwordType"
placeholder="password"
name="password"
v-model="loginForm.password"
/>
</el-form-item>
<el-button type="primary" class="submit">{{
$t("msg.login.loginBtn")
}}</el-button>
<div class="tips" v-html="$t('msg.login.desc')"></div>
</el-form>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { validatePassword } from "./rules";
import LangSelect from "./components/LangSelect/index.vue";
// 数据源
const loginForm = ref({
username: "super-admin",
password: "123456",
});
// 验证规则
const i18n = useI18n();
const loginRules = ref({
username: [
{
required: true,
trigger: "blur",
message: i18n.t("msg.login.usernameRule"),
},
],
password: [
{
required: true,
trigger: "blur",
validator: validatePassword(),
},
],
});
</script>
login/rules
import i18n from '@/i18n'
export const validatePassword = () => {
return (rule, value, callback) => {
if (value.length < 6) {
callback(new Error(i18n.global.t('msg.login.passwordRule')))
} else {
callback()
}
}
}
07:方案落地:国际化缓存处理
我们希望在 刷新页面后,当前的国际化选择可以被保留,所以想要实现这个功能,那么就需要进行 国际化的缓存处理
此处的缓存,我们依然通过两个方面进行:
vuex
缓存LocalStorage
缓存
只不过这里的缓存,我们已经在处理 langSelect
组件时 处理完成了,所以此时我们只需要使用缓存下来的数据即可。
在 i18n/index
中,创建 getLanguage
方法:
import store from "@/store";
// 返回当前 lang
function getLanguage() {
return store && store.getters && store.getters.language;
}
修改 createI18n
的 locale
为 getLanguage()
const i18n = createI18n({
// 使用 Composition API 模式,则需要将其设置为false
legacy: false,
// 全局注入 $t 函数
globalInjection: true,
locale: getLanguage(),
messages,
});
08:国际化方案总结
国际化是前端项目中的一个非常常见的功能,那么在前端项目中实现国际化主要依靠的就是 vue-i18n
这个第三方的包。
而 i18n
的使用,整体来说就分为这么四步:
- 创建
messages
数据源 - 创建
locale
语言变量 - 初始化
i18n
实例 - 注册
i18n
实例
核心的内容其实就是 数据源的部分,但是大家需要注意,如果你的项目中使用了 第三方组件库 ,那么不要忘记 第三方组件库的数据源 需要 单独 进行处理!