对国际化 i18n 项目的一点思考
点击上方 前端Q,关注公众号
回复加群,加入前端Q技术交流群
国际化是什么?
国际化 对应的英文单词为 Internationalization,又称 **i18n
**:
i
为单词的 【第一个】 字母18
为 【**i
和n
之间**】 单词的个数n
代表这个单词的 【最后一个】 字母
如果你的项目是 Vue
,那么相信你在实现国际化功能时,也必不可少的会使用到 **`vue-i18n`**[2] 这个库,接下来本文也是通过这个库搭配 Vue
实现最基本的国际化功能,但关注点并不是如何使用这个库,而是在实现的过程中思考 可优化的点。
实现基本国际化功能
这里就不再多余演示 demo
项目的创建过程了,并且文中只演示最基本的 中英文 切换,好了现在直奔核心吧!
集成 vue-i18n
安装依赖
熟悉的命令:npm install vue-i18n \-S
配置 vue-i18n
在 src
目录下创建language
目录用于保持和语言切换相关的内容在 language
目录下创建lang
目录用于保存不同语言的映射关系,如中文对应zh.js
、英文对应en.js
等在 language
目录下创建index.js
作为默认导出,并在其中创建i18n
对象
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()
方法,大概内容如下:
通过 app.provide(app.__VUE_I18N_SYMBOL__, i18n)
将i18n
对象提供给应用中的所有后代组件可通过inject
注入通过 app.config.globalProperties.xxx = xxx
的方式为应用添加 全局属性/方法,实际上是对Vue2
中Vue.prototype
使用方式的一种替代常见全局属性,如 $i18n
通过app.config.globalProperties.$i18n = i18n
添加到全局常见全局方法,如: $t, $rt, $d, $n, $tm
通过Object.defineProperty(app.config.globalProperties, `$${method}`, desc)
添加到全局通过 aplly(...)
方法注册常用的 全局指令v-t
和 全局组件i18n
等在根组件卸载时移除/释放 i18n
相关内容const unmountApp = app.unmount;
app.unmount = () => {
i18n.dispose();
unmountApp();
};
复制代码注册 vue-devtools
的相关插件
根据数据信息填充国际化内容
页面渲染
假设需要渲染如下数据对应的列表,并且要实现国际化:
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>
复制代码
对应的页面效果如下:
填充 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 />
组件中进行翻译处理
翻译处理可通过如下方式处理:
使用 $t(...)
方法使用 v-t
指令使用 <i18n-t></i18n-t>
组件
这里选择第一种,因为它更灵活,能使用的范围也更广,指令和组件形式限定在了 template
中:
效果演示
优化 i18n 配置
基于以上简单的例子,已经能够实现了国际化切换功能,但其中需要考虑的优化点还不少,下面的内容仅属于 抛转引玉,不一定全面。
优化翻译文件中的 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 defualt
或 export
将对应文件内容向外导出,但其实我们可以将 .js
文件转换为 .json
文件直接使用,如下:
excel 转 .json
在实际项目中翻译的内容通常是由业务专门找对应的翻译人员提供的,而真正到了开发者手中往往就是一个 excel
类型的表格文件,如果我们使用的是前面的纯 json
方式,免不了要自己一个一个从表格中复制对应的内容到我们对应的 .json
文件中,而且是分别填充到 zh.json
和 en.json
中,值得注意的是现在才是仅支持两个国家的语言,如果后续支持的国家变多,那么手动复制的方式岂不是要
因此,最好的做法是我们根据业务方提供的表格自动转成 json
格式的数据,避免不必要的手动操作,用命令帮我们处理这个内容:
安装依赖
npm install xlsx-to-json
将对应的转换操作封装
excel2json.js
文件中,基于第三方库简单封装即可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);
}
}
);
复制代码修改
i18n
配置的入口文件src\language\index.js
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
系统,并提供对应的翻译包上传、自动解析、去重、添加命名空间等功能,再加上对应的列表管理功能,重点是可供多人员、多系统进行 复用/协作,对前端来讲就可以通过 接口 获取对应的翻译包数据,也能减少前端最终构建产物的体积。
处理翻译文件中重复的内容
什么叫重复的内容呢?其实很简单,比如有个文字内容为 确认 按钮,它在 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" }
]
复制代码
这样变动小,并且也没有丢失掉原本的语义。
考虑不同语言的样式
由于不同语言的表现形式不同,内容长度也不一致,因此在前端进行展示时,就必须要考虑到最终的显式问题,否则一旦切换语言环境那么一定会导致页面的布局展示出现问题,处理方式无非几种:
允许文字内容换行展示,在文字发生换行时,要通过
CSS
设置按完整词换行不允许换行的,就要控制固定宽度,超出部分打点展示,鼠标移入展示全部内容等
单独为不同语言环境设置样式,具体还是得看展示需求,如需要考虑不同语言环境下文字的对齐方式、文字间距等
针对难以处理的翻译内容,可以通过和业务沟通是否可以替换翻译内容、缩减文字长度等等
考虑后端接口语言环境变更
一个项目的国际化不可能都是前端来实现的,一些接口动态返回的内容也是需要后端去处理的,通常接口的请求头中会存储一个用于标识当前页面语言环境的字段,然后再决定返回给前端页面的具体内容。
基于前面处理的国家化切换功能,本身是会基于 vue
的响应式来切换翻译内容的,即不会刷新页面,因此在切换对应翻译内容后,同样需要修改后续接口请求头中的语言环境。
但这样还是有问题的,已经通过接口返回的数据内容,此时没有办法切换成对应的翻译内容,因为当前的国际化切换是基于页面的变动,但基于接口变动的部分还没重新请求获取新的内容,那怎么处理呢?
前端切换语言环境后,重新刷新页面,让接口也重新获取新的内容 后端在返回数据时,将对应的不同语言环境的翻译内容一起返回,由前端根据语言环境决定如何渲染 将所有的翻译内容全部交由前端管理,一开始就初始化好各个语言环境对应的翻译内容
最后
以上内容是基于国际化功能的一点思考,文中对应的思考点是笔者自己在项目中遇到的点,并不一定适合所有项目,当然也期待评论区给出更多、更好的方案。
关于本文
作者:熊的猫
https://juejin.cn/post/7131737709231472670
往期推荐
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...