AI翻訳
翻訳パスは、リポジトリにすでに存在するMarkdownを、実際のページとまったく同じようにレンダリングし、分割して、人間が読める部分を翻訳し、その結果をページごと・言語ごとに保存します。翻訳ファイルも、メッセージキーも、エクスポート手順もありません。このページでは、コードが実際に行っていることを仕組みのレベルで説明します。
パスを開始するもの#
5つあり、各実行ではどれが原因だったかを記録します。そのため、パネルにはプッシュをあなたの操作として扱うのではなく、コミット a1b2c3d によってトリガーされましたと表示できます。
| トリガー | 発生原因 |
|---|---|
language_enabled |
言語を有効にしました。 |
commit |
自動モードのスキャナーが、リポジトリの HEAD が移動したことを検出しました。 |
manual |
今すぐ翻訳を押したか、誰かがダッシュボードから実行しました。 |
agent |
run_translation_pass と呼ばれるエージェントです。エージェントのキーと実行 ID はジョブ行に記録されます。 |
| 再開ランナー | 2分ごとに cron のティックが実行され、15分間ハートビートが途絶えている実行を回収してロックを解放し、90秒間アイドル状態の最大3つの実行中ジョブを進めます。 |
スキャナーは、15分間確認していないワークスペースだけを対象とし、1回のティックにつき、最後にスキャンしてからの経過時間が最も長い順に最大4つのワークスペースだけを調べます。リポジトリの HEAD に変更がなければそこで処理を打ち切ります。また、比較対象のウォーターマークが進むのは、そのコミットで有効なすべての言語の同期が確認された場合だけです。そのため、予算切れで停止した実行によって、部分的に翻訳された状態のままワークスペースが固定され、二度と確認されなくなることはありません。
1回のパスで同時に開始される言語は、最大でも3つで、優先順位の低いものから処理されます。各実行には数分にわたる課金対象のモデル処理が含まれるため、エージェントのステップで10個を開始すると、1回のトリガーで1か月分の予算を使い果たしてしまいます。残りは over_language_cap として報告され、次回に処理されます。これが「今は実行しない」という正直な扱いです。
ページの分割方法#
翻訳単位はページではありません。セクションです。
- Markdown は前処理され、ウィジェットのブロックリストを含め、公開ページで使用されるものと同じパイプラインを通じてレンダリングされます。そのため、翻訳で扱われるのはソースを別の方法で解釈したものではなく、読者が見るページです。
- レンダリングされた HTML は、
<h2>と<h3>の境界で分割されます。最初の見出しより前のコンテンツは、独立した先頭チャンクになります。 - 9,000 文字を超えるチャンクは再度分割されます。ただし、トップレベルのブロック境界(
</p>、</li>、</table>、</pre>、</figure>、</h1>…</h6>)でのみ分割されるため、フラグメントがタグの途中で切れることはありません。 - 各チャンクは、そのチャンク自身のコンテンツを基にハッシュ化されます。ハッシュと言語の組み合わせがキャッシュキーになります。
- チャンクは一度に最大 3 つまで、各チャンクに 30 秒の上流タイムアウトを設定して翻訳され、元の順序で連結されます。
この設計から導かれる重要な点は 2 つあり、これがこの方式を採用している理由のすべてです。
- 1 つの段落を編集すると、1 つのセクションだけが再翻訳されます。それ以外のチャンクのハッシュは変わらないため、キャッシュから提供されます。誤字の修正で必要になるのはページ全体ではなく、1 セクションです。再利用されたチャンク数とモデルに送信されたチャンク数の内訳は、ページごとに支出台帳へ 1 回記録されるため、節約効果は主張ではなく測定値として示されます。
- 変更していないテキストで用語が揺れることはありません。未編集のセクションは、前回翻訳されたときとバイト単位で同一です。文字どおり、同じキャッシュ文字列だからです。
リクエストは温度 0で送信され、出力トークンの予算は一律に確保されるのではなく、入力長から算出されます。倍率は 2.6 です。これは、キリル文字や CJK では英語のソースよりも文字あたりのトークン数がはるかに多く、1.5 倍の予算ではページが途中で切れてしまったため選ばれました。
モデルから保護されるもの#
ここには2種類の異なる保護があり、これらを混同すると、ドキュメントがコードの実際の動作以上のことを約束してしまいます。
構造的に保護 — モデルには決して表示されない#
| 要素 | 仕組み |
|---|---|
| フェンス付きコードブロック | リクエスト前に抽出され、__CODE_BLOCK_N__ に置き換えられます。その後、バイト単位で元に戻されます。 |
インラインコード (`like this`) |
同じように抽出され、同じくバイト単位で元に戻されます。 |
| フロントマターのキーと値 | モデルに届くことは一切ありません。翻訳はレンダリング済みHTML上で実行され、フロントマターはレンダリング時に処理済みです。 |
ウィジェットマーカー (<!-- widget:name -->) |
これらもモデルに届くことはありません。ウィジェットはチャンク化の前にレンダリングパイプラインによってHTMLへ展開されます。破損させられるマーカーは残りません。ウィジェット内側の可視テキスト(たとえばカードのタイトル)は散文であり、翻訳されます。 |
これらは保証です。モデルに与えられていないコードサンプルを、モデルが変更、再配置、あるいは「翻訳」することはできません。
命令によって保護されている — これらを確認し、前提にしない#
プロンプトはモデルに対し、HTML タグ、属性、クラス名、ID、href の値やデータ属性を翻訳または変更しないこと、HTML 構造を変更しないこと、コード識別子や変数名を翻訳しないこと、コメントを追加しないこと、そして __CODE_BLOCK_N__ プレースホルダーをそのまま再現することを絶対的なルールとして指示しています。これは温度 0 で実行されるモデルに対する強力な指示であり、実際にも機能します — しかしこれは仕組みではなく指示であり、それを正直に表す言葉は通常はです。
知っておく価値のある 3 つの結果:
- リンクのターゲットは維持されます。
hrefは属性であり、属性は変更してはならない対象のリストに含まれます。リンクのテキストは本文であり、翻訳されます。 - 見出しのアンカーは原言語のまま維持されます。 見出しの
id属性は翻訳前に設定され、変更せずに残すよう指示されているため、英語の見出しアンカーへのディープリンクは、翻訳されたページでも機能し続けます。 - 画像の
altテキストは翻訳されません。 これは HTML 属性であり、hrefを保護するルールはaltも同時に保護します。読者の言語でのアクセシブルな alt テキストが重要であれば、それは機能ではなく欠落です。
個別ではなく、セットとして翻訳#
ナビゲーションラベルは、同じ数のラベルを同じ順序で返す必要がある1回のリクエストで、1つのグループとして翻訳されます。形式が不正、または一致しないレスポンスの場合は、推測せずに元のラベルが維持されます。返されたすべてのラベルが元のラベルと同一である場合(翻訳が実行されなかったことを示す特徴)、結果はキャッシュされずに破棄されます。そのため、次回の試行で再度翻訳を試みることができ、英語のラベルが永久に固定されることを防げます。ページのタイトルと説明は1つのペアとして一緒に翻訳されるため、両者の内容が食い違うことはありません。
古い翻訳の検出方法#
ステータスフラグではなく、git との比較によって検出します。
保存されているすべての翻訳行には source_hash、つまり翻訳時点におけるソースファイルの git blob SHAが保持されています。カバレッジは、HEAD のリポジトリツリーを読み込み、パスごとに比較することで計算されます。
| 状態 | 意味 |
|---|---|
current |
機械翻訳が存在し、保存されている SHA が HEAD 時点のファイルの SHA と一致しています。 |
behind |
機械翻訳は存在しますが、そのページの古いバージョンに対する翻訳です。 |
missing |
ページはリポジトリに存在しますが、この言語にはまだ翻訳されていません。 |
manual |
手書きまたはアップロードされたものです。最新性は作成者の判断に委ねられるため、遅れているものとして数えられることはありません。 |
orphaned |
ソースファイルが HEAD 時点で存在しなくなった翻訳です。 |
カバレッジは (current + manual) / total であり、behind と missing がどちらもゼロの場合、その言語は同期済みです。リポジトリを読み取れない場合、カバレッジはnull — 決して確実なゼロではありません。すべての画面では、健全な言語を赤く表示するのではなく、「不明」と報告されます。
データベースの status = 'outdated' 列は、この目的では意図的に使用されていません。製品内のどの処理も自動的にこの列へ書き込まないため、すべてのワークスペースでゼロのままです。これを基に最新性を確認すると、永遠に完全な状態であると報告されてしまいます。
この比較に基づいて順序付けられた処理では、欠落より先に遅れを翻訳します。古い翻訳は、ドキュメントがもはや述べていないことを読者に積極的に伝えてしまいます。一方、欠落している翻訳は原文にフォールバックするだけで、役に立たないにとどまります。
レビューと承認のフロー#
保存された翻訳には3つの出所があり、それぞれ意図的に異なる方法で扱われます。
| 出所 | 作成者 | 読者への提供 | 後続のパスによる上書き |
|---|---|---|---|
docsbook_ai |
翻訳パス | はい | はい |
manual_upload |
パネルエディターまたは upload_translation を通じたあなた |
まずはこちらをお読みください | いいえ — 自動パスによって置き換えられることはありません |
external_api |
external モードで実行する独自のパイプライン |
まずはこちらをお読みください | いいえ |
アップロードはデフォルトで下書きになります。list_pending_translations は下書きを返し、approve_translation はそのうちの1つを公開済みに移動します。また、翻訳の内容を編集すると、その行は手動アップロードとしてマークされるため、後続のパスではそのまま維持されます。external モードでは、処理の流れは次のようになります。ページが翻訳される直前に Docsbook が translation.needed を出力し、あなたのパイプラインが処理を行い、upload_translation が結果を送り返します。
機械翻訳はこのキューに入りません。パスはステータス auto の行を書き込み、draft ではないため、list_pending_translations に表示されることはありません。承認フローは外部から入ってくる翻訳に対するゲートであり、AIの出力前に設けられた人手によるレビュー工程ではありません。読者に表示される前にAIの出力をレビューしたい場合は、external モードがそれを実現する形態です。デフォルトの auto モードでは、処理しながら公開されます。
実行が失敗するとどうなるか#
以下の各失敗モードは、壊れたものを保存するのではなく、元の内容を提供するための意図的な決定です。
| 失敗 | 動作 |
|---|---|
モデルが出力トークン上限(finish_reason: length)に達する |
ハード障害として扱われ、拒否され、保存されません。途中で切り詰められたチャンクが、ページの半分だけを翻訳として永久にキャッシュしてしまったことがかつてありました。 |
| モデルが空のコンテンツを返す | これもハード障害です。成功として伝播した空文字列により、あるプロジェクトではかつて808件の空の翻訳行が蓄積しました。 |
| 1つのチャンクが失敗する | そのチャンクについては元のテキストを使ってページを組み立て、そのリクエストに限り提供します。RedisにもPostgresにも書き込まれず、インデックスにも登録されません。 |
| ソースが空でないのに、組み立てられたページが空白になる | 保存せず、代わりにソースを提供します。 |
| チャンクが最近失敗した | 10分間のネガティブキャッシュにより、再試行の殺到を防ぎます。期限切れ後の次回アクセスでは、欠落しているチャンクのみを再翻訳します。 |
| プロバイダーがDocsbookの共有キーに対して402/403を返す | 4時間のグローバル停止が設定され、使い果たされたアカウントに繰り返しアクセスする代わりに、保留中のすべての実行が直ちに停止します。独自のキーを持つワークスペースには影響しません。 |
| 自分のキーのクォータを使い果たした | 失敗するのは自分のプロジェクトの実行だけです。他の誰かの使い果たされたクォータがあなたを停止させることはなく、あなたのクォータが他の人を停止させることもありません。 |
| プロジェクトの支出予算を使い果たした | その理由を言葉で示して実行を停止し、残りのページは残高が許すようになった後の実行で翻訳します。すでに支払ったものが失われることはありません。 |
| 実行中に呼び出しが強制終了される | ジョブはカーソルとハートビートを保持します。2分間隔のランナーが15分間応答のないジョブを回収し、そのロックを解放して、次に未翻訳のページから再開します。 |
部分的に完了した実行は正常であり、エラー状態ではありません。大規模なサイトでは1回の呼び出しですべてを処理できないため、各呼び出しは許された実行時間の範囲で可能な限り進み、次のタイミングで続行します。決して起きてはならないのは、ページの半分が英語で、残りの半分が別の言語になることです。そのような組み立てをコードが永続化することを拒否するからです。
ページにまだ翻訳がない間、読者には元の内容が提供されます。スピナーもエラーもなく、このリクエストでは生成されない翻訳を約束するバナーも表示されません。古い翻訳だけが存在する場合、読者は元の内容に戻されるのではなく、すぐにその古い翻訳を受け取ります。待たせるよりも、適切な言語で読めるコンテンツを提供する方が優れているためです。
制限#
- 用語の一貫性を支える用語集はありません。 用語ベースも、提供できる翻訳禁止リストも、ページ間の一貫性チェックもありません。一貫性が保たれている部分は、temperature 0、編集されていないセクションがキャッシュからそのまま提供されること、そしてラベルとタイトル/説明がセットとして翻訳されることによるものです。同じ用語を使用する2つの異なるページは個別に翻訳されるため、一致しない場合があります。
- 「識別子を翻訳しない」は指示であって、保証ではありません。 フェンスやバッククォート内のコードは機械的に安全です。通常の文章として記述された裸の識別子(バッククォートがなく、文中にあるパラメーター名など)は、プロンプトによってのみ保護されます。自分の翻訳でできる最も効果の高い対策は、識別子をバッククォートで囲んで記述することです。
- 画像の
altテキストは原文の言語のままです。 上記を参照してください。これは、HTML 属性全体を保護した結果です。 - キャッシュされた翻訳は、書き込まれた時点で有効だったウィジェットのブロックリストに基づいて表示されます。 ウィジェットをオフにしても、すでに翻訳されたページは書き換えられません。次回の処理時に反映されます。表示切り替えのためにブロックリストをキャッシュのキーに含めると、すべての言語のすべてのページが再翻訳されることになります。
- 自動モードはプッシュではなく、ポーリングに反応します。 翻訳設定を参照してください。
関連#
- 翻訳設定 — 言語の有効化、モデル、モード、ロケール URL
- 翻訳の品質と SEO — 測定対象、翻訳の修正方法、
hreflang、翻訳されたページを検索エンジンがどのように扱うか - 訪問者の国別レポート — まだ翻訳対象にしていない地域からの訪問状況