成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

現(xiàn)有 Vue.js 項(xiàng)目快速實(shí)現(xiàn)多語(yǔ)言切換的一種思路

開發(fā) 項(xiàng)目管理
項(xiàng)目是基于 Vue.js 開發(fā)的,已經(jīng)迭代過(guò)很多版本了。其實(shí)一開始是有規(guī)劃多語(yǔ)言的,也引進(jìn)了 vue-i18n 插件。這個(gè)插件就是上面第二種方案,用 JSON 文件管理多語(yǔ)言的文本資源,在 Vue 組件模板里通過(guò)鍵名引用文本。

[[342380]]

 Web 項(xiàng)目多語(yǔ)言(i18n,即國(guó)際化)是比較常見的需求,常規(guī)的做法大概有以下幾種:

  1. 每種語(yǔ)言單獨(dú)開發(fā)頁(yè)面,適用于 CMS 之類的網(wǎng)站
  2. 多語(yǔ)言文本和頁(yè)面結(jié)構(gòu)分離,運(yùn)行時(shí)動(dòng)態(tài)替換。適用于單頁(yè)應(yīng)用(SPA)
  3. 直接用網(wǎng)頁(yè)翻譯插件,機(jī)器翻譯。這種效果不太理想,同時(shí)有一些局限性(后面會(huì)講到)

問(wèn)題

每一種方案都有各自的優(yōu)點(diǎn)和局限性,具體項(xiàng)目應(yīng)該根據(jù)實(shí)際情況選擇。最近在工作中碰到的需求是要在現(xiàn)有的項(xiàng)目基礎(chǔ)上快速推出多語(yǔ)言版本。

項(xiàng)目是基于 Vue.js 開發(fā)的,已經(jīng)迭代過(guò)很多版本了。其實(shí)一開始是有規(guī)劃多語(yǔ)言的,也引進(jìn)了 vue-i18n 插件。這個(gè)插件就是上面第二種方案,用 JSON 文件管理多語(yǔ)言的文本資源,在 Vue 組件模板里通過(guò)鍵名引用文本。

但是要管理這些英文鍵名比較麻煩,命名就很頭疼。而且閱讀代碼的時(shí)候也很難從鍵名快速識(shí)別出對(duì)應(yīng)的中文。后面發(fā)現(xiàn) VS Code 有相關(guān)的插件,可以顯示出對(duì)應(yīng)的中文,但是代碼找起來(lái)還是有點(diǎn)麻煩。再加上產(chǎn)品的多語(yǔ)言版本一直沒(méi)有提上日程,時(shí)間久了就嫌麻煩,慢慢地就直接在模板里寫中文了。

結(jié)果,該來(lái)的還是來(lái)了。產(chǎn)品突然說(shuō)最近要快速推出英文版,后續(xù)還有其他語(yǔ)言。一開始的想法是直接用 Chrome 瀏覽器自帶的 Google 翻譯功能,怎么快怎么來(lái)。但經(jīng)過(guò)一番測(cè)試,發(fā)現(xiàn)了不少問(wèn)題。

首先機(jī)翻的效果肯定是要打折扣的,但這還在接受范圍內(nèi)。最關(guān)鍵的是會(huì)影響到功能使用。什么問(wèn)題呢?由于項(xiàng)目是用 Vue.js 開發(fā)的單頁(yè)應(yīng)用,頁(yè)面內(nèi)容完全是用 JS 動(dòng)態(tài)渲染的。有些對(duì)話框內(nèi)的文字 Google 翻譯就忽略了。

另外,Google 翻譯只處理了 DOM 文本節(jié)點(diǎn),input輸入框內(nèi)的文字(包括placeholder)被忽略了。最嚴(yán)重的問(wèn)題是,經(jīng)過(guò) Google 翻譯處理后的 DOM 元素,竟然失去了 Vue 響應(yīng)式特性,數(shù)據(jù)變化后 DOM 內(nèi)的文字不會(huì)更新了!

如果要繼續(xù)采用瀏覽器 Google 翻譯的方案,就要解決這幾個(gè)問(wèn)題。通過(guò)調(diào)試發(fā)現(xiàn) Google 翻譯用的 JS 腳本是嵌入到瀏覽器 VM 里的,通過(guò) HTTP 調(diào)用翻譯服務(wù),然后修改 DOM 元素。JS 腳本是壓縮混淆過(guò)的,格式化后也很難看。想要找到更新 DOM 的代碼,然后用自己的邏輯去覆蓋?眼睛都看瞎了,還是算了。

Google 翻譯JS代碼

鑒于以上原因,瀏覽器自帶的 Google 翻譯方案基本不考慮了。

現(xiàn)在只剩下第二種方案了,語(yǔ)言配置文件和頁(yè)面結(jié)構(gòu)分離。前面提過(guò),vue-i18n用得不徹底,如果把所有組件重新規(guī)范化,工作量太大了。有沒(méi)有辦法不修改現(xiàn)有代碼,也能實(shí)現(xiàn)文本翻譯呢?很自然地就想到了 Google 翻譯的思路,直接對(duì)頁(yè)面渲染結(jié)果進(jìn)行翻譯。自己翻譯的優(yōu)勢(shì)就是,可以精細(xì)地控制 DOM 操作,比如可以把輸入框里的文本和placeholder也翻譯出來(lái)。

同時(shí),經(jīng)過(guò)研究發(fā)現(xiàn),Vue 組件通過(guò)數(shù)據(jù)綁定渲染出來(lái)的 DOM 元素,包含的文本內(nèi)容不能直接通過(guò) innerHTML或者innerText修改,這樣會(huì)導(dǎo)致響應(yīng)式失效。解決辦法是操作它的子元素,也就是文本節(jié)點(diǎn)(nodeType為3的節(jié)點(diǎn)),修改它的 textContent屬性。

多語(yǔ)言配置映射表

跟 Google 翻譯不同之處在于,我們采用靜態(tài)翻譯,也就是通過(guò)多語(yǔ)言配置文件映射。 vue-i18n 是每種語(yǔ)言準(zhǔn)備一個(gè) JSON 文件,屬性名用英文,用命名空間(多層級(jí)對(duì)象)的方式避免命名沖突。我直接簡(jiǎn)化了,用一個(gè) JS 對(duì)象存儲(chǔ)所有語(yǔ)言版本,鍵名就是頁(yè)面用到的中文。隨著日積月累的開發(fā)迭代,這些中文散落在幾百個(gè)文件里……我的做法是用 VS Code 全局正則搜索,把查找結(jié)果復(fù)制出來(lái),寫一個(gè) JS 方法把這些字符串處理成 JS 對(duì)象。

搜索中文

匹配中文的正則(不夠全面,有些還夾雜了其他符號(hào)):

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

將結(jié)果復(fù)制到翻譯工具翻譯,再寫一個(gè)函數(shù)把這些文本合并成對(duì)象,并保存到labels.js文件中備用。

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

對(duì)象的結(jié)構(gòu)大致如下:

  1. // labels.js 
  2. export default { 
  3.   客戶性名: { 
  4.     en: 'Customer Name'
  5.   }, 
  6.  // 動(dòng)態(tài)文本,后面會(huì)講到 
  7.  '剩余{0}臺(tái)礦機(jī)未登記': { 
  8.     en: '{0} unregistered'
  9.   }, 
  10.   xxxx: { 
  11.     en: 'XXX'
  12.   } 

操作 DOM

跟 Google 翻譯類似,我們也采取事后更新 DOM 的方式來(lái)進(jìn)行翻譯。由于是單頁(yè)應(yīng)用,隨著用戶的操作,會(huì)不停地更新 DOM。一開始的想法是監(jiān)聽整個(gè) body的變化,在回調(diào)里再更新 DOM。監(jiān)聽 DOM 變化有一個(gè)原生的 API 可用,就是 MutationObserver。

  1. mounted() { 
  2.   this.observeDOM(document.body); 
  3. }, 
  4. methods: { 
  5.   observeDOM(el) { 
  6.     let mutationTimer; 
  7.     const vm = this; 
  8.     const observer = new MutationObserver(() => { 
  9.       // 類似于 debounce 的效果,多次調(diào)用合并為一次 
  10.       clearTimeout(mutationTimer); 
  11.       mutationTimer = setTimeout(() => { 
  12.         if (!vm.mutationFromTrans) { 
  13.           translate(); 
  14.           vm.mutationFromTrans = true
  15.           setTimeout(() => { 
  16.             vm.mutationFromTrans = false
  17.           }, 300); 
  18.         } 
  19.       }, 100); 
  20.     }); 
  21.     const options = { 
  22.       childList: true, // 監(jiān)視node直接子節(jié)點(diǎn)的變動(dòng) 
  23.       subtree: true, // 監(jiān)視node所有后代的變動(dòng) 
  24.       attributes: true, // 監(jiān)視node屬性的變動(dòng) 
  25.       characterData: true, // 監(jiān)視指定目標(biāo)節(jié)點(diǎn)或子節(jié)點(diǎn)樹中節(jié)點(diǎn)所包含的字符數(shù)據(jù)的變化。 
  26.     }; 
  27.     if (this.language === 'en') { 
  28.       observer.observe(el, options); 
  29.     } 
  30.   }, 
  31. }, 

但是試過(guò)之后發(fā)現(xiàn)這會(huì)導(dǎo)致無(wú)線循環(huán),因?yàn)闆](méi)有判斷 DOM 的變化來(lái)自用戶操作還是翻譯本身。所以代碼里后面加了判斷,但是結(jié)果依然不理想。這種操作代價(jià)太大了,頁(yè)面性能受了很大影響。而且還有個(gè)很明顯的問(wèn)題,就是進(jìn)入到新的界面會(huì)閃一下,從中文變成英文。這個(gè)體驗(yàn)太糟糕了。后面有改進(jìn)辦法。

翻譯

先來(lái)來(lái)看下翻譯的過(guò)程。翻譯就是從多語(yǔ)言配置對(duì)象里查找匹配的屬性名,獲取對(duì)應(yīng)語(yǔ)言的屬性值。這對(duì)于靜態(tài)文本來(lái)說(shuō)比較簡(jiǎn)單,直接用屬性名就好了。但是對(duì)于動(dòng)態(tài)的文本怎么處理呢?由于中英文表達(dá)方式不一樣,這種文本不能簡(jiǎn)單地拆分成多個(gè)部分單獨(dú)處理,而是要在英文的表達(dá)方式里替換動(dòng)態(tài)數(shù)據(jù)。

我的做法是使用帶格式的鍵名,比如{0}這樣的占位符。在查找的時(shí)候,優(yōu)先匹配固定文本。因?yàn)榇蟛糠智闆r是固定文本,而且這種匹配是O(1)時(shí)間復(fù)雜度的,優(yōu)先判斷會(huì)提高性能。匹配失敗的時(shí)候才去提前構(gòu)造好的正則列表里遍歷匹配,成功則提取正則匹配的group用于替換動(dòng)態(tài)數(shù)據(jù)。如果失敗,說(shuō)明沒(méi)有對(duì)應(yīng)的翻譯,直接返回原始字符串就行了。

  1. const keys = Object.keys(words); 
  2. // 提前緩存正則,避免重復(fù)執(zhí)行消耗性能 
  3. const regExps = keys.reduce((acc, key) => { 
  4.   // 模板型鍵名 
  5.   if (key.indexOf('{0}') > -1) { 
  6.     const reg = new RegExp(key.replace('{0}''(.+)')); 
  7.     acc.push({ 
  8.       expression: reg, 
  9.       key
  10.     }); 
  11.   } 
  12.   return acc; 
  13. }, []); 
  14. export function translate(el = document.body, lang = 'en') { 
  15.   const kv = words; 
  16.   if (!el.querySelectorAll) { 
  17.     return
  18.   } 
  19.   const _trans = label => { 
  20.     const text = label?.trim?.(); 
  21.     if (!text) { 
  22.       return label; 
  23.     } 
  24.     if (kv[text]?.[lang]) { 
  25.       return kv[text]?.[lang]; 
  26.     } 
  27.     for (let index = 0; index < regExps.length; index++) { 
  28.       const regItem = regExps[index]; 
  29.       const m = text.match(regItem.expression); 
  30.       if (m) { 
  31.         return kv[regItem.key][lang].replace('{0}', m[1]); 
  32.       } 
  33.     } 
  34.     return text; 
  35.   }; 
  36.   [...el.querySelectorAll('*')].forEach(node => { 
  37.     // 不能直接修改node.innerText,會(huì)導(dǎo)致Vue響應(yīng)式失效 
  38.     // node.innerText = kv[node.innerText?.trim?.()] || node.innerText; 
  39.     if (node.nodeName === 'INPUT' && node.type === 'text') { 
  40.       node.value = _trans(node.value); 
  41.       node.placeholder = _trans(node.placeholder); 
  42.     } 
  43.     const textNodes = [...node.childNodes].filter(n => n.nodeType === 3); 
  44.     textNodes.forEach(textNode => { 
  45.       textNode.textContent = _trans(textNode.textContent); 
  46.     }); 
  47.   }); 

改進(jìn)后的 DOM 操作

前面提過(guò),如果在 DOM 渲染后再執(zhí)行翻譯,頁(yè)面性能非常差。于是想到了 Vue 本身的渲染過(guò)程,能不能攔截 Vue 組件渲染過(guò)程,插入一些額外的邏輯呢?通過(guò)扒源碼發(fā)現(xiàn),Vue 原型上有個(gè)__patch__方法,每次更新 DOM 的時(shí)候都會(huì)執(zhí)行。就從這里入手, 重寫這個(gè)方法,對(duì)還沒(méi)掛載到文檔樹的 DOM 元素執(zhí)行翻譯操作。

  1. const __patch__ = Vue.prototype.__patch__; 
  2. Vue.prototype.__patch__ = function() { 
  3.   const elm = __patch__.apply(this, arguments); 
  4.   if (this.$store?.getters?.language) { 
  5.     translate(elm, this.$store?.getters?.language); 
  6.   } 
  7.   return elm; 
  8. }; 

至此,基本完成了多語(yǔ)言翻譯。經(jīng)過(guò)權(quán)衡對(duì)比,這個(gè)方案算是比較省時(shí)省力又能完成需求的了。當(dāng)然,這種方案或多或少對(duì)頁(yè)面性能有一定影響,畢竟增加了 DOM 更新的時(shí)間。尤其是動(dòng)態(tài)文本較多的情況,涉及到遍歷正則匹配,比較耗時(shí)。如果大家有更好的方案,歡迎留言!

本文轉(zhuǎn)載自微信公眾號(hào)「1024譯站」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系1024譯站公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 1024譯站
相關(guān)推薦

2021-07-24 11:41:42

前端開發(fā)技術(shù)

2022-06-23 07:05:46

跳板機(jī)服務(wù)器PAM

2022-04-04 16:53:56

Vue.js設(shè)計(jì)框架

2025-02-03 00:25:00

Asp語(yǔ)言配置

2011-08-05 17:54:33

Cocoa Touch 多語(yǔ)言

2012-04-19 11:40:21

Titanium

2009-08-25 10:44:50

C#實(shí)現(xiàn)多語(yǔ)言

2016-10-26 09:12:58

2024-05-09 08:20:29

AC架構(gòu)數(shù)據(jù)庫(kù)冗余存儲(chǔ)

2014-07-09 09:20:06

WPFWPF應(yīng)用

2017-08-24 15:02:01

前端增量式更新

2014-04-16 14:50:20

Spark

2009-08-31 17:13:09

2023-09-17 23:16:46

緩存數(shù)據(jù)庫(kù)

2023-05-23 14:14:14

技術(shù)模型

2018-04-18 07:34:58

2020-11-06 07:30:36

JS文件

2017-02-14 17:29:42

Android毛玻璃虛化效果

2021-09-07 10:17:35

iOS多語(yǔ)言適配設(shè)計(jì)

2021-06-29 21:48:32

開源語(yǔ)言架構(gòu)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久精品亚洲 | 亚洲一区二区三区在线观看免费 | 99久久婷婷 | 日本高清视频网站 | 国产精品免费大片 | 中国美女av | 亚洲成人精品视频 | 国产欧美日韩一区二区三区在线 | 九九综合 | 一区二区三区四区在线 | 日本福利片| 欧产日产国产精品v | 成年网站在线观看 | 国产综合久久 | 国产成人午夜电影网 | 欧美色a v | 国产精品久久久久久久久久久免费看 | 久久久91精品国产一区二区三区 | 在线免费看黄 | 天天色天天射天天干 | 成人影院在线视频 | 91九色网站 | 91综合网| 日韩中文字幕视频在线观看 | 一级网站| 偷拍自拍网| 精品国产视频 | 欧美亚洲国产一区二区三区 | 99久久久久国产精品免费 | 亚洲精品成人网 | av在线天天 | 久久久国产精品 | 在线观看午夜视频 | 久久精品国产清自在天天线 | 美女一级毛片 | 国产一二三区精品视频 | 免费的av网站 | 久久9精品| 成人不卡视频 | 免费精品一区 | 色婷婷综合网 |