DeepL API で Slack にアップロードしたファイルをクリックだけで翻訳する方法

こんにちは、Slack の公式 SDK 開発と日本の Developer Relations を担当している瀬良 (@seratch) と申します :wave:

### 2 年前の記事ではメッセージを翻訳していました

気がつけばもう約 2 年近く前になりましたが、以前 DeepL API を使って Slack のチャットメッセージを翻訳するアプリを公開し、解説記事を書きました。

https://qiita.com/seratch/items/a001985ee1dccaf95727

アプリのソースコードはこちらです:

https://github.com/seratch/deepl-for-slack

### 今回はファイルを翻訳してみます

2 年前の時点では DeepL はテキストを渡して翻訳する一つの API だけを公開していました。しかし、今回、久しぶりに確認してみると、いくつか API が追加されていることに気づきました。

https://www.deepl.com/ja/docs-api/

この記事で紹介するアプリでは、それらの API のうち [Translating Documents](https://www.deepl.com/ja/docs-api/translating-documents/) と [Monitoring Usage](https://www.deepl.com/docs-api/other-functions/monitoring-usage/) を使っています。

なお、アプリのコード・設定は全て以下の GitHub リポジトリで公開していますので、Slack アプリに馴染みのある方は手元で動かしてみてください :wave:

https://github.com/seratch/deepl-document-translator-for-slack

また、このボタンから Heroku にデプロイして動かしてみるのもよいかと思います。
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/seratch/deepl-document-translator-for-slack/tree/main)
もし、上のボタンからデプロイしようとして
>"We couldn't deploy your app because the source code violates the Salesforce Acceptable Use and External-Facing Services Policy."

というエラーになった場合は [GitHub リポジトリ](https://github.com/seratch/deepl-document-translator-for-slack)を fork して fork 先のリポジトリを指定した `https://heroku.com/deploy?template=https://github.com/{fork 先のアカウント}/deepl-document-translator-for-slack/tree/main` を試してみてください。


### どのようなアプリか？

このアプリが提供する機能は以下の二つです。

* Slack にアップロードしたファイルを含むメッセージをリアクション絵文字をつけるだけで翻訳を開始、翻訳が完了するとそのメッセージのスレッドに翻訳されたファイルがアップロードされる
* DeepL API をどれくらい使用したかをアプリのホームタブで確認できる

#### ファイルを翻訳する

百聞は一見に如かず、ということで早速どのように動作するかを見てください。

使い方は非常に簡単です。翻訳したい言語が使われている国や地域の `flag-` で始まる絵文字を指定してみてください。

<img width="600" src="https://user-images.githubusercontent.com/19658/161482554-fe0828c1-4058-4cab-9b20-0c7af9bbdb6b.png">

メッセージに `:flag-jp:` の絵文字リアクションをつけると、DeepL Document Translator というアプリが DeepL API とのやり取りを開始してくれます。ファイルによっては少し長い時間がかかりますが、翻訳が完了すると、同じスレッドにファイルをアップロードしてくれています。

##### 挙動の細かい点についての補足

すでに翻訳されている言語に対応したリアクションが他の人からつけられたときは無視するようになっています。

また、スレッド内のファイルの翻訳はできないようにしてあります。不用意にこの一定の費用がかかる API を使用しないための配慮なのですが、もしこの挙動がカスタマイズしたい場合はプロジェクトを fork して該当のチェック処理を外してもらえればと思います。

##### API の利用コストの違いに注意

上のパートで「一定の費用がかかる API」と書きました。このドキュメントの翻訳 API はテキストを翻訳する API とは費用感が少し異なります。この記事の執筆時点（2022 年 4 月）で私がテストした範囲ではファイルごとに 50,000 文字の翻訳コストがかかるようでした（ファイルの内容によっては 50,000 文字以外になることがあるかもしれません）。概算（この記事時点）で 50,000 文字の消費は約 125 円となります。また、Free Plan の場合、一ヶ月の上限が 500,000 文字なので 10 回ファイルを翻訳すると上限に達します。

この API を使ったアプリを運用する場合、アプリのボットユーザーを招待するチャンネルを翻訳実行用のチャンネル、という風にわかるようにして、そのチャンネルのルールをトピックに書いておく、または、ルールを周知したメッセージをピン留めしておくと混乱が少なくなるでしょう。

また、DeepL 側に利用できる上限金額を指定する機能がありますので、予算に合わせて指定しておくと安心かなと思います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/80506c26-5a02-04d6-f893-12ef07ed235f.png" width=300 />

なお、この 50,000 文字という消費量は、[ステータスを確認する API](https://www.deepl.com/ja/docs-api/translating-documents/checking-status/) からの応答に `billed_characters` という属性が含まれますので、この値をチェックすることで、わかるようになっています。毎回 50,000 かもしれませんが、一応、翻訳ファイルをアップロードするときのメッセージテキストに含めるようにしました。

##### PDF の翻訳をする場合

このドキュメント翻訳の API は[様々なファイルフォーマットに対応](https://www.deepl.com/ja/docs-api/translating-documents/uploading/)していますが、そのうちの一つである PDF ファイルの翻訳をしたい場合は、アカウントページで以下の翻訳の設定を有効にしておく必要があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/bc092314-ac60-5d58-5861-dbd6ead64544.png" width=500 />

PDF での利用もサポートする場合、必ずアプリをエンドユーザーに解放する前に、このページのチェックをオンにしておいてください。

#### ホームタブで利用状況を確認する

もう一つの機能は利用状況の確認機能です。

[DeepL のアカウントページ](https://www.deepl.com/ja/pro-account/usage)にいくと、より詳細なグラフ付きの情報を確認できるのですが、このアプリのホームタブでは、その対象月の利用文字数等をチェックすることができるようにしてあります。

<img width="500" src="https://user-images.githubusercontent.com/19658/161419330-a459b6ee-e19e-4ab0-b06e-6f0b3be2fb77.png">

上限に達したときには青色のサークルの表示が赤く変わります。


### アプリを運用する方法

最後に、このアプリを運用する方法について説明しておきます。ソケットモードではない動かし方をしたい場合は [bolt-python の examples](https://github.com/slackapi/bolt-python/tree/main/examples) を参考にコードをアップデートしてみてください。

#### Heroku

ソケットモードのアプリとなっていますので、Heroku の場合であれば Procfile を `worker: python app.py` とするだけで稼働させられるはずです。

カスタマイズせずにそのまま動かしたい場合は、以下のボタンからデプロイしてみてください（エラーメッセージが出た場合は GitHub リポジトリを fork してご自身のリポジトリを指定してデプロイしてください）。
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/seratch/deepl-document-translator-for-slack/tree/main)

#### AWS Lambda

また、AWS Lambda などの Function as a Serice の環境で動かしたい場合は、翻訳の結果を待つバックグラウンドの処理がありますので、今のコードのままでは動作しません。

まず、`App` のコンストラクターで `process_before_response=True` を指定します。

```python
app = App(token=os.environ["SLACK_BOT_TOKEN"], process_before_response=True)
```

次に `reactions_add` イベントのリスナーを以下のように変更して [Lazy Listeners](https://slack.dev/bolt-python/ja-jp/concepts#lazy-listeners) を使うようにしてください。

```python
def translate_files(
    event: dict,
    context: BoltContext,
    say: Say,
    client: WebClient,
    logger: logging.Logger,
):
    # リスナーの中身はそのまま

def just_ack():
    pass

app.event("reaction_added")(
    ack=just_ack,
    lazy=[translate_files],
)
```

最後に `SocketModeHandler` から AWS Lambda 用のハンドラーに切り替えれば完了です。

```python
from slack_bolt.adapter.aws_lambda import SlackRequestHandler

def handler(event, context):
    slack_handler = SlackRequestHandler(app=app)
    return slack_handler.handle(event, context)
```

### まとめ

いかがだったでしょうか？

このアプリのテスト中に様々なファイルを翻訳してみましたが、翻訳のクオリティに改めて驚きました。また、テーブルなどを含まないようなシンプルな文書であれば、レイアウトが大きく崩れてしまうことも少なそうな印象でした。

こちらの Slack 連携は [README の手順](https://github.com/seratch/deepl-document-translator-for-slack/blob/main/readme.md)を確認していただければ簡単に動かせるようになっていますので、ぜひ試してみてください :wave: