From ea1e23172cd60f599f55e9e7efc097a47b7051db Mon Sep 17 00:00:00 2001 From: Xun <58657914+eeeXun@users.noreply.github.com> Date: Fri, 10 Mar 2023 20:51:38 +0800 Subject: [PATCH] feat(translator): add ReversoTranslate (#14) --- README.md | 18 ++- config.go | 2 + .../translate/reversotranslate/language.go | 82 +++++++++++++ .../translate/reversotranslate/translator.go | 116 ++++++++++++++++++ internal/translate/reversotranslate/tts.go | 75 +++++++++++ internal/translate/translator.go | 8 +- ui.go | 4 +- 7 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 internal/translate/reversotranslate/language.go create mode 100644 internal/translate/reversotranslate/translator.go create mode 100644 internal/translate/reversotranslate/tts.go diff --git a/README.md b/README.md index dc7e93e..797d24e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Support: [`ApertiumTranslate`](https://www.apertium.org/), [`ArgosTranslate`](https://translate.argosopentech.com/), [`GoogleTranslate`](https://translate.google.com/) +[`ReversoTranslate`](https://www.reverso.net/text-translation) ## ScreenShot @@ -43,10 +44,12 @@ You can pass `-src` and `-dst` in argument to set source and destination languag gtt -src "English" -dst "Chinese (Traditional)" ``` -See language on -[Apertium Translate](https://www.apertium.org/) for `ApertiumTranslate`, -[argosopentech/argos-translate](https://github.com/argosopentech/argos-translate#supported-languages) for `ArgosTranslate`, -[Google Language support](https://cloud.google.com/translate/docs/languages) for `GoogleTranslate`. +See language on: + +- [Apertium Translate](https://www.apertium.org/) for `ApertiumTranslate` +- [argosopentech/argos-translate](https://github.com/argosopentech/argos-translate#supported-languages) for `ArgosTranslate` +- [Google Language support](https://cloud.google.com/translate/docs/languages) for `GoogleTranslate` +- [Reverso Translation](https://www.reverso.net/text-translation) for `ReversoTranslate` ## Key Map @@ -87,7 +90,7 @@ Stop play sound. Toggle transparent. `` -Toggle Definition & Part of speech +Toggle Definition/Example & Part of speech. ``, `` Cycle through the pop out widget. @@ -105,7 +108,10 @@ Switch pop out window. ## Credit -[soimort/translate-shell](https://github.com/soimort/translate-shell) For translation URL. +[soimort/translate-shell](https://github.com/soimort/translate-shell), +[SimplyTranslate-Engines](https://codeberg.org/SimpleWeb/SimplyTranslate-Engines), +[s0ftik3/reverso-api](https://github.com/s0ftik3/reverso-api) +For translation URL. [snsd0805/GoogleTranslate-TUI](https://github.com/snsd0805/GoogleTranslate-TUI) For inspiration. diff --git a/config.go b/config.go index 4088383..e7b7276 100644 --- a/config.go +++ b/config.go @@ -24,6 +24,8 @@ func configInit() { "destination.language.argostranslate": "English", "source.language.googletranslate": "English", "destination.language.googletranslate": "English", + "source.language.reversotranslate": "English", + "destination.language.reversotranslate": "English", "translator": "ArgosTranslate", } ) diff --git a/internal/translate/reversotranslate/language.go b/internal/translate/reversotranslate/language.go new file mode 100644 index 0000000..750f255 --- /dev/null +++ b/internal/translate/reversotranslate/language.go @@ -0,0 +1,82 @@ +package reversotranslate + +var ( + lang = []string{ + "Arabic", + "Chinese (Simplified)", + "Czech", + "Danish", + "Dutch", + "English", + "French", + "German", + "Greek", + "Hebrew", + "Hindi", + "Hungarian", + "Italian", + "Japanese", + "Korean", + "Persian", + "Polish", + "Portuguese", + "Romanian", + "Russian", + "Slovak", + "Spanish", + "Swedish", + "Thai", + "Turkish", + "Ukrainian", + } + langCode = map[string]string{ + "Arabic": "ara", + "Chinese (Simplified)": "chi", + "Czech": "cze", + "Danish": "dan", + "Dutch": "dut", + "English": "eng", + "French": "fra", + "German": "ger", + "Greek": "gre", + "Hebrew": "heb", + "Hindi": "hin", + "Hungarian": "hun", + "Italian": "ita", + "Japanese": "jpn", + "Korean": "kor", + "Persian": "per", + "Polish": "pol", + "Portuguese": "por", + "Romanian": "rum", + "Russian": "rus", + "Slovak": "slo", + "Spanish": "spa", + "Swedish": "swe", + "Thai": "tha", + "Turkish": "tur", + "Ukrainian": "ukr", + } + voiceName = map[string]string{ + "Arabic": "Mehdi22k", + "Chinese (Simplified)": "Lulu22k", + "Czech": "Eliska22k", + "Danish": "Mette22k", + "Dutch": "Sofie22k", + "English": "Heather22k", + "French": "Alice22k", + "German": "Andreas22k", + "Greek": "Dimitris22k", + "Hebrew": "he-IL-Asaf", + "Italian": "Fabiana22k", + "Japanese": "Sakura22k", + "Korean": "Minji22k", + "Polish": "Monika22k", + "Portuguese": "Celia22k", + "Romanian": "ro-RO-Andrei", + "Russian": "Alyona22k", + "Spanish": "Antonio22k", + "Swedish": "Emma22k", + "Turkish": "Ipek22k", + } +) diff --git a/internal/translate/reversotranslate/translator.go b/internal/translate/reversotranslate/translator.go new file mode 100644 index 0000000..66fe302 --- /dev/null +++ b/internal/translate/reversotranslate/translator.go @@ -0,0 +1,116 @@ +package reversotranslate + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "regexp" + + "github.com/eeeXun/gtt/internal/lock" +) + +const ( + textURL = "https://api.reverso.net/translate/v1/translation" + userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" +) + +type ReversoTranslate struct { + srcLang string + dstLang string + EngineName string + SoundLock *lock.Lock +} + +func (t *ReversoTranslate) GetEngineName() string { + return t.EngineName +} + +func (t *ReversoTranslate) GetAllLang() []string { + return lang +} + +func (t *ReversoTranslate) GetSrcLang() string { + return t.srcLang +} + +func (t *ReversoTranslate) GetDstLang() string { + return t.dstLang +} + +func (t *ReversoTranslate) SetSrcLang(srcLang string) { + t.srcLang = srcLang +} + +func (t *ReversoTranslate) SetDstLang(dstLang string) { + t.dstLang = dstLang +} + +func (t *ReversoTranslate) SwapLang() { + t.srcLang, t.dstLang = t.dstLang, t.srcLang +} + +func (t *ReversoTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) { + var data map[string]interface{} + + userData, _ := json.Marshal(map[string]interface{}{ + "format": "text", + "from": langCode[t.srcLang], + "to": langCode[t.dstLang], + "input": message, + "options": map[string]string{ + "sentenceSplitter": "true", + "origin": "translation.web", + "contextResults": "true", + "languageDetection": "false", + }, + }) + req, _ := http.NewRequest("POST", + textURL, + bytes.NewBuffer([]byte(userData))) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("User-Agent", userAgent) + res, err := http.DefaultClient.Do(req) + 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 { + return "", "", "", errors.New("Translation not found") + } + + // translation + translation += fmt.Sprintf("%v", data["translation"].([]interface{})[0]) + // definition and part of speech + if data["contextResults"] != nil { + for _, results := range data["contextResults"].(map[string]interface{})["results"].([]interface{}) { + results := results.(map[string]interface{}) + // definition + srcExample := results["sourceExamples"].([]interface{}) + dstExample := results["targetExamples"].([]interface{}) + if len(srcExample) > 0 && len(dstExample) > 0 { + for i := 0; i < len(srcExample) && i < len(dstExample); i++ { + definition += fmt.Sprintf("- %v\n\t\"%v\"\n", srcExample[i], dstExample[i]) + } + } + // part of speech + if results["partOfSpeech"] == nil { + partOfSpeech += fmt.Sprintf("%v\n", results["translation"]) + } else { + partOfSpeech += fmt.Sprintf("%v [%v]\n", results["translation"], results["partOfSpeech"]) + } + } + definition = regexp.MustCompile("<(|/)em>").ReplaceAllString(definition, "") + } + + return translation, definition, partOfSpeech, nil +} diff --git a/internal/translate/reversotranslate/tts.go b/internal/translate/reversotranslate/tts.go new file mode 100644 index 0000000..35b96de --- /dev/null +++ b/internal/translate/reversotranslate/tts.go @@ -0,0 +1,75 @@ +package reversotranslate + +import ( + "encoding/base64" + "errors" + "fmt" + "net/http" + "time" + + "github.com/hajimehoshi/go-mp3" + "github.com/hajimehoshi/oto/v2" +) + +const ( + ttsURL = "https://voice.reverso.net/RestPronunciation.svc/v1/output=json/GetVoiceStream/voiceName=%s?voiceSpeed=80&inputText=%s" +) + +func (t *ReversoTranslate) LockAvailable() bool { + return t.SoundLock.Available() +} + +func (t *ReversoTranslate) LockAcquire() { + t.SoundLock.Acquire() +} + +func (t *ReversoTranslate) StopTTS() { + t.SoundLock.Stop = true +} + +func (t *ReversoTranslate) PlayTTS(lang, message string) error { + name, ok := voiceName[lang] + if !ok { + return errors.New(t.EngineName + " does not support text to speech of " + lang) + } + urlStr := fmt.Sprintf( + ttsURL, + name, + base64.StdEncoding.EncodeToString([]byte(message)), + ) + req, _ := http.NewRequest("GET", urlStr, nil) + req.Header.Add("User-Agent", userAgent) + res, err := http.DefaultClient.Do(req) + 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 +} diff --git a/internal/translate/translator.go b/internal/translate/translator.go index 2f4541e..37c8917 100644 --- a/internal/translate/translator.go +++ b/internal/translate/translator.go @@ -5,10 +5,11 @@ import ( "github.com/eeeXun/gtt/internal/translate/apertiumtranslate" "github.com/eeeXun/gtt/internal/translate/argostranslate" "github.com/eeeXun/gtt/internal/translate/googletranslate" + "github.com/eeeXun/gtt/internal/translate/reversotranslate" ) var ( - AllTranslator = []string{"ApertiumTranslate", "ArgosTranslate", "GoogleTranslate"} + AllTranslator = []string{"ApertiumTranslate", "ArgosTranslate", "GoogleTranslate", "ReversoTranslate"} ) type Translator interface { @@ -48,6 +49,11 @@ func NewTranslator(name string) Translator { EngineName: "GoogleTranslate", SoundLock: lock.NewLock(), } + case "ReversoTranslate": + translator = &reversotranslate.ReversoTranslate{ + EngineName: "ReversoTranslate", + SoundLock: lock.NewLock(), + } } return translator diff --git a/ui.go b/ui.go index 2185e32..016e34d 100644 --- a/ui.go +++ b/ui.go @@ -38,7 +38,7 @@ const ( [#%[1]s][-] Toggle transparent. [#%[1]s][-] - Toggle Definition & Part of speech + Toggle Definition/Example & Part of speech. [#%[1]s], [-] Cycle through the pop out widget. [#%[1]s]<1>, <2>, <3>[-] @@ -237,7 +237,7 @@ func uiInit() { // input/output srcInput.SetBorder(true) dstOutput.SetBorder(true) - defOutput.SetBorder(true).SetTitle("Definition") + defOutput.SetBorder(true).SetTitle("Definition/Example") posOutput.SetBorder(true).SetTitle("Part of speech") // dropdown