翻訳
blume translate は AI エージェントを使ってロケールを埋めます — 各言語で欠けているページや古くなったページを見つけ、すでにお使いのエージェント CLI で翻訳し、翻訳のずれが生じたときに失敗する CI ゲートを提供します。
i18n を有効にすると、ソースページを編集するたびに、その翻訳は気づかないうちに古くなっていきます。blume translate はこの問題を解決します。各ロケールで欠けているページや古くなっているページを正確に割り出し、ローカルのエージェント CLI を使ってヘッドレスで翻訳し、その内容をコミット対象の台帳に記録します。これにより、次回の実行時にも CI でも、どの翻訳が最新なのかを判断できます。
blume translate --claude
blume translate 3 item(s) · 2 locale(s) · Claude Code
✔ docs/guides/install.mdx → fr 24.2s $0.11
✔ docs/guides/install.mdx → de 22.8s $0.10
✔ meta titles (2) → de 4.1s $0.01
Translated 3 files into 2 locales · 1 adopted · 14 already up to date · 51.1s · $0.22
仕組み
パイプラインは Blume が管理し、エージェントはテキストの翻訳だけを担当します。Blume は、翻訳が必要なファイルごとに翻訳プロンプトを組み立てます。次に、ファイル・シェル・Web の各ツールを無効にした状態でエージェント CLI をヘッドレスで実行し、応答の構造を検証してから、ターゲットファイルを Blume 自身が書き込みます。使用するエージェントは、--claude の場合は Claude Code、--codex の場合は Codex です。Blume は API キーを保持せず、モデルを直接呼び出すこともありません。
検証を通過した書き込みはすべて、プロジェクトルートの blume.translations.json に記録されます。ソースファイルとロケールの組み合わせごとに、翻訳した時点のソースのハッシュが保存されます。このファイルはコミットしてください。 再実行時に「翻訳済み」と「翻訳済みだが、その後ソースが変更された」を区別するためにこのファイルが使われます。CI ゲートもこのファイルがあるからこそ実現できています。
台帳はファイルの処理が 1 つ終わるたびに書き出されます。そのため、長時間の実行を途中で停止 (Ctrl+C) しても、失われるのは処理中だった翻訳だけです。次回の実行では中断したところから再開されます。デフォルトでは 4 ファイルずつ並列に処理されます。マシンの性能とエージェントのレート制限に余裕があれば、--concurrency で並列数を増やせます。
再実行はインクリメンタルに行われます。前回の翻訳以降に変更されていないソースはスキップされるため、1 ページを編集してから blume translate を実行すると、ロケールごとに 1 ページだけが翻訳されます。古くなったページを再翻訳する際は、既存の翻訳をエージェントに提示し、その文体・方言・用語に合わせるよう指示します。そのため、ソースを 1 段落編集した場合、翻訳もゼロから書き直されるのではなく、1 段落分だけが変更されます。
初回の翻訳には手本となる既存の翻訳がないため、ロケールの style であらかじめ方針を決めておきましょう ({ code: "pt", label: "Português", style: "Brazilian Portuguese, informal você" })。この指定はすべての翻訳プロンプトに含まれ、既存の翻訳と食い違う場合は style が優先されます。そのため、再翻訳を重ねるうちに、古いページも設定したスタイルへと近づいていきます。
翻訳対象
- ページ — デフォルトロケールの
.md/.mdxファイルです。エージェントが翻訳するのは、本文と、読者の目に触れるフロントマターの値 (title、description、sidebar.label、sidebar.badge、seo.title、seo.description) だけです。出力先はパーサーの設定に従い、dirではfr/guides/install.mdx、dotではguides/install.fr.mdxになります。 - フォルダーのナビゲーションタイトル —
dirパーサーでは、各ロケールで必要なmeta.tsのタイトルが 1 回の呼び出しでまとめて翻訳されます。生成されるロケールごとのmeta.tsには、タイトル以外のキー (order、pages、icon、collapsed) がそのままコピーされるため、各ロケールのサイドバーでも並び順が保たれます。
手動で書いた翻訳はそのまま採用され、上書きされることはありません。翻訳ファイルが存在するのに台帳にエントリがない場合、その翻訳は最新として記録され、変更されずに残ります。これを再翻訳するのは --force を指定した場合だけです。
検証
エージェントに構造を任せることはありません。Blume は書き込む前に各応答をチェックし、ソースを基にファイルを組み立て直します。
- フロントマターはソースファイルのデータから組み立て直され、翻訳対象の 6 つの値だけが翻訳で置き換えられます。エージェントが追加したキーは削除され、エージェントが削除したキーは復元されます。
slug、icon、order、日付は、この仕組みによって常にソースと同じ値になります。 - コードフェンスの数がソースと一致していること、本文が空でないこと、フロントマターをパースできることが必須です。
- 各見出しには、末尾に
[#id]マーカー を付けて、対応するソースの見出しと同じアンカー ID が固定されます。ただし、翻訳側ですでにマーカーが付いている場合は除きます。これにより、#fragmentリンクはどの言語でも同じ見出しを指します。見出しは出現順で対応付けられるため、見出しの構造がソースと一致しない翻訳にはマーカーが付きません。
検証に失敗した応答は書き込まれず、その項目は失敗として報告され、実行は次の項目へ進みます。成功した項目はすべて台帳に記録されたまま残るため、再実行すると失敗した項目だけが再試行されます。
CI を失敗させる
blume translate --check は読み取り専用のゲートです。欠けている翻訳と古くなった翻訳をすべて報告し、ずれがあればゼロ以外の終了コードで終了します。エージェントの実行やファイルの書き込みは一切行いません。
blume translate --check # exit 1 when translations are missing or stale
blume translate --check --json # machine-readable drift report on stdout
- run: npx blume translate --check
JSON レポートは、blume validate --json、blume audit --json、blume eval --json と同じ diagnostics + summary の形式で、ずれはロケールごとにまとめられます。欠けている翻訳や古くなった翻訳は、それぞれエラー診断 (BLUME_TRANSLATE_MISSING、BLUME_TRANSLATE_STALE) として報告されるため、summary.error は終了コードと一致します。手動で作成した (台帳で追跡されていない) 翻訳が原因でゲートが失敗することはありません。
制限事項
- メタタイトルの翻訳は
dirパーサーでのみ利用できます。dotパーサーには、ロケールごとにmeta.tsを用意する仕組みがないためです。関数を default export しているmeta.tsは、警告を出したうえでスキップされます。そのロケール用のファイルは手動で作成してください。 - リモートのソースや CMS から取得するソースはスキップされます。翻訳を書き込むローカルファイルがないためです。
- ヘッダーのタブラベルはコンテンツではなく
blume.config.tsで定義されています。ローカライズは、ロケールごとのラベルマップ を使ってblume.config.tsで行ってください。 - 翻訳の品質はエージェント次第です。他の人が書いた変更と同じように、出力をレビューしてください。台帳が保証するのは翻訳が最新であることだけで、訳文の自然さまでは保証しません。
フラグ
--claude/--codex— 翻訳に使用するエージェント CLI です。どちらか 1 つだけを指定する必要があります (--check使用時を除く)。--check— ファイルを書き込まずにずれを報告し、ゼロ以外の終了コードで終了します。--concurrency <n>— 並列に実行するエージェントセッションの数です。デフォルトは4、最大は16です。--locale <codes>— 翻訳先のロケールをカンマ区切りで指定します (デフォルトは、デフォルトロケール以外のすべてのロケールです)。--force— 最新のファイルや手動で作成したファイルも含め、すべてを再翻訳します。--timeout <seconds>— ファイルごとのエージェントの制限時間です。デフォルトは600です。この上限は応答しなくなったエージェントを止めるためのものなので、大きなページでも時間内に処理を終えられます。--json— どちらのモードでも、レポートを JSON 形式で標準出力に出力します。