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

Java .properties と .NET .resx — 長寿命な2つのリソース形式を比較する

ファイル形式には、毎年変わる新しいものもあれば、何十年もエンタープライズソフトウェアの下で静かに同じ役割を同じやり方で果たし続けているものもあります。Javaの.properties形式と.NETの.resx形式は、どちらも後者に属します。どちらも流行りではありませんが、今も大量のデスクトップ/サーバー/エンタープライズのコードベースでそのまま使われており、それぞれ翻訳可能テキストの持ち方に異なるトレードオフがあり、触る前に知っておく価値があります。

Java .properties — key=valueのプレーンテキスト

.propertiesファイルは、テキストベースのリソース形式としてはこれ以上ないほどシンプルです。1行に1つのkey-valueペアをイコール記号かコロンで区切り、バックスラッシュで特殊文字のエスケープと値の複数行への継続を行います。

# Login screen
welcome.title=Welcome back
logout.confirm=Are you sure you want to log out?
help.text=This is a long line that continues \
    onto the next physical line in the file.

翻訳の仕組みは他の多くのリソース形式と同じです。言語ごとに1ファイルで、ファイル名に言語を埋め込みます(デフォルトはmessages.properties、ドイツ語はmessages_de.properties、カナダのフランス語はmessages_fr_CA.properties)。アプリは一致するファイルを選び、より具体的なファイルや特定のキーが見つからない場合はベースファイルにフォールバックします。

エスケープと今も効いてくるエンコーディングの歴史

この形式にはクォート機構がないため、値の中で使うにはいくつかの文字をバックスラッシュでエスケープする必要があります。イコール記号とコロン(そうしないとkey-valueの区切りに見えてしまう)、バックスラッシュ自体、そして上の例のような複数行値のための改行です。この形式が本来想定していなかった範囲のUnicode文字も、リテラルバイトではなく\uXXXX形式のエスケープとして書かれるのが一般的です。

この最後の点は、実際の歴史的な制約に由来します。.propertiesファイルは長らくデフォルトでLatin-1(ISO-8859-1)として読み込まれると規定されていました。これは世界の多くの文字体系を直接表現できないシングルバイトのエンコーディングです。その範囲外のテキストはリテラル文字ではなく\uXXXX形式のエスケープシーケンスとして書く必要があり、そのため古いツールや古いコードベースには、プレーンテキストエディタで見ると読めない文字の羅列に見えるファイルが今も残っています。現代のツール群は概してUTF-8として.propertiesファイルを読み書きするため、翻訳テキストはエスケープシーケンスではなくリテラル文字として現れますが、あるビルド手順が前提とするエンコーディングと実際にファイルが持っているエンコーディングの不一致は、古いプロジェクトでは文字化けの根強い原因であり続けます。大丈夫だろうと決めつけず、明示的に確認する価値があります。

.NET .resx — 構造化されたXML、テキストだけではない

.resxファイルはXMLで、フラットな文字列の集まり以上のものを持ちます。各エントリはname属性で識別されるdata要素で、実際の内容を持つvalue要素と、翻訳者向けの文脈を伝える任意のcomment要素を含みます。飾り気のない.propertiesの1行より、コメント付きのApple .stringsエントリに近い発想です。

<data name="WelcomeTitle" xml:space="preserve">
  <value>Welcome back</value>
  <comment>Shown on the home screen after login</comment>
</data>
<data name="LogoutConfirm" xml:space="preserve">
  <value>Are you sure you want to log out?</value>
</data>

プレーンテキストの一覧ではなく完全なXMLベースのリソースコンテナであるため、.resxファイルは文字列以上のものを保持できます。埋め込み画像などのバイナリリソースも、XML内にbase64エンコードされた有効なエントリになります。実務ではローカライズ作業が触るのは文字列エントリだけですが、バイナリリソースの存在は、.resxが翻訳を第一に設計されたフォーマットではなく、Microsoftがテキスト用に流用した汎用リソースフォーマットであることを思い出させてくれます。

命名規則はこの比較の他の2形式と同じパターンに従います。ベースとなるResources.resxがあり、その隣にResources.de.resxやResources.fr-CA.resxのようなカルチャ別のファイルが並びます。ビルド時、.NETは各カルチャファイルをサテライトアセンブリにコンパイルします。これは、そのカルチャのリソースだけを含む別個のDLLで、メインアプリケーションの隣にカルチャコードの名前を付けたサブディレクトリに配置されます。これはここで扱った他の形式とは意味のある違いがある配布モデルです — 翻訳は実行時に読み込まれる単なる代替ファイルではなく、アプリケーションが必要なカルチャを知った時点で必要に応じて読み込むコンパイル済み成果物です。

それぞれの落とし穴

どちらの形式も、起きやすく気づきにくい失敗モードを持っています。

  • .properties — 値の中の未エスケープのイコール記号やコロンは、エラーにならずキーを静かに途中で切ってしまい、誰も意図していないキーを生み出す
  • .properties — ツールが読み込むエンコーディングと実際にファイルが持つエンコーディングが食い違うと、アクセント付き文字や非ラテン文字が警告なしに文字化けする
  • .resx — xml:space preserve属性を忘れると、XMLパーサが値の先頭/末尾の意味のある空白を詰めてしまう
  • .resx — 不正なdata要素(閉じタグ漏れ、無効なname)はファイル全体のパース失敗につながる。.propertiesの1行の誤りが通常そのエントリだけを壊すのとは対照的
  • .resx — サテライトアセンブリという仕組みのため、あるカルチャのファイルが欠けているのは単なるテキストファイル欠落ではなく、デプロイ時のDLL欠落を意味する。ビルドだけでなくデプロイスクリプトもこれを把握している必要がある

共通していること

構文の違いの下では、どちらの形式も同一の問題を同じやり方で解決しています。コードから参照される安定したキー、同じキー群を共有する言語ごとの1ファイル、特定の翻訳が欠けている場合のベース言語へのフォールバックです。両者は運用上のリスクも共有しています。フォールバックがどちらの形式でも静かに起きるため、キーの欠落や言語ファイルの欠落はエラーとして表面化せず、ユーザーがいずれ気づく未翻訳テキストとしてしか現れません。どちらの形式を使うコードベースであっても、全言語ファイルにわたるキーの網羅性チェックという規律は変わりません。

関連記事