Appleの.stringsと.stringsdict — iOS/macOSはどう翻訳テキストを保存するか
Appleのプラットフォームは、Swiftが存在するよりずっと前から翻訳可能テキストのためのプレーンテキスト/XMLリソース形式群を使っており、その基本概念は新しいツール群の内部にも今なお残っています。日々の編集が別のインターフェース経由であっても、.stringsと.stringsdictを直接理解しておく価値があります。これらは今もアプリバンドルの中に入って出荷されるファイルであり、最終的に翻訳者が正しく仕上げる必要があるファイルだからです。
key-value形式の.strings
.stringsファイルは、翻訳可能な文字列1つにつき1エントリのフラットなkey-valueリストで、C言語の文字列リテラルに近いテキスト表記のproperty listとして保存されます。各エントリはクォートされたキー、イコール記号、クォートされた値、末尾のセミコロンからなります。
/* Shown on the welcome screen after login */ "welcome_title" = "Welcome back"; /* Confirmation before logging the user out */ "logout_confirm" = "Are you sure you want to log out?";
各エントリの上のコメントは飾りではありません。キー名だけでは伝わらない文脈 — その文字列がどの画面に出るか、文字数制限のあるボタンラベルかどうか、文中のプレースホルダーが何に置き換わるか — を翻訳者に渡すための、慣習的な置き場所です。welcome_titleというキー名はそれ単体ではほとんど何も語らず、実際のブリーフィングはコメントに書かれています。
Androidのリソースファイルと同様、プロジェクトは言語ごとに1つの.stringsファイルを持ち、それぞれ言語別の.lprojディレクトリの中の対応するパスに置かれ、アプリは起動時にユーザーの優先言語に基づいて適切なファイルを選びます。ベースファイルには存在するが翻訳版に存在しないキーは、静かにベース言語にフォールバックします。だからこそ、すべての.lprojディレクトリにわたるキーの網羅性チェックは、記憶に頼らず定期的に自動化する価値があります。
フォーマット引数と位置の順序
.strings値の中の文字列フォーマットは、プラットフォームの他の場所で使われるのと同じprintf形式の慣例に従い、位置インデックスにも対応しているため、翻訳者はコードを変更することなく、対象言語の語順に合わせて引数を並べ替えられます。
"new_messages" = "Hello, %1$@! You have %2$ld new messages.";
複数形と幅バリアントのための.stringsdict
フラットなkey-valueファイルには、数によって文の言い回しが変わることを表現する方法がありません。しかも英語の「単数/複数」という2形態のルールは他の言語には一般化できず、いくつかの言語は複数の文法上の複数形カテゴリを区別します。.stringsdictはこれをXML property listで解決します。.stringsファイルと同じ文字列キーを使い、あるフォーマット指定子に対してUnicode CLDRの複数形カテゴリ(zero, one, two, few, many, other)ごとに1つのバリアントを与えます。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items_selected</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@items@</string>
<key>items</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d item selected</string>
<key>other</key>
<string>%d items selected</string>
</dict>
</dict>
</dict>
</plist>同じ仕組み — バリアントの辞書を通して解決されるフォーマット指定子 — は、もう1つの問題もカバーします。それが幅バリアントです。UI要素がスペースの制約がある場合(コンパクトなウォッチ画面 対 フルサイズの画面)に短い言い回しを必要とし、それ以外ではより自然で長い言い回しを使う、というケースです。複数形バリアントも幅バリアントも、アプリが実行時にしか分からない文脈に本質的に依存する何かを、1つのハードコードされた文字列では表現できないために存在します。
この入れ子構造は実質的な複雑さであり、.stringsdictファイルを手編集するのが.stringsファイルより間違いやすい理由でもあります。不正な複数形ルールタイプや、実際には必要な言語でのカテゴリ漏れは、実行時にクラッシュせず静かにフォールバック文字列を表示するだけなので、ミスに気づかないまま出荷されやすいのです。
String catalogという考え方
より新しいAppleのツール群には、string catalogという概念があります。プロジェクトのキー、ベース言語のテキスト、翻訳、複数形/幅バリアントを、.lprojディレクトリ内に散らばった.stringsや.stringsdictの集まりではなく、プロジェクトごとの1ファイルに集約するというものです。概念的には同じ情報(キー、コメント、複数形カテゴリ)であり、単に一箇所にまとめて保存することで、言語ごとにファイルを別々に開かずとも、あるキーについて全言語を並べて編集ツールに表示できるようになっています。
どちらの保存形式を使うプロジェクトであっても、根底の概念は変わりません — 安定したキー、文脈を与えるコメント、1つのベース値、そしてすべてのキーについて存在すべき言語ごとの値(存在しなければ静かにフォールバックする)です。
XLIFFと翻訳の往復
テキストを翻訳者に渡して戻す作業は、生の.stringsや.stringsdictファイルをそのまま手渡すことでは、まずありません。Appleのツールはプロジェクトの翻訳可能なコンテンツを、まさにこの目的のために標準化されたXMLベースのフォーマットであるXLIFF(Localization Interchange File Format)として書き出せます。原文テキスト、対象言語の枠、翻訳者向けの注記、状態情報(未翻訳/翻訳済み/レビュー済み)を、言語ごとに1つの交換ファイルにまとめ、元のリソース形式に依存しない形で扱えます。
翻訳者や翻訳ツールは.stringsファイルに直接触れることなく、完全にXLIFFの中だけで作業し、書き出し/取り込みのステップが完成した翻訳をプロジェクト本来のリソースファイルへ再統合します。この分離が有用なのは、まさに翻訳者が複数形ルールのXMLやproperty listの構文を理解する必要がないからです。翻訳者は原文と訳文のセグメントだけを扱い、往復の変換がプラットフォーム固有の形式への対応付けを引き受けます。