自製「Chrome 版 Word Wise」智能翻譯英文生字

2018-03-13

這個項目是 用「字頻數據」分析英文生字的難易度 的後續,靈感取自 Amazon Kindle 的 Word Wise,本來打算開發一個 Electron App,但後來發現做一個插件好像比較實際

跟 Google 翻譯的分別﹖

Google 也有自動翻譯功能,而且英譯中其實翻譯得還不錯,為甚麼我還要寫呢﹖

  • Google 翻譯會覆蓋掉原文
  • 整篇翻譯很常出現莫名奇妙的句子
  • 炫耀技術

p.s. 不過像這樣的文章就沒太大問題 Google translating sciencealert 圖中文章來源: Science Alert

插件功能

簡而言之就是翻譯,方法有三種

1. 雙擊翻譯

這是我最常用的單詞翻譯功能,只要在生字上連按兩下就可以在生字上方加入翻譯

Click to translate

2. 全頁翻譯

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

Full page translate

3. Highlight 翻譯

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

Highlight to translate

運作原理

這個項目總共有

  • 一個翻譯伺服器
  • 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/

使用方法

先根據下面指示設定好插件,然後找一個網頁(最好是文章)按這個插件圖示,然後神奇的事就發生了 ⋯

Translate Icon

插件設定

在插件圖示按右鍵可以找到 Options,是一個很簡陋的頁面 ⋯ Extension Options

Server - 伺服器

  • 輸入翻譯伺服器的 URL

Password - 伺服器密碼

  • 如果伺服器沒有設定密碼留空就好了

Language - 翻譯語言

  • Google 支援的語言,默認當然是繁體中文囉…

Level - 難度

  • 有 3 個難度可以選擇,Level 1 是最容易也是默認的選項

Auto translate - 換頁自動翻譯

  • 決定換頁的時候是自動翻譯還是按鍵再翻譯

後記

當初花不少時間找免費的離線英漢字庫,翻譯出來的結果完全不行,後來由 App 改做插件的時候才發現這個因漏洞而可以免費使用的 Google Translate API,雖然它已經兩年沒更新了,但竟然還能夠使用,這讓我很驚訝