2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

#はじめに
Go言語を少しだけ触って簡単な翻訳appを作成したので備忘録
コードはこちら
image.png

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

Go言語のwindowsデスクトップapp作成用ライブラリのWalkを用い簡単なGUIを作成。
また、GASを用いてgoogleのサーバ上に翻訳APIを作成。
あとはGUIの操作に合わせてPOSTを投げてレスポンスを表示させるだけ。超手抜き。
image.png

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

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

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という要素には別途定義した関数を渡すことでクリック時に実行できる。
あと使用する構造体は自身の定義した構造体にまとめて格納しておくのがいいらしい。。?

type MyMainWindow struct {
    *walk.MainWindow
    textArea *walk.TextEdit
    results  *walk.TextEdit
}
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をたたくための関数使用する形で記載した。

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参照)
引数としてpostするためのパラメータを指定しているのでまとめてjsonに格納してhttpのメソッドでPOSTを投げる。
レスポンスはioutilとbytesで処理して必要な翻訳後の文章のみ取り出してreturn

注意: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

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?