Go言語とGASで翻訳デスクトップAppを10分で作る。

#はじめに
Go言語を少しだけ触って簡単な翻訳appを作成したので備忘録
コードは[こちら](https://github.com/Gyabi/go-translateApp)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1532027/67c2aecb-11fc-c026-316c-ffe4c5a3a024.png)



#システム構成
OS:Windows10
GUIの記述:Go言語（Walk）
翻訳APIの記述:GAS

Go言語のwindowsデスクトップapp作成用ライブラリの[Walk](https://github.com/lxn/walk)を用い簡単なGUIを作成。
また、GASを用いてgoogleのサーバ上に翻訳APIを作成。
あとはGUIの操作に合わせてPOSTを投げてレスポンスを表示させるだけ。超手抜き。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1532027/88f4df20-0a34-8056-e944-b78bab3997c8.png)



#翻訳API
まず適当にスプレッドシートを作成してGASのプロジェクトを用意
下記のコードを追記してデプロイする。
もちろんローカル環境からPOSTでアクセスする際にURLが必要になるのでどこかにメモしておく。

以下コード解説。
まずdoPostという関数はGASで用意されている関数で
この中に記述した処理がPOSTでアクセスされた際に動作するようになっている。
次にJSON.parse()でリクエストに添付されているpayloadを取得する。
翻訳にはLanguageApp.translate()を使用。こちらもすでにGAS内で提供されている関数で第一引数を翻訳したstringを返却する。
一応補足しておくと第二引数が元の言語、第三引数が翻訳先の言語。
最後にjsonで書き込んでレスポンスとして送り返す。

```javascript
function doPost(e){
  var params = JSON.parse(e.postData.getDataAsString())
  var text = params.text
  var source = params.source
  var target = params.target
  Logger.log(text)
  Logger.log(source)
  Logger.log(target)

  var translatedText = LanguageApp.translate(text, source, target)

  var output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.JSON);
  output.setContent(JSON.stringify({ translatedText: translatedText }));

  return output;
}
```


#GUIの作成
まずGUIの設計を見せるがあまり調べても出てこなかったのでwalkの記述についても少し補足する。

```go　main.go
package main

import (
	"log"

	"github.com/lxn/walk"
	. "github.com/lxn/walk/declarative"

	"github.com/Gyabi/go-translateApp/translatePost"
)

type MyMainWindow struct {
	*walk.MainWindow
	textArea *walk.TextEdit
	results  *walk.TextEdit
}

func (mw *MyMainWindow) clickedUpArrow() {
	text := mw.results.Text()

	output := translatePost.Translate_post(text, "ja", "en")

	mw.textArea.SetText(output)
}

func (mw *MyMainWindow) clickedDownArrow() {
	text := mw.textArea.Text()

	output := translatePost.Translate_post(text, "en", "ja")

	mw.results.SetText(output)
}

func main() {
	mw := &MyMainWindow{}

	if _, err := (MainWindow{
		AssignTo: &mw.MainWindow,
		Title:    "todo",
		MinSize:  Size{300, 400},
		Layout:   VBox{},
		Children: []Widget{
			Label{
				Text: "Please enter the text you want to translate",
			},
			GroupBox{
				Layout: VBox{},
				Children: []Widget{
					Label{
						Text: "English",
					},
					TextEdit{
						AssignTo: &mw.textArea,
					},
				},
			},
			HSplitter{
				StretchFactor: 20,
				Children: []Widget{
					PushButton{
						Text:      "↓",
						OnClicked: mw.clickedDownArrow,
					},
					HSpacer{},
					PushButton{
						Text:      "↑",
						OnClicked: mw.clickedUpArrow,
					},
				},
			},
			GroupBox{
				Layout: VBox{},
				Children: []Widget{
					Label{
						Text: "Japanese",
					},
					TextEdit{
						AssignTo: &mw.results,
					},
				},
			},
		},
	}.Run()); err != nil {
		log.Fatal(err)
	}
}


```


##GUIデザインとWalkの補足
基本的にはmain関数内にGUIの構成を記載している。walkによって提供されている構造体を入れ子の構造で挿入していくことでGUIのデザインを定義できる。例として今回はMainWindowの子要素としてLabel,GroupBox x 2,HSplitterを使用している。
OnClickedという要素には別途定義した関数を渡すことでクリック時に実行できる。
あと使用する構造体は自身の定義した構造体にまとめて格納しておくのがいいらしい。。？

```go
type MyMainWindow struct {
    *walk.MainWindow
    textArea *walk.TextEdit
    results  *walk.TextEdit
}
```


```go
func main() {
    mw := &MyMainWindow{}

    if _, err := (MainWindow{
        AssignTo: &mw.MainWindow,
        Title:    "todo",
        MinSize:  Size{300, 400},
        Layout:   VBox{},
        Children: []Widget{
            Label{
                Text: "Please enter the text you want to translate",
            },
            GroupBox{
                Layout: VBox{},
                Children: []Widget{
                    Label{
                        Text: "English",
                    },
                    TextEdit{
                        AssignTo: &mw.textArea,
                    },
                },
            },
            HSplitter{
                StretchFactor: 20,
                Children: []Widget{
                    PushButton{
                        Text:      "↓",
                        OnClicked: mw.clickedDownArrow,
                    },
                    HSpacer{},
                    PushButton{
                        Text:      "↑",
                        OnClicked: mw.clickedUpArrow,
                    },
                },
            },
            GroupBox{
                Layout: VBox{},
                Children: []Widget{
                    Label{
                        Text: "Japanese",
                    },
                    TextEdit{
                        AssignTo: &mw.results,
                    },
                },
            },
        },
    }.Run()); err != nil {
        log.Fatal(err)
    }
}
```

##クリック時に動作する関数
クリック時には先ほど述べたようにすでに定義してある関数を呼び出して自動的に実行させることができる。
今回は別ファイルで定義してるAPIをたたくための関数使用する形で記載した。

```go
func (mw *MyMainWindow) clickedUpArrow() {
    text := mw.results.Text()

    output := translatePost.Translate_post(text, "ja", "en")

    mw.textArea.SetText(output)
}

func (mw *MyMainWindow) clickedDownArrow() {
    text := mw.textArea.Text()

    output := translatePost.Translate_post(text, "en", "ja")

    mw.results.SetText(output)
}
```


#API実行関数
APIをたたく関数は別のディレクトリで作成した。（[github](https://github.com/Gyabi/go-translateApp)参照）
引数としてpostするためのパラメータを指定しているのでまとめてjsonに格納してhttpのメソッドでPOSTを投げる。
レスポンスはioutilとbytesで処理して必要な翻訳後の文章のみ取り出してreturn

:::note info
注意:translatePost/config.iniに先ほどメモしたAPIのurlをコピペしておく
:::

```go　translatePost/translate.go

package translatePost

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"

	"gopkg.in/ini.v1"
)

type RequestBody struct {
	Text   string `json:"text"`
	Source string `json:"source"`
	Target string `json:"target"`
}

type ResBody struct {
	TranslatedText string `json:"translatedText"`
}

func Translate_post(text, source, target string) string {
	// you can set "ja" or "en" in source and target
	requestBody := &RequestBody{
		Text:   text,
		Source: source,
		Target: target,
	}
	jsonString, err := json.Marshal(requestBody)
	if err != nil {
		panic("Error")
	}

	cfg, err := ini.Load("translatePost/config.ini")
	if err != nil {
		panic("Error")
	}
	endpoint := cfg.Section("api").Key("url").String()

	req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(jsonString))
	if err != nil {
		panic("Error")
	}

	req.Header.Set("Content-Type", "application/json")

	client := new(http.Client)
	resp, err := client.Do(req)
	if err != nil {
		panic("Error")
	}

	defer resp.Body.Close()

	byteArray, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		panic("Error")
	}

	// fmt.Printf(string(byteArray))

	var resbody ResBody
	err = json.NewDecoder(bytes.NewReader(byteArray)).Decode(&resbody)
	if err != nil {
		panic("Error")
	}

	// fmt.Println(resbody.TranslatedText)
	return resbody.TranslatedText
}

```


#実行ファイルの生成
上記のファイルの作成が終わったら実行ファイルを作成する。
まずmanifestファイルを作成。githubのものをそのまま使用してOK。

```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
        <dependency>
            <dependentAssembly>
                <assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
            </dependentAssembly>
        </dependency>
    </assembly>
```

最後に、下記コマンドをたたくとexeファイルが出力される

```
 go build -ldflags="-H windowsgui"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1532027/67c2aecb-11fc-c026-316c-ffe4c5a3a024.png)