Vue.js 国际化语言插件实现
强调一下:本文为实现i18n
一样的功能,但不是i18n
的使用教程。
很久之前在js
项目中曾经用过i18n
这个工具,当时使用的时候要开两个可视窗口去对照着语言表来粘贴复制对应的字段;回头想想,这种使用方式的容错率十分低,因为使用方式无论在标签中还是js
中都是以字符串的形式作为设置值的调用,所以意味着单词的拼写有误很难被发现,同时也要依赖边看着语言对应表来开发;那不妨换个设计模式来规避掉这个问题,使得开发更加丝滑(使用方式看顶部GIF),下面就来做这样一件事件。
预览效果,只做了部分语言切换,并没有全局。
实现思路
首先建所需的语言表,这是必需的,然后把所有表集合到一个单例对象
中,再定义一个全局变量作为当前使用的语言,最后注入到全局中,并用computed
返回当前使用的语言表,这样全局文件通过读取这个computed
的属性就完成了;至于使用mixins
还是插件对象的方式自行决定,作用都是相等的,这里我使用后者,因为是全局,所以插件的概念会更加贴合。
这里我以 vue 2.6.x 的方式去作示例,后面再对比vue3的实现
目录结构及代码组织
types.ts
:定义类型来约定所有语言表,防止单词拼错或者他人修改不同步等非逻辑bug
/** 语言配置信息 */
export interface LanguageInfo {
login: {
/** 账号 */
account: string
/** 密码 */
password: string
/** 注册 */
register: string
/** 登录按钮 */
signin: string
/** 退出登录按钮 */
logout: string
},
/** 输入提示 */
input: string
/** 项目名称 */
projectName: string
/** 语言设置 */
languageSetting: string
}
/** 语言种类 */
export type LanguageType = "zh" | "en" | "ja";
/** 语言状态 */
export interface LanguageState {
/** 当前应用的语言类型 */
type: LanguageType
/** 是否需要缓存操作 */
cache: boolean
}
zh.ts
、en.ts
、ja.ts
因为相似代码比较多,所以我直接截图代替
插件的形式注入全局
import Vue, { VueConstructor } from "vue";
import { LanguageInfo, LanguageState, LanguageType } from "./types";
import en from "./en";
import ja from "./ja";
import zh from "./zh";
// 自定义的全局类型声明
declare module "vue/types/vue" {
interface Vue {
/** 语言类型 */
$languageType: LanguageType
/** 当前应用的语言信息 */
$language: LanguageInfo
/**
* 设置语言类型
* @param val 语言类型
*/
$setLanguage(val: LanguageType): void;
}
}
/** 单例对象 */
const languageMap = {
en,
zh,
ja
}
/** 语言状态对象,也是全局单例 */
const state: LanguageState = {
type: "zh",
cache: false
}
const cacheName = "language-current-type";
/** 语言切换全局配置 */
const Language = {
install(ctx: VueConstructor<Vue>, options: Partial<LanguageState> = {}) {
// console.log("options >>", options);
if (options.type) {
state.type = options.type;
}
if (options.cache) {
state.cache = options.cache;
const val = sessionStorage.getItem(cacheName) as LanguageType;
if (val) {
state.type = val;
}
}
ctx.mixin({
data() {
return {
languageState: state
}
},
computed: {
$language() {
const key = this.$languageType as LanguageType;
return languageMap[key];
},
"$languageType": {
get() {
return (this as any).languageState.type;
},
set(val: LanguageType) {
(this as any).languageState.type = val;
if (state.cache) {
sessionStorage.setItem(cacheName, val);
}
}
}
},
methods: {
$setLanguage(type: LanguageType) {
this.$languageType = type;
}
}
})
}
}
export default Language;
这样就可以在任意组件中通过$language
就可以读到当前使用的语言表了,并且会有开头GIF那样的智能提示;需要修改当前语言可以通过this.$languageType = xxx
或者this.$setLanguage(xxx)
这样都是没问题的。同样的,在js
项目中也是没问题,只需要把声明文件改为xxx.d.ts
,并且通过/// <reference path="./types/xxx.d.ts" />
这样的方式引用类型即可,也是全局共享。
最后在main.ts
中导入使用
import Vue from "vue";
import Language from "@/language";
// 插件的方式调用
Vue.use(Language, {
cache: true // 自定义的功能,看情况使用
});
组件使用场景
<template>
<div class="page-language">
<div class="mgb_20">
<el-select v-model="$languageType">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</div>
<h2 class="the-title mgb_20">{{ $language.projectName }}</h2>
<el-form label-width="140px" label-position="left">
<el-form-item :label="$language.login.account">
<el-input :placeholder="$language.input + $language.login.account"></el-input>
</el-form-item>
<el-form-item :label="$language.login.password">
<el-input :placeholder="$language.input + $language.login.password"></el-input>
</el-form-item>
<el-button type="primary" size="medium">{{ $language.login.signin }}</el-button>
<el-button type="success" size="medium">{{ $language.login.register }}</el-button>
<el-button type="text" size="medium">{{ $language.login.logout }}</el-button>
</el-form>
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
@Component({})
export default class LanguageDemo extends Vue {
options = [
{ label: "中文", value: "zh" },
{ label: "English", value: "en" },
{ label: "日本語", value: "ja" },
]
openTip() {
this.$message.warning(this.$language.input + this.$language.login.password)
}
}
</script>
<style lang="scss">
.page-language {
width: 100%;
.el-form {
width: 100%;
max-width: 600px;
}
}
</style>
可以看到实现该功能的核心代码其实是很少的。
vue 3.x 中实现
同样的,在vue3
中依然适用,不过因为有了composition-api
的加持下,可以在上面代码上再做精简;现在不需要插件或者mixins
的方式注入到全局了,我们只需要把核心的代码封装起来,然后导入到需要的地方使用即可,直接上代码:
import { computed, reactive } from "vue";
import { LanguageState, LanguageType } from "./types";
import en from "./en";
import ja from "./ja";
import zh from "./zh";
const cacheName = "language-current-type";
const languageMap = {
en,
zh,
ja
}
const state = reactive<LanguageState>({
type: "zh",
cache: false
})
if (state.cache) {
const val = sessionStorage.getItem(cacheName) as LanguageType;
if (val) {
state.type = val;
}
}
/** 当前应用的语言类型 */
export const languageType = computed({
get: () => state.type,
set(val) {
state.type = val;
if (state.cache) {
sessionStorage.setItem(cacheName, val);
}
}
});
/** 当前使用的语言表 */
export const language = computed(() => languageMap[languageType.value]);
使用时,导入language
或者languageType
到对应的组件即可;当然,也可以挂载到全局使用,像这样:
const app = createApp(App);
app.config.globalProperties.$language = language;
我不是很推荐在vue3
定义全局属性或者方法,因为在ts
中不好定义类型,并且与一些语法题写法会起冲突,具体看个人使用。
转载自:https://juejin.cn/post/7101194338113159205