3.2 国际化

0x00 前言

本文将系统讲解 element 国际化方案实现,项目内置了 51 门语言。src/locale 目录下存放了语言配置文件、国际化处理方法、文本格式等,下面将逐一讲解,耐心读完,相信会对您有所帮助。

0x01 国际化方案

element 的国际化方案没有引入第三方插件如 vue-i18n,通过自定义实现的。它兼容 vue-i18n,搭配使用能十分方便。

📁 src/locale/lang

用于存放支持语言的配置文件,默认语言为简体中文语言(zh-CN),src/locale/lang/zh-CN.js文件返回一个 JSON 格式的数据,内容为各组件相关描述翻译。

export default {
  el: {
    // 组件 select 选择器
    select: {
      loading: "加载中",
      noMatch: "无匹配数据",
      noData: "无数据",
      placeholder: "请选择",
    },
    // ...
  },
};

若需要使用其他的语言,在该目录下添加一个语言配置文件即可。

📃 src/locale/format.js

提供字符串模板函数,支持索引或命名关键字参数。 代码实现参考 npm/string-template

// 核心方法

/**
 * 字符串模板函数,支持索引或命名关键字参数
 * @param {String} string 模板字符串
 * @param {Array} ...args 参数
 * @return {String}  格式化文本
 *
 */
function template(string, ...args) {
  const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
  if (args.length === 1 && typeof args[0] === "object") {
    args = args[0];
  }

  if (!args || !args.hasOwnProperty) {
    args = {};
  }

  return string.replace(RE_NARGS, (match, prefix, i, index) => {
    let result;
    if (string[index - 1] === "{" && string[index + match.length] === "}") {
      return i;
    } else {
      result = hasOwn(args, i) ? args[i] : null;
      if (result === null || result === undefined) {
        return "";
      }
      return result;
    }
  });
}

📃 src/locale/index.js

index.js提供三个方法 useti18n 用于语言切换设置、国际化处理、i18n插件兼容以及自定义。详细内容请看注释源码。

use 方法参数为语言配置内容,用于语言切换。若参数为空,默认使用简体中文(zh-CN)。

import defaultLang from "element-ui/src/locale/lang/zh-CN";
let lang = defaultLang; // 默认语言

// 语言切换
export const use = function (l) {
  lang = l || lang;
};

i18n 方法用于外部插件方法的兼容和自定义处理方法引入。项目 i18nHandler方法的实现就是用来兼容外部插件vue-i18n@5.x

若传入自定义处理方法, i18nHandler方法实现将被覆盖。

// i18nHandler 是一个 i18n 的处理方法,这块逻辑就是用来兼容外部的 i18n 方案如 vue-i18n@5.x。
let i18nHandler = function () {
  // 若引入插件 vue-i18n@5.x,会在 Vue 的原型上挂载 $t 方法。
  // i18nHandler 默认会尝试去找 Vue 原型中的 $t 函数,
  const vuei18n = Object.getPrototypeOf(this || Vue).$t;
  // 方法vuei18n存在
  if (typeof vuei18n === "function" && !!Vue.locale) {
    // 合并语言配置内容
    if (!merged) {
      merged = true;
      Vue.locale(
        Vue.config.lang,
        deepmerge(lang, Vue.locale(Vue.config.lang) || {}, { clone: true })
      );
    }
    // 使用vuei18n进行国际化处理
    return vuei18n.apply(this, arguments);
  }
};

// i18n 的处理方法(支持引入自定义方法)
export const i18n = function (fn) {
  i18nHandler = fn || i18nHandler;
};

t 方法用于组件国际化处理,它根据传入的 path 路径,从语言配置中找到对应的文案,然后在结合 options进行格式化处理。

// 默认语言 中文简体
import defaultLang from "element-ui/src/locale/lang/zh-CN";
// 字符串模板函数
import Format from "./format";

// 等同于 template() 方法
const format = Format(Vue);
let lang = defaultLang; // 默认语言

let i18nHandler = function () {
  // ...
};

// 国际化处理方法
export const t = function (path, options) {
  // 用来兼容外部的 i18n 方案如 vue-i18n@5.x 或自定义方法
  let value = i18nHandler.apply(this, arguments);
  // 优先使用外部方法
  if (value !== null && value !== undefined) return value;

  // 路径截取 获取属性层级
  const array = path.split(".");
  // 翻译内容
  let current = lang;

  // 根据路径中属性,层级递归翻译内容
  for (let i = 0, j = array.length; i < j; i++) {
    const property = array[i];
    value = current[property];
    // console.log(property, value);
    if (i === j - 1) {
      // 格式化内容
      // value 获取翻译内容   options 组件传入参数
      return format(value, options);
    }
    if (!value) return "";
    current = value;
  }
  return "";
};

0x02 组件国际化引用

接下来通过SelectPagination组件的示例讲解下国际化功能引入使用。

功能引入

前文可知 src/mixins/locale.js 引入src/locale/index.js中的 t 方法,通过 mixin 方式提供给组件页面使用。

import { t } from "element-ui/src/locale";

export default {
  methods: {
    t(...args) {
      return t.apply(this, args);
    },
  },
};

Select组件源码中可以看到国际化处理逻辑。

import Locale from "element-ui/src/mixins/locale";

export default {
  mixins: [Locale],
  name: "ElSelect",
  componentName: "ElSelect",
  computed: {
    emptyText() {
      if (this.options.length === 0) {
        return this.noDataText || this.t("el.select.noData");
      }
      // ...
    },
  },
  // ...
};

t(path)

this.t('el.select.noData') 调用 t 方法据传入的 path 路径值 el.select.noData,参数optionsundefined ,默认情况下没有引入第三方插件或自定义方法,直接从默认语言(简体中文)配置中找到对应的文案 。

此时 value 值为 “无数据”,参数options 为 “undefined”,使用 format 方法(即 src/locale/format.js定义的 template 方法 )进行文本格式化。

format('无数据', undefined)  =>  '无数据'

组件效果如下 👇。

t(path, options)

Pagination分页组件中, this.t('el.pagination.total', { total: this.$parent.total }) 传入 path 路径值 el.pagination.total,参数 options{total: 1000}

根据 el.pagination.total 获取值为 共 {total} 条,是一个模板字符串。

{
 el: {
   pagination: {
     goto: "前往",
     pagesize: "条/页",
     total: "共 {total} 条",
     pageClassifier: "页",
   },
   // ...
 }
}

此时 value 值为 共 {total} 条,参数options{total: 1000}, 使用 format 方法进行文本格式化。

format('共 {total} 条', {total: 1000})  =>  '共 1000 条'

不是所有的值都是纯文本,存在模板字符串。引入src/locale/format.js定义的 format 方法用于字符串格式化操作。

组件效果如下 👇。

0x03 项目国际化设置

通过上述篇幅详细讨论组件内部国际化方案实现。接下来介绍组件的使用中如何使用设置多语言,引入第三方插件、手动扩展方法等操作。

完整引入

使用 Vue.use()全局注册组件时,也可以传入一个可选的选项对象,locale 用于语言设置、i18n 自定义 i18n 的处理方法 。

import Vue from "vue";
import ElementUI from "element-ui";
import lang from "element-ui/lib/locale/lang/en";

import "element-ui/lib/theme-chalk/index.css";

Vue.use(ElementUI, {
  locale: lang,
  i18n: function (path, options) {
    // ...
  },
});

src/index.js 中的 install 方法中提供 locale.use()locale.i18n() ,接收参数 opts,用于国际化全局设置。

import locale from "element-ui/src/locale";
// ...

const install = function (Vue, opts = {}) {
  locale.use(opts.locale);
  locale.i18n(opts.i18n);
  // ...
};

按需引入

引入部分组件比如 ButtonSelect,可以使用两种方式进行注册:插件注册 Vue.use() 或 组件全局注册Vue.component();引入 locale 方法设置语言;引入 i18n 方法用于处理方法;。

import Vue from "vue";
import { Button, Select, locale, i18n } from "element-ui";
import lang from "element-ui/lib/locale/lang/en";
// import locale from 'element-ui/lib/locale'

import "element-ui/lib/theme-chalk/index.css";

// 设置语言  等同于 locale.use()
locale(lang);

// 设置i18n处理方法  等同于  locale.i18n()
i18n(function (path, options) {
  // ...
});

// 引入组件
Vue.use(Button);
Vue.component(Select.name, Select);

src/index.js 中导出的 localei18n方法。 locale 等同于locale.usei18n 等同于locale.i18n

import locale from "element-ui/src/locale";
// ...

const install = function (Vue, opts = {}) {
  // ...
};

export default {
  locale: locale.use,
  i18n: locale.i18n,
  install,
  Button,
  Select,
  // ...
};

搭配vue-i18n使用

项目也可引入第三方插件,如vue-i18n,最新版本为 8.x ,使用需要手动处理 (Element 兼容 5.x) 。

import Vue from "vue";
import Element from "element-ui";
import VueI18n from "vue-i18n";
import enLocale from "element-ui/lib/locale/lang/en";
import zhLocale from "element-ui/lib/locale/lang/zh-CN";

Vue.use(VueI18n);

const messages = {
  en: {
    message: {
      hello: "hello world",
    },
    ...enLocale, // 或者用 Object.assign({ message: {hello: "hello world"}} }, enLocale)
  },
  zh: {
    message: {
      hello: "你好世界",
    },
    ...zhLocale, // 或者用 Object.assign({ message: {hello: "你好世界"} }, zhLocale)
  },
};

// 创建 VueI18n 实例
const i18n = new VueI18n({
  locale: "zh", // 设置语言
  messages, // 语言翻译内容
});

//注册Element  自定义i18n 的处理方法
Vue.use(Element, {
  i18n: (key, value) => i18n.t(key, value),
});

new Vue({
  i18n,
  render: (h) => h(App),
}).$mount("#app");

测试页面

<template>
  <div id="app">
    <div>message.hello -- {{ $t("message.hello") }}</div>
    <div>el.select.noData -- {{ $t("el.select.noData") }}</div>
  </div>
</template>

<script>
  export default {
    name: "App",
  };
</script>

设置语言为zh, 页面输出内容:

设置语言为en, 页面输出内容:

最后更新于