レイアウトを崩さずPDFを日本語化する翻訳アプリを自作した話
仕事や勉強で英語の文書を四苦八苦して読んでいて辛かったので、LLMを使って翻訳アプリを自作してみました。 OpenAI互換のwebapiが動いているllama.cpp serverやLM StudioをLLMサーバーとして使えば利用可能です。 そこまでリッチなモデルを使わなくても動くのでM1 Macとかでも利用可能です。
実行環境(動作確認環境)
- LLMサーバー: Minisforum PC(Ryzen AI HX370のNPU)の上に lemonade server を使って
gemma4-it-e4b-FLMを動かしている - クライアント環境: Pythonが動いているPC
中身はシンプル
やっていることはシンプルで、PDFを分解してテキストデータを取り出し、そのテキストデータをLLMサーバーへ送信して、翻訳結果のテキストに置き換えてPDFファイルを復元する、というものです。
実装自体はLLMにやらせましたが、一度自分で企画してアプリを作ってみたかったので挑戦してみました。まあまあな出来だと思っています。
リポジトリはこちらです。
https://github.com/yshr1982/pdf_translator
というわけで、ここから先はこの pdf_translator の仕組みと使い方を解説していきます。
どんなツールか
- 英語または中国語のPDFを日本語へ翻訳
- 元のレイアウトを保持したまま新しいPDFを再構築
- 入力PDFは書き換えず、出力は別ファイルとして生成される
翻訳エンジンにはOpenAI互換のAPIを利用するため、クラウドのOpenAI APIだけでなく、OpenAI互換のエンドポイントを持つローカルLLMでも動作します。
処理の流れ(3段階パイプライン)
内部的には次の3段階で処理が行われます。
[1. PDF解析] → [2. 翻訳] → [3. PDF再構成]
PDF解析 PyMuPDFを使ってテキスト・座標・フォント・画像・罫線を抽出します。数式やコード、表などの「文章ではないブロック」は自動的に判定され、翻訳対象から除外(原文のまま保持)されます。
翻訳 段落を結合し、カラムやページをまたいで途切れた文章を1つの単位にまとめたうえで、OpenAI互換APIに送信して日本語へ翻訳します。
PDF再構成 ここがこのツールの特徴的なところで、まっさらなPDFを新規作成するのではなく、原文のPDFを複製し、翻訳対象のテキスト部分だけを消して日本語のテキストを重ねて配置します。この方式のおかげで、内部リンクや画像、数式、表、文字装飾がそのまま維持されます。また、翻訳後の文章が元のブロックからはみ出さないよう、フォントサイズや折り返しも自動調整されます。
動作環境
- Python 3.14系
- OpenAI互換APIのエンドポイント(クラウドのOpenAI、またはローカルLLM)
- macOSではシステムのPythonへの直接
pip installがPEP 668によって拒否されるため、仮想環境(venv)の利用が前提です
セットアップ
リポジトリにはvenvが同梱されているため、そのまま使うこともできます。
source venv/bin/activate python -m pip install -r requirements.txt
新しく仮想環境を作る場合は以下の手順です。
python3.14 -m venv venv source venv/bin/activate python -m pip install -r requirements.txt
使い方
基本的な実行方法
CLIから直接実行できます。
source venv/bin/activate PYTHONPATH=src python -m pdf_translator.cli.main /path/to/input.pdf --lang en
付属の起動スクリプトを使う方法もあります。venvを使ってくれるうえ、日本語フォントがなければ自動取得も試みてくれるので、こちらの方が手軽です。
./scripts/start.sh /path/to/input.pdf --lang en
--lang オプション(対応言語 / プロンプト選択)
| キー | 意味 |
|---|---|
en |
英語 |
zh |
中国語 |
mixed |
英語と中国語の混在 |
unknown |
不明 |
config.yamlのpromptsセクションでシステムプロンプトを定義している場合は、en1〜en4のようにバリアント番号を付けて指定できます(en/zh/mixed/unknown × 1〜4の最大16通り)。番号を省略すると1が指定されたものとして扱われます。未定義のキーを指定した場合は、言語ベースの組み込みプロンプトにフォールバックします。
PYTHONPATH=src python -m pdf_translator.cli.main /path/to/input.pdf --lang en2
--page-range オプション(ページ範囲指定)
開始-終了の形式(1始まり・両端を含む)で、指定した範囲のみを翻訳できます。省略した場合は全ページが翻訳対象です。
PYTHONPATH=src python -m pdf_translator.cli.main /path/to/input.pdf --lang en --page-range 3-8
出力について
- 出力PDFは入力ファイルと同じディレクトリに生成されます
- ファイル名は
元ファイル名_jp.pdf - 出力は常に全ページを含みます。
--page-rangeを指定した場合、範囲外のページは原文のまま出力されます config.yamlでdebug: falseの場合、中間生成物を置く一時ディレクトリは処理後に自動削除されます
設定ファイル(config.yaml)
設定はリポジトリ直下のconfig.yaml(固定パス)で行います。雛形としてconfig_sample.yamlが用意されています。
llm: provider: "openai" # 現状は "openai" 固定 base_url: "https://api.openai.com/v1" # クラウド or ローカルLLMのエンドポイント api_key: "sk-xxxxxxxx" # ローカルLLMではダミー文字列でよい model: "gpt-4o-mini" # 使用モデル名 timeout_seconds: 60 max_retries: 3 # 失敗時のリトライ回数 translation: split_threshold_chars: 500 # この文字数を超える文書をLLM送信前に分割 merge: # 段落結合の設定(セクションごと省略可) enabled: true # false で段落結合・チェーンを無効化 paragraph_gap_ratio: 0.5 # 縦隣接ブロックを結合する行間しきい値(×font_size) font_size_tolerance: 0.5 # 同一段落とみなすフォントサイズ差の許容(pt) full_width_ratio: 0.6 # 全幅ブロック判定のしきい値(テキスト幅比) # prompts: # 任意: システムプロンプト上書き debug: false # true: 一時フォルダを残す / false: 削除
llmとtranslationは必須セクションで、欠けるとConfigErrorになります。merge・prompts・debugは省略可能で、省略時はデフォルト値が使われます。
よく変更しそうな設定項目
- クラウド ↔ ローカルLLMの切り替え:
llm.base_urlとllm.model(必要に応じてapi_key)を変更するだけで切り替えられます。ローカルLLMはOpenAI API互換のエンドポイントを想定しており、api_keyはダミーで構いません。 - 分割単位の調整: 長文をLLMに送る際の分割文字数は
translation.split_threshold_charsで変更できます。 - 段落結合の調整・無効化:
merge.enabled: falseにすると段落結合と継続チェーンを無効化できます。結合の積極性はparagraph_gap_ratio(値が大きいほど結合しやすい)やfont_size_toleranceで調整可能です。 - 翻訳の作風・専門性の切り替え: システムプロンプトを差し替えることで対応します(後述)。
- デバッグ:
debug: trueにすると、parsed.json・merged.json・translated.jsonや切り出し画像といった中間生成物が、入力PDFと同じ場所の隠し一時フォルダ.<名前>_tmp/に残り、DEBUGログも出力されます。
システムプロンプトのカスタマイズ
翻訳時のシステムプロンプトは、config.yamlの任意セクションpromptsで上書きできます。キーは<en|zh|mixed|unknown><1-4>(例: en1〜en4)で、--lang引数で選択します。
prompts: en1: "あなたは翻訳者です。..." zh1: "あなたは翻訳者です。..."
config_sample.yamlには、法律・CS/数学/物理・SF文学・地理歴史という4つのプリセット例(en1〜en4)が用意されているので、文書のジャンルに応じて使い分けることができます。
テスト
同梱のテストスイートは以下のコマンドで実行できます。
source venv/bin/activate PYTHONPATH=./src pytest tests/ -v
README記載時点では137件すべてがパスしていると報告されています。結合テスト(tests/test_pipeline.py)では、バンドルされた論文PDFを実際に翻訳(LLM部分はスタブ化)し、ページ数・リンク復元・画像や描画の一致・原文の除去・日本語出力・数式や表の保持・テキストのはみ出しがないことなどを検証しているとのことです。
主な依存パッケージ
- PyMuPDF(
pymupdf) — PDFの解析・再構成 - OpenAI — 翻訳API呼び出し
- PyYAML — 設定ファイルの読み込み
- Pillow — 画像処理
- fonttools — フォント関連処理
- pytest — テスト
まとめ
pdf_translatorは、単に翻訳文を流し込むだけでなく、「原文PDFを複製して該当箇所だけ置き換える」というアプローチによって、レイアウト崩れを最小限に抑えているのが大きな特徴です。数式やコード、表を自動で翻訳対象から除外してくれる点も、技術文書や論文を読む場面では嬉しいポイントだと思います。
クラウドのOpenAI APIだけでなくローカルLLMにも対応しているので、翻訳したい文書の機密性に応じてエンドポイントを使い分けられるのも魅力です。英語・中国語の資料を日本語で読みたいときに、ぜひ試してみてはいかがでしょうか。







































