"gettext" --- 多言語国際化サービス
**********************************

**ソースコード:** Lib/gettext.py

======================================================================

"gettext" モジュールは、Python のモジュールやアプリケーションに国際化
(I18N) と地域化 (L10N) の機能を提供します。 GNU **gettext** のメッセー
ジカタログ API と、Python ファイルにはより適している場合がある、クラス
形式の高水準 API の両方をサポートしています。 以下のインターフェースを
使うと、モジュールやアプリケーションのメッセージを 1 つの自然言語で書
き、ほかの自然言語で実行するための翻訳済みメッセージのカタログを用意で
きます。

ここでは Python のモジュールやアプリケーションを地域化するためのいくつ
かのヒントも提供しています。


GNU **gettext** API
===================

"gettext" モジュールは、GNU **gettext** API によく似た次の API を定義
しています。 この API を使うと、アプリケーション全体の翻訳に影響します
。 ユーザーのロケールに応じて 1 つの言語を選択するアプリケーションでは
、多くの場合これが望ましい動作です。 Python モジュールを地域化する場合
や、実行中に言語を切り替える必要がある場合は、クラス形式の API を使う
方が適しています。

gettext.bindtextdomain(domain, localedir=None)

   *domain* をロケールディレクトリ *localedir* に結び付けます。 具体的
   には、"gettext" は指定したドメインのバイナリ ".mo" ファイルを、Unix
   では "*localedir*/*language*/LC_MESSAGES/*domain*.mo" というパスで
   探します。 ここで *language* は、環境変数 "LANGUAGE"、"LC_ALL"、
   "LC_MESSAGES"、"LANG" から順に探します。

   *localedir* が省略されるか "None" の場合、現在 *domain* に対応付け
   られているロケールディレクトリが返されます。 [1]

gettext.textdomain(domain=None)

   現在のグローバルドメインを変更したり調べたりします。 *domain* が
   "None" の場合、現在のグローバルドメインが返されます。それ以外の場合
   には、グローバルドメインに *domain* を設定し、その設定されたグロー
   バルドメインを返します。

gettext.gettext(message, /)

   現在のグローバルドメイン、言語、ロケールディレクトリに基づいて、
   *message* の翻訳を返します。 通常、この関数はローカル名前空間で
   "_()" という別名で使います (以下の例を参照)。

gettext.dgettext(domain, message, /)

   "gettext()" と同様ですが、指定された *domain* からメッセージを探し
   ます。

gettext.ngettext(singular, plural, n, /)

   "gettext()" と同様ですが、複数形を考慮します。 翻訳が見つかった場合
   は、複数形の選択公式を *n* に適用し、その結果に対応するメッセージを
   返します (言語によっては複数形が 3 種類以上あります)。 翻訳が見つか
   らなかった場合は、*n* が 1 なら *singular*、それ以外なら *plural*
   を返します。

   複数形の選択公式はカタログのヘッダから取得されます。 選択公式は自由
   変数 *n* を持つ C または Python の式です; その式の評価結果はカタロ
   グにある複数形のインデックスになります。 ".po" ファイルで用いられる
   詳細な文法と、様々な言語における選択公式については GNU gettext ドキ
   ュメント を参照してください。

gettext.dngettext(domain, singular, plural, n, /)

   "ngettext()" と同様ですが、指定された *domain* からメッセージを探し
   ます。

gettext.pgettext(context, message, /)

gettext.dpgettext(domain, context, message, /)

gettext.npgettext(context, singular, plural, n, /)

gettext.dnpgettext(domain, context, singular, plural, n, /)

   接頭辞 "p" のない対応する関数 ("gettext()"、"dgettext()"、
   "ngettext()"、"dngettext()") と同様ですが、翻訳対象が指定したメッセ
   ージの *context* に限定されます。

   Added in version 3.8.

GNU **gettext** は "dcgettext()" メソッドも定義していますが、有用では
ないと判断されたため、現在は実装されていません。

以下にこの API の典型的な使用法を示します:

   import gettext
   gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
   gettext.textdomain('myapplication')
   _ = gettext.gettext
   # ...
   print(_('This is a translatable string.'))


クラス形式の API
================

"gettext" モジュールのクラス形式の API は、GNU **gettext** API より柔
軟で便利です。 Python のアプリケーションやモジュールの地域化には、この
API の使用を推奨します。 "gettext" は "GNUTranslations" クラスを定義し
ています。 このクラスは GNU ".mo" 形式のファイルの解析を実装し、文字列
を返すメソッドを持ちます。 このクラスのインスタンスは、組み込み名前空
間に "_()" 関数として自身をインストールすることもできます。

gettext.find(domain, localedir=None, languages=None, all=False)

   この関数は標準的な ".mo" ファイル検索アルゴリズムを実装しています。
   "textdomain()" と同じく、 *domain* を引数にとります。オプションの
   *localedir* は "bindtextdomain()" と同じです。またオプションの
   *languages* は文字列を列挙したリストで、各文字列は言語コードを表し
   ます。

   *localedir* が与えられていない場合、標準のシステムロケールディレク
   トリが使われます。 [2] *languages* が与えられなかった場合、以下の環
   境変数: "LANGUAGE" 、 "LC_ALL" 、 "LC_MESSAGES" 、および "LANG" が
   検索されます。空でない値を返した最初の候補が *languages* 変数として
   使われます。この環境変数は言語名をコロンで分かち書きしたリストを含
   んでいなければなりません。 "find()" はこの文字列をコロンで分割し、
   言語コードの候補リストを生成します。

   "find()" は言語コードを展開して正規化し、それらを順にたどりながら、
   次の要素からなるパスにファイルが存在するか調べます。

   "*localedir*/*language*/LC_MESSAGES/*domain*.mo"

   "find()" は最初に見つかったファイル名を返します。 ファイルが見つか
   らなければ "None" を返します。 *all* を指定した場合は、見つかったす
   べてのファイル名のリストを、言語リストまたは環境変数での順序で返し
   ます。

gettext.translation(domain, localedir=None, languages=None, class_=None, fallback=False)

   *domain*、*localedir*、*languages* に基づいて "*Translations" イン
   スタンスを返します。 これらの引数を最初に "find()" に渡し、関連する
   ".mo" ファイルのパスのリストを取得します。 同じ ".mo" ファイル名を
   持つインスタンスはキャッシュされます。 実際にインスタンス化するクラ
   スは、*class_* が指定されていればそのクラス、そうでなければ
   "GNUTranslations" です。 クラスのコンストラクタは *file object* を
   引数として 1 つ受け取る必要があります。

   複数の ".mo" ファイルがあった場合、後ろのファイルは前のファイルのフ
   ォールバックとして利用されます。 フォールバックの設定のために、
   "copy.copy()" を使いキャッシュから翻訳オブジェクトを複製します; こ
   うすることで、実際のインスタンスデータはキャッシュのものと共有され
   たままになります。

   ".mo" ファイルが見つからなかった場合、 *fallback* が偽 (デフォルト
   値) ならこの関数は "OSError" を送出し、 *fallback* が真なら
   "NullTranslations" インスタンスが返されます。

   バージョン 3.3 で変更: 以前は "IOError" が送出されました; それは現
   在 "OSError" のエイリアスです。

   バージョン 3.11 で変更: *codeset* 引数が削除されました。

gettext.install(domain, localedir=None, *, names=None)

   *domain* と *localedir* を "translation()" 関数に渡し、それに基づい
   て Python の組み込み名前空間に "_()" 関数をインストールします。

   *names* パラメータについては、翻訳オブジェクトの "install()" メソッ
   ドの説明を参照ください。

   以下のように、アプリケーションで翻訳候補の文字列をマークするには、
   通常 "_()" 関数の呼び出しで囲みます。

      print(_('This string will be translated.'))

   アプリケーションのすべてのモジュールから簡単にアクセスできるように
   、"_()" 関数を Python の組み込み名前空間にインストールすると便利で
   す。

   バージョン 3.11 で変更: *names* がキーワード専用引数になりました。


"NullTranslations" クラス
-------------------------

翻訳クラスは、元のソースファイル中のメッセージ文字列から翻訳されたメッ
セージ文字列への変換処理が実際に実装されているクラスです。 全ての翻訳
クラスで基底クラスとして使われているクラスが "NullTranslations" です;
このクラスは、独自の翻訳クラスを実装するのに使える基本的なインターフェ
ースを提供しています。 以下に "NullTranslations" のメソッドを示します:

class gettext.NullTranslations(fp=None)

   オプションの *ファイルオブジェクト* *fp* を取ります。この引数は基底
   クラスでは無視されます。このメソッドは  "保護された (protected)" イ
   ンスタンス変数 *_info* および  *_charset* を初期化します。これらの
   変数の値は派生クラスで設定することができます。同様に *_fallback* も
   初期化しますが、この値は "add_fallback()" で設定されます。その後、
   *fp* が "None" でない場合 "self._parse(fp)" を呼び出します。

   _parse(fp)

      基底クラスでは何もしない (no-op) ようになっています。このメソッ
      ドの役割はファイルオブジェクト *fp* を引数に取り、ファイルからデ
      ータを読み出し、メッセージカタログを初期化することです。サポート
      されていないメッセージカタログ形式を使っている場合、その形式を解
      釈するためにはこのメソッドを上書きしなくてはなりません。

   add_fallback(fallback)

      *fallback* を現在の翻訳オブジェクトの代替オブジェクトとして追加
      します。翻訳オブジェクトが与えられたメッセージに対して翻訳メッセ
      ージを提供できない場合、この代替オブジェクトに問い合わせることに
      なります。

   gettext(message, /)

      フォールバックが設定されている場合、フォールバックの "gettext()"
      に処理を移譲します。 そうでない場合、引数として受け取った
      *message* を返します。 派生クラスで上書きするメソッドです。

   ngettext(singular, plural, n, /)

      フォールバックが設定されている場合、フォールバックの
      "ngettext()" に処理を移譲します。 そうでない場合、 *n* が 1 なら
      *singular* を返します; それ以外なら *plural* を返します。 派生ク
      ラスで上書きするメソッドです。

   pgettext(context, message, /)

      代替オブジェクトが設定されている場合、 "pgettext()" を代替オブジ
      ェクトに転送します。そうでない場合、翻訳されたメッセージを返しま
      す。派生クラスで上書きするメソッドです。

      Added in version 3.8.

   npgettext(context, singular, plural, n, /)

      代替オブジェクトが設定されている場合、 "npgettext()" を代替オブ
      ジェクトに転送します。そうでない場合、翻訳されたメッセージを返し
      ます。派生クラスで上書きするメソッドです。

      Added in version 3.8.

   info()

      メッセージカタログファイルから取得したメタデータを含む辞書を返し
      ます。

   charset()

      メッセージカタログファイルのエンコーディングを返します。

   install(names=None)

      このメソッドは "gettext()" を組み込み名前空間にインストールし、
      変数 "_" に束縛します。

      *names* 引数を指定する場合は、"_()" に加えて組み込み名前空間にイ
      ンストールする関数名のシーケンスを渡してください。 サポートされ
      る名前は "'gettext'"、"'ngettext'"、"'pgettext'"、"'npgettext'"
      です。

      これは "_()" 関数をアプリケーションで利用可能にする最も便利な方
      法ですが、唯一の方法ではありません。 アプリケーション全体、特に
      組み込み名前空間に影響するため、地域化したモジュールでは決して
      "_()" をインストールしないでください。 代わりに次のコードで、そ
      のモジュール内で "_()" を利用可能にします。

         import gettext
         t = gettext.translation('mymodule', ...)
         _ = t.gettext

      これにより "_()" はモジュールのグローバル名前空間だけに置かれる
      ので、このモジュール内の呼び出しにだけ影響します。

      バージョン 3.8 で変更: "'pgettext'" と "'npgettext'" が追加され
      ました。


"GNUTranslations" クラス
------------------------

"gettext" モジュールは、"NullTranslations" の派生クラスとして
"GNUTranslations" も提供します。 このクラスは "_parse()" をオーバーラ
イドし、ビッグエンディアンとリトルエンディアンの両方の GNU **gettext**
形式の ".mo" ファイルを読み取れるようにします。

"GNUTranslations" は翻訳カタログから省略可能なメタデータを解析します。
GNU **gettext** では、空文字列の翻訳としてメタデータを格納する慣習があ
ります。 このメタデータは **RFC 822** 形式の "key: value" ペアで、
"Project-Id-Version" キーを含める必要があります。 "Content-Type" キー
があれば、その "charset" プロパティを使って「保護された」インスタンス
変数 "_charset" を初期化します。 見つからなければデフォルトの "None"
になります。 文字セットのエンコーディングが指定されていれば、カタログ
から読み取ったすべてのメッセージ ID とメッセージ文字列をそのエンコーデ
ィングで Unicode に変換します。 指定されていなければ ASCII と見なしま
す。

メッセージ ID も Unicode 文字列として読み取るため、すべての
"*gettext()" メソッドは、メッセージ ID がバイト文字列ではなく Unicode
文字列であることを前提とします。

キーと値のペア全体を辞書に格納し、「保護された」インスタンス変数
"_info" に設定します。

".mo" ファイルのマジックナンバーが不正な場合や、メジャーバージョン番号
が予期されないものの場合、あるいはその他の問題がファイルの読み出し中に
発生した場合、 "GNUTranslations" クラスのインスタンス化で "OSError" が
送出されることがあります。

class gettext.GNUTranslations

   以下のメソッドは基底クラスの実装からオーバライドされています:

   gettext(message, /)

      カタログから *message* id を検索して、対応するメッセージ文字列を
      Unicode 文字列として返します。 *message* id に対応するエントリが
      カタログに存在せず、フォールバックが設定されている場合、検索処理
      をフォールバックの "gettext()" メソッドに移譲します。 それ以外の
      場合は、 *message* id 自体が返されます。

   ngettext(singular, plural, n, /)

      メッセージ ID に対して複数形の検索を行います。 カタログの検索で
      は *singular* をメッセージ ID として使い、*n* をもとに使う複数形
      を決定します。 返されるメッセージ文字列は Unicode 文字列です。

      メッセージ id がカタログ中に見つからず、フォールバックが指定され
      ている場合は、メッセージ検索要求はフォールバックの "ngettext()"
      メソッドに移譲されます。 それ以外の場合、 *n* が 1 ならば
      *singular* が返され、それ以外なら *plural* が返されます。

      以下に例を示します。:

         n = len(os.listdir('.'))
         cat = GNUTranslations(somefile)
         message = cat.ngettext(
             'There is %(num)d file in this directory',
             'There are %(num)d files in this directory',
             n) % {'num': n}

   pgettext(context, message, /)

      カタログから *context* と *message* ID を検索し、対応するメッセ
      ージを Unicode 文字列として返します。 *message* ID と *context*
      に対応するエントリがなく、フォールバックが設定されている場合は、
      そのフォールバックの "pgettext()" メソッドに検索を委ねます。 そ
      うでなければ *message* ID 自体を返します。

      Added in version 3.8.

   npgettext(context, singular, plural, n, /)

      メッセージ ID に対して複数形の検索を行います。 カタログの検索で
      は *singular* をメッセージ ID として使い、*n* をもとに使う複数形
      を決定します。

      *context* に対するメッセージ id がカタログ中に見つからず、フォー
      ルバックオブジェクトが指定されている場合、メッセージ検索要求はフ
      ォールバックオブジェクトの "npgettext()" メソッドに転送されます
      。そうでない場合、 *n* が 1 ならば *singular* が返され、それ以外
      に対しては *plural* が返されます。

      Added in version 3.8.


Solaris メッセージカタログ機構のサポート
----------------------------------------

Solaris オペレーティングシステムでは、独自の ".mo" バイナリファイル形
式を定義していますが、この形式に関するドキュメントが手に入らないため、
現時点ではサポートされていません。


Catalog コンストラクタ
----------------------

GNOME は James Henstridge による "gettext" モジュールを使っていますが
、このバージョンの API は少し異なります。 ドキュメントに記載されていた
使い方は次のとおりです。

   import gettext
   cat = gettext.Catalog(domain, localedir)
   _ = cat.gettext
   print(_('hello world'))

この古いモジュールとの互換性のために、"Catalog()" 関数は、上記の
"translation()" 関数の別名になっています。

このモジュールと Henstridge のバージョンとの間には一つ相違点があります
: 彼のカタログオブジェクトはマップ型の API を介したアクセスがサポート
されていましたが、この API は使われていないらしく、現在はサポートされ
ていません。


プログラムやモジュールを国際化する
==================================

国際化 (I18N, I-nternationalizatio-N) とは、プログラムを複数の言語に対
応させる操作を指します。地域化 (L10N, L-ocalizatio-N) とは、すでに国際
化されているプログラムを特定地域の言語や文化的な事情に対応させることを
指します。Python プログラムに多言語メッセージ機能を追加するには、以下
の手順を踏む必要があります:

1. プログラムやモジュールで翻訳対象とする文字列に特殊なマークをつけて
   準備します

2. マークづけをしたファイルに一連のツールを走らせ、生のメッセージカタ
   ログを生成します

3. 特定の言語へのメッセージカタログの翻訳を作成します

4. "gettext" モジュールを使って、メッセージ文字列が適切に翻訳されるよ
   うにします。

コードを国際化に対応させるには、ファイル内のすべての文字列を調べる必要
があります。 翻訳が必要な文字列は "_('...')"、つまり "_" 関数の呼び出
しで囲んでマークしてください。 例えば次のようにします。

   filename = 'mylog.txt'
   message = _('writing a log message')
   with open(filename, 'w') as fp:
       fp.write(message)

この例では、文字列 "'writing a log message'" が翻訳対象候補としてマー
ク付けされており、文字列 "'mylog.txt'" および "'w'" はされていません。

翻訳対象の文字列を抽出するツールもあります。 オリジナルの GNU
**gettext** は C と C++ のソースコードしかサポートしませんが、拡張版の
**xgettext** は Python を含めた多くの言語で書かれたコードを読み取り、
翻訳できる文字列を発見します。 Babel は Python の国際化ライブラリで、
翻訳文字列の抽出とメッセージカタログのコンパイルを行う "pybabel" スク
リプトがあります。 François Pinard が開発した **xpot** と呼ばれるプロ
グラムは同じような処理を行え、彼の po-utils package の一部として利用可
能です。

(Python には **pygettext.py** および **msgfmt.py** という名前の pure-
Python 版プログラムもあります; これをインストールしてくれる Python デ
ィストリビューションもあります。 **pygettext.py** は **xgettext** に似
たプログラムですが Python のソースコードしか理解できず、 C や C++ のよ
うな他のプログラミング言語を扱えません。 **pygettext.py** は
**xgettext** と同様のコマンドラインインターフェースをサポートしていま
す; 詳しい使い方については "pygettext.py --help" と実行してください。
**msgfmt.py** は GNU **msgfmt** とバイナリ互換性があります。 この2つの
プログラムがあれば、 GNU **gettext** パッケージを使わずに Python アプ
リケーションを国際化できるでしょう。)

**xgettext** や **pygettext** のようなツールは、メッセージカタログであ
る ".po" ファイルを生成します。 このファイルは人間が判読可能な構造をし
ていて、ソースコード中のマークが着けられた文字列と、その文字列の仮置き
の訳文が一緒に書き込まれています。

次に、これらの ".po" ファイルのコピーを各翻訳者に渡し、サポートする自
然言語ごとに翻訳してもらいます。 完成した各言語の "<language-name>.po"
ファイルが返されたら、**msgfmt** プログラムで、機械が読み取れるバイナ
リカタログファイル ".mo" にコンパイルします。 実行時の翻訳処理では、
"gettext" モジュールがこの ".mo" ファイルを使います。

コードでの "gettext" モジュールの使い方は、単一のモジュールを国際化す
るのか、アプリケーション全体を国際化するのかによって異なります。 次の
2 つの節では、それぞれの場合について説明します。


モジュールを地域化する
----------------------

モジュールを地域化する場合、グローバルな変更、例えば組み込み名前空間へ
の変更を行わないように注意しなければなりません。GNU **gettext** API で
はなく、クラス形式の API を使うべきです。

仮に対象のモジュール名を "spam" とし、モジュールの各言語における翻訳が
収められた ".mo" ファイルが "/usr/share/locale" に GNU **gettext** 形
式で置かれているとします。この場合、モジュールの最初で以下のようにしま
す:

   import gettext
   t = gettext.translation('spam', '/usr/share/locale')
   _ = t.gettext


アプリケーションを地域化する
----------------------------

アプリケーションを地域化する場合は、"_()" 関数を組み込み名前空間にグロ
ーバルにインストールできます。 通常はアプリケーションのメインの実行フ
ァイルで行います。 これにより、アプリケーション固有のすべてのファイル
で、ファイルごとに明示的にインストールせずに "_('...')" を使えます。

単純な場合では、単に以下の短いコードをアプリケーションの主ドライバファ
イルに追加するだけです:

   import gettext
   gettext.install('myapplication')

ロケールのディレクトリを設定する必要がある場合は、"install()" 関数に渡
すことができます。

   import gettext
   gettext.install('myapplication', '/usr/share/locale')


動作中 (on the fly) に言語を切り替える
--------------------------------------

多くの言語を同時にサポートする必要がある場合、複数の翻訳インスタンスを
生成して、例えば以下のコードのように、インスタンスを明示的に切り替えて
もかまいません。:

   import gettext

   lang1 = gettext.translation('myapplication', languages=['en'])
   lang2 = gettext.translation('myapplication', languages=['fr'])
   lang3 = gettext.translation('myapplication', languages=['de'])

   # 最初は language1 を使う
   lang1.install()

   # ... 時間がたち、ユーザーが言語 2 を選ぶ
   lang2.install()

   # ... さらに時間がたち、ユーザーが言語 3 を選ぶ
   lang3.install()


翻訳処理の遅延解決
------------------

コードを書く上では、ほとんどの状況で文字列はコードされた場所で翻訳され
ます。しかし場合によっては、翻訳対象として文字列をマークはするが、その
後実際に翻訳が行われるように遅延させる必要が生じます。古典的な例は以下
のようなコートです:

   animals = ['mollusk',
              'albatross',
              'rat',
              'penguin',
              'python', ]
   # ...
   for a in animals:
       print(a)

ここで、リスト "animals" 内の文字列は翻訳対象としてマークはしたいが、
文字列が出力されるまで実際に翻訳を行うのは避けたいとします。

こうした状況を処理する一つの方法を以下に示します:

   def _(message): return message

   animals = [_('mollusk'),
              _('albatross'),
              _('rat'),
              _('penguin'),
              _('python'), ]

   del _

   # ...
   for a in animals:
       print(_(a))

"_()" の仮の定義は文字列を変更せずに返すだけなので、この方法でうまくい
きます。 この仮の定義は、組み込み名前空間にある "_()" の定義を一時的に
("del" コマンドまで) 覆い隠します。 ただし、ローカル名前空間ですでに
"_()" を定義している場合には注意してください。

2 回目の "_()" の使用では、引数が文字列リテラルではないため、
**gettext** プログラムは "a" を翻訳対象として認識しません。

もう一つの処理法は、以下の例のようなやり方です:

   def N_(message): return message

   animals = [N_('mollusk'),
              N_('albatross'),
              N_('rat'),
              N_('penguin'),
              N_('python'), ]

   # ...
   for a in animals:
       print(_(a))

この場合は "N_()" 関数で翻訳対象の文字列をマークするため、"_()" の定義
と衝突しません。 ただし、メッセージ抽出プログラムに、"N_()" でマークさ
れた文字列を探すよう指定する必要があります。 **xgettext**、
**pygettext**、"pybabel extract"、**xpot** は、いずれも "-k" コマンド
ラインオプションでこの指定をサポートします。 ここで "N_()" という名前
を選んだことに特別な意味はありません。
"MarkThisStringForTranslation()" でも同様に使えます。


謝辞
====

以下の人々が、このモジュールのコード、フィードバック、設計に関する助言
、過去の実装、そして有益な経験談による貢献をしてくれました:

* Peter Funk

* James Henstridge

* Juan David Ibáñez Palomar

* Marc-André Lemburg

* Martin von Löwis

* François Pinard

* Barry Warsaw

* Gustavo Niemeyer

-[ 脚注 ]-

[1] デフォルトのロケールディレクトリはシステムに依存します。 例えば
    Red Hat Linux では "/usr/share/locale"、Solaris では
    "/usr/lib/locale" です。 "gettext" モジュールはこうしたシステムご
    とのデフォルト値には対応せず、代わりに
    "*sys.base_prefix*/share/locale" をデフォルトとして使います
    ("sys.base_prefix" を参照)。 このため、アプリケーションの開始時に
    明示的な絶対パスを指定して "bindtextdomain()" を呼び出すのが最善で
    す。

[2] 上の "bindtextdomain()" に関する脚注を参照してください。
