【超長文対応】AzureとPythonで長文を一瞬で翻訳する方法【面倒なコピペ排除】【200万文字まで無料】

# はじめに
初めまして！この秋からkatolabに配属されました、ばしと申します。
katolabの[医工連携研究プロジェクト](https://www.katolab.nitech.ac.jp/html/research_screening_cognitive_impairment.html)への参加を希望していて、そのために論文を読んで勉強中です！

さて、みなさんは英語論文や英語の記事などの翻訳の際に、多くの翻訳サービスに設けられている文字数制限にイライラしたことはないでしょうか？
例えばDeepLであれば、無料で翻訳できるのは一括では最大5000字とかなり強めに制限がかかっています。
これに対し例えば一本の論文を全部翻訳しようとすると、5ページで2万字とかは当たり前で、全文翻訳しようとすると複数回のコピペを繰り返すことになります。控えめに言って超ダルいです。

そこで私はアドベントカレンダーに参加するにあたって、論文調査の中で誰もが一度はぶち当たるであろう「翻訳文字数制限」の問題を緩和することのできるプログラムを作成してやろうと企みました。
その結果としてそれなりの完成度のプログラムと、APIを使ってみる経験みたいなものを得ることができたので正直すっごく満足しております。^_^

というわけで今回は、PythonとPowerShellを用いてAzure AI Translator APIを呼び出し、面倒なコピペ作業を可能な限り自動化した翻訳プログラムを書いてみました。
DeepLには現状対応していないので、DeepLに対応するようなコードを書いたら追記したいと思っています。

:::note
本記事でわかること
- Azure AI Translator APIの基本的なセットアップ
- クリップボードに訳文をコピーするまでの工程の快適化方法
- 実際に作成したプログラム
:::

### 環境
※本記事で用いている環境は以下の通りです。
> - Windows11 23H2
> - Python 3.12.2
> - PowerShell 7.4.6
> - Azure for Students



# Azure AI Translator API
Azure AI Translator APIはMicrosoftが提供している翻訳APIです。
特徴はなんと言っても無料枠の大きさで、月に200万字を無料で翻訳できます。（「字」なのでLLMと違い漢字なども1文字扱い）

というわけで、まずはAzureにアクセスしてAPIキーを取得するところから始めましょう。

:::note warn
注意
もし次項でAzureにアクセスするところから躓かれた方は、下に示す記事を読んで先にAzureの環境構築を行うことを推奨します。
[Azure サブスクリプション を サインアップする #初心者 - Qiita](https://qiita.com/carol0226/items/066c06d95cb68427f1d0)
:::

## AzureからAPIキーを取得する
1. まずは[Azure AI services](https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/overview)にアクセスしましょう
ポータル上の「翻訳」から「作成」を選択してリソースを作成できます。
![スクリーンショット 2024-12-18 003449.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/760d61ca-8b29-8b08-2f57-c9d0e5ca73b5.png)
2. 新規作成画面ではリソースグループを選択または新規作成
リージョンは"Japan East", 名前を自分で設定、価格レベルはFree(F0)を設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/6a2bd9a4-cccb-c7d8-b9b2-8a8ec344890e.png)
3. 作成したTranslatorの「キーの管理」を開いて
![スクリーンショット 2024-12-18 010921.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/be89ad5d-b810-ee8a-7cb8-194fa6b01f8c.png)
APIキーとエンドポイントをコピペしちゃいましょう！
![スクリーンショット 2024-12-18 011346.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/4a21a142-524f-1534-98ee-7dfab38db0d3.png)
これでAzure側の準備は完了です。

## APIキーを環境変数にセット
PowerToysまたはコントロールパネルから環境変数の編集画面を開きます。（今回PowerToys）
![スクリーンショット 2024-12-18 012145.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/9e2d5316-3144-5961-318e-d787945b2b29.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/d8becbdd-202b-fd0a-9bb3-5b626de25a79.png)
好きなだけ登録したって構わない。

# プログラム
## 作りたいものの確認
今回は以下のように役割分担を行ってプログラムを作成しました。
- Python
    - テキストファイルから原文を取得
    - APIの呼び出し
    - APIから帰ってきたJSONから必要な部分を取り出す
        - Markdownの見出しデコレーションなどについておかしくなっている部分を修正
    - テキストファイルに訳文を書き込み
- PowerShell
    - Pythonファイルを起動
    - 訳文をテキストファイルから読み出してクリップボードにコピー
    - （将来的には）使用する翻訳APIを変更？

シーケンス図で示すと以下のようになります。

```plantuml
participant ユーザー
participant ファイル
participant PowerShell
participant Python
participant AzureAPI

ユーザー -> PowerShell: `azure_translate.ps1`を引数付きで実行
PowerShell -> Python: `azure.py`を引数付きで呼び出し
ファイル -> Python: 翻訳されたテキストをファイルから読み込む
Python -> AzureAPI: 翻訳リクエストを送信
AzureAPI -> Python: 翻訳されたテキストを返す
Python -> ファイル: 翻訳されたテキストをファイルに書き込む
Python -> PowerShell: 「次のコマンドへ移る」
PowerShell -> ユーザー: 翻訳されたテキストをクリップボードにコピー
```

一連のプログラムで2種類の処理を行っている関係で汚くなってしまっていますが、基本的にはPowerShellで読み出したPythonファイルを用いて翻訳をしたのち訳文をテキストファイルに出力、テキストファイルの内容をPowerShell側でクリップボードにコピーするような形です。

## ディレクトリ構成
ディレクトリ構成は以下のようになります。
```
<project_root>
  ├ azure.py
  ├ azure_translate.ps1
  └sbj/
    ├ ... 
    ├ ...
    ├ ...
    ├ hoge.txt
    └ hoge_translated.txt
```
:::note alert
ディレクトリ名を相対パスとはいえベタ書きしてしまっているせいで、プロジェクトルートのディレクトリの直下に`azure.py`と`azure_translated.ps1`、その中に`sbj/`というディレクトリを作成してその中にソースファイル作成、という形でないと正常動作しない形となってしまっています。
:::

## Python
今回作成した`azure.py`は以下の通りです。

```python:azure.py
import os
import sys
import re
import requests
import uuid

# APIキーを指定
KEY = os.getenv('AZURE_TRANSLATOR_API_KEY')

def translate(text, fn = 'default'):
    """テキストをAzure AI Translator APIにより翻訳する
    Args:
        text (str): 翻訳対象のテキスト
        fn (str): 出力ファイル名から拡張子を除いたもの
    """
    # APIエンドポイントを指定
    endpoint = "https://api.cognitive.microsofttranslator.com/"

    # リクエストのパラメータを指定
    location = "JapanEast"
    path = '/translate'
    constructed_url = endpoint + path

    params = {
        'api-version': '3.0',
        'from': 'en',
        'to': ['ja']
    }

    headers = {
        'Ocp-Apim-Subscription-Key': KEY,
        'Ocp-Apim-Subscription-Region': location,
        'Content-type': 'application/json',
        'X-ClientTraceId': str(uuid.uuid4())
    }

    body = [{
        'text': text
    }]

    # リクエストを送信して戻り値のJSONを取得
    request = requests.post(constructed_url, params=params, headers=headers, json=body)
    response = request.json()

    # 翻訳結果をファイルに保存
    file_name = 'sbj/'+fn+'_translated.txt'
    with open(file_name, 'w', encoding='Shift-JIS') as f:
        fixed_text = insert_space_after_hash(response[0]['translations'][0]['text'])
        f.write(fixed_text)

def insert_space_after_hash(text):
    # 正規表現で '\n#' の後に空白以外の文字が来る部分を見つけて、空白を挿入
    return re.sub(r'(\n#)(\S)', r'\1 \2', text)

# ファイルを読み込んで翻訳
fnDecorate = sys.argv[1]
sourcefile = 'sbj/'+fnDecorate+'.txt'
with open(sourcefile, 'r',encoding='UTF-8') as f:
    text = f.read()

translate(text, fnDecorate)
```
GitHub Copilotと二人三脚で、Azureから取得できるサンプルプログラムをもとに頑張りました。
Windows環境なのもあり、入力に使うファイルはVSCodeを使って作成する想定で`encoding="UTF-8"`を、出力ファイルはPowerShell側の関係もあり`encoding="Shift-JIS"`を指定。この部分を追記するまではいちいちVSCodeで一度作成したファイルを「エンコード付きで保存」により保存し直していました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/095a208c-8bee-554f-9d4d-3d940ccdb210.png)
![スクリーンショット 2024-12-18 025036.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3125104/a70f1693-3938-b6c7-71ff-93208e481d53.png)

また、文字列中にMarkdownのH1タグに特有の`\n#`という部分（つまり改行直後に#一個）が入っていた場合、その直後に空白が無いことを正規表現で突き止めた場合、その部分に半角スペースを1回だけ入れるようにしました。
これによりAPI側の問題でうまく"#"の後ろに空白が入らなかったとしても、うまく文字列として加工できるようになっています。

## PowerShell
今回作成した`azure_translate.ps1`は以下の通りです。

```powershell:azure_translate.ps1
# コマンドライン引数からファイル名を取得
$output = "sbj\" + $Args[0] + "_translated.txt"
python azure.py $Args[0]

# 翻訳終了！！
Write-Output "translation completed."

# クリップボードにコピー
Get-Content -Encoding Shift-JIS $output|Set-clipboard
```

コマンドライン引数からファイル名の拡張子以外の部分を受け取っています。
これによりPython実行のコマンドライン引数渡しとパイプ処理によるクリップボードへのコピーを容易にしています。

## 実行例
```powershell:実行例
~\translate> ./azure_translate.ps1 sample
translation completed.
# この時点でクリップボードに訳文がコピーされている
```

# まとめ
実は某サービスを使うことを想定しているんですが、普通に営業妨害すぎる内容な気がしたので明記は避けます。
自分用に、ホームディレクトリからプログラムがあるディレクトリまで一発で移動し、ついでに実行すべきコマンドを生成するような`PowerShell`ファイルも作成しています。

完走した感想ですが、マジで時間かかっただけのことはありました。これからこのプログラムを中心にいくつかのツールを使って大量の論文をアブストだけでも斜め読みできたらとか妄想しています。（知見ほしい|:3ミ）

この記事で少しでも多くの人が英語論文を一瞬で読めるようになると嬉しいです。

加藤昇平研究室では感性工学・医工連携・ロボティクス・人工生命の4つの研究テーマを持つ班が連携ながら活動しており、先輩方が日夜研究に励んでいらっしゃいます。
よろしければ[公式HP(katolab.nitech.ac.jp)](https://www.katolab.nitech.ac.jp)をご覧ください！

この記事を少しでもいいな( *´艸｀)って思ってくださった方、よければ「いいね！」もお願いします！

katolab所属B3のばしがお送りしました！