Vue3以及element-plus最新版,国际化,多语言,高度封装。按模块区分语言文件,适应大型项目。减少冲突。

一、下载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.首先先看下我项目里面的目录结构吧


image.png

先看看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
不了解的可以点击了解
打断点让大家看看里面各值,帮助大家理解

image.png

image.png

image.png

@/locales/lang@是我配置的路径别名,下面贴出我配置的路径别名,希望后面看代码的时候你不会再有路径问题的疑问

image.png

接下来我们再看 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各模块的内容

image.png
image.png
image.png

image.png

注意几个地方: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')

好了,以上就是多语言封装的所有内容了。代码都有。直接复制到项目中就可以跑起来了。


下面贴出使用文档,以及一些说明

约定:

  1. 已集成 element-plus 因为用了文件名来做 key,所以语言文件格式命名按照文末语言列表里面的来命名,如果如下列表的语言都不存在,则需要自行添加 element-plus语言表,参考 element-plus 语言文件 <a>https://element-plus.org/#/zh-CN/component/i18n</a>

  2. 语言文件定义,为了尽可能适用大型项目,以减少合并冲突问题,已将将语言文件按模块划分。共通的语言文件在common模块中定义

  3. 使用者不需要关注如何实现,有多少种语言在对应的模块建对应的语言文件就可以了

  4. 语言文件的定义如下:(要注意最外层为唯一的模块名)

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>

  1. 每新增一种语言则需要在src/locales/lang文件夹中增加一种,写法参照里面的en.js
  1. 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

ConfigProvider链接文档地址

// 加载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>

关键点就是el-config-provider组件接收一个locale对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容