ファイル形式・標準規格Read this article in English

Android文字列リソース入門 — strings.xml、複数形、リソース修飾子の仕組み

Androidの画面テキストはコードの中ではなく、リソースファイルの中に置かれます。ユーザーに見えるすべての文字列は、XMLファイル内で一度だけ宣言され、レイアウトやKotlin/Javaのコードからは固定名で参照されます。この分離があるからこそ、アプリのロジックに一切触れずに翻訳ができます。翻訳者はXMLファイルを開き、タグの中身だけを書き換え、アプリは端末の言語設定に応じて適切なファイルを実行時に選びます。

この「実行時の選択」を支えているのがリソース修飾子ディレクトリの仕組みで、正確に理解しておく価値があります。Androidのローカライズ不具合の多くは、翻訳の質そのものより、この修飾子がわずかに間違っていることが原因だからです。

基本のkey-value形式

文字列リソースファイルはXMLで、ルート要素1つとフラットなエントリの並びです。各エントリはname属性(アプリの他の箇所から参照されるキー)を持ち、要素の中身が翻訳対象のテキストになります。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="welcome_title">Welcome back</string>
    <string name="logout_confirm">Are you sure you want to log out?</string>
</resources>

リソース修飾子ディレクトリの仕組み

翻訳は「言語カラムのある1つのファイル」には入りません。同じ相対パスにある並行したファイル群に入り、それぞれがres/valuesディレクトリの中にあり、そのディレクトリ名に言語コード(2文字)、任意で地域コードを付けた修飾子サフィックスが付きます。

res/values/strings.xml          (デフォルト — 他に一致するものがない場合に使われる)
res/values-de/strings.xml        (ドイツ語)
res/values-fr/strings.xml        (フランス語)
res/values-pt-rBR/strings.xml    (ポルトガル語、ブラジル地域)
res/values-zh-rTW/strings.xml    (中国語 繁体字/台湾)

実行時、システムは端末のロケールをこれらのディレクトリに照合し、より具体的なファイルにキーがなければvalues/strings.xml(修飾子なしのデフォルトリソース)にフォールバックします。このフォールバックは静かに起きます — 翻訳漏れがあってもエラーにはならず、黙ってデフォルト言語のテキストが表示されるだけです。だからこそ、すべてのvalues-*ディレクトリにわたるキー漏れのチェックは、クラッシュで気づくものではなく、意図的に行う工程にする必要があります。

デフォルトファイルに存在するキーは、すべての言語ファイルに同じ名前で存在しているべきです。フランス語にあってドイツ語にないキーは、手元では気づけないドイツ語だけのバグとなり、端末をドイツ語に設定した時にだけ表面化します。

複数形とquantityキーワード

英語の複数形処理は単純に見えます — 単数形が1つ、それ以外は全部同じ形 — ですが、このルールは他の言語には当てはまりません。3つ、4つ、あるいは6つの複数形カテゴリを持ち、どの数がどのカテゴリに入るかのルールも言語ごとに違う言語があります。Androidのplurals(複数形)リソースは、カウントに応じたif/elseをハードコードする代わりに、文法カテゴリごとに1つの文字列を用意できるようにします。

<plurals name="items_selected">
    <item quantity="one">%d item selected</item>
    <item quantity="other">%d items selected</item>
</plurals>

quantity属性は自由なラベルではなく、Unicode CLDRが言語ごとに定める複数形ルールに基づく固定のキーワード(zero, one, two, few, many, other)のいずれかでなければなりません。どのカテゴリが実際に適用され、どの数がどのカテゴリに対応するかは言語によって異なります。双数形を持たない言語ではtwoは一切使われませんし、otherだけはすべての言語で必須です(該当しない数値の受け皿だからです)。複数の複数形カテゴリを持つスラブ系言語を担当する翻訳者は、英語ファイルからone=/other=だけをコピーするのではなく、該当するすべてのitemを埋める必要があります。

エスケープとフォーマット引数

ファイルがXMLである以上、XMLパーサにとって特別な意味を持つ文字(アンパサンドと山括弧)は、リテラルではなくエンティティとして書く必要があります。もう2つ、別の理由で注意が必要な文字があります。アポストロフィと直立ダブルクォートは、エスケープしない限りAndroid自身の文字列リソースパーサに意味のある文字として扱われます。これらの文字はリソースシステムの他の箇所で使われる引用符と混同されうるためです。

  • テキスト内のアポストロフィの前にはバックスラッシュを置くか、文字列全体をダブルクォートで囲む
  • テキスト内の直立ダブルクォートの前にはバックスラッシュを置く
  • XML予約文字(&, <, >)はアポストロフィ/クォートのルールとは別に、常にXMLエンティティにする

位置指定フォーマット引数は標準的なprintf形式の構文(文字列には%s、整数には%d)に従い、Androidはさらに位置インデックスをサポートするため、アプリのコードを変えずに翻訳者が言語の文法に合わせて引数の順序を入れ替えることができます。

<string name="greeting">Hello, %1$s! You have %2$d new messages.</string>

%1$sと%2$dは翻訳文の中でどちらの順序で現れても構いません。数字がどの引数がそのスロットを埋めるかを決めるのであって、文中の位置とは無関係です。これは翻訳の現場では常に重要です。多くの言語では主語やカウントが英語とは違う位置に来るため、位置インデックスがなければ翻訳者はそもそも文を並べ替えられません。

翻訳しない文字列にマークを付ける

strings.xmlファイルの中身がすべてユーザー向けの文章とは限りません。アナリティクスのイベント名、内部のログタグ、他のコードだけが消費するフォーマット文字列が、便宜上同じファイルに置かれていることがよくあり、これらを翻訳するとユーザーを助けるどころかアプリを壊してしまいます。Androidのtranslatable属性は、ツールにも、ファイルをレビューする人間にも「このエントリには触れないでください」と伝えるためのものです。

<string name="analytics_event_login" translatable="false">login_event</string>

これを正しくマークすることは、見た目の整理以上の意味があります。ファイルをスキャンして翻訳対象を探すツールは、マークがなければユーザーが読むはずのない文字列を翻訳者に渡してしまい、時間を無駄にするだけでなく、正確なリテラル値を期待するコードに翻訳済みの値が渡ってしまうリスクもあります。

関連記事