Claude Media
Claude CodeでDocusaurusサイトを作る — 日本語i18nとリンク切れ検査

Claude CodeでDocusaurusサイトを作る — 日本語i18nとリンク切れ検査

Claude CodeでDocusaurusの日本語化を進める手順です。i18nの翻訳ファイルの更新と、onBrokenLinksによるリンク切れ検査をStop hookに組み込みます。

Docusaurusの日本語化でClaude Codeに任せる範囲

Docusaurusは、Markdownで書いたドキュメントをReactベースの静的サイトにビルドするツールです。多言語対応(i18n)の仕組みを標準で持ち、翻訳データはすべて i18n/<ロケール>/ 以下のファイルに置かれます。

この構造は、Claude Codeと相性のよい作業に分解できます。

  • 翻訳ファイル(JSONとMarkdown)を読んで、訳文を書き込む
  • docusaurus build を実行して、結果を読んで直す

後者が肝です。Docusaurusは本番ビルドでリンク切れを検出し、既定ではエラーで止まります。この「通ったか落ちたか」をClaude Codeが自分で読める形にしておくと、翻訳を何ファイル更新しても、リンクの壊れを見逃さずに済みます。

本記事は、Docusaurusの公式i18nチュートリアル(フランス語を追加する例)を、日本語(ja)に読み替えて進めます。元の例のロケールは fr で、以降のコマンドは ja に置き換えた形で書きます。静的サイトジェネレーターの別の選択肢は、AstroブログとHugoの記事にまとめています。

日本語ロケールを追加する設定

docusaurus.config.jsにロケールを宣言する

チュートリアルでは、docusaurus.config.js の i18n に使うロケールを列挙します。

export default {
  i18n: {
    defaultLocale: 'en',
    locales: ['en', 'ja'],
  },
};

ロケール名は、翻訳ファイルの置き場所と、翻訳版サイトのベースURLに使われます。全ロケールをビルドしたとき、URLのパスからロケール名が省かれるのは既定のロケールだけです。日本語版は /ja/ 以下に出力されます。

<html lang="..."> やロケールのラベル、日付の書式は、ロケール名から妥当な既定値が決まります。変えたい場合は localeConfigs で上書きします。

ナビバーに言語切替を置く

利用者がロケールを選べるように、themeConfig のナビバー項目へ localeDropdown を追加します。

export default {
  themeConfig: {
    navbar: {
      items: [
        {
          type: 'localeDropdown',
          position: 'left',
        },
      ],
    },
  },
};

日本語版の開発サーバーを起動する

日本語版は、ロケールを指定して起動します。

npm run start -- --locale ja

サイトは http://localhost:3000/ja/ で開きます。この段階では翻訳がないので、大半は元の言語のままです。

各ロケールは独立したシングルページアプリケーションなので、開発サーバーで全ロケールを同時に立ち上げることはできません。Claude Codeに「日本語と英語を並べて確認して」と頼んでも、開発サーバーでは成立しない点に注意してください。

翻訳ファイルの置き場所と、Claude Codeに任せる単位

翻訳データは3系統に分かれる

日本語の翻訳データは、すべて i18n/ja 以下に置かれます。チュートリアルでは次の3系統に分かれます。

置き場所

翻訳ファイルの3系統

  • コード内の文言

    i18n/ja/code.json に集約されます。<Translate> コンポーネントや translate() で印を付けた文言を、write-translations が静的解析で抜き出します。

  • プラグインとテーマの文言

    ナビバーやフッターのラベル、ドキュメントのサイドバーのカテゴリ名などが、プラグインごとのJSONになります。例は i18n/ja/docusaurus-theme-classic/navbar.json です。

  • Markdown/MDXの本文

    ドキュメントは i18n/ja/docusaurus-plugin-content-docs/current、ブログは i18n/ja/docusaurus-plugin-content-blog に置きます。

JSONの翻訳ファイルは、message 属性の値だけを訳します。キーと description は変えません。この「何を変えてはいけないか」が明確な構造は、Claude Codeに任せる作業として扱いやすい部分です。

JSONの雛形を生成する

JSONは手で書き起こさず、コマンドで雛形を出します。

npm run write-translations -- --locale ja

write-translations の既定の出力先は website/i18n/<defaultLocale>/... で、--locale で出力するロケールのフォルダを選べます。--override を付けると既存の翻訳メッセージを上書きするので、すでに訳した文言がある状態では付けないのが安全です。

Markdownは cp -r で複製してから訳す

ドキュメントの翻訳は、まず元のファイルを i18n/ja/... にコピーします。手順は次のとおりです。

mkdir -p i18n/ja/docusaurus-plugin-content-docs/current
cp -r docs/. i18n/ja/docusaurus-plugin-content-docs/current

ここは実行して終わりではなく、コピーした各ファイルをClaude Codeが訳します。ブログは docusaurus-plugin-content-blog、単独ページは docusaurus-plugin-content-pages が行き先です。コピーされるのは .md と .mdx だけで、Reactのページは先のJSONで翻訳されます。

見出しIDを固定しないと、翻訳した瞬間にアンカーが壊れる

日本語化でリンク切れが増える最大の原因が、見出しのIDです。

Docusaurusは、見出し ### Hello World から hello-world というIDを自動生成します。他の文書は [link](#hello-world) でこれを参照できます。ところが翻訳して見出しが ### こんにちは世界 になると、IDも変わります。日本語の見出しからできるIDを、本文側のすべてのアンカーリンクに反映し直す羽目になります。

チュートリアルは、翻訳サイトでは明示的な見出しIDの使用を推奨しています。そのためのコマンドが write-heading-ids です。

npm run write-heading-ids

write-heading-ids は、Markdownの見出しに明示的なIDを書き込みます。構文は既定の classic({#id})と mdx-comment を選べ、--overwrite を付けると既存のIDを再生成します。

翻訳を始める前に一度実行して、IDを固定してください。

前と後

見出しIDを固定する前後

BEFORE

IDが自動生成のまま

英語の見出し ### Hello World を日本語に訳すと、IDが変わります。[link](#hello-world) は宛先を失います。

AFTER

明示的なIDを付けた状態

見出しの末尾に {#hello-world} のようなIDを書いておけば、見出しを訳しても参照先は動きません。

リンク切れを検査に使う: onBrokenLinksとビルド

既定の挙動を確認する

Docusaurusのリンク切れの扱いは、サイト設定のキーで決まります。

キー検出対象既定の挙動
onBrokenLinks検出対象リンク切れ全般既定の挙動エラーにする(throw)
onBrokenAnchors検出対象Headingコンポーネントで宣言されたアンカーの切れ既定の挙動警告を出す
markdown.hooks.onBrokenMarkdownLinks検出対象Markdownのリンク切れ既定の挙動警告を出す
markdown.hooks.onBrokenMarkdownImages検出対象Markdownの画像URLの切れ既定の挙動エラーにする(throw)

取り得る値は、どれも 'ignore'・'log'・'warn'・'throw' の4つです。onBrokenMarkdownLinks をトップレベルに置く書き方は、Docusaurus v3.9で非推奨になり、v4で削除される予定です。現在は markdown.hooks の中に置きます。

重要な制約が1つあります。リンク切れの検出は、本番ビルド(docusaurus build)でしか動きません。npm run start の開発サーバーでは検出されないので、「画面で確認して問題なし」は検査になりません。

検査を厳しく設定する

検査に使うには、アンカーやMarkdownのリンク切れも落とす設定にします。次の設定は、リファレンスにある値の種類を使った例です。

export default {
  onBrokenLinks: 'throw',
  onBrokenAnchors: 'throw',
  markdown: {
    hooks: {
      onBrokenMarkdownLinks: 'throw',
      onBrokenMarkdownImages: 'throw',
    },
  },
};

onBrokenAnchors と onBrokenMarkdownLinks は、既定では警告止まりです。画像は既定でエラーになるため、onBrokenMarkdownImages は明示のために書いています。そのままだとビルドは成功し、Claude Codeは「通った」と判断します。throw にして初めて、ビルドの終了コードが検査として働きます。

i18n を設定すると、日本語版は /ja/ 以下にビルドされます。翻訳したページのリンクがこの構成で壊れても、throw にしておけばビルドの時点で止まります。

ロケールごとにビルドして確かめる

全ロケールのビルドと、単一ロケールのビルドは出力が違います。

  • npm run build: ロケールごとにシングルページアプリケーションを1つずつ生成する。日本語版は build/ja
  • npm run build -- --locale ja: 日本語だけをビルドする。/ja/ のURLプレフィックスは付かない

後者は、ロケールごとに別ドメインへデプロイする構成(マルチドメイン配信)のためのものです。URLプレフィックスの有無で内部リンクの解決が変わるので、配信方式に合うほうで検査してください。

Claude Codeに検査を回させる: CLAUDE.mdとStop hook

CLAUDE.mdに検査コマンドと禁止事項を書く

Claude Codeのベストプラクティスは、「Claudeが自分で実行できる検査(テスト、ビルド、リンタ)を渡す」ことを最優先に挙げています。通ったか落ちたかの信号があれば、Claudeは作業、検査、結果の読み取り、修正のループを自力で回します。

CLAUDE.mdには、確認できる形で書きます。「Run npm test before committing」のように、検証できる具体性が要ります。

## 検査
- 翻訳を変更したら `npm run build` を実行し、エラーが0になるまで直す
- 見出しIDは変更しない(`{#...}` を訳さない)
- JSONの翻訳ファイルは `message` の値だけを訳す。キーと `description` は変えない
 
## 翻訳の更新手順
1. `npm run write-translations -- --locale ja` で未翻訳のキーを確認する
2. `--override` は付けない(訳済みの文言が消える)
3. `docs/` に追加・変更があったら、`i18n/ja/.../current` の同名ファイルを更新する

CLAUDE.mdはあくまで「文脈」であり、設定の強制ではありません。強制したい検査はhookに移します。

Stop hookでビルドが落ちたら終わらせない

ビルドを毎回の編集後に走らせると、翻訳ファイルを1つ直すたびにサイト全体のビルドが走ります。Claudeの応答が終わる時点で1回だけ走らせるほうが現実的です。それを担うのが Stop イベントです。

Stop はClaudeが応答を終えるときに発火します。hookが終了コード2で終わると、Claudeは止まらず会話を続けます。stderrの内容は、その理由としてClaudeに渡されます。

.claude/settings.json の例です。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": ".claude/hooks/check-docs-build.sh"
          }
        ]
      }
    ]
  }
}

スクリプトは、ビルドを実行して失敗の末尾をstderrに出します。

#!/bin/bash
# .claude/hooks/check-docs-build.sh(要 jq)
input=$(cat)
 
# 公式の注意: stop_hook_active が true のときは
# 既に hook 由来で続行中。直らない失敗で無限に続けない
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
 
if ! out=$(npm run build 2>&1); then
  echo "docusaurus build が失敗しました:" >&2
  echo "$out" | tail -n 40 >&2
  exit 2
fi

Stopフックの入力には stop_hook_active が含まれ、hookによる続行の最中は true になります。この値を見ないと、直せない失敗が残った場合にループが終わらなくなります。上のスクリプトは、2回目以降は通す設計にしています。

テストをhookで回す設計の比較はテスト自動化のhooks、失敗時に操作を止める設定はonFailureの記事が詳しいです。

翻訳の更新を非対話モードで回す

ドキュメントが更新されるたびに、翻訳ファイルの差分を埋める作業は繰り返しです。Claude Codeには、スクリプトから呼べる非対話モード(claude -p)があります。

claude -p "docs/ の変更に合わせて i18n/ja を更新し、\
npm run build が通ることを確認して" \
  --allowedTools "Read,Edit,Bash(npm run build),Bash(npm run write-translations *)"

--allowedTools は、確認なしで使えるツールを指定します。末尾の * は前方一致で、Bash(git diff *) のように使います。スペースを入れないと、別のコマンドまで一致します。上の例では、実行を許すBashコマンドをビルドと write-translations に絞っています。素の Bash を許可すると何でも通ってしまうため、許可を絞っています。

つまずきやすい点

落とし穴

一部のロケールだけ検査して、別のロケールのリンク切れが残る

npm run build -- --locale ja が通っても、英語版のリンクが壊れていないとは限りません。通常のビルドは全ロケールを対象にするため、最終確認は npm run build で行います。

落とし穴

動的な文言が write-translations に拾われない

write-translations は静的解析のみで、サイトを実際に動かしません。<Translate>{item.title}</Translate> のように文言が式になっていると、抽出されません。実行時には動作しますが、翻訳ファイルに出てこないので、Claude Codeが「未翻訳のキーはない」と誤判断する原因になります。文言は静的な文字列として書いてください。

落とし穴

--override で訳済みの文言が初期値に戻る

write-translations --override は既存の翻訳メッセージを上書きします。Bash(npm run write-translations *) の許可は npm run write-translations -- --locale ja --override にも一致するので、--override は止められません。許可を Bash(npm run write-translations -- --locale ja) のような完全一致の形に絞れば、オプション付きの呼び出しは確認待ちになります。CLAUDE.mdの「--override を付けない」は文脈であって強制ではないため、止めたい場合は完全一致の許可で守ります。

GitHub Pagesで配信する場合は、チュートリアルの注意も確認が必要です。ロケールごとに別ドメインへ出す構成は、デプロイ先を1つしか持てないGitHub Pagesでは使えません。404ページを /ja/404.html に振り分ける設定も、GitHub Pagesでは行えません(Netlifyでは可能)。

運用の流れ

手順

翻訳を更新する1サイクル

  1. 1

    見出しIDを固定する

    初回だけ write-heading-ids を実行します。

  2. 2

    雛形を出して訳す

    write-translations と cp -r で未翻訳のファイルを揃え、Claude Codeが訳します。

  3. 3

    Stop hookでビルドを走らせる

    リンク切れがあればエラーが返り、Claudeが自分で直します。

  4. 4

    全ロケールでビルドし直す

    最後に npm run build を通して終わりです。

よくある質問

翻訳の協業はどう管理するのが標準ですか

Docusaurusは翻訳の管理方法に関与せず、ビルド時に翻訳ファイルがファイルシステムにあればよい作りです。一般的な方法はgitで管理する方法とCrowdinを使う方法です。Claude Codeに訳させた結果を人が確認する運用なら、gitの差分レビューが素直です。

まとめ

日本語化の検査は、見出しIDの固定、throw に揃えたリンク切れ設定、Stop hookのビルドの3点で成り立ちます。どれか1つ欠けると、「ビルドが通った」がリンクの無事を保証しなくなります。まずは write-heading-ids を訳す前に1回実行し、設定を throw に揃えるところから始められます。

この記事を共有:XはてブLinkedIn