今天就跟大家聊聊有关vue实现多语言切换的方法,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。
鉴于以上原因,浏览器自带的 Google 翻译方案基本不考虑了。
现在只剩下第二种方案了,语言配置文件和页面结构分离。前面提过,vue-i18n用得不彻底,如果把所有组件重新规范化,工作量太大了。有没有办法不修改现有代码,也能实现文本翻译呢?很自然地就想到了 Google 翻译的思路,直接对页面渲染结果进行翻译。自己翻译的优势就是,可以精细地控制 DOM 操作,比如可以把输入框里的文本和placeholder也翻译出来。同时,经过研究发现,Vue 组件通过数据绑定渲染出来的 DOM 元素,包含的文本内容不能直接通过 innerHTML或者innerText修改,这样会导致响应式失效。解决办法是操作它的子元素,也就是文本节点(nodeType为3的节点),修改它的 textContent属性。
多语言配置映射表
跟 Google 翻译不同之处在于,我们采用静态翻译,也就是通过多语言配置文件映射。 vue-i18n
是每种语言准备一个 JSON 文件,属性名用英文,用命名空间(多层级对象)的方式避免命名冲突。我直接简化了,用一个 JS 对象存储所有语言版本,键名就是页面用到的中文。随着日积月累的开发迭代,这些中文散落在几百个文件里……我的做法是用 VS Code 全局正则搜索,把查找结果复制出来,写一个 JS 方法把这些字符串处理成 JS 对象。
匹配中文的正则(不够全面,有些还夹杂了其他符号):
[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?.trim?.()] || 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?.getters?.language) { translate(elm, this.$store?.getters?.language); } return elm; };
至此,基本完成了多语言翻译。经过权衡对比,这个方案算是比较省时省力又能完成需求的了。当然,这种方案或多或少对页面性能有一定影响,毕竟增加了 DOM 更新的时间。尤其是动态文本较多的情况,涉及到遍历正则匹配,比较耗时。
看完上述内容,你们对vue实现多语言切换的方法有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注亿速云行业资讯频道,感谢大家的支持。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。