最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 如何更优雅的实现微信小程序国际化

    正文概述 掘金(akrion)   2021-05-22   656

    常见的国际化方式

    官方方案

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

    强依赖目录结构

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

    如何更优雅的实现微信小程序国际化

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

    文档简陋

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

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

    调试麻烦

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

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

    如何更优雅的实现微信小程序国际化

    说下优点

    代码简洁。解释一下,从上图可以看到,他的书写方式和其他主流框架的国际化书写方式很类似(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无法引用外部资源的官方说明

    如何更优雅的实现微信小程序国际化

    优化

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

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

    先来看下效果

    如何更优雅的实现微信小程序国际化

    wxml代码

    <wxs src="../wxs/i18n.wxs" module="i18n" />
    <!-- 标题国际化 -->
    <page-meta>
      <navigation-bar  />
    </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方法中从全局获取当前的国际化语言。这样就不会每次都被重置成中文了。


    起源地下载网 » 如何更优雅的实现微信小程序国际化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元