Claude Media
Claude Codeでxcstringsを翻訳する — 未翻訳と要レビューだけ埋める手順

Claude Codeでxcstringsを翻訳する — 未翻訳と要レビューだけ埋める手順

NSLocalizedStringのiOSアプリをString Catalog(.xcstrings)へ移し、未翻訳と要レビューの行だけをClaude Codeに訳させる手順です。抽出・書き戻し・検証のスクリプト付き。

String Catalog(.xcstrings)は、アプリの文字列とすべての言語の訳を1つのファイルにまとめる、Xcodeの翻訳管理の形式です。.strings と .stringsdict に分かれていた翻訳が1ファイルに入り、各言語の訳に「New」「Needs Review」「Stale」といった状態が付きます。この状態を手がかりにすると、Claude Codeに渡す範囲を「まだ訳していない行」と「見直しが要る行」だけに絞れます。

このガイドは、既存のiOSアプリをString Catalogに移し、その状態列を使って翻訳を埋めるまでを扱います。役割分担は単純です。移行ボタンとカタログの目視確認はXcode、抽出・翻訳・書き戻し・検査はClaude Codeが受け持ちます。

先に結論: 分担と流れ

Xcodeにしかできないことと、Claude Codeに任せられることを先に分けておくと迷いません。

くらべる

Xcodeが握る作業とClaude Codeが受け持つ作業

GUIと言語機能

Xcode

右クリックの「Migrate to String Catalog」、ビルドによる文字列の抽出、状態列の確認、「Mark as Reviewed」、スキームの言語を切り替えての実行です。

ファイルとコマンド

Claude Code

移行前のコード点検、未翻訳行の抽出、訳文の生成、カタログへの書き戻し、プレースホルダーの検査、差分の確認です。

流れは次の5段です。

手順

未翻訳と要レビューを埋める5段階

  1. 1

    コードを点検する

    キーが変数になっている NSLocalizedString など、抽出されない書き方を洗い出します。

  2. 2

    Xcodeで移行してビルドする

    .strings をカタログに移し、ビルドで文字列を集め直します。

  3. 3

    対象の行を抜き出す

    未翻訳と要レビューの行だけを小さなJSONにします。

  4. 4

    Claude Codeに訳させて書き戻す

    訳文は「要レビュー」の状態で戻し、人が確認できる形にします。

  5. 5

    検査して、Xcodeで目視する

    プレースホルダーの不一致を機械で見つけ、言語を切り替えて起動します。

String Catalogで翻訳管理の何が変わるか

従来の仕組みでは、1つの文字列テーブルが言語ごとの lproj フォルダーに散らばる .strings と、複数形用の .stringsdict で構成されていました。String Catalogはこのテーブル全体を1ファイルに収めます。キーごとの全言語の訳と、コメントなどのメタデータが同居します。

WWDC23のセッション「Discover String Catalogs」によると、カタログの実体はJSONで、ソース管理で差分を追いやすい形式です。ビルド時にはこれまでと同じ .strings と .stringsdict にコンパイルされます。そのため最低デプロイメントターゲットを上げずに使い始められます。コードから抽出しただけのソース文字列は、最終的なビルドには含まれません。

もう1つの変化が、Xcodeによる同期です。Xcodeはビルドのたびに、コード中のローカライズ対象の文字列を拾ってカタログへ追加します。SwiftUIのビューに書いた文字列リテラルは、基本的にそのまま対象になります。Apple公式ドキュメントでは、既定のファイル名として Localizable.xcstrings が案内されています。

この「ビルドで勝手に増える」性質がClaude Codeとの相性を決めます。カタログのキーはコードが正で、訳文だけがカタログ側の資産です。Claude Codeが触るのは訳文の欄に限り、キーの追加や削除は触らない、という線引きが安全です。

状態の読み方 — どの行をClaude Codeに渡すか

Xcodeのカタログエディターは、言語ごとに各キーの状態を表示します。セッションで説明されている状態は次のとおりです。

状態意味Claude Codeに渡すか
New意味その言語にまだ訳がない(コードに文字列を足した直後など)Claude Codeに渡すか渡す
Needs Review意味値の見直しが必要。現在の値のままで良ければ「Mark as Reviewed」Claude Codeに渡すか渡す(人の確認が前提)
緑のチェック意味訳済みClaude Codeに渡すか渡さない
Stale意味翻訳済みの文字列がコードから見つからなくなったClaude Codeに渡すか渡さない

Staleの扱いには注意が要ります。訳のないキーがコードから消えた場合、Xcodeはカタログからも削除します。訳が入っているキーが消えた場合は、削除せずStaleとして残します。不要と確認できたら人が消す運用で、Claude Codeに一括削除させないほうが安全です。

動的に組み立てるキーやデータベース由来のキーは、インスペクターで手動管理に切り替えられます。手動管理の文字列は、ビルド後の同期で更新も削除もされません。

JSONの上では、これらが各言語の stringUnit の state と value として並びます。キー名を断定する前に、手元のカタログをテキストエディターで開いて構造を見てください。複数形やデバイス別の訳は variations の下に入れ子になり、1つのキーに状態が複数ぶら下がります。

移行前にClaude Codeでコードを点検する

.strings からの移行はXcodeの仕事ですが、その前にコード側の問題をClaude Codeに洗わせておくと、移行後の「抽出されない文字列」を減らせます。

見るのは次の3点です。

  • キーが文字列リテラルでない呼び出し。 NSLocalizedString のキーとコメントは文字列リテラルでなければなりません。変数や nil、空文字をキーにすると、Xcodeはその文字列をエクスポートできません。
  • Swiftの文字列抽出の設定。String Catalogは、Swiftコンパイラーの機能でローカライズ対象を抽出します。セッションでは、ビルド設定「Use Compiler to Extract Swift Strings」を有効にするよう案内されています。
  • 独自のマクロやInfo.plist。Objective-Cで独自のローカライズ用マクロを使っているなら、ビルド設定「Localized String Macro Names」に登録します。Info.plistの文字列は InfoPlist.xcstrings を別に足す形で扱います。

依頼の例です。読み取りだけの調査なので、編集は走らせません。

Swift/Objective-Cのソースから、ローカライズ対象になる呼び出し
(NSLocalizedString, String(localized:), LocalizedStringResource)を洗い出して。
キーが文字列リテラルでないもの、コメント引数が空のものを一覧にして。
ファイルは編集しないで。

編集前に提案だけ見たいときは、プランモードが使えます。claude --permission-mode plan で起動するか、セッション中に Shift+Tab で切り替えると、承認するまでファイルは書き換わりません。

SwiftUIのプロジェクトの下準備はClaude CodeとXcodeでSwiftUI iOSアプリを開発する手順にまとめています。

Xcodeで移行して、文字列を集め直す

ここはXcodeの操作です。セッションの実演では、対象の .strings を右クリックして「Migrate to String Catalog」を選びます。移行アシスタントが移行できるファイルを一覧にするので、テーブルとターゲットを選んで実行します。移行後にXcodeがビルドして文字列を抽出します。

セッションの例では、.strings の文字列も .stringsdict の複数形も移行され、既存のアラビア語訳はそのまま残りました。一方でフランス語は進捗が100%に届かず、訳が抜けていた行がそのまま可視化されています。この「抜けの可視化」が、次の抽出の入り口になります。

新しいカタログを足すだけなら、Xcodeで「File > New > File from Template」から「String Catalog」を選びます。名前は既定の Localizable のままで構いません。ビルドすれば、文字列がカタログに集まります。

.strings とString Catalogは同じプロジェクトに共存できます。テーブルごとに順番に移せるので、Localizable だけ先に移して様子を見る進め方もあります。

未翻訳と要レビューの行だけを抜き出す

ここからがClaude Codeの出番です。カタログ全体を読ませると、訳済みの行までコンテキストを消費します。抜き出して小さなJSONにするほうが、読む量も、間違えられる範囲も小さくなります。

次のコマンドは、ja の訳が「訳済み」でないキーを、コメントを添えて取り出します。jq が必要です。複数形の中に未訳が1つでもあるキーも拾えるよう、variations の下まで再帰的に状態を見ています。

jq '[.strings | to_entries[]
  | select(.value.extractionState != "stale")
  | select(([.value.localizations.ja | .. | .state?]
            | index("translated")) | not)
  | {key: .key, value: (.value.comment // "")}]
  | from_entries' Localizable.xcstrings > pending.json

訳文の入っていないキーは localizations 自体を持たないことがあります。上の式は、その場合も「未翻訳」として拾います。

検証用に作った4キーのカタログ(訳済み1、要レビュー1、未訳1、複数形の要レビュー1)で実行すると、pending.json には要レビューの Delete %@?、未訳の Welcome back、複数形の %lld items の3キーだけが出ました。訳済みの Save は出ません。期待どおり「これから触る行だけ」に絞れています。

要レビューのうち「現在の訳を承認するだけ」の行は、Claude Codeに回さず人が「Mark as Reviewed」で済ませる手もあります。誤訳の報告があって見直したい行は、逆にXcodeのメニューから要レビューに戻して、このコマンドの対象に入れます。

Claude Codeに訳させて、カタログへ書き戻す

訳させる対象は pending.json の中のキーだけです。キーの文字列は英語の原文そのものなので、そのまま訳文の元になります。コメントがあれば文脈の手がかりとして渡せます。

プロジェクトの CLAUDE.md に、翻訳の規約を書いておくと依頼が短くなります。

## ローカライズ規約
- ローカライズ対象は Localizable.xcstrings。キーの追加・削除・並べ替えはしない
- 訳すのは pending.json に載ったキーだけ。結果は translated.json に {"キー": "訳文"} で出す
- %@ %lld %d %f などのプレースホルダーは数と順序を原文と一致させる
- 日本語は「です・ます」調。ボタンは短い体言止めか動詞の基本形
- 用語は glossary.md に従う

依頼はこの規約を指すだけで済みます。

pending.json のキーを日本語に訳して translated.json に出して。
カタログ本体(Localizable.xcstrings)は編集しないで。

訳文を受け取ったら、カタログに書き戻します。カタログ全体をClaude Codeに書き換えさせず、書き戻しは決まったスクリプトに任せます。スクリプトなら、触る欄が localizations.ja に限られることを保証できます。

import json, sys
 
catalog, pending, lang = sys.argv[1:4]
data = json.load(open(catalog))
for key, text in json.load(open(pending)).items():
    entry = data["strings"].get(key)
    if entry is None:
        print("skip (no such key):", key)
        continue
    loc = entry.setdefault("localizations", {})
    if "variations" in loc.get(lang, {}):
        print("skip (plural/device):", key)
        continue
    loc[lang] = {"stringUnit": {"state": "needs_review", "value": text}}
with open(catalog, "w") as f:
    json.dump(data, f, indent=2, ensure_ascii=False, separators=(",", " : "))
    f.write("\n")

このスクリプトの方針は3つです。

  1. 存在しないキーは作らない。翻訳側が勝手にキーを増やす事故を防ぎます。
  2. 複数形とデバイス別の訳は飛ばす。言語ごとに複数形の形の数が違うため、Xcodeの「Vary by Plural」で作った枠に人が入れます。
  3. 状態は needs_review で戻す。AIの訳を「訳済み」にしてしまうと、人の確認を通ったのかどうかが区別できなくなります。要レビューで戻せば、Xcodeの状態列でレビュー待ちの行が一目で分かり、確認の済んだ行から「Mark as Reviewed」で消せます。

検証用カタログで試すと、複数形の %lld items と存在しない Nope は読み飛ばされ、Welcome back と Delete %@? だけが needs_review で書き込まれました。ほかのキーの内容は変わらず、差分は2キーに収まっています。

プレースホルダーの不一致を機械で見つける

AIの訳で起きやすい事故は、%@ や %lld の脱落と順序違いです。実行時に崩れるので、書き戻した直後に機械で検査します。

import json, re, sys
 
path, lang = sys.argv[1], sys.argv[2]
spec = re.compile(r"%(?:\d+\$)?\d*(?:\.\d+)?(?:ll|l|h)?[dfsu@]")
bad = 0
for key, entry in json.load(open(path))["strings"].items():
    unit = entry.get("localizations", {}).get(lang, {}).get("stringUnit")
    if unit and sorted(spec.findall(key)) != sorted(spec.findall(unit["value"])):
        print("NG:", key, "->", unit["value"])
        bad += 1
sys.exit(1 if bad else 0)

訳文の %@ を落とした検証用カタログに対しては、NG: Delete %@? -> を消す? と出て終了コード1で止まりました。直したカタログでは何も出ず、終了コードは0です。

終了コードが非0なら止まる作りなので、そのままフックやCIにも載せられます。位置指定の %1$@ のように順序を入れ替えた訳は、このスクリプトでは数と種類だけを比べるため通ります。順序の妥当性は人の目で見てください。

Claude Codeを非対話で回す

文字列が数百行を超えるなら、対話セッションではなく claude -p で一括処理する手があります。標準入力と標準出力がUnixのコマンドと同じように使えるので、スクリプトに組み込めます。

claude -p "pending.json のキーを日本語に訳して translated.json に出して。\
Localizable.xcstrings は編集しない。" \
  --allowedTools "Read,Write"

--allowedTools で許可するツールを絞っているので、読む・書く以外の操作は走りません。出力形式や権限のフラグは、Claude Codeの非対話モードの文書にまとまっています。

検証 — Xcodeで言語を切り替えて動かす

機械検査が通っても、見た目は崩れます。ボタンの文言が長くなって切れる、文脈の違う訳が当たる、といった問題です。

XcodeのスキームでアプリのLanguageを日本語に変えて起動すれば、実機に近い状態で確かめられます。セッションの実演でも、Edit Schemeのオプションで言語を切り替えて動かしています。

確認の順序は次のとおりです。

  1. カタログエディターで日本語列の状態を見て、要レビューの行を上に並べ替えます。
  2. 行を選ぶとアシスタントにソースが出るので、画面上の文脈を見ながら訳を直します。
  3. 問題なければ「Mark as Reviewed」で訳済みにします。
  4. 日本語で起動し、主要な画面を一巡します。

外部の翻訳会社に渡す場合は、直接編集の代わりにXcodeのエクスポートを使う手もあります。「Product > Export Localizations」で、言語ごとの翻訳用ファイルを含む .xcloc フォルダーができます。訳が戻ったら「Product > Import Localizations」か、コマンドで取り込みます。

xcodebuild -importLocalizations -project MyApp.xcodeproj -localizationPath ./ja.xcloc

AIで下訳を作り、人がカタログ上で直す今回の手順と比べると、エクスポートは外部の担当者に渡す工程が増える代わりに、翻訳者へ渡すファイルがXLIFF形式になります。社内で完結させるなら直接編集、外注が絡むならエクスポートという選び方です。

よくあるつまずき

落とし穴

Xcodeで開いたまま外から編集して衝突する

カタログをXcodeで開いている間にファイルを書き換えると、Xcode側の保存と食い違う恐れがあります。書き戻しの前にXcodeでの編集を保存してコミットしておき、書き換えたあとは差分を確認してから開き直すと、取り違えを防げます。

訳したのにキーが増えている

ビルドのたびにXcodeがコードから文字列を拾い直すため、コードを変えるとカタログも変わります。訳文を書き戻したあとに別の変更を入れたなら、抽出し直してから差分を見ます。

Staleの行が増え続ける

訳済みのキーがコードから消えると、Staleとして残ります。リファクタリングで文言を変えたときに起きやすく、Claude Codeに「Staleのキーが新しいキーの言い換えかどうか」を調べさせて、訳を引き継ぐ形にできます。消すかどうかの判断は人が持ちます。

複数形が崩れる

訳を埋める前に、Xcodeの「Vary by Plural」で複数形の枠を作ります。Xcodeは言語ごとに必要な形を追加するため(英語はOneとOther、ロシア語はOne・Few・Many・Otherなど)、枠の数が言語で違います。その枠を、必要な形ごとに埋めます。スクリプトが複数形を飛ばすのは、この前提があるからです。

ユニットテストやCIにつなぐ

プレースホルダー検査は終了コードで合否が分かれるので、プルリクエストごとの検査に向きます。ビルドの失敗を読み解くコツはxcodebuildのエラーを絞り込む手順が参考になります。

似た作業との関係

文字列をコードから外して管理表に移す点は、ゲームエンジン側のローカライズと同じ発想です。UnityのString Table移行では、CSVで翻訳と未翻訳の検査を回しました。iOSではその表の役割を、Xcodeが管理するカタログが担います。

パッケージ管理の移行と並べるなら、CocoaPodsからSwift Package Managerへの移行も同じ型です。ビルドで検証できる単位に区切り、Xcodeにしかできない操作は人が受け持ちます。XcodeにAI支援を組み込みたい場合はClaude for Xcodeも選択肢です。

まとめ

String Catalogの状態列は、翻訳の作業指示書として使えます。NewとNeeds Reviewの行だけをJSONに抜き出し、訳は「要レビュー」で戻し、プレースホルダーを機械で検査する。この3点を守れば、AIの訳を速く入れつつ、人の確認を飛ばさずに済みます。訳済みの行とキー自体はClaude Codeに触らせない線引きが、カタログを壊さない最大の保険です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. developer.apple.com/documentation/xcode/localizing-and-varying-text-with-a-str…
  2. developer.apple.com/videos/play/wwdc2023/10155
  3. developer.apple.com/documentation/foundation/nslocalizedstring
  4. developer.apple.com/documentation/xcode/exporting-localizations
  5. developer.apple.com/documentation/xcode/importing-localizations
  6. code.claude.com/docs/en/common-workflows
  7. code.claude.com/docs/en/headless
この記事を共有:XはてブLinkedIn