新しい言語を一つ足すだけで、なぜこんなに気を揉むのか——個人開発で複数の壁紙アプリを十数言語で出してきて、毎回そう感じていました。文言の取りこぼし、複数形のずれ、長い訳文によるレイアウト崩れ。iPhone 向けではこの作業を何度も繰り返してきましたが、Rork Max がネイティブ Swift を生成するようになって、土台の作り方しだいで負担が大きく変わると分かってきました。
Rork Max は英語の説明から Swift アプリを生成しますが、初期状態の文字列は本文に直接書かれていることが多いです。これを早い段階で String Catalog(.xcstrings)へ寄せておくと、後からの言語追加が驚くほど軽くなります。ここでは、その移行と運用の流れを順に追います。
なぜ String Catalog なのか
かつての Localizable.strings と .stringsdict は、キーの管理を人手に頼る部分が多く、コードと訳文がずれても気づきにくいものでした。String Catalog は Xcode が文字列を自動で拾い、未訳・重複・状態(要確認・翻訳済み)を一覧で見せてくれます。複数形も同じファイル内のバリエーションとして扱えるため、別ファイルを行き来する手間が消えます。
私が一番ありがたいと感じるのは「抽出が自動である」点です。コード側で String(localized:) を使っておけば、ビルドのたびに新しい文字列がカタログへ吸い上げられます。文言の取りこぼしという、多言語運用で最も多いミスがほぼ起きなくなります。
観点 Localizable.strings 時代 String Catalog(.xcstrings)
文字列の抽出 手動またはスクリプト ビルド時に自動
未訳の把握 差分を目視 状態列で一覧表示
複数形 別ファイル .stringsdict 同一ファイル内のバリエーション
使われない文字列 気づきにくい STALE として明示
生成コードを String(localized:) に寄せる
Rork Max が返す初期コードは、たとえばこう書かれています。
Text ( "お気に入りに追加しました" )
Button ( "もう一度試す" ) { retry () }
これを String Catalog に乗せるため、文字列を String(localized:) 経由に整えます。SwiftUI の Text は文字列リテラルを自動でローカライズ対象にしますが、キーと既定値を明示しておくと、後の管理がはっきりします。
Text ( "favorite.added" , defaultValue : "お気に入りに追加しました" )
Button ( String ( localized : "action.retry" , defaultValue : "もう一度試す" )) {
retry ()
}
キーは画面や役割で名前空間を切ると、訳者にも文脈が伝わりやすくなります。favorite.added のように「どこの・何の文言か」が読み取れる形にしておくと、十数言語に渡したとき、訳の取り違えが減ります。私はここを曖昧にして痛い目を見たので、最初の整地で必ず揃えるようにしています。
プロジェクトに String Catalog を追加するのは Xcode の File > New File から「String Catalog」を選ぶだけです。あとはビルドすると、String(localized:) で参照したキーが .xcstrings に並びます。
複数形と地域差をバリエーションで扱う
多言語で必ずつまずくのが複数形です。日本語は単複の区別が薄いですが、英語は 1 と複数、言語によっては 0・少数・多数で形が変わります。String Catalog では、対象のキーに「Vary by Plural」を設定し、zero・one・other などの形をその場で書き分けられます。
コード側は数値を渡すだけで、表示の出し分けはカタログが担います。
let count = favorites. count
let label = String ( localized : "favorite.count" ,
defaultValue : " \( count ) 件のお気に入り" )
英語の favorite.count では one に "1 favorite"、other に "(count) favorites" と入れておきます。アラビア語のように複数形が多段の言語でも、その言語の列にだけ追加の形を足せば成立します。別ファイルの .stringsdict を編集していた頃に比べ、見通しが段違いに良くなりました。
地域差も同様に「Vary by Device」や言語ごとの行で吸収できます。たとえば iPad と iPhone で語尾を変えたいような細かな調整も、コードを分岐させずに済みます。
翻訳の流し込みと検査を運用に組み込む
言語を足す作業は、.xcstrings の書き出し・翻訳・取り込みという往復に集約されます。Xcode の Product > Export Localizations で各言語の .xcloc を書き出し、訳文を入れて Import で戻す流れです。具体的には次の往復になります。
Product > Export Localizations で対象言語の .xcloc を書き出します。
言語ごとに訳文を入れ、プレースホルダを保ったまま戻します。
Import の前に、未訳と要確認の項目を機械的に洗い出します。私は言語ごとに担当を分け、戻ってきたファイルを取り込む前に必ず軽い検査を一度挟みます。
検査で見るのは主に三つです。プレースホルダ(%@ や \(...) 由来の指定)が訳文でも保たれているか、訳が空のまま残っていないか、そして極端に長い訳が画面を割らないか。簡単なスクリプトで .xcstrings の JSON を走査し、状態が translated でない項目を洗い出します。この一手間で、取りこぼしを早い段階で回避できます。
// .xcstrings は JSON。未訳と要確認を機械的に拾う
struct Catalog : Decodable {
let strings: [ String : Entry]
}
struct Entry : Decodable {
let localizations: [ String : Localization] ?
}
struct Localization : Decodable {
let stringUnit: StringUnit ?
}
struct StringUnit : Decodable {
let state: String // "translated" / "needs_review" など
let value: String
}
この JSON を読み、対象言語で state が translated でないキーを一覧化すれば、「この言語はまだ 12 件未訳」と一目で分かります。言語を増やすほど、この機械的な確認が効いてきます。手で全項目を見ていた頃は、たった一つの空欄が App Store のリリース後に露見して肝を冷やしたものです。
段階 やること 失敗しがちな点
抽出 ビルドでカタログ更新 動的に組む文字列が拾われない
書き出し Export Localizations 新キー追加前に書き出してしまう
翻訳 言語ごとに訳出 プレースホルダの欠落
検査 state を機械確認 未訳の見落とし
取り込み Import で反映 レイアウト崩れの未確認
崩れを早く見つける
長い訳文によるレイアウト崩れは、実機で各言語を切り替えて確かめるのが確実ですが、言語が増えると現実的でなくなります。ドイツ語やロシア語では、原文より30%ほど長くなることも珍しくありません。私は擬似ローカライズ(pseudolocalization)を併用しています。Xcode のスキームで「Double-Length Pseudolanguage」などを選ぶと、文字数を意図的に伸ばした疑似言語で起動でき、訳を待たずにレイアウトの弱い箇所が浮かびます。
ボタンやタブのように幅の限られた要素は、ここで早めに minimumScaleFactor や折り返しの方針を決めておくと、後から各言語で個別対応する手間が減ります。先に器を広げておく、という順番が結局いちばん楽でした。各言語で個別に直すより、先に広げておくことをお勧めします。
次の一歩
まずは生成直後のアプリで、画面に出る文字列を一つずつ String(localized:) へ寄せ、String Catalog を一枚追加してみてください。英語をもう一言語足すだけでも、自動抽出と状態表示の手応えが伝わるはずです。そこから複数形、機械検査と積み上げれば、言語を足すたびに身構える必要はなくなります。
私自身、多言語運用は今も試行錯誤の連続ですが、土台を整えておくと「あと一言語」が怖くなくなります。同じように複数の地域へアプリを届けようとしている方の役に立てば嬉しいです。