(この記事は、fukuoka.ex(その2) Elixir Advent Calendar 2017 - Adventarの19日目、自然言語処理 Advent Calendar 2017 - Qiita の16日目です)
昨日は@zacky1972さんの「ZEAM開発ログv0.1.6.1 Elixir / Rustler 小ネタ集〜 Rustler でリストからバイナリに変換」でした!
本題
前回の記事「Phoenix + Vue.js入門」ではPhoenix + Vueの構成で、DBに保存したレコードを画面表示するところまでを書きました。
今回は、PhoenixからMicrosoft Translator テキスト APIを叩いて結果を取得するまでを書いてみます。環境は前回記事と同様です。
事前準備
事前準備として、azureにサインアップ後、APIを有効にしてkeyを取得する必要があります。
keyの発行については以下の記事が参考になりました。
Microsoft Translator テキスト API で、日本語を英語に翻訳するサンプル
keyの発行が済んだら、次に進みましょう。
ゴール
「日本語のタイトル・本文と、それを英語に翻訳したタイトルと本文が画面に表示される」をゴールとします
httpoisonの導入
PhoenixからAPIを叩くために、Elixir製のHTTP clientであるhttpoisonを導入します。json parserのpoisonも合わせて入れておきましょう。
GitHub - edgurgel/httpoison: Yet Another HTTP client for Elixir powered by hackney
GitHub - devinus/poison: An incredibly fast, pure Elixir JSON library
mix.exsのapplications, depsに追記します。
def application do
[
extra_applications: [:logger, :runtime_tools, :httpoison, :poison]
]
end
defp deps do
[
...
{:poison, "~> 3.1"},
{:httpoison, "~> 1.0"}
]
end
変更後、お決まりのmix deps.get
を実行しておきます。
$ mix deps.get
APIを叩くmoduleを用意する
APIを叩くコードを書きます。実際にAPIを叩く部分を、lib/translate_api.ex
モジュールとして切り出してみました。
defmodule TranslateApi do
@moduledoc """
Microsoft Translate Api Client written by Elixir.
"""
@translate_api_endpoint "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0"
@issue_token_endpoint "https://api.cognitive.microsoft.com/sts/v1.0/issueToken"
@doc """
get authorization token from the token service.
"""
def get_token() do
{:ok, response} =
HTTPoison.post(@issue_token_endpoint, "{}", [
{"Ocp-Apim-Subscription-Key", "<API key>"}
])
response.body()
end
@doc """
translate texts by microsoft translate api.
## Examples
translate(["hello", "world"], %{"from" => "en", "to" => "ja"})
"""
def translate(texts \\ [], params \\ %{"to" => "en", "from" => "ja"}) do
data = Enum.map(texts, fn text -> %{"Text" => text} end)
{:ok, %HTTPoison.Response{body: body}} =
HTTPoison.post(
@translate_api_endpoint,
Poison.encode!(data),
[
{"Content-Type", "application/json"},
{"Authorization", "Bearer " <> get_token()}
],
params: params
)
Poison.decode!(body)
|> Enum.map(fn %{"translations" => [%{"text" => text}]} -> text end)
end
end
translate()にリストを渡せば、日本語→英語へ翻訳した結果が取得できるようにしました。
TranslateApi.translate(["こんにちは", "世界"])
→ ["Hello", "World"]
APIの実行にtokenが必要なので、token取得用の関数を用意しました。10分で有効期限が切れるようなのですが、都度叩く必要はないので、そこは今後の課題です(汗)
post値は以下のようなJSONフォーマットにする必要があるので、data = Enum.map(texts, fn text -> %{"Text" => text} end)
の部分でpost用のデータを作ってます。
[
{
"Text" => "翻訳したい文字"
},
{
"Text" => "翻訳したい文字"
},
{
"Text" => "翻訳したい文字"
},
...
]
APIを叩いてレコードの値を翻訳→Viewに渡す
article_controller.ex
のindex()を修正します。
def index(conn, _params) do
articles = Blog.list_articles()
|> Enum.map(fn article ->
# 翻訳の実行
[en_title, en_body] = TranslateApi.translate([article.title, article.body])
# 結果をmapに追加
Map.merge(article, %{en_title: en_title, en_body: en_body})
end)
render(conn, "index.json", articles: articles)
end
DBから取得したレコードの各行に対して、タイトルと本文に対して翻訳を実行し、articleのマップに追加しています。
レコード数分リクエストが走っちゃうので、ちゃんと作るなら実装に工夫が必要そうです。
実はこれだけではviewで値は利用できません。article_view.ex
のrender("article.json")
部分に、翻訳結果のフィールドを追記します。
def render("article.json", %{article: article}) do
%{id: article.id, title: article.title, body: article.body, en_title: article.en_title, en_body: article.en_body}
end
これで、view側に値が渡るようになりました。渡ってきた値を表示してみましょう。
<div v-for="article in articles">
<h2>{{ article.title }}({{ article.en_title }})</h2>
<pre>{{ article.body }}</pre>
<pre>{{ article.en_body }}</pre>
<hr>
</div>
翻訳した結果が表示できました!
まとめ
Microsoft Translator テキスト APIを利用して文字列の翻訳ができました。「外部APIで取得したJSONを、独自のフォーマットに変形する」といった処理はパターンマッチのおかげで書きやすいです♪
次回は、@kobatako さんの「Slack botで通知したい投稿日のものを通知する with Qiita API
」です!お楽しみに!
満員御礼!Elixir MeetUpを6月末に開催します
※応募多数により、増枠しました
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します。特別ゲストも迎え、非常に濃い2時間になること間違いなしです!ご興味のある方はぜひご参加ください!