v/GTT
1
0
mirror of https://github.com/eeeXun/GTT.git synced 2025-05-18 08:40:35 -07:00

Merge branch 'master' into theme

This commit is contained in:
eeeXun 2023-05-12 17:32:00 +08:00
commit 2f7037c146
22 changed files with 622 additions and 269 deletions

View File

@ -3,11 +3,22 @@
Google Translate TUI (Originally)
Supported Translator:
[`ApertiumTranslate`](https://www.apertium.org/),
[`ArgosTranslate`](https://translate.argosopentech.com/)(default),
[`BingTranslate`](https://www.bing.com/translator),
[`GoogleTranslate`](https://translate.google.com/),
[`ReversoTranslate`](https://www.reverso.net/text-translation)
[`Apertium`](https://www.apertium.org/),
[`Argos`](https://translate.argosopentech.com/),
[`Bing`](https://www.bing.com/translator),
[`ChatGPT`](https://chat.openai.com/),
[`Google`](https://translate.google.com/)(default),
[`Reverso`](https://www.reverso.net/text-translation)
## ⚠️ Note for ChatGPT
You need to apply an API key on [OpenAI API keys](https://platform.openai.com/account/api-keys).
And write it to `$XDG_CONFIG_HOME/gtt/gtt.yaml` or `$HOME/.config/gtt/gtt.yaml`.
```yaml
api_key:
chatgpt: YOUR_API_KEY # <- Replace with your API Key
```
## ScreenShot
@ -47,11 +58,12 @@ gtt -src "English" -dst "Chinese (Traditional)"
See available languages on:
- [Apertium Translate](https://www.apertium.org/) for `ApertiumTranslate`
- [argosopentech/argos-translate](https://github.com/argosopentech/argos-translate#supported-languages) for `ArgosTranslate`
- [Bing language-support](https://learn.microsoft.com/en-us/azure/cognitive-services/translator/language-support#translation) for `BingTranslate`
- [Google Language support](https://cloud.google.com/translate/docs/languages) for `GoogleTranslate`
- [Reverso Translation](https://www.reverso.net/text-translation) for `ReversoTranslate`
- [Apertium Translate](https://www.apertium.org/) for `Apertium`
- [argosopentech/argos-translate](https://github.com/argosopentech/argos-translate#supported-languages) for `Argos`
- [Bing language-support](https://learn.microsoft.com/en-us/azure/cognitive-services/translator/language-support#translation) for `Bing`
- `ChatGPT` is same as `Google`. See [Google Language support](https://cloud.google.com/translate/docs/languages)
- [Google Language support](https://cloud.google.com/translate/docs/languages) for `Google`
- [Reverso Translation](https://www.reverso.net/text-translation) for `Reverso`
## Key Map
@ -80,10 +92,10 @@ Copy all text in source of translation window.
Copy all text in destination of translation window.
`<C-o>`
Play sound on source of translation window.
Play text to speech on source of translation window.
`<C-p>`
Play sound on destination of translation window.
Play text to speech on destination of translation window.
`<C-x>`
Stop play sound.
@ -106,8 +118,6 @@ Switch pop out window.
[`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) for Linux/Wayland to copy text.
`pbcopy` For macOS to copy text.
## Credit
[soimort/translate-shell](https://github.com/soimort/translate-shell),

View File

@ -15,22 +15,24 @@ func configInit() {
defaultConfigPath string
themeConfig = config.New()
defaultConfig = map[string]interface{}{
"hide_below": false,
"transparent": false,
"theme": "Gruvbox",
"source.border_color": "red",
"destination.border_color": "blue",
"source.language.apertiumtranslate": "English",
"destination.language.apertiumtranslate": "English",
"source.language.argostranslate": "English",
"destination.language.argostranslate": "English",
"source.language.bingtranslate": "English",
"destination.language.bingtranslate": "English",
"source.language.googletranslate": "English",
"destination.language.googletranslate": "English",
"source.language.reversotranslate": "English",
"destination.language.reversotranslate": "English",
"translator": "ArgosTranslate",
"hide_below": false,
"transparent": false,
"theme": "gruvbox",
"source.border_color": "red",
"destination.border_color": "blue",
"source.language.apertium": "English",
"destination.language.apertium": "English",
"source.language.argos": "English",
"destination.language.argos": "English",
"source.language.bing": "English",
"destination.language.bing": "English",
"source.language.chatgpt": "English",
"destination.language.chatgpt": "English",
"source.language.google": "English",
"destination.language.google": "English",
"source.language.reverso": "English",
"destination.language.reverso": "English",
"translator": "Google",
}
)
@ -48,7 +50,7 @@ func configInit() {
config.AddConfigPath("$HOME/.config/gtt")
themeConfig.AddConfigPath("$HOME/.config/gtt")
// Create config file if not exists
// Create config file if it does not exist
// Otherwise check if config value is missing
if err := config.ReadInConfig(); err != nil {
for key, value := range defaultConfig {
@ -66,6 +68,16 @@ func configInit() {
missing = true
}
}
// Set to default theme if theme in config does not exist
if IndexOf(config.GetString("theme"), style.AllTheme) < 0 {
config.Set("theme", defaultConfig["theme"])
missing = true
}
// Set to default translator if translator in config does not exist
if IndexOf(config.GetString("translator"), translate.AllTranslator) < 0 {
config.Set("translator", defaultConfig["translator"])
missing = true
}
if missing {
config.WriteConfig()
}
@ -84,7 +96,7 @@ func configInit() {
}
}
// setup
// Setup
for _, name := range translate.AllTranslator {
translators[name] = translate.NewTranslator(name)
translators[name].SetSrcLang(
@ -98,7 +110,11 @@ func configInit() {
uiStyle.Transparent = config.GetBool("transparent")
uiStyle.SetSrcBorderColor(config.GetString("source.border_color")).
SetDstBorderColor(config.GetString("destination.border_color"))
// set argument language
// Set API Key
if config.Get("api_key.chatgpt") != nil {
translators["ChatGPT"].SetAPIKey(config.GetString("api_key.chatgpt"))
}
// Set argument language
if len(*srcLangArg) > 0 {
translator.SetSrcLang(*srcLangArg)
}

View File

@ -5,10 +5,10 @@ import (
)
var (
AllTheme = []string{"Gruvbox", "Nord"}
AllTheme = []string{"gruvbox", "nord"}
Palette = []string{"red", "green", "yellow", "blue", "purple", "cyan", "orange"}
themes = map[string]map[string]tcell.Color{
"Gruvbox": {
"gruvbox": {
"bg": tcell.NewHexColor(0x282828),
"fg": tcell.NewHexColor(0xebdbb2),
"gray": tcell.NewHexColor(0x665c54),
@ -20,7 +20,7 @@ var (
"cyan": tcell.NewHexColor(0x8ec07c),
"orange": tcell.NewHexColor(0xfe8019),
},
"Nord": {
"nord": {
"bg": tcell.NewHexColor(0x3b4252),
"fg": tcell.NewHexColor(0xeceff4),
"gray": tcell.NewHexColor(0x4c566a),

View File

@ -1,4 +1,4 @@
package apertiumtranslate
package apertium
var (
lang = []string{

View File

@ -0,0 +1,81 @@
package apertium
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/eeeXun/gtt/internal/translate/core"
)
const (
textURL = "https://www.apertium.org/apy/translate?langpair=%s|%s&q=%s"
)
type Translator struct {
*core.APIKey
*core.Language
*core.TTSLock
core.EngineName
}
func NewTranslator() *Translator {
return &Translator{
APIKey: new(core.APIKey),
Language: new(core.Language),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("Apertium"),
}
}
func (t *Translator) GetAllLang() []string {
return lang
}
func (t *Translator) Translate(message string) (translation *core.Translation, err error) {
translation = new(core.Translation)
var data map[string]interface{}
urlStr := fmt.Sprintf(
textURL,
langCode[t.GetSrcLang()],
langCode[t.GetDstLang()],
url.QueryEscape(message),
)
res, err := http.Get(urlStr)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(body, &data); err != nil {
return nil, err
}
if len(data) <= 0 {
return nil, errors.New("Translation not found")
}
// If responseData is nil, then suppose the translation pair is not available
if data["responseData"] == nil {
return nil, errors.New(fmt.Sprintf("%s does not support translate from %s to %s.",
t.GetEngineName(),
t.GetSrcLang(),
t.GetDstLang(),
))
}
translation.TEXT = data["responseData"].(map[string]interface{})["translatedText"].(string)
return translation, nil
}
func (t *Translator) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
return errors.New(t.GetEngineName() + " does not support text to speech")
}

View File

@ -1,82 +0,0 @@
package apertiumtranslate
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/eeeXun/gtt/internal/translate/core"
)
const (
textURL = "https://www.apertium.org/apy/translate?langpair=%s|%s&q=%s"
)
type ApertiumTranslate struct {
*core.Language
*core.TTSLock
core.EngineName
}
func NewApertiumTranslate() *ApertiumTranslate {
return &ApertiumTranslate{
Language: core.NewLanguage(),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("ApertiumTranslate"),
}
}
func (t *ApertiumTranslate) GetAllLang() []string {
return lang
}
func (t *ApertiumTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) {
var data map[string]interface{}
urlStr := fmt.Sprintf(
textURL,
langCode[t.GetSrcLang()],
langCode[t.GetDstLang()],
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 {
return "", "", "", errors.New("Translation not found")
}
switch res.StatusCode {
case 200:
translation = fmt.Sprintf("%v",
data["responseData"].(map[string]interface{})["translatedText"])
default:
return "", "", "", errors.New(
fmt.Sprintf("%s does not support translate from %s to %s.\nSee available pair on %s",
t.GetEngineName(),
t.GetSrcLang(),
t.GetDstLang(),
"https://www.apertium.org/",
))
}
return translation, definition, partOfSpeech, nil
}
func (t *ApertiumTranslate) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
return errors.New(t.GetEngineName() + " does not support text to speech")
}

View File

@ -1,4 +1,4 @@
package argostranslate
package argos
var (
lang = []string{

View File

@ -1,9 +1,8 @@
package argostranslate
package argos
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
@ -15,25 +14,28 @@ const (
textURL = "https://translate.argosopentech.com/translate"
)
type ArgosTranslate struct {
type Translator struct {
*core.APIKey
*core.Language
*core.TTSLock
core.EngineName
}
func NewArgosTranslate() *ArgosTranslate {
return &ArgosTranslate{
Language: core.NewLanguage(),
func NewTranslator() *Translator {
return &Translator{
APIKey: new(core.APIKey),
Language: new(core.Language),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("ArgosTranslate"),
EngineName: core.NewEngineName("Argos"),
}
}
func (t *ArgosTranslate) GetAllLang() []string {
func (t *Translator) GetAllLang() []string {
return lang
}
func (t *ArgosTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) {
func (t *Translator) Translate(message string) (translation *core.Translation, err error) {
translation = new(core.Translation)
var data map[string]interface{}
res, err := http.PostForm(textURL,
@ -43,26 +45,26 @@ func (t *ArgosTranslate) Translate(message string) (translation, definition, par
"target": {langCode[t.GetDstLang()]},
})
if err != nil {
return "", "", "", err
return nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", "", err
return nil, err
}
if err = json.Unmarshal(body, &data); err != nil {
return "", "", "", err
return nil, err
}
if len(data) <= 0 {
return "", "", "", errors.New("Translation not found")
return nil, errors.New("Translation not found")
}
translation = fmt.Sprintf("%v", data["translatedText"])
translation.TEXT = data["translatedText"].(string)
return translation, definition, partOfSpeech, nil
return translation, nil
}
func (t *ArgosTranslate) PlayTTS(lang, message string) error {
func (t *Translator) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
return errors.New(t.GetEngineName() + " does not support text to speech")

View File

@ -1,4 +1,4 @@
package bingtranslate
package bing
var (
lang = []string{

View File

@ -1,4 +1,4 @@
package bingtranslate
package bing
import (
"encoding/json"
@ -24,7 +24,8 @@ const (
ttsSSML = "<speak version='1.0' xml:lang='%[1]s'><voice xml:lang='%[1]s' xml:gender='Female' name='%s'><prosody rate='-20.00%%'>%s</prosody></voice></speak>"
)
type BingTranslate struct {
type Translator struct {
*core.APIKey
*core.Language
*core.TTSLock
core.EngineName
@ -37,20 +38,21 @@ type setUpData struct {
token string
}
func NewBingTranslate() *BingTranslate {
return &BingTranslate{
Language: core.NewLanguage(),
func NewTranslator() *Translator {
return &Translator{
APIKey: new(core.APIKey),
Language: new(core.Language),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("BingTranslate"),
EngineName: core.NewEngineName("Bing"),
}
}
func (t *BingTranslate) GetAllLang() []string {
func (t *Translator) GetAllLang() []string {
return lang
}
func (t *BingTranslate) setUp() (*setUpData, error) {
var data setUpData
func (t *Translator) setUp() (*setUpData, error) {
data := new(setUpData)
res, err := http.Get(setUpURL)
if err != nil {
@ -79,15 +81,16 @@ func (t *BingTranslate) setUp() (*setUpData, error) {
data.key = paramsStr[0]
data.token = paramsStr[1][1 : len(paramsStr[1])-1]
return &data, nil
return data, nil
}
func (t *BingTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) {
func (t *Translator) Translate(message string) (translation *core.Translation, err error) {
translation = new(core.Translation)
var data []interface{}
initData, err := t.setUp()
if err != nil {
return "", "", "", err
return nil, err
}
userData := url.Values{
"fromLang": {langCode[t.GetSrcLang()]},
@ -104,23 +107,23 @@ func (t *BingTranslate) Translate(message string) (translation, definition, part
req.Header.Add("User-Agent", core.UserAgent)
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", "", "", err
return nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", "", err
return nil, err
}
if err = json.Unmarshal(body, &data); err != nil {
return "", "", "", err
return nil, err
}
if len(data) <= 0 {
return "", "", "", errors.New("Translation not found")
return nil, errors.New("Translation not found")
}
// translation
translation = fmt.Sprintf("%v",
data[0].(map[string]interface{})["translations"].([]interface{})[0].(map[string]interface{})["text"])
translation.TEXT =
data[0].(map[string]interface{})["translations"].([]interface{})[0].(map[string]interface{})["text"].(string)
// request part of speech
userData.Del("fromLang")
@ -133,11 +136,11 @@ func (t *BingTranslate) Translate(message string) (translation, definition, part
req.Header.Add("User-Agent", core.UserAgent)
res, err = http.DefaultClient.Do(req)
if err != nil {
return "", "", "", err
return nil, err
}
body, err = ioutil.ReadAll(res.Body)
if err != nil {
return "", "", "", err
return nil, err
}
// Bing will return the request with list when success.
// Otherwises, it would return map. Then the following err would not be nil.
@ -154,13 +157,13 @@ func (t *BingTranslate) Translate(message string) (translation, definition, part
}
poses.add(pos["posTag"].(string), words)
}
partOfSpeech = poses.format()
translation.POS = poses.format()
}
return translation, definition, partOfSpeech, nil
return translation, nil
}
func (t *BingTranslate) PlayTTS(lang, message string) error {
func (t *Translator) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
name, ok := voiceName[lang]

View File

@ -1,4 +1,4 @@
package bingtranslate
package bing
import (
"fmt"

View File

@ -0,0 +1,142 @@
package chatgpt
var (
// Generated from Google
lang = []string{
"Afrikaans",
"Albanian",
"Amharic",
"Arabic",
"Armenian",
"Auto",
"Assamese",
"Aymara",
"Azerbaijani",
"Bambara",
"Basque",
"Belarusian",
"Bengali",
"Bhojpuri",
"Bosnian",
"Bulgarian",
"Catalan",
"Cebuano",
"Chinese (Simplified)",
"Chinese (Traditional)",
"Corsican",
"Croatian",
"Czech",
"Danish",
"Dhivehi",
"Dogri",
"Dutch",
"English",
"Esperanto",
"Estonian",
"Ewe",
"Filipino (Tagalog)",
"Finnish",
"French",
"Frisian",
"Galician",
"Georgian",
"German",
"Greek",
"Guarani",
"Gujarati",
"Haitian Creole",
"Hausa",
"Hawaiian",
"Hebrew",
"Hindi",
"Hmong",
"Hungarian",
"Icelandic",
"Igbo",
"Ilocano",
"Indonesian",
"Irish",
"Italian",
"Japanese",
"Javanese",
"Kannada",
"Kazakh",
"Khmer",
"Kinyarwanda",
"Konkani",
"Korean",
"Krio",
"Kurdish",
"Kurdish (Sorani)",
"Kyrgyz",
"Lao",
"Latin",
"Latvian",
"Lingala",
"Lithuanian",
"Luganda",
"Luxembourgish",
"Macedonian",
"Maithili",
"Malagasy",
"Malay",
"Malayalam",
"Maltese",
"Maori",
"Marathi",
"Meiteilon (Manipuri)",
"Mizo",
"Mongolian",
"Myanmar (Burmese)",
"Nepali",
"Norwegian",
"Nyanja (Chichewa)",
"Odia (Oriya)",
"Oromo",
"Pashto",
"Persian",
"Polish",
"Portuguese (Portugal, Brazil)",
"Punjabi",
"Quechua",
"Romanian",
"Russian",
"Samoan",
"Sanskrit",
"Scots Gaelic",
"Sepedi",
"Serbian",
"Sesotho",
"Shona",
"Sindhi",
"Sinhala (Sinhalese)",
"Slovak",
"Slovenian",
"Somali",
"Spanish",
"Sundanese",
"Swahili",
"Swedish",
"Tagalog (Filipino)",
"Tajik",
"Tamil",
"Tatar",
"Telugu",
"Thai",
"Tigrinya",
"Tsonga",
"Turkish",
"Turkmen",
"Twi (Akan)",
"Ukrainian",
"Urdu",
"Uyghur",
"Uzbek",
"Vietnamese",
"Welsh",
"Xhosa",
"Yiddish",
"Yoruba",
"Zulu",
}
)

View File

@ -0,0 +1,94 @@
package chatgpt
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"github.com/eeeXun/gtt/internal/translate/core"
)
const (
textURL = "https://api.openai.com/v1/chat/completions"
)
type Translator struct {
*core.APIKey
*core.Language
*core.TTSLock
core.EngineName
}
func NewTranslator() *Translator {
return &Translator{
APIKey: new(core.APIKey),
Language: new(core.Language),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("ChatGPT"),
}
}
func (t *Translator) GetAllLang() []string {
return lang
}
func (t *Translator) Translate(message string) (translation *core.Translation, err error) {
translation = new(core.Translation)
var data map[string]interface{}
if len(t.GetAPIKey()) <= 0 {
return nil, errors.New("Please write your API Key in config file for " + t.GetEngineName())
}
userData, _ := json.Marshal(map[string]interface{}{
"model": "gpt-3.5-turbo",
"messages": []map[string]string{{
"role": "user",
"content": fmt.Sprintf(
"Translate following text from %s to %s\n%s",
t.GetSrcLang(),
t.GetDstLang(),
message,
),
}},
"temperature": 0.7,
})
req, _ := http.NewRequest("POST",
textURL,
bytes.NewBuffer(userData),
)
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer "+t.GetAPIKey())
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(body, &data); err != nil {
return nil, err
}
if len(data) <= 0 {
return nil, errors.New("Translation not found")
}
if data["error"] != nil {
return nil, errors.New(data["error"].(map[string]interface{})["message"].(string))
}
translation.TEXT =
data["choices"].([]interface{})[0].(map[string]interface{})["message"].(map[string]interface{})["content"].(string)
return translation, nil
}
func (t *Translator) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
return errors.New(t.GetEngineName() + " does not support text to speech")
}

View File

@ -0,0 +1,13 @@
package core
type APIKey struct {
key string
}
func (k *APIKey) SetAPIKey(key string) {
k.key = key
}
func (k *APIKey) GetAPIKey() string {
return k.key
}

View File

@ -5,10 +5,6 @@ type Language struct {
dstLang string
}
func NewLanguage() *Language {
return &Language{}
}
func (l *Language) GetSrcLang() string {
return l.srcLang
}

View File

@ -3,3 +3,14 @@ package core
const (
UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
)
type Translation struct {
// translation text
TEXT string
// translation definition or example
DEF string
// translation part of speech
POS string
}

View File

@ -1,4 +1,4 @@
package googletranslate
package google
// https://cloud.google.com/translate/docs/languages
var (
@ -9,10 +9,14 @@ var (
"Arabic",
"Armenian",
"Auto",
"Assamese",
"Aymara",
"Azerbaijani",
"Bambara",
"Basque",
"Belarusian",
"Bengali",
"Bhojpuri",
"Bosnian",
"Bulgarian",
"Catalan",
@ -23,10 +27,14 @@ var (
"Croatian",
"Czech",
"Danish",
"Dhivehi",
"Dogri",
"Dutch",
"English",
"Esperanto",
"Estonian",
"Ewe",
"Filipino (Tagalog)",
"Finnish",
"French",
"Frisian",
@ -34,6 +42,7 @@ var (
"Georgian",
"German",
"Greek",
"Guarani",
"Gujarati",
"Haitian Creole",
"Hausa",
@ -44,6 +53,7 @@ var (
"Hungarian",
"Icelandic",
"Igbo",
"Ilocano",
"Indonesian",
"Irish",
"Italian",
@ -53,36 +63,48 @@ var (
"Kazakh",
"Khmer",
"Kinyarwanda",
"Konkani",
"Korean",
"Krio",
"Kurdish",
"Kurdish (Sorani)",
"Kyrgyz",
"Lao",
"Latin",
"Latvian",
"Lingala",
"Lithuanian",
"Luganda",
"Luxembourgish",
"Macedonian",
"Maithili",
"Malagasy",
"Malay",
"Malayalam",
"Maltese",
"Maori",
"Marathi",
"Meiteilon (Manipuri)",
"Mizo",
"Mongolian",
"Myanmar (Burmese)",
"Nepali",
"Norwegian",
"Nyanja (Chichewa)",
"Odia (Oriya)",
"Oromo",
"Pashto",
"Persian",
"Polish",
"Portuguese (Portugal, Brazil)",
"Punjabi",
"Quechua",
"Romanian",
"Russian",
"Samoan",
"Sanskrit",
"Scots Gaelic",
"Sepedi",
"Serbian",
"Sesotho",
"Shona",
@ -101,8 +123,11 @@ var (
"Tatar",
"Telugu",
"Thai",
"Tigrinya",
"Tsonga",
"Turkish",
"Turkmen",
"Twi (Akan)",
"Ukrainian",
"Urdu",
"Uyghur",
@ -121,10 +146,14 @@ var (
"Arabic": "ar",
"Armenian": "hy",
"Auto": "auto",
"Assamese": "as",
"Aymara": "ay",
"Azerbaijani": "az",
"Bambara": "bm",
"Basque": "eu",
"Belarusian": "be",
"Bengali": "bn",
"Bhojpuri": "bho",
"Bosnian": "bs",
"Bulgarian": "bg",
"Catalan": "ca",
@ -135,10 +164,14 @@ var (
"Croatian": "hr",
"Czech": "cs",
"Danish": "da",
"Dhivehi": "dv",
"Dogri": "doi",
"Dutch": "nl",
"English": "en",
"Esperanto": "eo",
"Estonian": "et",
"Ewe": "ee",
"Filipino (Tagalog)": "fil",
"Finnish": "fi",
"French": "fr",
"Frisian": "fy",
@ -146,6 +179,7 @@ var (
"Georgian": "ka",
"German": "de",
"Greek": "el",
"Guarani": "gn",
"Gujarati": "gu",
"Haitian Creole": "ht",
"Hausa": "ha",
@ -156,6 +190,7 @@ var (
"Hungarian": "hu",
"Icelandic": "is",
"Igbo": "ig",
"Ilocano": "ilo",
"Indonesian": "id",
"Irish": "ga",
"Italian": "it",
@ -165,36 +200,48 @@ var (
"Kazakh": "kk",
"Khmer": "km",
"Kinyarwanda": "rw",
"Konkani": "gom",
"Korean": "ko",
"Krio": "kri",
"Kurdish": "ku",
"Kurdish (Sorani)": "ckb",
"Kyrgyz": "ky",
"Lao": "lo",
"Latin": "la",
"Latvian": "lv",
"Lingala": "ln",
"Lithuanian": "lt",
"Luganda": "lg",
"Luxembourgish": "lb",
"Macedonian": "mk",
"Maithili": "mai",
"Malagasy": "mg",
"Malay": "ms",
"Malayalam": "ml",
"Maltese": "mt",
"Maori": "mi",
"Marathi": "mr",
"Meiteilon (Manipuri)": "mni-Mtei",
"Mizo": "lus",
"Mongolian": "mn",
"Myanmar (Burmese)": "my",
"Nepali": "ne",
"Norwegian": "no",
"Nyanja (Chichewa)": "ny",
"Odia (Oriya)": "or",
"Oromo": "om",
"Pashto": "ps",
"Persian": "fa",
"Polish": "pl",
"Portuguese (Portugal, Brazil)": "pt",
"Punjabi": "pa",
"Quechua": "qu",
"Romanian": "ro",
"Russian": "ru",
"Samoan": "sm",
"Sanskrit": "sa",
"Scots Gaelic": "gd",
"Sepedi": "nso",
"Serbian": "sr",
"Sesotho": "st",
"Shona": "sn",
@ -213,8 +260,11 @@ var (
"Tatar": "tt",
"Telugu": "te",
"Thai": "th",
"Tigrinya": "ti",
"Tsonga": "ts",
"Turkish": "tr",
"Turkmen": "tk",
"Twi (Akan)": "ak",
"Ukrainian": "uk",
"Urdu": "ur",
"Uyghur": "ug",

View File

@ -1,4 +1,4 @@
package googletranslate
package google
import (
"encoding/json"
@ -19,25 +19,28 @@ const (
ttsURL = "https://translate.google.com.vn/translate_tts?ie=UTF-8&q=%s&tl=%s&client=tw-ob"
)
type GoogleTranslate struct {
type Translator struct {
*core.APIKey
*core.Language
*core.TTSLock
core.EngineName
}
func NewGoogleTranslate() *GoogleTranslate {
return &GoogleTranslate{
Language: core.NewLanguage(),
func NewTranslator() *Translator {
return &Translator{
APIKey: new(core.APIKey),
Language: new(core.Language),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("GoogleTranslate"),
EngineName: core.NewEngineName("Google"),
}
}
func (t *GoogleTranslate) GetAllLang() []string {
func (t *Translator) GetAllLang() []string {
return lang
}
func (t *GoogleTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) {
func (t *Translator) Translate(message string) (translation *core.Translation, err error) {
translation = new(core.Translation)
var data []interface{}
urlStr := fmt.Sprintf(
@ -48,24 +51,24 @@ func (t *GoogleTranslate) Translate(message string) (translation, definition, pa
)
res, err := http.Get(urlStr)
if err != nil {
return "", "", "", err
return nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", "", err
return nil, err
}
if err = json.Unmarshal(body, &data); err != nil {
return "", "", "", err
return nil, err
}
if len(data) <= 0 {
return "", "", "", errors.New("Translation not found")
return nil, errors.New("Translation not found")
}
// translation = data[0]
for _, line := range data[0].([]interface{}) {
translatedLine := line.([]interface{})[0]
translation += fmt.Sprintf("%v", translatedLine)
translation.TEXT += translatedLine.(string)
}
// part of speech = data[1]
if data[1] != nil {
@ -73,23 +76,23 @@ func (t *GoogleTranslate) Translate(message string) (translation, definition, pa
partOfSpeeches := partOfSpeeches.([]interface{})
// part of speech
pos := partOfSpeeches[0]
partOfSpeech += fmt.Sprintf("[%v]\n", pos)
translation.POS += fmt.Sprintf("[%v]\n", pos)
for _, words := range partOfSpeeches[2].([]interface{}) {
words := words.([]interface{})
// dst lang
dstWord := words[0]
partOfSpeech += fmt.Sprintf("\t%v:", dstWord)
translation.POS += fmt.Sprintf("\t%v:", dstWord)
// src lang
firstWord := true
for _, word := range words[1].([]interface{}) {
if firstWord {
partOfSpeech += fmt.Sprintf(" %v", word)
translation.POS += fmt.Sprintf(" %v", word)
firstWord = false
} else {
partOfSpeech += fmt.Sprintf(", %v", word)
translation.POS += fmt.Sprintf(", %v", word)
}
}
partOfSpeech += "\n"
translation.POS += "\n"
}
}
}
@ -99,25 +102,25 @@ func (t *GoogleTranslate) Translate(message string) (translation, definition, pa
definitions := definitions.([]interface{})
// part of speech
pos := definitions[0]
definition += fmt.Sprintf("[%v]\n", pos)
translation.DEF += fmt.Sprintf("[%v]\n", pos)
for _, sentences := range definitions[1].([]interface{}) {
sentences := sentences.([]interface{})
// definition
def := sentences[0]
definition += fmt.Sprintf("\t- %v\n", def)
translation.DEF += fmt.Sprintf("\t- %v\n", def)
// example sentence
if len(sentences) >= 3 && sentences[2] != nil {
example := sentences[2]
definition += fmt.Sprintf("\t\t\"%v\"\n", example)
translation.DEF += fmt.Sprintf("\t\t\"%v\"\n", example)
}
}
}
}
return translation, definition, partOfSpeech, nil
return translation, nil
}
func (t *GoogleTranslate) PlayTTS(lang, message string) error {
func (t *Translator) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
urlStr := fmt.Sprintf(
@ -129,6 +132,9 @@ func (t *GoogleTranslate) PlayTTS(lang, message string) error {
if err != nil {
return err
}
if res.StatusCode == 400 {
return errors.New(t.GetEngineName() + " does not support text to speech of " + lang)
}
decoder, err := mp3.NewDecoder(res.Body)
if err != nil {
return err

View File

@ -1,4 +1,4 @@
package reversotranslate
package reverso
var (
lang = []string{

View File

@ -1,4 +1,4 @@
package reversotranslate
package reverso
import (
"bytes"
@ -21,29 +21,32 @@ const (
ttsURL = "https://voice.reverso.net/RestPronunciation.svc/v1/output=json/GetVoiceStream/voiceName=%s?voiceSpeed=80&inputText=%s"
)
type ReversoTranslate struct {
type Translator struct {
*core.APIKey
*core.Language
*core.TTSLock
core.EngineName
}
func NewReversoTranslate() *ReversoTranslate {
return &ReversoTranslate{
Language: core.NewLanguage(),
func NewTranslator() *Translator {
return &Translator{
APIKey: new(core.APIKey),
Language: new(core.Language),
TTSLock: core.NewTTSLock(),
EngineName: core.NewEngineName("ReversoTranslate"),
EngineName: core.NewEngineName("Reverso"),
}
}
func (t *ReversoTranslate) GetAllLang() []string {
func (t *Translator) GetAllLang() []string {
return lang
}
func (t *ReversoTranslate) Translate(message string) (translation, definition, partOfSpeech string, err error) {
func (t *Translator) Translate(message string) (translation *core.Translation, err error) {
translation = new(core.Translation)
var data map[string]interface{}
if t.GetSrcLang() == t.GetDstLang() {
return "", "", "", errors.New(
return nil, errors.New(
fmt.Sprintf("%s doesn't support translation of the same language.\ni.e. %s to %s",
t.GetEngineName(), t.GetSrcLang(), t.GetDstLang()))
}
@ -62,28 +65,28 @@ func (t *ReversoTranslate) Translate(message string) (translation, definition, p
})
req, _ := http.NewRequest("POST",
textURL,
bytes.NewBuffer([]byte(userData)))
bytes.NewBuffer(userData))
req.Header.Add("Content-Type", "application/json")
req.Header.Add("User-Agent", core.UserAgent)
res, err := http.DefaultClient.Do(req)
if err != nil {
return "", "", "", err
return nil, err
}
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", "", "", err
return nil, err
}
if err = json.Unmarshal(body, &data); err != nil {
return "", "", "", err
return nil, err
}
if len(data) <= 0 {
return "", "", "", errors.New("Translation not found")
return nil, errors.New("Translation not found")
}
// translation
for _, line := range data["translation"].([]interface{}) {
translation += fmt.Sprintf("%v", line)
translation.TEXT += line.(string)
}
// definition and part of speech
if data["contextResults"] != nil {
@ -94,23 +97,23 @@ func (t *ReversoTranslate) Translate(message string) (translation, definition, p
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])
translation.DEF += 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"])
translation.POS += fmt.Sprintf("%v\n", results["translation"])
} else {
partOfSpeech += fmt.Sprintf("%v [%v]\n", results["translation"], results["partOfSpeech"])
translation.POS += fmt.Sprintf("%v [%v]\n", results["translation"], results["partOfSpeech"])
}
}
definition = regexp.MustCompile("<(|/)em>").ReplaceAllString(definition, "")
translation.DEF = regexp.MustCompile("<(|/)em>").ReplaceAllString(translation.DEF, "")
}
return translation, definition, partOfSpeech, nil
return translation, nil
}
func (t *ReversoTranslate) PlayTTS(lang, message string) error {
func (t *Translator) PlayTTS(lang, message string) error {
defer t.ReleaseLock()
name, ok := voiceName[lang]

View File

@ -1,20 +1,23 @@
package translate
import (
"github.com/eeeXun/gtt/internal/translate/apertiumtranslate"
"github.com/eeeXun/gtt/internal/translate/argostranslate"
"github.com/eeeXun/gtt/internal/translate/bingtranslate"
"github.com/eeeXun/gtt/internal/translate/googletranslate"
"github.com/eeeXun/gtt/internal/translate/reversotranslate"
"github.com/eeeXun/gtt/internal/translate/apertium"
"github.com/eeeXun/gtt/internal/translate/argos"
"github.com/eeeXun/gtt/internal/translate/bing"
"github.com/eeeXun/gtt/internal/translate/chatgpt"
"github.com/eeeXun/gtt/internal/translate/core"
"github.com/eeeXun/gtt/internal/translate/google"
"github.com/eeeXun/gtt/internal/translate/reverso"
)
var (
AllTranslator = []string{
"ApertiumTranslate",
"BingTranslate",
"ArgosTranslate",
"GoogleTranslate",
"ReversoTranslate",
"Apertium",
"Argos",
"Bing",
"ChatGPT",
"Google",
"Reverso",
}
)
@ -40,6 +43,9 @@ type Translator interface {
// Swap source and destination language of the translator
SwapLang()
// Set API Key
SetAPIKey(key string)
// Check if lock is available
LockAvailable() bool
@ -50,7 +56,7 @@ type Translator interface {
StopTTS()
// Translate from source to destination language
Translate(message string) (translation, definition, partOfSpeech string, err error)
Translate(message string) (translation *core.Translation, err error)
// Play text to speech
PlayTTS(lang, message string) error
@ -60,16 +66,18 @@ func NewTranslator(name string) Translator {
var translator Translator
switch name {
case "ApertiumTranslate":
translator = apertiumtranslate.NewApertiumTranslate()
case "ArgosTranslate":
translator = argostranslate.NewArgosTranslate()
case "BingTranslate":
translator = bingtranslate.NewBingTranslate()
case "GoogleTranslate":
translator = googletranslate.NewGoogleTranslate()
case "ReversoTranslate":
translator = reversotranslate.NewReversoTranslate()
case "Apertium":
translator = apertium.NewTranslator()
case "Argos":
translator = argos.NewTranslator()
case "Bing":
translator = bing.NewTranslator()
case "ChatGPT":
translator = chatgpt.NewTranslator()
case "Google":
translator = google.NewTranslator()
case "Reverso":
translator = reverso.NewTranslator()
}
return translator

74
ui.go
View File

@ -30,9 +30,9 @@ const (
[#%[1]s]<C-r>[-]
Copy all text in destination of translation window.
[#%[1]s]<C-o>[-]
Play sound on source of translation window.
Play text to speech on source of translation window.
[#%[1]s]<C-p>[-]
Play sound on destination of translation window.
Play text to speech on destination of translation window.
[#%[1]s]<C-x>[-]
Stop play sound.
[#%[1]s]<C-t>[-]
@ -233,6 +233,26 @@ func attachItems(center bool, direction int, items ...Item) *tview.Flex {
return container
}
func showLangPopout() {
mainPage.HidePage("stylePopOut")
mainPage.HidePage("keyMapPopOut")
mainPage.ShowPage("langPopOut")
app.SetFocus(langCycle.GetCurrentUI())
}
func showStylePopout() {
mainPage.HidePage("langPopOut")
mainPage.HidePage("keyMapPopOut")
mainPage.ShowPage("stylePopOut")
app.SetFocus(styleCycle.GetCurrentUI())
}
func showKeyMapPopout() {
mainPage.HidePage("langPopOut")
mainPage.HidePage("stylePopOut")
mainPage.ShowPage("keyMapPopOut")
}
func uiInit() {
// input/output
srcInput.SetBorder(true)
@ -296,7 +316,7 @@ func uiInit() {
Item{item: attachItems(true, tview.FlexColumn,
Item{item: attachItems(false, tview.FlexRow,
Item{item: translatorDropDown, fixedSize: 0, proportion: 1, focus: false}),
fixedSize: 0, proportion: 2, focus: false}),
fixedSize: 0, proportion: 1, focus: false}),
fixedSize: 1, proportion: 1, focus: false},
Item{item: attachItems(false, tview.FlexColumn,
Item{item: srcLangDropDown, fixedSize: 0, proportion: 1, focus: true},
@ -402,23 +422,9 @@ func uiInit() {
mainPage.HidePage("keyMapPopOut")
}
})
langButton.SetSelectedFunc(func() {
mainPage.HidePage("stylePopOut")
mainPage.HidePage("keyMapPopOut")
mainPage.ShowPage("langPopOut")
app.SetFocus(langCycle.GetCurrentUI())
})
styleButton.SetSelectedFunc(func() {
mainPage.HidePage("langPopOut")
mainPage.HidePage("keyMapPopOut")
mainPage.ShowPage("stylePopOut")
app.SetFocus(styleCycle.GetCurrentUI())
})
keyMapButton.SetSelectedFunc(func() {
mainPage.HidePage("langPopOut")
mainPage.HidePage("stylePopOut")
mainPage.ShowPage("keyMapPopOut")
})
langButton.SetSelectedFunc(showLangPopout)
styleButton.SetSelectedFunc(showStylePopout)
keyMapButton.SetSelectedFunc(showKeyMapPopout)
}
func mainPageHandler(event *tcell.EventKey) *tcell.EventKey {
@ -454,13 +460,13 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
message := srcInput.GetText()
// Only translate when message exist
if len(message) > 0 {
translation, definition, partOfSpeech, err := translator.Translate(message)
translation, err := translator.Translate(message)
if err != nil {
dstOutput.SetText(err.Error())
} else {
dstOutput.SetText(translation)
defOutput.SetText(definition, false)
posOutput.SetText(partOfSpeech, false)
dstOutput.SetText(translation.TEXT)
defOutput.SetText(translation.DEF, false)
posOutput.SetText(translation.POS, false)
}
}
case tcell.KeyCtrlQ:
@ -494,7 +500,7 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
}
dstOutput.SetText(srcText)
case tcell.KeyCtrlO:
// Play source sound
// Play text to speech on source of translation window.
if translator.LockAvailable() {
message := srcInput.GetText()
// Only play when message exist
@ -504,13 +510,14 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
err := translator.PlayTTS(translator.GetSrcLang(), message)
if err != nil {
srcInput.SetText(err.Error(), true)
app.Draw()
}
}()
}
}
case tcell.KeyCtrlP:
// Play destination sound
// Play text to speech on destination of translation window.
if translator.LockAvailable() {
message := dstOutput.GetText(false)
// Only play when message exist
@ -520,6 +527,7 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey {
err := translator.PlayTTS(translator.GetDstLang(), message)
if err != nil {
dstOutput.SetText(err.Error())
app.Draw()
}
}()
}
@ -537,19 +545,11 @@ func popOutHandler(event *tcell.EventKey) *tcell.EventKey {
switch ch {
case '1':
mainPage.HidePage("stylePopOut")
mainPage.HidePage("keyMapPopOut")
mainPage.ShowPage("langPopOut")
app.SetFocus(langCycle.GetCurrentUI())
showLangPopout()
case '2':
mainPage.HidePage("langPopOut")
mainPage.HidePage("keyMapPopOut")
mainPage.ShowPage("stylePopOut")
app.SetFocus(styleCycle.GetCurrentUI())
showStylePopout()
case '3':
mainPage.HidePage("langPopOut")
mainPage.HidePage("stylePopOut")
mainPage.ShowPage("keyMapPopOut")
showKeyMapPopout()
}
return event