之前我的 pdf 阅读器上使用的是浏览器的翻译插件,但是我现在突然想让阅读器本身自带翻译和朗读功能。
我们可以使用一下两款 golang package 来完成目标:
- github.com/Conight/go-googletrans: 使用免费的 api 进行文本翻译;
- github.com/hegedustibor/htgo-tts: 使用免费的 api 进行语言合成朗读;
我们可以先使用 go get 命令来安装它们。
导入包
我们先导入它们:
import (
"github.com/Conight/go-googletrans"
"github.com/hegedustibor/htgo-tts"
"github.com/hegedustibor/htgo-tts/voices"
)
实现对应的 handler 函数
1. 文本翻译
func translate_handler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
http.Error(w, "load request faild", http.StatusBadRequest)
return
}
var params SelectionParams
err = json.Unmarshal(body, ¶ms)
if err != nil {
log.Println(err)
http.Error(w, "load request faild", http.StatusBadRequest)
return
}
log.Println(params)
t := translator.New()
result, err := t.Translate(params.Words, "auto", "zh-CN")
if err != nil {
log.Println(err)
http.Error(w, "translate faild", http.StatusInternalServerError)
return
}
fmt.Fprint(w, result.Text)
}
2. 语言合成朗读
func to_speech_handler(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
log.Println(err)
http.Error(w, "load request faild", http.StatusBadRequest)
return
}
var params SelectionParams
err = json.Unmarshal(body, ¶ms)
if err != nil {
log.Println(err)
http.Error(w, "load request faild", http.StatusBadRequest)
return
}
log.Println(params)
speech := htgotts.Speech{Folder: "audio", Language: voices.English}
err = speech.Speak(params.Words)
if err != nil {
log.Println(err)
http.Error(w, "convert to speech faild", http.StatusInternalServerError)
}
}
我们还需要为这两个函数指定路由:
http.HandleFunc("/translate", translate_handler)
http.HandleFunc("/to_speech", to_speech_handler)
重新定义右键菜单
我们需要通过前端代码重新定义右键菜单:
function create_context_menu() {
const contextMenu = document.createElement('div');
document.body.insertAdjacentElement('beforeEnd', contextMenu);
contextMenu.style.position = 'fixed';
contextMenu.hidden = true;
const itemTranslate = document.createElement('button');
contextMenu.appendChild(itemTranslate);
itemTranslate.innerText = 'Translate';
itemTranslate.onclick = async () => {
const origin = document.getSelection().toString();
const resp = await fetch('/translate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
words: origin,
})
})
if (!resp.ok) {
return;
}
const text = await resp.text()
console.log(text);
alert(text);
}
const itemToSpeech = document.createElement('button');
contextMenu.appendChild(itemToSpeech);
itemToSpeech.innerText = 'To Speech';
itemToSpeech.onclick = async () => {
const origin = document.getSelection().toString();
const resp = await fetch('/to_speech', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
words: origin,
})
})
if (!resp.ok) {
return;
}
}
document.addEventListener('contextmenu', (event) => {
event.preventDefault();
contextMenu.style.left = event.clientX + 'px';
contextMenu.style.top = event.clientY + 'px';
contextMenu.hidden = false;
});
document.addEventListener('click', () => {
contextMenu.hidden = true;
});
}
在 HTML 文档加载完成的时候运行它:
window.onload = async () => {
const page_ix = (new URLSearchParams(location.search)).get('start') || 0;
await load_page('beforeEnd', page_ix);
await load_page('afterBegin', page_ix - 1);
load_more();
// 我们自定义的右键菜单
create_context_menu();
}