编写简朴i18n库

i18n是什么?i18n(其泉源是英文单词internationalization的首末字符i和n,18为中心的字符数)是“国际化”的简称。

媒介

第一次打仗多语言是用野生javascript写H5运用的时刻,那时刻写了一大堆的累坠反复的代码用来切换页面的多语言,今后天然发明很难保护啦。至于到第二次开辟另一个H5运用的时刻,用了vue做了一个SPA。多语言天然用了官方的vue-i18n。

因为两次的开辟保护体验产生了对照,使我产生了不小的兴致:假定一个简朴的页面须要多语言。固然用不着vue,然则也不想用jquery怎样办?假如要开辟相似的i18n库,我该怎样完成?

因而花了三天(应该也是两个月前了)写了这个东西库n-i18n,今后写多语言页面的工作量就能够削减啦~

剖析

简朴剖析后,发明能够参考vue-i18n的设置。然则因为没有完成也没有必要完成模板引擎。因而实在能够将设置参数放在DOM节点的datasetdata-i18n)属性上。遍历读取有该dataset的节点。剖析内里设置的参数后,就能够读取该节点应该绑定多语言里的哪一个文本,设置什么参数和数据。

在现实开辟中。多语言有时刻每每不止切换纯真的文本。有时刻多是切换HTML,以至切换图片,款式(比方background-image)的状况涌现。因而衬着情势也被我分为了$t; $h; $m; $c四种情势,离别对应文本情势、HTML情势、图片情势、款式情势。

完成难点或许说风趣的点在于:

  • 怎样正确寻找到有指定dataset的一切DOM节点?
  • 奇妙应用正则剖析dataset中的多样设置。
  • 多种情势怎样正确衬着和组合衬着?
  • 完成依靠动态数据,数据转变便更新对应的DOM节点。

基本完成

代码参考:https://github.com/Gotjoy/n-i18n/blob/master/src/i18n-a.js

1. 怎样正确寻找到有指定dataset的一切DOM节点?

应用递归一层层遍历节点树,符合要求的节点就保存在一个map里,留待今后对其的操纵的索引。这里的name现实上是默许的i18n这个字符串,固然也能够设置其他字符串,然后就能够在节点中设置属性如data-i18n=""

(function _trace(parent) {
  const children = parent.children;
  for (let i = 0, len = children.length; i < len; i++) {
    const child = children[i];
    if (child.dataset[name]) {
      map[`${name}#${++tid}`] = child;
    }
    if (child.children.length > 0) {
      _trace(child);
    }
  }
}(this.$mount));

2. 奇妙应用正则剖析dataset中的多样设置

起首应用字符串截取操纵的api来剖析设置虽然也能够,然则会相称烦琐,翻看很多优异框架的源码,都是平常倾向于用正则去剖析。比方说我会存在以下四种设置,那末该怎样去剖析data-i18n内里的设置文本从而拿到本身感兴致的信息呢?

<p data-i18n="$t('message.hello', {msg: '巨大的眇小~', msg2: 'Until the day!'})"></p>

在这里有两个及其主要的正则,代码稍后表态。

baseRe正则担任婚配如上的'message.hello'($1)和{msg: '巨大的眇小~', msg2: 'Until the day!'}($2)
confRe正则担任进一步婚配{msg: '巨大的眇小~', msg2: 'Until the day!'}文本中key($1)和value($2)

正则的实验引荐这个网站,多去尝试https://regexr.com。固然正则我不会细致引见了,毕竟也是一个很深挚的学问。

经由正则的处置惩罚,已拿到了悉数感兴致的信息。接下来就是能够应用这些信息去读取多语言设置里lang的数据而且更新DOM节点了。

const baseRe = /\$[t|h|c|m]\(['"](.*?)['"]\,*\s*(.*)\)/g;
const confRe = /(\w+)\:\s*['"](.+?)['"]/g;
let base = '';
let conf = Object.create(null);

c.replace(baseRe, (match, $1, $2) => {
  base = $1;
  if ($2) {
    $2.replace(confRe, (match, $1, $2) => {
      conf[$1] = $2;
    });
  }
});
const lang = {
  en: {
    message: {
      hello: 'hello world! {msg2}'
    }
  },
  zh: {
    message: {
      hello: '你好,天下! {msg}'
    }
  }
};

仔细的同砚可能会发明一个问题了,怎样以a.b.c情势猎取对象属性这个不难。一个遍历即可,简朴完成的话只要value不是原始值就继承往内里走就能够了。

function getValueBy (obj, keystr) {
    const keyset = keystr.split('.');
    for (let i = 0, len = keyset.length; i < len; i++) {
        let v = obj[keyset[i]];
        if (v || _.isPrimitive(v)) {
            obj = v;
        }
    }
    return _.isPrimitive(obj) ? obj : '';
}

找到数据了后,设置文本lang中占位的{msg}的替代应用动态天生正则new RegExp('{' + keys[i] + '}', 'g');全局替代即可。

3. 图片情势和款式情势

以上讲的是文本情势和HTML情势。两个简朴的辨别就是innerTextinnerHTML替代的辨别。然则图片情势和款式情势怎样完成?

起首容我烦琐几句,为何我会创造出这两种情势呢?因为有时刻设想稿中的某些图片的特别文本也是多语言的,艺术字体(什么高光,花式渐变、浮雕等等)不可能用代码完成,这时刻每一个多语言对应切个图片就好了,然后应用图片情势切换就好了。款式情势也是差不多的运用场景了。

图片情势简朴完成要领就是途径的替代(固然条件是肯定要对多语言图片定名和寄存位置都举行强束缚)。款式情势实在就是简朴的切换class。

// class衬着
function render$c (v, c) {
    const locale = this.$locale;
    const langs = Object.keys(this.$messages);
    for (let i = 0, len = langs.length; i < len; i++) {
        if (langs[i] !== locale) {
            _.removeClass(v, `${langs[i]}-${c.base}`);
        }
    }
    _.addClass(v, `${locale}-${c.base}`)
}

// 图片衬着
function render$m (v, c) {
    const locale = this.$locale;
    const langs = Object.keys(this.$messages).join('|');
    const nameRe = new RegExp('(\/(' + langs + '))?\/[^\/]+(?=\\.[^\/]*$)', 'g');
    const src = v.getAttribute('src');
    const path = src.replace(nameRe, `/${locale}/${c.base}`);
    
    v.setAttribute('src', path);
}

4. 多种情势怎样正确衬着

多种情势夹杂运用的时刻,怎样辨别并正确衬着?这个只须要合理断开设置文本,并离别运用在该节点上即可。须要注重的是,断开设置时应该推断分号是不是不在文本里,不然轻易误伤友军。

<img class="d1-common" src="./images/holder.jpg" alt="先占位后替代加载新图片" data-i18n="$m('d1'); $c('d1')">
const dataI18n = v.dataset[name].split(/;(?:\s*\$[t|h|c|m])/g);
dataI18n.forEach(c => {
  const _c = this.parse(c.trim());
  if (c.includes('$t')) {
    this.render$t(v, _c);
  }
  if (c.includes('$h')) {
    this.render$h(v, _c);
  }
  if (c.includes('$c')) {
    this.render$c(v, _c);
  }
  if (c.includes('$m')) {
    this.render$m(v, _c);
  }
});

更进一步

斟酌运用场景以下,某些多语言数据依靠于后端返回,并在运用生命周期内延续更新。为了防止低效的手动操纵,这些多语言数据应该动态依靠,完成数据转变的时刻动态更新依靠了这些数据的DOM节点就好了。

怎样做到这一点。应用Object.defineProperty这个因vue而让人人熟习的api,遍历设置的中data并举行视察。重点是在内里的setter。当修正data的某个值时,会触发对应的setter,并发射信号关照DOM节点去更新。

代码参考:https://github.com/Gotjoy/n-i18n/blob/master/src/i18n-b.js

总结

造轮子是个进修探究的历程,愿望人人能够喜好这篇文章。固然另有假如n-i18n这个东西对你们有所启示或许协助,那就更好了~

    原文作者:bestar
    原文地址: https://segmentfault.com/a/1190000013311035
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞