必要な部分だけを DeepL API で翻訳して置き換える Greple モジュール

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/bdbc6413-be82-92b0-8201-6981313484de.png)


## deepl コマンド

DeepL という精度の高い翻訳サービスがあります。ウェブに翻訳したい文章を入れると、翻訳結果を表示してくれます。Mac にアプリをインストールしていると、Command-C を2回叩くとカットバッファの内容を翻訳してくれて便利です。

とは言っても、いちいち画面を切り替えるのがちょっと面倒だと思っていたら、最近 API を通してコマンドラインから利用する方法が公開されていることに気がつきました。

https://github.com/DeepLcom/deepl-python

このツールをインストールすると `deepl` というコマンドが使えるようになって、コマンドラインからテキストを翻訳することが可能になります。

```sh
deepl text --to JA 'even monkeys fall from trees'
さるもきからおちる
```

`document` というサブコマンドを使うと、ファイルの内容を翻訳することもできます。何も考えずにドキュメントを含むプログラムを対象にすると、こんな結果になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/75730530-462e-688a-8904-f06e755967f4.png)

奇妙な顔文字や不必要な部分を翻訳してしまっているのはともかくして、日本語がなんだか変です。これは、元の文章の途中に改行が入っているためです。ウェブやアプリで使う際にも、改行を取り除かないとうまく翻訳できません。テキストファイルの内容を翻訳しようとすると、どうも一手間二手間かかるのがストレスです。

## greple -Mxlate::deepl

https://github.com/kaz-utashiro/App-Greple-xlate

そこで greple コマンドを使って、ファイルの中の指定した部分を `deepl` コマンドで翻訳した結果に置き換えるモジュールを作りました。

次の画像はこのモジュールを実装しているファイルの中身です。POD という形式でドキュメントが記述されています。この内容を翻訳することを考えると、プログラム部分は当然として、POD のキーワードなども外して、必要な部分だけを対象にする必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/76325541-bff7-b59f-f519-c8a8787b6eb9.png)

POD ドキュメントは先頭が `=` の行から始まり `=cut` で終わるので `^=(?s:.*?)(^=cut|\z)` というパターンで表せます。ドキュメント内の地の文は行の先頭からはじまるので、`^(\w.*\n)+` というパターンで文章部分を選ぶことができます。文字から始まる行の連続という意味です。greple の `--inside` オプションで範囲を指定して検索すると次のようになります。

```sh
greple --all --ci=A --inside '^=(?s:.*?)(^=cut|\z)' '^(\w.*\n)+'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/5acc9285-d74c-0f8b-9a36-72fb085bc00c.png)

うまく対象部分が選べているようです。この例では、`=encoding` より前の部分は検索対象に入っていないので選ばれません。また、`=` で始まる POD コマンドやインデントしている部分はマッチしません。

`--all` はファイル全体を表示。`--ci=A` はマッチした部分毎に異なる色を割り当てます。そうしないと、連続しているテキストが1行ずつマッチしているのか、全体でマッチしたのか区別できません。

<!--

実は、greple には `-Mperl` というモジュールが同梱されていて、`--pod` というオプションで POD 部分だけを検索対象とすることができます。

```sh
greple -Mperl --pod --all --ci=A --re '^(\w.*\n)+'
```

-->

## greple -Mxlate::deepl

このファイルを greple の `xlate::deepl` モジュールで翻訳してみます。このモジュールには POD のドキュメント部分を選ぶための `--match-podtext` というオプションが予め定義されています。

```
greple -Mxlate::deepl --match-podtext
```

のように実行すると、上と同じ結果が出るので、どこが処理対象になるかを事前に確認することができます。

### --xlate

範囲に問題がなければ `--xlate` オプションをつけて翻訳します。

```
greple -Mxlate::deepl --match-podtext --xlate --xlate-fold --xlate-format=none
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/68208e1d-4871-bbbe-b38c-893d7977bddd.png)

`--xlate-fold` は行を折り返すためのオプションで、なくても構いません。`--xlate-format=none` は出力形式に `none` を指定し、対象部分を翻訳結果で置き換えます。

### --xlate-format=conflict

`--xlate-format` を指定しないとこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/6fc6528f-853e-35f5-b9da-64e618a6924b.png)

デフォルトの出力形式は `conflict` で、対象となる部分の原文と翻訳結果の両方を git の conflict marker の形式で出力します。DeepL の翻訳はかなり正確ではあるものの、すべて正しいとは限らないので原文を見ながらでないとやはり不安です。

```
<<<<<<< ORIGINAL
App::Greple::xlate - translation support module for greple
=======
App::Greple::xlate - greple 用の翻訳サポートモジュール
>>>>>>> JA
```

### --xlate-format=ifdef

`ifdef` 形式で出力することもできます。

```
#ifdef ORIGINAL
App::Greple::xlate - translation support module for greple
#endif
#ifdef JA
App::Greple::xlate - greple 用の翻訳サポートモジュール
#endif
```

こうすると、`unifdef` コマンドで簡単に必要な部分を取り出すことができます。

## sdif -V

https://github.com/kaz-utashiro/sdif-tools

conflict marker 形式のファイルを見やすく表示したくなったので、diff 出力を並べて表示する `sdif` コマンドを対応させました。実は `cdif` は以前から対応していたのですが、`sdif` の必要はあまり感じられなくて未対応でした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/2093939d-72b1-9f35-aa28-62fcbef1818a.png)

sdif は、diff 形式ではないデータはそのまま表示します。これでも悪くはないのですが、diff 以外のテキストは共通部分として扱うための `-V` オプションを追加しました。

```sh
greple -Mxlate::deepl --match-podtext --xlate --xlate-fold | sdif --no-cdif -V | less
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/d1f08793-60b5-4469-0148-496156dc127d.png)

こちらの方が、かなり読みやすく感じられます。`--no-cdif` は、単語単位の差分を表示するための `cdif` を使わないためです。校正ならともかく、翻訳文章だとうるさいだけで役に立ちません。

次の画像は `-Mxlate::deepl` モジュールを使って韓国語と中国語に変換した2つのファイル比較して `sdif` で表示したものです。ほとんど同じように見えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/dc5d489b-addd-7037-fd23-3afe76ed4064.png)

ちなみに、これらのファイルを再び DeepL で変換すると、ちゃんと意味の通じる日本語になって出てくるのですごいです。

## Emacs から使う

Emacs から使うのも簡単です。コマンド一発で変換できるようにしたので `shell-command-on-region` を直接使ってもなんとかできますが、オプションを覚えるのとか面倒なので `xlate.el` というマクロを作りました。これを読み込んで、選択した領域に対して `xlate-region` マクロを実行すると、次のような結果になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36551/dbc55e32-d01b-4253-80a2-361c57e59e99.png)

下手な英語でドキュメントを書いていると、ちゃんと意味が伝わるか不安になることがあります。そんな時にちょっと確認するのに便利です。安心したら `undo` すれば元に戻ります。

## キャッシュ

DeepL を API で利用するためにはアカウント登録が必要です。フリーアカウントでも認証キーが発行できるので、それを使って `deepl` コマンドを利用することができます。ただし、月間50万文字が上限です。コマンドのテストということもあって、何度も同じ文章を変換する必要があり、だんだんもったいなくなってきたのでキャッシュの仕組みを実装しました。

`xlate.pm` というファイルなら `xlate.pm.xlate-deepl-JA.json` というファイルに、JSON 形式で原文と翻訳結果を格納するようになっています。

`--xlate-cache` オプションでキャッシュ方針を指定できます。デフォルトは `auto` で、キャッシュファイルがすでに存在すれば、それを読み込んで更新します。なければ何もしません。最初に `--xlate-cache=create` で空のキャッシュファイルを作っておくと、後は自動的に管理されます。

実装してみると、開発用途ではなくても有用なことがわかってきました。同じファイルを異なる形式で表示したいと思うことは結構あって、キャッシュのおかげで、一度変換しておけば次回からは瞬時に処理が終わります。

## 他の形式のファイルを処理するためには

この記事では POD 形式の文書ファイルの必要な部分だけを翻訳する方法について紹介しました。

異なる形式のデータに適用するためには、そのための準備をする必要があります。greple の `--inside`, `--outside`, `--include`, `--exclude` オプションを使うと、対象となる領域をパターンで絞り込むことができます。また、これらのオプションや検索パターンには関数を指定することもできるので、パターンマッチでは表現できないような複雑な書式にも対応することが可能です。

https://qiita.com/kaz-utashiro/items/8783c2fd0cc4315b9a3d

https://qiita.com/kaz-utashiro/items/84f5a6be6bf996076c64

greple は、テキストファイルだけではなく Word や PDF のファイルも対象にできるので、結果を見るだけならある程度使い道はあると思います。使い方について不明な点などあれば、お尋ねいただければ可能な範囲で対応します。もし開発作業が必要ならご相談にのります :-)。

https://qiita.com/kaz-utashiro/items/30594c16ed6d931324f9

Greple に関しては、Hugo と docsy テーマを使って、こんなページを作ってみました。まだ作りかけですが、マニュアルや Qiita のアドベントカレンダーに書いた記事などを置いてあります。

https://tecolicom.github.io/Greple/

## 今後の計画

実は、このモジュールは翻訳のためではなく、DeepL が最近始めた校正サービスに使えないかと思って作り始めたものです。現在は無料ウェブサービスでしか使えないようなので、API から利用できるようになったら、このモジュールをベースに対応しようと考えています。

今は、Python 実装の deepl コマンドを使っていますが、直越 API を叩くことも可能です。異常ケースを考えなければそんなに難しくはなさそうですが、サーバとのトランザクションに比べると効果は限定的なので、当面は様子見でしょうか。

2023-02-03 に更新した Version 0.04 では、`deepl` コマンドを一度だけ実行して、すべての翻訳を一回ですませるようにしました。これによって、初回の実行速度が大幅に改善しました。また、キャッシュを使わなくても、実用的な時間で結果を得ることができます。このために、`greple` に `--postgrep` というオプションを追加してあります。

この記事を書いてから、`-Mxlate` モジュールをより簡単に使うための `xlate` というシェルスクリプトとポータブルな Docker 環境を作りました。これについては以下の記事に書いてあります。

https://qiita.com/kaz-utashiro/items/1b9e155d6ae0620ab4dd

## 関連記事

https://qiita.com/masatoi0/items/26c7384749393a1bf8fb

https://qiita.com/minoruGH/items/ebbe88f13f8350e884d6

https://qiita.com/koshian2/items/0c695274ef82e170adc5