mirror of
https://github.com/eeeXun/GTT.git
synced 2025-05-16 07:40:44 -07:00
refactor: make translator to an interface
This commit is contained in:
parent
b27a7dc11b
commit
ed2bd312a3
25
config.go
25
config.go
@ -16,11 +16,12 @@ var (
|
||||
defaultConfig = map[string]interface{}{
|
||||
"transparent": false,
|
||||
"theme": "Gruvbox",
|
||||
"source.language": "English",
|
||||
"source.borderColor": "red",
|
||||
"destination.language": "Chinese (Traditional)",
|
||||
"destination.borderColor": "blue",
|
||||
"source.google.language": "English",
|
||||
"destination.google.language": "Chinese (Traditional)",
|
||||
"hide_below": false,
|
||||
"translator": "google",
|
||||
}
|
||||
)
|
||||
|
||||
@ -62,15 +63,19 @@ func configInit() {
|
||||
}
|
||||
|
||||
// setup
|
||||
switch config.GetString("translator") {
|
||||
case "google":
|
||||
translator = googleTranslate
|
||||
if len(*srcLangArg) > 0 {
|
||||
translator.SrcLang = *srcLangArg
|
||||
translator.SetSrcLang(*srcLangArg)
|
||||
} else {
|
||||
translator.SrcLang = config.GetString("source.language")
|
||||
translator.SetSrcLang(config.GetString("source.google.language"))
|
||||
}
|
||||
if len(*dstLangArg) > 0 {
|
||||
translator.DstLang = *dstLangArg
|
||||
translator.SetDstLang(*dstLangArg)
|
||||
} else {
|
||||
translator.DstLang = config.GetString("destination.language")
|
||||
translator.SetDstLang(config.GetString("destination.google.language"))
|
||||
}
|
||||
}
|
||||
hideBelow = config.GetBool("hide_below")
|
||||
style.Theme = config.GetString("theme")
|
||||
@ -85,15 +90,15 @@ func updateConfig() {
|
||||
|
||||
// Source language is not passed in argument
|
||||
if len(*srcLangArg) == 0 &&
|
||||
config.GetString("source.language") != translator.SrcLang {
|
||||
config.GetString("source.google.language") != googleTranslate.GetSrcLang() {
|
||||
changed = true
|
||||
config.Set("source.language", translator.SrcLang)
|
||||
config.Set("source.google.language", googleTranslate.GetSrcLang())
|
||||
}
|
||||
// Destination language is not passed in argument
|
||||
if len(*dstLangArg) == 0 &&
|
||||
config.GetString("destination.language") != translator.DstLang {
|
||||
config.GetString("destination.google.language") != googleTranslate.GetDstLang() {
|
||||
changed = true
|
||||
config.Set("destination.language", translator.DstLang)
|
||||
config.Set("destination.google.language", googleTranslate.GetDstLang())
|
||||
}
|
||||
if config.GetBool("hide_below") != hideBelow {
|
||||
changed = true
|
||||
|
@ -1,4 +1,4 @@
|
||||
package translate
|
||||
package lock
|
||||
|
||||
type Lock struct {
|
||||
Stop bool
|
@ -1,8 +1,8 @@
|
||||
package translate
|
||||
package google
|
||||
|
||||
// https://cloud.google.com/translate/docs/languages
|
||||
var (
|
||||
Lang = []string{
|
||||
lang = []string{
|
||||
"Afrikaans",
|
||||
"Albanian",
|
||||
"Amharic",
|
||||
@ -113,7 +113,7 @@ var (
|
||||
"Yoruba",
|
||||
"Zulu",
|
||||
}
|
||||
LangCode = map[string]string{
|
||||
langCode = map[string]string{
|
||||
"Afrikaans": "af",
|
||||
"Albanian": "sq",
|
||||
"Amharic": "am",
|
127
internal/translate/google/translator.go
Normal file
127
internal/translate/google/translator.go
Normal file
@ -0,0 +1,127 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gtt/internal/lock"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
textURL = "https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&dt=bd&dt=md&dt=ex&sl=%s&tl=%s&q=%s"
|
||||
)
|
||||
|
||||
type GoogleTranslate struct {
|
||||
srcLang string
|
||||
dstLang string
|
||||
SoundLock *lock.Lock
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) GetAllLang() []string {
|
||||
return lang
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) GetSrcLang() string {
|
||||
return t.srcLang
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) GetDstLang() string {
|
||||
return t.dstLang
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) SetSrcLang(srcLang string) {
|
||||
t.srcLang = srcLang
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) SetDstLang(dstLang string) {
|
||||
t.dstLang = dstLang
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) SwapLang() {
|
||||
t.srcLang, t.dstLang = t.dstLang, t.srcLang
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) Translate(message string) (
|
||||
translation string,
|
||||
definition string,
|
||||
partOfSpeech string,
|
||||
err error) {
|
||||
var data []interface{}
|
||||
|
||||
urlStr := fmt.Sprintf(
|
||||
textURL,
|
||||
langCode[t.srcLang],
|
||||
langCode[t.dstLang],
|
||||
url.QueryEscape(message),
|
||||
)
|
||||
res, err := http.Get(urlStr)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &data); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
// translation = data[0]
|
||||
for _, lines := range data[0].([]interface{}) {
|
||||
translatedLine := lines.([]interface{})[0]
|
||||
translation += fmt.Sprintf("%v", translatedLine)
|
||||
}
|
||||
|
||||
// part of speech = data[1]
|
||||
if data[1] != nil {
|
||||
for _, parts := range data[1].([]interface{}) {
|
||||
// part of speech
|
||||
part := parts.([]interface{})[0]
|
||||
partOfSpeech += fmt.Sprintf("[%v]\n", part)
|
||||
for _, words := range parts.([]interface{})[2].([]interface{}) {
|
||||
// dst lang
|
||||
dstWord := words.([]interface{})[0]
|
||||
partOfSpeech += fmt.Sprintf("\t%v:", dstWord)
|
||||
// src lang
|
||||
firstWord := true
|
||||
for _, word := range words.([]interface{})[1].([]interface{}) {
|
||||
if firstWord {
|
||||
partOfSpeech += fmt.Sprintf(" %v", word)
|
||||
firstWord = false
|
||||
} else {
|
||||
partOfSpeech += fmt.Sprintf(", %v", word)
|
||||
}
|
||||
}
|
||||
partOfSpeech += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// definition = data[12]
|
||||
if len(data) >= 13 && data[12] != nil {
|
||||
for _, parts := range data[12].([]interface{}) {
|
||||
// part of speech
|
||||
part := parts.([]interface{})[0]
|
||||
definition += fmt.Sprintf("[%v]\n", part)
|
||||
for _, sentences := range parts.([]interface{})[1].([]interface{}) {
|
||||
// definition
|
||||
def := sentences.([]interface{})[0]
|
||||
definition += fmt.Sprintf("\t- %v\n", def)
|
||||
// example sentence
|
||||
if len(sentences.([]interface{})) >= 3 && sentences.([]interface{})[2] != nil {
|
||||
example := sentences.([]interface{})[2]
|
||||
definition += fmt.Sprintf("\t\t\"%v\"\n", example)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return translation, definition, partOfSpeech, nil
|
||||
}
|
||||
|
||||
return "", "", "", errors.New("Translation not found")
|
||||
}
|
68
internal/translate/google/tts.go
Normal file
68
internal/translate/google/tts.go
Normal file
@ -0,0 +1,68 @@
|
||||
package google
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3"
|
||||
"github.com/hajimehoshi/oto/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
ttsURL = "https://translate.google.com.vn/translate_tts?ie=UTF-8&q=%s&tl=%s&client=tw-ob"
|
||||
)
|
||||
|
||||
func (t *GoogleTranslate) LockAvailable() bool {
|
||||
return t.SoundLock.Available()
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) LockAcquire() {
|
||||
t.SoundLock.Acquire()
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) StopTTS() {
|
||||
t.SoundLock.Stop = true
|
||||
}
|
||||
|
||||
func (t *GoogleTranslate) PlayTTS(lang string, message string) error {
|
||||
urlStr := fmt.Sprintf(
|
||||
ttsURL,
|
||||
url.QueryEscape(message),
|
||||
langCode[lang],
|
||||
)
|
||||
res, err := http.Get(urlStr)
|
||||
if err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
decoder, err := mp3.NewDecoder(res.Body)
|
||||
if err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
otoCtx, readyChan, err := oto.NewContext(decoder.SampleRate(), 2, 2)
|
||||
if err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
<-readyChan
|
||||
player := otoCtx.NewPlayer(decoder)
|
||||
player.Play()
|
||||
for player.IsPlaying() {
|
||||
if t.SoundLock.Stop {
|
||||
t.SoundLock.Release()
|
||||
return nil
|
||||
} else {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
if err = player.Close(); err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
|
||||
t.SoundLock.Release()
|
||||
return nil
|
||||
}
|
@ -1,154 +1,32 @@
|
||||
package translate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/hajimehoshi/go-mp3"
|
||||
"github.com/hajimehoshi/oto/v2"
|
||||
"gtt/internal/lock"
|
||||
"gtt/internal/translate/google"
|
||||
)
|
||||
|
||||
const (
|
||||
textURL = "https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&dt=bd&dt=md&dt=ex&sl=%s&tl=%s&q=%s"
|
||||
soundURL = "https://translate.google.com.vn/translate_tts?ie=UTF-8&q=%s&tl=%s&client=tw-ob"
|
||||
)
|
||||
|
||||
type Translator struct {
|
||||
SrcLang string
|
||||
DstLang string
|
||||
SoundLock *Lock
|
||||
}
|
||||
|
||||
func NewTranslator() *Translator {
|
||||
return &Translator{
|
||||
SoundLock: NewLock(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Translator) Translate(message string) (
|
||||
type Translator interface {
|
||||
// text
|
||||
GetAllLang() []string
|
||||
GetSrcLang() string
|
||||
GetDstLang() string
|
||||
SetSrcLang(srcLang string)
|
||||
SetDstLang(dstLang string)
|
||||
SwapLang()
|
||||
Translate(message string) (
|
||||
translation string,
|
||||
definition string,
|
||||
partOfSpeech string,
|
||||
err error) {
|
||||
var data []interface{}
|
||||
|
||||
urlStr := fmt.Sprintf(
|
||||
textURL,
|
||||
LangCode[t.SrcLang],
|
||||
LangCode[t.DstLang],
|
||||
url.QueryEscape(message),
|
||||
)
|
||||
res, err := http.Get(urlStr)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
err error)
|
||||
// text to speech
|
||||
LockAvailable() bool
|
||||
LockAcquire()
|
||||
StopTTS()
|
||||
PlayTTS(lang string, message string) error
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &data); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
if len(data) > 0 {
|
||||
// translation = data[0]
|
||||
for _, lines := range data[0].([]interface{}) {
|
||||
translatedLine := lines.([]interface{})[0]
|
||||
translation += fmt.Sprintf("%v", translatedLine)
|
||||
}
|
||||
|
||||
// part of speech = data[1]
|
||||
if data[1] != nil {
|
||||
for _, parts := range data[1].([]interface{}) {
|
||||
// part of speech
|
||||
part := parts.([]interface{})[0]
|
||||
partOfSpeech += fmt.Sprintf("[%v]\n", part)
|
||||
for _, words := range parts.([]interface{})[2].([]interface{}) {
|
||||
// dst lang
|
||||
dstWord := words.([]interface{})[0]
|
||||
partOfSpeech += fmt.Sprintf("\t%v:", dstWord)
|
||||
// src lang
|
||||
firstWord := true
|
||||
for _, word := range words.([]interface{})[1].([]interface{}) {
|
||||
if firstWord {
|
||||
partOfSpeech += fmt.Sprintf(" %v", word)
|
||||
firstWord = false
|
||||
} else {
|
||||
partOfSpeech += fmt.Sprintf(", %v", word)
|
||||
func NewGoogleTranslate() *google.GoogleTranslate {
|
||||
return &google.GoogleTranslate{
|
||||
SoundLock: lock.NewLock(),
|
||||
}
|
||||
}
|
||||
partOfSpeech += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// definition = data[12]
|
||||
if len(data) >= 13 && data[12] != nil {
|
||||
for _, parts := range data[12].([]interface{}) {
|
||||
// part of speech
|
||||
part := parts.([]interface{})[0]
|
||||
definition += fmt.Sprintf("[%v]\n", part)
|
||||
for _, sentences := range parts.([]interface{})[1].([]interface{}) {
|
||||
// definition
|
||||
def := sentences.([]interface{})[0]
|
||||
definition += fmt.Sprintf("\t- %v\n", def)
|
||||
// example sentence
|
||||
if len(sentences.([]interface{})) >= 3 && sentences.([]interface{})[2] != nil {
|
||||
example := sentences.([]interface{})[2]
|
||||
definition += fmt.Sprintf("\t\t\"%v\"\n", example)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return translation, definition, partOfSpeech, nil
|
||||
}
|
||||
|
||||
return "", "", "", errors.New("Translation not found")
|
||||
}
|
||||
|
||||
func (t *Translator) PlaySound(lang string, message string) error {
|
||||
urlStr := fmt.Sprintf(
|
||||
soundURL,
|
||||
url.QueryEscape(message),
|
||||
LangCode[lang],
|
||||
)
|
||||
res, err := http.Get(urlStr)
|
||||
if err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
decoder, err := mp3.NewDecoder(res.Body)
|
||||
if err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
otoCtx, readyChan, err := oto.NewContext(decoder.SampleRate(), 2, 2)
|
||||
if err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
<-readyChan
|
||||
player := otoCtx.NewPlayer(decoder)
|
||||
player.Play()
|
||||
for player.IsPlaying() {
|
||||
if t.SoundLock.Stop {
|
||||
t.SoundLock.Release()
|
||||
return nil
|
||||
} else {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
if err = player.Close(); err != nil {
|
||||
t.SoundLock.Release()
|
||||
return err
|
||||
}
|
||||
|
||||
t.SoundLock.Release()
|
||||
return nil
|
||||
}
|
||||
|
3
main.go
3
main.go
@ -15,7 +15,8 @@ var (
|
||||
srcLangArg *string = flag.String("src", "", "Set source language")
|
||||
dstLangArg *string = flag.String("dst", "", "Set destination language")
|
||||
// Translate
|
||||
translator = translate.NewTranslator()
|
||||
translator translate.Translator
|
||||
googleTranslate = translate.NewGoogleTranslate()
|
||||
// UI
|
||||
app = tview.NewApplication()
|
||||
srcInput = tview.NewTextArea()
|
||||
|
37
ui.go
37
ui.go
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"gtt/internal/color"
|
||||
"gtt/internal/translate"
|
||||
"strconv"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
@ -172,16 +171,14 @@ func updateAllColor() {
|
||||
|
||||
// Update title and option
|
||||
func updateTitle() {
|
||||
srcInput.SetTitle(translator.SrcLang)
|
||||
dstOutput.SetTitle(translator.DstLang)
|
||||
srcInput.SetTitle(translator.GetSrcLang())
|
||||
dstOutput.SetTitle(translator.GetDstLang())
|
||||
srcLangDropDown.SetCurrentOption(
|
||||
IndexOf(translator.SrcLang,
|
||||
translate.Lang)).
|
||||
SetTitle(translator.SrcLang)
|
||||
IndexOf(translator.GetSrcLang(), translator.GetAllLang())).
|
||||
SetTitle(translator.GetSrcLang())
|
||||
dstLangDropDown.SetCurrentOption(
|
||||
IndexOf(translator.DstLang,
|
||||
translate.Lang)).
|
||||
SetTitle(translator.DstLang)
|
||||
IndexOf(translator.GetDstLang(), translator.GetAllLang())).
|
||||
SetTitle(translator.GetDstLang())
|
||||
}
|
||||
|
||||
func attachButton() *tview.Flex {
|
||||
@ -204,7 +201,7 @@ func uiInit() {
|
||||
|
||||
// dropdown
|
||||
for _, langDropDown := range []*tview.DropDown{srcLangDropDown, dstLangDropDown} {
|
||||
langDropDown.SetOptions(translate.Lang, nil).
|
||||
langDropDown.SetOptions(translator.GetAllLang(), nil).
|
||||
SetBorder(true)
|
||||
}
|
||||
themeDropDown.SetLabel("Theme: ").
|
||||
@ -323,13 +320,13 @@ func uiInit() {
|
||||
keyMapWindow.SetInputCapture(popOutWindowHandler)
|
||||
srcLangDropDown.SetDoneFunc(langDropDownHandler).
|
||||
SetSelectedFunc(func(text string, index int) {
|
||||
translator.SrcLang = text
|
||||
translator.SetSrcLang(text)
|
||||
srcInput.SetTitle(text)
|
||||
srcLangDropDown.SetTitle(text)
|
||||
})
|
||||
dstLangDropDown.SetDoneFunc(langDropDownHandler).
|
||||
SetSelectedFunc(func(text string, index int) {
|
||||
translator.DstLang = text
|
||||
translator.SetDstLang(text)
|
||||
dstOutput.SetTitle(text)
|
||||
dstLangDropDown.SetTitle(text)
|
||||
})
|
||||
@ -444,7 +441,7 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
|
||||
CopyToClipboard(text[:len(text)-1])
|
||||
}
|
||||
case tcell.KeyCtrlS:
|
||||
translator.SrcLang, translator.DstLang = translator.DstLang, translator.SrcLang
|
||||
translator.SwapLang()
|
||||
updateTitle()
|
||||
srcText := srcInput.GetText()
|
||||
dstText := dstOutput.GetText(false)
|
||||
@ -457,13 +454,13 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
|
||||
dstOutput.SetText(srcText)
|
||||
case tcell.KeyCtrlO:
|
||||
// Play source sound
|
||||
if translator.SoundLock.Available() {
|
||||
if translator.LockAvailable() {
|
||||
message := srcInput.GetText()
|
||||
// Only play when message exist
|
||||
if len(message) > 0 {
|
||||
translator.SoundLock.Acquire()
|
||||
translator.LockAcquire()
|
||||
go func() {
|
||||
err := translator.PlaySound(translator.SrcLang, message)
|
||||
err := translator.PlayTTS(translator.GetSrcLang(), message)
|
||||
if err != nil {
|
||||
srcInput.SetText(err.Error(), true)
|
||||
}
|
||||
@ -473,13 +470,13 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
|
||||
}
|
||||
case tcell.KeyCtrlP:
|
||||
// Play destination sound
|
||||
if translator.SoundLock.Available() {
|
||||
if translator.LockAvailable() {
|
||||
message := dstOutput.GetText(false)
|
||||
// Only play when message exist
|
||||
if len(message) > 0 {
|
||||
translator.SoundLock.Acquire()
|
||||
translator.LockAcquire()
|
||||
go func() {
|
||||
err := translator.PlaySound(translator.DstLang, message)
|
||||
err := translator.PlayTTS(translator.GetDstLang(), message)
|
||||
if err != nil {
|
||||
dstOutput.SetText(err.Error())
|
||||
}
|
||||
@ -488,7 +485,7 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
|
||||
}
|
||||
case tcell.KeyCtrlX:
|
||||
// Stop play sound
|
||||
translator.SoundLock.Stop = true
|
||||
translator.StopTTS()
|
||||
}
|
||||
|
||||
return event
|
||||
|
Loading…
x
Reference in New Issue
Block a user