微信小程序-国际化(miniprogram-i18n)
前情提要
最近维护了一个微信小程序的老项目,维护的其中一项是添加国际化。由于踩了蛮多坑,所以就有了这篇文档!!!
miniprogram-i18n
对除小程序外的其他框架开发做过国际化的朋友来说i18n这几个字母应该不陌生,i18n之所以叫i18n是因为次单词长度为20,以i开头以n结束,i和n之间间隔18位。
国际化全流程及踩坑
- 首先,在小程序项目中引入依赖。官方文档上有依赖安装位置及文件关系,详情可点击miniprogram-i18n查看,简单理解为,在小程序根目录下使用命令行安装依赖
npm i -D gulp @miniprogram-i18n/gulp-i18n-locales @miniprogram-i18n/gulp-i18n-wxml
👆这一步做了什么呢?
安装了gulp、和miniprogram-i18n的gulp插件。
👆为什么要安装这些呢?
为了打包生成翻译文件以及编译`.wxml
`文件。
👆为什么要打包呢?
这就是一个需要了解国际化原理的问题了,我将其理解为:在打包之前,我们写的语言翻译配置文件与页面还没有真正地联系起来,打包之后会生成一个`locales.js
`和`locales.wxs
`文件,配合`gulp-i18n-xml
`插件将`.wxs
`引入到每一个页面中,这样才能真正的实现页面翻译。
👆为什么要用`gulp-i18n-xml
`打包`.wxml
`文件呢?
因为按照官方时使用指南,我们在页面的使用中直接是`{{t('key')}}
`这样的方式进行文本翻译的,然而真正实现国际化的`.xml
`写法的语句是`<wxs src="生成的locale.wxs文件路径" module="i18n"/> <view>{{i18n.t('key', $_locale)}}</view>
`这样的。也就是说,使用此插件可以自动为`.wxml
`引入`.wxs
`并且将翻译的文本转换为实现所需要的格式,如此可以减少开发者的代码编写数量。
👆一定要使用`gulp-i18n-xml
`才能实现翻译吗?
不是。如上所说,此插件实际上只是减少开发者的代码编写数量,如果开发者手动引入文件,并在使用时以`i18n.t('key', $_locale)
`这样的形式实现文本翻译时,即可不用此插件。
顺便一说,如果要直接使用此插件生成翻译文件,那几乎是将现有文件完全生成一份新的到目标文件夹下,项目发布时,直接发布打包后的文件到生产。这对于从0开始的项目来说这并没有什么影响甚至能减少开发者工作量(也许),但如果是维护老项目的话,此举可能并不是明智的。
👆如果使用`i18n.t('key', $_locale)
`的形式实现,引入的文件从哪里获得呢?
前面说过,打包之后会生成一个`locales.js
`和`locales.wxs
`文件,引入的就是此`locales.wxs
`文件。
👆完全交由gulp工具打包需要注意什么呢?
如果是维护老项目,那么目录结构一般都是`pages、app.js、app.json、app.wxss、otherFolders/页面文件(夹),此时打包需要注意将同级目录每一个需要的文件都copy一份到目标文件夹下,并注意路径。如果是新项目可以将需要打包的文件全部放置于一个src文件夹内(注意:需要的文件多半还包括package.json,也就是src文件夹内外部可能都需要一个package.json文件,可参照官方example的目录结构搭建)。
注意:如果没有将`node_modules
`或`miniprogram_npm
`打包到目标文件夹下,需要在目标文件夹下执行`npm i
`和`构建npm
`
根据官方文档和example可以发现,官方打包前的文件目录结构是app.js/json/wxs以及page、i18n、静态资源文件夹等都由一个src文件夹包裹着,gulp配置文件中将i18n交由`gulp-i18n-locale
`处理,wxml文件交由`gulp-i18n-xml
`处理,src目录下的其他文件全部copy到目标目标生成文件夹下。
- 上一步我们已经往安装了依赖,现在需要在小程序中使用工具构建npm,如此才能在小程序js文件中成功引用依赖。
`工具
`->`构建npm
`
👆构建npm出错?
可能的原因是:未成功安装依赖,可以先卸载依赖再安装;当前构建npm的位置没有package.json文件;
- 上一步我们已经往小程序中注入了依赖,接下来就是如何使用的问题。通过官方文档可以直到需要在i18n文件夹内新建语言配置的json文件(比如en-US、zh-CN)
// en
{
"hello": "Hello"
}
// zh
{
"hello": "你好"
}
然后在需要使用国际化的页面的js中
...
import { I18n } from '@miniprogram-i18n/core';
Component({
behaviores: [I18n]
})
页面使用
<view>{{t('hello')}}</view>
<view>{{t('day', {day: '12'})}}</view>
👆必须要用Component吗?
我们在小程序官方文档发现`behaviors
`是Component构造器所有的,那么如果当前是Page怎么办呢?国际化官方文档中建议都使用Component构造器定义,否则就需要引入I18nPage代替Page构造器。然而在实践中发现直接在Page构造器中直接使用`behaviors
`并不会报错,i18n也能被正确引入(直到2022/01/19)。当然这只是针对维护老项目而言,也许这样做存在系列潜在风险,出于安全考虑,在使用Component构造器或者I18nPage都方便的前提下,最好还是不要尝试以上的方法。
👆为什么照官方文档做了,还是不能正常翻译?
如果在控制台看到这样的报错:
根据以上报错,提示我们在使用I18n之前确保在app.js文件 中运行了initI18n()。回顾我们之前的操作,没见过也没有运行过这一函数。不必惊慌,导致这一错误并不完全是我们的问题,因为官方的快速开始文档里确实没有对这一步的相关描述(不过在接口文档里有描述)。
按照控制台报错,我们在app.js中执行InitI18n()
...
import { initI18n } from '@miniprogram-i18n/core'
initI18n('en-US')
App({
...
})
再检查页面,此时翻译文本就正常显示了,并且控制台不再有出现上面描述的错误
- 为什么官方的select,我不能成功使用?
截止2022/01/19,本人尚未成功使用过select。根据文档,我想文档中特性部分的`目前 miniprogram-i18n 仅支持纯文本及文本插值,后续会对其他 i18n 特性进行支持。
`这句话也许是答案。
- 如何在js中使用i18n?
先说结果: this.t('hello') || this.t('day', {day : this.data.day}) // 文本插值语法
我在最初编写这篇博文的时候,是没有成功在自己的js中成功使用js的,尽管官方的example成功了,我几番比对都没有发现自己漏掉了哪一步。直到前天突然灵光一闪,会不会是js中使用i18n的机制问题。结果我将原本单独存放locale.*文件的i18n文件夹与国际化配置文件的i18n文件夹合并,都放在根目录下。这时在js中使用国际化成功!
- 什么时候build呢?
每当翻译配置文件有内容修改时。如果是手动引入locales.wxs,每当翻译配置文件有内容修改时,都需要build生成新的文件。
每当涉及国际化的文件有变动时。如果是自动build实现locales.wxs文件引入,每当wxml有国际化相关内容变动、翻译配置文件有内容修改时,都需要build重新生成。
本人项目参考
前提了解:维护老项目,没有一个用于包裹pages、utils、assets、app.*等文件(夹)的src文件夹。
- 安装依赖
- 构建npm
- 新建语言配置文件
在根目录下(与app.*文件同级)新建i18n文件夹,文件夹内新建两个json文件,分别是`en-US.json
`与`zh-CN
`
{
"index": "首页",
"hello": "你好{name}, 欢迎!"
...
}
{
"index": "Index",
"hello": "Hello {name}, Welcome!"
...
}
- gulp配置
在根目录下(与app.*文件同级)新建gulpfile.js文件。(从配置会在第一次build时,在i18n文件夹下生成locales.js和locales.wxs文件,之后每一次build这两个文件都会随着配置文件的更新而更新
1 const { src, dest, series } = require('gulp')
2 const gulpI18nWxml = require('@miniprogram-i18n/gulp-i18n-wxml')
3 const gulpI18nLocales = require('@miniprogram-i18n/gulp-i18n-locales')
4
5 function mergeAndGenerateLocales() {
6 return src('i18n/*.json')
7 .pipe(gulpI18nLocales({ defaultLocale: 'zh-CN', fallbackLocale: 'zh-CN' }))
8 .pipe(dest('i18n/'))
9 }
10 export.default = series(mergeAndGenerateLocales)
- package.json文件"script"配置
{
...
"script": {
"build": "gulp",
...
},
...
}
- i18n初始化
在app.js文件中
...
import { initI18n, getI18nInstance } from '@miniprogram-i18n/core'
const i18n = getI18nInstance()
initI18n("en-US")
App({
onLaunch: function() {
/** 获得本地语言 */
const lang = wx.getAppBaseInfo().language
/** 根据本地语言设置小程序语言 */
i18n.setLocale(lang.toLowerCase().includes('zh') ? 'zh-CN' : 'en-US')
}
})
- 在需要使用到国际化翻译页面的js文件引入I18n
...
const { I18n } = require('@miniproogram-i18n/core')
Component({
behabiors: [I18n],
...
})
// 或者
...
const { I18nPage } = require('@miniprogram-i18n/core')
I18nPage({
...
})
- 打包生成locale.*文件
在gulpfile.js文件所在文件夹,用命令行运行
npm run build
- 在需要使用到国际化翻译页面的wxml文件中引入并书写翻译文本
<wxs src="../..i18n/locales.wxs" module="i18n"></wxs>
<view class="warpper">
<view>{{ i18n.t('index', $_locale) }}</view>
<text>{{ i18n.t('hello', { name: 'Jone' }, $_locale) }}</text>
</view>
- 在需要使用到国际化翻译的js文件中
import { I18n } from "@miniprogram-i18n/core" Component({ behaviors: [I18n] data: {name: 'Developer'} methods: { OnLoad: function () { console.log(this.t('index'), this.t('hello', {name: this.data.name}) } } })
//或者
import { I18nPage } from "@miniprogram-i18n/core"
I18nPage({
data: {name: 'Lii'}
OnLoad: function () {
console.log(this.t('index'), this.t('hello', {name: this.data.name})
}
})
- 当翻译配置文件有变动的时候,重新build并更新i18n/文件夹下的两个文件
官方样本
在社区看到不少朋友有无法顺利打开官方提供的example的困扰,在这里也单独说一下。
- 先从glthub将example下载到本地,此时可以先不在开发者工具中打开此项目,即使打开页面也不会正常显示,因为根文件夹下没有app.*文件。
- 在gulpfile.js所在的文件位置,命令行运行。
- 此时会有一个新的`
dist
`文件夹,进入dist文件夹。 - 命令行依次运行。
npm install
npm run build
- 在微信开发者工具中导入dist文件夹这个项目(注意是dist)。
- 点击`
工具
`->`构建npm
` - 此时页面应该已经加载在模拟器中了,如果没有,可以点击`
预览
`(Windows系统也可以使用Ctrl+B快捷键)。
下载下来的example不能直接打开的可能原因一是:没有依赖。二是:根目录下没有`app.*
`文件。
example就是典型的将所有都交给gulp打包的案例,我们编辑的文件只是为了用于生成dist文件夹中的内容,而真正查看效果以及上传到版本的是dist内的生成的文件。
个人遗留问题
- 在Page构造器中使用`
behaviors
`有没有/有哪些副作用?