小程序多语言——@wooc/mini-i18n

mini-i18n

Multilingual Mini Program

一、前言

现有三方整合框架较多,如(Taro、UniApp)且支持框架不同(例:Vue + Taro、React + Taro),每种跟随框架的多语言包都依赖于该框架的特性,无法通用但性能较好。该库适合寻求框架通用、无三方依赖、且无频繁语言切换的场景使用。切换语言时会重新加载页面,造成一定的消耗。

当然也可以将该库结合三方框架的特性使用,如在 Vue 的插件中使用,将语言切换做为数据响应的出发条件使用。

优点

1、不依赖任何框架、通用性较好。接入的成本低,页面改动较少

2、 支持 微信 / 京东 / 百度 / 支付宝 / 字节跳动 / QQ 小程序

带来的问题

重载到 tabbar: 会清空 tabbar 的页面缓存及路由栈。支付宝小程序除外(支付宝使用 redirectTo)。非 tabbar 无此问题。

小程序特性

标题

Tabbar(自定义除外)、NavigationBarTitle 的设置需要通过 Api 单独设置,即使采用跟随框架的方式更新多语言,也需要额外提供函数,触发已加载页面的 Tabbar、NavigationBarTitle 的设置函数,方可更新。

接口

语言切换后,需要刷新所有接口并附带当前语言标记,让后端感知,并返回相应多语言的数据或错误提示。

跳转至小程序

1、公众号跳转至小程序页面(或小程序中的插件页面)

2、小程序跳转至小程序

3、H5 跳转至小程序

4、微信链接跳转至小程序

// 上述的跳转方式都可以通过一下 API 中拿到当前打开页面的信息来源
onAppShow:referrerInfo(来源信息)
getLaunchOptionsSync:referrerInfo(来源信息)scene(场景值)

插件

本地缓存、运行时缓存和宿主小程序共享内存但各自环境独立,并且没有全局的 生命周期,可以看做小程序中的一个组件。所以实现时主要需要考虑插件中的:

1、多语言工具不重复初始化

可以通过 i18n.getLanguagePackList(当前所有语言的集合)字段,判断长度是否大于0

2、宿主语言的语言环境参数传递

● 本地缓存:环境相互隔离。 此路不通

● 路径传参:可以实现。 plugin://hello-plugin/demo?lang=en_US

● 插件主文件:可以实现。 需要在跳转插件之前调用主文件中方法如下代码所示

// 插件主文件 index.js----------------------------------------
import { i18n } from '@wooc/mini-i18n'
import locales from '../../locales/index' // 引入脚本自动执行初始化操作

module.exports = {
  init(lang) {
    i18n.setLocales(lang || 'zh_CN') // 默认语言
  }
}

// 宿主环境使用-------------------------------------------------
const plugin = requirePlugin('hello-plugin')
Page({
  data: {},
  onLoad() {
    plugin.init('zh_CN') // 当前语言环境标记,可通过小程序 Api 获取
  },
})

3、页面渲染的更新

在初始化、更改语言后的页面已经渲染。

通过 this.setData({title1: t('home.device_list.room_manage')}) 实现页面的及时渲染

4、多语言接口动态更新语言

接口返回,通过 i18n.updateLocale(obj) 更新,等待下一次新进入的插件页面,即可更新。

各小程序特性

微信及其他小程序

非自定义,无法使用多语言。可通过 API 设置 tabbar 多语言

自定义:可使用i18n。

支付宝

非自定义:官方支持四种语言设置:中文、英文、zh-HK、zh-TW。

自定义:可使用i18n。

二、使用

安装

npm install @wooc/mini-i18n

创建 locales.js 语言文件

// index.ts
// locales 结构如下,将语言包的key值替换为 ua.ts 中对应的value的标记。具体内容请查看
// https://github.com/zhangchao-wooc/mini-i18n/blob/main/until/ua.ts
// 与react-i18n 要求基本相同
// {
//    "zh-Hans": zh
//      "en": "en"
//  }
// 语言文件为对象嵌套格式
import zh from './zh';
import en from './en';

export default {
  'zh-Hans': zh,
  en
}

app.ts / app.tsx

import { i18n, t } from '@wooc/mini-i18n'
import locales from './locales' // 语言文件

onLaunch (options) {
  i18n.init({
    locales,
    lang: 'zh_CN',
    defualtLang: 'en_US'
    isHint: true,
    themeColor: '#ff6600',
    homePath: '/pages/my/index'
  })
},

使用

{
  home: {
    list: {
      title: '家庭列表',
      go_to: '前往%'
    }
  }
}

1、获取词条
t('home.list.title')

2、动态拼接
t('home.list.go_to', '涂鸦智能', '%')
第二个参数为动态参数
第三个参数为占位符,默认为'%', 也可自定义。为了满足不同语言翻译时顺序不一致时词条的位置动态拼接
替换词条中的'%'为动态参数。输出: 前往涂鸦智能

小程序

// JS
import { t } from '@wooc/mini-i18n'

Page({
  data: {},
  onLoad() {
    this.setData({
      title: t('home.device_list.room_manage')
    })
  },
})

// WXML
<button id="add" bindtap="addItem">{{ title }}</button>

wxs 中无法引入外部 js 使用

React

import { i18n, t } from '@wooc/mini-i18n';

onShow () {
  wx.setNavigationBarTitle({title: t('login') })
}

<View className={style.tip}>{t('loginHint')}</View>
                             
                             
 <Button
    className={style.apBtn}
    style={{ backgroundColor: OEMConfig.extConfig.colorStyle.themaStyle,           position:'fixed',right: 0, top: '30%' }}         
    hoverClass={style.hoverclass}
    onClick={() =>
       i18n.setLocales(i18n.getLocales() === 'zh_CN' ? 'en_US' : 'zh_CN')
    }
 >切换语言</Button>                            

Vue

// plugins/t.js
export default {
  install: (app, options) => {
    app.mixin({ // 混入全局 methods 中, 否则在模版中无法使用。或是挂载到全局均可
      methods: {
        t (id: string) {
          return options(id)
        },
      }
    })
  }
}

// app.ts
import i18 from './plugins/t'

const App = createApp({

})

App.use(i18, t)

// 使用
import {i18n, t } from '@wooc/mini-i18n'

// 全局混入 methods 或 在 methods 中创建一个 t 函数,方可在模版引擎中使用
<view class="commodity-info_name">{{t(item.name)}}</view>

// setup创建时,组件实例并未创建,无法使用 methods 中的 t 函数,通过import引用方式,在 setup 中使用
setup(){
    const state = reactive({
      msg: t('home'),
      menuItems: [
        {
          name: t('chinese'),
          value: 'zh_CN'
        },
        {
          name: t('english'),
          value: 'en_US'
        },
      ],
    })
    onMounted(() => {
      Taro.setNavigationBarTitle({title: t('home')})
    })
    return {onMounted, ...toRefs(state)}
  })

小程序插件

小程序插件中没有全局生命周期,可以看做为小程序的组件。
// locales.js 初始化------------------------------------------
import { i18n, t } from '@wooc/mini-i18n'
import zh from './zh-Hans';
import en from './en';

const locales =  {
  'zh-Hans': zh,
  en
}

const isInit = Object.keys(i18n.allLangData)
isInit && i18n.init({ // 防止多次 init
  locales,
  lang: 'zh_CN',
  defualtLang: 'en_US',
  isHint: false,
})

// js 使用-------------------------------------------------------
import { i18n, t } from '@wooc/mini-i18n'

Page({
  data: {},
  onLoad(options) {
    const { lang } = options
    if(lang !== i18n.getLocales()) {
      i18n.setLocales({
        lang: 'zh_CN', 
        isReload: true, 
        path 'plugin://hello-plugin/hello-page?lang=zh'
      })
    }
    // setLocales(lang) 时页面已经渲染。使用setData 触发页面的数据更新
    this.setData({title: t('home.device_list.room_manage')})
  },
})

// wxml 使用-----------------------------------------------------
<text>
  {{title}}
</text>

三、API

1、init 初始化

i18n.init({
  locales: object;        // 兜底语言数据必须存在
  defualtLang?: string;   // 兜底语言    默认:'en_US'
  lang?: string;          // 当前显示语言  默认:'en_US'
  themeColor?: string;    // 主题颜色,用于全局提示时,颜色一致  默认:'#000'
  homePath: string;       // reload 默认跳转到的页面, 建议为首页,不建议插件中使用。
  isHint?: boolean;       // 是否显示语言切换提示
  isVerifiyApi?: boolean; // 是否校验当前环境mini- i18n Api 是否可用   默认:false
})

locales: { 'en': {home: '首页'}, 'zh': {home: 'Home'}}
结构中的key值,应与 dist/until/ua.js 中对应多语言的 value 值一致
https://github.com/zhangchao-wooc/mini-i18n/blob/main/until/ua.ts

defualtLang:获取当前容器中的多语言直接传入即可,i18n中已做转换并映射到 locales 对于语言数据

lang: 同上

themeColor:十六进制颜色。如 isHint 为 true,提示框中的按钮颜色。默认为 '#000' 
// 支付宝不支持
// 字节小程序不支持,自动跟随主题色。但字节小程序中有微信小程序的实例.所以复用微信小程序api,目前可以使用

homePath 
/*
 * '/pages/home/index' 重新加载指向的页面。插件中使用时通常带有动态参数,建议插件中使用
 * setLocales,updateLocale 中的自定义路径,方便携带动态参数
 */

isHint:是否弹出提示框,判断系统语言是否与当前小程序语言一致,如不一致弹出提示框是否切换?如下图
插件中不可使用:isHint,因为插件中没有 onAppShow API

isVerifiyApi: 
/*
 * 校验当前环境中api是否可用。插件中不可使用,因为插件中没有 canIUse 及其它众多 API
 * 工具中使用的 Api 在各容器中的版本建议不同,某些版本下不可使用
 * 由于 canIUse 在不同环境下表现不稳定,所以暴露是否开启配置项
 * 建议将开发工具、基础库升级到主流配置。同时查看小程序后台,查看当前用户使用的基础库版本,对于指定版本的用户推送
 * 升级消息
 */

isHint:

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6639433813034a3782f7a444080cb38c~tplv-k3u1fbpfcp-zoom-1.image" />

2、setLocales 语言切换

i18n.setLocales({
  lang: string, 
  isReload: boolean = false,
  path?: string,
  query?: {
    [propName: string]: string
  }
})  

lang:'zh_CN'

isReload?: 是否定向到指定页面,完成页面更新。默认为 false

path?: string,
自定义的跳转路径。 为空时,以 homePath(不建议于插件) 路径为准。 
插件通常携带动态参数,建议如下方式使用
插件使用自定义路径:'plugin://hello-plugin/hello-page?lang=zh' 模式

query?: string,
参数。也可以将参数直接拼接到 path 后,如上所示

3、getLocales 当前语言获取

// 语言获取顺序 params lang > localStorage > userAgent > defualt Lang
i18n.getLocales()

4、getLanguagePackList 当前已有的语言列表

i18n.getLanguagePackList()

5、updateLocale 语言数据增量更新

// 每次设置多语言时,在主文件中调用多语言接口,使用该 Api 更新多语言数据

i18n.updateLocale({
  locales, 
  isReload = false, 
  isAnalyticalData = true, 
  mark = '.',
  path: string,
  query: {
    [propName: string]: string
  }
})  

例:locales 
    { 'en': {home: '首页'}, 'zh': {home: 'Home'}}

   isReload?: 
   是否定向到指定页面,完成页面更新。默认 false

   isAnalyticalData?: boolean, 
   是否解析数据,默认为 true,如过结构为对象、json嵌套形式,可设置为 false

   mark?: string,          
   分割语言 code 的占位符,默认为 '.'

   path?: string,
   自定义的跳转路径。 为空时,以 homePath(不建议于插件) 路径为准。 
   插件通常携带动态参数,建议如下方式使用 
   插件使用自定义路径:'plugin://hello-plugin/hello-page?lang=zh' 模式

   query?: string,
   参数。也可以将参数直接拼接到 path 后,如上所示

6、兜底处理

init 校验

校验当前传入语言包中,是否存在传入的兜底语言的语言包,如无,控制台提示

当前语言包是否存在

是:当前 id 是否存在 > 显示兜底语言中对应 id 的value

否:显示兜底语言中对应 id 的value > key。

提示:控制台提示相应语言包不存在,对应的词条不存在

Ua参考

https://github.com/zhangchao-wooc/mini-i18n/blob/main/until/ua.ts

四、各小程序的兼容问题

字节小程序

1、不支持showModal中按钮颜色自定义,且字段key值不同

2、tt.getSystemInfoSync 获取的系统信息中不含有 language 字段,tt.getUserInfo 中可以获取到,但需要授权弹框,故不使用

Tip:在字节小程序中获取到微信小程序 wx 实例,所有字节小程序上均走 wx 实例上的api,且均可以成功调用,故功能上完全兼容字节小程。注意字节小程序将wx实例从中完全剥离时,该库则不支持字节小程序,请注意后续更新。

QQ小程序

1、全局也存在 wx 实例,且两个实例的变化是同步的

基础库版本建议

微信 wx

2.1.2 及以上

支付宝 my

1.4.0 及以上

京东 jd

1.10.8 及以上

字节跳动 tt

1.46.0 及以上

百度 swan

3.140.1 及以上

声明:文章来自 mp.weixin.qq.com/s/63MflTaR_…

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容