diff --git a/README.md b/README.md index ac8b0b7..58abc37 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Google Translate TUI ## ScreenShot -![screenshot](https://i.imgur.com/ECtL7ac.gif) +![screenshot](https://imgur.com/b935CfI.gif) ## Install @@ -31,28 +31,28 @@ Exit program. Toggle pop out window. `` -Translate from left window to right window. +Translate from source to destination window. `` Swap language. `` -Clear all text in left window. +Clear all text in source of translation window. `` -Copy selected text in left window. +Copy selected text. `` -Copy all text in left window. +Copy all text in source of translation window. `` -Copy all text in right window. +Copy all text in destination of translation window. `` -Play sound on left window. +Play sound on source of translation window. `` -Play sound on right window. +Play sound on destination of translation window. `` Stop play sound. @@ -60,6 +60,9 @@ Stop play sound. `` Toggle transparent. +`` +Toggle Definition & Part of speech + ``, `` Cycle through the pop out widget. @@ -74,6 +77,8 @@ Switch pop out window. ## Credit +[soimort/translate-shell](https://github.com/soimort/translate-shell) For translation URL. + [snsd0805/GoogleTranslate-TUI](https://github.com/snsd0805/GoogleTranslate-TUI) For inspiration. [turk/free-google-translate](https://github.com/turk/free-google-translate) For Google translate in Golang. diff --git a/config.go b/config.go index 5a0bfcb..cf3fd35 100644 --- a/config.go +++ b/config.go @@ -2,12 +2,20 @@ package main import ( "flag" + "gtt/internal/color" "os" + + "github.com/spf13/viper" ) var ( + // argument srcLangArg *string = flag.String("src", "", "Source Language") dstLangArg *string = flag.String("dst", "", "Destination Language") + // settings + config = viper.New() + style = color.NewStyle() + hideBelow bool ) // Search XDG_CONFIG_HOME or $HOME/.config @@ -32,6 +40,7 @@ func configInit() { config.Set("source.borderColor", "red") config.Set("destination.language", "Chinese (Traditional)") config.Set("destination.borderColor", "blue") + config.Set("hide_below", false) if _, err = os.Stat(defaultConfigPath); os.IsNotExist(err) { os.MkdirAll(defaultConfigPath, os.ModePerm) } @@ -50,6 +59,7 @@ func configInit() { } else { translator.DstLang = config.GetString("destination.language") } + hideBelow = config.GetBool("hide_below") style.Theme = config.GetString("theme") style.Transparent = config.GetBool("transparent") style.SetSrcBorderColor(config.GetString("source.borderColor")). @@ -72,6 +82,10 @@ func updateConfig() { changed = true config.Set("destination.language", translator.DstLang) } + if config.GetBool("hide_below") != hideBelow { + changed = true + config.Set("hide_below", hideBelow) + } if config.GetString("theme") != style.Theme { changed = true config.Set("theme", style.Theme) diff --git a/internal/translate/translator.go b/internal/translate/translator.go index 7494875..b13a6d3 100644 --- a/internal/translate/translator.go +++ b/internal/translate/translator.go @@ -14,7 +14,7 @@ import ( ) const ( - textURL = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=%s&tl=%s&dt=t&q=%s" + 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" ) @@ -30,7 +30,11 @@ func NewTranslator() *Translator { } } -func (t *Translator) Translate(message string) (translation string, err error) { +func (t *Translator) Translate(message string) ( + translation string, + definition string, + partOfSpeech string, + err error) { var data []interface{} urlStr := fmt.Sprintf( @@ -41,26 +45,71 @@ func (t *Translator) Translate(message string) (translation string, err error) { ) res, err := http.Get(urlStr) if err != nil { - return "", err + return "", "", "", err } body, err := ioutil.ReadAll(res.Body) if err != nil { - return "", err + return "", "", "", err } if err = json.Unmarshal(body, &data); err != nil { - return "", err + return "", "", "", err } if len(data) > 0 { + // translation = data[0] for _, lines := range data[0].([]interface{}) { translatedLine := lines.([]interface{})[0] translation += fmt.Sprintf("%v", translatedLine) } - return translation, nil + + // 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") + return "", "", "", errors.New("Translation not found") } func (t *Translator) PlaySound(lang string, message string) error { diff --git a/main.go b/main.go index a6fe8b2..fdd6857 100644 --- a/main.go +++ b/main.go @@ -1,12 +1,10 @@ package main import ( - "gtt/internal/color" "gtt/internal/translate" "gtt/internal/ui" "github.com/rivo/tview" - "github.com/spf13/viper" ) var ( @@ -16,26 +14,33 @@ var ( app = tview.NewApplication() srcInput = tview.NewTextArea() dstOutput = tview.NewTextView() + defOutput = tview.NewTextArea() + posOutput = tview.NewTextArea() srcLangDropDown = tview.NewDropDown() dstLangDropDown = tview.NewDropDown() langCycle = ui.NewUICycle(srcLangDropDown, dstLangDropDown) themeDropDown = tview.NewDropDown() transparentDropDown = tview.NewDropDown() + hideBelowDropDown = tview.NewDropDown() srcBorderDropDown = tview.NewDropDown() dstBorderDropDown = tview.NewDropDown() - styleCycle = ui.NewUICycle(themeDropDown, transparentDropDown, srcBorderDropDown, dstBorderDropDown) - keyMapMenu = tview.NewTextView() - langButton = tview.NewButton("(1)Language") - styleButton = tview.NewButton("(2)Style") - keyMapButton = tview.NewButton("(3)KeyMap") - translateWindow = tview.NewFlex() - langWindow = tview.NewFlex() - styleWindow = tview.NewFlex() - keyMapWindow = tview.NewFlex() - mainPage = tview.NewPages() - // settings - config = viper.New() - style = color.NewStyle() + styleCycle = ui.NewUICycle( + themeDropDown, + transparentDropDown, + hideBelowDropDown, + srcBorderDropDown, + dstBorderDropDown) + keyMapMenu = tview.NewTextView() + langButton = tview.NewButton("(1)Language") + styleButton = tview.NewButton("(2)Style") + keyMapButton = tview.NewButton("(3)KeyMap") + translateWindow = tview.NewFlex() + translateAboveWidget = tview.NewFlex() + translateBelowWidget = tview.NewFlex() + langWindow = tview.NewFlex() + styleWindow = tview.NewFlex() + keyMapWindow = tview.NewFlex() + mainPage = tview.NewPages() ) func main() { diff --git a/ui.go b/ui.go index 0febe4b..a06ed6f 100644 --- a/ui.go +++ b/ui.go @@ -18,31 +18,44 @@ const ( [#%[1]s][-] Toggle pop out window. [#%[1]s][-] - Translate from left window to right window. + Translate from source to destination window. [#%[1]s][-] Swap language. [#%[1]s][-] - Clear all text in left window. + Clear all text in source of translation window. [#%[1]s][-] - Copy selected text in left window. + Copy selected text. [#%[1]s][-] - Copy all text in left window. + Copy all text in source of translation window. [#%[1]s][-] - Copy all text in right window. + Copy all text in destination of translation window. [#%[1]s][-] - Play sound on left window. + Play sound on source of translation window. [#%[1]s][-] - Play sound on right window. + Play sound on destination of translation window. [#%[1]s][-] Stop play sound. [#%[1]s][-] Toggle transparent. +[#%[1]s][-] + Toggle Definition & Part of speech [#%[1]s], [-] Cycle through the pop out widget. [#%[1]s]<1>, <2>, <3>[-] Switch pop out window.` ) +func updateTranslateWindow() { + translateWindow.Clear() + if hideBelow { + translateWindow.AddItem(translateAboveWidget, 0, 1, true) + } else { + translateWindow.SetDirection(tview.FlexRow). + AddItem(translateAboveWidget, 0, 1, true). + AddItem(translateBelowWidget, 0, 1, false) + } +} + func updateBackgroundColor() { // input/output srcInput.SetTextStyle(tcell.StyleDefault. @@ -50,6 +63,14 @@ func updateBackgroundColor() { Foreground(style.ForegroundColor())). SetBackgroundColor(style.BackgroundColor()) dstOutput.SetBackgroundColor(style.BackgroundColor()) + defOutput.SetTextStyle(tcell.StyleDefault. + Background(style.BackgroundColor()). + Foreground(style.ForegroundColor())). + SetBackgroundColor(style.BackgroundColor()) + posOutput.SetTextStyle(tcell.StyleDefault. + Background(style.BackgroundColor()). + Foreground(style.ForegroundColor())). + SetBackgroundColor(style.BackgroundColor()) // dropdown for _, dropdown := range []*tview.DropDown{ @@ -57,6 +78,7 @@ func updateBackgroundColor() { dstLangDropDown, themeDropDown, transparentDropDown, + hideBelowDropDown, srcBorderDropDown, dstBorderDropDown} { dropdown.SetListStyles(tcell.StyleDefault. @@ -78,6 +100,10 @@ func updateBorderColor() { SetTitleColor(style.SrcBorderColor()) dstOutput.SetBorderColor(style.DstBorderColor()). SetTitleColor(style.DstBorderColor()) + defOutput.SetBorderColor(style.SrcBorderColor()). + SetTitleColor(style.SrcBorderColor()) + posOutput.SetBorderColor(style.DstBorderColor()). + SetTitleColor(style.DstBorderColor()) // dropdown for _, srcDropDown := range []*tview.DropDown{srcLangDropDown, srcBorderDropDown} { @@ -96,6 +122,12 @@ func updateNonConfigColor() { Background(style.SelectedColor()). Foreground(style.ForegroundColor())) dstOutput.SetTextColor(style.ForegroundColor()) + defOutput.SetSelectedStyle(tcell.StyleDefault. + Background(style.SelectedColor()). + Foreground(style.ForegroundColor())) + posOutput.SetSelectedStyle(tcell.StyleDefault. + Background(style.SelectedColor()). + Foreground(style.ForegroundColor())) // dropdown for _, noLabelDropDown := range []*tview.DropDown{srcLangDropDown, dstLangDropDown} { @@ -106,6 +138,7 @@ func updateNonConfigColor() { for _, labelDropDown := range []*tview.DropDown{ themeDropDown, transparentDropDown, + hideBelowDropDown, srcBorderDropDown, dstBorderDropDown} { labelDropDown.SetLabelColor(style.LabelColor()). @@ -166,6 +199,8 @@ func uiInit() { // input/output srcInput.SetBorder(true) dstOutput.SetBorder(true) + defOutput.SetBorder(true).SetTitle("Definition") + posOutput.SetBorder(true).SetTitle("Part of speech") // dropdown for _, langDropDown := range []*tview.DropDown{srcLangDropDown, dstLangDropDown} { @@ -175,6 +210,11 @@ func uiInit() { themeDropDown.SetLabel("Theme: "). SetOptions(color.AllTheme, nil). SetCurrentOption(IndexOf(style.Theme, color.AllTheme)) + hideBelowDropDown.SetLabel("Hide below: "). + SetOptions([]string{"true", "false"}, nil). + SetCurrentOption( + IndexOf(strconv.FormatBool(hideBelow), + []string{"true", "false"})) transparentDropDown.SetLabel("Transparent: "). SetOptions([]string{"true", "false"}, nil). SetCurrentOption( @@ -203,9 +243,13 @@ func uiInit() { SetTitle("Key Map") // window - translateWindow.SetDirection(tview.FlexColumn). + translateAboveWidget.SetDirection(tview.FlexColumn). AddItem(srcInput, 0, 1, true). AddItem(dstOutput, 0, 1, false) + translateBelowWidget.SetDirection(tview.FlexColumn). + AddItem(defOutput, 0, 1, false). + AddItem(posOutput, 0, 1, false) + updateTranslateWindow() langWindow.SetDirection(tview.FlexRow). AddItem(nil, 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). @@ -225,10 +269,11 @@ func uiInit() { AddItem(nil, 0, 1, false). AddItem(tview.NewFlex().SetDirection(tview.FlexRow). AddItem(themeDropDown, 0, 1, true). - AddItem(transparentDropDown, 0, 1, false), + AddItem(transparentDropDown, 0, 1, false). + AddItem(hideBelowDropDown, 0, 1, false), 0, 1, true). AddItem(nil, 0, 1, false), - 2, 1, true). + 3, 1, true). AddItem(tview.NewFlex().SetDirection(tview.FlexColumn). AddItem(srcBorderDropDown, 0, 1, false). AddItem(dstBorderDropDown, 0, 1, false), @@ -254,6 +299,25 @@ func uiInit() { // handler mainPage.SetInputCapture(mainPageHandler) translateWindow.SetInputCapture(translateWindowHandler) + for _, widget := range []*tview.TextArea{srcInput, defOutput, posOutput} { + // fix for loop problem + // https://github.com/golang/go/discussions/56010 + widget := widget + widget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + key := event.Key() + switch key { + case tcell.KeyCtrlY: + // copy selected text + text, _, _ := widget.GetSelection() + + // only copy when text selected + if len(text) > 0 { + CopyToClipboard(text) + } + } + return event + }) + } langWindow.SetInputCapture(popOutWindowHandler) styleWindow.SetInputCapture(popOutWindowHandler) keyMapWindow.SetInputCapture(popOutWindowHandler) @@ -279,6 +343,11 @@ func uiInit() { style.Transparent, _ = strconv.ParseBool(text) updateBackgroundColor() }) + hideBelowDropDown.SetDoneFunc(styleDropDownHandler). + SetSelectedFunc(func(text string, index int) { + hideBelow, _ = strconv.ParseBool(text) + updateTranslateWindow() + }) srcBorderDropDown.SetDoneFunc(styleDropDownHandler). SetSelectedFunc(func(text string, index int) { style.SetSrcBorderColor(text) @@ -325,6 +394,12 @@ func mainPageHandler(event *tcell.EventKey) *tcell.EventKey { transparentDropDown.SetCurrentOption( IndexOf(strconv.FormatBool(style.Transparent), []string{"true", "false"})) + case tcell.KeyCtrlBackslash: + hideBelow = !hideBelow + updateTranslateWindow() + hideBelowDropDown.SetCurrentOption( + IndexOf(strconv.FormatBool(hideBelow), + []string{"true", "false"})) } return event @@ -341,23 +416,17 @@ func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { message := srcInput.GetText() // Only translate when message exist if len(message) > 0 { - translation, err := translator.Translate(message) + translation, definition, partOfSpeech, err := translator.Translate(message) if err != nil { dstOutput.SetText(err.Error()) } else { dstOutput.SetText(translation) + defOutput.SetText(definition, false) + posOutput.SetText(partOfSpeech, false) } } case tcell.KeyCtrlQ: srcInput.SetText("", true) - case tcell.KeyCtrlY: - // copy selected text - text, _, _ := srcInput.GetSelection() - - // only copy when text selected - if len(text) > 0 { - CopyToClipboard(text) - } case tcell.KeyCtrlG: // copy all text in Input text := srcInput.GetText()