vue-i18n 的使用方式
安装
vue2版需要安装8.x版本的,9.x的是vue3版本
使用上大同小异。
vue2安装:
yarn add vue-i18n@8
vue3安装:
yarn add vue-i18n
封装
官方虽然支持很不错的用法,但是自定义处理是难免的。
vue3
文件目录结构
├─ src
│ ├─ language
│ │ ├─ lang
│ │ │ ├─ en.json
│ │ │ └─ zh.json
│ │ ├─ core
│ │ │ ├─ i18n.ts
│ │ │ ├─ customization.ts
│ │ │ └─ language.ts
│ │ ├─ index.ts
│ │ └─ types.ts
│ ├─ main.ts
lang目录中存放所有翻译的文件,语言文件建议就取前两位的小写,因为通过浏览器navigator.language
取得的语言文件会有很多变种:en-US、en-AU...;这并不利于我们判断,所以我的做法就是取其前两位的小写。
core为核心文件:
language.ts
封装的自定义语言处理类,比如首次加载语言,异步加载语言,切换语言customization.ts
自定义多语言处理,对$t
的二次封装i18n.ts
实例化处理,频繁的修改在此文件
其他:
index.ts
入口文件,包含i18n注册,导出一些自定义语言处理函数types.ts
一些类型声明
本来我是想用用9版本的i18n的Composition API
写法的,但是考虑到需要适配两个vue的版本,所以统一采用低版本的写法,有兴趣可以查看官方的这种写法:《Composition API》
下面是核心代码了!
language.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-30 13:59:14
* @LastEditTime: 2022-05-30 16:38:59
* @LastEditors: mulingyuer
* @Description:
* @FilePath: \casino-xian\src\language\core\language.ts
* 怎么可能会有bug!!!
*/
import { I18n } from "vue-i18n";
import { getItem, setItem } from "@/utils";
import { nextTick } from "vue";
import { LanguageOptions } from "../types";
class Language {
private i18n: I18n; //i18n实例
private defaultLocale = "en"; //默认语言
private SUPPORT_LOCALES: Array<string> = []; //支持的语言数组
private localKey = "language"; //本地持久化key
private loadLocale: Array<string> = []; //已加载的语言
constructor(i18n: I18n, options: LanguageOptions) {
this.i18n = i18n;
if (options) {
this.initOptions(options);
}
}
private initOptions(options: LanguageOptions): void {
const { defaultLocale, supportLocales, localKey, loadLocale } = options;
if (typeof defaultLocale === "string" && defaultLocale.trim() !== "") {
this.defaultLocale = defaultLocale;
}
if (Array.isArray(supportLocales)) {
this.SUPPORT_LOCALES = supportLocales;
}
if (this.SUPPORT_LOCALES.length === 0) {
this.SUPPORT_LOCALES.push(this.defaultLocale);
}
if (typeof localKey === "string" && localKey.trim() !== "") {
this.localKey = localKey;
}
if (Array.isArray(loadLocale)) {
this.loadLocale = loadLocale;
}
}
/**
* @description: 设置语言
* @param {string} locale 语言
* @Date: 2022-05-16 16:48:21
* @Author: mulingyuer
*/
private setI18nLanguage(locale: string) {
if (this.i18n.mode === "legacy") {
this.i18n.global.locale = locale;
} else {
(this.i18n.global.locale as any).value = locale;
}
document.querySelector("html")?.setAttribute("lang", locale);
setItem(this.localKey, locale);
}
/**
* @description: 异步加载语言并使用
* @param {string} locale 语言
* @Date: 2022-05-16 15:25:55
* @Author: mulingyuer
*/
public async loadLocaleMessages(locale: string): Promise<void> {
try {
//是否已经加载了
if (this.loadLocale.includes(locale)) {
this.setI18nLanguage(locale);
return nextTick();
}
//不合法的语言
if (!this.SUPPORT_LOCALES.includes(locale)) {
//使用默认的语言
return this.loadLocaleMessages(this.defaultLocale);
}
//加载语言
const messages = await import(/* webpackChunkName: "locale-[request]" */ `../lang/${locale}.json`);
//设置i18n的messages
this.i18n.global.setLocaleMessage(locale, messages.default);
this.loadLocale.push(locale);
this.setI18nLanguage(locale);
return nextTick();
} catch (error) {
console.error(`获取语言文件失败:${locale}`, error);
return nextTick();
}
}
/**
* @description: 首次加载语言文件
* @Date: 2022-05-16 17:18:33
* @Author: mulingyuer
*/
public async firstLoadI18nLocale(specifyLang?: string) {
const htmlLang = this.getDocumentLanguage();
const localLang = getItem(this.localKey);
const systemLang = this.getStystemLanguage();
const lang = specifyLang || localLang || systemLang || htmlLang || this.defaultLocale;
await this.loadLocaleMessages(lang);
this.firstLoadI18nLocale = () => Promise.resolve();
}
/**
* @description: 获取系统语言
* @Date: 2022-05-18 19:39:48
* @Author: mulingyuer
*/
private getStystemLanguage() {
const lang = navigator.language;
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
/**
* @description: 获取文档语言
* @Date: 2022-05-19 10:54:51
* @Author: mulingyuer
*/
private getDocumentLanguage() {
const lang = document.documentElement.getAttribute("lang");
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
}
export default Language;
types.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 14:18:51
* @LastEditTime: 2022-05-30 15:36:33
* @LastEditors: mulingyuer
* @Description: 类型声明
* @FilePath: \casino-xian\src\language\types.ts
* 怎么可能会有bug!!!
*/
//api中的info字段
export type Info = string | null | undefined;
//language类的options
export type LanguageOptions = {
defaultLocale?: string;
supportLocales: Array<string>;
localKey?: string;
loadLocale?: Array<string>;
};
getItem, setItem
是对于localStorage
的封装,可以看成是:localStorage.getItem,localStorage.setItem
customization.ts
import i18n from "./i18n";
const $t = i18n.global.t;
/**
* @description: 多语言保底输出 tOriginalOutput
* @param message 文本
* @Date: 2022-05-19 19:36:23
* @Author: mulingyuer
*/
export function $toop(message: string, backMessage: any = "") {
const langMessage = $t(message);
//如果没有对应的文本,用最后一个.后面的内容作为翻译文本
if (langMessage === message) {
return backMessage;
}
return langMessage;
}
i18n.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-19 17:21:29
* @LastEditTime: 2022-05-30 16:34:40
* @LastEditors: mulingyuer
* @Description: i18n
* @FilePath: \casino-xian\src\language\core\i18n.ts
* 怎么可能会有bug!!!
*/
import { createI18n } from "vue-i18n";
import Language from "./language";
//创建实例
const i18n = createI18n({
// locale: getItem("language") || defaultLocale, //读取本地存储的语言
datetimeFormats: {},
numberFormats: {},
globalInjection: true, //是否全局注入
// fallbackLocale: "en", //回退用的语言
messages: {
// en: require("../lang/en.json"),
},
});
const language = new Language(i18n, {
supportLocales: ["en", "zh"],
defaultLocale: "en",
});
export { language };
export default i18n;
index.ts
/*
* @Author: mulingyuer
* @Date: 2022-03-04 11:30:23
* @LastEditTime: 2022-05-30 16:34:59
* @LastEditors: mulingyuer
* @Description: 多语言
* @FilePath: \casino-xian\src\language\index.ts
* 怎么可能会有bug!!!
*/
import i18n, { language } from "./core/i18n";
import { $toop } from "./core/customization";
//方便的获取i18n的方法
const $t = i18n.global.t;
export { language, $toop, $t };
export default {
install(app: any) {
//全局注册自定义方法
app.config.globalProperties.$toop = $toop;
//注册i18n
app.use(i18n);
},
};
基本处理都在这里了,下面是使用!
vue3使用
main.ts中注册
import { createApp } from "vue";
import App from "./App.vue";
import VueI18n from "./language";
createApp(App).use(VueI18n);
路由守卫中使用
因为语言采用异步加载的方式引入,所以需要保证在页面首次进入时加载了一份语言包,如果在i18n.ts
初始化i18n实例时默认在messages中require引入了一份语言,其实就可以考虑不用再路由守卫中处理首次加载的问题,因为你一定会有一份语言包的。
import { language } from "@/language";
router.beforeEach(async (to, from, next) => {
//首次加载语言文件
await language.firstLoadI18nLocale();
next();
});
切换语言
import { language } from "@/language";
language.loadLocaleMessages("zh");
基本使用就是这样了,后续可以将支持的语言导出,作为在vue中for循环遍历的数组啥的。
类封装的是通用的逻辑,如果业务变化需要改动,尽量改动i18n.ts
中的逻辑。
vue2封装
vue2版大同小异,需要注意的是i18n的使用,以及注册挂载的问题。目录结构也是一样的。
language.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-30 13:59:14
* @LastEditTime: 2022-05-30 16:39:40
* @LastEditors: mulingyuer
* @Description:
* @FilePath: \casino-vue2.0\src\language\core\language.ts
* 怎么可能会有bug!!!
*/
import { IVueI18n } from "vue-i18n";
import { getItem, setItem } from "@/utils";
import Vue from "vue";
import { LanguageOptions } from "../types";
class Language {
private i18n: IVueI18n; //i18n实例
private defaultLocale = "en"; //默认语言
private SUPPORT_LOCALES: Array<string> = []; //支持的语言数组
private localKey = "language"; //本地持久化key
private loadLocale: Array<string> = []; //已加载的语言
constructor(i18n: IVueI18n, options: LanguageOptions) {
this.i18n = i18n;
if (options) {
this.initOptions(options);
}
}
private initOptions(options: LanguageOptions): void {
const { defaultLocale, supportLocales, localKey, loadLocale } = options;
if (typeof defaultLocale === "string" && defaultLocale.trim() !== "") {
this.defaultLocale = defaultLocale;
}
if (Array.isArray(supportLocales)) {
this.SUPPORT_LOCALES = supportLocales;
}
if (this.SUPPORT_LOCALES.length === 0) {
this.SUPPORT_LOCALES.push(this.defaultLocale);
}
if (typeof localKey === "string" && localKey.trim() !== "") {
this.localKey = localKey;
}
if (Array.isArray(loadLocale)) {
this.loadLocale = loadLocale;
}
}
/**
* @description: 设置语言
* @param {string} locale 语言
* @Date: 2022-05-16 16:48:21
* @Author: mulingyuer
*/
private setI18nLanguage(locale: string) {
this.i18n.locale = locale;
document.querySelector("html")?.setAttribute("lang", locale);
setItem(this.localKey, locale);
}
/**
* @description: 异步加载语言并使用
* @param {string} locale 语言
* @Date: 2022-05-16 15:25:55
* @Author: mulingyuer
*/
public async loadLocaleMessages(locale: string): Promise<void> {
try {
//是否已经加载了
if (this.loadLocale.includes(locale)) {
this.setI18nLanguage(locale);
return Vue.nextTick();
}
//不合法的语言
if (!this.SUPPORT_LOCALES.includes(locale)) {
//使用默认的语言
return this.loadLocaleMessages(this.defaultLocale);
}
//加载语言
const messages = await import(/* webpackChunkName: "locale-[request]" */ `../lang/${locale}.json`);
//设置i18n的messages
this.i18n.setLocaleMessage(locale, messages.default);
this.loadLocale.push(locale);
this.setI18nLanguage(locale);
return Vue.nextTick();
} catch (error) {
console.error(`获取语言文件失败:${locale}`, error);
return Vue.nextTick();
}
}
/**
* @description: 首次加载语言文件
* @Date: 2022-05-16 17:18:33
* @Author: mulingyuer
*/
public async firstLoadI18nLocale(specifyLang?: string) {
const htmlLang = this.getDocumentLanguage();
const localLang = getItem(this.localKey);
const systemLang = this.getStystemLanguage();
const lang = specifyLang || localLang || systemLang || htmlLang || this.defaultLocale;
await this.loadLocaleMessages(lang);
this.firstLoadI18nLocale = () => Promise.resolve();
}
/**
* @description: 获取系统语言
* @Date: 2022-05-18 19:39:48
* @Author: mulingyuer
*/
private getStystemLanguage() {
const lang = navigator.language;
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
/**
* @description: 获取文档语言
* @Date: 2022-05-19 10:54:51
* @Author: mulingyuer
*/
private getDocumentLanguage() {
const lang = document.documentElement.getAttribute("lang");
return lang ? lang.substring(0, 2).toLocaleLowerCase() : "";
}
}
export default Language;
types.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 14:18:51
* @LastEditTime: 2022-05-30 16:06:38
* @LastEditors: mulingyuer
* @Description: 类型声明
* @FilePath: \casino-vue2.0\src\language\types.ts
* 怎么可能会有bug!!!
*/
//api中的info字段
export type Info = string | null | undefined;
//language类的options
export type LanguageOptions = {
defaultLocale?: string;
supportLocales: Array<string>;
localKey?: string;
loadLocale?: Array<string>;
};
customization.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-19 17:24:44
* @LastEditTime: 2022-05-30 16:44:18
* @LastEditors: mulingyuer
* @Description: 自定义转换方法
* @FilePath: \casino-vue2.0\src\language\core\customization.ts
* 怎么可能会有bug!!!
*/
import i18n from "./i18n";
const $t = i18n.t.bind(i18n);
/**
* @description: 多语言保底输出 tOriginalOutput
* @param message 文本
* @Date: 2022-05-19 19:36:23
* @Author: mulingyuer
*/
export function $toop(message: string, backMessage: any = "") {
const langMessage = $t(message);
//如果没有对应的文本,用最后一个.后面的内容作为翻译文本
if (langMessage === message) {
return backMessage;
}
return langMessage;
}
i18n.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 15:55:00
* @LastEditTime: 2022-05-30 16:44:13
* @LastEditors: mulingyuer
* @Description:i18n
* @FilePath: \casino-vue2.0\src\language\core\i18n.ts
* 怎么可能会有bug!!!
*/
import Vue from "vue";
import VueI18n from "vue-i18n";
import Language from "./language";
Vue.use(VueI18n);
//创建实例
const i18n = new VueI18n({
silentTranslationWarn: true,
// locale: defaultLocale,
// fallbackLocale: "en", //回退用的语言
// messages: {
// en: require("../lang/en.json"),
// },
});
const language = new Language(i18n, {
supportLocales: ["en", "zh"],
defaultLocale: "en",
});
export { language };
export default i18n;
因为vue3版和vue2的ts类型声明不一样,你会发现在实例化i18n
的时候,部分参数不同。
index.ts
/*
* @Author: mulingyuer
* @Date: 2022-05-20 15:55:00
* @LastEditTime: 2022-05-30 16:44:35
* @LastEditors: mulingyuer
* @Description: 多语言
* @FilePath: \casino-vue2.0\src\language\index.ts
* 怎么可能会有bug!!!
*/
import i18n, { language } from "./core/i18n";
import { $toop } from "./core/customization";
//方便的获取i18n的方法
const $t = i18n.t.bind(i18n);
export { language, $toop, $t };
export const installI18n = {
install(app: any) {
//全局注册方法
app.prototype.$toop = $toop;
},
};
export default i18n;
细心点你会发现,在vue3版中,我们从i18n
实例上获取t
是直接拿到的函数,而在vue2版需要进行绑定this指向为i18n
实例自身。
应该是vue3版改动较大,支持这种用法,也可能是凑巧,如果以后不行了,可以尝试也进行一次bind。
vue2版使用
main.ts中注册
import Vue from "vue";
import App from "./App.vue";
import i18n, { installI18n } from "@/language";
Vue.use(installI18n);
new Vue({
i18n,
render: (h) => h(App),
}).$mount("#app");
路由守卫中使用
因为语言采用异步加载的方式引入,所以需要保证在页面首次进入时加载了一份语言包,如果在i18n.ts
初始化i18n实例时默认在messages中require引入了一份语言,其实就可以考虑不用再路由守卫中处理首次加载的问题,因为你一定会有一份语言包的。
import { language } from "@/language";
router.beforeEach(async (to, from, next) => {
//首次加载语言文件
await language.firstLoadI18nLocale();
next();
});
切换语言
import { language } from "@/language";
language.loadLocaleMessages("zh");
基本使用就是这样了,后续可以将支持的语言导出,作为在vue中for循环遍历的数组啥的。
类封装的是通用的逻辑,如果业务变化需要改动,尽量改动i18n.ts
中的逻辑。
自定义语言处理
在上面的代码封装中,我们封装了一个$toop
的函数,这个函数会挂载到vue的全局。
为此,我们可以直接在template
模板语法中使用。
<template>
<div>
{{ $toop("xxx.xxx.xx","没有对应的值,采用我这个回退值") }}
</div>
</template>
这个方法的考虑:
语言包文件为了更好的语义化和维护,肯定会有层级嵌套的,即便他只是一个json文件,他可能会是这种结构:
{
"a": {
"b": {
"c" : "value"
}
}
}
但是有时候你不能保证这个层级下一定会有你的值,比如这个值可能是一个动态的后端返的值,你的语言包只存在旧的数据,新的数据没有这个,那么$t
它会默认将你的key字符串原样返回,显然这并不能很好的回退,所以我自定义了$toop
,我会使用第二个参数作为回退的值。
更多的自定义可以自己去尝试。
也正是因为我需要全局注册自定义方法,所以index.ts
看上去好像有些复杂。
vue-i18n官网
vue2版:vue-i18n
vue3版:vue-i18n
一些注意事项
- vue-i18n的语言包val值只能是字符串,数字会被视为不存在对应的翻译,如:
{
"name": 1
}
这种会被视为不存在翻译,仔细一想也是,多语言本来就是翻译字符串的,这么做好像也没错,但是我们平时使用还是得注意下。
- 关闭警告
在new i18n实例的时候配置一下即可:
//创建实例
const i18n = createI18n({
// locale: getItem("language") || defaultLocale, //读取本地存储的语言
datetimeFormats: {},
numberFormats: {},
globalInjection: true, //是否全局注入
// fallbackLocale: "en", //回退用的语言
messages: {
// en: require("../lang/en.json"),
},
silentTranslationWarn: true, //是否显示警告
silentFallbackWarn: true, //是否显示回退警告
});
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
全部评论 2
vue2
Google Chrome Windows 10对于vue2来货,这个没有啊, 请问有没有什么解决方法?
import { getItem, setItem } from "@/utils";
木灵鱼儿
FireFox Windows 10