├── asset ├── 1.png ├── 2.png └── 3.png ├── main.go ├── go.mod ├── .gitignore ├── config ├── source.json └── config.go ├── README.md ├── .goreleaser.yml ├── install.sh ├── go.sum ├── cmd └── cmd.go └── print └── print.go /asset/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakz/fanyi/HEAD/asset/1.png -------------------------------------------------------------------------------- /asset/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakz/fanyi/HEAD/asset/2.png -------------------------------------------------------------------------------- /asset/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakz/fanyi/HEAD/asset/3.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fanyi/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module fanyi 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/atotto/clipboard v0.1.2 7 | github.com/bitly/go-simplejson v0.5.0 8 | github.com/gookit/color v1.2.7 9 | ) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | .idea 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | -------------------------------------------------------------------------------- /config/source.json: -------------------------------------------------------------------------------- 1 | { 2 | "iciba": "http://dict-co.iciba.com/api/dictionary.php?key=D191EBD014295E913574E1EAF8E06666&w=${word}", 3 | "youdao": "http://fanyi.youdao.com/openapi.do?keyfrom=node-fanyi&key=110811608&type=data&doctype=json&version=1.1&q=${word}", 4 | "dictionaryapi": "http://www.dictionaryapi.com/api/v1/references/collegiate/xml/${word}?key=82c5d495-ccf0-4e72-9051-5089e85c2975" 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fanyi 2 | A translation tool in your command line 3 | 4 | ## Install 5 | For macOs 6 | 7 | ```bash 8 | $ brew install sakz/tap/fanyi 9 | ``` 10 | For Linux 11 | 12 | ```bash 13 | $ source <(curl -sL https://git.io/fanyi-install) 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```bash 19 | $ fanyi word 20 | ``` 21 | 22 | ``` 23 | Usage: fanyi word 24 | 25 | Examples: 26 | $ fanyi word 27 | $ fanyi world peace 28 | $ fanyi 中文 29 | ``` 30 | 31 | > translate English word 32 | 33 | ![](./asset/1.png) 34 | 35 | > translate English phrase 36 | 37 | ![](./asset/2.png) 38 | 39 | > translate Chinese 40 | 41 | ![](./asset/3.png) 42 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # you may remove this if you don't use vgo 6 | - go mod download 7 | builds: 8 | - env: 9 | - CGO_ENABLED=0 10 | archives: 11 | - replacements: 12 | darwin: Darwin 13 | linux: Linux 14 | windows: Windows 15 | 386: i386 16 | amd64: x86_64 17 | checksum: 18 | name_template: 'checksums.txt' 19 | snapshot: 20 | name_template: "{{ .Tag }}" 21 | changelog: 22 | sort: asc 23 | filters: 24 | exclude: 25 | - '^docs:' 26 | - '^test:' 27 | 28 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | Iciba string `json:"iciba"` 5 | Youdao string `json:"youdao"` 6 | Dictionaryapi string `json:"dictionaryapi"` 7 | } 8 | 9 | var SourceCfg Config 10 | 11 | func init() { 12 | SourceCfg = Config{ 13 | Iciba: "http://dict-co.iciba.com/api/dictionary.php?key=D191EBD014295E913574E1EAF8E06666&w=${word}", 14 | Youdao: "http://fanyi.youdao.com/openapi.do?keyfrom=node-fanyi&key=110811608&type=data&doctype=json&version=1.1&q=${word}", 15 | Dictionaryapi: "http://www.dictionaryapi.com/api/v1/references/collegiate/xml/${word}?key=82c5d495-ccf0-4e72-9051-5089e85c2975", 16 | } 17 | } 18 | 19 | //var filepath = "config/source.json" 20 | //var SourceCfg Config 21 | // 22 | //func init() { 23 | // file, err := os.OpenFile(filepath, os.O_RDONLY, 0) 24 | // defer file.Close() 25 | // if err != nil { 26 | // log.Fatal(err) 27 | // } 28 | // data, err := ioutil.ReadAll(file) 29 | // if err != nil { 30 | // log.Fatal(err) 31 | // } 32 | // err = json.Unmarshal(data, &SourceCfg) 33 | // if err != nil { 34 | // log.Fatal(err) 35 | // } 36 | //} 37 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DOWNLAOD_URL="https://github.com/sakz/fanyi/releases/download" 4 | 5 | VERSION_CHECK="https://api.github.com/repos/sakz/fanyi/releases/latest" 6 | 7 | UPDATE=0 8 | 9 | [[ -e /usr/local/bin/fanyi ]] && UPDATE=1 10 | 11 | RED="31m" 12 | GREEN="32m" 13 | YELLOW="33m" 14 | BLUE="36m" 15 | FUCHSIA="35m" 16 | 17 | colorEcho(){ 18 | COLOR=$1 19 | echo -e "\033[${COLOR}${@:2}\033[0m" 20 | } 21 | 22 | checkSys() { 23 | if [[ $(uname -m 2> /dev/null) != x86_64 ]]; then 24 | colorEcho $YELLOW "Please run this script on x86_64 machine." 25 | exit 1 26 | fi 27 | 28 | # 缺失/usr/local/bin路径时自动添加 29 | [[ -z `echo $PATH|grep /usr/local/bin` ]] && { echo 'export PATH=$PATH:/usr/local/bin' >> /etc/profile; source /etc/profile; } 30 | } 31 | 32 | install(){ 33 | if [[ $UPDATE == 1 ]];then 34 | rm -rf /usr/local/bin/fanyi 35 | fi 36 | LASTEST_VERSION=$(curl -H 'Cache-Control: no-cache' -s "$VERSION_CHECK" | grep 'tag_name' | cut -d\" -f4) 37 | echo "正在下载程序`colorEcho $BLUE $LASTEST_VERSION`版本..." 38 | # https://github.com/sakz/fanyi/releases/download/v0.0.4/fanyi_0.0.4_Linux_x86_64.tar.gz 39 | cd /usr/local/bin/ 40 | echo "$DOWNLAOD_URL/$LASTEST_VERSION/fanyi_${LASTEST_VERSION#*v}_Linux_x86_64.tar.gz" 41 | curl -LO "$DOWNLAOD_URL/$LASTEST_VERSION/fanyi_${LASTEST_VERSION#*v}_Linux_x86_64.tar.gz" 42 | tar zxf fanyi_${LASTEST_VERSION#*v}_Linux_x86_64.tar.gz 43 | rm -rf fanyi_${LASTEST_VERSION#*v}_Linux_x86_64.tar.gz README.md 44 | cd 45 | echo "安装完成,在终端输入 `colorEcho $BLUE fanyi` 体验" 46 | } 47 | 48 | main(){ 49 | checkSys 50 | install 51 | } 52 | main 53 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY= 2 | github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 3 | github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= 4 | github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= 5 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 6 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gookit/color v1.2.7 h1:4qePMNWZhrmbfYJDix+J4V2l0iVW+6jQGjicELlN14E= 10 | github.com/gookit/color v1.2.7/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= 11 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 12 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 20 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 21 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fanyi/config" 5 | "fanyi/print" 6 | "flag" 7 | "fmt" 8 | "github.com/atotto/clipboard" 9 | "io/ioutil" 10 | "log" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "strings" 15 | ) 16 | 17 | func Execute() { 18 | flag.Usage = func() { 19 | fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s word\n\n", os.Args[0]) 20 | flag.PrintDefaults() 21 | eg := `Examples: 22 | $ fanyi word 23 | $ fanyi world peace 24 | $ fanyi 中文` 25 | fmt.Println(eg) 26 | } 27 | flag.Parse() 28 | var queryString string 29 | if len(os.Args[1:]) == 0 { 30 | text, err := clipboard.ReadAll() 31 | if err != nil || text == "" { 32 | //读取剪切板失败或者没内容 33 | flag.Usage() 34 | return 35 | } 36 | fmt.Printf(" \n 默认读取剪贴板: %s\n", text) 37 | queryString = text 38 | } else { 39 | queryString = strings.Join(flag.Args(), " ") 40 | } 41 | queryString = url.QueryEscape(queryString) 42 | fmt.Println() 43 | ch := make(chan string) 44 | go youdao(queryString, ch) 45 | go iciba(queryString, ch) 46 | for i := 0; i < 2; i++ { 47 | <-ch 48 | } 49 | } 50 | 51 | func youdao(queryString string, ch chan<- string) { 52 | cfg := config.SourceCfg 53 | youdaoUrl := strings.Replace(cfg.Youdao, "${word}", queryString, 1) 54 | resp, err := http.Get(youdaoUrl) 55 | if err != nil { 56 | log.Println("有道翻译接口问题") 57 | ch <- "youdao failed" 58 | return 59 | } 60 | data, err := ioutil.ReadAll(resp.Body) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | print.Youdao(data) 65 | ch <- "youdao done" 66 | } 67 | 68 | func iciba(queryString string, ch chan<- string) { 69 | cfg := config.SourceCfg 70 | icibaUrl := strings.Replace(cfg.Iciba, "${word}", queryString, 1) 71 | resp, err := http.Get(icibaUrl) 72 | if err != nil { 73 | log.Println("iciba翻译接口问题") 74 | ch <- "iciba failed" 75 | return 76 | } 77 | data, err := ioutil.ReadAll(resp.Body) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | print.Iciba(data) 82 | ch <- "iciba done" 83 | } 84 | -------------------------------------------------------------------------------- /print/print.go: -------------------------------------------------------------------------------- 1 | package print 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "github.com/bitly/go-simplejson" 7 | "github.com/gookit/color" 8 | "log" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | "unicode" 13 | ) 14 | 15 | type IcibaResp struct { 16 | //xmlName xml.Name `xml:"dict"` 17 | Key string `xml:"key"` 18 | Ps []string `xml:"ps"` 19 | Pron []string `xml:"pron"` 20 | Pos []string `xml:"pos"` 21 | Acceptation []string `xml:"acceptation"` 22 | Sent []Sent `xml:"sent"` 23 | } 24 | 25 | type Sent struct { 26 | Orig string `xml:"orig"` 27 | Trans string `xml:"trans"` 28 | } 29 | 30 | var magenta = color.FgMagenta.Render 31 | var gray = color.FgGray.Render 32 | var green = color.FgGreen.Render 33 | var cyan = color.FgCyan.Render 34 | 35 | func Youdao(data []byte) { 36 | json, err := simplejson.NewJson(data) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | query, _ := json.Get("query").String() 41 | phonetic, err := json.Get("basic").Get("phonetic").String() 42 | var phoneticStr string 43 | if err != nil { 44 | phoneticStr = "" 45 | } else { 46 | phoneticStr = fmt.Sprintf("[ %s ]", magenta(phonetic)) 47 | } 48 | fmt.Printf(" %s %s %s\n\n", query, phoneticStr, gray("~ fanyi.youdao.com")) 49 | explains, _ := json.Get("basic").Get("explains").Array() 50 | for _, value := range explains { 51 | fmt.Printf(" %s %s\n", gray("-"), green(value)) 52 | } 53 | fmt.Println() 54 | web, _ := json.Get("web").Array() 55 | for i, value := range web { 56 | val := value.(map[string]interface{}) 57 | fmt.Printf(" %s %s\n", gray(strconv.Itoa(i+1)+"."), highlight(val["key"].(string), query)) 58 | valuelen := len(val["value"].([]interface{})) 59 | valArr := make([]string, valuelen) 60 | for i, value := range val["value"].([]interface{}) { 61 | valArr[i] = value.(string) 62 | } 63 | valueStr := strings.Join(valArr, ", ") 64 | fmt.Printf(" %s\n", cyan(valueStr)) 65 | } 66 | fmt.Println() 67 | fmt.Println(gray(" --------")) 68 | fmt.Println() 69 | } 70 | 71 | func Iciba(data []byte) { 72 | v := IcibaResp{} 73 | err := xml.Unmarshal(data, &v) 74 | if err != nil { 75 | fmt.Printf("error: %v", err) 76 | return 77 | } 78 | var phoneticStr string 79 | for i, value := range v.Ps { 80 | if i == 0 { 81 | phoneticStr += "英" + "[ " + value + "] " 82 | } else { 83 | phoneticStr += "美" + "[ " + value + "] " 84 | } 85 | } 86 | fmt.Printf(" %s %s %s\n\n", v.Key, magenta(phoneticStr), gray("~ iciba.com")) 87 | if !isChinese(v.Key) { 88 | for i := 0; i < len(v.Pos); i++ { 89 | fmt.Printf(" %s %s %s", gray("-"), green(v.Pos[i]), green(v.Acceptation[i])) 90 | } 91 | } 92 | fmt.Println() 93 | for i := 0; i < len(v.Sent); i++ { 94 | fmt.Printf(" %s %s\n", gray(strconv.Itoa(i+1)+"."), highlight(del(v.Sent[i].Orig), v.Key)) 95 | fmt.Printf(" %s\n", cyan(del(v.Sent[i].Trans))) 96 | } 97 | fmt.Println() 98 | fmt.Println(gray(" --------")) 99 | fmt.Println() 100 | } 101 | 102 | // 高亮句子中的单词 103 | func highlight(str string, query string) string { 104 | yellow := color.FgYellow.Render 105 | //r := regexp.MustCompile("(?i)" + query) 106 | //f := func(s string) string { 107 | // return yellow(s) 108 | //} 109 | //res := r.ReplaceAllStringFunc(str, f) 110 | 111 | // 句子中单词用黄色,其他用灰色 112 | r := regexp.MustCompile("(?i)" + "(.*)" + "(" + query + ")" + "(.*)") 113 | res1 := r.ReplaceAllString(str, "$1$2"+gray("$3")) 114 | r2 := regexp.MustCompile("(?i)" + "(.*?)" + "(" + query + ")") 115 | res2 := r2.ReplaceAllString(res1, gray("$1")+yellow("$2")) 116 | return res2 117 | } 118 | 119 | // 删除string中的换行符 120 | func del(str string) string { 121 | r := regexp.MustCompile("\n") 122 | res := r.ReplaceAllString(str, "") 123 | return res 124 | } 125 | 126 | // 是否包含中文 127 | func isChinese(str string) bool { 128 | count := 0 129 | for _, v := range str { 130 | if unicode.Is(unicode.Han, v) { 131 | count++ 132 | } 133 | } 134 | return count > 0 135 | } 136 | --------------------------------------------------------------------------------