注册

让我们一起实现微信小程序国际化吧

常见的国际化方式

官方方案

官方链接,其实官方的解决方案最大的问题就是麻烦,主要体现在以下几个方面

强依赖目录结构

由于gulp.js里是按照此目录结构进行处理的,如果要维持自定义目录需要修改glup文件,如下图

57c3c9a26011c362966f7d606c914a01.png

特别好笑的一点官方示例里居然不是这个目录结构,不过依然是强依赖目录结构,因为gulp中路径是写死的

文档简陋

通过官方文档快速入门居然无法搭建起项目,暂时只用这种方式搭建起来了 官方github demo,通过对比发现好多必要代码都没有在文档中说明。


比如需要在app.js中开始便需要执行getI18nInstance(),否则全局都无法正常国际化。这么重要的信息居然在快速入门中没有说明


调试麻烦

每次修改代码都要重新执行npm run build,注意是每次


由于国际化必须通过npm run build来实现,而每次npm run build过后dist文件就会被覆盖,所以每次只能修改src,而小程序预览的却是dist文件,这也就导致必须频繁的执行build命令。下面演示一下增加一个表头的操作步骤

2021-05-22 07-57-21.2021-05-22 08_03_21.gif

说下优点

代码简洁。解释一下,从上图可以看到,他的书写方式和其他主流框架的国际化书写方式很类似(vue,react)。view层都是类似t(key,参数),对js侵入也很小,下面是上图页面对应的js部分。

import { I18nPage } from '@miniprogram-i18n/core'

I18nPage({
onLoad() {
this.onLocaleChange((locale) => {
console.log('current locale:', this.getLocale(), locale)
})

this.setLocale('zh-CN')
},

toggleLocale() {
this.setLocale(
this.getLocale() === 'zh-CN' ? 'en-US' : 'zh-CN'
)
},

nativate() {
wx.navigateTo({
url: '/pages/logs/logs'
})
}
})

可以说除了I18nPage以外没有别的侵入,剩下那些代码都是用于切换语言所需,如果只是最简单国际化,只需要I18nPage({})即可

聊一下为什每次都需build

其实咱们看下dist/i18n/locales.wxs文件即可

var fallbackLocale = "zh-CN";
var translations = {
"en-US": {
test: ["test messages"],
test2: ["test message 2, ", ["label"], ", ", ["label2"]],
nested: ["nested message: ", ["test"]],
toggle: ["Toggle locale"],
navigate: ["Navigate to Log"],
"window.title": ["I18n test"],
"index.test": ["Test fallback"],
navigate2: ["Navigation 2nd"],
},
"zh-CN": {
test: ["测试消息"],
test2: ["测试消息二, ", ["label"], ", ", ["label2"]],
nested: ["嵌套消息: ", ["test"]],
toggle: ["切换语言"],
navigate: ["跳转"],
"window.title": ["国际化测试"],
"index.test": ["备选"],
navigate2: ["导航2"],
},
};
var Interpreter = (function (r) {
var i = "";
function f(r, n) {
return r
? "string" == typeof r
? r
: r
.reduce(function (r, t) {
return r.concat([
(function (n, e) {
if (((e = e || {}), "string" == typeof n)) return n;
if (n[2] && "object" == typeof n[2]) {
var r = Object.keys(n[2]).reduce(function (r, t) {
return (r[t] = f(n[2][t], e)), r;
}, {}),
t = r[e[0]],
u = e[n[0]];
return void 0 !== u
? r[u.toString()] || r.other || i
: t || r.other || i;
}
if ("object" == typeof n && 0 < n.length) {
return (function r(t, n, e) {
void 0 === e && (e = 0);
if (!n || !t || t.length <= 0) return "";
var n = n[t[e]];
if ("string" == typeof n) return n;
if ("number" == typeof n) return n.toString();
if (!n) return "{" + t.join(".") + "}";
return r(t, n, ++e);
})(n[0].split("."), e, 0);
}
return "";
})(t, n),
]);
}, [])
.join("")
: i;
}
function c(r, t, n) {
t = r[t];
if (!t) return n;
t = t[n];
return t || n;
}
return (
(r.getMessageInterpreter = function (i, o) {
function e(r, t, n) {
var e, u;
return f(
((e = r),
(u = o),
((n = (r = i)[(n = n)]) && (n = n[e])) || c(r, u, e)),
t
);
}
return function (r, t, n) {
return 2 === arguments.length
? e(r, null, t)
: 3 !== arguments.length
? ""
: e(r, t, n);
};
}),
r
);
})({});

module.exports.t = Interpreter.getMessageInterpreter(
translations,
fallbackLocale
);
其实搞这么麻烦构建方式主要是为了生成这个wxs文件,我们之所以能在vue中看到{{t('key')}}的方式进行国际化,是因为wxml层本身支持函数调用且函数可以调用外部资源(国际化文件),而小程序只允许通过wxs(官方文档)的方式在页面使用函数,出于性能考虑又不允许wxs引用任何外部资源(除了其他的wxs),所以这个build最核心的诉求是把 国际化文件copy一份到wxs文件中。下面是wxs无法引用外部资源的官方说明
1bc628e20506c3cc1d555e4fdc4c7988.png

优化

我们国际化的核心诉求就是解决官方国际化问题同时保留他的优点,这里我们列下此次的目标

  •  路径灵活,不强依赖路径减少后期添加国际化的路径改动成本
  •  调试方便,和原始开发调试方式相同
  •  书写简洁,保持和vue一样的书写方式
2021-05-22 10-27-02.2021-05-22 10_28_39.gif

wxml代码

<wxs src="../wxs/i18n.wxs" module="i18n" />
<!-- 标题国际化 -->
<page-meta>
<navigation-bar title="{{i18n.t(locales['主页'])}}" />
</page-meta>
<!-- 一般国际化 -->
<view>{{i18n.t(locales['通往爱人家里的路总不会漫长。'])}}</view>
<!-- js国际化 -->
<view>{{jsMsg}}</view>
<!-- 支持变量国际化 -->
<view>{{i18n.t(locales['当前页面:{path}'],[{key:'path',value:'home-page/home-page'}])}}</view>
<!-- 切换中英文按钮 -->
<button bindtap="zhClick" type="default">{{i18n.t(locales['切换中文'])}}</button>
<button bindtap="enClick" type="warn">{{i18n.t(locales['切换英语'])}}</button>

js代码

const i18n = require('../behaviors/i18n');
// home-page/home-page.js
Component({
behaviors: [i18n],
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
zhClick() {
this.switchLanguage('zh_CN')
},
enClick() {
this.switchLanguage('en_US')
},
}
})

基本维持和官方相同的写法,尽可能少的代码写入

代码段链接

解决思路

利用behaviors(官方文档)将国际化文案进行引入每个页面。然后将所有国际化数据和key值以参数的形式传递给wxs函数,这样就可以避开wxs外部资源限制实现和vue i18n相同的效果。



  • behaviors负责将国际化方法和文案导入全局,以下是behaviors/i18n.js源码:

// behaviors/i18n.js

const {
t
} = require('../utils/index')
const i18n = Behavior({
data: {
language:{}, // 当前语种
locales: {}, // 当前语言的全部国际化信息
},
pageLifetimes: {
// 每次页面打开拉取对应语言国际化数据
show() {
if (this.data.language === 'en_US') {
this.setData({
locales: require('../i18n/en_US')
})
} else {
this.setData({
locales: require('../i18n/zh_CN')
})
}
}
},
methods: {
// 全局js国际化便捷调用
$t(key, option) {
return t(key, option)
},
// 由于tab只能通过js修改,所以每次语言切换需要重新更新tab国际化内容
refreshTab() {
wx.setTabBarItem({
index: 0,
text: this.data.locales['主页']
})
wx.setTabBarItem({
index: 1,
text: this.data.locales['我的']
})
},
// 切换语种
switchLanguage(language) {
this.setData({
language
})
if (language === 'zh_CN') {
this.setData({
locales: require('../i18n/zh_CN')
})
} else {
this.setData({
locales: require('../i18n/en_US')
})
}
// 切换下方tab
this.refreshTab()
},
}
})

module.exports = i18n
wxs负责为wxml层提供国际化方法,此处逻辑比较简单,先找到国际化文件中key值对应的语句,然后根据第二参数(arr)将变量进行替换,此处替换逻辑比较粗暴,使用{key}方式代表变量。如:"我的年龄是{age}",age代表变量,参数传递格式为 [{key:'xxx',value:'xxxx'}]

// 国际化.js
{
"ageText":"my age is {age}",
}
// wxml
<view>{{i18n.t(locales['ageText'],[{key:'age',value:'18'}])}}</view>

wxs源码如下:

var i18n = {
t: function (str, arr) {
var result = str;
if (arr) {
arr.forEach(function (item) {
if(result){
result = result.replace('{'+item.key+'}', item.value)
}
})
}
return result
}
}
module.exports = i18n

同时提供一个在js里获取国际化的util方法

// 国际化
const t = (key, option = {}) => {
const language = wx.getStorageSync('language');
let locales = null
if (language === 'en_US') {
locales = require('../i18n/en_US')
} else {
locales = require('../i18n/zh_CN')
}
let result = locales[key]
for (let optionKey in option) {
result = result.replace(`{${optionKey}}`, option[optionKey])
}
return result
}

module.exports = {
t
}

这样就基本实现了同时在wxml,js中进行国际化的基本需求,同时也解决了官方调试体验不足的缺点。

不足




  • 其实调试体验还不是那么完美,由于只有在show中初始化国际化文件内容,所以当开启“热重载”时对国际化文件进行修改,国际化内容不会自动进行更新




  • 每个page的js文件都需要引入behaviors/i18n.js,wxml文件引入<wxs src="../wxs/i18n.wxs" module="i18n" />有些略显繁琐




  • require('../i18n/xxx')整个项目引入了3遍略显繁琐,可以封装一下




  • i18n.t(locales['key']) 其中locals每次都要写一遍比较繁琐,不过由于wxs的限制也想不到太好的方法

使用建议


由于只是写一个demo很多逻辑并没有写完整,所以如果使用的话需要根据项目进行修改




  1. 如果i18n路径和命名方式不同需要同时修改behaviors/i18n.js,以及utils/index.js中的路径




  2. 现在每次刷新页面国际化都会被重置成中文,建议在behaviors/i18n.js的show方法中从全局获取当前的国际化语言。这样就不会每次都被重置成中文了。


链接:https://juejin.cn/post/6964963316493975588


0 个评论

要回复文章请先登录注册