published on in Go

自制PDF阅读器・翻译与朗读

之前我的 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, &params)
    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, &params)
    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();
}

效果演示