Claude CodeでREADMEの日本語版を同期する — 変更節だけ訳して構造を検査
README.mdを更新したPRで、Claude Code GitHub ActionがREADME.ja.mdの該当節だけを訳し、コードブロックとリンクの食い違いをスクリプトで落とす手順です。
英語のREADME.mdを正とし、README.ja.mdを後から追随させる運用は、放っておくと数か月で内容がずれます。直す方法は2段に分けると安定します。変更された節だけをClaude Code GitHub Actionに訳させ、訳さない要素(コードブロックとリンク先)はスクリプトで機械的に突き合わせます。この記事は、そのワークフローと検査スクリプトを手元で動かせる形にまとめます。
訳文の質は人が読んで判断するしかありません。一方で、コードの書き換わりやURLの取り違えは機械で止められます。判断が要る部分と要らない部分を分けるのが、この運用の骨格です。
日本語版を同期する流れ
READMEの同期は、PRごとに次の順で進みます。
README.md更新PRでの同期の流れ
- 1
PRがREADME.mdを変更する
ワークフローは
pathsにREADME.mdを指定し、READMEに触れたPRだけで起動します。 - 2
Claudeが変更節だけをREADME.ja.mdに反映する
PRの差分を読み、該当する節だけを日本語に直します。他の節は触りません。
- 3
スクリプトが構造の食い違いを検査する
節の数、コードブロック、リンク先が英語版と一致しなければ、ここでジョブが失敗します。
- 4
変更があればPRブランチにコミットする
README.ja.md以外が変わっていないことを確かめてからpushします。
Claude Code GitHub Actionは、ワークフローに prompt を渡すと自動実行モードで動き、メンションを待ちません。prompt がなければ @claude を待つ対話モードになります。今回は前者です。
自動実行モードで気をつける点があります。プロンプトが平文のとき、ClaudeはシェルもGitHub APIも使えず、必要なツールを claude_args の --allowedTools か settings の許可ルールで与える必要があります。以降のワークフローで許可を細かく書くのはこのためです。
訳す要素と訳さない要素を先に決める
同期の品質は、何を訳さないかを先に固定できるかで決まります。READMEで扱いが分かれる要素を表にします。
| 要素 | 日本語版での扱い | 機械で検査できるか |
|---|---|---|
| 見出し・地の文 | 日本語版での扱い訳す | 機械で検査できるかできない(人が読む) |
| コードブロック | 日本語版での扱い一字も変えない | 機械で検査できるかできる(フェンス内の完全一致) |
| 外部URL・相対パスのリンク先 | 日本語版での扱い変えない(リンクの文言は訳す) | 機械で検査できるかできる(リンク先の集合一致) |
ページ内アンカー(#install) | 日本語版での扱い見出しの訳に追随して変わる | 機械で検査できるか対象外 |
| 節の数・順序 | 日本語版での扱い英語版と揃える | 機械で検査できるかできる |
ページ内アンカーを検査から外しているのは、見出しを訳すとアンカーの文字列も変わるからです。## Install が ## インストール になれば、#install は #インストール に変わります。アンカーまで完全一致を求めると、正しく訳したPRが毎回落ちます。
コードブロックを訳さない方針は、内部のコメントも対象にします。コメントまで日本語にしたいなら、検査を緩める必要があります。ここでは「コードブロックは完全一致」に固定したほうが、検査の意味が単純で運用に乗せやすくなります。
構造を比べる検査スクリプト
英語版と日本語版を ## 見出しで節に分け、節ごとにコードブロックとリンク先を比較します。Pythonの標準ライブラリだけで書けます。
#!/usr/bin/env python3
"""README.md と README.ja.md の構造を比べる。"""
import re
import sys
from pathlib import Path
FENCE = re.compile(r"^```.*?^```", re.S | re.M)
LINK = re.compile(r"\]\(((?:https?://|\./|\.\./)[^)\s]+)\)")
def split_sections(text):
parts = re.split(r"^(?=## )", text, flags=re.M)
return [p for p in parts if p.startswith("## ")]
def heading(sec):
return sec.splitlines()[0]
def main(en_path, ja_path):
en = split_sections(Path(en_path).read_text(encoding="utf-8"))
ja = split_sections(Path(ja_path).read_text(encoding="utf-8"))
errors = []
if len(en) != len(ja):
errors.append(f"節の数が違います: en={len(en)} ja={len(ja)}")
for i, (a, b) in enumerate(zip(en, ja), start=1):
name = f"{i}番目の節 ({heading(a)} / {heading(b)})"
if FENCE.findall(a) != FENCE.findall(b):
errors.append(f"{name}: コードブロックが一致しません")
if sorted(LINK.findall(a)) != sorted(LINK.findall(b)):
errors.append(f"{name}: リンク先が一致しません")
for e in errors:
print("NG", e)
print("OK" if not errors else f"{len(errors)} 件の不一致")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main(*sys.argv[1:3]))scripts/check_readme_sync.py として置き、手元で動かして挙動を確かめます。確認用に2組の小さなREADMEを用意しました。1組目は正しく訳した例、2組目は日本語版でコードの --fast を --高速 に書き換え、リンク先に /ja/ を差し込んだ例です。Python 3.12.2で実行した結果は次のとおりです。
python3 check_readme_sync.py ok/README.md ok/README.ja.md
# OK(終了コード 0)
python3 check_readme_sync.py bad/README.md bad/README.ja.md
# NG 1番目の節 (## Install / ## インストール): リンク先が一致しません
# NG 2番目の節 (## Usage / ## 使い方): コードブロックが一致しません
# 2 件の不一致(終了コード 1)どの節で何が食い違ったかが見出し付きで出るので、ジョブのログから直す場所を探せます。英語版の末尾の節を削って節数をずらした場合は、先頭に「節の数が違います」が出て、以降の節の対応もずれて報告されます。節の増減は、まず数の不一致から直してください。
この検査には限界があります。インラインコード(`--fast` のような短い記述)、表のセル、バッジ画像の記述は見ていません。見出しの階層(## と ### の対応)も比べません。ここまで厳密にしたいときは、同じ要領で抽出対象を増やします。
GitHub Actionのワークフロー
PRごとに起動するワークフローを書きます。Claudeの手順と、検査・コミットの手順を別ステップに分けるのがポイントです。モデルにpushまでさせず、モデルが触ったファイルを決定的な手順で確かめてからコミットします。
name: Sync README.ja
on:
pull_request:
paths: ["README.md"]
jobs:
sync:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
README.md が base ブランチ (origin/${{ github.base_ref }}) から
どう変わったかを git diff で確認し、変更された節だけを
README.ja.md に反映してください。README.ja.md 以外は編集しない。
claude_args: |
--max-turns 8
--allowedTools "Read,Edit,Bash(git diff *)"
- name: 構造を検査
run: python3 scripts/check_readme_sync.py README.md README.ja.md
- name: 日本語版をコミット
run: |
changed=$(git diff --name-only)
[ -z "$changed" ] && exit 0
[ "$changed" = "README.ja.md" ] || { echo "想定外の変更: $changed"; exit 1; }
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.ja.md
git commit -m "docs: README.ja.md を README.md に同期"
git push各行の意図を順に説明します。
ifでフォークからのPRを外しています。公開リポジトリでは、フォークPRから起動した実行にGitHubがシークレットを渡さないためです。id-token: writeは、既定のGitHub App認証に必要です。サブスクリプションで認証するなら、anthropic_api_keyの行をclaude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}に置き換えます。fetch-depth: 0は、baseブランチとの差分をgit diffで取るために履歴を持ってくる指定です。actions/checkoutは、Claudeの作業場所にリポジトリのコピーを置く役目も持ちます。--allowedTools "Read,Edit,Bash(git diff *)"で、読む・編集する・差分を見る、の3つに絞っています。ReadとEditを並べると、確認なしでファイルを読み書きできます。末尾の*は前方一致で、git diffの前に空白を置くのが肝心です。空白がないとgit diff-indexまで通ってしまいます。--max-turnsとtimeout-minutesは、暴走したジョブの費用を抑える手段です。コスト管理の節でも、ターン数の上限とワークフロー単位のタイムアウトが挙げられています。
最後のステップは、変更が README.ja.md だけであることを条件にコミットします。Edit を許可しているので、モデルが別のファイルに手を入れる余地は残ります。その保険をシェルの2行で持たせています。
認証と実行条件の注意
Claude Code GitHub Actionは、起動前に実行者を2つ確かめます。PRやissueのイベントでは、実行したユーザーにリポジトリの書き込み権限が必要です。また、botが実行者のときは allowed_bots に載せない限り拒否されます。Dependabotが依存更新と一緒にREADMEを書き換えるリポジトリでは、この拒否でジョブが失敗します。
訳し方をCLAUDE.mdに書いておく
プロンプトだけに訳の方針を詰めると、PRごとに表記がぶれます。リポジトリ直下のCLAUDE.mdに規約を書いておけば、コード規約やレビュー基準と同じように、Claudeはその指針に従ってPRを作ります。READMEの翻訳規約なら、次のような断片になります。
## README の翻訳規約
- README.md が正。README.ja.md は README.md に追随させる
- コードブロック、インラインコード、URL、バッジは訳さない
- 用語は次の表記に揃える: pull request → プルリクエスト、
workflow → ワークフロー、secret → シークレット
- 敬体(ですます調)で書き、見出しの順序と数は英語版と同じにする
- 訳せない固有名詞は原文のまま残し、初出のみ括弧で補うCLAUDE.mdは各実行で読み込まれるので、簡潔に保ちます。長大になると、毎回の費用に響きます。用語表を増やしたくなったら、別ファイル(docs/glossary.md)に出してCLAUDE.mdから参照する形が扱いやすくなります。
用語集を整える段階は、翻訳ワークフロー全般の話です。JSON形式の文言を検査しながら訳す手順はi18nextのJSON翻訳が、ドキュメントサイト全体を多言語化する構成はDocusaurusの日本語i18nが扱っています。
運用で踏みやすい落とし穴
落とし穴
自動コミットの後にCIが動かない
ワークフローがデフォルトの GITHUB_TOKEN でpushしたコミットは、GitHubの仕様で他のワークフローを起動しません。必須チェックがあるPRでは、README.ja.mdのコミット後にチェックが付かず、マージできなくなることがあります。対処として、デフォルトの GITHUB_TOKEN をやめ、カスタムのアプリトークンなどで認証する方法があります。同期ワークフロー自身は paths が README.md だけなので、日本語版のコミットで再起動することはありません。
もう1つ、過去に溜まった差分の扱いがあります。このワークフローは、PRで変わった節だけを訳します。すでに食い違っている日本語版は、それより前のPRで取り残された分なので、導入前に一度、手元で全体を揃えます。
python3 scripts/check_readme_sync.py README.md README.ja.md最初の実行でNGが出た節から、claude に「README.mdのこの節に合わせてREADME.ja.mdを直して」と頼んで揃えれば、以降は差分だけで回ります。README自体を新しく書く手順は、JSDoc・README・変更ログの生成が受け持ちます。この記事は、それが書かれたあとの多言語版を保つ側です。
訳文は人が読む
構造検査を通ったことは、訳が正しいことを意味しません。意味の取り違え、敬体の混在、用語のゆれはスクリプトに映りません。PRとして日本語版の差分を出す運用にして、レビュー担当が読む段を残します。ワークフローがmainに直接pushしない構成にしているのは、この段を失わないためです。
ワークフロー自体の安全性も点検する価値があります。シークレットを扱うジョブでは、権限の絞り込みや第三者アクションの固定が効きます。点検の手順はzizmorによるGitHub Actionsの脆弱性点検にあります。
設定を変えたくなったときの調整点
実行が重いと感じたら、claude_args に --model を足して、より軽いモデルを選べます。ドキュメントの例では --model claude-sonnet-5 と書かれています。指定しなければ、Claude Codeの既定のモデルが使われます。
READMEが短く、節ごとの訳が数行で済むなら、--max-turns を下げても足ります。節が10を超える長いREADMEでは、差分が大きいPRでターン数が足りなくなるので、実行ログのターン数を見ながら調整します。
PRを待たず、毎晩の定期実行で差分を集めて直す手もあります。スケジュール実行は schedule トリガーで書けますが、GitHubはデフォルトブランチからしか定期実行しない点と、公開リポジトリでは60日間活動がないと停止する点に注意が必要です。
まとめ
README.ja.mdの追随は、「訳す」と「訳さない」を分ける設計が効きます。訳す側はClaude Code GitHub Actionに任せ、変更節だけに絞ります。訳さない側は、コードブロックとリンク先の突き合わせをスクリプトに任せます。モデルにpushさせず、触ったファイルを確かめてからコミットする分離も、事故を抑える要所です。まず手元でスクリプトを走らせて既存の差分を揃えたうえで、ワークフローを入れる順序が進めやすくなります。Skillで文書作成の型を揃える方法は、documentationスキルの記事にも続きます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。