%@、%1$@、%lld:翻訳で壊れる書式指定子

更新 · LocalePilot

変数を含む iOS の文字列には %@ や %lld が入っています。翻訳者はそれを、正しい位置に、正しい型のまま残さなければなりません。繰り返し起きるミス(そのうち 1 つはフランス語特有)と、その見つけ方を紹介します。

お急ぎですか?翻訳のプレースホルダーをブラウザでそのままチェックできます。無料・登録不要で、何もアップロードされません。

% はどこから来るのか

iOS アプリでは、変数を含むローカライズ文字列は書式文字列になります。SwiftUI では暗黙的にそうなります。

Text("\(count) items left")                  // key: "%lld items left"
Text("Welcome back, \(name)!")               // key: "Welcome back, %@!"
String(localized: "Total: \(price) €")       // price is a Double: "Total: %lf €"

Int は %lld、String は %@、Double は %lf になります。古い API では書式を自分で書きます:String(format: NSLocalizedString("items_left", comment: ""), count)。この場合、.strings ファイルには %d や %ld が入ります。

いずれにしても、翻訳者が受け取るテキストには途中に % 記号が入っていて、そこで問題が起きます。

翻訳が壊れる 3 つのパターン

指定子を落とす。"Invitez %@ à modifier cette liste" のはずが "Invitez à modifier cette liste" になっているケースです。クラッシュはしません。引数が無視され、相手の名前が画面から消えるだけです。最もよくあるミスで、最も気づきにくいものです。

型を変える。%lld を %@ に置き換える、またはその逆です。書式は引数を間違った型として読みます。よくてもおかしな数値、悪ければクラッシュです。%@ はオブジェクトを想定しているのに、整数が渡されるからです。

黙って順序を変える。語順の異なる言語では、"%@ shared %@" が 2 人の名前が入れ替わった文になります。構文は正しくても、意味が違います。日本語は語順が英語と大きく異なるので、特に注意が必要です。

位置指定で順序を入れ替える

順序を変えるには、引数に番号を付けます。%1$@ は 1 番目の引数、%2$@ は 2 番目の引数で、文中のどこにあってもかまいません。

/* source */      "%@ shared %@ with you" = "%1$@ shared %2$@ with you";
/* French */      "%@ shared %@ with you" = "%1$@ vous a partagé %2$@";
/* other order */ "%@ shared %@ with you" = "%2$@ : partagé par %1$@";

1 つの文字列の中では、すべてに番号を付けるか、どれにも付けないかのどちらかにします。%1$@ と %@ の混在は未定義動作です。

フランス語の罠:「20 % de remise」

フランス語版を出すなら、次の点に注意してください。フランス語の組版ではパーセント記号の前にスペースを入れます:「20 % de remise」(20% オフ)。書式文字列ではこれが問題になります。% d(パーセント、スペース、d)が有効な指定子だからです。スペースは printf のフラグ(「正の数の前にスペースを入れる」)で、d は整数の変換です。次の文は

"Profitez de %lld % de remise"

引数 1 つに対して %lld と % d の 2 つの指定子を持つことになります。2 つ目は存在しない値を読みます。正しくは記号を二重にします。

"Profitez de %lld %% de remise"

影響するのは書式を通る文字列、つまり変数を 1 つ以上含む文字列だけです。引数のない固定の「20 % de remise」は書いたとおりに表示されます。ただ、翻訳は後から変わります。誰かがその文に変数を追加した時点で、単独の % はバグになります。

複数形

「1 item / 3 items」のために、キーを 2 つ用意してコードで if を書くのはやめましょう。String Catalog では、%lld items left キーに複数形バリエーション(one、other、言語によっては few や many)を持たせ、それぞれに %lld を入れます。日本語は other だけです。1 つの文に独立して変わる数値が 2 つある場合、Xcode は置換(%#@items@)を使います。こちらはまれなケースで、Xcode で編集します。

リリース前のチェック

基本のチェックは機械的です。各翻訳の指定子のリスト(型と位置)が、ソースと一致していなければなりません。LocalePilot の QA が .strings と .xcstrings ファイルで行うのがこれです。%@、%1$@、%d、%ld、%lld、%.2f、%u、%i、%s を認識し、%% はテキストとして扱い、指定子の欠落や余分を書き出し前に検出します。%e や %x のような珍しい変換は、「20%en」をエラーと誤認しないよう、あえて無視しています。上の「% d」のケースは人がレビューで確認してください。フランス語訳で、スペースが後に続く % を検索してください。

よくある質問

%d、%ld、%lld の違いは?

想定する整数のサイズです。SwiftUI は補間された Int に %lld を使います。手書きの .strings ファイルでは、ソースの指定子をそのまま使ってください。

翻訳した文字列に % 記号を入れるには?

変数を含む文字列では %% と二重にします。そうしないと「% d」や「% s」が指定子として読まれることがあります。

翻訳者は変数の順序を変えられますか?

はい、位置指定を使えば変えられます(%2$@ の後に %1$@)。ただしその場合は、文字列全体に番号を付ける必要があります。