到目前為止,
翻譯修改功能應該已經蠻好用了。
但是,每次遇到全新的網頁,
都要先進行分句、備份原文、取得整頁翻譯、再備份譯文,
然後才能開始進行翻譯修改,
這些很明顯就是重複性的工作,
嚴重違反 DRY(Don't Repeat Youself)的原則呀!
如果可以像瀏覽器內建的 Google 翻譯功能,
一鍵就能完成所有工作,這樣才像話嘛!!
仔細回顧一下我們的流程,
必須分步驟進行的原因,主要就是因為
必須使用瀏覽器執行 Google 翻譯、
最後還要關閉 Google 翻譯重整頁面。
Google 翻譯是瀏覽器所提供的功能,
無法讓 Javascript 直接呼叫使用或停用,
所以才需要使用者一步一步手動執行。
如果想要一鍵完成,勢必就要另闢蹊徑。
既然瓶頸在於瀏覽器的 Google 翻譯無法被 JS 呼叫使用,
我們就改用 JS 可直接呼叫使用的 Google 翻譯 API 吧。
使用 Google 翻譯 API 雖然可行,但還是有好幾個關卡要過:
由上可見,這段路可說處處陷阱。
不過只要按部就班、逐一通關,
實際做起來也不會太難啦。
一開始還請各位先不要緊張,
為了享受一鍵完成的暢快使用體驗,
接著就請各位綁好安全帶,
跟著我們一步一步前進吧。
首先,繼【原始內容】content.js、【彈出頁面】popup.html 之後,
【背景服務】background.js 終於要登場了。
我們先建立 background 子目錄,
然後在裡頭建立 background.js,
並到 manifest.json 裡做好宣告的動作。
"background": {
"service_worker": "background/background.js"
}
原本 Manifest V2 版本,背景服務還可以有個 html 檔案,掛入其他的 js 檔案,
但是到了 Manifest V3 版本之後,就只能使用單一個 background.js 了。
所以,我們必須把【背景服務】所要執行的工作,
全都寫在 background.js 之中。
做好以上動作之後,重新載入外掛,
就可以在【擴充功能】的列表畫面中,
看到外掛多了一行文字:
【查看檢視模式 Service Worker】
接著只要點擊 Service Worker,就可以進到 console 後台了。
我們可以先在這個後台測試一下向外連線的功能。
請在 console 後台輸入下面的指令:
fetch('https://www.google.com.tw/')
結果應該會回應一個 Promise,
只要點擊左邊的小三角形,
展開內容就可以看到它順利取得了 Response 回應。
如果回到網頁的【原始內容】,在原本的頁面中按下 F12,
進入 console 後台之後,
同樣輸入上面的指令,則會出現錯誤訊息。
至於【彈出頁面】會如何呢?
你可以點擊外掛的圖標,
彈出頁面後點擊滑鼠右鍵,
選擇【檢查】就可以打開相應的 console 後台。
輸入同樣的指令之後,可以看到它也能順利向外連線。
我們可以看到,由於安全上的考量,
【原始內容】的 content.js 並不能向外連線。
【彈出頁面】的 popup.js 雖然可以向外連線,
但它只在頁面彈開時才有作用,
大部分時候它都是無法作用的。
唯有【背景服務】的 background.js,
才能從瀏覽器開啟之後持續發揮作用,
就像常駐的服務一樣。
我們打算在【背景服務】執行的工作,
主要就是負責跨網域的向外連線。
由於【背景服務】無法引用其他 js 檔案,
因此像 jQuery、axios 等等常用的 js 函式庫,
很難在【背景服務】中使用。
還好,JS 還有一個原生的 Fetch API。
比如剛才我們使用的就是 fetch() 指令。
接著我們回到 Service Worker 的後台,
再試試向外連線到 Google 翻譯 API。
Google 翻譯 API 有 V2、V3 和 V3beta1 這幾個版本,
若要使用其服務,就必須提供憑證。
V2 版本需要用到一個【API_KEY】,
V3 和 V3beta1 更麻煩,
需要用到【API_KEY】、【ACCESS_TOKEN】和【PROJECT_ID】這幾個東西。
為了簡單起見,我們就用 V2 版本來嘗試一下好了。
取得 API_KEY 的方式這裡就不多說了,
實在不知道怎麼做的同學,
可以自行 Google 或參考這個連結。
取得 API_KEY 之後,請在 console 輸入以下的指令:
API_KEY='[你所取得的 API_KEY]'
p = fetch(`https://translation.googleapis.com/language/translate/v2?key=${API_KEY}`, {method: 'post', body: JSON.stringify({ q: 'hello, world!', target: "zh-TW" })})
p.then(r=>r.json()).then(r=>console.log(r.data.translations[0].translatedText))
一切順利的話,我們就可以開始編寫程式碼了。
這次程式碼的部分,內容比較多一些,
我們預計分成兩天來介紹。
今天我們先介紹,
如何讓使用者輸入自己的 API_KEY。
為什麼需要使用者輸入自己的 API_KEY 呢?
因為使用 Google 翻譯 API 來進行翻譯,
是有可能需要花錢的。
Google 翻譯 API 針對免費使用,有一定的配額。
各位可參考 Google 的說明。
這裡要特別提醒的是,大量使用一定會超過配額,
如果你的 API_KEY 被其他人利用,
也可能會因為被濫用而使你無緣無故被扣錢。
所以,請保管好您的 API_KEY,
千萬不要把 API_KEY 寫到程式碼中。
因此,我們在這裡會先設定一個輸入界面,
讓使用者可以輸入自己的 API_KEY,以供自己使用。
API_KEY 的輸入界面,
可透過【彈出頁面】來達成。
我們先在 popup.html 中,添加 API_KEY 的輸入界面:
<div>
<h3>一鍵翻譯:</h3>
<label for="api_key_input_text">
此功能會使用到 Google 翻譯 API。請使用您自己的 API_KEY:
</label>
<div>
<input type="text"
id="api_key_input_text"
name="api_key" required size="45">
<input type="button"
id="api_key_confirm_button"
value="送出" >
<input type="button"
id="api_key_clear_button"
value="清除" >
</div>
<div id="current_api_key">
目前尚未設定 API_KEY。。。
</div>
</div>
接著在 popup.js 中,編寫相應的處理函式:
// 點擊【送出】按鍵的話...
document.querySelector("#api_key_confirm_button").onclick = e =>{
var api_key = document.querySelector("#api_key_input_text").value
sendAPIKEY2Background(api_key)
}
// 在輸入框按下【Enter】的話...
document.querySelector("#api_key_input_text").onkeydown = e => {
if (e.key == 'Enter') {
var api_key = e.target.value
sendAPIKEY2Background(api_key)
}
}
// 點擊【清除】按鍵的話...
document.querySelector("#api_key_clear_button").onclick = e =>{
chrome.storage.local.remove('key',()=>{
document.querySelector("#current_api_key").innerHTML = '目前尚未設定 API_KEY。。。'
});
}
// 把 API_KEY 送往背景服務
function sendAPIKEY2Background(api_key) {
chrome.runtime.sendMessage(
{cmd: 'api_key', data: {api_key: api_key}},
(response) => {
// background.js 那邊收到 API_KEY,會把 API_KEY 再送回來。
if (response == api_key){
partial_api_key = (api_key.length>5)?
api_key.substring(0,5)+'......' : api_key
var desc = document.querySelector("#current_api_key")
desc.innerHTML = `目前 API_KEY:${partial_api_key}`
}
});
}
我們可以看到,
【彈出頁面】利用 sendMessage 的機制,
送出了使用者所輸入的 API_KEY。
接著在【背景服務】background.js 這邊,
則利用 onMessage 來接收這個 API_KEY:
var api_key = null // 設為全域變數,稍後向外部請求翻譯時還會用到。
// 回應【原始內容】或【彈出頁面】送過來的請求
chrome.runtime.onMessage.addListener(
(message, sender, sendResponse) => {
if (message.cmd == 'api_key') {
api_key = message.data.api_key
sendResponse(api_key); // 再把 api_key 當做回應送回去
}
});
到此為止,我們的【背景服務】,
總算可以拿到使用者所輸入的 API_KEY 了。
有了這個 API_KEY,
我們就可以向外部取得翻譯結果了。
不過我們今天暫且就此打住,
其他部分明天再繼續囉。。。