タグ、属性、リンクを壊さずにHTMLファイルを翻訳する方法
HTMLファイルで翻訳すべき箇所と触れてはいけない箇所、そしてその確認方法。サンプルページ、無料チェッカー、ローカルLLMによる9回のテスト付き。

HTMLファイルを翻訳するには、人が読むテキストと、読者向けの語句を含む属性(alt、title、placeholder、aria-label、ページのメタディスクリプションなど)を翻訳します。マークアップはそのままにします。タグ名、href、src、id、class、スクリプト、スタイル、そしてtranslate="no"が付いたものはすべて触れません。その後、忘れがちな1つの値、<html>要素のlang属性を変更して、ブラウザ、スクリーンリーダー、検索エンジンがページの現在の言語を認識できるようにします。
タグをそのまま保つことは、実は簡単な部分でした。59行のHTMLヘルプページをローカルモデルで簡体字中国語に9回翻訳し、3つの方法を使い、すべての出力をスクリプトでチェックしました。どの実行でも、すべてのタグ、リンク、IDが保持されていました。問題はもっと静かに起こりました。ファイル全体を送信した6回の実行すべてで、モデルは<pre>コードブロック内の1行を翻訳してしまい、6回すべてでナビゲーションのaria-labelを英語のままにし、設計上マークアップを最も安全に保つ方法であるテキストを1つずつ翻訳する方法では、3回の実行すべてで読者に間違った操作を指示する文が生成されました。
簡単な答え:翻訳するものと触れないもの
| ファイルの部分 | サンプルの例 | 翻訳する? |
|---|---|---|
| 表示されるテキスト | 見出し、段落、表のセル、リンクテキスト、<label> | はい |
<title>とメタディスクリプション | <title>Export Your Library | Shelfmark Help</title> | はい。どちらも検索結果とブラウザのタブに表示されます。 |
og:titleとその他のソーシャルメタタグ | <meta property="og:title" content="Export Your Library"> | はい、contentの値のみ |
alt, title, placeholder, aria-label | alt="The Export dialog with EPUB selected", aria-label="Breadcrumb" | はい |
<html>のlang | <html lang="en"> | ターゲット言語に変更します。例えばzh-Hans |
| タグ名と属性名 | <a>、<img>、width、aria-label自体 | いいえ |
href, src, id, class, for, name, type, data-* | href="#choose-format", id="q" | いいえ |
<code>, <pre>, <script>, <style> | shelfmark export --format epub | いいえ。コードコメントとサンプル出力については別途判断してください。 |
| CSSで設定されたテキスト | .note::before { content: "Note: "; } | はい、ただしスタイルシート内にあるため、そのつもりで計画してください |
| JSON-LD構造化データ | "name": "Export your Shelfmark library" | nameとdescriptionの値を翻訳し、キーとJSONは有効なままにします |
translate="no"が付いた要素 | <span translate="no">Shelfmark</span> | いいえ |
| HTMLコメント | <!-- Generated by the help-center build... --> | 任意です。読者が目にすることはありません。 |
| 文字参照 | <library>, , © | エスケープされたものはエスケープされたままにします。<は実際の<になってはいけません。 |
このリストは私たちが発明したものではありません。HTML Standardのtranslate属性に関するセクションでは、「翻訳可能な属性」を定義しています。すべての要素のtitle、画像のalt、入力のplaceholder、翻訳可能であることが知られている名前を持つmeta要素のcontent、そして新しい言語に合わせて変更する必要があるとされるlangです。また、他の仕様が追加できる属性の例としてARIAのaria-labelを挙げ、translate="no"要素内ではテキストと属性値は「ページのローカライズ時にそのままにされる」と述べています。
テストした内容
私たちはsample-source.htmlを書きました。これは、Markdown翻訳テストで使用したのと同じ架空の読書アプリ「Shelfmark」のヘルプセンターページです。上の表の各行の例が1つずつあり、さらにいくつかの罠があります。
- インラインタグで分割された文:「エクスポートする前に、ノートをバックアップしてください。エクスポートは本をコピーしますが、Shelfmarkから何も削除しません」という文で、製品名は
translate="no"内にあります。 - 順序が重要な2つのインライン
<code>要素を含む文:「--outの後に<library>フォルダへのパスを入力してください」 - コマンドと1行のサンプル出力
# Export finished: 128 booksを含む<pre><code>ブロック - 段落の前に「Note:」という単語を挿入するCSSルール
#choose-formatへのページ内リンク、mailto:リンク、検索フォーム、およびJSON-LDブロック
次に、ローカルモデルに簡体字中国語への翻訳を3つの方法で依頼しました。
- ファイル全体。 ファイル全体を貼り付け、翻訳されたファイルを返すように依頼します。
- 指示付き。 同じですが、8つのルールを追加します。すべてのタグと属性名を保持し、
href、src、id、classなどの値を変更せず、リストされたテキスト属性とメタタグを翻訳し、<code>、<pre>、<script>、<style>はJSON-LDのnameとdescriptionを除いてそのままにし、translate="no"を尊重し、文字参照とコメントを保持し、langをzh-Hansに設定します。 - セグメント。 HTMLからテキストを抽出するツールのアプローチ:スクリプトがファイルをタグとテキストに分割し、
<code>、<pre>、<script>、<style>、およびtranslate="no"要素をスキップし、残りのテキスト部分とテキスト属性を番号付きJSONリストとしてモデルに送信し、翻訳を未変更のマークアップに書き戻します。また、lang="zh-Hans"を自身で設定します。
モデルはLM StudioのQwen 3.6 27B(Q4_K_M)で、思考をオフにし、温度0.3、Apple M4 Max 64 GBメモリ上で実行しました。2026年10月27日に各方法を3回、合計9回実行しました。スクリプトはrun_llm_html_test.pyで、9つの出力はすべて未編集で公開されています。READMEにすべてのファイルがリストされています。ローカルモデルを設定したことがない場合は、ローカルLLM翻訳ガイドで設定を説明しています。
これは1つのモデル、1つの言語ペア、1つのページによる小規模なテストでした。どのような問題が発生するか、そしてそれをどう捉えるかを示しています。モデルやツールのランキングではありません。
結果:マークアップは生き残ったが、3つの決定はそうではなかった
すべての出力を、Python の標準ライブラリのみを使用するチェッカー check_html_translation.py で検証しました。完全な出力は check-results.txt にあり、results.csv には実行ごとに 1 行の結果が記録されています。
| チェック項目 | ファイル全体 (3 回実行) | 指示付き (3 回実行) | セグメント分割 (3 回実行) |
|---|---|---|---|
同じ 58 個のタグが順序どおりに存在し、すべての href、src、id、class が変更されていない | 3 回中 3 回 | 3 回中 3 回 | 3 回中 3 回 |
translate="no" のテキストが保持され、ページ内リンクも機能し、<library> もエスケープされたまま | 3 回中 3 回 | 3 回中 3 回 | 3 回中 3 回 |
<pre> 内のサンプル出力行が翻訳されている | 3 回中 3 回 | 3 回中 3 回 | 3 回中 0 回 (仕様によりスキップ) |
CSS の content: "Note: " が翻訳されている | 3 回中 3 回 | 3 回中 0 回 | 3 回中 0 回 (仕様によりスキップ) |
JSON-LD の name と description が翻訳されている | 3 回中 3 回 | 3 回中 3 回 | 3件中0件(仕様によりスキップ) |
aria-label="Breadcrumb" を翻訳済み | 3件中0件 | 3件中0件 | 3件中3件 |
インラインの <code> 要素を2つ含む文が正しく伝わっている | 3件中3件 | 3件中3件 | 3件中0件 |
| インラインタグをまたぐ中国語テキスト間のスペース | なし | 0、5、4 | 各実行で6件 |
<html> の lang | zh-CN(指定なし) | zh-Hans | zh-Hans(スクリプトによる設定) |
9回の実行すべてで、同じ58個のタグが同じ順序で保持され、すべての href、src、id、class、ページ内リンクのターゲット、コメント、エスケープされた <library>、そして translate="no" 内の製品名も維持されました。モデルがシンプルなページのマークアップを壊すのではないかと心配していたなら、それは杞憂でした。問題はすべて「何を翻訳するか」にありました。
1. <pre> 内のサンプル出力が翻訳された
サンプルのコードブロックは、プログラム出力の行で終わっています:
<pre><code>shelfmark export --format epub --covers --out ~/Books/export
# Export finished: 128 books</code></pre>
ファイル全体を対象とした6回の実行と指示付きの実行すべてで、モデルはその行に # 导出完成:128 本书 を返しました。これには、<pre> 内は変更しないようプロンプトで指示した3回の実行も含まれます。コマンド自体は一切変更されませんでした。モデルはその行をコメントとして扱いましたが、チュートリアルではコメントを翻訳するのは妥当な選択です。しかし、この行はプログラムが出力する内容であり、読者が自分のターミナルと比較すると英語が表示されます。翻訳を始める前に、サンプル出力やコードコメントを翻訳するかどうかを決めてツールに伝えるか、<pre> ブロックを完全に保護してください。
ファイル全体を対象とした実行では、<style> ブロック内の CSS content: "Note: " も翻訳されましたが、<style> をそのままにするよう指示された実行では英語のまま保持されました。どちらも単独では間違いではありません。スタイルシートの翻訳は CSS がインラインのページでは正しく、共有スタイルシートがすべての言語に提供されるサイトでは誤りです。いずれにせよ、これは誰かが決めるべきことであり、モデルが推測すべき詳細ではありません。
2. アクセシビリティラベルがスキップされた
パンくずナビゲーションは <nav aria-label="Breadcrumb"> です。スクリーンリーダーはそのラベルを読み上げますが、目の見える読者がそれを見ることはありません。ファイル全体を対象とした6回の実行と指示付きの実行すべてで、翻訳対象の属性に aria-label が含まれているとプロンプトで指示されていたにもかかわらず、英語のままでした。ある指示付きの実行では、検索ボタンの aria-label="Search" も英語のままでした。
セグメント方式では毎回翻訳されました。スクリプトがすべての aria-label、alt、title、placeholder の値を収集し、それぞれを明示的に要求したからです。これは、モデルが気づくことを期待するのではなく、テキスト属性を意図的に抽出することの最も強い根拠です。
3. 断片ごとの翻訳はタグを保ったが、文を壊した
セグメント方式は、構造上マークアップに対して安全です。モデルはタグを一切見ないため、タグを壊しようがありません。3回の実行すべてで、あらゆる構造チェックに合格しました。しかし同時に、このテストで唯一、読者に誤った指示を与える翻訳を生み出しました。
英語の文は「Type the path to the <library> folder after --out.」です。インラインの <code> がこれを3つの断片に分割します。「Type the path to the」「folder after」、そしてピリオドです。中国語ではこれらの語順が異なるため、翻訳者は2つのコード要素を移動させる必要があります。セグメント方式ではそれらを移動できません。3回の実行すべてで指示が逆になりました。実行2は次のようになりました(実行1と3は「之后」の代わりに「后」ですが、意味は同じです)。
在 <code><library></code> 之后输入 <code>--out</code>文件夹的路径。
これは「<library> の後に、--out フォルダのパスを入力してください」と読め、指示とは逆です。ファイル全体方式と指示付き方式の6回の実行はすべてコード要素を移動させ、正しく訳しました。
在 <code>--out</code> 后面输入 <code><library></code> 文件夹的路径。
同じ分割により、タグと中国語テキストの境目に余分なスペースが残りました。たとえば 前往 <b>设置</b> や <b>书库</b> , などで、セグメント方式の3回の実行すべてに、1回あたり6か所ありました。ブラウザでは、中国語の句読点の前も含め、目に見える隙間として表示されます。ファイル全体方式にはまったくなく、指示付き方式の3回のうち2回には4~5か所ありました。
構造チェッカーは逆になった文を検出できません。すべてのタグが元の位置に正確にあるからです。私たちのチェッカーはインラインコードの順序が変わったときに報告しますが、これは多くの言語で正常なことです。しかしその警告では正誤を判断できません。このテストでは、正しい6つの翻訳すべてで警告が出て、誤った3つでは何も出ませんでした。誤った翻訳は英語の語順を保っていたからです。文を読む人間にしか判断できません。解決策は、インラインタグを含む文全体を翻訳者に送ること(ファイル全体方式がそうしました)と、その後で構造をチェックすることです。
小さな違い
lang: ファイル全体方式の実行は、独自にzh-CNに変更しました。指示付き方式の実行は、指示どおりzh-Hansを使いました。どちらも有効な言語タグです。zh-Hansは文字体系(簡体字中国語)を、zh-CNは地域を表します。W3Cの言語宣言に関するガイダンスは、<html>要素で言語を宣言し、Content-Languageメタタグでは宣言しないことを推奨しています。- JSON-LD: ファイル全体方式と指示付き方式の実行は、
nameとdescriptionの値を翻訳し、JSONを有効なまま保ちました。私たちのセグメントスクリプトは<script>の内容をスキップしたため、構造化データは英語のまま残りました。スクリプトを無視するツールも同じように動作します。 - 文字参照: ファイル全体方式と指示付き方式の実行は、
›、 、©、および絵文字の参照を書かれたまま保持しました。これら6回のうち5回は、範囲2–5を中国語の語(2 到 5 または 2 至 5)に書き換えました。指示付き方式の1回は、参照を保持するよう指示されていたにもかかわらず、参照の代わりに普通のダッシュを書きました。私たちのセグメントスクリプトはそれらをすべてデコードして文字を書き戻しましたが、表示は同じです。重要なもの、つまり<code>内の<library>は、9回の実行すべてでエスケープされたままでした。
翻訳する前に:ファイルを準備する
- 変更してはいけないものをマークする。 製品名、型番、文章中に現れるコードのような文字列、その他そのままにすべきものに
translate="no"を付けます。これはHTMLの一部で、コストはかからず、ファイル全体を送った6回の実行すべてでモデルはこれを尊重しました。私たちのスクリプトのようにこれを読むセグメントツールは、それらの要素を完全にスキップします。翻訳しないものガイドでは、そのリストに何を載せるかの決め方を説明しています。 - コードのポリシーを決める。 コードのコメントは翻訳するのか? サンプル出力は? スタイルシートは言語間で共有するのか? 答えを、変更してはいけない名前とともに翻訳用語集に書き留め、翻訳する人またはものに渡します。
- IDはソース言語のままにする。 ページ内リンクは
idの値を指します。誰かがidを翻訳すると、そのリンクはすべて壊れます。私たちの実行ではどれもそうなりませんでしたが、「親切心で」アンカーをローカライズする翻訳者はそうするでしょう。 - 隠れたテキストを集める。 言葉を含む属性、つまり
alt、title、placeholder、aria-label、ボタンのvalue、メタタグ、JSON-LDをリストアップし、チェック漏れがないようにします。 - JavaScriptの文字列をその場で翻訳しない。 アラートやコード内で作成されるボタンラベルなど、スクリプトが設定するメッセージは、コード内で別のローカライズ手順が必要です。ファイル全体方式と指示付き方式の6回の実行はいずれも私たちのスクリプトブロックを変更しませんでしたが、それは正しいものの、それらの文字列は英語のまま残ることを意味します。
翻訳した後に:チェックする
-
構造チェックを実行する。
python3 check_html_translation.py source.html translated.html --lang zh-Hans --keep Shelfmarkタグとその順序、読者向けテキストではないすべての属性、
<pre>、<script>、<style>の内容、インラインコード、JSON-LDのキー、translate="no"のテキスト、コメント、ページ内リンクを比較します。英語のままのテキスト属性、期待と異なるlangの値、未翻訳に見える可視テキスト、順序が変わったインラインコード、インラインタグをまたぐ中国語・日本語・韓国語テキスト間のスペースについて警告します。翻訳の品質は判断しません。 -
インラインタグを含むすべての文を読む。 私たちのテストで意味が壊れたのはここです。これらの文を見つけるのに、チェッカーの「インラインコードの順序変更」警告に頼らないでください。私たちのテストでは、誤った翻訳こそが元の順序を保っていたものでした。
-
ブラウザでページを開く。 ページ内リンクをクリックし、可能ならスクリーンリーダーでフォームをタブ移動し、太字やリンクされたテキストの周りの隙間を探します。
-
右から左に書く言語の場合、
<html>要素にdir="rtl"も設定します。他に何が変わるかは右から左への書籍翻訳ガイドで説明しています。 -
XHTMLの場合、 EPUB内の章ファイルなどは、ファイルが依然として整形式のXMLであることも確認します。たとえば
xmllint --noout chapter.xhtmlを使います。ブラウザは閉じられていないタグを許しますが、電子書籍リーダーは許さないかもしれません。電子書籍の残りの部分についてはEPUB翻訳QAチェックリストで説明しています。
BookTranslatorが適する場面
BookTranslator のアップロードページでは、書籍や文書と同様に .html、.htm、.xhtml ファイルを受け付けるため、保存したページや静的なヘルプ記事を丸ごと 1 ファイルとしてアップロードできます。この記事ではこのサンプルを BookTranslator で処理しておらず、インラインコードの順序、CSS コンテンツ、JSON-LD の扱いも検証していないため、他のツールと同様に出力を確認してください。料金は支払い前に表示されます。
どのツールを使う場合でも、翻訳前に準備の手順を行い、翻訳後にチェックを行ってください。すべてのタグが無傷のページでも、読者に誤った入力内容を指示している可能性があります。HTML が実質的に Markdown で書かれたドキュメントである場合は、Markdown 翻訳ガイドでソース形式について説明しています。
テストファイルを入手する
このテストのすべての内容は自由に再利用できます:
- sample-source.html: 59 行の英語サンプル;
- check_html_translation.py: 構造チェッカー (Python 標準ライブラリのみ);
- run_llm_html_test.py: 3 つの手法 (OpenAI 互換のローカルサーバー用);
- outputs フォルダ内の 9 つの出力と、check-results.txt および results.csv。





