« ^ »

translate-shellとEmacsを使って翻訳する

所要時間: 約 6分

translate-shellを用いるとCLIで翻訳ができる。今回はこれを用いてEmacsからどのように翻訳機能を実装できるのか考える。

Emacs用の拡張もあるが、今回はコマンドの使い方も含めて実装方法を考えたいのでこの実装は使わない。

translate-shellをインストールする

translate-shellをインストールする。READMEにいくつかのインストール方法が記載されている。Homebrewも用意されておりそちらからのインストールが楽だったので今回はHomebrewを用いた。

https://formulae.brew.sh/formula/translate-shell#default

brew install translate-shell

インストールが完了するとtransコマンドが使えるようになる。

Usage

Usageを見ると以下のオプションが用意されている。

Usage:  trans [OPTIONS] [SOURCES]:[TARGETS] [TEXT]...

Information options:
    -V, -version
        Print version and exit.
    -H, -help
        Print help message and exit.

〜省略〜

See the man page trans(1) for more information.

それなりに自由度が高そうにみえる。

簡単な翻訳を実行してみる

例えばHelloという単語を英語から日本語に翻訳する場合は次のコマンドを実行する。

trans en:ja 'Hello'
Hello

こんにちは
(Kon'nichiwa)

Hello の定義
[ English -> 日本語 ]

感嘆詞
    もしもし!
        Hello!
    今日は!
        Hi!, Hello!, Good afternoon!, Good day!

Hello
    こんにちは, もしもし

標準入力に翻訳する文字列を渡す

transコマンドは標準入力からの文字列も受け付ける。

echo 'Do not load any initialization script.' | trans en:ja
Do not load any initialization script.

初期化スクリプトをロードしないでください。
(Shokka sukuriputo o rōdo shinaide kudasai.)

「Do not load any initialization script.」の翻訳
[ English -> 日本語 ]

Do not load any initialization script.
    初期化スクリプトをロードしないでください。, 任意の初期化スクリプトをロードしないでください。

プロセスを事前に起動しておいて標準入力に適宜翻訳文字列を渡す

前述の方法では翻訳を実行するたびにtransコマンドを実行しプロセスを起動した。それではプロセスの起動のためのオーバーヘッドが大きいと考え事前に起動しておく方法を試す。

まずはmake-processでプロセスを起動する。

  (make-process :name "trans"
                :buffer (get-buffer-create "*Trans Process*")
                :command '("trans"
                           "en:ja"
                           "-show-original" "n"
                           "-show-prompt-message" "n"
                           "-brief"
                           "-no-pager"
                           "-no-ansi"
                           ))
make-processでプロセスを起動する

式を評価するとtransコマンドが実行されプロセスが起動する。このプロセスは終了することなく入力待ちの状態となる。

実行する方法として都合が良いようにオプション引数を調整した。

指定した引数意味
"en:ja"英語から日本語へ翻訳する。
"-show-original" "n"オリジナルの文字列を表示しない。
"-show-prompt-message" "n"プロンプトメッセージを表示しない。
"-brief"辞書ではなく簡単な翻訳を表示する。
"-no-pager"ページャーを使わない。
"-no-ansi"色をつけるなどの制御コードを使わない。
指定したオプション

このプロセスの標準入力に翻訳したい文字列を渡す。プロセスの取得には get-process 関数を、プロセスの標準入力へのデータの送信には process-send-string 関数を用いる。

  (process-send-string (get-process "trans")
		     "Yay\n")
翻訳文字

式を評価するとバッファには次のように出力される。

わーい
Yayの翻訳結果

transコマンドは文章内に改行をつけることで翻訳を実行し結果を表示した後、入力待ちの状態に戻る。改行を含めないと翻訳を実行せず入力を待つことになる。

翻訳結果を一時バッファに表示する

プロセス用のバッファに翻訳結果を出力していたが、結果が残り続けるため次回の翻訳結果と混ざってしまう。Emacsでは一時的なバッファを使って値を表示するための関数 with-output-to-temp-buffer が用意されている。この関数はEmacs内でhelpの表示などでもよく使われている。今回はこの関数を利用することにする。

標準出力への出力を取得して処理を行うにはプロセスフィルターを設定する方法がよく使われる。今回の場合、標準出力のデータを with-output-to-temp-buffer で作成した一時バッファに出力するようなフィルター関数を用意すれば良い。

  (defun trans-output (process output)
    (with-output-to-temp-buffer "*Trans Result*"
      (princ output)))
フィルター関数の例

フィルター関数は通常 make-process 関数の呼び出し時に設定するが set-process-filter 関数を使って後からでも設定できる。

  (set-process-filter (get-process "trans") #'trans-output)
フィルター関数を設定する

フィルターに渡される文字列はマルチバイト文字の可能性があるので set-process-filter-multibyte も設定する。

  (set-process-filter-multibyte (get-process "trans") t)
マルチバイト文字のための設定

再度翻訳を実行すると今度は *Trans Result* というバッファが作成され翻訳結果が表示される。

  (process-send-string (get-process "trans")
		     "Yay\n")
翻訳文字

q をタイプするとバッファは削除される。

複数行に渡る文字列の翻訳を行う

改行が入った文字列を渡されると改行ごとに翻訳が実行されてしまう。これは若干不便だ。例えば1つの文の中に改行が入っていた場合、文章がぶつ切りになった状態で翻訳されてしまう。

例えば以下のような文章を翻訳使用とする。改行は1行目の「これは」の後ろ、2行目の「文章がぶつ切りに」の後ろ、3行目の「翻訳されてしまう。」の後ろとなる。

改行が入った文字列を渡されると改行ごとに翻訳が実行されてしまう。これは
若干不便だ。例えば1つの文の中に改行が入っていた場合、文章がぶつ切りに
なった状態で翻訳されてしまう。

しかしこんなところでぶつ切りにされて別々に翻訳しても意味のある文章にはならない。そのためプロセスに文字を渡す前に改行文字を削除してしまう必要がある。今回は改行文字がある場所は半角スペース1つに置換することとした。

  (defun trans-cleanup-sentence (sentence)
    (with-temp-buffer
      (insert sentence)
      (delete-trailing-whitespace)
      (replace-regexp "\n" " " nil (point-min) (point-max))
      (buffer-substring-no-properties (point-min) (point-max))))
改行文字がある場所は半角スペース1つに置換する

一時バッファを作成し値をバッファに書き込み置換処理をした後バッファ内のの文字列を返している。 すべての改行文字を置換するため最後に改行をつける。

  (process-send-string (get-process "trans")
                     (concat (trans-cleanup-sentence
                      "Yay\nHello\n") "\n"))
翻訳文字

実行すると次のような *Trans results* バッファに次のように表示される。

イェーイこんにちは
翻訳結果

これで期待通りの結果が得られた。

プロセスを終了する

起動中のtransコマンドのプロセスはSIGTERMを送信することで停止する。

  (signal-process (get-process "trans") 15)  ;; Send SIGTERM
プロセスにSIGTERMを送る

1kBの制限

1kBより大きいデータはコマンドが受け付けないようだった。 どうやらAPIあたりに制限があるようだ。

パッケージ化した

諸々を調査したので練習用に簡単なパッケージにした。Githubで置いておく1。ただの練習用だしそもそも本家がもっと良い実装を用意しているのでMELPAには上げていない。

https://github.com/TakesxiSximada/trans.el


1

以前はGistに置いていたが、管理上の理由からGithubに移動した。元のGistはこちら[[https://gist.github.com/TakesxiSximada/0a849059d1fb61de397f57477ed38c92]]。