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

PO ファイル形式を正確に読む — msgid・msgstr と gettext が積み上げた仕組み

GNU gettext は、現在も使われているローカライズツールの中でもかなり古株で、その PO(Portable Object)形式は今でもオープンソースソフトウェアのデフォルトです。原文の文字列(msgid)とその訳(msgstr)を対にする、というただ一点を核にしたプレーンテキスト形式で、それ以外のすべての仕組みは、この対応関係が実際の翻訳作業の中でも壊れないようにするために存在しています。

この記事では、仕様のレベルで PO 形式を見ていきます。各エントリの構造、複数形と曖昧さ回避の仕組み、そして選択肢がいくらでもある中で今もこの形式が選ばれ続ける理由です。

基本のエントリ

PO ファイルはエントリの並びで、各エントリは任意のコメント行と、それに続く msgid/msgstr のペアからなります。文字列内の空白や改行はそのまま保持され、原文側もそうなので、抽出されたテキストは実行時の文字列と完全に一致していなければなりません。

#: src/dialogue/tutorial.c:42
#, c-format
msgid "Press %s to open your inventory."
msgstr "%s を押してインベントリを開く。"

複数形(Plural forms)

英語の複数形は2種類ですが、それより多い区分を持つ言語もあれば、英語が示唆するほどの区分を持たない言語もあります。gettext はこれを、言語ごとの複数形ルールを C 言語風の短い式として定義するヘッダーと、複数形が必要なエントリごとの msgid_plural / msgstr[n] ブロックで扱います。

Plural-Forms ヘッダーは、その言語に何種類の形があり、どの整数式がどの形を実行時に選ぶかを宣言します。翻訳者は msgstr[0]、msgstr[1]... を埋めるだけで、形の数や選択ロジック自体は文字列ごとに推測するものではなく、ヘッダーですでに決まっています。

"Plural-Forms: nplurals=2; plural=(n != 1);\n"

msgid "%d item found"
msgid_plural "%d items found"
msgstr[0] "%d 件のアイテムが見つかりました"
msgstr[1] "%d 件のアイテムが見つかりました"

msgctxt — 同じ原文の曖昧さを解消する

同じ英単語でも、出てくる場所によって意味が変わることはよくあります。ボタンの動詞としての「Close(閉じる)」と、近さを表す形容詞としての「Close(近い)」がその例です。gettext は原文の文字列そのものを翻訳のキーにするため、まったく同じ msgid が2つあると衝突し、区別できなくなります。

msgctxt(メッセージコンテキスト)はこれを解決します。msgid の前に置く追加フィールドで、そのエントリの範囲を限定します。これにより「Close」|button と「Close」|distance は、英語のテキストが同一であっても、独立した別々のエントリとして翻訳できます。

msgctxt "button"
msgid "Close"
msgstr "閉じる"

msgctxt "distance"
msgid "Close"
msgstr "近い"

コメント、fuzzy エントリ、フラグ

msgid の上にある各行は、特定のコメント記号で始まり、その記号によって種類の異なる情報を運びます。ツールはこの記号を見て正しく処理します。

  • # — 翻訳者コメント。手書きで、再抽出をまたいでも保持される
  • #. — 抽出コメント。開発者がソースコード中に、翻訳者向けに書いたもの
  • #: — 参照コメント。抽出元のファイルと行番号を示す
  • #, — フラグ。c-format(printf 形式の文字列であることを示し、プレースホルダーの検証に使われる)や fuzzy など

POT テンプレートと言語別 PO ファイル

プロジェクトは POT ファイル(Portable Object Template、msgstr が空の状態で抽出された原文の集合)を1つと、そこから派生した言語ごとの PO ファイルを保持します。原文が変わるとテンプレートを再生成し、それを各言語の PO ファイルにマージします。変更のないエントリはそのまま残り、変更されたエントリは fuzzy とマークされて人間のレビュー対象になり、削除されたエントリはいきなり消すのではなく obsolete(コメントアウト)として残されます。復活する可能性があるからです。

このマージの仕組みこそが、gettext がオープンソースプロジェクトで標準であり続けている理由です。完全にファイルベースでバージョン管理と相性がよく、fuzzy/obsolete の仕組みによって、原文が変わるたびに翻訳者には「全部やり直し」ではなく、はっきりと範囲の決まった作業リストが渡されます。

関連記事