LoginSignup
5
5

More than 5 years have passed since last update.

Golangで自分用のcli翻訳ツール作ってみた

Last updated at Posted at 2017-12-17

結論

Golang標準パッケージ整っててAPI叩くのやりやすい + TDDでやると見通し良くなる

導入

金曜日の夜に突如思い立って Golang の勉強に cli ツールを3つ作ることにした。
この記事は1つ目。

Golang で cliツールを作るにあたって urfave/cli というパッケージを使うのが楽らしいので、そこは乗っかることに。

go get github.com/urfave/cli

でローカル環境でとりあえず使えるようにした。

やること

コマンドラインでパッと変数名になる英単語を調べれる

  • テストベースで書く(TDDもどき)

目標

  • trans [日本語の単語] で英語を返す
  • trans -e [英単語] で日本語を返す

やらないこと

  • 熟語・短文を調べれるようにする(APIが賢いおかげで日本語→英語は余裕でできた!)
  • 速度を追い求める
  • オフラインでも使えるようにする

実装開始

ディレクトリ構造

$GOPATH/github.com/ahaha0807/translation
└── src
    └── script
        ├── Gopkg.lock
        ├── Gopkg.toml
        ├── main
        │   └── main.go
        ├── translation
        │   └── translation.go
        └── vendor

まずはサンプルコード書いてみた

package main

import (
    "github.com/urfave/cli"
    "fmt"
    "os"
)

func main () {
    app := cli.NewApp()
    app.Name = "translation-ahaha0807"
    app.Usage = "translation on command"
    app.Version = "0.0.1"

    app.Action = func (context *cli.Context) error {
        fmt.Println("Hello world on cli")
        return nil
    }

    app.Run(os.Args)
}

実行してみる

go run ./translation.go
# ==> Hello world on cli

あと、cliパッケージを使ってることで、helpオプションを自動で作ってくれるんだって。便利ー。

go run ./translation.go help
NAME: translation-ahaha0807 - translation on command

USAGE:
   translation [global options] command [command options] [arguments...]

VERSION:
   0.0.1

COMMANDS:
     help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h     show help
   --version, -v  print the version

まずはベースとなるテストを書く

メソッドが定義されてないとコンパイルできないので、処理のないメソッドを定義する

translation/translation.go

func ToEnglish(str string) string {
    return ""
}

func ToJapanese(str string) string {
    return ""
}

そして、テストを書く

translation/translation_test.go
func TestToEnglish(t *testing.T) {
    actual := ToEnglish("バナナ")
    expected := "Banana"

    AssertTranslation(actual, expected, t)

    actual = ToEnglish("耳")
    expected = "Ear"

    AssertTranslation(actual, expected, t)

    actual = ToEnglish("変数")
    expected = "Variable"

    AssertTranslation(actual, expected, t)
}

func TestToJapanese(t *testing.T) {
    actual := ToJapanese("Banana")
    expected := "バナナ"

    AssertTranslation(actual, expected, t)

    actual = ToJapanese("ear")
    expected = "耳"

    AssertTranslation(actual, expected, t)

    actual = ToJapanese("VARIABLE")
    expected = "変数"

    AssertTranslation(actual, expected, t)

    actual = ToJapanese("CoFfEe")
    expected = "コーヒー"

    AssertTranslation(actual, expected, t)
}

func AssertTranslation(actual string, expected string, t *testing.T) {
    if actual == "" {
        t.Errorf("翻訳を実行できませんでした。")
    }

    if actual != expected {
        t.Errorf("翻訳結果が異なります %v, %v", actual, expected)
    }
}

$ go test translation_test.go
実装はまだ書いてないので、当然落ちる。

メソッドの実装

翻訳部分の実装

翻訳部分は Microsoft の Translator API を利用する。

Translator APIの使い方とかは 公式サイト ( https://www.microsoft.com/ja-jp/translator/getstarted.aspx )をどうぞ

Translator APIを使うためにAPIトークンを取得する

認証が必要なので、まず Azureのコンパネで見れるAPI Keyを使って API Tokenを取得するメソッドを作る

translator/translator.go
func getAPIToken() string {
    apiKey := string(os.Getenv("MICROSOFT_TRANSLATE_APIKEY"))

    client := &http.Client{}

    req, _ := http.NewRequest("POST", "https://api.cognitive.microsoft.com/sts/v1.0/issueToken", nil)
    req.Header.Set("Ocp-Apim-Subscription-Key", string(apiKey))
    req.Header.Set("Content-Type", "application/json")

    res, err := client.Do(req)
    if err != nil {
        panic(err)
    }

    apiToken, _ := ioutil.ReadAll(res.Body)
    defer res.Body.Close()

    return string(apiToken)
}

取得したトークンと翻訳したい文字列を送りつける

translator/translator.go
func doTranslate(str string, srcLanguage string, destLanguage string, token string) string {
    host := "https://api.microsofttranslator.com"
    path := "/V2/Http.svc/Translate"

    values := url.Values{}
    values.Set("to", destLanguage)
    values.Add("from", srcLanguage)
    values.Add("text", str)

    option := values.Encode()

    client := &http.Client{}

    s := host + path + "?" + option

    req, _ := http.NewRequest("GET", s, nil)
    req.Header.Set("Authorization", "Bearer "+token)

    res, err := client.Do(req)
    if err != nil {
        return ""
    }
    defer res.Body.Close()

    body, _ := ioutil.ReadAll(res.Body)

    return string(body)
}

上記を実行するとレスポンスが


<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Apple</string>

な感じで届く。

レスポンスをパースする

これを encode/xmlUnmarshalメソッドで変換しようと思ったけど、1層目にデータがあるせいでうまく Unmarshal できなかったので、正規表現と文字列操作を使って力技で求めるデータだけを取り出す
format メソッドを定義する。(んだけども、これに関しては MSさんが修正するか選択できるようにしてほしい…欲を言えば JSON で返してほしい…)

translator/translator.go
func format(raw string) string {
    re := regexp.MustCompile(">.*<")
    result := string(re.Find([]byte(raw)))

    return result[1:len(result)-1]
}

ここまでで定義したメソッドを繋ぐ(ToXXXXの中でそれぞれ呼び出す)

translator/translator.go
func ToEnglish(str string) string {
    apiToken := getAPIToken()

    rawResult := doTranslate(str, "ja", "en", apiToken)
    result := format(rawResult)

    return result
}

func ToJapanese(str string) string {
    apiToken := getAPIToken()

    rawResult := doTranslate(str, "en", "ja", apiToken)
    result := format(rawResult)

    return result
}

これで基本的にはテストが通るようになる(はず。)

ToXXXX系メソッドを cli から呼び出せるように繋ぐ

main/main.go
app.Action = func(context *cli.Context) error {
    if len(context.Args()) == 0 {
        fmt.Println("USAGE: trans --[option] [words]")
        return nil
    }

    if !context.Bool("english") {
        result := translation.ToEnglish(context.Args().Get(0))
        fmt.Println(result)
        return nil
    } else {
        result := translation.ToJapanese(context.Args().Get(0))
        fmt.Println(result)
        return nil
    }
}

!context.Bool("english") でフラグがついているかを判断し、振り分けている

オプションで 英語→日本語と日本語→英語 を切り替えれるようにする

main/main.go
app.Flags = []cli.Flag{
    cli.BoolFlag{
        Name:  "english, e",
        Usage: "英語 to 日本語",
    },
}

ビルド + PATHを通す

cd src/script/main/
go build -i -o trans main.go

これでビルドされ、 ./trans でコマンドが使える。

./trans バナナ
Banana
./trans -e Banana
バナナ
./trans -h
# ~~~~~

あとはこれを $GOPATH/bin に移動させるだけ!

mv ./trans $GOPATH/bin

trans バナナ
Banana

まとめ

単純なコマンドで、オフラインじゃ使えないまだまだ稚拙なこまんどだけど、自分で作ったやつが動くと嬉しい!
あと、 $GOPATH 使えば読み手の環境を色々考えなくてもよくって、共通化できててすごいこれ嬉しいのかも…!w

次回は コマンドからTwitterに呟ける のを作って記事にします!

リポジトリ

(Golang勉強中なのでPRとかIssueとかご意見を募集してますー!)

5
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
5