温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

vue实现多语言切换的方法

发布时间:2020-11-03 15:46:39 来源:亿速云 阅读:564 作者:Leah 栏目:开发技术

今天就跟大家聊聊有关vue实现多语言切换的方法,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

vue实现多语言切换的方法

鉴于以上原因,浏览器自带的 Google 翻译方案基本不考虑了。

现在只剩下第二种方案了,语言配置文件和页面结构分离。前面提过,vue-i18n用得不彻底,如果把所有组件重新规范化,工作量太大了。有没有办法不修改现有代码,也能实现文本翻译呢?很自然地就想到了 Google 翻译的思路,直接对页面渲染结果进行翻译。自己翻译的优势就是,可以精细地控制 DOM 操作,比如可以把输入框里的文本和placeholder也翻译出来。同时,经过研究发现,Vue 组件通过数据绑定渲染出来的 DOM 元素,包含的文本内容不能直接通过 innerHTML或者innerText修改,这样会导致响应式失效。解决办法是操作它的子元素,也就是文本节点(nodeType为3的节点),修改它的 textContent属性。

多语言配置映射表

跟 Google 翻译不同之处在于,我们采用静态翻译,也就是通过多语言配置文件映射。 vue-i18n 是每种语言准备一个 JSON 文件,属性名用英文,用命名空间(多层级对象)的方式避免命名冲突。我直接简化了,用一个 JS 对象存储所有语言版本,键名就是页面用到的中文。随着日积月累的开发迭代,这些中文散落在几百个文件里……我的做法是用 VS Code 全局正则搜索,把查找结果复制出来,写一个 JS 方法把这些字符串处理成 JS 对象。

vue实现多语言切换的方法

匹配中文的正则(不够全面,有些还夹杂了其他符号):

[A-Z]*[\u4e00-\u9fa5][,,!! 0-9a-zA-Z\u4e00-\u9fa5]*

将结果复制到翻译工具翻译,再写一个函数把这些文本合并成对象,并保存到labels.js文件中备用。

var kv = dist.reduce((acc,cur, index) => {
acc[cur]=en[index] || cur;return acc;
},{})

对象的结构大致如下:

// labels.js
export default {
 客户性名: {
  en: 'Customer Name',
 },
 // 动态文本,后面会讲到
 '剩余{0}台矿机未登记': {
  en: '{0} unregistered',
 },
 xxxx: {
  en: 'XXX',
 }
}

操作 DOM

跟 Google 翻译类似,我们也采取事后更新 DOM 的方式来进行翻译。由于是单页应用,随着用户的操作,会不停地更新 DOM。一开始的想法是监听整个 body的变化,在回调里再更新 DOM。监听 DOM 变化有一个原生的 API 可用,就是 MutationObserver。

mounted() {
 this.observeDOM(document.body);
},
methods: {
 observeDOM(el) {
  let mutationTimer;
  const vm = this;
  const observer = new MutationObserver(() => {
   // 类似于 debounce 的效果,多次调用合并为一次
   clearTimeout(mutationTimer);
   mutationTimer = setTimeout(() => {
    if (!vm.mutationFromTrans) {
     translate();
     vm.mutationFromTrans = true;
     setTimeout(() => {
      vm.mutationFromTrans = false;
     }, 300);
    }
   }, 100);
  });
  const options = {
   childList: true, // 监视node直接子节点的变动
   subtree: true, // 监视node所有后代的变动
   attributes: true, // 监视node属性的变动
   characterData: true, // 监视指定目标节点或子节点树中节点所包含的字符数据的变化。
  };
  if (this.language === 'en') {
   observer.observe(el, options);
  }
 },
}

但是试过之后发现这会导致无线循环,因为没有判断 DOM 的变化来自用户操作还是翻译本身。所以代码里后面加了判断,但是结果依然不理想。这种操作代价太大了,页面性能受了很大影响。而且还有个很明显的问题,就是进入到新的界面会闪一下,从中文变成英文。这个体验太糟糕了。后面有改进办法。

翻译

先来来看下翻译的过程。翻译就是从多语言配置对象里查找匹配的属性名,获取对应语言的属性值。这对于静态文本来说比较简单,直接用属性名就好了。但是对于动态的文本怎么处理呢?由于中英文表达方式不一样,这种文本不能简单地拆分成多个部分单独处理,而是要在英文的表达方式里替换动态数据。我的做法是使用带格式的键名,比如{0}这样的占位符。在查找的时候,优先匹配固定文本。因为大部分情况是固定文本,而且这种匹配是O(1)时间复杂度的,优先判断会提高性能。匹配失败的时候才去提前构造好的正则列表里遍历匹配,成功则提取正则匹配的group用于替换动态数据。如果失败,说明没有对应的翻译,直接返回原始字符串就行了。

const keys = Object.keys(words);
// 提前缓存正则,避免重复执行消耗性能
const regExps = keys.reduce((acc, key) => {
 // 模板型键名
 if (key.indexOf('{0}') > -1) {
  const reg = new RegExp(key.replace('{0}', '(.+)'));
  acc.push({
   expression: reg,
   key,
  });
 }
 return acc;
}, []);
export function translate(el = document.body, lang = 'en') {
 const kv = words;
 if (!el.querySelectorAll) {
  return;
 }
 const _trans = label => {
  const text = label?.trim?.();
  if (!text) {
   return label;
  }
  if (kv[text]?.[lang]) {
   return kv[text]?.[lang];
  }
  for (let index = 0; index < regExps.length; index++) {
   const regItem = regExps[index];
   const m = text.match(regItem.expression);
   if (m) {
    return kv[regItem.key][lang].replace('{0}', m[1]);
   }
  }
  return text;
 };
 [...el.querySelectorAll('*')].forEach(node => {
  // 不能直接修改node.innerText,会导致Vue响应式失效
  // node.innerText = kv[node.innerText&#63;.trim&#63;.()] || node.innerText;
  if (node.nodeName === 'INPUT' && node.type === 'text') {
   node.value = _trans(node.value);
   node.placeholder = _trans(node.placeholder);
  }
  const textNodes = [...node.childNodes].filter(n => n.nodeType === 3);
  textNodes.forEach(textNode => {
   textNode.textContent = _trans(textNode.textContent);
  });
 });
}

改进后的 DOM 操作

前面提过,如果在 DOM 渲染后再执行翻译,页面性能非常差。于是想到了 Vue 本身的渲染过程,能不能拦截 Vue 组件渲染过程,插入一些额外的逻辑呢?通过扒源码发现,Vue 原型上有个__patch__方法,每次更新 DOM 的时候都会执行。就从这里入手, 重写这个方法,对还没挂载到文档树的 DOM 元素执行翻译操作。

const __patch__ = Vue.prototype.__patch__;
Vue.prototype.__patch__ = function() {
 const elm = __patch__.apply(this, arguments);
 if (this.$store&#63;.getters&#63;.language) {
  translate(elm, this.$store&#63;.getters&#63;.language);
 }
 return elm;
};

至此,基本完成了多语言翻译。经过权衡对比,这个方案算是比较省时省力又能完成需求的了。当然,这种方案或多或少对页面性能有一定影响,毕竟增加了 DOM 更新的时间。尤其是动态文本较多的情况,涉及到遍历正则匹配,比较耗时。

看完上述内容,你们对vue实现多语言切换的方法有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI