Richard's Blog

用Golang做一个命令行翻译工具

字数统计: 1.3k阅读时长: 6 min
2019/05/04 Share

目标

用golang写一个命令行的翻译工具,在命令行输入以下命令:

1
$ fanyi hello

输出以下结果:

1
2
3
4
5
6
7
8
9
10
hello [həˈləʊ]

- 你好

1. Hello Android
创建,创立,建立
2. Hello Neighbor
你好邻居,你好
3. Hello English
新纪元小学英语,学英语,律动欢唱学英语

接收参数

创建目录和main.go,接收命令行参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"os"
)

func main() {
args := os.Args
// 切片第一项为命令本身,所以[1:]为我们需要翻译的内容
words := args[1:]
// 如果没有输入则退出
if len(words) == 0 {
return
}
fmt.Println("words: ", words)
}

找一个翻译API

我这里用的是有道的API

1
http://fanyi.youdao.com/openapi.do?keyfrom=node-fanyi&key=<your key here>&type=data&doctype=json&version=1.1&q=hello

创建翻译包

我把具体的翻译逻辑放到一个叫translate的包中,基于有道翻译的逻辑我写在youdao.go文件中

1
2
3
4
5
6
7
// translate/youdao.go
package translate

func Youdao(words string) (string, error) {
// TODO
return "", nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// main.go
package main

import (
"fmt"
"os"
"log"
"strings"

"./translate"
)

func main() {
args := os.Args
// 切片第一项为命令本身,所以[1:]为我们需要翻译的内容,转换成字符串
words := strings.Join(args[1:], " ")
// 如果没有输入则退出
if len(words) == 0 {
log.Fatal("empty params")
}
translate, err := translate.Youdao(words)
if err != nil {
log.Fatal(err)
}
fmt.Println(translate)
}

向有道API发起请求

使用golang内置的net/http包发起http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// translate/youdao.go
package translate

import (
"fmt"
"strings"
"net/http"
"ioutil"
)
// 这里我使用${word}占位符
const youdaoApi = "http://fanyi.youdao.com/openapi.do?keyfrom=node-fanyi&key=<your key here>&type=data&doctype=json&version=1.1&q=${word}"

func Youdao(words string) (string, error) {
fmt.Println("youdao: ", words)
// 替换字符串
url := strings.replace(youdaoApi, "${word}", words, 1)
res, err := http.Get(url)
// 有错误返回错误
if err != nil {
return "", err
}
// 使用golang的defer机制确保资源正确关闭
defer res.Body.Close()
// res.Body是一个io.ReadCloser接口
// 使用ioutil.ReadAll将Body中的数据读出来,结果是[]byte
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
// 输出是一段[]byte数据,不可读
fmt.Println(body)
}

解析json数据

golang已经内置了用于解析json数据的工具,我们只需要按照json数据的格式创建对应的struct。

有道API返回的json数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"translation": ["你好"],
"basic": {
"us-phonetic": "helˈō",
"phonetic": "həˈləʊ",
"uk-phonetic": "həˈləʊ",
"explains": ["n. 表示问候, 惊奇或唤起注意时的用语", "int. 喂;哈罗", "n. (Hello)人名;(法)埃洛"]
},
"query": "hello",
"errorCode": 0,
"web": [{
"value": ["创建", "创立", "建立"],
"key": "Hello Android"
}, {
"value": ["你好邻居", "你好"],
"key": "Hello Neighbor"
}, {
"value": ["新纪元小学英语", "学英语", "律动欢唱学英语"],
"key": "Hello English"
}]
}

在youdao.go中创建对应的struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// YoudaoData 有道返回的json数据
type YoudaoData struct {
Translation []string `json:"translation"`
Basic YoudaoBasic `json:"basic"`
Query string `json:"query"`
ErrorCode string `json:"errorCode"`
Web []YoudaoWebItem `json:"web"`
}

// YoudaoBasic json数据中的basic
type YoudaoBasic struct {
UsPhonetic string `json:"us-phonetic"`
Phonetic string `json:"phonetic"`
UkPhonetic string `json:"uk-phonetic"`
Explains []string `json:"explains"`
}

// YoudaoWebItem json数据中的web字段
type YoudaoWebItem struct {
Value []string `json:"value"`
Key string `json:"key"`
}

修改Youdao(words string)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func Youdao(words string) (YoudaoData, error) {
var jsonData YoudaoData
// 替换字符串
url := strings.replace(youdaoApi, "${word}", words, 1)
res, err := http.Get(url)
// 有错误返回错误
if err != nil {
return jsonData, err
}
// 使用golang的defer机制确保资源正确关闭
defer res.Body.Close()
// res.Body是一个io.ReadCloser接口
// 使用ioutil.ReadAll将Body中的数据读出来,结果是[]byte
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return jsonData, err
}
// 使用json包解析json数据
json.Unmarshal(body, &jsonData)
return jsonData, nil
}

现在可以在命令行看到结果了

1
{[你好] {helˈō həˈləʊ həˈləʊ [n. 表示问候, 惊奇或唤起注意时的用语 int. 喂;哈罗 n. (Hello)人名;(法)埃洛]} hello  [{[创建 创立 建立] Hello Android} {[你好邻居 你好] Hello Neighbor} {[新纪元小学英语 学英语 律动欢唱学英语] Hello English}]}

这样的结果不是我们想要的,我们希望在fmt.Println(translate)是数据能自动带上格式。

数据格式化输出

就像Java里的toString方法一样,我们只需要让struct实现String()接口就可以修改输出内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func (data YoudaoData) String() string {
phonetic := fmt.Sprintf("%s [%s]\n", data.Query, data.Basic.Phonetic)
var explains string
for _, val := range data.Translation {
explains += fmt.Sprintf("- %s\n", val)
}
var examples string
for index, item := range data.Web {
examples += fmt.Sprintf("%d. %s\n %s\n", index+1, item.Key, strings.Join(item.Value, ","))
}
return fmt.Sprintf(`
%s
%s
%s
`, phonetic, explains, examples)
}

再次运行go run main.go hello,输出为

1
2
3
4
5
6
7
8
9
10
hello [həˈləʊ]

- 你好

1. Hello Android
创建,创立,建立
2. Hello Neighbor
你好邻居,你好
3. Hello English
新纪元小学英语,学英语,律动欢唱学英语

感觉目的达到了。

Bug

查看有道的API文档,发现这个API是智能翻译,意思是输入什么语言都可以。
我们试试go run main.go 你好,输出了乱码加空白

1
2

你好 []

既然我们是调用的http的get请求,那么在传递特殊字符时应该先encode一下,修改Youdao(words string)方法

1
2
3
4
5
6
7
func Youdao(words string) (YoudaoData, error) {
var jsonData YoudaoData
url := strings.Replace(youdao, "${word}", url.QueryEscape(words), 1)

...

}

再试一次go run main.go 你好

1
2
3
4
5
6
7
8
9
10
你好 []

- hello

1. 老师你好
Teacher Kim Bong-du,My Teacher Mr Kim,SALVE MAGISTER
2. 你好嘢
Her Fatal Ways,HIS FATAL WAYS,wvkj
3. 你好总统
Alo Presidente

感觉好像可以了。

CATALOG
  1. 1. 目标
  2. 2. 接收参数
  3. 3. 找一个翻译API
  4. 4. 创建翻译包
  5. 5. 向有道API发起请求
  6. 6. 解析json数据
  7. 7. 数据格式化输出
  8. 8. Bug