アーキテクチャ
アーキテクチャの概要
コードベースは4つのレイヤーで構成されています。このセクションは概念モデルとして使用し、ファイルレベルの詳細が必要な場合はソースツリーを開いてください。
syncの実行の仕組み
sync(および個々の翻訳コマンド)は、有効な機能を次の順序で実行します。
| ステップ | コマンド | 実行内容 |
|---|---|---|
| 1 | extract → translate-ui | UIソースのスキャン → strings.jsonの更新 → フラットなロケールJSON(de.jsonなど)の入力 |
| 2 | translate-svg (オプション) | config.svg配下のSVGテキストを翻訳 |
| 3 | translate-docs | Markdown、MDX、.astro ページを翻訳。Docusaurus カタログ JSON。Nextra _meta / 辞書 .ts。VitePress テーマカタログ |
| 4 | translate-json (オプション) | json[]配下のネストされたJSONリーフを翻訳 |
すべてのパイプラインは、セグメントの抽出 → 構文の保護 → バッチ処理 → キャッシュ検索またはLLM呼び出し → 出力の書き込みという同じコアループに従います。設定、プレースホルダー、キャッシュ、用語集、LlmClientなどの中央の共有サービスについては、共有インフラストラクチャで説明しています。
モジュールマップ
| レイヤー | フォルダー | 役割 |
|---|---|---|
| エントリ | src/cli/ | CLIコマンド: init、extract、mark-html、translate-ui、translate-docs、translate-json、translate-svg、sync、status、dashboard、… |
| パイプライン | src/extractors/ | JS/TS、HTMLマーカー、Markdown、JSON、SVG、.astroからのセグメント抽出 |
src/processors/ | プレースホルダー保護、バッチ処理、検証、リンク書き換え | |
| 共有 | src/core/ | 設定、型、SQLiteキャッシュ、プロンプト、出力パス、ロケールユーティリティ |
src/api/ | LlmClient — プロバイダーに依存しないチャットクライアント(Vercel AI SDK)、モデルフォールバック付き | |
src/glossary/ | プロンプト用の用語集読み込み、用語ヒント、およびオプションのプロジェクトコンテキストファイル | |
src/utils/ | ロガー、ハッシュ化、無視パーサー、表示幅テーブル、.envローダー | |
| アプリのランタイム | src/runtime/ | i18nextヘルパーと表示ユーティリティ — 'ai-i18n-tools/runtime'としてエクスポートされます(ランタイムヘルパー) |
| ツールUI (ドッグフーディング) | src/i18n/、src/dashboard-app/、src/server/ | このパッケージ自身のCLIと翻訳ダッシュボードをローカライズ — プロジェクトコンテンツとは別です(自己ローカライズ) |
プログラムによる使用を目的としたものはすべてsrc/index.tsから再エクスポートされます(プログラムAPI)。
パイプラインの概要
| パイプライン | セクション | 入力 → 出力 |
|---|---|---|
| UI 文字列 | UI 文字列の内部 | ソースファイル → strings.json → フラットな {locale}.json |
| ドキュメント | ドキュメントの内部 | Markdown / MDX / .astro / Docusaurus JSON → docs[].outputDir 以下のロケールごとのファイル |
| JSON バンドル | JSON の内部 | json[] 以下のネストされた JSON → ロケールごとの JSON ファイル |
| SVG | ドキュメントの内部 — エクストラクター | config.svg 以下の SVG ファイル → 翻訳された SVG コピー |
UI 文字列の内部構造
| ステップ | コンポーネント | 結果 |
|---|---|---|
| 1 | ソースファイル (JS/TS; オプションの .astro / .html) | ディスク上のファイル |
| 2 | UIStringExtractor (i18next-scanner; .astro は ui-string-babel.ts 経由) | MD5 ハッシュでキー付けされたセグメント |
| 3 | strings.json | マスターカタログ: { hash: { source, translated, models?, locations? } } |
| 4 | LlmClient.translateUIBatch() | ソース文字列の JSON 配列 → 翻訳 (+ バッチごとのモデル ID) |
| 5 | de.json、pt-BR.json、… | フラットマップ: ソース文字列 → 翻訳 (モデルメタデータなし) |
UIStringExtractor
JS/TSファイル内のt("literal")およびi18n.t("literal")呼び出しを見つけるために、i18next-scannerのParser.parseFuncFromStringを使用します。.astroソース(ui.uiExtractor.extensionsにリストされている場合)について、ui-string-babel.tsはフロントマターとテンプレート{expression}ブロックを@babel/parserで解析し、同じfuncNamesルールを適用します。関数名とファイル拡張子はui.uiExtractorで設定可能です(ui.reactExtractorはサポートされているエイリアスです)。extract また、非スキャナー入力を同じカタログにマージします: includePackageDescriptionが有効な場合(デフォルト)のプロジェクトpackage.json description、およびincludeUiLanguageEnglishNamesがtrueの場合のバンドルされたui-languagesマスターカタログ(sourceLocale + targetLocalesから構築)からの各englishName(ソースで既に見つかった文字列が優先されます;languagesManifestPathは読み取りません)。extractはlanguagesManifestPathでui-languages.jsonも再生成します。セグメントハッシュは、トリムされたソース文字列のMD5の最初の8文字の16進数です。これらはstrings.jsonのキーになります。
.html / .htm ソース(ui.uiExtractor.extensionsにリストされている場合)の場合、extractはファイルをhtml-i18n-marks.ts経由でルーティングし、data-i18n / data-i18n-title / data-i18n-placeholderマーカー属性(ui.uiExtractor.htmlI18nAttributesで設定可能)をスキャンします。ベアマーカーは要素自身のtextContent / title / placeholderからソーステキストを取得します。値を持つマーカー(data-i18n="Key")は値を使用します。同じモジュールがmark-htmlコマンドにも使用されており、ベアマーカーを自動的に挿入します。HTMLファイルはBabel / i18next-scannerのパスには到達しません。
プレーンなAstro SSGサイトではi18nextをスキップし、ビルド時にフラットな{locale}.jsonを読み込み、ソーステキストキーでt('English')を解決できます(examples/astro-website/src/i18n/t.tsおよびUI strings — Astro websiteを参照してください)。
プレーンなHTMLアプリは、t()呼び出しの代わりにマーカー属性を使用して同じカタログモデルに従います — Marking HTML for translationを参照してください。
strings.json
マスターカタログの構造は以下の通りです。
{
"a1b2c3d4": {
"source": "The English string",
"translated": {
"de": "Der deutsche Text",
"pt-BR": "O texto em português"
},
"models": {
"de": "anthropic/claude-3.5-haiku",
"pt-BR": "openai/gpt-4o"
},
"locations": [{ "file": "src/app/page.tsx", "line": 51 }]
}
}models (オプション) — ロケールごとに、そのロケールの最後に成功したtranslate-ui実行後にどのモデルがその翻訳を生成したか(または、テキストが翻訳ダッシュボードから保存された場合はuser-edited)。locations (オプション) — extractが文字列を見つけた場所(スキャナー + パッケージの説明行;バンドルされたマスターenglishName文字列はlocationsを省略する場合があります)。
extractは新しいキーを追加し、スキャンにまだ存在するキー(スキャナーリテラル、オプションの説明、オプションのバンドルされたマスターenglishName)の既存のtranslated / modelsデータを保持します。translate-uiは欠落しているtranslatedエントリを埋め、翻訳するロケールのmodelsを更新し、フラットなロケールファイルを書き込みます。
ui-languages.json マニフェスト — { code, label, englishName, direction }のJSON配列(BCP-47 code、UI label、参照englishName、"ltr"または"rtl")。sourceLocale + targetLocalesおよびバンドルされたマスターdata/ui-languages-complete.jsonからプロジェクトファイルを構築するには、generate-ui-languagesまたはextractを使用します。
フラットなロケールファイル
各ターゲットロケールには、ソース文字列 → 翻訳(models フィールドなし)をマッピングするフラットなJSONファイル(de.json)が割り当てられます。
{
"The English string": "Der deutsche Text",
"Save": "Speichern"
}i18nextはこれらをリソースバンドルとして読み込み、ソース文字列(キーをデフォルトとするモデル)で翻訳を検索します。
UI 翻訳プロンプト
buildUIPromptMessages は以下の内容を含むシステムおよびユーザー向けメッセージを構築します。
- ソース言語とターゲット言語を特定します(
localeDisplayNamesまたはui-languages.jsonの表示名を使用)。 - ターゲットロケールに期待される書記体系がある場合、スクリプトディレクティブを先頭に付加します(明示的なBCP-47スクリプト、または
hi→ デーヴァナーガリー、ar→ アラビア文字、ja→ 日本語の仮名/漢字などの言語デフォルト)。 - 文字列のJSON配列を送信し、翻訳のJSON配列を返すよう要求します。
- 利用可能な場合は用語集のヒントを含めます。
LlmClient.translateUIBatchは各モデルを順番に試し、解析エラー、ネットワークエラー、またはスクリプトエラー(ネイティブスクリプトのロケールでのローマ字/ラテン文字フォールバックを含む)が発生した場合はフォールバックします。CLIはlocaleModels、オプションのuiModels、およびtranslationModelsからターゲットロケールごとにそのリストを構築します(プロバイダーとモデルを参照)。
ドキュメントの内部構造
| ステップ | コンポーネント | 結果 |
|---|---|---|
| 1 | Markdown / MDX / JSON / .astro ファイル (translate-docs) | ソースファイル |
| 2 | MarkdownExtractor / JsonExtractor / AstroTemplateExtractor | segments[] — ハッシュ + コンテンツを含む型付きセグメント |
| 3 | PlaceholderHandler | 保護されたテキスト — HTML、アドモニッション、アンカー、MDX、URL、インラインコード、トークンとしてマスクされた強調 |
| 4 | splitTranslatableIntoBatches | batches[] — カウント + 文字数制限でグループ化 |
| 5 | TranslationCache ルックアップ | キャッシュヒット → スキップ; ミス → LlmClient.translateDocumentBatch |
| 6 | PlaceholderHandler.restoreAfterTranslation | 最終テキスト — プレースホルダーが復元されたもの |
| 7 | resolveDocumentationOutputPath | 出力ファイル — Docusaurus レイアウトまたはフラットレイアウト |
エクストラクター
すべてのエクストラクターは BaseExtractor を継承し、extract(content, filepath): Segment[] を実装しています。
MarkdownExtractor- Markdownを型付きセグメントに分割します:frontmatter、heading、paragraph、code、admonition。YAMLフロントマターはnon-translatableに分類されます(slug、id、およびその他のルーティングキーは安定します)。トップレベルのexport ...ブロック(例:Reactコンポーネント定義)は、既存のimport ...処理とともに、翻訳不可のotherセグメントとして分類されます。大文字のJSXタグで始まる複数行ブロック(例:<Tabs>ブロック)は、翻訳可能な段落として分類されます。翻訳不可のセグメント(コードブロック、生のHTML)はそのまま保持されます。AstroTemplateExtractor-.astroマーケティングページ用の解析と置換(doc-translate.ts内のtranslateAstroFile経由のtranslate-docs)。ユーザー向けのHTMLテキストノードと翻訳可能な属性(alt、title、aria-label、placeholder)、およびユーザー向けの場合のテンプレート{expression}ブロック内の文字列リテラルを抽出します。フロントマターのTypeScript、<script>、<style>、保護された属性/キー値、およびt('…')内のリテラルをスキップします。出力パスが深い場合、再構成時に相対インポートを調整します(例:src/pages/de/index.astro)。Astro website pagesを参照してください。JsonExtractor- Docusaurus JSONラベルファイルから文字列値を抽出します(MDX本文ではなくDocusaurus UIカタログ)。SvgExtractor- SVGから<text>、<title>、および<desc>コンテンツを抽出します(config.svg下のファイルに対してtranslate-svgで使用され、translate-docsでは使用されません)。html-i18n-marks.ts-extractが.html/.htmソースに使用し、mark-htmlコマンドによって使用される、集中型のHTMLタグスキャナー。collectHtmlI18nStrings/collectHtmlI18nLocationsはdata-i18n*マーカー属性(ベアマーカー → 要素のtextContent/title/placeholder、値を持つマーカー → 値)を読み取り、markHtmlContentはベアマーカーをリーフテキスト/タイトル/プレースホルダー要素に挿入します(冪等性があり、data-i18n-ignoreを尊重し、コードのような要素や混合コンテンツ要素はスキップします)。共有のnormalizeI18nTextヘルパーは、ビルド時のキーをブラウザランタイムと同じにします。
Astro ハイブリッドサイト (UI + ページHTML)
プレーンな Astro アプリは、UI 文字列とドキュメントの両方を1つの設定で有効にすることがよくあります(参照: examples/astro-website/)。
| レイヤー | メカニズム | 出力 |
|---|---|---|
| テンプレート HTML | AstroTemplateExtractor + translate-docs | docs[].outputDir 以下のロケールごとの .astro |
Frontmatter / t('…') | ui-string-babel.ts + extract + translate-ui | フラットなpublic/locales/{locale}.json(英語ソースをキーとして使用) |
sync コマンドは、有効なステップを順に実行します: extract、次に translate-ui(features.translateUIStrings の場合)→ オプションの translate-svg → translate-docs → オプションの translate-json(--no-ui、--no-svg、--no-docs、または --no-json でスキップされない限り)。初期テンプレート ui-astro-website は UI 文字列のみを足場として提供します。ページ HTML には docs[] と features.translateDocs を追加します。
見出しアンカーの挿入(write-heading-ids CLI)
write-heading-ids コマンドは、ドキュメントの Markdown 用のローカルかつ非LLMな前処理ツールです。実装:src/cli/write-heading-ids.ts がファイルの検出を調整し、src/markdown/write-heading-ids-core.ts が行を解析してアンカーを挿入します。
有効な設定には、少なくとも1つの docs[] ブロックが必要です。各ブロックについて、contentPaths の下にある .md / .mdx ファイルを収集し、プロジェクトの .translate-ignore ルール(ドキュメント翻訳と同じ考え方)を適用し、オプションで --path / --file を使用してサブツリーに制限します。各ファイルは applyHeadingAnchorsToMarkdown で変換されます。コードブロック外のすべてのフラットなATX見出し(# … から ###### … まで)について、任意の形式の既存の見出しIDが選択したスタイルに置き換えられます。HTMLスタイルは、サフィックスのない見出しの上の行に <a id="slug"></a> を書き込みます。--slug-style mdx-comment は見出し行に {/* #slug */} を書き込みます(また、先行するHTMLアンカーを削除します)。スラッグは常に現在の見出しテキストから取得されます。--remove はHTMLアンカー、古典的な {#id} サフィックス、およびMDXコメントIDを削除しますが、新しいものは書き込みません。スラッグアルゴリズムは一般的なエコシステムに一致します — github(デフォルト)、bitbucket、gitlab、pymdown(オプションのUnicode正規化 / パーセントエンコーディングフラグ)、azure-devops、および mdx-comment(githubスラッグ + MDXコメント出力) — そのため、アンカーIDは既存のツール(doctoc、PyMdown、Docusaurusなど)と一貫性を保ちます。--dry-run は書き込まずに編集予定の内容を報告します。
ソースパスの後、同じコマンドがそれらの英語IDを各ロケールの既存の翻訳済みマークダウン(resolveTranslatedOutputPath)に再配置します。翻訳されたタイトルは再スラッグ化されません。見出し途中の{#id} / {/* #id */}は、選択されたスタイルが期待するサフィックス(またはHTMLアンカー)に戻されます。存在しないロケールファイルはスキップされます。
このコマンドは translate-docs や sync 内では実行されません。翻訳または公開前に、ソースファイル内で安定したフラグメントIDを確保したい場合に明示的に実行してください。
プレースホルダー保護
翻訳前に、LLMによる破損を防ぐために、機微な構文が不透明なトークンに置き換えられます。以下の順序で適用されます(復元は逆順):
- 見出しIDのサフィックス(ATX見出し末尾のクラシックな
{#id}/ MDX{/* #id */})— 行から完全に剥がされ、モデルには送信されません。復元後、一致する見出しの末尾に再び固定され、Docusaurusが引き続き有効なIDを認識できるようにします。見出し途中のコメントはテキスト内に残り、後続のMDX /{#…}レイヤーで処理されます。 - HTMLタグとコメント(
<strong>、<!-- ... -->など)- 既知の許可リストからの小文字HTMLタグはトークンに置換されます。大文字のJSXタグ(<Highlight>、<Tabs>、</Tab>)はMDXレイヤー(ステップ5)で別途処理されます。 - アドモニションマーカー(
:::note、:::)- 開始行のディレクティブプレフィックスのみがに置換されます。同じ行にあるタイトルはモデルが翻訳するために残されます。元のテキストと完全に一致する形で復元されます。 - ドキュメントアンカー(HTML
<a id="…">、見出し途中に残ったDocusaurus{#…})- そのまま保持されます。 - MDX専用の構成要素(
src/processors/mdx-placeholders.ts):- MDXコメント(
{/* … */}、行末サフィックスではない見出し途中の{/* #my-id */}を含む)はに置換されます。 - 大文字のJSXタグ(
<Highlight>、<Tabs>、<TabItem>、<TOCInline />、</Highlight>)-として保持され、翻訳可能な文字列属性(label、tooltip、aria-label)は、属性名がdocs[].protectAttributesに含まれていない限り、タグ内でに書き換えられます。<Tabs values={[ { label: '…' } ]}>オブジェクトリテラル内のlabel:(docs[].protectKeysでスキップ可能)および<TabItem value="…">(label属性が存在せず、小文字のスラッグ風の値をスキップする場合)も抽出されます。セグメントに||JXA_N: …||行として追加され、restoreMdxによってマージして戻されます。 - MDXブレース式(
{frontMatter.title}、style={{…}})- 深さを考慮したマッチングで、に置換されます。
- MDXコメント(
- Markdown URL(
](url)、src="…")- 翻訳後にマップから復元されます。 - インラインコードスパン(
`code`)および太字で囲まれたインラインコード(**code**)- 保持されます。 - Markdownの強調(オプション、CJK/RTLロケールでは自動的に有効)- 強調デリミタがマスクされます。
モデルが戻った後、translate-docsはマップを復元し、セグメントを検証します。二重中括弧トークンの同じ多重集合が存在する必要があり、構造トークン({{HTM_N}}、警告マーカー)は順序付けられたサブシーケンスを維持する必要があり({{ILC_N}} / {{URL_N}} / **などのコンテンツトークンは語順に合わせて移動できます)、復元されたHTMLタグの種類は保護されていないソースと一致する必要があり、残っている二重中括弧の識別子はソースに既に存在していたものでなければなりません(したがって、でっち上げられたトークンは失敗します)。ドキュメントプロンプトはまた、モデルに対して各トークンを1回コピーし、構造トークンの順序を維持し、新しい二重中括弧ラッパーをでっち上げないように要求します。機械的なチェックが権威を持ちます。
AstroテンプレートとMDX JSXの共有属性/キー保護はsrc/processors/expression-attribute-protection.tsで実装されており、docs[].protectAttributesとdocs[].protectKeysによってブロックごとに駆動されます(protectAttributes / protectKeysを参照)。
キャッシュ (TranslationCache)
SQLiteデータベース (node:sqlite 経由) は、(source_hash, locale) をキーとして translated_text、model、filepath、last_hit_at および関連フィールドを持つ行を格納します。ハッシュは、正規化されたコンテンツ(空白文字を圧縮)のSHA-256の最初の16文字の16進数です。
各実行時に、セグメントはハッシュ × ロケールで検索されます。キャッシュミスのみがLLMに送られます。翻訳後、現在の翻訳スコープ内でヒットしなかったセグメント行の last_hit_at がリセットされます。ドキュメント翻訳中のキャッシュヒット成功は、そのセグメントの古い translation_failures 行をクリアします。cleanup は最初に sync --force-update を実行し、その後、古いセグメント行(null の last_hit_at / 空のファイルパス)を削除し、解決されたソースパスがディスク上に存在しない場合に file_tracking キーを整理し(doc-block:…, json-block:…, svg-files:… など)、メタデータのファイルパスが存在しないファイルを指している翻訳行を削除し、孤立した translation_failures 行を整理し、解決されたソースパスがディスク上に存在しない孤立した markdown_source_issues 行を整理し、設定に存在しないロケールのキャッシュ行を破棄します(sourceLocale、ルート targetLocales、およびブロックごとの docs[] / json[] targetLocales; SQLiteのみ — 生成されたファイルを削除するには purge-locale を使用してください)。--backup <path> が渡されない限り cache.db をバックアップしませんが、渡された場合は最初にそのパスへバックアップを書き込みます。
課金対象のモデル呼び出し (採用された翻訳と破棄された再試行) は api_calls に保存されます。呼び出しを記録するコマンドの実行後、UTC暦日で7日を超えた詳細行は月次の api_totals にロールアップされます。usage とダッシュボードの「使用量とコスト」ビューでは、両方のテーブルが統合されます。使用量とコスト を参照してください。
用語集の Context メモと glossary.contextFiles はフィンガープリント化 (prompt_context_hash) され、UI、ドキュメント、JSON、SVG、および校正プロンプトに挿入されます。そのガイダンスを変更すると、次回の実行時に対応するキャッシュセグメントとファイル追跡行が無効化されます。ダッシュボードの user-edited キャッシュ行は保持されます。用語集の優先翻訳のみを変更した場合は、--force / --force-update まで既存のキャッシュがそのまま保持されます。
translate-docsコマンドはファイルトラッキングも使用するため、変更されていないソースで既存の最新の出力がある場合、作業を完全にスキップできます。--check-cacheは期待される書記体系でロケールを再度開き、キャッシュされたセグメントを再検証します。--force-updateはセグメントキャッシュを使用しながら、各ロケールのファイル処理を再実行します。--forceはファイルトラッキングをクリアし、API翻訳のセグメントキャッシュ読み取りをバイパスします。設定されたすべてのモデルがMarkdownセグメントのAST検証に失敗した場合、translate-docsはセグメントを段階的に分割し、より小さな部分を再試行できます(docs[].segmentSplitting.qualityRetrySplit、デフォルトでオン)。フラグの完全な表については、ドキュメント — キャッシュの動作とフラグを参照してください。
バッチプロンプト形式: translate-docs --prompt-formatは、LlmClient.translateDocumentBatchのみのXML(<seg> / <t>)またはJSON配列/オブジェクトの形式を選択します。抽出、プレースホルダー、検証は変更されません。バッチプロンプト形式を参照してください。
出力パスの解決
resolveDocumentationOutputPath(config, cwd, locale, relPath, kind)はソース相対パスを出力パスにマッピングします:
nestedスタイル (デフォルト): Markdown には{outputDir}/{locale}/{relPath}を使用します。doc-systemスタイル:docsRootの下では、出力は{outputDir}/{locale}/[localeSubpath/]{relativeToDocsRoot}を使用します。docsRoot外のパスはネストされたレイアウトにフォールバックします。エイリアス:docusaurus(デフォルトlocaleSubpath= Docusaurus プラグインパス)、astro-starlight(デフォルトは空のlocaleSubpath)、vitepress(空のdoc-systemと同じlocaleSubpath; BCP-47 フォルダの大文字と小文字を保持)。flatスタイル:{outputDir}/{stem}.{locale}{extension}。flatPreserveRelativeDirがtrueの場合、ソースサブディレクトリはoutputDirの下に保持されます。- カスタム
pathTemplate:{outputDir}、{locale}、{LOCALE}、{relPath}、{stem}、{basename}、{extension}、{docsRoot}、{relativeToDocsRoot}を使用する任意の Markdown レイアウト。 - カスタム
jsonPathTemplate: JSON ラベルファイル用の個別のカスタムレイアウト。同じプレースホルダーを使用。 linkRewriteDocsRootは、翻訳された出力がデフォルトのプロジェクトルート以外に配置される場合に、フラットリンク書き換えツールが正しいプレフィックスを計算できるようにする。
フラットリンクの書き換え
docsOutput.style === "flat"の場合、翻訳されたMarkdownファイルは、ロケールサフィックス付きでソースと並んで配置されます。ページ間の相対リンクは、readme.de.md内の[Guide](./guide.md)がguide.de.mdを指すように書き換えられます。rewriteRelativeLinksによって制御されます(カスタムpathTemplateのないフラットスタイルでは自動的に有効になります)。同じパスは、postProcessing.regexAdjustmentsが実行される前に、Markdown以外のアセットURLにファイルごとの深さのプレフィックスを付加します — Flat link rewriterを参照してください。
JSON の内部構造
| ステップ | コンポーネント | 結果 |
|---|---|---|
| 1 | json[].contentPaths | ファイルが解決されました(ファイル、ディレクトリ、またはグロブ) |
| 2 | NestedJsonExtractor | keyPolicyによって選択された文字列リーフ(ドットパス + ミニマッチ) |
| 3 | PlaceholderHandler + バッチ + TranslationCache | キャッシュヒット → スキップ; ミス → LlmClient.translateDocumentBatch(共有SQLite) |
| 4 | NestedJsonExtractor.reassemble | expandJsonBlockOutputPath(outputPathTemplate)を介した出力ファイル |
NestedJsonExtractor(src/extractors/nested-json-extractor.ts) は任意のネストされた JSON を走査し、翻訳可能な文字列リーフごとに1つのセグメントを出力します。keyPolicy.mode(allowlist、denylist、またはboth) は、ドット表記の minimatch でパスをフィルタリングします(slugのようなベア名は最終キーセグメントに一致します)。- キャッシュファイルの追跡は
file_trackingのjson-block:{blockIndex}:{projectRelPath}を使用します(ドキュメントや SVG と同じcacheDir)。 - Docusaurus
write-translationsカタログ ({ message, description }形式) には使用されません。これらはドキュメント (translate-docs内のdocs[].docusaurusCatalogDir+JsonExtractor) を使用します。 t()UI 文字列には使用されません。UI 文字列 (strings.json+ フラットバンドル) を使用します。- CLI:
translate-json;オーケストレーションはsrc/cli/translate-json-run.ts内。initテンプレート:ui-json-bundles。
共有インフラストラクチャ
LlmClient
Vercel AI SDK (ai + @ai-sdk/openai-compatible) 上に構築された、プロバイダーに依存しないチャットクライアント。アクティブなプロバイダーを provider / providers から解決し、そのプロバイダーの baseUrl + API キー用の OpenAI 互換クライアント (createOpenAICompatible) を構築し、すべての呼び出しを generateText 経由でルーティングします。OpenRouterClient は非推奨のエイリアスとして保持されます。主な動作:
- モデルフォールバック: 解決済みリスト内の各モデルを順番に試行し、リクエストまたは解析の失敗時にフォールバックします。各ターゲットロケールは独自の解決済みチェーンを持ちます。設定されている場合は
localeModels(locale)が最初、次にuiModels(UIパイプラインのみ)、次にtranslationModelsとなります。ドキュメント、JSON、SVGの翻訳は非UIチェーンを使用してロケールごとのクライアントを作成します。一方、bench-modelsコマンドは、設定された各IDごとに単一モデルのクライアントを構築します(translationModels、uiModels、localeModelsの和集合、translationModels: [id]、フォールバックなし)。これにより各モデルを個別に計測および価格評価できます。 - リクエストタイムアウト: アクティブなプロバイダーの
requestTimeout(秒)またはrequestTimeoutMs、それ以外の場合は設定ファイルの最上位にある同じキー(デフォルト45秒)が、AbortSignal.timeoutを介して各リクエストを中止します。CLIがcheck-models(任意のプロバイダー)用にプロバイダーのモデルリストを読み込む際、GET /modelsにも同じ値が適用されます。未知のモデルIDを除外するオプションのプレフライトフィルターは、アクティブなプロバイダーがOpenRouterの場合のみ実行されます。 - OpenRouter拡張機能(
openrouterがアクティブな場合のみ):providerリクエストフィールドによるスループットルーティング、HTTP-Referer/X-Titleヘッダー、およびusage.costから読み取った正確なUSDコスト。トークン使用量はすべてのプロバイダーで報告されます。プロバイダーがusage.costを省略した場合、USDコストはproviders.<name>.modelPricingまたはプロバイダー全体のpricingデフォルトから計算され、api_calls行に保存されます。プロバイダーから報告されたコストは置き換えられません。 - デバッグトラフィックログ:
debugTrafficFilePathが設定されている場合、リクエストとレスポンスのJSONをファイルに追記します(プログラマティック)。CLIの--debug-failedは、cacheDir配下にFAILED-TRANSLATIONファイルを書き出します。これには、失敗したUI、ドキュメント、JSON、SVGの翻訳チェック試行に対するシステム/ユーザープロンプト、生のアシスタント応答、および検証エラーが含まれます。プロバイダーAPI / 空ボディの失敗は、プロンプトのみのファイルをダンプする代わりにコンソールに出力されます。
設定の読み込み
loadI18nConfigFromFile(configPath, cwd)パイプライン:
ai-i18n-tools.config.json(JSON) を読み取って解析します。mergeWithDefaults-defaultI18nConfigPartialとディープマージし、docs[].sourceFilesエントリをcontentPathsにマージします。expandTargetLocalesFileReferenceInRawInput-targetLocalesを配列に強制変換し、パスのようなエントリを拒否します(ui-languages.jsonへのパスではなく、BCP-47コードである必要があります);languagesManifestPathはmergeWithDefaults中に{ui.flatOutputDir}/ui-languages.jsonにデフォルト設定されます。expandDocumentationTargetLocalesInRawInput- 各docs[].targetLocalesエントリについても同様です。expandJsonTargetLocalesInRawInput- 各json[].targetLocalesエントリで同じです。parseI18nConfig- Zod 検証 +validateI18nBusinessRules。applyProviderOverrideToRawInput- CLI で-P/--providerが渡された場合。applyEnvOverrides-OPENROUTER_BASE_URL、OLLAMA_BASE_URL、I18N_SOURCE_LOCALE、およびI18N_TARGET_LOCALESが設定されている場合に適用します (API キーはLlmClient内でプロバイダーごとに個別に解決されます)。augmentConfigWithUiLanguagesMaster- バンドルされたマスターカタログからマニフェスト表示名を添付します。assertEffectiveLocalesInUiLanguagesMaster- 該当する場合、マスターカタログに対してロケールコードを検証します。
init は initConfigTemplates からスターター構成を書き込みます: ui-markdown (UI + オプションのアプリマークダウン)、ui-docusaurus、ui-starlight、ui-vitepress (VitePress ドキュメント + vitepressThemeCatalog)、ui-nextra (Nextra ドキュメント + nextraDictionaryPath)、ui-astro-website (プレーンな Astro UI。docs[] を追加して .astro ページを翻訳)、ui-json-bundles (JSON json[] のみ)。クイックスタート — 初期化 を参照してください。
ロガー
Loggerは、ANSIカラー出力でdebug、info、warn、errorレベルをサポートします。詳細モード(-v)ではdebugが有効になります。logFilePathが設定されている場合、ログ行はそのファイルにも書き込まれます。
ツールの自己ローカライズ (ツール UI)
ツールは、コマンドラインインターフェースのヘルプ、高トラフィックログ/サマリー/エラーメッセージ、および翻訳ダッシュボードを含む自身のUIを、翻訳対象のコンテンツとは別にローカライズします。
- ロケール解決 (
resolveUiLocaleinsrc/core/ui-locale.ts):-L/--ui-lang>AI_I18N_LANG> 設定uiLanguage> ホスト OS ロケール (Intl.DateTimeFormat().resolvedOptions().locale) から UI ロケールを選択します。候補は正規化され、出荷されたバンドルセットと完全に一致するか、最も近いバリエーション (例:pt-PT→pt-BR、en-US→en-GB) で一致させ、ソースロケール (en-GB) にフォールバックします。CLI は、ヘルプが構築される前 (argv スキャンを事前解析) と、設定の読み込み後に再度解決されるため、uiLanguageが適用されます (フラグと環境変数が優先されます)。 - ランタイム (
src/i18n/index.ts):補間を備えた最小限のt(source, vars)で、src/i18n/locales/<code>.json内のフラットなロケールごとのバンドルに対して英語のソース文字列をキーとしています (ビルド時にdist/i18n/localesにコピーされます)。不足しているキーまたはバンドルはソーステキストを返します。これは UI 文字列と同じキーをデフォルトとするモデルであり、ハッシュルックアップはありません。 - ダッシュボード: サーバーは、解決された UI ロケールに対して
{ locale, dir, bundle }を返すGET /api/ui-i18nを公開します。フロントエンドは<html lang>/dirを設定し、data-i18n*属性を介して静的マークアップをローカライズします。 - ドッグフーディング: バンドルは、パッケージ自身の抽出 →
translate-uiパイプラインをai-i18n-self.config.json(pnpm i18n:self) に対して実行することによって生成されます。カタログキーは、src/cli/およびsrc/i18n/全体のt()呼び出しと、src/dashboard-app/index.html内のダッシュボードのdata-i18n*マーカーから取得されます。
拡張ポイント
カスタム関数名(UI抽出)
設定を通じて非標準の翻訳関数名を追加します。
{
"ui": {
"uiExtractor": {
"funcNames": ["t", "i18n.t", "translate", "i18n.translate"],
"extensions": [".js", ".jsx", ".ts", ".tsx", ".astro", ".html"],
"htmlI18nAttributes": ["data-i18n", "data-i18n-title", "data-i18n-placeholder"]
}
}
}(ui.reactExtractorはui.uiExtractorの完全にサポートされたエイリアスです。)
.html / .htmをextensionsに追加して、extract中にHTMLマーカー属性をスキャンします。ui.uiExtractor.htmlI18nAttributesはオプションで、デフォルトは["data-i18n", "data-i18n-title", "data-i18n-placeholder"]です。data-i18nは要素のtextContentにマッピングされ、data-i18n-<attr>はその属性の値にマッピングされます(例:data-i18n-aria-label)。
カスタムエクストラクタ
パッケージから ContentExtractor を実装します。
import { BaseExtractor, type Segment } from 'ai-i18n-tools';
class MyExtractor extends BaseExtractor {
readonly name = 'my-format';
canHandle(filepath: string) { return filepath.endsWith('.myext'); }
extract(content: string, filepath: string): Segment[] { /* … */ }
reassemble(segments: Segment[], translations: Map<string, string>): string { /* … */ }
}'ai-i18n-tools' からエクスポートされたパブリックエクストラクタクラスを拡張して、カスタムエクストラクタを登録します (例: MarkdownExtractor をサブクラス化します)。CLI は組み込みエクストラクタを内部的に接続します。doc-translate.ts のディープインポートはサポートされていません。
カスタム出力パス
任意のファイルレイアウトには docsOutput.pathTemplate を使用します
{
"docs": [
{
"docsOutput": {
"pathTemplate": "{outputDir}/{locale}/{relativeToDocsRoot}"
}
}
]
}ソースツリー
完全な src/ レイアウト (ファイルレベルの参照)
src/
├── index.ts Public API re-exports
│
├── cli/
│ ├── index.ts CLI entry point (commander)
│ ├── extract-strings.ts `extract` command implementation
│ ├── mark-html.ts `mark-html` command (insert bare `data-i18n*` markers into HTML)
│ ├── translate-ui-strings.ts `translate-ui` command implementation
│ ├── doc-translate.ts `translate-docs` command (documentation files only)
│ ├── translate-json-run.ts `translate-json` command (`json[]` nested locale bundles)
│ ├── translate-svg.ts `translate-svg` command (SVG files from `config.svg`)
│ ├── write-heading-ids.ts `write-heading-ids` command (markdown heading anchors)
│ ├── bench-models.ts `bench-models` command (per-model translate latency/token/cost benchmark)
│ ├── helpers.ts Shared CLI utilities
│ └── file-utils.ts File collection helpers
│
├── markdown/
│ └── write-heading-ids-core.ts Slug styles + `<a id="…">` insertion for `write-heading-ids`
│
├── core/
│ ├── types.ts Zod schemas + TypeScript types for all config shapes
│ ├── config.ts Config loading, merging, validation, init templates
│ ├── cache.ts SQLite translation cache (node:sqlite)
│ ├── prompt-builder.ts LLM prompt construction for docs and UI strings
│ ├── output-paths.ts Docusaurus / flat output path resolution
│ ├── ui-languages.ts ui-languages.json loading and locale resolution
│ ├── ui-locale.ts Resolve the tool's own UI locale (flag/env/config/OS → shipped bundle)
│ ├── locale-utils.ts BCP-47 normalisation, locale list parsing, script/Han-variant validation
│ └── errors.ts Typed error classes
│
├── extractors/
│ ├── base-extractor.ts Abstract base class for all extractors
│ ├── ui-string-extractor.ts JS/TS source scanner (i18next-scanner + Babel for `.astro`)
│ ├── ui-string-babel.ts Babel-based `t()` discovery in `.astro` frontmatter and `{expression}` blocks
│ ├── ui-string-locations.ts Source locations for extracted UI strings
│ ├── html-i18n-marks.ts HTML `data-i18n*` marker scanner + `mark-html` annotator
│ ├── classify-segment.ts Heuristic segment type classification
│ ├── markdown-extractor.ts Markdown / MDX segment extraction
│ ├── markdown-segment-split.ts Optional segment splitting for long markdown blocks
│ ├── frontmatter-fields.ts Selective YAML front matter field translation
│ ├── astro-template-extractor.ts `.astro` parse-and-replace (HTML + template expressions; used by `translate-docs`)
│ ├── json-extractor.ts Docusaurus catalog JSON extraction (`translate-docs`)
│ ├── nested-json-extractor.ts Arbitrary nested JSON leaves (`translate-json`, `json[]`)
│ └── svg-extractor.ts SVG text extraction
│
├── processors/
│ ├── placeholder-handler.ts Chain: HTML → admonitions → anchors → MDX → URLs → emphasis
│ ├── expression-attribute-protection.ts Shared protected attribute/key lists (Astro + MDX JSX)
│ ├── url-placeholders.ts Markdown URL protection/restore
│ ├── admonition-placeholders.ts Docusaurus admonition protection/restore
│ ├── anchor-placeholders.ts HTML anchor / heading ID protection/restore
│ ├── html-tag-placeholders.ts Lowercase HTML tag / comment protection ({{HTM_N}})
│ ├── placeholder-integrity.ts Pre/post-restore token sequence + tag-kind + invented {{IDENT}} checks
│ ├── mdx-placeholders.ts MDX comments, JSX tags, brace expressions, JSX attribute extraction
│ ├── batch-processor.ts Segment → batch grouping (count + char limits)
│ ├── validator.ts Post-translation structural checks
│ └── flat-link-rewrite.ts Relative link rewriting for flat output
│
├── api/
│ ├── llm-client.ts LlmClient: provider-agnostic chat client (AI SDK) with model fallback chain
│ └── provider-models-catalog.ts Fetch/parse any provider's OpenAI-compatible GET /models catalog
│
├── glossary/
│ ├── glossary.ts Glossary loading (CSV + auto-build from strings.json)
│ ├── matcher.ts Term hint extraction for prompts
│ └── translation-context.ts contextFiles loader and guidance fingerprints
│
├── runtime/
│ ├── index.ts Runtime re-exports
│ ├── template.ts interpolateTemplate, flipUiArrowsForRtl
│ ├── ui-language-display.ts getUILanguageLabel, getUILanguageLabelNative
│ └── i18next-helpers.ts RTL detection, i18next setup factories
│
├── i18n/ Self-localization runtime for the tool's own UI
│ ├── index.ts t(source, vars) + bundle/manifest loaders (keyed by English source string)
│ └── locales/ Shipped UI bundles (de.json, es.json, …; generated by `pnpm i18n:self`)
│
├── dashboard-app/
│ ├── index.html Translation Dashboard static UI (HTML/CSS/JS)
│ ├── app.js
│ └── styles.css
│
├── server/
│ └── translation-dashboard.ts Express app for Translation Dashboard (cache / strings.json / glossary)
│
└── utils/
├── logger.ts Leveled logger with ANSI support
├── hash.ts Segment hash (SHA-256 first 16 hex)
├── table.ts Display-width aware table rendering (CJK/emoji column alignment)
├── load-dotenv.ts Auto-load `.env` from the cwd at CLI startup (never overrides existing env)
└── ignore-parser.ts .translate-ignore file parser