Livebook から Amazon Translate を使ってテキスト翻訳する

## はじめに

Livebook　から AWS のサービスを操作するシリーズです

今回は Amazon Translate を使います

その名の通り、翻訳サービスです

https://aws.amazon.com/jp/translate/

対応言語は 75 言語（日本語含む）、 100 万文字あたり $15 で使用できます

最初の12ヶ月は無料利用枠が適用されるため、 200万文字 / 月まで無料で利用できます

ただし、2022年12月19日現在、東京リージョンでは使えない点に注意してください

実装したノートブックはこちら

https://github.com/RyoWakabayashi/elixir-learning/blob/main/livebooks/aws/translate.livemd

## 事前作業

AWS のアカンウトと、 Translate の権限を持った IAM ユーザーと、その認証情報（ACCESS_KEY_ID と SECRET_ACCESS_KEY）が必要です

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_users_create.html#id_users_create_console

## 実行環境

Livebook 0.8.0 の Docker　イメージを元にしたコンテナで動かしました

https://hub.docker.com/layers/livebook/livebook/0.8.0/images/sha256-6ef3fdd439fd72af9ae01d58e947664a46a8493f031e179cc0e91c08ddac02e5?context=explore

コンテナ定義はこちらを参照

https://github.com/RyoWakabayashi/elixir-learning

## セットアップ

ex_aws_translate を中心に、必要なモジュールをインストールします

```elixir
Mix.install([
  {:ex_aws, "~> 2.0"},
  {:ex_aws_translate, "~> 0.3"},
  {:jason, "~> 1.4"},
  {:poison, "~> 5.0"},
  {:hackney, "~> 1.18"},
  {:sweet_xml, "~> 0.7"},
  {:kino, "~> 0.8"}
])
```

エイリアスを付けておきます

```elixir
alias ExAws.Translate
```

## 認証

入力エリアを用意し、そこに IAM ユーザーの認証情報を入力します

ACCESS_KEY_ID　と SECRET_ACCESS_KEY は秘密情報なので、値が見えないように `Kino.Input.password` を使います

https://qiita.com/RyoWakabayashi/items/debb1d048613ce505988

```elixir
access_key_id_input = Kino.Input.password("ACCESS_KEY_ID")
```

```elixir
secret_access_key_input = Kino.Input.password("SECRET_ACCESS_KEY")
```

普段なら東京リージョンを使うのですが、 Translate は東京リージョンで使えないため、バージニアリージョンを指定します

```elixir
region_input = Kino.Input.text("REGION", default: "us-east-1")
```

![スクリーンショット 2022-12-19 17.32.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/6dac1d3f-0bc8-c46d-aed4-921753beea5e.png)

各認証情報を ExAws に渡すためにまとめておきます

秘密情報が実行結果に現れないよう、セルの最後には `"dummy"` を入れておきましょう

```elixir
auth_config = [
  access_key_id: Kino.Input.read(access_key_id_input),
  secret_access_key: Kino.Input.read(secret_access_key_input),
  region: Kino.Input.read(region_input)
]

"dummy"
```

## 言語一覧の取得

実は ex_aws_translate には list_languages 関数が実装されていません

また、なんと GitHub リポジトリーも閉じられてしまっています

https://hex.pm/packages/ex_aws_translate

パンがなければ作ればいい、ということで無理矢理実装します

```elixir
languages =
  %ExAws.Operation.JSON{
    stream_builder: nil,
    http_method: :post,
    parser: nil,
    path: "/",
    data: %{},
    params: %{},
    headers: [
      {"Content-Type", "application/x-amz-json-1.1"},
      {"X-Amz-Target", "AWSShineFrontendService_20170701.ListLanguages"}
    ],
    service: :translate,
    before_request: nil
  }
  |> ExAws.request!(auth_config)
  |> then(& &1["Languages"])
  |> Enum.map(&{&1["LanguageCode"], &1["LanguageName"]})
```

![スクリーンショット 2022-12-19 17.35.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/24b96043-1e1c-9f72-833b-05e28707ec5b.png)

何言語あるか確認してみましょう

```elixir
Enum.count(languages)
```

![スクリーンショット 2022-12-19 17.39.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e948f13f-090c-064a-6b58-d7981c69eaa3.png)

公式 FAQ では 75 言語と書いてあるのに、 76 言語ありました

多い分には良いですが

https://aws.amazon.com/jp/translate/faqs/

## 言語の選択

言語を選択できるようにします

```elixir
src_lang_select = Kino.Input.select("翻訳元言語", languages, default: "en")
```

```elixir
src_lang = Kino.Input.read(src_lang_select)
```

```elixir
dst_lang_select = Kino.Input.select("翻訳先言語", languages, default: "ja")
```

```elixir
dst_lang = Kino.Input.read(dst_lang_select)
```

![languages.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/57c62d21-73a4-6b2b-e6dc-dbeb19df2754.gif)

## 翻訳

元言語と先言語を指定して翻訳します

```elixir
"Hello"
|> Translate.translate_text(src_lang, dst_lang)
|> ExAws.request!(auth_config)
```

![スクリーンショット 2022-12-19 17.43.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/c6305113-e62e-9eb8-6e05-27cb5b771c54.png)

文章を翻訳してみましょう

```elixir
text_input =
  Kino.Input.textarea("TEXT",
    default:
      ~s/Elixir is a dynamic, functional language for building scalable and maintainable applications. Elixir runs on the Erlang VM, known for creating low-latency, distributed, and fault-tolerant systems. These capabilities and Elixir tooling allow developers to be productive in several domains, such as web development, embedded software, data pipelines, and multimedia processing, across a wide range of industries./
  )
```

![スクリーンショット 2022-12-19 17.44.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/da71ed06-47eb-bed0-2f84-ea5191ac4dd7.png)

```elixir
text_input
|> Kino.Input.read()
|> Translate.translate_text(src_lang, dst_lang)
|> ExAws.request!(auth_config)
|> then(& &1["TranslatedText"])
```

```text
"Elixirは、スケーラブルで保守可能なアプリケーションを構築するための動的で機能的な言語です。Elixirは、低レイテンシー、分散型、耐障害性のあるシステムを作ることで知られるErlang VM上で動作します。これらの機能とElixirツールにより、開発者は幅広い業界のウェブ開発、組み込みソフトウェア、データパイプライン、マルチメディア処理など、さまざまな分野で生産性を高めることができます。"
```

かなり良い感じに翻訳できています

ちなみに、翻訳元言語に "auto" を指定すれば自動的に識別してくれます

```elixir
# 翻訳元言語の自動識別
text_input
|> Kino.Input.read()
|> Translate.translate_text("auto", dst_lang)
|> ExAws.request!(auth_config)
|> then(& &1["TranslatedText"])
```

## 用語登録

Translate の翻訳でも、専門用語など、微妙に正しく翻訳してくれない場合があります

そういった場合に特定の単語はこう翻訳しなさい、と指定できます

以下は手巻き寿司の説明文です

```elixir
text =
  "Temakizushi are cones made of nori seaweed and filled with sushi rice, seafood and vegetables."
```

```elixir
text
|> Translate.translate_text("auto", dst_lang)
|> ExAws.request!(auth_config)
|> then(& &1["TranslatedText"])
```

そのまま翻訳すると以下のような文章になります

```text
"手巻き寿司は、海苔でできたコーンに寿司飯、魚介類、野菜を詰めたものです。"
```

「コーン」だとトウモロコシだと誤解を招きそうです

「円錐」にしてもらいましょう（それもどうかと思いますが）

用語データは CSV 形式の文字列を BASE64 エンコードして渡します（TSV や TMX にも対応しています）

ヘッダーに言語コード、2行目以降に用語を列挙します

以下のようにすると、英語で「cones」は日本語で「円錐」になります

```elixir
sushi_terminology_data =
  ("en,ja\n" <>
     "cones,円錐")
  |> Base.encode64()
```

用語データを Translate に登録します

```elixir
Translate.import_terminology(
  "sushi_terminology",
  %{
    file: sushi_terminology_data,
    format: "CSV"
  },
  "OVERWRITE"
)
|> ExAws.request!(auth_config)
```

登録した用語データを `terminology_names` に指定して翻訳しましょう

```elixir
text
|> Translate.translate_text("auto", dst_lang, terminology_names: ["sushi_terminology"])
|> ExAws.request!(auth_config)
|> then(& &1["TranslatedText"])
```

```text
"手巻き寿司は、海苔でできた円錐に寿司飯、魚介類、野菜を詰めたものです。"
```

きちんと「円錐」になりました

## まとめ

翻訳サービスも色々あるのであえてこれを選ばないといけないこともないですが、 AWS 上に建てたシステムからは簡単に呼び出せるので選択肢の一つです

もちろん、 Bumblebee で翻訳できるようになるのももうすぐですが