自製「Chrome 版 Word Wise」智能翻譯英文生字
2018-03-13
這個項目是 用「字頻數據」分析英文生字的難易度 的後續,靈感取自 Amazon Kindle 的 Word Wise,本來打算開發一個 Electron App,但後來發現做一個插件好像比較實際
跟 Google 翻譯的分別﹖
Google 也有自動翻譯功能,而且英譯中其實翻譯得還不錯,為甚麼我還要寫呢﹖
- Google 翻譯會覆蓋掉原文
 - 整篇翻譯很常出現莫名奇妙的句子
 炫耀技術
p.s. 不過像這樣的文章就沒太大問題
圖中文章來源: Science Alert
插件功能
簡而言之就是翻譯,方法有三種
1. 雙擊翻譯
這是我最常用的單詞翻譯功能,只要在生字上連按兩下就可以在生字上方加入翻譯

2. 全頁翻譯
這是最初的構思,就像 Amazon 的 Wordwise 一樣在頁面的所有段落上方加插翻譯,為減少誤判一律只翻譯 <p> 和 <li>

3. Highlight 翻譯
全頁翻譯比較花時間而且界面有時候會嚴重變形,所以加上這個只翻譯選取段落的功能

運作原理
這個項目總共有
- 一個翻譯伺服器
 - Chrome 和 Firefox 的插件
 - Google 翻譯 API
 
伺服器的運算
提供翻譯 API 給瀏覽器插件,背後用了我寫的 difficulty 模組分析生字難度,翻譯部份用了「免費的」 google-translate-api,啊不過請低調使用…
翻譯 API
difficulty 預設有 3 個等級,最低是 1 最高是 3,而 API 的預設值是 1,即常用字以外的字都會翻譯
POST: /api/translate
可用的參數
{
  level: number;
  words: string[];
  lang: string;
}
curl 例子
curl -XPOST -H "Content-type: application/json" -d '{
  "level": 3,
  "words": [ "apple", "cappuccino" ],
  "lang": "zh-tw"
}' 'http://localhost:4000/api/translate'
因為 apple 是常用字,所以只會對 cappuccino 進行翻譯
{
  "cappuccino": "熱奶咖啡"
}
控制界面的瀏覽器插件
我只寫了 Firefox 和 Chrome 的插件,全靠這兩個模組我可以使用一樣的原碼
找生字和插入翻譯是我花了最多時間的部份
從 <p> 和 <li> 找出生字
用 jQuery 抽出選取中的 <p> 和 <li>,移除 stop words 和標點符號後用空格把它們拆成一個 Array,以 I have a pen. I have an apple. 為例子
變成 Array
重覆出現的生字不會在 Array 出現
[
  "I",
  "have",
  "a",
  "pen.",
  "an",
  "apple."
]
移除 stop words 和標點
移除 stop words 後
[
  "pen.",
  "apple."
]
最終會變成這樣
[
  "pen",
  "apple"
]
從 API 取得的 data
假設這兩個都是困難的字,透過 API 取回來的翻譯結果將會是這個格式的
{
  "apple": "蘋果",
  "pen": "筆"
}
運用 CSS 整加行距和插入翻譯
以 apple 作例子,這個插件會把所有 <p> 或 <li> 的 apple 變成
<span data-after="蘋果"" class="ww-trans-word">apple</span>
.ww-trans-word 的用途是增加行距和利用 :after 插入從 data-after 讀取的翻譯到生字上方,以下是這個 class 的 CSS
.ww-trans-word {
  position: relative;
  line-height: 3em !important;
}
.ww-trans-word:after {
  content: attr(data-after);
  position: absolute;
  top: -1.8em;
  left: 0;
  width: 200px;
  height: 0;
}
只翻譯選取的段落
找字和翻譯的部份都使用了 JS 的 Selection,先建立一個新的 <div> 然後把選取的內容 clone 到裡面,插入翻譯的方法同上,最後直接把這個 <div> 取代整段選取的字串
addTranslationsToSelection(translations: any, selection: Selection) {
  // Get range and parent element from selection
  const range = selection.getRangeAt(0);
  const parent = selection.focusNode.parentElement;
  // Ingore translated word when single word is selected
  if ($(parent).hasClass('ww-trans-word')) {
    return;
  }
  // Create div and append the origin word(s)
  const div = document.createElement('div');
  div.appendChild(range.cloneContents());
  // Append translations
  let html = div.innerHTML;
  for (let en in translations) {
    const zhtw = translations[en];
    // Condition to check if the word is already translated
    if (html.indexOf(`data-after="${zhtw}"`) === -1) {
      const regexp = new RegExp(`\\b${en}\\b`);
      html = html.replace(
        regexp,
        `<span class='ww-trans-word' data-after='${zhtw}'>${en}</span>`
      );
    }
  }
  // Replace the selection with the div
  range.deleteContents();
  range.insertNode(range.createContextualFragment(html));
}
雙擊翻譯
原理跟譯段落一樣,因為雙擊在瀏覽器會自動選選取字串,所以只要用 jQuery 聆聽 $.dblclick() 事件再觸發翻譯就可以了
本地伺服器和插件開發
下載翻譯伺服器
$ git clone https://github.com/auphone/wordwise-translation-server.git
$ cd wordwise-translation-server
$ npm install
Build 和運行
$ npm run build
$ node dist/index.js
然後下載 extension
$ git clone https://github.com/auphone/wordwise-chrome-extension.git
到 Google Chrome => 進入插件頁面 => 在右上角的開發者模組打勾 => 然後載入插件,位置在 ./wordwise-chrome-extension/
使用方法
先根據下面指示設定好插件,然後找一個網頁(最好是文章)按這個插件圖示,然後神奇的事就發生了 ⋯
![]()
插件設定
在插件圖示按右鍵可以找到 Options,是一個很簡陋的頁面 ⋯

Server - 伺服器
- 輸入翻譯伺服器的 URL
 
Password - 伺服器密碼
- 如果伺服器沒有設定密碼留空就好了
 
Language - 翻譯語言
- Google 支援的語言,默認當然是繁體中文囉…
 
Level - 難度
- 有 3 個難度可以選擇,Level 1 是最容易也是默認的選項
 
Auto translate - 換頁自動翻譯
- 決定換頁的時候是自動翻譯還是按鍵再翻譯
 
後記
當初花不少時間找免費的離線英漢字庫,翻譯出來的結果完全不行,後來由 App 改做插件的時候才發現這個因漏洞而可以免費使用的 Google Translate API,雖然它已經兩年沒更新了,但竟然還能夠使用,這讓我很驚訝