Topics

Google翻訳APIをやめてLLM翻訳に作り直した話 ― Claude API + R2差分キャッシュで多言語化

  • column

以前、Astro SSGサイトをGoogle翻訳APIで多言語化してみたという記事を書きました。Google Cloud Translation APIを使い、ビルド後の静的HTMLを翻訳して各言語版を生成する仕組みです。費用を抑えた簡易多言語化としては十分実用的でした。

ただ、しばらく運用していると、SEOと翻訳品質の両面でどうしても気になる点が出てきました。「なぜGoogle翻訳をやめたのか」という話は別記事を見てみてください。

記事:機械翻訳をGoogle翻訳からClaude APIへ移行

この記事は 「じゃあ実際どう作り直したのか」という実装の話です。エンジンをGoogle翻訳からLLM(Claude API)に載せ替え、ついでに前回の積み残しだった運用の手作業も一掃しました。

ビルド時にLLMで翻訳し、R2に差分キャッシュ

基本方針は前回と同じで、翻訳はビルド時(サーバー側)で完結させます。ブラウザからClaude APIを呼ぶようなことはしません。違うのは、翻訳エンジンとキャッシュの置き場所です。

ビルドフローはこうなっています。

npm run build
  ├── fetch-microcms   (microCMSから記事データ取得)
  ├── astro build      (日本語HTMLを生成 → dist/)
  ├── translate        (各ロケールのHTMLを生成 → dist/{locale}/)
  └── update-xml       (sitemap更新)

translate の中身(translate-html-llm.mjs)がやっていることは、おおまかにこの順番です。

  1. Cloudflare R2 から翻訳キャッシュ(単一のJSONファイル)をダウンロード
  2. dist/ 配下の各HTMLを cheerio で読み込み、翻訳すべきテキストを抽出
  3. キャッシュにあればそれを使い、無いものだけ Claude API に投げる
  4. 翻訳結果でテキストを差し替え、dist/{locale}/ に書き出す
  5. 新しく翻訳した分をキャッシュにマージして R2 にアップロード

ポイントは「変わったところだけ翻訳する」差分翻訳と、そのキャッシュを R2 に置くことの2つです。

① インラインタグをまたぐ訳をどう自然にするか

今回いちばん改善したかったのがこれです。

たとえば本文にこういうHTMLがあるとします。

<p>私たちは<strong>ウェブアクセシビリティ</strong>を重視しています</p>

普通に翻訳を処理すると、私たちは / ウェブアクセシビリティ / を重視しています という3つの断片を別々に訳すことになります。日本語と英語では語順が違うので、訳した断片を元の位置に戻すと <strong> がかかる場所がずれたり、そもそも文として不自然になったりします。インラインタグが多い文ほど崩れる。これが旧仕組みの一番の不満でした。

改善方法は以下の2段構えです。

1つ目:ブロック要素単位でチャンク化する。 翻訳の最小単位を「タグの隙間」ではなく、p h1〜h6 li td blockquote といったブロック要素の中身(innerHTML)まるごとにします。文を分断しません。

2つ目:インラインタグをマーカーに置換して、文全体を1つの翻訳単位として渡す。 チャンクの中の <strong> や <a> を、いったん [[T1]]...[[/T1]] のようなマーカーに置き換えます。

私たちは[[T1]]ウェブアクセシビリティ[[/T1]]を重視しています

LLMには「これは1つの文。自然に訳したうえで、強調すべき語に同じマーカーを付け直してよい。マーカーの位置は訳語の語順に合わせて動かしてよい」と指示します。訳が返ってきたら、マーカーを元の <strong> や <a href="..."> に復元します。属性(href や class)はそのまま持ち回ります。

これで、<strong> が英語側でも正しい単語にかかり、文としても自然になりました。

② 差分翻訳とR2キャッシュの設計

毎回すべてを翻訳していたらコストがいくらあっても足りません。前回も translate-cache.json というキャッシュを持っていましたが、今回はキーの作り方と保存場所を見直しました。

プロンプトを直したら訳も作り直す仕組み

キャッシュのキーは「元の日本語 + ロケール + 翻訳プロンプトの内容」を組み合わせて作っています。こうしておくと、元の日本語が変わればそのエントリだけ、翻訳の指示や用語方針(プロンプト)を変えれば全エントリが、自動で「キャッシュに無い」扱いになって翻訳し直されます。

狙いは「プロンプトを改善したのに、古い訳がキャッシュに残り続ける」事故を防ぐことです。実際にはこれらを sha256 でハッシュ化した文字列をキーにしています。

キャッシュの中身はこんな構造です。

{"<sha256のキー>":{"value":"翻訳結果(マーカー込み)","locale":"en","model":"claude-haiku-4-5-20251001","translatedAt":"2026-06-12T..."}}

キャッシュをR2に置いて、手作業を撤廃した

ここが前回の積み残しの回収です。

前回はキャッシュをリポジトリ内のJSONファイルで管理していました。そのため、CMSで記事を追加してデプロイフックでビルドすると、サーバーはGit上の古いキャッシュしか見られません。仕方なく「記事を足したらローカルでビルドして、更新されたキャッシュファイルをGitにpushする」という運用ルールでしのいでいました。

今回はキャッシュを Cloudflare R2 上の単一JSON blobに置きました。ビルドのたびに R2 から取得して、終わったら書き戻します。これで webhook経由のビルドでもキャッシュが永続化 され、ローカルビルド→push の手作業が完全に消えました。記事を追加して push すれば、増えた分だけが翻訳され、キャッシュも自動で更新されます。

翻訳の優先順位

訳語のブレ(社名 Liberogic の表記揺れなど)は前回も悩みどころでした。今回は4段階で処理しています。

  1. 手動オーバーライド(data-i18n-key) ― HTML側で data-i18n-key を付けた要素は、あらかじめ用意した手書きの訳で固定します。LLMにも辞書にも任せず「ここだけは絶対この訳にしたい」という対応ができます。
  2. 用語辞書(glossary) ― ナビやページタイトルのような繰り返し出る固定ラベルは、JSONの辞書で訳語を固定します。辞書を直して再ビルドすれば即反映されます。
  3. R2キャッシュ ― 上のどちらにも無ければキャッシュを見ます。
  4. Claude API ― どれにも無いものだけ、最後にLLMへ。

本文中に出てくる用語の統一は、ピンポイントの辞書では拾いきれない(部分一致には対応していない)ので、システムプロンプト側の用語ヒントで緩くそろえます。

LLMの「癖」と付き合う

機械翻訳がおかしな訳を出すのは前回も同じでしたが、LLMにはLLMなりの癖があります。実運用で見えてきたものをいくつか紹介します。

  • 技術系の文がまるごと英語のまま残る。 API・React・Vue といった「訳さない用語」リストを過剰適用して、文全体を英語のまま返してくることがあります。ユーザープロンプトで「対象ロケールの言語で出力せよ」を強めに念押しして対処。
  • マーカーの位置が意味的に逆転する。 強調すべき語の判断を取り違えて、<strong> が変な範囲にかかることがあります。該当エントリだけ消して訳し直すと直ります。
  • CJK言語でレスポンスが途中で切れる。 漢字は1文字あたりのトークン消費が多く、まとめて投げると出力が上限に達してJSONが壊れます。max_tokens を引き上げ、1バッチの件数を抑えて対処。
  • リテラルの \n やCJK日付の崩れ。 改行が文字列 \n として混入したり、2026 年 05 月 22 日 のように余計なスペースが入ったり。これらは後処理スクリプトで一括掃除します。

ひとつ運用知として効いたのは、おかしい箇所は1件ずつ消して訳し直すのが一番成功率が高いということ。複数まとめて直そうとすると、LLMが同じ構造を一括で同じように誤判定する傾向があります。急がば回れでした。

コストの話

エンジンは Claude Haiku 4.5 に統一しています。コスト重視で、品質に問題が出たらまずプロンプトと辞書で改善する方針です。システムプロンプトには prompt caching を効かせて、毎回同じ固定部分のコストを圧縮しています。

実測の目安はこんな感じです。

内容

コスト

あるロケールの初回フル翻訳

約 $2.5〜3

全ロケールの初回フル翻訳

約 $20

通常のデプロイ(キャッシュヒットのみ)

ほぼ $0

記事を1本追加(数件の翻訳)

約 $0.01

日常のデプロイはほぼ無料、記事追加も1円〜数円となっています。初回の作り直しこそまとまった費用がかかりますが、そこを越えればランニングはむしろ前より軽くなりました。

まとめ

Google翻訳からLLM翻訳への載せ替え、結果として運用コストはあまりかからず、翻訳の質もかなり向上しました。

LLMだからすべて自動で完璧、とはなりません。癖を見つけては潰す地道な調整は続きます。でも、いじる箇所がプロンプトと辞書という分かりやすい場所に集約されたので、改善のサイクルは回しやすくなりました。

この記事を書いた人

DTPからWebの世界へ飛び込み、気づけばマークアップもフロントエンドもディレクションもアクセシビリティもこなす"技の仙人"。リベロジック創業期からマルチに活躍し、今や社内の生き字引的存在。最近は「アクセシビリティ対応、もっとAIに頼れないかな?」と、プロンプトを駆使した効率化の探究にハマり中。技術も思考も、まだまだ進化中

二俣 歩

IAAP認定ウェブアクセシビリティスペシャリスト(WAS) / マークアップエンジニア / フロントエンドエンジニア / ウェブディレクター

このスタッフの記事を見る

安心のチーム体制とスピードのある対応力が自慢

リベロジックでは、ベテランスタッフが積極的にプロジェクトを推進するため、お客様から高く評価されています。
プロジェクトマネージャー、ディレクターをきちんとアサインし、プロジェクト全体をスムーズに進行することを心掛けています。 不必要なフルコミットでのコスト増加を防ぎ、適材適所にリソースを配分するスタイルで、業務内容の把握から見積作成/提出の速さにも定評があります。

※ 当社はSES的な常駐業務等は積極的に行っておりませんので予めご了承ください。

Slack、Teams、Redmine、Backlog、Asana、Jira、Notion、Google Workspace、Zoom、Webexなど、ほぼすべての主要なプロジェクト管理ツールやチャットツールをご利用いただけます。

Webのお悩みご相談ください。

ケーススタディ

AIコンシェルジュ リベロ爺がお答えします。

このサイトに掲載されている内容をもとにAIが回答します。チャットには個人情報を入力しないでください。内容に誤りが含まれる場合があるため、重要な事項はお問い合わせからご確認ください。