├── .github └── workflows │ ├── aur.yml │ ├── docker_build.yml │ ├── format.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── build ├── Dockerfile └── PKGBUILD ├── config.go ├── example ├── keymap.yaml ├── server.yaml └── theme.yaml ├── go.mod ├── go.sum ├── internal ├── style │ ├── color.go │ └── style.go ├── translate │ ├── apertium │ │ ├── language.go │ │ └── translator.go │ ├── bing │ │ ├── language.go │ │ ├── translator.go │ │ └── utils.go │ ├── chatgpt │ │ ├── language.go │ │ └── translator.go │ ├── core │ │ ├── language.go │ │ ├── name.go │ │ ├── server.go │ │ ├── tts.go │ │ └── utils.go │ ├── deepl │ │ ├── language.go │ │ └── translator.go │ ├── google │ │ ├── language.go │ │ └── translator.go │ ├── libre │ │ ├── language.go │ │ └── translator.go │ ├── reverso │ │ ├── language.go │ │ └── translator.go │ └── translator.go └── ui │ └── cycle.go ├── key.go ├── main.go ├── ui.go └── utils.go /.github/workflows/aur.yml: -------------------------------------------------------------------------------- 1 | name: aur 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["release"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | aur: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set env VERSION 16 | run: | 17 | git fetch --tags 18 | echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV 19 | 20 | - name: Replace PKGVER, SHA256SUMS_X86_64 in PKGBUILD 21 | run: | 22 | sed -i "s/PKGVER/$(echo ${{ env.VERSION }} | cut -c2-)/g" build/PKGBUILD 23 | wget https://github.com/eeeXun/gtt/releases/download/${{ env.VERSION }}/gtt-linux-amd64.tar.gz 24 | sed -i "s/SHA256SUMS_X86_64/$(sha256sum gtt-linux-amd64.tar.gz | awk '{print $1}')/g" build/PKGBUILD 25 | 26 | - name: Publish to the AUR 27 | uses: KSXGitHub/github-actions-deploy-aur@v2.7.2 28 | with: 29 | pkgname: gtt-bin 30 | pkgbuild: build/PKGBUILD 31 | commit_message: Update to ${{ env.VERSION }} 32 | commit_username: ${{ secrets.AUR_USERNAME }} 33 | commit_email: ${{ secrets.AUR_EMAIL }} 34 | ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} 35 | -------------------------------------------------------------------------------- /.github/workflows/docker_build.yml: -------------------------------------------------------------------------------- 1 | name: docker_build 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["release"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | docker_build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Set env VERSION 16 | run: | 17 | git fetch --tags 18 | echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV 19 | 20 | - name: Login to Docker Hub 21 | uses: docker/login-action@v2 22 | with: 23 | username: ${{ secrets.DOCKERHUB_USERNAME }} 24 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 25 | 26 | - name: Build and push 27 | uses: docker/build-push-action@v4 28 | with: 29 | context: build/ 30 | build-args: | 31 | VERSION=${{ env.VERSION }} 32 | push: true 33 | tags: | 34 | ${{ secrets.DOCKERHUB_USERNAME }}/gtt:${{ env.VERSION }} 35 | ${{ secrets.DOCKERHUB_USERNAME }}/gtt:latest 36 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: format 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - "**.go" 9 | 10 | jobs: 11 | format: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup Go 17 | uses: actions/setup-go@v3 18 | with: 19 | go-version: "^1.19" 20 | 21 | - name: Format with gofmt 22 | run: gofmt -w . 23 | 24 | - name: Commit changes 25 | uses: stefanzweifel/git-auto-commit-action@v4 26 | with: 27 | commit_message: "style(format): run gofmt" 28 | 29 | - name: Install goimports 30 | run: go install golang.org/x/tools/cmd/goimports@latest 31 | 32 | - name: Format with goimports 33 | run: goimports -w . 34 | 35 | - name: Commit changes 36 | uses: stefanzweifel/git-auto-commit-action@v4 37 | with: 38 | commit_message: "style(format): run goimports" 39 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | release: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest] 13 | 14 | name: Build on ${{ matrix.os }} 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Setup Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: "^1.19" 23 | 24 | - name: Install dependencies 25 | if: ${{ startsWith(matrix.os, 'ubuntu-') }} 26 | run: | 27 | sudo apt-get update 28 | sudo apt-get install libasound2-dev 29 | 30 | - name: Build 31 | run: | 32 | [ -z $version ] && version=$(git describe --tags) 33 | mkdir -p dist 34 | go get 35 | go build -ldflags="-s -w -X main.version=$version" 36 | tar -czf "dist/gtt-$(go env GOOS)-$(go env GOARCH).tar.gz" gtt 37 | rm gtt 38 | 39 | - name: Release 40 | uses: softprops/action-gh-release@v1 41 | with: 42 | files: dist/* 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gtt 2 | *.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 eeeXun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gtt 2 | 3 | Google Translate TUI (Originally) 4 | 5 | Supported Translator: 6 | [`Apertium`](https://www.apertium.org/), 7 | [`Bing`](https://www.bing.com/translator), 8 | [`ChatGPT`](https://chat.openai.com/), 9 | [`DeepL`](https://deepl.com/translator)(only free API), 10 | [`DeepLX`](https://github.com/OwO-Network/DeepLX), 11 | [`Google`](https://translate.google.com/)(default), 12 | [`Libre`](https://libretranslate.com/), 13 | [`Reverso`](https://www.reverso.net/text-translation) 14 | 15 | ## ScreenShot 16 | 17 | ![screenshot](https://github.com/eeeXun/gtt/assets/58657914/3841c2bf-62f7-434a-9e77-91c3748c5675) 18 | 19 | ## ⚠️ Note for ChatGPT and DeepL 20 | 21 | ChatGPT and DeepL translations require API keys, which can be obtained from 22 | [OpenAI API keys](https://platform.openai.com/account/api-keys) and 23 | [DeepL API signup](https://www.deepl.com/pro-api) pages, respectively. Note 24 | that only the free API is supported for DeepL currently. Once you have your 25 | API key add it to `$XDG_CONFIG_HOME/gtt/server.yaml` or `$HOME/.config/gtt/server.yaml`. 26 | See the example in [server.yaml](example/server.yaml) file. 27 | 28 | ```yaml 29 | api_key: 30 | chatgpt: 31 | value: CHATGPT_API_KEY # <- Replace with your API Key 32 | # file: $HOME/secrets/chatgpt.txt # <- You can also specify the file where to read API Key 33 | deepl: 34 | value: DEEPL_API_KEY # <- Replace with your API Key 35 | # file: $HOME/secrets/deepl.txt # <- You can also specify the file where to read API Key 36 | ``` 37 | 38 | ## DeepLX 39 | 40 | DeepLX is [self-hosted server](https://github.com/OwO-Network/DeepLX). 41 | You must provide IP address and port at 42 | `$XDG_CONFIG_HOME/gtt/server.yaml` or `$HOME/.config/gtt/server.yaml`. 43 | The api key for DeepLX is optional, depending on your setting. 44 | See the example in [server.yaml](example/server.yaml) file. 45 | 46 | ```yaml 47 | api_key: 48 | deeplx: 49 | value: DEEPLX_API_KEY # <- Replace with your TOKEN 50 | # file: $HOME/secrets/deeplx.txt # <- You can also specify the file where to read API Key 51 | host: 52 | deeplx: 127.0.0.1:1188 # <- Replace with your server IP address and port 53 | ``` 54 | 55 | ## Libre 56 | 57 | If you want to use official [LibreTranslate](https://libretranslate.com/), you have to obtain an API Key on their [website](https://portal.libretranslate.com/). 58 | Alternatively, if you want to host it by yourself, you must provide the IP address and port. 59 | Make sure add them to `$XDG_CONFIG_HOME/gtt/server.yaml` or `$HOME/.config/gtt/server.yaml`. 60 | See the example in [server.yaml](example/server.yaml) file. 61 | 62 | ```yaml 63 | api_key: 64 | libre: 65 | value: LIBRE_API_KEY # <- Replace with your API Key 66 | # file: $HOME/secrets/libre.txt # <- You can also specify the file where to read API Key 67 | host: 68 | libre: 127.0.0.1:5000 # <- Replace with your server IP address and port 69 | ``` 70 | 71 | ## Install 72 | 73 | ### Dependencies 74 | 75 | For Arch Linux, you need `alsa-lib`. 76 | For Ubuntu or Debian, you need `libasound2-dev`. 77 | For RedHat-based Linux, you need `alsa-lib-devel`. 78 | 79 | [`xclip`](https://github.com/astrand/xclip) (optional) - for Linux/X11 to copy text. 80 | 81 | [`wl-clipboard`](https://github.com/bugaevc/wl-clipboard) (optional) - for Linux/Wayland to copy text. 82 | 83 | Or, if your terminal supports OSC 52, you can enable OSC 52 in page 2 of the pop out menu to copy text. 84 | 85 | ### Arch Linux ([AUR](https://aur.archlinux.org/packages/gtt-bin)) 86 | 87 | ```sh 88 | yay -S gtt-bin 89 | ``` 90 | 91 | ### Nix ❄️ ([nixpkgs-unstable](https://search.nixos.org/packages?channel=unstable&show=gtt&from=0&size=50&sort=relevance&type=packages&query=gtt)) 92 | 93 | add to your package list or run with: 94 | 95 | ```sh 96 | nix-shell -p '(import {}).gtt' --run gtt 97 | ``` 98 | 99 | or with flakes enabled: 100 | 101 | ```sh 102 | nix run github:nixos/nixpkgs#gtt 103 | ``` 104 | 105 | ### Prebuild 106 | 107 | Binary file is available in [Release Page](https://github.com/eeeXun/gtt/releases) for Linux and macOS on x86_64. 108 | 109 | ### From source 110 | 111 | #### go install 112 | 113 | ```sh 114 | go install -ldflags="-s -w" github.com/eeeXun/gtt@latest 115 | ``` 116 | 117 | And make sure `$HOME/go/bin` is in your `$PATH` 118 | 119 | ```sh 120 | export PATH=$PATH:$HOME/go/bin 121 | ``` 122 | 123 | #### go build 124 | 125 | ```sh 126 | git clone https://github.com/eeeXun/gtt.git && cd gtt && go build -ldflags="-s -w -X main.version=$(git describe --tags)" 127 | ``` 128 | 129 | ### Run on Docker ([Docker Hub](https://hub.docker.com/r/eeexun/gtt/tags)) 130 | 131 | ```sh 132 | docker run -it eeexun/gtt:latest 133 | ``` 134 | 135 | ## Key Map 136 | 137 | `` 138 | Exit program. 139 | 140 | `` 141 | Toggle pop out menu. 142 | 143 | `` 144 | Translate from source to destination window. 145 | 146 | `` 147 | Swap language. 148 | 149 | `` 150 | Clear all text in source of translation window. 151 | 152 | `` 153 | Copy selected text. 154 | 155 | `` 156 | Copy all text in source of translation window. 157 | 158 | `` 159 | Copy all text in destination of translation window. 160 | 161 | `` 162 | Play text to speech on source of translation window. 163 | 164 | `` 165 | Play text to speech on destination of translation window. 166 | 167 | `` 168 | Stop playing text to speech. 169 | 170 | `` 171 | Toggle transparent. 172 | 173 | `` 174 | Toggle Definition/Example & Part of speech. 175 | 176 | ``, `` 177 | Cycle through the pop out widget. 178 | 179 | `<1>`, `<2>`, `<3>` 180 | Switch pop out menu. 181 | 182 | ### Customize key map 183 | 184 | You can overwrite the following key 185 | 186 | - `exit`: Exit program. 187 | - `translate`: Translate from source to destination window. 188 | - `swap_language`: Swap language. 189 | - `clear`: Clear all text in source of translation window. 190 | - `copy_selected`: Copy selected text. 191 | - `copy_source`: Copy all text in source of translation window. 192 | - `copy_destination`: Copy all text in destination of translation window. 193 | - `tts_source`: Play text to speech on source of translation window. 194 | - `tts_destination`: Play text to speech on destination of translation window. 195 | - `stop_tts`: Stop playing text to speech. 196 | - `toggle_transparent`: Toggle transparent. 197 | - `toggle_below`: Toggle Definition/Example & Part of speech. 198 | 199 | For key to combine with `Ctrl`, the value can be `C-Space`, `C-\`, `C-]`, `C-^`, `C-_` or `C-a` to `C-z`. 200 | 201 | For key to combine with `Alt`, the value can be `A-Space` or `A-` with the character you want. 202 | 203 | Or you can use function key, the value can be `F1` to `F64`. 204 | 205 | See the example in [keymap.yaml](example/keymap.yaml) file. This file should be located at `$XDG_CONFIG_HOME/gtt/keymap.yaml` or `$HOME/.config/gtt/keymap.yaml`. 206 | 207 | ## Customize theme 208 | 209 | You can create a theme with theme name. And you must provide the color of `bg`, `fg`, `gray`, `red`, `green`, `yellow`, `blue`, `purple`, `cyan`, `orange`. 210 | 211 | And note that: 212 | 213 | - `bg` is for background color 214 | - `fg` is for foreground color 215 | - `gray` is for selected color 216 | - `yellow` is for label color 217 | - `orange` is for KeyMap menu color 218 | - `purple` is for button pressed color 219 | 220 | See the example in [theme.yaml](example/theme.yaml) file. This file should be located at `$XDG_CONFIG_HOME/gtt/theme.yaml` or `$HOME/.config/gtt/theme.yaml`. 221 | 222 | ## Language in argument 223 | 224 | You can pass `-src` and `-dst` in argument to set source and destination language. 225 | 226 | ```sh 227 | gtt -src "English" -dst "Chinese (Traditional)" 228 | ``` 229 | 230 | See available languages on: 231 | 232 | - [Apertium Translate](https://www.apertium.org/) for `Apertium` 233 | - [Bing language-support](https://learn.microsoft.com/en-us/azure/cognitive-services/translator/language-support#translation) for `Bing` 234 | - `ChatGPT` is same as `Google`. See [Google Language support](https://cloud.google.com/translate/docs/languages) 235 | - [DeepL API docs](https://www.deepl.com/docs-api/translate-text/) for `DeepL` 236 | - `DeepLX` is same as `DeepL`. See [DeepL API docs](https://cloud.google.com/translate/docs/languages) 237 | - [Google Language support](https://cloud.google.com/translate/docs/languages) for `Google` 238 | - [LibreTranslate Languages](https://libretranslate.com/languages) for `Libre` 239 | - [Reverso Translation](https://www.reverso.net/text-translation) for `Reverso` 240 | 241 | ## Credit 242 | 243 | [soimort/translate-shell](https://github.com/soimort/translate-shell), 244 | [SimplyTranslate-Engines](https://codeberg.org/SimpleWeb/SimplyTranslate-Engines), 245 | [s0ftik3/reverso-api](https://github.com/s0ftik3/reverso-api) 246 | For request method. 247 | 248 | [snsd0805/GoogleTranslate-TUI](https://github.com/snsd0805/GoogleTranslate-TUI) For inspiration. 249 | 250 | [turk/free-google-translate](https://github.com/turk/free-google-translate) For Google translate in Golang. 251 | -------------------------------------------------------------------------------- /build/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | ARG VERSION 3 | 4 | RUN apt update && apt install -y wget libasound2-dev 5 | RUN wget https://github.com/eeeXun/gtt/releases/download/${VERSION}/gtt-linux-amd64.tar.gz 6 | RUN tar -xvf gtt-linux-amd64.tar.gz -C /usr/bin/ 7 | ENV TERM xterm-256color 8 | 9 | ENTRYPOINT ["gtt"] 10 | -------------------------------------------------------------------------------- /build/PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: eeeXun 2 | 3 | pkgname=gtt-bin 4 | pkgver=PKGVER 5 | pkgrel=1 6 | pkgdesc='Translate TUI in Golang' 7 | url='https://github.com/eeeXun/gtt' 8 | license=('MIT') 9 | source_x86_64=("gtt-${pkgver}-linux-amd64.tar.gz::${url}/releases/download/v${pkgver}/gtt-linux-amd64.tar.gz") 10 | arch=('x86_64') 11 | depends=('alsa-lib') 12 | optdepends=('xclip: for clipboard support on X11' 'wl-clipboard: for clipboard support on Wayland') 13 | provides=('gtt') 14 | sha256sums_x86_64=('SHA256SUMS_X86_64') 15 | 16 | package() { 17 | install -Dm755 "gtt" "$pkgdir/usr/bin/gtt" 18 | } 19 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/eeeXun/gtt/internal/style" 9 | "github.com/eeeXun/gtt/internal/translate" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | var ( 14 | // Main config 15 | config = viper.New() 16 | ) 17 | 18 | // Search XDG_CONFIG_HOME or $HOME/.config 19 | func configInit() { 20 | var ( 21 | defaultConfigPath string 22 | themeConfig = viper.New() 23 | keyMapConfig = viper.New() 24 | serverConfig = viper.New() 25 | defaultKeyMaps = map[string]string{ 26 | "exit": "C-c", 27 | "translate": "C-j", 28 | "swap_language": "C-s", 29 | "clear": "C-q", 30 | "copy_selected": "C-y", 31 | "copy_source": "C-g", 32 | "copy_destination": "C-r", 33 | "tts_source": "C-o", 34 | "tts_destination": "C-p", 35 | "stop_tts": "C-x", 36 | "toggle_transparent": "C-t", 37 | "toggle_below": "C-\\", 38 | } 39 | defaultConfig = map[string]interface{}{ 40 | "osc52": false, 41 | "hide_below": false, 42 | "transparent": false, 43 | "theme": "gruvbox", 44 | "source.border_color": "red", 45 | "destination.border_color": "blue", 46 | "source.language.apertium": "English", 47 | "destination.language.apertium": "English", 48 | "source.language.bing": "English", 49 | "destination.language.bing": "English", 50 | "source.language.chatgpt": "English", 51 | "destination.language.chatgpt": "English", 52 | "source.language.deepl": "English", 53 | "destination.language.deepl": "English", 54 | "source.language.deeplx": "English", 55 | "destination.language.deeplx": "English", 56 | "source.language.google": "English", 57 | "destination.language.google": "English", 58 | "source.language.libre": "English", 59 | "destination.language.libre": "English", 60 | "source.language.reverso": "English", 61 | "destination.language.reverso": "English", 62 | "translator": "Google", 63 | } 64 | ) 65 | 66 | config.SetConfigName("gtt") 67 | themeConfig.SetConfigName("theme") 68 | keyMapConfig.SetConfigName("keymap") 69 | serverConfig.SetConfigName("server") 70 | for _, c := range []*viper.Viper{config, themeConfig, keyMapConfig, serverConfig} { 71 | c.SetConfigType("yaml") 72 | } 73 | if len(os.Getenv("XDG_CONFIG_HOME")) > 0 { 74 | defaultConfigPath = os.Getenv("XDG_CONFIG_HOME") + "/gtt" 75 | for _, c := range []*viper.Viper{config, themeConfig, keyMapConfig, serverConfig} { 76 | c.AddConfigPath(defaultConfigPath) 77 | } 78 | } else { 79 | defaultConfigPath = os.Getenv("HOME") + "/.config/gtt" 80 | } 81 | for _, c := range []*viper.Viper{config, themeConfig, keyMapConfig, serverConfig} { 82 | c.AddConfigPath("$HOME/.config/gtt") 83 | } 84 | 85 | // Import theme if file exists 86 | if err := themeConfig.ReadInConfig(); err == nil { 87 | var ( 88 | palate = make(map[string]int32) 89 | colors = []string{"bg", "fg", "gray", "red", "green", "yellow", "blue", "purple", "cyan", "orange"} 90 | ) 91 | for name := range themeConfig.AllSettings() { 92 | for _, color := range colors { 93 | palate[color] = themeConfig.GetInt32(fmt.Sprintf("%s.%s", name, color)) 94 | } 95 | style.NewTheme(name, palate) 96 | } 97 | } 98 | // Create config file if it does not exist 99 | // Otherwise check if config value is missing 100 | if err := config.ReadInConfig(); err != nil { 101 | for key, value := range defaultConfig { 102 | config.Set(key, value) 103 | } 104 | if _, err = os.Stat(defaultConfigPath); os.IsNotExist(err) { 105 | os.MkdirAll(defaultConfigPath, os.ModePerm) 106 | } 107 | config.SafeWriteConfig() 108 | } else { 109 | missing := false 110 | for key, value := range defaultConfig { 111 | if config.Get(key) == nil { 112 | config.Set(key, value) 113 | missing = true 114 | } 115 | } 116 | // Set to default theme if theme in config does not exist 117 | if IndexOf(config.GetString("theme"), style.AllTheme) < 0 { 118 | config.Set("theme", defaultConfig["theme"]) 119 | missing = true 120 | } 121 | // Set to default translator if translator in config does not exist 122 | if IndexOf(config.GetString("translator"), translate.AllTranslator) < 0 { 123 | config.Set("translator", defaultConfig["translator"]) 124 | missing = true 125 | } 126 | if missing { 127 | config.WriteConfig() 128 | } 129 | } 130 | 131 | // Setup key map 132 | // If keymap file exist and action in file exist, then set the keyMap 133 | // Otherwise, set to defaultKeyMap 134 | if err := keyMapConfig.ReadInConfig(); err == nil { 135 | for action, key := range defaultKeyMaps { 136 | if keyMapConfig.Get(action) == nil { 137 | keyMaps[action] = key 138 | } else { 139 | keyMaps[action] = keyMapConfig.GetString(action) 140 | } 141 | } 142 | } else { 143 | for action, key := range defaultKeyMaps { 144 | keyMaps[action] = key 145 | } 146 | } 147 | // Setup 148 | for _, name := range translate.AllTranslator { 149 | translators[name] = translate.NewTranslator(name) 150 | translators[name].SetSrcLang( 151 | config.GetString(fmt.Sprintf("source.language.%s", name))) 152 | translators[name].SetDstLang( 153 | config.GetString(fmt.Sprintf("destination.language.%s", name))) 154 | } 155 | translator = translators[config.GetString("translator")] 156 | uiStyle.Theme = config.GetString("theme") 157 | uiStyle.OSC52 = config.GetBool("osc52") 158 | uiStyle.HideBelow = config.GetBool("hide_below") 159 | uiStyle.Transparent = config.GetBool("transparent") 160 | uiStyle.SetSrcBorderColor(config.GetString("source.border_color")). 161 | SetDstBorderColor(config.GetString("destination.border_color")) 162 | // Import api key and host if file exists 163 | if err := serverConfig.ReadInConfig(); err == nil { 164 | // api key 165 | for _, name := range []string{"ChatGPT", "DeepL", "DeepLX", "Libre"} { 166 | // Read from value first, then read from file 167 | if serverConfig.Get(fmt.Sprintf("api_key.%s.value", name)) != nil { 168 | translators[name].SetAPIKey(serverConfig.GetString(fmt.Sprintf("api_key.%s.value", name))) 169 | } else if serverConfig.Get(fmt.Sprintf("api_key.%s.file", name)) != nil { 170 | buff, err := os.ReadFile(os.ExpandEnv(serverConfig.GetString(fmt.Sprintf("api_key.%s.file", name)))) 171 | if err == nil { 172 | translators[name].SetAPIKey(strings.TrimSpace(string(buff))) 173 | } 174 | } 175 | } 176 | // host 177 | for _, name := range []string{"DeepLX", "Libre"} { 178 | if serverConfig.Get(fmt.Sprintf("host.%s", name)) != nil { 179 | translators[name].SetHost(serverConfig.GetString(fmt.Sprintf("host.%s", name))) 180 | } 181 | } 182 | } 183 | // Set argument language 184 | if len(*srcLangArg) > 0 { 185 | translator.SetSrcLang(*srcLangArg) 186 | } 187 | if len(*dstLangArg) > 0 { 188 | translator.SetDstLang(*dstLangArg) 189 | } 190 | } 191 | 192 | // Check if need to modify config file when quit program 193 | func updateConfig() { 194 | changed := false 195 | 196 | // Source language is not passed in argument 197 | if len(*srcLangArg) == 0 { 198 | for t_str, t := range translators { 199 | if config.GetString(fmt.Sprintf("source.language.%s", t_str)) != t.GetSrcLang() { 200 | changed = true 201 | config.Set(fmt.Sprintf("source.language.%s", t_str), t.GetSrcLang()) 202 | } 203 | } 204 | } 205 | // Destination language is not passed in argument 206 | if len(*dstLangArg) == 0 { 207 | for t_str, t := range translators { 208 | if config.GetString(fmt.Sprintf("destination.language.%s", t_str)) != t.GetDstLang() { 209 | changed = true 210 | config.Set(fmt.Sprintf("destination.language.%s", t_str), t.GetDstLang()) 211 | } 212 | } 213 | } 214 | if config.GetString("translator") != translator.GetEngineName() { 215 | changed = true 216 | config.Set("translator", translator.GetEngineName()) 217 | } 218 | if config.GetString("theme") != uiStyle.Theme { 219 | changed = true 220 | config.Set("theme", uiStyle.Theme) 221 | } 222 | if config.GetBool("transparent") != uiStyle.Transparent { 223 | changed = true 224 | config.Set("transparent", uiStyle.Transparent) 225 | } 226 | if config.GetBool("hide_below") != uiStyle.HideBelow { 227 | changed = true 228 | config.Set("hide_below", uiStyle.HideBelow) 229 | } 230 | if config.GetBool("osc52") != uiStyle.OSC52 { 231 | changed = true 232 | config.Set("osc52", uiStyle.OSC52) 233 | } 234 | if config.GetString("source.border_color") != uiStyle.SrcBorderStr() { 235 | changed = true 236 | config.Set("source.border_color", uiStyle.SrcBorderStr()) 237 | } 238 | if config.GetString("destination.border_color") != uiStyle.DstBorderStr() { 239 | changed = true 240 | config.Set("destination.border_color", uiStyle.DstBorderStr()) 241 | } 242 | 243 | if changed { 244 | config.WriteConfig() 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /example/keymap.yaml: -------------------------------------------------------------------------------- 1 | # This file should be located at $XDG_CONFIG_HOME/gtt/keymap.yaml or $HOME/.config/gtt/keymap.yaml. 2 | 3 | # For key to combine with Ctrl, the value can be C-Space, C-\, C-], C-^, C-_ or C-a to C-z. 4 | 5 | # For key to combine with Alt, the value can be A-Space or A- with the character you want. 6 | 7 | # Or you can use function key, the value can be F1 to F64. 8 | 9 | 10 | # The following is the default key map 11 | 12 | # Exit program, 13 | exit: C-c 14 | # Translate from source to destination window, 15 | translate: C-j 16 | # Swap language, 17 | swap_language: C-s 18 | # Clear all text in source of translation window, 19 | clear: C-q 20 | # Copy selected text, 21 | copy_selected: C-y 22 | # Copy all text in source of translation window, 23 | copy_source: C-g 24 | # Copy all text in destination of translation window, 25 | copy_destination: C-r 26 | # Play text to speech on source of translation window, 27 | tts_source: C-o 28 | # Play text to speech on destination of translation window, 29 | tts_destination: C-p 30 | # Stop playing text to speech, 31 | stop_tts: C-x 32 | # Toggle transparent, 33 | toggle_transparent: C-t 34 | # Toggle Definition/Example & Part of speech, 35 | toggle_below: C-\ 36 | -------------------------------------------------------------------------------- /example/server.yaml: -------------------------------------------------------------------------------- 1 | # This file should be located at $XDG_CONFIG_HOME/gtt/server.yaml or $HOME/.config/gtt/server.yaml. 2 | api_key: 3 | chatgpt: 4 | value: CHATGPT_API_KEY 5 | # file: $HOME/secrets/chatgpt.txt 6 | deepl: 7 | value: DEEPL_API_KEY 8 | # file: $HOME/secrets/deepl.txt 9 | deeplx: 10 | value: DEEPLX_API_KEY 11 | # file: $HOME/secrets/deeplx.txt 12 | libre: 13 | value: LIBRE_API_KEY 14 | # file: $HOME/secrets/libre.txt 15 | host: 16 | deeplx: 127.0.0.1:1188 17 | libre: 127.0.0.1:5000 18 | -------------------------------------------------------------------------------- /example/theme.yaml: -------------------------------------------------------------------------------- 1 | # This file should be located at $XDG_CONFIG_HOME/gtt/theme.yaml or $HOME/.config/gtt/theme.yaml. 2 | # You have to provide theme name. e.g. tokyonight. 3 | # And colors: bg, fg, gray, red, green, yellow, blue, purple, cyan, orange. 4 | # bg is for background color 5 | # fg is for foreground color 6 | # gray is for selected color 7 | # yellow is for label color 8 | # orange is for KeyMap menu color 9 | # purple is for button pressed color 10 | tokyonight: 11 | bg: 0x1a1b26 12 | fg: 0xc0caf5 13 | gray: 0x414868 14 | red: 0xf7768e 15 | green: 0x9ece6a 16 | yellow: 0xe0af68 17 | blue: 0x7aa2f7 18 | purple: 0xbb9af7 19 | cyan: 0x7dcfff 20 | orange: 0xff9e64 21 | catppuccin: 22 | bg: 0x24273A 23 | fg: 0xCAD3F5 24 | gray: 0x5B6078 25 | red: 0xED8796 26 | green: 0xA6DA95 27 | yellow: 0xEED49F 28 | blue: 0x8AADF4 29 | purple: 0xF5BDE6 30 | cyan: 0x8BD5CA 31 | orange: 0xF5A97F 32 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eeeXun/gtt 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/gdamore/tcell/v2 v2.7.1 7 | github.com/hajimehoshi/go-mp3 v0.3.3 8 | github.com/hajimehoshi/oto/v2 v2.3.1 9 | github.com/rivo/tview v0.0.0-20240807205129-e4c497cc59ed 10 | github.com/spf13/viper v1.13.0 11 | ) 12 | 13 | require ( 14 | github.com/fsnotify/fsnotify v1.5.4 // indirect 15 | github.com/gdamore/encoding v1.0.0 // indirect 16 | github.com/hashicorp/hcl v1.0.0 // indirect 17 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 18 | github.com/magiconair/properties v1.8.6 // indirect 19 | github.com/mattn/go-runewidth v0.0.15 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/pelletier/go-toml v1.9.5 // indirect 22 | github.com/pelletier/go-toml/v2 v2.0.5 // indirect 23 | github.com/rivo/uniseg v0.4.7 // indirect 24 | github.com/spf13/afero v1.8.2 // indirect 25 | github.com/spf13/cast v1.5.0 // indirect 26 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | github.com/subosito/gotenv v1.4.1 // indirect 29 | golang.org/x/sys v0.17.0 // indirect 30 | golang.org/x/term v0.17.0 // indirect 31 | golang.org/x/text v0.14.0 // indirect 32 | gopkg.in/ini.v1 v1.67.0 // indirect 33 | gopkg.in/yaml.v2 v2.4.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 42 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 43 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 44 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 45 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 46 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 47 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 48 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 49 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 50 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 51 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 52 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 53 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 54 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 55 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 56 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 57 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 58 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 59 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 60 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 61 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 62 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 63 | github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= 64 | github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= 65 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 66 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 67 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 68 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 69 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 70 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 71 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 72 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 73 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 74 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 75 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 76 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 77 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 78 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 79 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 80 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 81 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 82 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 83 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 84 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 85 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 86 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 87 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 88 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 89 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 90 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 91 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 92 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 93 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 94 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 95 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 96 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 97 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 98 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 99 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 100 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 101 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 102 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 103 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 104 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 105 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 106 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 107 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 108 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 109 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 110 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 111 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 112 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 113 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 114 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 115 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 116 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 117 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 118 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 119 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 120 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 121 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 122 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 123 | github.com/hajimehoshi/go-mp3 v0.3.3 h1:cWnfRdpye2m9ElSoVqneYRcpt/l3ijttgjMeQh+r+FE= 124 | github.com/hajimehoshi/go-mp3 v0.3.3/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= 125 | github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= 126 | github.com/hajimehoshi/oto/v2 v2.3.1 h1:qrLKpNus2UfD674oxckKjNJmesp9hMh7u7QCrStB3Rc= 127 | github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= 128 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 129 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 130 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 131 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 132 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 133 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 134 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 135 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 136 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 137 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 138 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 139 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 140 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 141 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 142 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 143 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 144 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 145 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= 146 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 147 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 148 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 149 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 150 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 151 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 152 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 153 | github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= 154 | github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= 155 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 156 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 157 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 158 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 159 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 160 | github.com/rivo/tview v0.0.0-20240807205129-e4c497cc59ed h1:bd91xrd1ECzkEJ4XttUJEiyyGBHQdaOmXVEffJbAm6M= 161 | github.com/rivo/tview v0.0.0-20240807205129-e4c497cc59ed/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= 162 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 163 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 164 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 165 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 166 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 167 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 168 | github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= 169 | github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= 170 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 171 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 172 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 173 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 174 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 175 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 176 | github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= 177 | github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= 178 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 179 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 180 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 181 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 182 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 183 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 184 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 185 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 186 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 187 | github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= 188 | github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 189 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 190 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 191 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 192 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 193 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 194 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 195 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 196 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 197 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 198 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 199 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 200 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 201 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 202 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 203 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 204 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 205 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 206 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 207 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 208 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 209 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 210 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 211 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 212 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 213 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 214 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 215 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 216 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 217 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 218 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 219 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 220 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 221 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 222 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 223 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 224 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 225 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 226 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 227 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 228 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 229 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 230 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 231 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 232 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 233 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 234 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 235 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 236 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 237 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 238 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 239 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 240 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 241 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 242 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 243 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 244 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 245 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 246 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 247 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 248 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 249 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 250 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 251 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 252 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 253 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 254 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 255 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 256 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 257 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 258 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 259 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 260 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 261 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 262 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 263 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 264 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 265 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 266 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 267 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 268 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 269 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 270 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 271 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 272 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 273 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 274 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 275 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 276 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 277 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 278 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 279 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 280 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 281 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 282 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 283 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 284 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 285 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 286 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 287 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 288 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 289 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 290 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 291 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 292 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 293 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 294 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 295 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 296 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 297 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 298 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 299 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 300 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 321 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 330 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 331 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 332 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 333 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 334 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 335 | golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 336 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 337 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 338 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 339 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 340 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 341 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 342 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 343 | golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= 344 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 345 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 346 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 347 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 348 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 349 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 350 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 351 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 352 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 353 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 354 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 355 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 356 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 357 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 358 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 359 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 360 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 361 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 362 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 363 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 364 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 365 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 366 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 367 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 368 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 369 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 370 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 371 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 372 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 373 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 374 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 375 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 376 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 377 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 378 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 379 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 380 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 381 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 382 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 383 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 384 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 385 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 386 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 387 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 388 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 389 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 390 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 391 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 392 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 393 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 394 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 395 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 396 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 397 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 398 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 399 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 400 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 401 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 402 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 403 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 404 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 405 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 406 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 407 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 408 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 409 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 410 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 411 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 412 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 413 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 414 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 415 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 416 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 417 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 418 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 419 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 420 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 421 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 422 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 423 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 424 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 425 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 426 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 427 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 428 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 429 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 430 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 431 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 432 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 433 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 434 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 435 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 436 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 437 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 438 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 439 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 440 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 441 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 442 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 443 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 444 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 445 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 446 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 447 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 448 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 449 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 450 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 451 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 452 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 453 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 454 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 455 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 456 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 457 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 458 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 459 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 460 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 461 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 462 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 463 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 464 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 465 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 466 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 467 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 468 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 469 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 470 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 471 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 472 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 473 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 474 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 475 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 476 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 477 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 478 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 479 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 480 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 481 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 482 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 483 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 484 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 485 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 486 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 487 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 488 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 489 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 490 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 491 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 492 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 493 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 494 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 495 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 496 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 497 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 498 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 499 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 500 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 501 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 502 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 503 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 504 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 505 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 506 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 507 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 508 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 509 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 510 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 511 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 512 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 513 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 514 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 515 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 516 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 517 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 518 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 519 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 520 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 521 | -------------------------------------------------------------------------------- /internal/style/color.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | var ( 8 | AllTheme = []string{"gruvbox", "nord"} 9 | Palette = []string{"red", "green", "yellow", "blue", "purple", "cyan", "orange"} 10 | themes = map[string]map[string]tcell.Color{ 11 | "gruvbox": { 12 | "bg": tcell.NewHexColor(0x282828), 13 | "fg": tcell.NewHexColor(0xebdbb2), 14 | "gray": tcell.NewHexColor(0x665c54), 15 | "red": tcell.NewHexColor(0xfb4934), 16 | "green": tcell.NewHexColor(0xb8bb26), 17 | "yellow": tcell.NewHexColor(0xfabd2f), 18 | "blue": tcell.NewHexColor(0x83a598), 19 | "purple": tcell.NewHexColor(0xd3869b), 20 | "cyan": tcell.NewHexColor(0x8ec07c), 21 | "orange": tcell.NewHexColor(0xfe8019), 22 | }, 23 | "nord": { 24 | "bg": tcell.NewHexColor(0x2e3440), 25 | "fg": tcell.NewHexColor(0xeceff4), 26 | "gray": tcell.NewHexColor(0x4c566a), 27 | "red": tcell.NewHexColor(0xbf616a), 28 | "green": tcell.NewHexColor(0xa3be8c), 29 | "yellow": tcell.NewHexColor(0xebcb8b), 30 | "blue": tcell.NewHexColor(0x81a1c1), 31 | "purple": tcell.NewHexColor(0xb48ead), 32 | "cyan": tcell.NewHexColor(0x8fbcbb), 33 | "orange": tcell.NewHexColor(0xd08770), 34 | }, 35 | } 36 | ) 37 | 38 | func NewTheme(name string, palette map[string]int32) { 39 | AllTheme = append(AllTheme, name) 40 | themes[name] = make(map[string]tcell.Color) 41 | for color, rgb := range palette { 42 | themes[name][color] = tcell.NewHexColor(rgb) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/style/style.go: -------------------------------------------------------------------------------- 1 | package style 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | type style struct { 8 | OSC52 bool 9 | HideBelow bool 10 | Transparent bool 11 | Theme string 12 | srcBorderColor string 13 | dstBorderColor string 14 | backgroundColor string 15 | foregroundColor string 16 | selectedColor string 17 | prefixColor string 18 | labelColor string 19 | pressColor string 20 | highLightColor string 21 | } 22 | 23 | func NewStyle() *style { 24 | return &style{ 25 | backgroundColor: "bg", 26 | foregroundColor: "fg", 27 | selectedColor: "gray", 28 | prefixColor: "cyan", 29 | labelColor: "yellow", 30 | pressColor: "purple", 31 | highLightColor: "orange", 32 | } 33 | } 34 | 35 | func (s style) BackgroundColor() tcell.Color { 36 | if s.Transparent { 37 | return tcell.ColorDefault 38 | } 39 | return themes[s.Theme][s.backgroundColor] 40 | } 41 | 42 | func (s style) ForegroundColor() tcell.Color { 43 | return themes[s.Theme][s.foregroundColor] 44 | } 45 | 46 | func (s style) SelectedColor() tcell.Color { 47 | return themes[s.Theme][s.selectedColor] 48 | } 49 | 50 | func (s style) PrefixColor() tcell.Color { 51 | return themes[s.Theme][s.prefixColor] 52 | } 53 | 54 | func (s style) LabelColor() tcell.Color { 55 | return themes[s.Theme][s.labelColor] 56 | } 57 | 58 | func (s style) PressColor() tcell.Color { 59 | return themes[s.Theme][s.pressColor] 60 | } 61 | 62 | func (s style) HighLightColor() tcell.Color { 63 | return themes[s.Theme][s.highLightColor] 64 | } 65 | 66 | func (s style) SrcBorderColor() tcell.Color { 67 | return themes[s.Theme][s.srcBorderColor] 68 | } 69 | 70 | func (s style) DstBorderColor() tcell.Color { 71 | return themes[s.Theme][s.dstBorderColor] 72 | } 73 | 74 | func (s style) SrcBorderStr() string { 75 | return s.srcBorderColor 76 | } 77 | 78 | func (s style) DstBorderStr() string { 79 | return s.dstBorderColor 80 | } 81 | 82 | func (s *style) SetSrcBorderColor(color string) *style { 83 | s.srcBorderColor = color 84 | return s 85 | } 86 | 87 | func (s *style) SetDstBorderColor(color string) *style { 88 | s.dstBorderColor = color 89 | return s 90 | } 91 | -------------------------------------------------------------------------------- /internal/translate/apertium/language.go: -------------------------------------------------------------------------------- 1 | package apertium 2 | 3 | var ( 4 | lang = []string{ 5 | "Afrikaans", 6 | "Arabic", 7 | "Aranese", 8 | "Aragonese", 9 | "Arpitan", 10 | "Basque", 11 | "Belarusian", 12 | "Breton", 13 | "Bulgarian", 14 | "Catalan", 15 | "Crimean Tatar", 16 | "Danish", 17 | "Dutch", 18 | "East Norwegian, vi→vi", 19 | "English", 20 | "Esperanto", 21 | "French", 22 | "Galician", 23 | "Gascon", 24 | "Hindi", 25 | "Icelandic", 26 | "Indonesian", 27 | "Italian", 28 | "Kazakh", 29 | "Macedonian", 30 | "Malay", 31 | "Maltese", 32 | "Northern Sami", 33 | "Norwegian Bokmål", 34 | "Norwegian Nynorsk", 35 | "Occitan", 36 | "Polish", 37 | "Portuguese", 38 | "Romanian", 39 | "Russian", 40 | "Serbo-Croatian", 41 | "Silesian", 42 | "Slovenian", 43 | "Spanish", 44 | "Swedish", 45 | "Tatar", 46 | "Turkish", 47 | "Ukrainian", 48 | "Urdu", 49 | "Welsh", 50 | } 51 | langCode = map[string]string{ 52 | "Afrikaans": "afr", 53 | "Arabic": "ara", 54 | "Aranese": "oci_aran", 55 | "Aragonese": "arg", 56 | "Arpitan": "frp", 57 | "Basque": "eus", 58 | "Belarusian": "bel", 59 | "Breton": "bre", 60 | "Bulgarian": "bul", 61 | "Catalan": "cat", 62 | "Crimean Tatar": "crh", 63 | "Danish": "dan", 64 | "Dutch": "nld", 65 | "East Norwegian, vi→vi": "nno_e", 66 | "English": "eng", 67 | "Esperanto": "epo", 68 | "French": "fra", 69 | "Galician": "glg", 70 | "Gascon": "oci_gascon", 71 | "Hindi": "hin", 72 | "Icelandic": "isl", 73 | "Indonesian": "ind", 74 | "Italian": "ita", 75 | "Kazakh": "kaz", 76 | "Macedonian": "mkd", 77 | "Malay": "zlm", 78 | "Maltese": "mlt", 79 | "Northern Sami": "sme", 80 | "Norwegian Bokmål": "nob", 81 | "Norwegian Nynorsk": "nno", 82 | "Occitan": "oci", 83 | "Polish": "pol", 84 | "Portuguese": "por", 85 | "Romanian": "ron", 86 | "Russian": "rus", 87 | "Serbo-Croatian": "hbs", 88 | "Silesian": "szl", 89 | "Slovenian": "slv", 90 | "Spanish": "spa", 91 | "Swedish": "swe", 92 | "Tatar": "tat", 93 | "Turkish": "tur", 94 | "Ukrainian": "ukr", 95 | "Urdu": "urd", 96 | "Welsh": "cym", 97 | } 98 | ) 99 | -------------------------------------------------------------------------------- /internal/translate/apertium/translator.go: -------------------------------------------------------------------------------- 1 | package apertium 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/eeeXun/gtt/internal/translate/core" 12 | ) 13 | 14 | const ( 15 | textURL = "https://www.apertium.org/apy/translate?langpair=%s|%s&q=%s" 16 | ) 17 | 18 | type Translator struct { 19 | *core.Server 20 | *core.Language 21 | *core.TTS 22 | core.EngineName 23 | } 24 | 25 | func NewTranslator() *Translator { 26 | return &Translator{ 27 | Server: new(core.Server), 28 | Language: new(core.Language), 29 | TTS: core.NewTTS(), 30 | EngineName: core.NewEngineName("Apertium"), 31 | } 32 | } 33 | 34 | func (t *Translator) GetAllLang() []string { 35 | return lang 36 | } 37 | 38 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 39 | translation = new(core.Translation) 40 | var data map[string]interface{} 41 | 42 | urlStr := fmt.Sprintf( 43 | textURL, 44 | langCode[t.GetSrcLang()], 45 | langCode[t.GetDstLang()], 46 | url.QueryEscape(message), 47 | ) 48 | res, err := http.Get(urlStr) 49 | if err != nil { 50 | return nil, err 51 | } 52 | body, err := io.ReadAll(res.Body) 53 | if err != nil { 54 | return nil, err 55 | } 56 | if err = json.Unmarshal(body, &data); err != nil { 57 | return nil, err 58 | } 59 | 60 | if len(data) <= 0 { 61 | return nil, errors.New("Translation not found") 62 | } 63 | // If responseData is nil, then suppose the translation pair is not available 64 | if data["responseData"] == nil { 65 | return nil, errors.New(fmt.Sprintf("%s does not support translate from %s to %s.", 66 | t.GetEngineName(), 67 | t.GetSrcLang(), 68 | t.GetDstLang(), 69 | )) 70 | } 71 | 72 | translation.TEXT = data["responseData"].(map[string]interface{})["translatedText"].(string) 73 | 74 | return translation, nil 75 | } 76 | 77 | func (t *Translator) PlayTTS(lang, message string) error { 78 | defer t.ReleaseLock() 79 | 80 | return errors.New(t.GetEngineName() + " does not support text to speech") 81 | } 82 | -------------------------------------------------------------------------------- /internal/translate/bing/language.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | var ( 4 | lang = []string{ 5 | "Afrikaans", 6 | "Albanian", 7 | "Amharic", 8 | "Auto", 9 | "Arabic", 10 | "Armenian", 11 | "Assamese", 12 | "Azerbaijani", 13 | "Bangla", 14 | "Bashkir", 15 | "Basque", 16 | "Bosnian", 17 | "Bulgarian", 18 | "Cantonese (Traditional)", 19 | "Catalan", 20 | "Chinese (Literary)", 21 | "Chinese Simplified", 22 | "Chinese Traditional", 23 | "Croatian", 24 | "Czech", 25 | "Danish", 26 | "Dari", 27 | "Divehi", 28 | "Dutch", 29 | "English", 30 | "Estonian", 31 | "Faroese", 32 | "Fijian", 33 | "Filipino", 34 | "Finnish", 35 | "French", 36 | "French (Canada)", 37 | "Galician", 38 | "Ganda", 39 | "Georgian", 40 | "German", 41 | "Greek", 42 | "Gujarati", 43 | "Haitian Creole", 44 | "Hausa", 45 | "Hebrew", 46 | "Hindi", 47 | "Hmong Daw", 48 | "Hungarian", 49 | "Icelandic", 50 | "Igbo", 51 | "Indonesian", 52 | "Inuinnaqtun", 53 | "Inuktitut", 54 | "Inuktitut (Latin)", 55 | "Irish", 56 | "Italian", 57 | "Japanese", 58 | "Kannada", 59 | "Kazakh", 60 | "Khmer", 61 | "Kinyarwanda", 62 | "Klingon (Latin)", 63 | "Korean", 64 | "Kurdish (Central)", 65 | "Kurdish (Northern)", 66 | "Kyrgyz", 67 | "Lao", 68 | "Latvian", 69 | "Lingala", 70 | "Lithuanian", 71 | "Macedonian", 72 | "Malagasy", 73 | "Malay", 74 | "Malayalam", 75 | "Maltese", 76 | "Marathi", 77 | "Mongolian (Cyrillic)", 78 | "Mongolian (Traditional)", 79 | "Myanmar (Burmese)", 80 | "Māori", 81 | "Nepali", 82 | "Norwegian", 83 | "Nyanja", 84 | "Odia", 85 | "Pashto", 86 | "Persian", 87 | "Polish", 88 | "Portuguese (Brazil)", 89 | "Portuguese (Portugal)", 90 | "Punjabi", 91 | "Querétaro Otomi", 92 | "Romanian", 93 | "Rundi", 94 | "Russian", 95 | "Samoan", 96 | "Serbian (Cyrillic)", 97 | "Serbian (Latin)", 98 | "Sesotho", 99 | "Sesotho sa Leboa", 100 | "Setswana", 101 | "Shona", 102 | "Slovak", 103 | "Slovenian", 104 | "Somali", 105 | "Spanish", 106 | "Swahili", 107 | "Swedish", 108 | "Tahitian", 109 | "Tamil", 110 | "Tatar", 111 | "Telugu", 112 | "Thai", 113 | "Tibetan", 114 | "Tigrinya", 115 | "Tongan", 116 | "Turkish", 117 | "Turkmen", 118 | "Ukrainian", 119 | "Urdu", 120 | "Uyghur", 121 | "Uzbek (Latin)", 122 | "Vietnamese", 123 | "Welsh", 124 | "Xhosa", 125 | "Yoruba", 126 | "Yucatec Maya", 127 | "Zulu", 128 | } 129 | langCode = map[string]string{ 130 | "Afrikaans": "af", 131 | "Albanian": "sq", 132 | "Amharic": "am", 133 | "Auto": "auto-detect", 134 | "Arabic": "ar", 135 | "Armenian": "hy", 136 | "Assamese": "as", 137 | "Azerbaijani": "az", 138 | "Bangla": "bn", 139 | "Bashkir": "ba", 140 | "Basque": "eu", 141 | "Bosnian": "bs", 142 | "Bulgarian": "bg", 143 | "Cantonese (Traditional)": "yue", 144 | "Catalan": "ca", 145 | "Chinese (Literary)": "lzh", 146 | "Chinese Simplified": "zh-Hans", 147 | "Chinese Traditional": "zh-Hant", 148 | "Croatian": "hr", 149 | "Czech": "cs", 150 | "Danish": "da", 151 | "Dari": "prs", 152 | "Divehi": "dv", 153 | "Dutch": "nl", 154 | "English": "en", 155 | "Estonian": "et", 156 | "Faroese": "fo", 157 | "Fijian": "fj", 158 | "Filipino": "fil", 159 | "Finnish": "fi", 160 | "French": "fr", 161 | "French (Canada)": "fr-CA", 162 | "Galician": "gl", 163 | "Ganda": "lug", 164 | "Georgian": "ka", 165 | "German": "de", 166 | "Greek": "el", 167 | "Gujarati": "gu", 168 | "Haitian Creole": "ht", 169 | "Hausa": "ha", 170 | "Hebrew": "he", 171 | "Hindi": "hi", 172 | "Hmong Daw": "mww", 173 | "Hungarian": "hu", 174 | "Icelandic": "is", 175 | "Igbo": "ig", 176 | "Indonesian": "id", 177 | "Inuinnaqtun": "ikt", 178 | "Inuktitut": "iu", 179 | "Inuktitut (Latin)": "iu-Latn", 180 | "Irish": "ga", 181 | "Italian": "it", 182 | "Japanese": "ja", 183 | "Kannada": "kn", 184 | "Kazakh": "kk", 185 | "Khmer": "km", 186 | "Kinyarwanda": "rw", 187 | "Klingon (Latin)": "tlh-Latn", 188 | "Korean": "ko", 189 | "Kurdish (Central)": "ku", 190 | "Kurdish (Northern)": "kmr", 191 | "Kyrgyz": "ky", 192 | "Lao": "lo", 193 | "Latvian": "lv", 194 | "Lingala": "ln", 195 | "Lithuanian": "lt", 196 | "Macedonian": "mk", 197 | "Malagasy": "mg", 198 | "Malay": "ms", 199 | "Malayalam": "ml", 200 | "Maltese": "mt", 201 | "Marathi": "mr", 202 | "Mongolian (Cyrillic)": "mn-Cyrl", 203 | "Mongolian (Traditional)": "mn-Mong", 204 | "Myanmar (Burmese)": "my", 205 | "Māori": "mi", 206 | "Nepali": "ne", 207 | "Norwegian": "nb", 208 | "Nyanja": "nya", 209 | "Odia": "or", 210 | "Pashto": "ps", 211 | "Persian": "fa", 212 | "Polish": "pl", 213 | "Portuguese (Brazil)": "pt", 214 | "Portuguese (Portugal)": "pt-PT", 215 | "Punjabi": "pa", 216 | "Querétaro Otomi": "otq", 217 | "Romanian": "ro", 218 | "Rundi": "run", 219 | "Russian": "ru", 220 | "Samoan": "sm", 221 | "Serbian (Cyrillic)": "sr-Cyrl", 222 | "Serbian (Latin)": "sr-Latn", 223 | "Sesotho": "st", 224 | "Sesotho sa Leboa": "nso", 225 | "Setswana": "tn", 226 | "Shona": "sn", 227 | "Slovak": "sk", 228 | "Slovenian": "sl", 229 | "Somali": "so", 230 | "Spanish": "es", 231 | "Swahili": "sw", 232 | "Swedish": "sv", 233 | "Tahitian": "ty", 234 | "Tamil": "ta", 235 | "Tatar": "tt", 236 | "Telugu": "te", 237 | "Thai": "th", 238 | "Tibetan": "bo", 239 | "Tigrinya": "ti", 240 | "Tongan": "to", 241 | "Turkish": "tr", 242 | "Turkmen": "tk", 243 | "Ukrainian": "uk", 244 | "Urdu": "ur", 245 | "Uyghur": "ug", 246 | "Uzbek (Latin)": "uz", 247 | "Vietnamese": "vi", 248 | "Welsh": "cy", 249 | "Xhosa": "xh", 250 | "Yoruba": "yo", 251 | "Yucatec Maya": "yua", 252 | "Zulu": "zu", 253 | } 254 | voiceName = map[string]string{ 255 | "Afrikaans": "af-ZA-AdriNeural", 256 | "Amharic": "am-ET-MekdesNeural", 257 | "Arabic": "ar-SA-HamedNeural", 258 | "Bangla": "bn-IN-TanishaaNeural", 259 | "Bulgarian": "bg-BG-BorislavNeural", 260 | "Cantonese (Traditional)": "zh-HK-HiuGaaiNeural", 261 | "Catalan": "ca-ES-JoanaNeural", 262 | "Chinese Simplified": "zh-CN-XiaoxiaoNeural", 263 | "Chinese Traditional": "zh-CN-XiaoxiaoNeural", 264 | "Croatian": "hr-HR-SreckoNeural", 265 | "Czech": "cs-CZ-AntoninNeural", 266 | "Danish": "da-DK-ChristelNeural", 267 | "Dutch": "nl-NL-ColetteNeural", 268 | "English": "en-US-AriaNeural", 269 | "Estonian": "et-EE-AnuNeural", 270 | "Finnish": "fi-FI-NooraNeural", 271 | "French": "fr-FR-DeniseNeural", 272 | "French (Canada)": "fr-CA-SylvieNeural", 273 | "German": "de-DE-KatjaNeural", 274 | "Greek": "el-GR-NestorasNeural", 275 | "Gujarati": "gu-IN-DhwaniNeural", 276 | "Hebrew": "he-IL-AvriNeural", 277 | "Hindi": "hi-IN-SwaraNeural", 278 | "Hungarian": "hu-HU-TamasNeural", 279 | "Icelandic": "is-IS-GudrunNeural", 280 | "Indonesian": "id-ID-ArdiNeural", 281 | "Irish": "ga-IE-OrlaNeural", 282 | "Italian": "it-IT-DiegoNeural", 283 | "Japanese": "ja-JP-NanamiNeural", 284 | "Kannada": "kn-IN-SapnaNeural", 285 | "Kazakh": "kk-KZ-AigulNeural", 286 | "Khmer": "km-KH-SreymomNeural", 287 | "Korean": "ko-KR-SunHiNeural", 288 | "Lao": "lo-LA-KeomanyNeural", 289 | "Latvian": "lv-LV-EveritaNeural", 290 | "Lithuanian": "lt-LT-OnaNeural", 291 | "Macedonian": "mk-MK-MarijaNeural", 292 | "Malay": "ms-MY-OsmanNeural", 293 | "Malayalam": "ml-IN-SobhanaNeural", 294 | "Maltese": "mt-MT-GraceNeural", 295 | "Marathi": "mr-IN-AarohiNeural", 296 | "Myanmar (Burmese)": "my-MM-NilarNeural", 297 | "Norwegian": "nb-NO-PernilleNeural", 298 | "Pashto": "ps-AF-LatifaNeural", 299 | "Persian": "fa-IR-DilaraNeural", 300 | "Polish": "pl-PL-ZofiaNeural", 301 | "Portuguese (Brazil)": "pt-BR-FranciscaNeural", 302 | "Portuguese (Portugal)": "pt-PT-FernandaNeural", 303 | "Romanian": "ro-RO-EmilNeural", 304 | "Russian": "ru-RU-DariyaNeural", 305 | "Serbian (Cyrillic)": "sr-RS-SophieNeural", 306 | "Slovak": "sk-SK-LukasNeural", 307 | "Slovenian": "sl-SI-RokNeural", 308 | "Spanish": "es-ES-ElviraNeural", 309 | "Swedish": "sv-SE-SofieNeural", 310 | "Tamil": "ta-IN-PallaviNeural", 311 | "Telugu": "te-IN-ShrutiNeural", 312 | "Thai": "th-TH-NiwatNeural", 313 | "Turkish": "tr-TR-EmelNeural", 314 | "Ukrainian": "uk-UA-PolinaNeural", 315 | "Urdu": "ur-IN-GulNeural", 316 | "Uzbek (Latin)": "uz-UZ-MadinaNeural", 317 | "Vietnamese": "vi-VN-NamMinhNeural", 318 | "Welsh": "cy-GB-NiaNeural", 319 | } 320 | ) 321 | -------------------------------------------------------------------------------- /internal/translate/bing/translator.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "regexp" 11 | "strings" 12 | 13 | "github.com/eeeXun/gtt/internal/translate/core" 14 | ) 15 | 16 | const ( 17 | setUpURL = "https://www.bing.com/translator" 18 | textURL = "https://www.bing.com/ttranslatev3?IG=%s&IID=%s" 19 | posURL = "https://www.bing.com/tlookupv3?IG=%s&IID=%s" 20 | ttsURL = "https://www.bing.com/tfettts?IG=%s&IID=%s" 21 | ttsSSML = "%s" 22 | ) 23 | 24 | type Translator struct { 25 | *core.Server 26 | *core.Language 27 | *core.TTS 28 | core.EngineName 29 | } 30 | 31 | type setUpData struct { 32 | ig string 33 | iid string 34 | key string 35 | token string 36 | } 37 | 38 | func NewTranslator() *Translator { 39 | return &Translator{ 40 | Server: new(core.Server), 41 | Language: new(core.Language), 42 | TTS: core.NewTTS(), 43 | EngineName: core.NewEngineName("Bing"), 44 | } 45 | } 46 | 47 | func (t *Translator) GetAllLang() []string { 48 | return lang 49 | } 50 | 51 | func (t *Translator) setUp() (*setUpData, error) { 52 | data := new(setUpData) 53 | 54 | res, err := http.Get(setUpURL) 55 | if err != nil { 56 | return nil, err 57 | } 58 | body, err := io.ReadAll(res.Body) 59 | if err != nil { 60 | return nil, err 61 | } 62 | bodyStr := string(body) 63 | igData := regexp.MustCompile(`IG:"([^"]+)"`).FindStringSubmatch(bodyStr) 64 | if len(igData) < 2 { 65 | return nil, errors.New(t.GetEngineName() + " IG not found") 66 | } 67 | data.ig = igData[1] 68 | iidData := regexp.MustCompile(`data-iid="([^"]+)`).FindStringSubmatch(bodyStr) 69 | if len(iidData) < 2 { 70 | return nil, errors.New(t.GetEngineName() + " IID not found") 71 | } 72 | data.iid = iidData[1] 73 | params := regexp.MustCompile(`params_AbusePreventionHelper = ([^;]+);`).FindStringSubmatch(bodyStr) 74 | if len(params) < 2 { 75 | return nil, errors.New(t.GetEngineName() + " Key and Token not found") 76 | } 77 | paramsStr := strings.Split(params[1][1:len(params[1])-1], ",") 78 | data.key = paramsStr[0] 79 | data.token = paramsStr[1][1 : len(paramsStr[1])-1] 80 | 81 | return data, nil 82 | } 83 | 84 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 85 | translation = new(core.Translation) 86 | var data []interface{} 87 | 88 | initData, err := t.setUp() 89 | if err != nil { 90 | return nil, err 91 | } 92 | userData := url.Values{ 93 | "fromLang": {langCode[t.GetSrcLang()]}, 94 | "to": {langCode[t.GetDstLang()]}, 95 | "text": {message}, 96 | "key": {initData.key}, 97 | "token": {initData.token}, 98 | } 99 | req, _ := http.NewRequest(http.MethodPost, 100 | fmt.Sprintf(textURL, initData.ig, initData.iid), 101 | strings.NewReader(userData.Encode()), 102 | ) 103 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 104 | req.Header.Add("User-Agent", core.UserAgent) 105 | res, err := http.DefaultClient.Do(req) 106 | if err != nil { 107 | return nil, err 108 | } 109 | body, err := io.ReadAll(res.Body) 110 | if err != nil { 111 | return nil, err 112 | } 113 | if err = json.Unmarshal(body, &data); err != nil { 114 | return nil, err 115 | } 116 | 117 | if len(data) <= 0 { 118 | return nil, errors.New("Translation not found") 119 | } 120 | 121 | // translation 122 | translation.TEXT = 123 | data[0].(map[string]interface{})["translations"].([]interface{})[0].(map[string]interface{})["text"].(string) 124 | 125 | // request part of speech 126 | userData.Del("fromLang") 127 | userData.Add("from", langCode[t.GetSrcLang()]) 128 | req, _ = http.NewRequest(http.MethodPost, 129 | fmt.Sprintf(posURL, initData.ig, initData.iid), 130 | strings.NewReader(userData.Encode()), 131 | ) 132 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 133 | req.Header.Add("User-Agent", core.UserAgent) 134 | res, err = http.DefaultClient.Do(req) 135 | if err != nil { 136 | return nil, err 137 | } 138 | body, err = io.ReadAll(res.Body) 139 | if err != nil { 140 | return nil, err 141 | } 142 | // Bing will return the request with list when success. 143 | // Otherwises, it would return map. Then the following err would not be nil. 144 | if err = json.Unmarshal(body, &data); err != nil { 145 | return nil, err 146 | } 147 | 148 | if len(data) <= 0 { 149 | return nil, errors.New("Translation not found") 150 | } 151 | 152 | poses := make(posSet) 153 | for _, pos := range data[0].(map[string]interface{})["translations"].([]interface{}) { 154 | pos := pos.(map[string]interface{}) 155 | var words posWords 156 | 157 | words.target = pos["displayTarget"].(string) 158 | for _, backTranslation := range pos["backTranslations"].([]interface{}) { 159 | backTranslation := backTranslation.(map[string]interface{}) 160 | words.add(backTranslation["displayText"].(string)) 161 | } 162 | poses.add(pos["posTag"].(string), words) 163 | } 164 | translation.POS = poses.format() 165 | 166 | return translation, nil 167 | } 168 | 169 | func (t *Translator) PlayTTS(lang, message string) error { 170 | defer t.ReleaseLock() 171 | 172 | name, ok := voiceName[lang] 173 | if !ok { 174 | return errors.New(t.GetEngineName() + " does not support text to speech of " + lang) 175 | } 176 | initData, err := t.setUp() 177 | if err != nil { 178 | return err 179 | } 180 | userData := url.Values{ 181 | // lang='%s' in ssml should be xx-XX, e.g. en-US 182 | // But xx also works, e.g. en 183 | // So don't do extra work to get xx-XX 184 | "ssml": {fmt.Sprintf(ttsSSML, langCode[lang], name, message)}, 185 | "key": {initData.key}, 186 | "token": {initData.token}, 187 | } 188 | req, _ := http.NewRequest(http.MethodPost, 189 | fmt.Sprintf(ttsURL, initData.ig, initData.iid), 190 | strings.NewReader(userData.Encode()), 191 | ) 192 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 193 | req.Header.Add("User-Agent", core.UserAgent) 194 | res, err := http.DefaultClient.Do(req) 195 | if err != nil { 196 | return err 197 | } 198 | return t.Play(res.Body) 199 | } 200 | -------------------------------------------------------------------------------- /internal/translate/bing/utils.go: -------------------------------------------------------------------------------- 1 | package bing 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type posWords struct { 8 | target string 9 | backTargets []string 10 | } 11 | 12 | func (t *posWords) add(s string) { 13 | t.backTargets = append(t.backTargets, s) 14 | } 15 | 16 | type posSet map[string][]posWords 17 | 18 | func (set posSet) add(tag string, words posWords) { 19 | set[tag] = append(set[tag], words) 20 | } 21 | 22 | func (set posSet) format() (s string) { 23 | for tag := range set { 24 | s += fmt.Sprintf("[%s]\n", tag) 25 | for _, words := range set[tag] { 26 | s += fmt.Sprintf("\t%s:", words.target) 27 | firstWord := true 28 | for _, backTarget := range words.backTargets { 29 | if firstWord { 30 | s += fmt.Sprintf(" %s", backTarget) 31 | firstWord = false 32 | } else { 33 | s += fmt.Sprintf(", %s", backTarget) 34 | } 35 | } 36 | s += "\n" 37 | } 38 | } 39 | return s 40 | } 41 | -------------------------------------------------------------------------------- /internal/translate/chatgpt/language.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | var ( 4 | // Generated from Google 5 | lang = []string{ 6 | "Afrikaans", 7 | "Albanian", 8 | "Amharic", 9 | "Arabic", 10 | "Armenian", 11 | "Auto", 12 | "Assamese", 13 | "Aymara", 14 | "Azerbaijani", 15 | "Bambara", 16 | "Basque", 17 | "Belarusian", 18 | "Bengali", 19 | "Bhojpuri", 20 | "Bosnian", 21 | "Bulgarian", 22 | "Catalan", 23 | "Cebuano", 24 | "Chinese (Simplified)", 25 | "Chinese (Traditional)", 26 | "Corsican", 27 | "Croatian", 28 | "Czech", 29 | "Danish", 30 | "Dhivehi", 31 | "Dogri", 32 | "Dutch", 33 | "English", 34 | "Esperanto", 35 | "Estonian", 36 | "Ewe", 37 | "Filipino (Tagalog)", 38 | "Finnish", 39 | "French", 40 | "Frisian", 41 | "Galician", 42 | "Georgian", 43 | "German", 44 | "Greek", 45 | "Guarani", 46 | "Gujarati", 47 | "Haitian Creole", 48 | "Hausa", 49 | "Hawaiian", 50 | "Hebrew", 51 | "Hindi", 52 | "Hmong", 53 | "Hungarian", 54 | "Icelandic", 55 | "Igbo", 56 | "Ilocano", 57 | "Indonesian", 58 | "Irish", 59 | "Italian", 60 | "Japanese", 61 | "Javanese", 62 | "Kannada", 63 | "Kazakh", 64 | "Khmer", 65 | "Kinyarwanda", 66 | "Konkani", 67 | "Korean", 68 | "Krio", 69 | "Kurdish", 70 | "Kurdish (Sorani)", 71 | "Kyrgyz", 72 | "Lao", 73 | "Latin", 74 | "Latvian", 75 | "Lingala", 76 | "Lithuanian", 77 | "Luganda", 78 | "Luxembourgish", 79 | "Macedonian", 80 | "Maithili", 81 | "Malagasy", 82 | "Malay", 83 | "Malayalam", 84 | "Maltese", 85 | "Maori", 86 | "Marathi", 87 | "Meiteilon (Manipuri)", 88 | "Mizo", 89 | "Mongolian", 90 | "Myanmar (Burmese)", 91 | "Nepali", 92 | "Norwegian", 93 | "Nyanja (Chichewa)", 94 | "Odia (Oriya)", 95 | "Oromo", 96 | "Pashto", 97 | "Persian", 98 | "Polish", 99 | "Portuguese (Portugal, Brazil)", 100 | "Punjabi", 101 | "Quechua", 102 | "Romanian", 103 | "Russian", 104 | "Samoan", 105 | "Sanskrit", 106 | "Scots Gaelic", 107 | "Sepedi", 108 | "Serbian", 109 | "Sesotho", 110 | "Shona", 111 | "Sindhi", 112 | "Sinhala (Sinhalese)", 113 | "Slovak", 114 | "Slovenian", 115 | "Somali", 116 | "Spanish", 117 | "Sundanese", 118 | "Swahili", 119 | "Swedish", 120 | "Tagalog (Filipino)", 121 | "Tajik", 122 | "Tamil", 123 | "Tatar", 124 | "Telugu", 125 | "Thai", 126 | "Tigrinya", 127 | "Tsonga", 128 | "Turkish", 129 | "Turkmen", 130 | "Twi (Akan)", 131 | "Ukrainian", 132 | "Urdu", 133 | "Uyghur", 134 | "Uzbek", 135 | "Vietnamese", 136 | "Welsh", 137 | "Xhosa", 138 | "Yiddish", 139 | "Yoruba", 140 | "Zulu", 141 | } 142 | ) 143 | -------------------------------------------------------------------------------- /internal/translate/chatgpt/translator.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | 11 | "github.com/eeeXun/gtt/internal/translate/core" 12 | ) 13 | 14 | const ( 15 | textURL = "https://api.openai.com/v1/chat/completions" 16 | ) 17 | 18 | type Translator struct { 19 | *core.Server 20 | *core.Language 21 | *core.TTS 22 | core.EngineName 23 | } 24 | 25 | func NewTranslator() *Translator { 26 | return &Translator{ 27 | Server: new(core.Server), 28 | Language: new(core.Language), 29 | TTS: core.NewTTS(), 30 | EngineName: core.NewEngineName("ChatGPT"), 31 | } 32 | } 33 | 34 | func (t *Translator) GetAllLang() []string { 35 | return lang 36 | } 37 | 38 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 39 | translation = new(core.Translation) 40 | var data map[string]interface{} 41 | 42 | if len(t.GetAPIKey()) <= 0 { 43 | return nil, errors.New("Please write your API Key in config file for " + t.GetEngineName()) 44 | } 45 | 46 | userData, _ := json.Marshal(map[string]interface{}{ 47 | "model": "gpt-3.5-turbo", 48 | "messages": []map[string]string{{ 49 | "role": "user", 50 | "content": fmt.Sprintf( 51 | "Translate following text from %s to %s\n%s", 52 | t.GetSrcLang(), 53 | t.GetDstLang(), 54 | message, 55 | ), 56 | }}, 57 | "temperature": 0.7, 58 | }) 59 | req, _ := http.NewRequest(http.MethodPost, 60 | textURL, 61 | bytes.NewBuffer(userData), 62 | ) 63 | req.Header.Add("Content-Type", "application/json") 64 | req.Header.Add("Authorization", "Bearer "+t.GetAPIKey()) 65 | res, err := http.DefaultClient.Do(req) 66 | if err != nil { 67 | return nil, err 68 | } 69 | body, err := io.ReadAll(res.Body) 70 | if err != nil { 71 | return nil, err 72 | } 73 | if err = json.Unmarshal(body, &data); err != nil { 74 | return nil, err 75 | } 76 | 77 | if len(data) <= 0 { 78 | return nil, errors.New("Translation not found") 79 | } 80 | if data["error"] != nil { 81 | return nil, errors.New(data["error"].(map[string]interface{})["message"].(string)) 82 | } 83 | 84 | translation.TEXT = 85 | data["choices"].([]interface{})[0].(map[string]interface{})["message"].(map[string]interface{})["content"].(string) 86 | 87 | return translation, nil 88 | } 89 | 90 | func (t *Translator) PlayTTS(lang, message string) error { 91 | defer t.ReleaseLock() 92 | 93 | return errors.New(t.GetEngineName() + " does not support text to speech") 94 | } 95 | -------------------------------------------------------------------------------- /internal/translate/core/language.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type Language struct { 4 | srcLang string 5 | dstLang string 6 | } 7 | 8 | func (l *Language) GetSrcLang() string { 9 | return l.srcLang 10 | } 11 | 12 | func (l *Language) GetDstLang() string { 13 | return l.dstLang 14 | } 15 | 16 | func (l *Language) SetSrcLang(lang string) { 17 | l.srcLang = lang 18 | } 19 | 20 | func (l *Language) SetDstLang(lang string) { 21 | l.dstLang = lang 22 | } 23 | 24 | func (l *Language) SwapLang() { 25 | l.srcLang, l.dstLang = l.dstLang, l.srcLang 26 | } 27 | -------------------------------------------------------------------------------- /internal/translate/core/name.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type EngineName struct { 4 | name string 5 | } 6 | 7 | func NewEngineName(name string) EngineName { 8 | return EngineName{ 9 | name: name, 10 | } 11 | } 12 | 13 | func (e EngineName) GetEngineName() string { 14 | return e.name 15 | } 16 | -------------------------------------------------------------------------------- /internal/translate/core/server.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | type Server struct { 4 | host string 5 | apiKey string 6 | } 7 | 8 | func (s *Server) SetHost(host string) { 9 | s.host = host 10 | } 11 | 12 | func (s *Server) GetHost() string { 13 | return s.host 14 | } 15 | 16 | func (s *Server) SetAPIKey(key string) { 17 | s.apiKey = key 18 | } 19 | 20 | func (s *Server) GetAPIKey() string { 21 | return s.apiKey 22 | } 23 | -------------------------------------------------------------------------------- /internal/translate/core/tts.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/hajimehoshi/go-mp3" 8 | "github.com/hajimehoshi/oto/v2" 9 | ) 10 | 11 | type TTS struct { 12 | stop bool 13 | using bool 14 | } 15 | 16 | func NewTTS() *TTS { 17 | return &TTS{ 18 | stop: true, 19 | using: false, 20 | } 21 | } 22 | 23 | func (s *TTS) LockAvailable() bool { 24 | return s.stop && !s.using 25 | } 26 | 27 | func (s *TTS) AcquireLock() { 28 | s.stop = false 29 | s.using = true 30 | } 31 | 32 | func (s *TTS) IsStopped() bool { 33 | return s.stop 34 | } 35 | 36 | func (s *TTS) StopTTS() { 37 | s.stop = true 38 | } 39 | 40 | func (s *TTS) ReleaseLock() { 41 | s.stop = true 42 | s.using = false 43 | } 44 | 45 | func (s *TTS) Play(body io.Reader) error { 46 | decoder, err := mp3.NewDecoder(body) 47 | if err != nil { 48 | return err 49 | } 50 | otoCtx, readyChan, err := oto.NewContext(decoder.SampleRate(), 2, 2) 51 | if err != nil { 52 | return err 53 | } 54 | <-readyChan 55 | player := otoCtx.NewPlayer(decoder) 56 | player.Play() 57 | for player.IsPlaying() { 58 | if s.IsStopped() { 59 | return player.Close() 60 | } 61 | time.Sleep(time.Millisecond) 62 | } 63 | return player.Close() 64 | } 65 | -------------------------------------------------------------------------------- /internal/translate/core/utils.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | const ( 4 | UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36" 5 | ) 6 | 7 | type Translation struct { 8 | // translation text 9 | TEXT string 10 | 11 | // translation definition or example 12 | DEF string 13 | 14 | // translation part of speech 15 | POS string 16 | } 17 | -------------------------------------------------------------------------------- /internal/translate/deepl/language.go: -------------------------------------------------------------------------------- 1 | package deepl 2 | 3 | // https://www.deepl.com/docs-api/translate-text 4 | // TODO: Retrieve from DeepL API (https://www.deepl.com/docs-api/general/get-languages/) 5 | var ( 6 | lang = []string{ 7 | "Bulgarian", 8 | "Chinese", 9 | "Czech", 10 | "Danish", 11 | "Dutch", 12 | "English", 13 | "Estonian", 14 | "Finnish", 15 | "French", 16 | "German", 17 | "Greek", 18 | "Hungarian", 19 | "Indonesian", 20 | "Italian", 21 | "Japanese", 22 | "Korean", 23 | "Latvian", 24 | "Lithuanian", 25 | "Norwegian", 26 | "Polish", 27 | "Portuguese", 28 | "Romanian", 29 | "Russian", 30 | "Slovak", 31 | "Slovenian", 32 | "Spanish", 33 | "Swedish", 34 | "Turkish", 35 | "Ukrainian", 36 | } 37 | langCode = map[string]string{ 38 | "Bulgarian": "BG", 39 | "Chinese": "ZH", 40 | "Czech": "CS", 41 | "Danish": "DA", 42 | "Dutch": "NL", 43 | "English": "EN", 44 | "Estonian": "ET", 45 | "Finnish": "FI", 46 | "French": "FR", 47 | "German": "DE", 48 | "Greek": "EL", 49 | "Hungarian": "HU", 50 | "Indonesian": "ID", 51 | "Italian": "IT", 52 | "Japanese": "JA", 53 | "Korean": "KO", 54 | "Latvian": "LV", 55 | "Lithuanian": "LT", 56 | "Norwegian": "NB", 57 | "Polish": "PL", 58 | "Portuguese": "PT", 59 | "Romanian": "RO", 60 | "Russian": "RU", 61 | "Slovak": "SK", 62 | "Slovenian": "SL", 63 | "Spanish": "ES", 64 | "Swedish": "SV", 65 | "Turkish": "TR", 66 | "Ukrainian": "UK", 67 | } 68 | ) 69 | -------------------------------------------------------------------------------- /internal/translate/deepl/translator.go: -------------------------------------------------------------------------------- 1 | package deepl 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | 12 | "github.com/eeeXun/gtt/internal/translate/core" 13 | ) 14 | 15 | const ( 16 | textURL = "https://api-free.deepl.com/v2/translate" 17 | ) 18 | 19 | type Translator struct { 20 | *core.Server 21 | *core.Language 22 | *core.TTS 23 | core.EngineName 24 | } 25 | 26 | func NewTranslator(name string) *Translator { 27 | return &Translator{ 28 | Server: new(core.Server), 29 | Language: new(core.Language), 30 | TTS: core.NewTTS(), 31 | EngineName: core.NewEngineName(name), 32 | } 33 | } 34 | 35 | func (t *Translator) GetAllLang() []string { 36 | return lang 37 | } 38 | 39 | func (t *Translator) deeplTranslate(message string) (translation *core.Translation, err error) { 40 | translation = new(core.Translation) 41 | var data map[string]interface{} 42 | 43 | if len(t.GetAPIKey()) <= 0 { 44 | return nil, errors.New("Please write your API Key in config file for " + t.GetEngineName()) 45 | } 46 | 47 | userData := url.Values{ 48 | "text": {message}, 49 | "source_lang": {langCode[t.GetSrcLang()]}, 50 | "target_lang": {langCode[t.GetDstLang()]}, 51 | } 52 | req, _ := http.NewRequest(http.MethodPost, 53 | textURL, 54 | strings.NewReader(userData.Encode()), 55 | ) 56 | req.Header.Add("Authorization", "DeepL-Auth-Key "+t.GetAPIKey()) 57 | req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 58 | res, err := http.DefaultClient.Do(req) 59 | if err != nil { 60 | return nil, err 61 | } 62 | body, err := io.ReadAll(res.Body) 63 | if err != nil { 64 | return nil, err 65 | } 66 | if err = json.Unmarshal(body, &data); err != nil { 67 | return nil, err 68 | } 69 | 70 | if len(data) <= 0 { 71 | return nil, errors.New("translation not found") 72 | } 73 | 74 | translation.TEXT = data["translations"].([]interface{})[0].(map[string]interface{})["text"].(string) 75 | 76 | return translation, nil 77 | } 78 | 79 | func (t *Translator) deeplxTranslate(message string) (translation *core.Translation, err error) { 80 | translation = new(core.Translation) 81 | var data map[string]interface{} 82 | 83 | if len(t.GetHost()) <= 0 { 84 | return nil, errors.New("Please write your host in config file for " + t.GetEngineName()) 85 | } 86 | 87 | userData, _ := json.Marshal(map[string]interface{}{ 88 | "text": message, 89 | "source_lang": langCode[t.GetSrcLang()], 90 | "target_lang": langCode[t.GetDstLang()], 91 | }) 92 | req, _ := http.NewRequest(http.MethodPost, 93 | "http://"+t.GetHost()+"/translate", 94 | bytes.NewBuffer(userData), 95 | ) 96 | req.Header.Add("Content-Type", "application/json") 97 | req.Header.Add("Authorization", "Bearer "+t.GetAPIKey()) 98 | res, err := http.DefaultClient.Do(req) 99 | if err != nil { 100 | return nil, err 101 | } 102 | body, err := io.ReadAll(res.Body) 103 | if err != nil { 104 | return nil, err 105 | } 106 | if err = json.Unmarshal(body, &data); err != nil { 107 | return nil, err 108 | } 109 | 110 | if len(data) <= 0 { 111 | return nil, errors.New("Translation not found") 112 | } 113 | if res.StatusCode != 200 { 114 | return nil, errors.New(data["message"].(string)) 115 | } 116 | 117 | translation.TEXT = data["data"].(string) 118 | 119 | return translation, nil 120 | } 121 | 122 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 123 | switch t.GetEngineName() { 124 | case "DeepLX": 125 | return t.deeplxTranslate(message) 126 | default: 127 | return t.deeplTranslate(message) 128 | } 129 | } 130 | 131 | func (t *Translator) PlayTTS(lang, message string) error { 132 | defer t.ReleaseLock() 133 | 134 | return errors.New(t.GetEngineName() + " does not support text to speech") 135 | } 136 | -------------------------------------------------------------------------------- /internal/translate/google/language.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | // https://cloud.google.com/translate/docs/languages 4 | var ( 5 | lang = []string{ 6 | "Afrikaans", 7 | "Albanian", 8 | "Amharic", 9 | "Arabic", 10 | "Armenian", 11 | "Auto", 12 | "Assamese", 13 | "Aymara", 14 | "Azerbaijani", 15 | "Bambara", 16 | "Basque", 17 | "Belarusian", 18 | "Bengali", 19 | "Bhojpuri", 20 | "Bosnian", 21 | "Bulgarian", 22 | "Catalan", 23 | "Cebuano", 24 | "Chinese (Simplified)", 25 | "Chinese (Traditional)", 26 | "Corsican", 27 | "Croatian", 28 | "Czech", 29 | "Danish", 30 | "Dhivehi", 31 | "Dogri", 32 | "Dutch", 33 | "English", 34 | "Esperanto", 35 | "Estonian", 36 | "Ewe", 37 | "Filipino (Tagalog)", 38 | "Finnish", 39 | "French", 40 | "Frisian", 41 | "Galician", 42 | "Georgian", 43 | "German", 44 | "Greek", 45 | "Guarani", 46 | "Gujarati", 47 | "Haitian Creole", 48 | "Hausa", 49 | "Hawaiian", 50 | "Hebrew", 51 | "Hindi", 52 | "Hmong", 53 | "Hungarian", 54 | "Icelandic", 55 | "Igbo", 56 | "Ilocano", 57 | "Indonesian", 58 | "Irish", 59 | "Italian", 60 | "Japanese", 61 | "Javanese", 62 | "Kannada", 63 | "Kazakh", 64 | "Khmer", 65 | "Kinyarwanda", 66 | "Konkani", 67 | "Korean", 68 | "Krio", 69 | "Kurdish", 70 | "Kurdish (Sorani)", 71 | "Kyrgyz", 72 | "Lao", 73 | "Latin", 74 | "Latvian", 75 | "Lingala", 76 | "Lithuanian", 77 | "Luganda", 78 | "Luxembourgish", 79 | "Macedonian", 80 | "Maithili", 81 | "Malagasy", 82 | "Malay", 83 | "Malayalam", 84 | "Maltese", 85 | "Maori", 86 | "Marathi", 87 | "Meiteilon (Manipuri)", 88 | "Mizo", 89 | "Mongolian", 90 | "Myanmar (Burmese)", 91 | "Nepali", 92 | "Norwegian", 93 | "Nyanja (Chichewa)", 94 | "Odia (Oriya)", 95 | "Oromo", 96 | "Pashto", 97 | "Persian", 98 | "Polish", 99 | "Portuguese (Portugal, Brazil)", 100 | "Punjabi", 101 | "Quechua", 102 | "Romanian", 103 | "Russian", 104 | "Samoan", 105 | "Sanskrit", 106 | "Scots Gaelic", 107 | "Sepedi", 108 | "Serbian", 109 | "Sesotho", 110 | "Shona", 111 | "Sindhi", 112 | "Sinhala (Sinhalese)", 113 | "Slovak", 114 | "Slovenian", 115 | "Somali", 116 | "Spanish", 117 | "Sundanese", 118 | "Swahili", 119 | "Swedish", 120 | "Tagalog (Filipino)", 121 | "Tajik", 122 | "Tamil", 123 | "Tatar", 124 | "Telugu", 125 | "Thai", 126 | "Tigrinya", 127 | "Tsonga", 128 | "Turkish", 129 | "Turkmen", 130 | "Twi (Akan)", 131 | "Ukrainian", 132 | "Urdu", 133 | "Uyghur", 134 | "Uzbek", 135 | "Vietnamese", 136 | "Welsh", 137 | "Xhosa", 138 | "Yiddish", 139 | "Yoruba", 140 | "Zulu", 141 | } 142 | langCode = map[string]string{ 143 | "Afrikaans": "af", 144 | "Albanian": "sq", 145 | "Amharic": "am", 146 | "Arabic": "ar", 147 | "Armenian": "hy", 148 | "Auto": "auto", 149 | "Assamese": "as", 150 | "Aymara": "ay", 151 | "Azerbaijani": "az", 152 | "Bambara": "bm", 153 | "Basque": "eu", 154 | "Belarusian": "be", 155 | "Bengali": "bn", 156 | "Bhojpuri": "bho", 157 | "Bosnian": "bs", 158 | "Bulgarian": "bg", 159 | "Catalan": "ca", 160 | "Cebuano": "ceb", 161 | "Chinese (Simplified)": "zh-CN", 162 | "Chinese (Traditional)": "zh-TW", 163 | "Corsican": "co", 164 | "Croatian": "hr", 165 | "Czech": "cs", 166 | "Danish": "da", 167 | "Dhivehi": "dv", 168 | "Dogri": "doi", 169 | "Dutch": "nl", 170 | "English": "en", 171 | "Esperanto": "eo", 172 | "Estonian": "et", 173 | "Ewe": "ee", 174 | "Filipino (Tagalog)": "fil", 175 | "Finnish": "fi", 176 | "French": "fr", 177 | "Frisian": "fy", 178 | "Galician": "gl", 179 | "Georgian": "ka", 180 | "German": "de", 181 | "Greek": "el", 182 | "Guarani": "gn", 183 | "Gujarati": "gu", 184 | "Haitian Creole": "ht", 185 | "Hausa": "ha", 186 | "Hawaiian": "haw", 187 | "Hebrew": "he", 188 | "Hindi": "hi", 189 | "Hmong": "hmn", 190 | "Hungarian": "hu", 191 | "Icelandic": "is", 192 | "Igbo": "ig", 193 | "Ilocano": "ilo", 194 | "Indonesian": "id", 195 | "Irish": "ga", 196 | "Italian": "it", 197 | "Japanese": "ja", 198 | "Javanese": "jv", 199 | "Kannada": "kn", 200 | "Kazakh": "kk", 201 | "Khmer": "km", 202 | "Kinyarwanda": "rw", 203 | "Konkani": "gom", 204 | "Korean": "ko", 205 | "Krio": "kri", 206 | "Kurdish": "ku", 207 | "Kurdish (Sorani)": "ckb", 208 | "Kyrgyz": "ky", 209 | "Lao": "lo", 210 | "Latin": "la", 211 | "Latvian": "lv", 212 | "Lingala": "ln", 213 | "Lithuanian": "lt", 214 | "Luganda": "lg", 215 | "Luxembourgish": "lb", 216 | "Macedonian": "mk", 217 | "Maithili": "mai", 218 | "Malagasy": "mg", 219 | "Malay": "ms", 220 | "Malayalam": "ml", 221 | "Maltese": "mt", 222 | "Maori": "mi", 223 | "Marathi": "mr", 224 | "Meiteilon (Manipuri)": "mni-Mtei", 225 | "Mizo": "lus", 226 | "Mongolian": "mn", 227 | "Myanmar (Burmese)": "my", 228 | "Nepali": "ne", 229 | "Norwegian": "no", 230 | "Nyanja (Chichewa)": "ny", 231 | "Odia (Oriya)": "or", 232 | "Oromo": "om", 233 | "Pashto": "ps", 234 | "Persian": "fa", 235 | "Polish": "pl", 236 | "Portuguese (Portugal, Brazil)": "pt", 237 | "Punjabi": "pa", 238 | "Quechua": "qu", 239 | "Romanian": "ro", 240 | "Russian": "ru", 241 | "Samoan": "sm", 242 | "Sanskrit": "sa", 243 | "Scots Gaelic": "gd", 244 | "Sepedi": "nso", 245 | "Serbian": "sr", 246 | "Sesotho": "st", 247 | "Shona": "sn", 248 | "Sindhi": "sd", 249 | "Sinhala (Sinhalese)": "si", 250 | "Slovak": "sk", 251 | "Slovenian": "sl", 252 | "Somali": "so", 253 | "Spanish": "es", 254 | "Sundanese": "su", 255 | "Swahili": "sw", 256 | "Swedish": "sv", 257 | "Tagalog (Filipino)": "tl", 258 | "Tajik": "tg", 259 | "Tamil": "ta", 260 | "Tatar": "tt", 261 | "Telugu": "te", 262 | "Thai": "th", 263 | "Tigrinya": "ti", 264 | "Tsonga": "ts", 265 | "Turkish": "tr", 266 | "Turkmen": "tk", 267 | "Twi (Akan)": "ak", 268 | "Ukrainian": "uk", 269 | "Urdu": "ur", 270 | "Uyghur": "ug", 271 | "Uzbek": "uz", 272 | "Vietnamese": "vi", 273 | "Welsh": "cy", 274 | "Xhosa": "xh", 275 | "Yiddish": "yi", 276 | "Yoruba": "yo", 277 | "Zulu": "zu", 278 | } 279 | ) 280 | -------------------------------------------------------------------------------- /internal/translate/google/translator.go: -------------------------------------------------------------------------------- 1 | package google 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/url" 10 | 11 | "github.com/eeeXun/gtt/internal/translate/core" 12 | ) 13 | 14 | const ( 15 | textURL = "https://translate.googleapis.com/translate_a/single?client=gtx&dt=t&dt=bd&dt=md&dt=ex&sl=%s&tl=%s&q=%s" 16 | ttsURL = "https://translate.google.com.vn/translate_tts?ie=UTF-8&q=%s&tl=%s&client=tw-ob" 17 | ) 18 | 19 | type Translator struct { 20 | *core.Server 21 | *core.Language 22 | *core.TTS 23 | core.EngineName 24 | } 25 | 26 | func NewTranslator() *Translator { 27 | return &Translator{ 28 | Server: new(core.Server), 29 | Language: new(core.Language), 30 | TTS: core.NewTTS(), 31 | EngineName: core.NewEngineName("Google"), 32 | } 33 | } 34 | 35 | func (t *Translator) GetAllLang() []string { 36 | return lang 37 | } 38 | 39 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 40 | translation = new(core.Translation) 41 | var data []interface{} 42 | 43 | urlStr := fmt.Sprintf( 44 | textURL, 45 | langCode[t.GetSrcLang()], 46 | langCode[t.GetDstLang()], 47 | url.QueryEscape(message), 48 | ) 49 | res, err := http.Get(urlStr) 50 | if err != nil { 51 | return nil, err 52 | } 53 | body, err := io.ReadAll(res.Body) 54 | if err != nil { 55 | return nil, err 56 | } 57 | if err = json.Unmarshal(body, &data); err != nil { 58 | return nil, err 59 | } 60 | 61 | if len(data) <= 0 { 62 | return nil, errors.New("Translation not found") 63 | } 64 | 65 | // translation = data[0] 66 | for _, line := range data[0].([]interface{}) { 67 | translatedLine := line.([]interface{})[0] 68 | translation.TEXT += translatedLine.(string) 69 | } 70 | // part of speech = data[1] 71 | if data[1] != nil { 72 | for _, partOfSpeeches := range data[1].([]interface{}) { 73 | partOfSpeeches := partOfSpeeches.([]interface{}) 74 | // part of speech 75 | pos := partOfSpeeches[0] 76 | translation.POS += fmt.Sprintf("[%v]\n", pos) 77 | for _, words := range partOfSpeeches[2].([]interface{}) { 78 | words := words.([]interface{}) 79 | // dst lang 80 | dstWord := words[0] 81 | translation.POS += fmt.Sprintf("\t%v:", dstWord) 82 | // src lang 83 | firstWord := true 84 | for _, word := range words[1].([]interface{}) { 85 | if firstWord { 86 | translation.POS += fmt.Sprintf(" %v", word) 87 | firstWord = false 88 | } else { 89 | translation.POS += fmt.Sprintf(", %v", word) 90 | } 91 | } 92 | translation.POS += "\n" 93 | } 94 | } 95 | } 96 | // definition = data[12] 97 | if len(data) >= 13 && data[12] != nil { 98 | for _, definitions := range data[12].([]interface{}) { 99 | definitions := definitions.([]interface{}) 100 | // part of speech 101 | pos := definitions[0] 102 | translation.DEF += fmt.Sprintf("[%v]\n", pos) 103 | for _, sentences := range definitions[1].([]interface{}) { 104 | sentences := sentences.([]interface{}) 105 | // definition 106 | def := sentences[0] 107 | translation.DEF += fmt.Sprintf("\t- %v\n", def) 108 | // example sentence 109 | if len(sentences) >= 3 && sentences[2] != nil { 110 | example := sentences[2] 111 | translation.DEF += fmt.Sprintf("\t\t\"%v\"\n", example) 112 | } 113 | } 114 | } 115 | } 116 | 117 | return translation, nil 118 | } 119 | 120 | func (t *Translator) PlayTTS(lang, message string) error { 121 | defer t.ReleaseLock() 122 | 123 | urlStr := fmt.Sprintf( 124 | ttsURL, 125 | url.QueryEscape(message), 126 | langCode[lang], 127 | ) 128 | res, err := http.Get(urlStr) 129 | if err != nil { 130 | return err 131 | } 132 | if res.StatusCode == 400 { 133 | return errors.New(t.GetEngineName() + " does not support text to speech of " + lang) 134 | } 135 | return t.Play(res.Body) 136 | } 137 | -------------------------------------------------------------------------------- /internal/translate/libre/language.go: -------------------------------------------------------------------------------- 1 | package libre 2 | 3 | var ( 4 | lang = []string{ 5 | "Albanian", 6 | "Arabic", 7 | "Auto", 8 | "Azerbaijani", 9 | "Bengali", 10 | "Bulgarian", 11 | "Catalan", 12 | "Chinese", 13 | "Chinese (traditional)", 14 | "Czech", 15 | "Danish", 16 | "Dutch", 17 | "English", 18 | "Esperanto", 19 | "Estonian", 20 | "Finnish", 21 | "French", 22 | "German", 23 | "Greek", 24 | "Hebrew", 25 | "Hindi", 26 | "Hungarian", 27 | "Indonesian", 28 | "Irish", 29 | "Italian", 30 | "Japanese", 31 | "Korean", 32 | "Latvian", 33 | "Lithuanian", 34 | "Malay", 35 | "Norwegian", 36 | "Persian", 37 | "Polish", 38 | "Portuguese", 39 | "Romanian", 40 | "Russian", 41 | "Slovak", 42 | "Slovenian", 43 | "Spanish", 44 | "Swedish", 45 | "Tagalog", 46 | "Thai", 47 | "Turkish", 48 | "Ukranian", 49 | "Urdu", 50 | } 51 | langCode = map[string]string{ 52 | "Albanian": "sq", 53 | "Arabic": "ar", 54 | "Auto": "auto", 55 | "Azerbaijani": "az", 56 | "Bengali": "bn", 57 | "Bulgarian": "bg", 58 | "Catalan": "ca", 59 | "Chinese": "zh", 60 | "Chinese (traditional)": "zt", 61 | "Czech": "cs", 62 | "Danish": "da", 63 | "Dutch": "nl", 64 | "English": "en", 65 | "Esperanto": "eo", 66 | "Estonian": "et", 67 | "Finnish": "fi", 68 | "French": "fr", 69 | "German": "de", 70 | "Greek": "el", 71 | "Hebrew": "he", 72 | "Hindi": "hi", 73 | "Hungarian": "hu", 74 | "Indonesian": "id", 75 | "Irish": "ga", 76 | "Italian": "it", 77 | "Japanese": "ja", 78 | "Korean": "ko", 79 | "Latvian": "lv", 80 | "Lithuanian": "lt", 81 | "Malay": "ms", 82 | "Norwegian": "nb", 83 | "Persian": "fa", 84 | "Polish": "pl", 85 | "Portuguese": "pt", 86 | "Romanian": "ro", 87 | "Russian": "ru", 88 | "Slovak": "sk", 89 | "Slovenian": "sl", 90 | "Spanish": "es", 91 | "Swedish": "sv", 92 | "Tagalog": "tl", 93 | "Thai": "th", 94 | "Turkish": "tr", 95 | "Ukranian": "uk", 96 | "Urdu": "ur", 97 | } 98 | ) 99 | -------------------------------------------------------------------------------- /internal/translate/libre/translator.go: -------------------------------------------------------------------------------- 1 | package libre 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/eeeXun/gtt/internal/translate/core" 11 | ) 12 | 13 | const ( 14 | defaultURL = "https://libretranslate.com/translate" 15 | ) 16 | 17 | type Translator struct { 18 | *core.Server 19 | *core.Language 20 | *core.TTS 21 | core.EngineName 22 | } 23 | 24 | func NewTranslator() *Translator { 25 | return &Translator{ 26 | Server: new(core.Server), 27 | Language: new(core.Language), 28 | TTS: core.NewTTS(), 29 | EngineName: core.NewEngineName("Libre"), 30 | } 31 | } 32 | 33 | func (t *Translator) GetAllLang() []string { 34 | return lang 35 | } 36 | 37 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 38 | translation = new(core.Translation) 39 | var data map[string]interface{} 40 | 41 | var textURL string 42 | if len(t.GetHost()) > 0 { 43 | textURL = "http://" + t.GetHost() + "/translate" 44 | } else { 45 | textURL = defaultURL 46 | } 47 | 48 | res, err := http.PostForm(textURL, 49 | url.Values{ 50 | "q": {message}, 51 | "source": {langCode[t.GetSrcLang()]}, 52 | "target": {langCode[t.GetDstLang()]}, 53 | "api_key": {t.GetAPIKey()}, 54 | }) 55 | if err != nil { 56 | return nil, err 57 | } 58 | body, err := io.ReadAll(res.Body) 59 | if err != nil { 60 | return nil, err 61 | } 62 | if err = json.Unmarshal(body, &data); err != nil { 63 | return nil, err 64 | } 65 | 66 | if len(data) <= 0 { 67 | return nil, errors.New("Translation not found") 68 | } 69 | if res.StatusCode != 200 { 70 | return nil, errors.New(data["error"].(string)) 71 | } 72 | 73 | translation.TEXT = data["translatedText"].(string) 74 | 75 | return translation, nil 76 | } 77 | 78 | func (t *Translator) PlayTTS(lang, message string) error { 79 | defer t.ReleaseLock() 80 | 81 | return errors.New(t.GetEngineName() + " does not support text to speech") 82 | } 83 | -------------------------------------------------------------------------------- /internal/translate/reverso/language.go: -------------------------------------------------------------------------------- 1 | package reverso 2 | 3 | var ( 4 | lang = []string{ 5 | "Arabic", 6 | "Chinese (Simplified)", 7 | "Czech", 8 | "Danish", 9 | "Dutch", 10 | "English", 11 | "French", 12 | "German", 13 | "Greek", 14 | "Hebrew", 15 | "Hindi", 16 | "Hungarian", 17 | "Italian", 18 | "Japanese", 19 | "Korean", 20 | "Persian", 21 | "Polish", 22 | "Portuguese", 23 | "Romanian", 24 | "Russian", 25 | "Slovak", 26 | "Spanish", 27 | "Swedish", 28 | "Thai", 29 | "Turkish", 30 | "Ukrainian", 31 | } 32 | langCode = map[string]string{ 33 | "Arabic": "ara", 34 | "Chinese (Simplified)": "chi", 35 | "Czech": "cze", 36 | "Danish": "dan", 37 | "Dutch": "dut", 38 | "English": "eng", 39 | "French": "fra", 40 | "German": "ger", 41 | "Greek": "gre", 42 | "Hebrew": "heb", 43 | "Hindi": "hin", 44 | "Hungarian": "hun", 45 | "Italian": "ita", 46 | "Japanese": "jpn", 47 | "Korean": "kor", 48 | "Persian": "per", 49 | "Polish": "pol", 50 | "Portuguese": "por", 51 | "Romanian": "rum", 52 | "Russian": "rus", 53 | "Slovak": "slo", 54 | "Spanish": "spa", 55 | "Swedish": "swe", 56 | "Thai": "tha", 57 | "Turkish": "tur", 58 | "Ukrainian": "ukr", 59 | } 60 | voiceName = map[string]string{ 61 | "Arabic": "Mehdi22k", 62 | "Chinese (Simplified)": "Lulu22k", 63 | "Czech": "Eliska22k", 64 | "Danish": "Mette22k", 65 | "Dutch": "Sofie22k", 66 | "English": "Heather22k", 67 | "French": "Alice22k", 68 | "German": "Andreas22k", 69 | "Greek": "Dimitris22k", 70 | "Hebrew": "he-IL-Asaf", 71 | "Italian": "Fabiana22k", 72 | "Japanese": "Sakura22k", 73 | "Korean": "Minji22k", 74 | "Polish": "Monika22k", 75 | "Portuguese": "Celia22k", 76 | "Romanian": "ro-RO-Andrei", 77 | "Russian": "Alyona22k", 78 | "Spanish": "Antonio22k", 79 | "Swedish": "Emma22k", 80 | "Turkish": "Ipek22k", 81 | } 82 | ) 83 | -------------------------------------------------------------------------------- /internal/translate/reverso/translator.go: -------------------------------------------------------------------------------- 1 | package reverso 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "regexp" 12 | 13 | "github.com/eeeXun/gtt/internal/translate/core" 14 | ) 15 | 16 | const ( 17 | textURL = "https://api.reverso.net/translate/v1/translation" 18 | ttsURL = "https://voice.reverso.net/RestPronunciation.svc/v1/output=json/GetVoiceStream/voiceName=%s?voiceSpeed=80&inputText=%s" 19 | ) 20 | 21 | type Translator struct { 22 | *core.Server 23 | *core.Language 24 | *core.TTS 25 | core.EngineName 26 | } 27 | 28 | func NewTranslator() *Translator { 29 | return &Translator{ 30 | Server: new(core.Server), 31 | Language: new(core.Language), 32 | TTS: core.NewTTS(), 33 | EngineName: core.NewEngineName("Reverso"), 34 | } 35 | } 36 | 37 | func (t *Translator) GetAllLang() []string { 38 | return lang 39 | } 40 | 41 | func (t *Translator) Translate(message string) (translation *core.Translation, err error) { 42 | translation = new(core.Translation) 43 | var data map[string]interface{} 44 | 45 | if t.GetSrcLang() == t.GetDstLang() { 46 | return nil, errors.New( 47 | fmt.Sprintf("%s doesn't support translation of the same language.\ni.e. %s to %s", 48 | t.GetEngineName(), t.GetSrcLang(), t.GetDstLang())) 49 | } 50 | 51 | userData, _ := json.Marshal(map[string]interface{}{ 52 | "format": "text", 53 | "from": langCode[t.GetSrcLang()], 54 | "to": langCode[t.GetDstLang()], 55 | "input": message, 56 | "options": map[string]string{ 57 | "sentenceSplitter": "true", 58 | "origin": "translation.web", 59 | "contextResults": "true", 60 | "languageDetection": "false", 61 | }, 62 | }) 63 | req, _ := http.NewRequest(http.MethodPost, 64 | textURL, 65 | bytes.NewBuffer(userData)) 66 | req.Header.Add("Content-Type", "application/json") 67 | req.Header.Add("User-Agent", core.UserAgent) 68 | res, err := http.DefaultClient.Do(req) 69 | if err != nil { 70 | return nil, err 71 | } 72 | body, err := io.ReadAll(res.Body) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if err = json.Unmarshal(body, &data); err != nil { 77 | return nil, err 78 | } 79 | 80 | if len(data) <= 0 { 81 | return nil, errors.New("Translation not found") 82 | } 83 | 84 | // translation 85 | for _, line := range data["translation"].([]interface{}) { 86 | translation.TEXT += line.(string) 87 | } 88 | // definition and part of speech 89 | if data["contextResults"] != nil { 90 | for _, results := range data["contextResults"].(map[string]interface{})["results"].([]interface{}) { 91 | results := results.(map[string]interface{}) 92 | // definition 93 | srcExample := results["sourceExamples"].([]interface{}) 94 | dstExample := results["targetExamples"].([]interface{}) 95 | if len(srcExample) > 0 && len(dstExample) > 0 { 96 | for i := 0; i < len(srcExample) && i < len(dstExample); i++ { 97 | translation.DEF += fmt.Sprintf("- %v\n\t\"%v\"\n", srcExample[i], dstExample[i]) 98 | } 99 | } 100 | // part of speech 101 | if results["partOfSpeech"] == nil { 102 | translation.POS += fmt.Sprintf("%v\n", results["translation"]) 103 | } else { 104 | translation.POS += fmt.Sprintf("%v [%v]\n", results["translation"], results["partOfSpeech"]) 105 | } 106 | } 107 | translation.DEF = regexp.MustCompile("<(|/)em>").ReplaceAllString(translation.DEF, "") 108 | } 109 | 110 | return translation, nil 111 | } 112 | 113 | func (t *Translator) PlayTTS(lang, message string) error { 114 | defer t.ReleaseLock() 115 | 116 | name, ok := voiceName[lang] 117 | if !ok { 118 | return errors.New(t.GetEngineName() + " does not support text to speech of " + lang) 119 | } 120 | urlStr := fmt.Sprintf( 121 | ttsURL, 122 | name, 123 | base64.StdEncoding.EncodeToString([]byte(message)), 124 | ) 125 | req, _ := http.NewRequest("GET", urlStr, nil) 126 | req.Header.Add("User-Agent", core.UserAgent) 127 | res, err := http.DefaultClient.Do(req) 128 | if err != nil { 129 | return err 130 | } 131 | return t.Play(res.Body) 132 | } 133 | -------------------------------------------------------------------------------- /internal/translate/translator.go: -------------------------------------------------------------------------------- 1 | package translate 2 | 3 | import ( 4 | "github.com/eeeXun/gtt/internal/translate/apertium" 5 | "github.com/eeeXun/gtt/internal/translate/bing" 6 | "github.com/eeeXun/gtt/internal/translate/chatgpt" 7 | "github.com/eeeXun/gtt/internal/translate/core" 8 | "github.com/eeeXun/gtt/internal/translate/deepl" 9 | "github.com/eeeXun/gtt/internal/translate/google" 10 | "github.com/eeeXun/gtt/internal/translate/libre" 11 | "github.com/eeeXun/gtt/internal/translate/reverso" 12 | ) 13 | 14 | var ( 15 | AllTranslator = []string{ 16 | "Apertium", 17 | "Bing", 18 | "ChatGPT", 19 | "DeepL", 20 | "DeepLX", 21 | "Google", 22 | "Libre", 23 | "Reverso", 24 | } 25 | ) 26 | 27 | type Translator interface { 28 | // Get engine name of the translator 29 | GetEngineName() string 30 | 31 | // Get all languages of the translator 32 | GetAllLang() []string 33 | 34 | // Get source language of the translator 35 | GetSrcLang() string 36 | 37 | // Get destination language of the translator 38 | GetDstLang() string 39 | 40 | // Set source language of the translator 41 | SetSrcLang(lang string) 42 | 43 | // Set destination language of the translator 44 | SetDstLang(lang string) 45 | 46 | // Swap source and destination language of the translator 47 | SwapLang() 48 | 49 | // Set API Key 50 | SetAPIKey(key string) 51 | 52 | // Set host 53 | SetHost(host string) 54 | 55 | // Check if lock is available 56 | LockAvailable() bool 57 | 58 | // Acquire the lock 59 | AcquireLock() 60 | 61 | // Stop text to speech 62 | StopTTS() 63 | 64 | // Translate from source to destination language 65 | Translate(message string) (translation *core.Translation, err error) 66 | 67 | // Play text to speech 68 | PlayTTS(lang, message string) error 69 | } 70 | 71 | func NewTranslator(name string) Translator { 72 | var translator Translator 73 | 74 | switch name { 75 | case "Apertium": 76 | translator = apertium.NewTranslator() 77 | case "Bing": 78 | translator = bing.NewTranslator() 79 | case "ChatGPT": 80 | translator = chatgpt.NewTranslator() 81 | case "DeepL", "DeepLX": 82 | translator = deepl.NewTranslator(name) 83 | case "Google": 84 | translator = google.NewTranslator() 85 | case "Libre": 86 | translator = libre.NewTranslator() 87 | case "Reverso": 88 | translator = reverso.NewTranslator() 89 | } 90 | 91 | return translator 92 | } 93 | -------------------------------------------------------------------------------- /internal/ui/cycle.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "github.com/rivo/tview" 5 | ) 6 | 7 | type UICycle struct { 8 | widget []tview.Primitive 9 | index int8 10 | len int8 11 | } 12 | 13 | func NewUICycle(widgets ...tview.Primitive) *UICycle { 14 | var w []tview.Primitive 15 | 16 | for _, widget := range widgets { 17 | w = append(w, widget) 18 | } 19 | 20 | return &UICycle{ 21 | widget: w, 22 | index: 0, 23 | len: int8(len(w)), 24 | } 25 | } 26 | 27 | func (ui *UICycle) Increase() { 28 | ui.index = (ui.index + 1) % ui.len 29 | } 30 | 31 | func (ui *UICycle) Decrease() { 32 | ui.index = ((ui.index-1)%ui.len + ui.len) % ui.len 33 | } 34 | 35 | func (ui *UICycle) GetCurrentUI() tview.Primitive { 36 | return ui.widget[ui.index] 37 | } 38 | -------------------------------------------------------------------------------- /key.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gdamore/tcell/v2" 5 | ) 6 | 7 | var keyNames = map[tcell.Key]string{ 8 | tcell.KeyF1: "F1", 9 | tcell.KeyF2: "F2", 10 | tcell.KeyF3: "F3", 11 | tcell.KeyF4: "F4", 12 | tcell.KeyF5: "F5", 13 | tcell.KeyF6: "F6", 14 | tcell.KeyF7: "F7", 15 | tcell.KeyF8: "F8", 16 | tcell.KeyF9: "F9", 17 | tcell.KeyF10: "F10", 18 | tcell.KeyF11: "F11", 19 | tcell.KeyF12: "F12", 20 | tcell.KeyF13: "F13", 21 | tcell.KeyF14: "F14", 22 | tcell.KeyF15: "F15", 23 | tcell.KeyF16: "F16", 24 | tcell.KeyF17: "F17", 25 | tcell.KeyF18: "F18", 26 | tcell.KeyF19: "F19", 27 | tcell.KeyF20: "F20", 28 | tcell.KeyF21: "F21", 29 | tcell.KeyF22: "F22", 30 | tcell.KeyF23: "F23", 31 | tcell.KeyF24: "F24", 32 | tcell.KeyF25: "F25", 33 | tcell.KeyF26: "F26", 34 | tcell.KeyF27: "F27", 35 | tcell.KeyF28: "F28", 36 | tcell.KeyF29: "F29", 37 | tcell.KeyF30: "F30", 38 | tcell.KeyF31: "F31", 39 | tcell.KeyF32: "F32", 40 | tcell.KeyF33: "F33", 41 | tcell.KeyF34: "F34", 42 | tcell.KeyF35: "F35", 43 | tcell.KeyF36: "F36", 44 | tcell.KeyF37: "F37", 45 | tcell.KeyF38: "F38", 46 | tcell.KeyF39: "F39", 47 | tcell.KeyF40: "F40", 48 | tcell.KeyF41: "F41", 49 | tcell.KeyF42: "F42", 50 | tcell.KeyF43: "F43", 51 | tcell.KeyF44: "F44", 52 | tcell.KeyF45: "F45", 53 | tcell.KeyF46: "F46", 54 | tcell.KeyF47: "F47", 55 | tcell.KeyF48: "F48", 56 | tcell.KeyF49: "F49", 57 | tcell.KeyF50: "F50", 58 | tcell.KeyF51: "F51", 59 | tcell.KeyF52: "F52", 60 | tcell.KeyF53: "F53", 61 | tcell.KeyF54: "F54", 62 | tcell.KeyF55: "F55", 63 | tcell.KeyF56: "F56", 64 | tcell.KeyF57: "F57", 65 | tcell.KeyF58: "F58", 66 | tcell.KeyF59: "F59", 67 | tcell.KeyF60: "F60", 68 | tcell.KeyF61: "F61", 69 | tcell.KeyF62: "F62", 70 | tcell.KeyF63: "F63", 71 | tcell.KeyF64: "F64", 72 | tcell.KeyCtrlA: "C-a", 73 | tcell.KeyCtrlB: "C-b", 74 | tcell.KeyCtrlC: "C-c", 75 | tcell.KeyCtrlD: "C-d", 76 | tcell.KeyCtrlE: "C-e", 77 | tcell.KeyCtrlF: "C-f", 78 | tcell.KeyCtrlG: "C-g", 79 | tcell.KeyCtrlJ: "C-j", 80 | tcell.KeyCtrlK: "C-k", 81 | tcell.KeyCtrlL: "C-l", 82 | tcell.KeyCtrlN: "C-n", 83 | tcell.KeyCtrlO: "C-o", 84 | tcell.KeyCtrlP: "C-p", 85 | tcell.KeyCtrlQ: "C-q", 86 | tcell.KeyCtrlR: "C-r", 87 | tcell.KeyCtrlS: "C-s", 88 | tcell.KeyCtrlT: "C-t", 89 | tcell.KeyCtrlU: "C-u", 90 | tcell.KeyCtrlV: "C-v", 91 | tcell.KeyCtrlW: "C-w", 92 | tcell.KeyCtrlX: "C-x", 93 | tcell.KeyCtrlY: "C-y", 94 | tcell.KeyCtrlZ: "C-z", 95 | tcell.KeyCtrlSpace: "C-Space", 96 | tcell.KeyCtrlUnderscore: "C-_", 97 | tcell.KeyCtrlRightSq: "C-]", 98 | tcell.KeyCtrlBackslash: "C-\\", 99 | tcell.KeyCtrlCarat: "C-^", 100 | } 101 | 102 | func getKeyName(event *tcell.EventKey) string { 103 | var key = event.Key() 104 | 105 | keyName := keyNames[key] 106 | 107 | if event.Modifiers() == tcell.ModAlt && key == tcell.KeyRune { 108 | if event.Rune() == ' ' { 109 | keyName = "A-Space" 110 | } else { 111 | keyName = "A-" + string(event.Rune()) 112 | } 113 | } 114 | 115 | return keyName 116 | } 117 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/eeeXun/gtt/internal/style" 7 | "github.com/eeeXun/gtt/internal/translate" 8 | "github.com/eeeXun/gtt/internal/ui" 9 | "github.com/rivo/tview" 10 | ) 11 | 12 | var ( 13 | // version 14 | version string 15 | // argument 16 | srcLangArg *string = flag.String("src", "", "Set source language") 17 | dstLangArg *string = flag.String("dst", "", "Set destination language") 18 | // Translate 19 | translator translate.Translator 20 | translators = make(map[string]translate.Translator, len(translate.AllTranslator)) 21 | // UI style 22 | uiStyle = style.NewStyle() 23 | // keyMaps 24 | keyMaps = make(map[string]string) 25 | // UI 26 | app = tview.NewApplication() 27 | srcInput = tview.NewTextArea() 28 | dstOutput = tview.NewTextView() 29 | defOutput = tview.NewTextArea() 30 | posOutput = tview.NewTextArea() 31 | translatorDropDown = tview.NewDropDown() 32 | srcLangDropDown = tview.NewDropDown() 33 | dstLangDropDown = tview.NewDropDown() 34 | langCycle = ui.NewUICycle(srcLangDropDown, dstLangDropDown, translatorDropDown) 35 | themeDropDown = tview.NewDropDown() 36 | transparentDropDown = tview.NewDropDown() 37 | hideBelowDropDown = tview.NewDropDown() 38 | osc52DropDown = tview.NewDropDown() 39 | srcBorderDropDown = tview.NewDropDown() 40 | dstBorderDropDown = tview.NewDropDown() 41 | styleCycle = ui.NewUICycle( 42 | themeDropDown, 43 | transparentDropDown, 44 | hideBelowDropDown, 45 | osc52DropDown, 46 | srcBorderDropDown, 47 | dstBorderDropDown) 48 | keyMapMenu = tview.NewTextView() 49 | langButton = tview.NewButton("(1)Language") 50 | styleButton = tview.NewButton("(2)Style") 51 | keyMapButton = tview.NewButton("(3)KeyMap") 52 | translateWindow = tview.NewFlex() 53 | translateAboveWidget = tview.NewFlex() 54 | translateBelowWidget = tview.NewFlex() 55 | langPopOut = tview.NewFlex() 56 | stylePopOut = tview.NewFlex() 57 | keyMapPopOut = tview.NewFlex() 58 | mainPage = tview.NewPages() 59 | ) 60 | 61 | func main() { 62 | showVersion := flag.Bool("version", false, "Show version") 63 | flag.Parse() 64 | 65 | switch { 66 | case *showVersion: 67 | print(version, "\n") 68 | default: 69 | configInit() 70 | uiInit() 71 | SetTermTitle(translator.GetEngineName()) 72 | 73 | if err := app.SetRoot(mainPage, true). 74 | EnableMouse(true).Run(); err != nil { 75 | panic(err) 76 | } 77 | 78 | // Check if config file need to be updated 79 | defer updateConfig() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ui.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/eeeXun/gtt/internal/style" 8 | "github.com/eeeXun/gtt/internal/translate" 9 | "github.com/gdamore/tcell/v2" 10 | "github.com/rivo/tview" 11 | ) 12 | 13 | type Item struct { 14 | item tview.Primitive 15 | fixedSize int 16 | proportion int 17 | focus bool 18 | } 19 | 20 | const ( 21 | popOutMenuHeight int = 20 22 | langStrMaxLength int = 32 23 | keyMapText string = `[#%[1]s][-] 24 | Exit program. 25 | [#%[1]s][-] 26 | Toggle pop out menu. 27 | [#%[1]s]<%[2]s>[-] 28 | Translate from source to destination window. 29 | [#%[1]s]<%[3]s>[-] 30 | Swap language. 31 | [#%[1]s]<%[4]s>[-] 32 | Clear all text in source of translation window. 33 | [#%[1]s]<%[5]s>[-] 34 | Copy selected text. 35 | [#%[1]s]<%[6]s>[-] 36 | Copy all text in source of translation window. 37 | [#%[1]s]<%[7]s>[-] 38 | Copy all text in destination of translation window. 39 | [#%[1]s]<%[8]s>[-] 40 | Play text to speech on source of translation window. 41 | [#%[1]s]<%[9]s>[-] 42 | Play text to speech on destination of translation window. 43 | [#%[1]s]<%[10]s>[-] 44 | Stop playing text to speech. 45 | [#%[1]s]<%[11]s>[-] 46 | Toggle transparent. 47 | [#%[1]s]<%[12]s>[-] 48 | Toggle Definition/Example & Part of speech. 49 | [#%[1]s], [-] 50 | Cycle through the pop out widget. 51 | [#%[1]s]<1>, <2>, <3>[-] 52 | Switch pop out menu.` 53 | ) 54 | 55 | func updateTranslateWindow() { 56 | if uiStyle.HideBelow { 57 | translateWindow.RemoveItem(translateBelowWidget) 58 | } else { 59 | translateWindow.AddItem(translateBelowWidget, 0, 1, false) 60 | } 61 | } 62 | 63 | func updateBackgroundColor() { 64 | // input/output 65 | srcInput.SetTextStyle(tcell.StyleDefault. 66 | Background(uiStyle.BackgroundColor()). 67 | Foreground(uiStyle.ForegroundColor())). 68 | SetBackgroundColor(uiStyle.BackgroundColor()) 69 | dstOutput.SetBackgroundColor(uiStyle.BackgroundColor()) 70 | defOutput.SetTextStyle(tcell.StyleDefault. 71 | Background(uiStyle.BackgroundColor()). 72 | Foreground(uiStyle.ForegroundColor())). 73 | SetBackgroundColor(uiStyle.BackgroundColor()) 74 | posOutput.SetTextStyle(tcell.StyleDefault. 75 | Background(uiStyle.BackgroundColor()). 76 | Foreground(uiStyle.ForegroundColor())). 77 | SetBackgroundColor(uiStyle.BackgroundColor()) 78 | 79 | // dropdown 80 | for _, dropdown := range []*tview.DropDown{ 81 | translatorDropDown, 82 | srcLangDropDown, 83 | dstLangDropDown, 84 | themeDropDown, 85 | transparentDropDown, 86 | hideBelowDropDown, 87 | osc52DropDown, 88 | srcBorderDropDown, 89 | dstBorderDropDown} { 90 | dropdown.SetListStyles(tcell.StyleDefault. 91 | Background(uiStyle.BackgroundColor()). 92 | Foreground(uiStyle.ForegroundColor()), 93 | tcell.StyleDefault. 94 | Background(uiStyle.SelectedColor()). 95 | Foreground(uiStyle.PrefixColor())). 96 | SetBackgroundColor(uiStyle.BackgroundColor()) 97 | } 98 | 99 | // key map 100 | keyMapMenu.SetBackgroundColor(uiStyle.BackgroundColor()) 101 | } 102 | 103 | func updateBorderColor() { 104 | // input/output 105 | srcInput.SetBorderColor(uiStyle.SrcBorderColor()). 106 | SetTitleColor(uiStyle.SrcBorderColor()) 107 | dstOutput.SetBorderColor(uiStyle.DstBorderColor()). 108 | SetTitleColor(uiStyle.DstBorderColor()) 109 | defOutput.SetBorderColor(uiStyle.SrcBorderColor()). 110 | SetTitleColor(uiStyle.SrcBorderColor()) 111 | posOutput.SetBorderColor(uiStyle.DstBorderColor()). 112 | SetTitleColor(uiStyle.DstBorderColor()) 113 | 114 | // dropdown 115 | for _, srcDropDown := range []*tview.DropDown{srcLangDropDown, srcBorderDropDown} { 116 | srcDropDown.SetBorderColor(uiStyle.SrcBorderColor()). 117 | SetTitleColor(uiStyle.SrcBorderColor()) 118 | } 119 | for _, dstDropDown := range []*tview.DropDown{dstLangDropDown, dstBorderDropDown} { 120 | dstDropDown.SetBorderColor(uiStyle.DstBorderColor()). 121 | SetTitleColor(uiStyle.DstBorderColor()) 122 | } 123 | } 124 | 125 | func updateNonConfigColor() { 126 | // input/output 127 | srcInput.SetSelectedStyle(tcell.StyleDefault. 128 | Background(uiStyle.SelectedColor()). 129 | Foreground(uiStyle.ForegroundColor())) 130 | dstOutput.SetTextColor(uiStyle.ForegroundColor()) 131 | defOutput.SetSelectedStyle(tcell.StyleDefault. 132 | Background(uiStyle.SelectedColor()). 133 | Foreground(uiStyle.ForegroundColor())) 134 | posOutput.SetSelectedStyle(tcell.StyleDefault. 135 | Background(uiStyle.SelectedColor()). 136 | Foreground(uiStyle.ForegroundColor())) 137 | 138 | // dropdown 139 | for _, noLabelDropDown := range []*tview.DropDown{srcLangDropDown, dstLangDropDown} { 140 | noLabelDropDown.SetFieldBackgroundColor(uiStyle.SelectedColor()). 141 | SetFieldTextColor(uiStyle.ForegroundColor()). 142 | SetPrefixTextColor(uiStyle.PrefixColor()) 143 | } 144 | for _, labelDropDown := range []*tview.DropDown{ 145 | translatorDropDown, 146 | themeDropDown, 147 | transparentDropDown, 148 | hideBelowDropDown, 149 | osc52DropDown, 150 | srcBorderDropDown, 151 | dstBorderDropDown} { 152 | labelDropDown.SetLabelColor(uiStyle.LabelColor()). 153 | SetFieldBackgroundColor(uiStyle.SelectedColor()). 154 | SetFieldTextColor(uiStyle.ForegroundColor()). 155 | SetPrefixTextColor(uiStyle.PrefixColor()) 156 | } 157 | 158 | // button 159 | for _, button := range []*tview.Button{langButton, styleButton, keyMapButton} { 160 | button.SetStyle(tcell.StyleDefault. 161 | Background(uiStyle.SelectedColor()). 162 | Foreground(uiStyle.ForegroundColor())). 163 | SetActivatedStyle( 164 | tcell.StyleDefault. 165 | Background(uiStyle.PressColor()). 166 | Foreground(uiStyle.ForegroundColor())) 167 | } 168 | 169 | // key map 170 | keyMapMenu.SetTextColor(uiStyle.ForegroundColor()). 171 | SetText(fmt.Sprintf(keyMapText, 172 | fmt.Sprintf("%.6x", uiStyle.HighLightColor().TrueColor().Hex()), 173 | keyMaps["translate"], 174 | keyMaps["swap_language"], 175 | keyMaps["clear"], 176 | keyMaps["copy_selected"], 177 | keyMaps["copy_source"], 178 | keyMaps["copy_destination"], 179 | keyMaps["tts_source"], 180 | keyMaps["tts_destination"], 181 | keyMaps["stop_tts"], 182 | keyMaps["toggle_transparent"], 183 | keyMaps["toggle_below"], 184 | )). 185 | SetBorderColor(uiStyle.HighLightColor()). 186 | SetTitleColor(uiStyle.HighLightColor()) 187 | } 188 | 189 | func updateAllColor() { 190 | updateBackgroundColor() 191 | updateBorderColor() 192 | updateNonConfigColor() 193 | } 194 | 195 | // SetSelectedFunc of DropDown need to update when options change 196 | func updateLangDropDown() { 197 | srcLangDropDown.SetOptions(translator.GetAllLang(), 198 | func(text string, index int) { 199 | translator.SetSrcLang(text) 200 | srcInput.SetTitle(text) 201 | srcLangDropDown.SetTitle(text) 202 | }) 203 | dstLangDropDown.SetOptions(translator.GetAllLang(), 204 | func(text string, index int) { 205 | translator.SetDstLang(text) 206 | dstOutput.SetTitle(text) 207 | dstLangDropDown.SetTitle(text) 208 | }) 209 | } 210 | 211 | // Update language title and option 212 | func updateCurrentLang() { 213 | srcInput.SetTitle(translator.GetSrcLang()) 214 | dstOutput.SetTitle(translator.GetDstLang()) 215 | srcLangDropDown.SetCurrentOption( 216 | IndexOf(translator.GetSrcLang(), translator.GetAllLang())). 217 | SetTitle(translator.GetSrcLang()) 218 | dstLangDropDown.SetCurrentOption( 219 | IndexOf(translator.GetDstLang(), translator.GetAllLang())). 220 | SetTitle(translator.GetDstLang()) 221 | } 222 | 223 | func attachButton() *tview.Flex { 224 | return tview.NewFlex().SetDirection(tview.FlexColumn). 225 | AddItem(nil, 0, 1, false). 226 | AddItem(langButton, 11, 1, true). 227 | AddItem(nil, 18, 1, false). 228 | AddItem(styleButton, 8, 1, true). 229 | AddItem(nil, 18, 1, false). 230 | AddItem(keyMapButton, 9, 1, true). 231 | AddItem(nil, 0, 1, false) 232 | } 233 | 234 | // If center is true, it will center the items 235 | func attachItems(center bool, direction int, items ...Item) *tview.Flex { 236 | container := tview.NewFlex().SetDirection(direction) 237 | if center { 238 | container.AddItem(nil, 0, 1, false) 239 | } 240 | for _, item := range items { 241 | container.AddItem(item.item, item.fixedSize, item.proportion, item.focus) 242 | } 243 | if center { 244 | container.AddItem(nil, 0, 1, false) 245 | } 246 | return container 247 | } 248 | 249 | func showLangPopout() { 250 | mainPage.HidePage("stylePopOut") 251 | mainPage.HidePage("keyMapPopOut") 252 | mainPage.ShowPage("langPopOut") 253 | app.SetFocus(langCycle.GetCurrentUI()) 254 | } 255 | 256 | func showStylePopout() { 257 | mainPage.HidePage("langPopOut") 258 | mainPage.HidePage("keyMapPopOut") 259 | mainPage.ShowPage("stylePopOut") 260 | app.SetFocus(styleCycle.GetCurrentUI()) 261 | } 262 | 263 | func showKeyMapPopout() { 264 | mainPage.HidePage("langPopOut") 265 | mainPage.HidePage("stylePopOut") 266 | mainPage.ShowPage("keyMapPopOut") 267 | } 268 | 269 | func uiInit() { 270 | // pages 271 | mainPage.AddPage("translateWindow", translateWindow, true, true) 272 | mainPage.AddPage("langPopOut", langPopOut, true, false) 273 | mainPage.AddPage("stylePopOut", stylePopOut, true, false) 274 | mainPage.AddPage("keyMapPopOut", keyMapPopOut, true, false) 275 | 276 | // input/output 277 | srcInput.SetBorder(true) 278 | dstOutput.SetBorder(true) 279 | defOutput.SetBorder(true).SetTitle("Definition/Example") 280 | posOutput.SetBorder(true).SetTitle("Part of speech") 281 | 282 | // dropdown 283 | translatorDropDown.SetLabel("Translator: "). 284 | SetOptions(translate.AllTranslator, nil). 285 | SetCurrentOption(IndexOf(translator.GetEngineName(), translate.AllTranslator)) 286 | srcLangDropDown.SetBorder(true) 287 | dstLangDropDown.SetBorder(true) 288 | themeDropDown.SetLabel("Theme: "). 289 | SetOptions(style.AllTheme, nil). 290 | SetCurrentOption(IndexOf(uiStyle.Theme, style.AllTheme)) 291 | transparentDropDown.SetLabel("Transparent: "). 292 | SetOptions([]string{"true", "false"}, nil). 293 | SetCurrentOption( 294 | IndexOf(strconv.FormatBool(uiStyle.Transparent), 295 | []string{"true", "false"})) 296 | hideBelowDropDown.SetLabel("Hide below: "). 297 | SetOptions([]string{"true", "false"}, nil). 298 | SetCurrentOption( 299 | IndexOf(strconv.FormatBool(uiStyle.HideBelow), 300 | []string{"true", "false"})) 301 | osc52DropDown.SetLabel("OSC 52: "). 302 | SetOptions([]string{"true", "false"}, nil). 303 | SetCurrentOption( 304 | IndexOf(strconv.FormatBool(uiStyle.OSC52), 305 | []string{"true", "false"})) 306 | srcBorderDropDown.SetLabel("Border Color: "). 307 | SetOptions(style.Palette, nil). 308 | SetCurrentOption( 309 | IndexOf(uiStyle.SrcBorderStr(), 310 | style.Palette)). 311 | SetBorder(true). 312 | SetTitle("Source") 313 | dstBorderDropDown.SetLabel("Border Color: "). 314 | SetOptions(style.Palette, nil). 315 | SetCurrentOption( 316 | IndexOf(uiStyle.DstBorderStr(), 317 | style.Palette)). 318 | SetBorder(true). 319 | SetTitle("Destination") 320 | 321 | // key map 322 | keyMapMenu.SetDynamicColors(true). 323 | SetBorder(true). 324 | SetTitle("Key Map") 325 | 326 | // window 327 | translateAboveWidget.SetDirection(tview.FlexColumn). 328 | AddItem(srcInput, 0, 1, true). 329 | AddItem(dstOutput, 0, 1, false) 330 | translateBelowWidget.SetDirection(tview.FlexColumn). 331 | AddItem(defOutput, 0, 1, false). 332 | AddItem(posOutput, 0, 1, false) 333 | translateWindow.SetDirection(tview.FlexRow). 334 | AddItem(translateAboveWidget, 0, 1, true) 335 | updateTranslateWindow() 336 | langPopOut.SetDirection(tview.FlexRow). 337 | AddItem(nil, 0, 1, false). 338 | AddItem(attachItems(true, tview.FlexColumn, 339 | Item{item: attachItems(false, tview.FlexRow, 340 | Item{item: attachItems(true, tview.FlexColumn, 341 | Item{item: attachItems(false, tview.FlexRow, 342 | Item{item: translatorDropDown, fixedSize: 0, proportion: 1, focus: false}), 343 | fixedSize: 0, proportion: 1, focus: false}), 344 | fixedSize: 1, proportion: 1, focus: false}, 345 | Item{item: attachItems(false, tview.FlexColumn, 346 | Item{item: srcLangDropDown, fixedSize: 0, proportion: 1, focus: true}, 347 | Item{item: dstLangDropDown, fixedSize: 0, proportion: 1, focus: false}), 348 | fixedSize: 0, proportion: 1, focus: true}), 349 | fixedSize: 2 * langStrMaxLength, proportion: 1, focus: true}), 350 | popOutMenuHeight, 1, true). 351 | AddItem(attachButton(), 1, 1, false). 352 | AddItem(nil, 0, 1, false) 353 | stylePopOut.SetDirection(tview.FlexRow). 354 | AddItem(nil, 0, 1, false). 355 | AddItem(attachItems(true, tview.FlexColumn, 356 | Item{item: attachItems(false, tview.FlexRow, 357 | Item{item: attachItems(true, tview.FlexColumn, 358 | Item{item: attachItems(false, tview.FlexRow, 359 | Item{item: themeDropDown, fixedSize: 0, proportion: 1, focus: true}, 360 | Item{item: transparentDropDown, fixedSize: 0, proportion: 1, focus: false}, 361 | Item{item: hideBelowDropDown, fixedSize: 0, proportion: 1, focus: false}, 362 | Item{item: osc52DropDown, fixedSize: 0, proportion: 1, focus: false}), 363 | fixedSize: 0, proportion: 1, focus: true}), 364 | fixedSize: 4, proportion: 1, focus: true}, 365 | Item{item: attachItems(false, tview.FlexColumn, 366 | Item{item: srcBorderDropDown, fixedSize: 0, proportion: 1, focus: false}, 367 | Item{item: dstBorderDropDown, fixedSize: 0, proportion: 1, focus: false}), 368 | fixedSize: 0, proportion: 1, focus: false}), 369 | fixedSize: 2 * langStrMaxLength, proportion: 1, focus: true}), 370 | popOutMenuHeight, 1, true). 371 | AddItem(attachButton(), 1, 1, false). 372 | AddItem(nil, 0, 1, false) 373 | keyMapPopOut.SetDirection(tview.FlexRow). 374 | AddItem(nil, 0, 1, false). 375 | AddItem(attachItems(true, tview.FlexColumn, 376 | Item{item: keyMapMenu, fixedSize: 2 * langStrMaxLength, proportion: 1, focus: true}), 377 | popOutMenuHeight, 1, true). 378 | AddItem(attachButton(), 1, 1, false). 379 | AddItem(nil, 0, 1, false) 380 | 381 | updateAllColor() 382 | updateLangDropDown() 383 | updateCurrentLang() 384 | 385 | // handler 386 | app.SetInputCapture(appHandler) 387 | translateWindow.SetInputCapture(translateWindowHandler) 388 | for _, widget := range []*tview.TextArea{srcInput, defOutput, posOutput} { 389 | // fix for loop problem 390 | // https://github.com/golang/go/discussions/56010 391 | widget := widget 392 | widget.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 393 | keyName := getKeyName(event) 394 | 395 | if len(keyName) == 0 { 396 | return event 397 | } 398 | 399 | switch keyName { 400 | case keyMaps["copy_selected"]: 401 | // copy selected text 402 | text, _, _ := widget.GetSelection() 403 | 404 | // only copy when text selected 405 | if len(text) > 0 { 406 | CopyToClipboard(text) 407 | } 408 | return nil 409 | } 410 | return event 411 | }) 412 | } 413 | langPopOut.SetInputCapture(popOutHandler) 414 | stylePopOut.SetInputCapture(popOutHandler) 415 | keyMapPopOut.SetInputCapture(popOutHandler) 416 | translatorDropDown.SetDoneFunc(langDropDownHandler). 417 | SetSelectedFunc(func(text string, index int) { 418 | translator = translators[text] 419 | updateLangDropDown() 420 | updateCurrentLang() 421 | SetTermTitle(translator.GetEngineName()) 422 | }) 423 | srcLangDropDown.SetDoneFunc(langDropDownHandler) 424 | dstLangDropDown.SetDoneFunc(langDropDownHandler) 425 | themeDropDown.SetDoneFunc(styleDropDownHandler). 426 | SetSelectedFunc(func(text string, index int) { 427 | uiStyle.Theme = text 428 | updateAllColor() 429 | }) 430 | transparentDropDown.SetDoneFunc(styleDropDownHandler). 431 | SetSelectedFunc(func(text string, index int) { 432 | uiStyle.Transparent, _ = strconv.ParseBool(text) 433 | updateBackgroundColor() 434 | }) 435 | hideBelowDropDown.SetDoneFunc(styleDropDownHandler). 436 | SetSelectedFunc(func(text string, index int) { 437 | uiStyle.HideBelow, _ = strconv.ParseBool(text) 438 | updateTranslateWindow() 439 | }) 440 | osc52DropDown.SetDoneFunc(styleDropDownHandler). 441 | SetSelectedFunc(func(text string, index int) { 442 | uiStyle.OSC52, _ = strconv.ParseBool(text) 443 | }) 444 | srcBorderDropDown.SetDoneFunc(styleDropDownHandler). 445 | SetSelectedFunc(func(text string, index int) { 446 | uiStyle.SetSrcBorderColor(text) 447 | updateBorderColor() 448 | }) 449 | dstBorderDropDown.SetDoneFunc(styleDropDownHandler). 450 | SetSelectedFunc(func(text string, index int) { 451 | uiStyle.SetDstBorderColor(text) 452 | updateBorderColor() 453 | }) 454 | keyMapMenu.SetDoneFunc(func(key tcell.Key) { 455 | switch key { 456 | case tcell.KeyEsc: 457 | mainPage.HidePage("keyMapPopOut") 458 | } 459 | }) 460 | langButton.SetSelectedFunc(showLangPopout) 461 | styleButton.SetSelectedFunc(showStylePopout) 462 | keyMapButton.SetSelectedFunc(showKeyMapPopout) 463 | } 464 | 465 | func appHandler(event *tcell.EventKey) *tcell.EventKey { 466 | keyName := getKeyName(event) 467 | 468 | if len(keyName) == 0 { 469 | return event 470 | } 471 | 472 | switch keyName { 473 | case keyMaps["exit"]: 474 | app.Stop() 475 | return nil 476 | case keyMaps["toggle_transparent"]: 477 | // Toggle transparent 478 | uiStyle.Transparent = !uiStyle.Transparent 479 | // The following will trigger transparentDropDown SetDoneFunc 480 | transparentDropDown.SetCurrentOption( 481 | IndexOf(strconv.FormatBool(uiStyle.Transparent), 482 | []string{"true", "false"})) 483 | return nil 484 | case keyMaps["toggle_below"]: 485 | // Toggle Hide below window 486 | uiStyle.HideBelow = !uiStyle.HideBelow 487 | // The following will trigger hideBelowDropDown SetDoneFunc 488 | hideBelowDropDown.SetCurrentOption( 489 | IndexOf(strconv.FormatBool(uiStyle.HideBelow), 490 | []string{"true", "false"})) 491 | return nil 492 | } 493 | 494 | // Force C-c not to exit program 495 | if event.Key() == tcell.KeyCtrlC { 496 | return tcell.NewEventKey(tcell.KeyCtrlC, 0, tcell.ModNone) 497 | } 498 | 499 | return event 500 | } 501 | 502 | func translateWindowHandler(event *tcell.EventKey) *tcell.EventKey { 503 | if event.Key() == tcell.KeyEsc { 504 | showLangPopout() 505 | return nil 506 | } 507 | 508 | keyName := getKeyName(event) 509 | 510 | switch keyName { 511 | case keyMaps["translate"]: 512 | message := srcInput.GetText() 513 | // Only translate when message exist 514 | if len(message) > 0 { 515 | translation, err := translator.Translate(message) 516 | if err != nil { 517 | dstOutput.SetText(err.Error()) 518 | } else { 519 | dstOutput.SetText(translation.TEXT) 520 | defOutput.SetText(translation.DEF, false) 521 | posOutput.SetText(translation.POS, false) 522 | } 523 | } 524 | return nil 525 | case keyMaps["clear"]: 526 | srcInput.Replace(0, len(srcInput.GetText()), "") 527 | return nil 528 | case keyMaps["copy_source"]: 529 | // copy all text in Input 530 | text := srcInput.GetText() 531 | 532 | // only copy when text exist 533 | if len(text) > 0 { 534 | CopyToClipboard(text) 535 | } 536 | return nil 537 | case keyMaps["copy_destination"]: 538 | // copy all text in Output 539 | text := dstOutput.GetText(false) 540 | 541 | // only copy when text exist 542 | if len(text) > 0 { 543 | CopyToClipboard(text) 544 | } 545 | return nil 546 | case keyMaps["swap_language"]: 547 | translator.SwapLang() 548 | updateCurrentLang() 549 | tmp := srcInput.GetText() 550 | srcInput.SetText(dstOutput.GetText(false), true) 551 | dstOutput.SetText(tmp) 552 | return nil 553 | case keyMaps["tts_source"]: 554 | // Play text to speech on source of translation window. 555 | if translator.LockAvailable() { 556 | message := srcInput.GetText() 557 | // Only play when message exist 558 | if len(message) > 0 { 559 | translator.AcquireLock() 560 | go func() { 561 | err := translator.PlayTTS(translator.GetSrcLang(), message) 562 | if err != nil { 563 | srcInput.SetText(err.Error(), true) 564 | app.Draw() 565 | } 566 | }() 567 | } 568 | 569 | } 570 | return nil 571 | case keyMaps["tts_destination"]: 572 | // Play text to speech on destination of translation window. 573 | if translator.LockAvailable() { 574 | message := dstOutput.GetText(false) 575 | // Only play when message exist 576 | if len(message) > 0 { 577 | translator.AcquireLock() 578 | go func() { 579 | err := translator.PlayTTS(translator.GetDstLang(), message) 580 | if err != nil { 581 | dstOutput.SetText(err.Error()) 582 | app.Draw() 583 | } 584 | }() 585 | } 586 | } 587 | return nil 588 | case keyMaps["stop_tts"]: 589 | // Stop play sound 590 | translator.StopTTS() 591 | return nil 592 | } 593 | 594 | return event 595 | } 596 | 597 | func popOutHandler(event *tcell.EventKey) *tcell.EventKey { 598 | ch := event.Rune() 599 | 600 | switch ch { 601 | case '1': 602 | showLangPopout() 603 | case '2': 604 | showStylePopout() 605 | case '3': 606 | showKeyMapPopout() 607 | } 608 | 609 | return event 610 | } 611 | 612 | func langDropDownHandler(key tcell.Key) { 613 | switch key { 614 | case tcell.KeyTab: 615 | langCycle.Increase() 616 | app.SetFocus(langCycle.GetCurrentUI()) 617 | case tcell.KeyBacktab: 618 | langCycle.Decrease() 619 | app.SetFocus(langCycle.GetCurrentUI()) 620 | case tcell.KeyEsc: 621 | mainPage.HidePage("langPopOut") 622 | } 623 | } 624 | 625 | func styleDropDownHandler(key tcell.Key) { 626 | switch key { 627 | case tcell.KeyTab: 628 | styleCycle.Increase() 629 | app.SetFocus(styleCycle.GetCurrentUI()) 630 | case tcell.KeyBacktab: 631 | styleCycle.Decrease() 632 | app.SetFocus(styleCycle.GetCurrentUI()) 633 | case tcell.KeyEsc: 634 | mainPage.HidePage("stylePopOut") 635 | } 636 | } 637 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | "runtime" 9 | "strings" 10 | ) 11 | 12 | func IndexOf(candidate string, arr []string) int { 13 | for index, element := range arr { 14 | if element == candidate { 15 | return index 16 | } 17 | } 18 | return -1 19 | } 20 | 21 | func SetTermTitle(name string) { 22 | fmt.Printf("\033]0;gtt - %s\007", name) 23 | } 24 | 25 | func CopyToClipboard(text string) { 26 | if uiStyle.OSC52 { 27 | fmt.Printf("\033]52;c;%s\a", base64.StdEncoding.EncodeToString([]byte(text))) 28 | return 29 | } 30 | 31 | var cmd *exec.Cmd 32 | 33 | switch runtime.GOOS { 34 | case "linux": 35 | switch os.Getenv("XDG_SESSION_TYPE") { 36 | case "x11": 37 | cmd = exec.Command("xclip", "-selection", "clipboard") 38 | case "wayland": 39 | cmd = exec.Command("wl-copy") 40 | } 41 | case "darwin": 42 | cmd = exec.Command("pbcopy") 43 | case "windows": 44 | cmd = exec.Command("clip") 45 | } 46 | 47 | cmd.Stdin = strings.NewReader(text) 48 | cmd.Start() 49 | } 50 | --------------------------------------------------------------------------------