Javascript保留格式翻譯選區內容及預覽(一)

談笑斗酒發表於2019-10-15

本文來自道招網《Javascript保留格式翻譯選區內容及預覽(一)》原創

目前市面上的不少翻譯,一般場景比較簡單,都是純文字翻譯(可能會包含換行\n之類的),但是最近遇到一個需求是要實現富文字里面的翻譯,這裡的翻譯很大的概率會有格式,比如這種

file
我們需要帶格式翻譯,翻譯成如下的效果
file
這種翻譯跟大多數翻譯一樣,也需要一個預覽的功能,便於使用者預覽翻譯結果和微調翻譯結果,然後點選替換就直接將翻譯結果替換到富文字的內容裡面,樣式需要保留,比如上面的加粗。 做個類似選擇功能,選區的都應該知道下列api Selection Range 常用的方法就是window.getSelection()以及window.getSelection().getRangeAt(),具體的用法大家可以參看MDN,這裡主要說下里面的坑。

怎麼保留樣式

首先大家想到的是如果裡面有複雜樣式,比如選區的文字有加粗、顏色什麼的,選區裡面還有圖片啊,更深一層講的話可能就是有裡面會還有classstyleimg之類的,怎麼過濾呢?其實我們這樣看還只是看到的是視覺層面的東西,我們應該看裡面都是一個個的element node:text,我們所謂的保留格式替換,其實也只是更換裡面的文字內容資訊即可,其它只有不是一股腦全換掉,就實現了我們的目標了。

怎麼找到需要替換的文字

其實使用window.getSelection().toString()能獲取到選擇的文字,但是這種方式類似innerText獲取的是當前節點及其位元組的文字,所以會丟失層級關係。

根據我們的需求我們要使用的是遍歷對應的dom節點的方法。常用的獲取子節點的屬性有兩個:

  1. children返回的是HTMLCollection,不包含textNode
  2. childNodes返回的是NodeList,包含textNode

以下面的程式碼為例

<h2 class="lg_loginbox_title" id="logintitle">攜程賬號登入<a href="https://passport.ctrip.com/user/member/fastOrder" class="login_phone_number getOrder">手機號查單<i>></i></a></h2>
複製程式碼

我們分別根據上述方式看看返回

file
很顯然,這裡我們需要使用childNodes。 既然遍歷,就需要知道入口,我們從哪裡開始遍歷呢? 這就要用到前面說的Range了。 我們以這樣的選區為例,我們模板就是獲取到“賬號登入”和“手機號”這幾個文字並實現替換。
file
我們使用widow.getSelection().getRangeAt(0).cloneContents(),目前我們都是隻有一個選區,所以用getRangeAt(0)獲取第一個選區的Range
file
返回的fragment是就是我們選區裡面的dom結構,並且classstyle以及層級結構都保留了,這個API為我們做了很大貢獻。 我們直接遍歷它就能獲取到選區的所有文字了,我們就按照深度遍歷來將選區內的文字放到一個陣列裡面。我們就可以將陣列['賬號登入', '手機號']進行翻譯得到['account login', 'phone number']。

怎麼將翻譯好的文字替換到原來位置

這就需要我們找到選區在當前dom的位置了,這時我們還是看看Range

file
window.getSelection.getRangeAt(0).commonAncestorContainer返回的是選區的節點的最近一層的共同父節點。
file
可以看到我們現在的選區其實都在h2裡面,的確如此。 現在唯一的難點就是怎麼找到文字的起始位置了,如果像我們上面的選區那樣,使用者選取的只是文字的一部分怎麼辦,比如“攜程賬號登入”只選取“賬號登入”,“手機號查單”只選取“手機號”。 我們有兩個辦法可以做到
file

  1. window.getSelection.getRangeAt(0).startContainerwindow.getSelection.getRangeAt(0).startOffset可以獲取到“賬號登入”,因為“賬號登入”就是從“攜程賬號登入”的第2位開始的(0為第一位,下同),就是說startContainer裡面的第2位開始都是選區內容了。
  2. window.getSelection.getRangeAt(0).endContainerwindow.getSelection.getRangeAt(0).endOffset可以獲取到“手機號”,因為“手機號”就是從“手機號查單”的第3位開始結束的,類似slice的前閉後開原則,就是說endContainer裡面的第3位開始都是選區外了。 到此,是不是覺得這個需求有眉目了,能搞定了?剩下的我們下篇再講。

相關文章