前情提要
最近維護了一個微信小程序的老項目,維護的其中一項是添加國際化。由於踩了蠻多坑,所以就有了這篇文檔!!!
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
`有沒有/有哪些副作用?