BookTranslator
BookTranslator

コード、リンク、フロントマターを壊さずにMarkdownファイルを翻訳する方法

MarkdownまたはMDXファイルで翻訳すべき部分、そのままにすべき部分、そして結果を確認する方法。サンプルファイル、無料チェッカー、9回のローカルLLMテスト付き。

BookTranslator

BookTranslator Team

23 min read

Markdownファイルを翻訳するには、文章を翻訳し、仕組み部分はそのままにします。フロントマターのキー、コード、URL、リンクのラベル、コンポーネントタグ、脚注ラベルはバイト単位でそのまま返ってくる必要があります。その後、注意深い翻訳でも壊れてしまう3つの点を確認します。見出しへのリンク、コードコメント、そして中国語・日本語・韓国語における太字テキストや数値範囲の前後のスペースです。

私たちは73行のMDXページをローカルモデルで9回、3種類のプロンプトを使って簡体字中国語に翻訳し、すべての結果をスクリプトで確認しました。通常心配される部分は9回すべてで無事でした。コード、インラインコード、URL、表、JSXコンポーネント、タスクリストです。しかし、別の3つの点で問題が発生しました。見出しへのリンクは9回すべてで壊れました。コードコメントは、ファイル全体を送信した6回すべてで翻訳され、そのうち3回はプロンプトでコードに触れないよう指示していました。そして、構文をプレースホルダーの背後に隠すプロンプトは、最も安全な選択肢に思えますが、その3回の実行すべてでフロントマターを壊しました。

簡単な答え:翻訳するものとそのままにするもの

ファイルの部分サンプルからの例翻訳する?
フロントマターのキーslug:, sidebar_position:, tags:いいえ
フロントマターの値title: "Export Your Library"title や description など、読者向けのもののみ
インポートとエクスポート(MDX)import Callout from '@site/src/components/Callout';いいえ
コードブロックshelfmark export --format epubいいえ。コメントとユーザー向け文字列については別途判断してください。
インラインコードlibrary.json, --coversいいえ
リンクと画像のターゲット(./export-options.md), (https://www.w3.org/TR/epub-33/ "EPUB 3.3")いいえ。引用符で囲まれたオプションのタイトルは除く
リンクテキストと画像の代替テキスト[glossary section], ![Folder layout after an export]はい
参照ラベルと定義[project glossary][glossary], [glossary]: ./glossary.md#termsラベル [glossary] とURLはそのままにし、表示されるテキストを翻訳する
見出し## Keep one glossary for the whole bookはい。ただし、先に固定IDを付けてください(下記参照)
JSXコンポーネント<Callout type="warning" title="Back up first">タグ名と属性名はそのままにし、title など読者向けの属性値は翻訳します
アドモニションマーカー:::tip と :::いいえ
脚注ラベル[^sync]いいえ
MDXコメント{/* Translator note: ... */}任意です。読者には見えません。

それ以外はすべて通常の散文です:段落、リスト項目、表のセル、引用ブロック。

テストした内容

私たちは sample-source.mdx を書きました。これは Shelfmark という架空の読書アプリのドキュメントページです。Docusaurus や同様のドキュメントサイトで使われる MDX スタイルで書かれており、上の表の各構文の例に加えて、2スペースによる強制改行、太字のリンク、括弧で終わる太字のフレーズ、enダッシュで書かれた2つの数値範囲、そして「Keep one glossary for the whole book.」という見出しへのページ内リンクが含まれています。

次に、ローカルモデルに簡体字中国語への翻訳を3通りの方法で依頼しました:

  1. ファイル全体。 ファイル全体を貼り付け、翻訳されたファイルを返すよう依頼します。
  2. 指示付き。 同じですが、6つのルールを追加します:フロントマターのキーを保持する、コード内は一切変更しない、URLと参照定義を保持する、インポート・タグ・コメントを保持する、脚注ラベルとアドモニションマーカーを保持する、見出し・リンクテキスト・代替テキスト・title 属性を翻訳する。
  3. 保護付き。 コード、インラインコード、URL、タグ、コメント、脚注ラベル、アドモニションマーカー、フロントマターの行を ⟦12⟧ のような番号付きプレースホルダーに置き換え、残りを翻訳してから元に戻します。title と description の値は翻訳用に露出させたままでしたが、結果としてフロントマターの --- 行と閉じ引用符も同様に露出していました。

モデルは LM Studio の Qwen 3.6 27B(Q4_K_M)で、思考をオフ、temperature 0.3、Apple M4 Max 64 GB メモリ上で実行しました。2026年10月25日に各条件を3回、合計9回実行しました。スクリプトは run_llm_markdown_test.py で、9つの出力はすべて未編集で公開されています。README にすべてのファイルがリストされています。ローカルモデルの設定が初めての方は、ローカルLLM翻訳ガイド で設定を説明しています。

これは1つのモデル、1つの言語ペア、1つのページによる小規模なテストでした。どのような問題が起こるか、そしてそれをどう捉えるかを示すものです。モデルやプロンプトのランキングではありません。

結果:構文は生き残ったが、リンクと判断はそうではなかった

すべての出力を check_markdown_translation.py でチェックし、remark と MDX コンパイラでレンダリングしました。チェッカーの完全な出力は check-results.txt にあり、results.csv には実行ごとに1行ずつ記録されています。

チェックファイル全体指示済み保護済み
MDXとしてコンパイル可能3/33/33/3
コード、インラインコード、URL、表、タグ、タスクリストは変更なし3/33/33/3
太字と斜体は引き続きレンダリングされる3/33/33/3
フロントマターはそのまま3/33/30/3
ページ内リンクは依然として見出しを指している0/30/30/3
コードコメントは書かれたまま残る0/30/33/3
コールアウトの title を翻訳済み3/33/30/3

最初の行は良い知らせです。このモデルはファイル全体を貼り付けたとき、Markdown 構文をそのまま返しました。コード、URL、表に修正すべき点は見つかりませんでした。残りの4行が課題であり、いずれもサイトをビルドしてもエラーとして表示されません。

1. 翻訳された見出しがリンクを壊す

サンプルは用語集セクションに [glossary section](#keep-one-glossary-for-the-whole-book) でリンクしています。どの実行でも見出しは期待通りに翻訳され、リンク先も期待通りに保持されました。その結果、存在しない ID へのリンクになってしまいます。

ほとんどの Markdown ツールは見出しテキストから見出し ID を生成します。Pandoc のドキュメントでは識別子を「見出しテキストに基づく」と説明しており、Docusaurus は警告しています。見出しを変更または翻訳する際に「リンクを壊さないように既存の ID を更新しない」ことを推奨しています。見出しが 为整本书保留一个术语表 になると、生成される ID は中国語になり、#keep-one-glossary-for-the-whole-book はどこにもスクロールしません。ページはビルドされるため、何も知らされません。

翻訳後にリンクを編集しても修正できません。9回の実行でモデルは同じ見出しを4通りに書き分けたため、生成される ID は再翻訳のたびに変わってしまいます。修正は翻訳前に行うべきです。リンクされる各見出しに固定 ID を付けてください。

  • Docusaurus MDX では ## Keep one glossary for the whole book {/* #keep-one-glossary */} を使用します。Docusaurus は古い {#id} 形式は「もはや有効な MDX 構文ではない」と述べており、write-heading-ids コマンドで全ドキュメントに ID を追加できます。
  • Pandoc および PHP Markdown Extra に準拠する Markdown プロセッサでは、見出しの末尾に {#keep-one-glossary} を使用します。

これにより ID はどの言語でも同じままになり、チェッカーはそれをリンク先として扱います。

2. コードコメントが翻訳され、表示文字列は翻訳されない

サンプルにはシェルコメントと YAML コメントがあります:

library:
  name: "My books"   # shown in the sidebar
  sort: title

ファイル全体を送信した6回の実行すべてで両方のコメントが翻訳されました。プロンプトに「フェンス付きコードブロック内は何も変更しないでください」とあった3回も含まれます。コード自体は9回すべてで変更されませんでした。一方、コメントでサイドバーに表示されると説明されている "My books" は9回すべてで英語のままでした。モデルはローカライズされたページに必要なことの逆を行い、開発者向けの注記を翻訳し、読者が目にするラベルをそのままにしました。

どちらの選択も常に間違いとは限りません。読者がコードを自分のプロジェクトにコピーするチュートリアルでは、翻訳されたコメントが役立ちます。コードがリポジトリと一致する必要があるリファレンスドキュメントでは、ページとソースが食い違います。プロジェクトごとに一度決定し、設定例の文字列値は手動で確認してください。ツールは表示ラベルと識別子を区別できないからです。このため、チェッカーはコメントのみの変更を警告として報告します。

3. 安全に見えた方法がフロントマターを壊した

保護条件は約束通りに機能しました。3回の実行すべてで32個のプレースホルダーがすべて戻り、コード、URL、タグは完全に復元されました。しかし、スクリプトは2つの小さなものをプレースホルダーの外に残しました。フロントマターを開く --- 行と、翻訳されたタイトルと説明の後の閉じ引用符です。モデルは3回の実行すべてでそれらをすべて削除しました。ファイルは次のように始まりました:

title: "导出您的书库
description: "在更换设备之前,请备份您的笔记和书籍。
slug: export-library

開く --- がないと、Markdown パーサーはフロントマターを認識しません。6行の YAML と閉じ --- を1つの見出しとして読み取るため、ページは YAML を含む大きな見出しで始まります。それでも MDX としてコンパイルされました。--- を戻すだけでは不十分です。YAML パーサーは閉じられていない2つの引用符を拒否します。

教訓はプレースホルダーを避けることではありません。露出させたものは変更されて戻ってくる可能性があり、フロントマターはページの一部としてモデルに送るべきではないということです。それを解析し、読者向けの値(title、description)だけを個別の文字列として送信し、翻訳を元の構造に書き戻します。これは数行のコードで、問題のクラス全体を排除できます。

中国語、日本語、韓国語における太字テキストと範囲

さらに2つの問題は9回の実行では現れませんでしたが、ページを静かに壊すため確認する価値があります。どちらも BookTranslator 自身のブログで発生しました。ブログは MDX で書かれ、27言語に機械翻訳されており、ある定期的な翻訳実行で韓国語の記事2件が構造チェックに3回連続で失敗しました。原因は以下の2つのパターンでした。

句読点で終わる太字。 CommonMark は、** が太字を開始または終了できるかどうかを、その両側の文字を見て判断します。句読点の直後に来る閉じ ** は、その後にスペース、句読点、または行末が続く必要があります。これが右側フランキング規則です。英語では常にそこにスペースがあります。中国語、日本語、韓国語ではしばしばそうではありません。したがって、打开**设置 → 导出(测试版)**并选择 はリテラルのアスタリスクを表示します。太字が全角括弧で終わり、直後に 并 が続くためです。括弧を除いた同じ行は太字でレンダリングされ、** の後にスペースを入れた行も同様です。太字のリンクも同じ問題を抱えます。リンクは ) で終わるためです。点击**[导出](./export-options.md)**打开 は失敗し、韓国語の **[API](./api.md)**는 も失敗します。

私たちのモデルは偶然、すべての太字フレーズの両側にスペースを入れていたため、その出力は正しくレンダリングされました。後で中国語の組版慣習に従ってそのスペースを削除する編集者は、バグを再発させます。render-cases.md で7つのバリアントすべてを試せます。

チルダを使った数値範囲。 中国語や日本語のテキストでは、範囲を 3~5 と書くことがよくあります。remark 上に構築された GitHub Flavored Markdown サイトは、デフォルトで単一のチルダを打ち消し線に変えます。remark-gfm のオプション singleTilde は、変更しない限り true です。1つの段落に2つの範囲がある場合、たとえば 需要 3~5 分钟,生成 10~20 MB 的文件夹 は、その間のテキストが打ち消し線でレンダリングされます。私たちのサンプルでは en ダッシュを使用し、8回の実行でそれを維持しましたが、9回目は 3 到 5 と書きました。範囲には en ダッシュまたは単語を使うよう依頼するか、remark の設定で singleTilde: false を設定してください。

構造チェッカーは両方のパターンをフラグします。実際に何がレンダリングされるかを確認するには、render_check.mjs がソースと翻訳の太字、斜体、打ち消し線、残ったアスタリスクを数え、MDX コンパイルを試みます:

npm install @mdx-js/mdx@3 unified@11 remark-parse@11 remark-gfm@4
node render_check.mjs source.mdx translated.mdx

翻訳で太字ノードが少ない、またはリテラルのアスタリスクが多い場合、一部の強調が閉じなくなったことを意味します。

持ちこたえるワークフロー

  1. まず見出し ID を修正します。 何かがリンクしているすべての見出しに、サイトジェネレーターがサポートする構文で明示的な ID を追加します。ソース言語で一度だけ行い、何かが翻訳される前にコミットします。

  2. フロントマターをページから取り出します。 title や description など読者向けの値のみを個別の文字列として翻訳し、元の YAML に書き戻します。

  3. コードポリシーを決定します。 コメントを翻訳するかどうか、設定例の表示文字列を翻訳するかどうか。サンプルの Shelfmark のように変更してはならない製品名の隣に、翻訳用語集 に書き留めます。

  4. 本文を翻訳します。 Markdown を解析するツールを使用するか、明示的なルールを付けてページ全体を送信します。私たちのテストでは、ページ全体を送信すると構文が intact に保たれました。プレースホルダーを使用する場合は、完全な構造を保護し、半分露出したままにしないでください。

  5. 構造チェックを実行します。

    python3 check_markdown_translation.py source.mdx translated.mdx --keep Shelfmark
    

    フロントマターのキーと値、インポート、コードブロック、インラインコード、リンクターゲット、参照定義、見出しレベル、ページ内リンクターゲット、JSX タグと属性、MDX コメント、アドモニションマーカー、脚注ラベル、テーブル形状、タスクリストボックス、ハード改行、閉じられない強調、チルダ範囲、および保持を依頼した用語を比較します。翻訳品質は判断しません。

  6. CJK ターゲットのレンダリングを render_check.mjs で確認し、句読点の後に続く閉じ ** の後にスペースを追加します。

  7. サイトをビルドしてリンクをクリックします。 各言語でページを開き、すべてのページ内リンクをクリックし、設定例を読みます。ビルドが成功することは、ファイルがコンパイルされることだけを証明します。

正確さが重要なドキュメントでは、テクニカル翻訳ワークフロー の用語とレビューのステップがこれに加えて適用されます。

BookTranslator の位置づけ

BookTranslator の TXT 翻訳ツール は .txt、.md、.mdx ファイルを受け付けます。製品ページによると、段落、改行、特殊文字、テキスト構造を維持しながら翻訳し、大きなファイルをセグメントに分割してコンテキストを考慮した翻訳を行うため、Markdown で書かれたマニュアルや書籍全体を、部分的に貼り付けるのではなくアップロードできます。

製品ページには、フロントマター、コードブロック、MDX コンポーネントの特別な処理については記載されておらず、この記事のためにこのサンプルを BookTranslator で実行したわけでもありません。どのツールを使用するにしても、翻訳前にステップ1〜3を、翻訳後にステップ5〜7を行ってください。固定 ID のない見出しは依然としてリンクを失い、チルダで書かれた範囲は依然として打ち消し線になります。書籍を複数言語で出版する場合、その作業の残りは多言語での自費出版ガイドで説明しています。

テストファイルを入手

このテストのすべては自由に再利用できます:

関連記事