Claude CodeでRailsのi18n-tasksを片付ける — missingと未使用キーの扱い
i18n-tasksのmissingでja.ymlとen.ymlの差分を埋め、unusedは動的キーを見分けてから消す手順と、完了前にmissingを検査するStop hookの置き方をまとめます。
i18n-tasksは、RailsのI18n(国際化)で起きる「訳が足りない」「使われていない訳が残る」を検出するgemです。ja.ymlにあってen.ymlにないキー、ビューから消えたのにYAMLに残ったキーを、コマンド一つで数えられます。Claude Codeに片付けを任せるなら、押さえる点は3つです。missingは機械的に埋められる、unusedは動的キーを見分けてから消す、終わる前に検査を必ず通す。
この記事は、その3点をhealthの読み方、missingとunusedの扱い、Stop hookの順で組みます。移行やRuboCopなど開発全般はClaude CodeでRailsアプリを開発する手順が扱うので、ここではI18nのキーの過不足に絞ります。
まずhealthで過不足の数を出す
作業の起点はi18n-tasks healthです。このコマンドは次の3つを一度に調べます。
- 足りない(missing)キーと、使われていない(unused)キー
- 補間変数(
%{name}など)がロケール間で食い違っていないか - ロケールファイルが正規化(整形)されているか
導入はGemfileにgem 'i18n-tasks', '~> 1.1.2', group: :developmentを足し、設定ファイルをコピーするだけです。
bundle add i18n-tasks --group development
cp $(bundle exec i18n-tasks gem-path)/templates/config/i18n-tasks.yml config/
bundle exec i18n-tasks healthClaudeに頼む前に、この出力をそのまま貼って「missingが何件、unusedが何件」を共有しておくと、作業の前後を件数で比べられます。件数が減っていなければ、片付いていません。
missing: ja.ymlとen.ymlの差分を埋める
missingは、ベースロケールにあって他のロケールに無いキー、またはコードが参照しているのにYAMLに無いキーです。埋める手段は2つあります。
add-missingは、ベースの値かキーを読みやすくした文字列をプレースホルダとして入れます。-vで値の形も決められます。たとえば-v 'TRME %{value}'と指定すれば、未翻訳の行にTRMEが付くので、あとから検索して訳し直せます。translate-missingは、Google Translate・DeepL・OpenAIなどのバックエンドで翻訳まで進めます。
どちらを使うかは、訳文の品質をどこで担保するかで決まります。Claude Codeに訳させるなら、add-missingでプレースホルダを入れた状態から始めると、翻訳対象の行が明確です。
bundle exec i18n-tasks missing -l ja
bundle exec i18n-tasks add-missing -v 'TRME %{value}' ja訳を書き込んだら、再度healthを回します。healthの確認事項には補間変数の整合が含まれるので、%{count}や%{name}が訳文で欠けていれば、ここで検出されます。
並行して、Rails側の設定も見直せます。ガイドによると、config.i18n.raise_on_missing_translationsをtrueにすると、翻訳が無いときにTranslation missingの文字列を返さず、エラーを出します。テスト環境でこれを有効にしておけば、ビューで描画される経路でも漏れに気づけます。ただしこれは実行された経路でしか効かず、静的に全キーを調べるi18n-tasksとは役割が違います。
unused: 消す前に動的キーを分ける
unusedは、コードから参照されていないように見えるキーです。「見える」と書いたのは、静的解析で参照を追えないケースがあるからです。ここで機械的にremove-unusedを回すと、使われているキーを消します。
「Features & limitations」の節に、検出できない参照がはっきり書かれています。
| 書き方 | i18n-tasksの扱い |
|---|---|
t "cats.#{cat}.name"(動的キー) | i18n-tasksの扱い既定では認識しない。search.strict: falseか--no-strictで推論できる |
I18n.localize | i18n-tasksの扱い非対応。参照の判断にオブジェクトの型が要るため |
t ".title"(相対キー) | i18n-tasksの扱い対応。ファイルのパスやcontroller.action_nameから解決する |
key.{one,other}(複数形) | i18n-tasksの扱い対応 |
動的キーの扱いは二択です。一つは# i18n-tasks-use t('activerecord.models.user')のヒントコメントを、該当コードの直前に置く方法です。こちらが推奨の方法です。もう一つはsearch.strictをfalseにして、補間部分をワイルドカードとして扱う方法です。後者はcats.tenderlove.nameを使用済みにしますが、cats.tenderlove.special.nameのように深い階層は未使用と報告します。補間1か所につきワイルドカードは1階層分、という限定です。
ignoreで始まる設定で、特定のキーを検査対象から外すこともできます。READMEはignore*としか書いておらず、具体的な項目名はテンプレートのconfig/i18n-tasks.ymlに載っています。
削除の手順
手順は次の順にすると、誤削除を防ぎやすくなります。
未使用キーを消すまでの順序
- 1
unusedの一覧を出す
bundle exec i18n-tasks unusedの出力をClaudeに渡し、キーを「動的に組み立てているもの」「管理画面などで文字列連結しているもの」「本当に使われていないもの」に分類させます。迷うキーは
bundle exec i18n-tasks find 'auth.*'のようにワイルドカード付きで検索すると、キーが使われている場所を一覧できます。find common.helpと完全なキーを指定することもできます。'{number,currency}.format.*'のように波かっこで候補を並べる書き方も使えます。unusedに出たキーの階層を指定して検索し、参照が一件も出なければ「本当に使われていない」側に、動的な呼び出しの近くで見つかれば「動的キー」側に分けます。 - 2
動的キーにヒントを足す
前者2つの参照箇所に
i18n-tasks-useコメントを足すか、設定をstrict: falseにします。足した後にunusedを再実行し、一覧が減ることを確かめます。 - 3
消す対象を確定してから実行する
remove-unusedは削除前に確認を挟みます。確認の出力をClaudeではなく人間が一度読み、git diffで差分を見てからコミットします。 - 4
テストと画面で確認する
raise_on_missing_translationsを有効にしたテストを通し、主要画面を開いてTranslation missingが出ないことを見ます。
pruneは別の用途です。ベースロケールに無いのに他のロケールに残っているキーを消します。ja.ymlだけに古いキーが残る状況なら、pruneの出番です。
Claudeに渡す指示の書き方
i18n-tasksの作業は、危ない操作と安全な操作がはっきり分かれます。CLAUDE.mdに次のような規約を置くと、毎回の指示を短くできます。
## I18n
- 翻訳キーを足したら `bundle exec i18n-tasks health` を通す
- `remove-unused` と `rm` は、動的キーの確認前に実行しない
- `t("a.#{x}.b")` のような動的キーには `# i18n-tasks-use t('a.foo.b')` を足す
- ja.yml と en.yml のキーを手で並べ替えない。整形は `i18n-tasks normalize` に任せる最後の行は意味があります。READMEによると、i18n-tasksはYAMLの整形にPsychを使い、複数行文字列の書式が意図せず変わることがあります。行末に空白が1つ入っているだけで、|形式が引用符付きの一行に変わる例が載っています。手で整えた差分とnormalizeの差分が混ざると、レビューで本物の変更が埋もれます。整形のコミットは分けると読みやすくなります。
Stop hookで未達のまま終わらせない
「訳を足した」と言って作業を終えたのに、missingが残っている。これを防ぐのに向くのがStop hookです。hooksのリファレンスによると、Stopでexit code 2を返すと、Claudeが止まらずに作業を続けます。stderrの内容は、続行の理由として渡されます。
また同じ資料には、stop_hook_activeがtrueなら、すでにStop hookの結果で続行している最中という説明があります。この値を見ずに毎回exit 2を返すと、直せない条件のまま止まらなくなるので、スクリプトの冒頭で確認します。
.claude/settings.jsonに次を足します。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/i18n-missing.sh"
}
]
}
]
}
}スクリプトは次のとおりです。
#!/bin/bash
# .claude/hooks/i18n-missing.sh
input=$(cat)
# すでにStop hookの結果で続行中なら、二重にブロックしない
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
exit 0
fi
cd "$CLAUDE_PROJECT_DIR" || exit 0
out=$(bundle exec i18n-tasks missing -f keys 2>&1)
if [ -n "$out" ]; then
echo "i18n-tasks missing が残っています。足りないキーを埋めてください。" >&2
echo "$out" >&2
exit 2
fi
exit 0-f keysはadd-missing --helpの出力形式に載っている値です。missingも-fを受けますが、選べる値はbundle exec i18n-tasks missing --helpで確かめてください。出力が空かどうかで判定するので、i18n-tasksが終了コードをどう返すかには依存しません。
PostToolUseではなくStopにしたのは、編集のたびに走らせる必要が薄いからです。PostToolUseは、リファレンスの表ではexit 2でもstderrをClaudeに見せるだけで、続行を強制しません。一連の作業が終わる地点で一度だけ検査するほうが、途中の未完成状態でClaudeが差し戻されずに済みます。
片付けた状態を保つ
CIで毎回missingとunusedを検査するテストのテンプレートが、gemに同梱されています。RSpecならtemplates/rspec/i18n_spec.rb、Minitestならtemplates/minitest/i18n_test.rbをコピーします。
cp $(bundle exec i18n-tasks gem-path)/templates/rspec/i18n_spec.rb spec/hookはClaudeの作業中に効く仕組みで、CIは人間の変更も含めて効く仕組みです。両方置くと、ローカルでは自走し、マージ前には落ちます。
つまずきやすい点
検査の結果が現実とずれるときは、次の順で疑います。
- 参照が見つからない: 既定では
app/ディレクトリしか探しません。lib/などにあるtの呼び出しは、設定ファイルのsearchセクションでパスを足します - モデルの属性名が未使用扱い:
Model.human_attribute_nameやmodel_name.humanの参照は、search.prism: "rails"のモードが扱います。READMEによると、before_action内の翻訳呼び出しにも対応します。ただしPrismスキャナーはテスト協力を募っている段階の機能で、bundle exec i18n-tasks check-prismで既存のスキャナーとの差を比べられます - ヘルパー経由のキー:
page_titleのようにt '.page_title'を返すメソッドは、組み込みのPatternMapperで対応づけます .hamlなどERB以外: AST解析は.rbと.html.erbが対象で、他はregexpスキャナーです。複雑な書き方は取りこぼす可能性があります
未使用コードの検出という点では、JavaScriptのknipで未使用のexportと依存を削る手順と考え方が共通しています。動的な参照を先に洗い出してから消す、という順序です。一方、フロントエンド側のi18nextのキー検査はi18nextのJSON翻訳を進める記事で扱っています。RuboCopの指摘を並行して片付けるなら、RuboCopのtodoの縮め方が同じ進め方です。
まとめ
i18n-tasksの片付けでは、missingは機械で埋められ、unusedだけが判断を要します。消す前に動的キーへヒントを足しておけば、誤削除の大半を避けられます。そのうえで、Stop hookでmissingの残りを止める検査を置き、CIのテストで人間の変更も拾えば、片付けた状態が続きます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。