微信小程序国际化探索(附源码地址)

3,913 阅读6分钟

随着小程序应用越来越广泛,国际化支持逐渐成了刚需。

官方文档给出了一个 国际化方案,但觉得配置起来稍微有点复杂,对项目结构还有一定的要求。如果是旧项目改动成本太大,遂决定自己实现一个小程序国际化方案。

源码地址:github.com/cachecats/m…

一、项目结构

整体目录结构如下图:

  • assets 存放资源文件,如图片
  • constants 存放项目中用到的常量
  • i18n 存放语言文件,中文是 zh-CN.js 英文是 en-US.js ,如果还需要支持其他语言再建一个 js 即可
  • pages 存放业务逻辑代码
  • utils 存放工具类。LangUtils 是封装的国际化工具类。

二、工具类封装及语言包准备

2.1 语言包准备

i18n 目录下的各语言包结构要一致,即对象的 key 保持一致,value 是对应的语言文本。

建议每个小模块分为一个对象,单个对象的内容不宜过多。

zh-CN.js

export default {
  common: {
    language: '语言',
    chinese: '中文',
    english: '英语',
  },
  tabBarTitles: ['主页', '论坛', '我的'],
  navTitle: {
    home: '主页',
    forum: '论坛',
    mine: '我的',
    setting: '设置'
  },
  home: {
    motto: '我们宁愿拥有一个不完美的变革,也不愿看到一个没有希望的未来',
    respect: '致勇者',
    getUserInfo: '获取头像昵称'
  },
  forum: {
    forumModule: '我是论坛模块',
    tip: '下面是一个组件,用来展示组件的国际化配置'
  },
  comment: {
    title: '评论组件',
    msg: '网络一线牵,珍惜这段缘'
  },
  mine: {
    title: '这是我的页面',
    toNewPage: '跳转到新页面'
  },
  setting: {
    title: '我是设置页面'
  }
}

en-US.js

export default {
  common: {
    language: 'Language',
    chinese: 'Chinese',
    english: 'English',
  },
  tabBarTitles: ['Home', 'Forum', 'Mine'],
  navTitle: {
    home: 'Home',
    forum: 'Forum',
    mine: 'Mine',
    setting: 'setting'
  },
  home: {
    motto: 'We would rather have an imperfect change than see a hopeless future',
    respect: 'to warrior',
    getUserInfo: 'Get avatar nickname'
  },
  forum: {
    forumModule: 'I am forum module',
    tip: 'The following is a component to show the international configuration of the component'
  },
  comment: {
    title: 'Comment Components',
    msg: 'The network leads, cherish this relationship'
  },
  mine: {
    title: 'This is mine page',
    toNewPage: 'Go to new page'
  },
  setting: {
    title: 'I am setting page'
  }
}

2.2 工具类 LangUtils 封装

工具类 LangUtils 封装了国际化所需的所有方法,包括获取当前语言、设置语言、获取当前语言的资源文件、设置 TabBar、设置 NavigationBar 等方法。

实现思路是把当前设置的语言存在小程序提供的 storage 中,每次项目初始化时从 storage 中读取语言,如果没有读到则默认设置为中文。

然后在每个页面或组件的 data 中将页面需要用到的文本资源引入进来,wxml 中使用 data 中绑定的变量展示文字。同时在生命周期的 onShow 方法中重新读取当前语言并设置 data,使得每次改变语言都能正确的加载语言包。

先看 LangUtils 的代码:

import zh from '../i18n/zh-CN.js'
import en from '../i18n/en-US.js'
import Constants from '../constants/Constants';

export default{

  //初始化语言设置。在 app.js 里调用这个方法。
  initLang(){
    //先获取是不是已存在语言的设置
    let lang = wx.getStorageSync('lang')
    if(!lang){
      //如果不存在,设置默认语言为中文
      this.setLang(Constants.langCN)
    }
  },

  //设置语言
  setLang(lang){
    try{
      wx.setStorageSync('lang', lang)
    }catch(e){
      console.log('设置语言失败', e)
    }
  },

  //获取语言设置
  getLang(){
    try{
      let lang = wx.getStorageSync('lang')
      return lang;
    }catch(e){
      console.log('获取语言设置失败', e)
    }
  },

  //获取当前语言下的资源文件
  getLangSrc(){
    let lang = this.getLang();
    if(lang === Constants.langCN){
      return zh;
    } else if(lang === Constants.langEN){
      return en;
    }else{
      return zh;
    }
  },

  //设置 NavigationBarTitle
  setNavigationBarTitle(title){
    wx.setNavigationBarTitle({
      title: title
    })
  },

  /**
   * 设置 tabBar。因为 tabBar 是在 app.json 里写死的,需要使用 wx.setTabBarItem
   * 循环设置 tabBar
   */
  setTabBarLang(){
    let tabBarTitles = this.getLangSrc().tabBarTitles;
    console.log('tabBarTitles', tabBarTitles)
    tabBarTitles.forEach((item, index) => {
      console.log(item, index)
      wx.setTabBarItem({
        index: index,
        text: item,
        success: (res) => {
          console.log('setTabBarItem success', res)
        },
        fail: (err) => {
          console.log('setTabBarItem fail', err)
        }
      });
    });
  },
}

先引入中文和英文的语言包,以便根据当前语言设置返回对应的资源包。

Constants 是对常量的封装,这里保存的是中英文编码标识。

Constants.js

/**
 * 保存项目中的常量
 */
export default{
  //中文编码
  langCN: 'zh-CN',
  //英文编码
  langEN: 'en-US',
}

需要注意的是 tabBar 的处理,因为 tabBar 是写死在 app.json 中的,不能动态的改变文本,所以每次语言改变只能用小程序暴露出来的 wx.setTabBarItem 方法循环的设置 tabBar

至此前期的准备工作已经做完啦,接下来对具体的页面和组件做处理。

三、项目使用

需要改动三个地方

  • app.js 初始化语言
  • xxx.jsdata 添加语言属性,并在 onShow 生命周期方法中调用 setData 重新设置语言
  • xxx.wxml 中的文本替换为 data 里绑定的变量

3.1 app.js 初始化语言

在项目入口文件 app.js 中做初始化。

//初始化国际化语言设置
import LangUtils from './utils/LangUtils'

App({
  onLaunch: function () {
    // 国际化的初始化
    LangUtils.initLang();
    LangUtils.setTabBarLang();
  }
})

3.2 Page 页面的国际化

js 中使用

js 中的使用分三步:

  1. 首先引入 LangUtils.js

  2. 然后在 data 中定义变量 lang ,通过 ... 对象的解构赋值,把语言文件中对应模块定义的变量都赋值给 lang ,方便调用。如果是 settings 模块,可以这样写: lang: {...LangUtils.getLangSrc().settings} 。也可以只写个空对象: lang: {} ,然后在 onShow() 方法里对 lang 赋值。

  3. onShow() 生命周期方法里,更新 lang 的值,以防语言被改变。如果需要设置小程序标题,则再调用 LangUtils.setNavigationBarTitle() 方法。

// pages/setting/setting.js
import LangUtils from '../../utils/LangUtils'
let langSrc = LangUtils.getLangSrc()

Page({

  /**
   * 页面的初始数据
   */
  data: {
    lang: {
      ...langSrc.setting
    }
  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    this.setLanguage();
  },

   /**
   * 重新设置语言
   */
  setLanguage() {
    langSrc = LangUtils.getLangSrc();
    this.setData({
      lang: {
        ...langSrc.setting
      }
    })
    // 设置 NavigationBarTitle
    LangUtils.setNavigationBarTitle(langSrc.navTitle.setting);
  }
})

wxml 中使用

wxml 里比较简单,跟普通的变量使用方法一样。

<view class="setting-container">
	<text class="title">{{lang.title}}</text>
</view>

3.2 Component 组件的国际化

ComponentPage 国际化基本上相同,但因为生命周期方法不同,稍微有点区别。

Coponentsthis.setLanguage() 在生命周期的 pageLifetimesshow 方法中调用。

// pages/forum/components/comment.js
import LangUtils from '../../../../utils/LangUtils'
let langSrc = LangUtils.getLangSrc();

Component({
  data: {
    lang: {
      ...langSrc.comment
    }
  },

  pageLifetimes: {
    // 组件所在页面的生命周期函数
    show: function () { 
      console.log('page show---')
      this.setLanguage();
    },
  },

  /**
   * 组件的方法列表
   */
  methods: {
    /**
     * 重新设置语言
     */
    setLanguage() {
      langSrc = LangUtils.getLangSrc();
      this.setData({
        lang: {
          ...langSrc.comment
        }
      })
    }
  }
})

3.3 切换语言

切换语言放在 demo 的 home 页面中。用户更改语言后要调用 LangUtils.setLang 更改语言值,还要调用 LangUtils.setTabBarLang 重新设置 tabBar 的文本。

切换后的效果

//index.js
//获取应用实例
const app = getApp()

import Constants from '../../constants/Constants'
// 获取对应语言的资源文件
import LangUtils from '../../utils/LangUtils'
let langSrc = LangUtils.getLangSrc();

// 语言选项
const LANGUAGE_OPTIONS = [{
    key: Constants.langCN,
    value: '中文'
  },
  {
    key: Constants.langEN,
    value: 'English'
  }
]

Page({
  data: {
    // 通过解构赋值将 common 和 home 下的变量赋值给 lang。最好每个模块建一个对象
    // 对象里的属性不宜过多,否则在 data 里放入太多内容会影响性能,用到什么放什么。
    lang: {
      ...langSrc.common,
      ...langSrc.home
    },
    langOptions: LANGUAGE_OPTIONS,
    index: 0
  },

  onLoad: function () {
    // 根据当前语言设置 picker 默认选中的值
    let lang = LangUtils.getLang();
    this.setData({
      index: lang === Constants.langCN ? 0 : 1
    })
  },
  onShow: function () {
    //每次 onShow 重新设置语言,以防语言更新
    this.setLanguage();
  },

  getUserInfo: function (e) {
    console.log(e)
    app.globalData.userInfo = e.detail.userInfo
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  },

  /**
   * 选择语言变化回调函数
   */
  onLanguageChange(e) {
    const index = e.detail.value
    console.log(e)
    this.setData({
      index: index
    })
    // 更改语言
    LangUtils.setLang(this.data.langOptions[index].key);
    // 重新设置 tabBar 的语言
    LangUtils.setTabBarLang();
    this.setLanguage();
  },

  /**
   * 重新设置语言
   */
  setLanguage() {
    langSrc = LangUtils.getLangSrc();
    this.setData({
      lang: {
        ...langSrc.common,
        ...langSrc.home
      }
    })
    // 设置 NavigationBarTitle
    LangUtils.setNavigationBarTitle(langSrc.navTitle.home);
  }
})

四、总结

代码乍一看还挺多的,但优点是不用引入第三方模块,也不用按要求改项目结构。其实把前期的准备工作做完后,后期维护起来还是很方便的。

当然这个方案还有可优化的地方,比如每个页面的 onShow 方法里都要执行相似的逻辑,以后有时间会做优化。

项目地址:github.com/cachecats/m…