对国际化 i18n 项目的一点思考

238次阅读  |  发布于1年以前

国际化是什么?

国际化 对应的英文单词为 Internationalization,又称 **i18n**:

如果你的项目是 Vue,那么相信你在实现国际化功能时,也必不可少的会使用到 **vue-i18n**[2] 这个库,接下来本文也是通过这个库搭配 Vue 实现最基本的国际化功能,但关注点并不是如何使用这个库,而是在实现的过程中思考 可优化的点

实现基本国际化功能

这里就不再多余演示 demo 项目的创建过程了,并且文中只演示最基本的 中英文 切换,好了现在直奔核心吧!

05873B02.jpg

集成 vue-i18n

安装依赖

熟悉的命令:npm install vue-i18n \-S

配置 vue-i18n

image.png

import { createI18n } from "vue-i18n"; import zh from './lang/zh'; import en from './lang/en'; const i18n = createI18n({ legacy: false, locale: "zh", // 初始化配置语言 messages: { zh, en, }, });

export default i18n; 复制代码


### main.js 注册 i18n

内容非常简单,直接上代码:

```js
import { createApp } from "vue";
import i18n from "./language";
import store from "./store";
import App from "./App.vue";

createApp(App)
  .use(store)
  .use(i18n)
  .mount("#app");
复制代码

实际上在通过 use(i18n) 时,会调用 i18n.install() 方法,大概内容如下:

 const unmountApp = app.unmount;
app.unmount = () => {
  i18n.dispose();
  unmountApp();
};
复制代码

根据数据信息填充国际化内容

页面渲染

假设需要渲染如下数据对应的列表,并且要实现国际化:

const data = [
  {
    url: vueImg,
    title: 'Vue',
    describe: '渐进式 JavaScript 框架'
  },
  {
    url: reactImg,
    title: 'React',
    describe: '用于构建用户界面的 JavaScript 库'
  },
  {
    url: angularImg,
    title: 'Angular',
    describe: '现代 Web 开发平台'
  },
  {
    url: nodeImg,
    title: 'Node',
    describe: 'Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时'
  },
  {
    url: webpackImg,
    title: 'Webpack',
    describe: 'webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具'
  },
];
复制代码

对应 App.vue 组件模板内容如下:

<template>
  <button class="btn" @click="changeLang">{{ $t("中/英") }}</button>
  <List :data="data" />
</template>
复制代码

对应的页面效果如下:

image.png

填充 lang 目录下映射关系

lang/zh.js 文件中:

export default {
  "渐进式 JavaScript 框架": "渐进式 JavaScript 框架",

  "用于构建用户界面的 JavaScript 库": "用于构建用户界面的 JavaScript 库",

  "现代 Web 开发平台": "现代 Web 开发平台",

  "Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时":
    "Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时",

  "webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具":
    "webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具",

  "中/英": "中/英",
};
复制代码

lang/zh.js 文件中:

export default {
  "渐进式 JavaScript 框架": "Progressive JavaScript framework",

  "用于构建用户界面的 JavaScript 库":
    "JavaScript library for building user interface",

  "现代 Web 开发平台": "Modern web development platform",

  "Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时":
    "Node.js is a JavaScript runtime based on the chrome V8 engine",

  "webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具":
    "Webpack is a static module packaging tool for modern JavaScript applications",

  "中/英": "Chinese / English",
};
复制代码

<List /> 组件中进行翻译处理

翻译处理可通过如下方式处理:

这里选择第一种,因为它更灵活,能使用的范围也更广,指令和组件形式限定在了 template 中:

效果演示

1.gif

优化 i18n 配置

基于以上简单的例子,已经能够实现了国际化切换功能,但其中需要考虑的优化点还不少,下面的内容仅属于 抛转引玉,不一定全面。

05F51BEA.jpg

优化翻译文件中的 key

现在很明显的一点,就是 zh.js、en.js 文件中用于映射的 key 太长了,导致整个文件看起来会很多、很乱,因此我们可以将对应的 key 进行精简,如下:

// zh.js
export default {
  "Vue 简介": "渐进式 JavaScript 框架",

  "React 简介": "用于构建用户界面的 JavaScript 库",

  "Angular 简介": "现代 Web 开发平台",

  "Node 简介": "Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时",

  "Webpack 简介":
    "webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具",

  "中/英": "中/英",
};

// en.js
export default {
  "Vue 简介": "Progressive JavaScript framework",

  "React 简介": "JavaScript library for building user interface",

  "Angular 简介": "Modern web development platform",

  "Node 简介": "Node.js is a JavaScript runtime based on the chrome V8 engine",

  "Webpack 简介":
    "Webpack is a static module packaging tool for modern JavaScript applications",

  "中/英": "Chinese / English",
};
复制代码

那么对应到外部传入需要渲染的数据源 data 就可以简写为:

const data = [
  {
    url: vueImg,
    title: "Vue",
    describe: "Vue 简介",
  },
  {
    url: reactImg,
    title: "React",
    describe: "React 简介",
  },
  {
    url: angularImg,
    title: "Angular",
    describe: "Angular 简介",
  },
  {
    url: nodeImg,
    title: "Node",
    describe: "Node 简介",
  },
  {
    url: webpackImg,
    title: "Webpack",
    describe: "Webpack 简介",
  },
];
复制代码

另一种精简方式就是将 key 用对应的类似于 变量命名 的方式来定义,但我个人不是很喜欢这种方式,首先在语义上很难直接读出相关信息,而且很难用一两个英文单词去概括文字内容,而且在后期需要排查对应问题并需要定位 tmeplate 时是极其不方便.

转换翻译文件类型

.js 转 .json

上述的翻译文件是 .js 文件,因此,为了能够让其能被其他文件导入,我们不得不在文件中使用 export defualtexport 将对应文件内容向外导出,但其实我们可以将 .js 文件转换为 .json 文件直接使用,如下:

excel 转 .json

在实际项目中翻译的内容通常是由业务专门找对应的翻译人员提供的,而真正到了开发者手中往往就是一个 excel 类型的表格文件,如果我们使用的是前面的纯 json 方式,免不了要自己一个一个从表格中复制对应的内容到我们对应的 .json 文件中,而且是分别填充到 zh.jsonen.json 中,值得注意的是现在才是仅支持两个国家的语言,如果后续支持的国家变多,那么手动复制的方式岂不是要

因此,最好的做法是我们根据业务方提供的表格自动转成 json 格式的数据,避免不必要的手动操作,用命令帮我们处理这个内容:

  const xlsx2json = require("xlsx-to-json");
const path = require("path");

xlsx2json(
{
  input: path.join(__dirname, "./i18n.xlsx"),
  output: path.join(__dirname, "./i18n.json"),
},
function (err, result) {
  if (err) {
    console.error(err);
  } else {
    console.log(result);
  }
}
);
复制代码
  import { createI18n } from "vue-i18n";
import i18njson from "./i18n.json";

// 动态获取 message 信息
function getMessage() {

const messages = { zh: {}, en: {} }; i18njson.forEach(({Short, Chinese, English}) => { messages.zh[Short] = Chinese; messages.en[Short] = English; }); return messages; }

const i18n = createI18n({ legacy: false, locale: "zh", // 初始化配置语言 messages: getMessage(), });

export default i18n; 复制代码


- 提供 `i18n.xlsx` 文件作为数据源

![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/48e8dc4d4cf8411e9884d42f1f4b9728~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp?)

- 在 `package.json` 问文件中添加对应转换命令

```js
"scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "i18n": "node ./src/language/excel2json.js"
  }
复制代码

具体效果如下:

升级为 i18n 系统

上面已经将对应的翻译包从 *.xlsx 转成了 *.json 的形式,这样一定程度上能减少重复劳动力,但在 复用/协作 方面还是不够理想,因此可以从更高的维度将这整个内容升级到 i18n 系统,并提供对应的翻译包上传、自动解析、去重、添加命名空间等功能,再加上对应的列表管理功能,重点是可供多人员、多系统进行 复用/协作,对前端来讲就可以通过 接口 获取对应的翻译包数据,也能减少前端最终构建产物的体积。

处理翻译文件中重复的内容

06A99BDD.gif

什么叫重复的内容呢?其实很简单,比如有个文字内容为 确认 按钮,它在 A 页面需要翻译为 Confirm,它在 B 页面需要翻译为 OK,而它的中文内容就是 **确认**,意味着它对应的数据内容为:

[
  { "Short": "确认", "Chinese": "确认", "English": "Confirm" },
  { "Short": "确认", "Chinese": "确认", "English": "OK" }
]
复制代码

但这样其实是不行的,这样最终会被后面的内容覆盖掉,即 A、B 页面最终的翻译内容都为 OK,因为数据中的 Short 其实就是最终的不同语言映射的 key,如下:

const messages = {
    zh: {
     "确认": "确认",
     "确认": "确认"
    },
    en: {
     "确认": "Confirm",
     "确认": "OK"
    }
};
复制代码

既然这样,那么其实我们只要为不同的翻译内容设置不同的 Short 值即可,如下:

[
  { "Short": "确认1", "Chinese": "确认", "English": "Confirm" },
  { "Short": "确认2", "Chinese": "确认", "English": "OK" }
]
复制代码

这样变动小,并且也没有丢失掉原本的语义。

考虑不同语言的样式

由于不同语言的表现形式不同,内容长度也不一致,因此在前端进行展示时,就必须要考虑到最终的显式问题,否则一旦切换语言环境那么一定会导致页面的布局展示出现问题,处理方式无非几种:

考虑后端接口语言环境变更

一个项目的国际化不可能都是前端来实现的,一些接口动态返回的内容也是需要后端去处理的,通常接口的请求头中会存储一个用于标识当前页面语言环境的字段,然后再决定返回给前端页面的具体内容。

06D25789.jpg

基于前面处理的国家化切换功能,本身是会基于 vue 的响应式来切换翻译内容的,即不会刷新页面,因此在切换对应翻译内容后,同样需要修改后续接口请求头中的语言环境。

但这样还是有问题的,已经通过接口返回的数据内容,此时没有办法切换成对应的翻译内容,因为当前的国际化切换是基于页面的变动,但基于接口变动的部分还没重新请求获取新的内容,那怎么处理呢?

最后

以上内容是基于国际化功能的一点思考,文中对应的思考点是笔者自己在项目中遇到的点,并不一定适合所有项目,当然也期待评论区给出更多、更好的方案。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8