gettext モジュールは、 Python によるモジュールやアプリケーションの国際化 (I18N, I-nternationalizatio-N) および地域化 (L10N, L-ocalizatio-N) サービスを提供します。このモジュールは GNU gettext メッセージカタログへの API と、より高レベルで Python ファイルに適しているクラスに基づいた API の両方をサポートしてます。以下で述べるインタフェースを使うことで、モジュールやアプリケーションのメッセージをある自然言語で記述しておき、翻訳されたメッセージのカタログを与えて他の異なる自然言語の環境下で動作させることができます。
ここでは Python のモジュールやアプリケーションを地域化するためのいくつかのヒントも提供しています。
gettext モジュールでは、以下の GNU gettext API に非常に良く似た API を提供しています。この API を使う場合、メッセージ翻訳の影響はアプリケーション全体に及ぼすことになります。アプリケーションが単一の言語しか扱わず、各言語に依存する部分をユーザのロケール情報によって選ぶのなら、ほとんどの場合この方法でやりたいことを実現できます。Python モジュールを地域化していたり、アプリケーションの実行中に言語を切り替えたい場合、おそらくクラスに基づいた API を使いたくなるでしょう。
domain をロケール辞書 localedir に結び付け (bind) ます。具体的には、 gettext は与えられたドメインに対するバイナリ形式の .mo ファイルを、(Unixでは) localedir/language/LC_MESSAGES/domain.mo から探します。ここで languages はそれぞれ環境変数 LANGUAGE 、 LC_ALL 、 LC_MESSAGES 、および LANG の中から検索されます。
localedir が省略されるか None の場合、現在 domain に結び付けられている内容が返されます。 [1]
domain を codeset に結び付けて、 gettext() ファミリの関数が返す文字列のエンコード方式を変更します。 codeset を省略すると、現在結び付けられているコードセットを返します。
バージョン 2.4 で追加.
現在のグローバルドメインを調べたり変更したりします。 domain が None の場合、現在のグローバルドメインが返されます。それ以外の場合にはグローバルドメインは domain に設定され、設定されたグローバルドメインを返します。
現在のグローバルドメイン、言語、およびロケール辞書に基づいて、 message の特定地域向けの翻訳を返します。通常、ローカルな名前空間ではこの関数に _() という別名をつけます (下の例を参照してください)。
gettext() と同じですが、 bind_textdomain_codeset() で特にエンコードを指定しない限り、翻訳結果を優先システムエンコーディング (preferred system encoding) で返します。
バージョン 2.4 で追加.
dgettext() と同じですが、 bind_textdomain_codeset() で特にエンコードを指定しない限り、翻訳結果を優先システムエンコーディング (preferred system encoding) で返します。
バージョン 2.4 で追加.
gettext() と同様ですが、複数形の場合を考慮しています。翻訳文字列が見つかった場合、 n の様式を適用し、その結果得られたメッセージを返します (言語によっては二つ以上の複数形があります)。翻訳文字列が見つからなかった場合、 n が 1 なら singular を返します; そうでない場合 plural を返します。
複数形の様式はカタログのヘッダから取り出されます。様式は C または Python の式で、自由な変数 n を持ちます; 式の評価値はカタログ中の複数形のインデクスとなります。 .po ファイルで用いられる詳細な文法と、様々な言語における様式については、GNU gettext ドキュメントを参照してください。
バージョン 2.3 で追加.
ngettext() と同じですが、 bind_textdomain_codeset() で特にエンコードを指定しない限り、翻訳結果を優先システムエンコーディング (preferred system encoding) で返します。
バージョン 2.4 で追加.
ngettext() と同様ですが、指定された domain からメッセージを探します。
バージョン 2.3 で追加.
dngettext() と同じですが、 bind_textdomain_codeset() で特にエンコードを指定しない限り、翻訳結果を優先システムエンコーディング (preferred system encoding) で返します。
バージョン 2.4 で追加.
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.')
クラス形式の gettext モジュールのAPI は GNU gettext API よりも高い柔軟性と利便性を持っています。 Python のアプリケーションやモジュールを地域化するにはこちらを使う方を勧めます。 gettext では、GNU .mo 形式のファイルを解釈し、標準の 8 ビット文字列または Unicode 文字列形式でメッセージを返す “翻訳” クラスを定義しています。この “翻訳” クラスのインスタンスも、組み込み名前空間に関数 _() として組みこみ (install) できます。
この関数は標準的な .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 が与えられていれば、全ファイル名のリストが言語リストまたは環境変数で指定されている順番に並べられたものを返します。
Translations インスタンスを domain 、 localedir 、および languages に基づいて生成して返します。 domain 、 localedir 、および languages はまず関連付けられている .mo ファイルパスのリストを取得するために find() に渡されます。同じ .mo ファイル名を持つインスタンスはキャッシュされます。実際にインスタンス化されるクラスは class_ が与えられていればそのクラスが、そうでない時には GNUTranslations です。クラスのコンストラクタは単一の引数としてファイルオブジェクトを取らなくてはなりません。 codeset を指定した場合、翻訳文字列のエンコードに使う文字セットを変更します。
複数のファイルが発見された場合、後で見つかったファイルは前に見つかったファイルの代替でと見なされ、後で見つかった方が利用されます。代替の設定を可能にするには、 copy.copy() を使ってキャッシュから翻訳オブジェクトを複製します; こうすることで、実際のインスタンスデータはキャッシュのものと共有されます。
.mo ファイルが見つからなかった場合、 fallback が偽 (標準の設定です) ならこの関数は IOError を送出し、 fallback が真なら NullTranslations インスタンスが返されます。
バージョン 2.4 で変更: codeset パラメタを追加しました.
translation() に domain 、 localedir 、および codeset を渡してできる関数 _() を Python の組み込み名前空間に組み込みます。 unicode フラグは translation() の返す翻訳オブジェクトの install() メソッドに渡されます。
names パラメタについては、翻訳オブジェクトの install() メソッドの説明を参照ください。
以下に示すように、通常はアプリケーション中の文字列を関数 _() の呼び出しで包み込んで翻訳対象候補であることを示します:
print _('This string will be translated.')
利便性を高めるためには、 _() 関数を Python の組み込み名前空間に組み入れる必要があります。こうすることで、アプリケーション内の全てのモジュールからアクセスできるようになります。
バージョン 2.4 で変更: codeset パラメタを追加しました.
バージョン 2.5 で変更: names パラメタを追加しました.
翻訳クラスは、元のソースファイル中のメッセージ文字列から翻訳されたメッセージ文字列への変換を実際に実装しているクラスです。全ての翻訳クラスが基底クラスとして用いるクラスが NullTranslations です; このクラスでは独自の特殊な翻訳クラスを実装するために使うことができる基本的なインタフェースを以下に NullTranslations のメソッドを示します:
オプションのファイルオブジェクト fp を取ります。この引数は基底クラスでは無視されます。このメソッドは “保護された (protected)” インスタンス変数 _info および _charset を初期化します。これらの変数の値は派生クラスで設定することができます。同様に _fallback も初期化しますが、この値は add_fallback() で設定されます。その後、 fp が None でない場合 self._parse(fp) を呼び出します。
基底クラスでは何もしない (no-op) ようになっています。このメソッドの役割はファイルオブジェクト fp を引数に取り、ファイルからデータを読み出し、メッセージカタログを初期化することです。サポートされていないメッセージカタログ形式を使っている場合、その形式を解釈するためにはこのメソッドを上書きしなくてはなりません。
fallback を現在の翻訳オブジェクトの代替オブジェクトとして追加します。翻訳オブジェクトが与えられたメッセージに対して翻訳メッセージを提供できない場合、この代替オブジェクトに問い合わせることになります。
代替オブジェクトが設定されている場合、 gettext() を代替オブジェクトに転送します。そうでない場合、翻訳されたメッセージを返します。派生クラスで上書きするメソッドです。
代替オブジェクトが設定されている場合、 lgettext() を代替オブジェクトに転送します。そうでない場合、翻訳されたメッセージを返します。派生クラスで上書きするメソッドです。
バージョン 2.4 で追加.
代替オブジェクトが設定されている場合、 gettext() を代替オブジェクトに転送します。そうでない場合、翻訳されたメッセージを Unicode 文字列で返します。派生クラスで上書きするメソッドです。
代替オブジェクトが設定されている場合、 ngettext() を代替オブジェクトに転送します。そうでない場合、翻訳されたメッセージを返します。派生クラスで上書きするメソッドです。
バージョン 2.3 で追加.
代替オブジェクトが設定されている場合、 lngettext() を代替オブジェクトに転送します。そうでない場合、翻訳されたメッセージを返します。派生クラスで上書きするメソッドです。
バージョン 2.4 で追加.
代替オブジェクトが設定されている場合、 ungettext() を代替オブジェクトに転送します。そうでない場合、翻訳されたメッセージを Unicode 文字列で返します。派生クラスで上書きするメソッドです。
バージョン 2.3 で追加.
“protected” の _info 変数を返します。
“protected” の _charset 変数を返します。
翻訳メッセージとして返す文字列のエンコードを決める、 “protected” の _output_charset 変数を返します。
バージョン 2.4 で追加.
翻訳メッセージとして返す文字列のエンコードを決める、 “protected” の変数 _output_charset を変更します。
バージョン 2.4 で追加.
unicode フラグが偽の場合、このメソッドは self.gettext() を組み込み名前空間に組み入れ、 _ と結び付けます。 unicode が真の場合、 self.gettext() の代わりに self.ugettext() を結び付けます。標準では unicode は偽です。
names パラメタには、 _() 以外に組み込みの名前空間にインストールしたい関数名のシーケンスを指定します。サポートしている名前は 'gettext' (unicode フラグの設定に応じて self.gettext() あるいは self.ugettext() のいずれかに対応します)、 'ngettext' (unicode フラグの設定に応じて self.ngettext() あるいは self.ungettext() のいずれかに対応します)、 'lgettext' および 'lngettext' です。
この方法はアプリケーションで _() 関数を利用できるようにするための最も便利な方法ですが、唯一の手段でもあるので注意してください。この関数はアプリケーション全体、とりわけ組み込み名前空間に影響するので、地域化されたモジュールで _() を組み入れることができないのです。その代わりに、以下のコード:
import gettext
t = gettext.translation('mymodule', ...)
_ = t.gettext
を使って _() を使えるようにしなければなりません。
この操作は _() をモジュール内だけのグローバル名前空間に組み入れるので、モジュール内の _() の呼び出しだけに影響します。
バージョン 2.5 で変更: names パラメタを追加しました.
gettext モジュールでは NullTranslations から派生したもう一つのクラス: GNUTranslations を提供しています。このクラスはビッグエンディアン、およびリトルエンディアン両方のバイナリ形式の GNU gettext .mo ファイルを読み出せるように _parse() を上書きしています。また、このクラスはメッセージ id とメッセージ文字列の両方を Unicode に型強制します。
このクラスではまた、翻訳カタログ以外に、オプションのメタデータを読み込んで解釈します。GNU gettext では、空の文字列に対する変換先としてメタデータを取り込むことが慣習になっています。このメタデータは RFC 822 形式の key: value のペアになっており、 Project-Id-Version キーを含んでいなければなりません。キー Content-Type があった場合、 charset の特性値 (property) は “保護された” _charset インスタンス変数を初期化するために用いられます。値がない場合には、デフォルトとして None が使われます。エンコードに用いられる文字セットが指定されている場合、カタログから読み出された全てのメッセージ id とメッセージ文字列は、指定されたエンコードを用いて Unicode に変換されます。 ugettext() は常に Unicode を返し、 gettext() はエンコードされた 8 ビット文字列を返します。どちらのメソッドにおける引数 id の場合も、Unicode 文字列か US-ASCII 文字のみを含む 8 ビット文字列だけが受理可能です。国際化されたPython プログラムでは、メソッドの Unicode 版 (すなわち ugettext() や ungettext()) の利用が推奨されています。
key/value ペアの集合全体は辞書型データ中に配置され、”保護された” _info インスタンス変数に設定されます。
.mo ファイルのマジックナンバーが不正な場合、あるいはその他の問題がファイルの読み出し中に発生した場合、 GNUTranslations クラスのインスタンス化で IOError が送出されることがあります。
以下のメソッドは基底クラスの実装からオーバライドされています:
カタログから message id を検索して、対応するメッセージ文字列を、カタログの文字セットが既知のエンコードの場合、エンコードされた 8 ビット文字列として返します。 message id に対するエントリがカタログに存在せず、フォールバックが設定されている場合、フォールバック検索はオブジェクトの gettext() メソッドに転送されます。そうでない場合、 message id 自体が返されます。
カタログから message id を検索して、対応するメッセージ文字列を、 Unicode でエンコードして返します。 message id に対するエントリがカタログに存在せず、フォールバックが設定されている場合、フォールバック検索はオブジェクトの ugettext() メソッドに転送されます。そうでない場合、 message id 自体が返されます。
メッセージ id に対する複数形を検索します。カタログに対する検索では singular がメッセージ id として用いられ、 n にはどの複数形を用いるかを指定します。返されるメッセージ文字列は 8 ビットの文字列で、カタログの文字セットが既知の場合にはその文字列セットでエンコードされています。
メッセージ id がカタログ中に見つからず、フォールバックオブジェクトが指定されている場合、メッセージ検索要求はフォールバックオブジェクトの ngettext() メソッドに転送されます。そうでない場合、 n が 1 ならば singular が返され、それ以外に対しては plural が返されます。
バージョン 2.3 で追加.
メッセージ id に対する複数形を検索します。カタログに対する検索では singular がメッセージ id として用いられ、 n にはどの複数形を用いるかを指定します。返されるメッセージ文字列は Unicode 文字列です。
メッセージ id がカタログ中に見つからず、フォールバックオブジェクトが指定されている場合、メッセージ検索要求はフォールバックオブジェクトの ungettext() メソッドに転送されます。そうでない場合、 n が 1 ならば singular が返され、それ以外に対しては plural が返されます。
以下に例を示します。:
n = len(os.listdir('.'))
cat = GNUTranslations(somefile)
message = cat.ungettext(
'There is %(num)d file in this directory',
'There are %(num)d files in this directory',
n) % {'num': n}
バージョン 2.3 で追加.
Solaris オペレーティングシステムでは、独自の .mo バイナリファイル形式を定義していますが、この形式に関するドキュメントが手に入らないため、現時点ではサポートされていません。
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 プログラムに多言語メッセージ機能を追加するには、以下の手順を踏む必要があります:
ソースコードを I18N 化する準備として、ファイル内の全ての文字列を探す必要があります。翻訳を行う必要のある文字列はどれも _('...') — すなわち関数 _() の呼び出しで包むことでマーク付けしなくてはなりません。例えば以下のようにします:
filename = 'mylog.txt'
message = _('writing a log message')
fp = open(filename, 'w')
fp.write(message)
fp.close()
この例では、文字列 'writing a log message' が翻訳対象候補としてマーク付けされており、文字列 'mylog.txt' および 'w' はされていません。
Python の配布物には、ソースコードに準備作業を行った後でメッセージカタログの生成を助ける 2 つのツールが付属します。これらはバイナリ配布の場合には付属していたりしなかったりしますが、ソースコード配布には入っており、 Tools/i18n ディレクトリにあります。
pygettext プログラム [3] は全ての Python ソースコードを走査し、予め翻訳対象としてマークした文字列を探し出します。このツールは GNU gettext プログラムと同様ですが、Python ソースコードの機微について熟知している反面、C 言語や C++言語のソースコードについては全く知りません。(C 言語による拡張モジュールのように) C 言語のコードも翻訳対象にしたいのでない限り、 GNU gettext は必要ありません。
pygettext は、テキスト形式 Uniforum スタイルによる人間が判読可能なメッセージカタログ .pot ファイル群を生成します。このファイル群はソースコード中でマークされた全ての文字列と、それに対応する翻訳文字列のためのプレースホルダを含むファイルで構成されています。 pygettext はコマンドライン形式のスクリプトで、 xgettext と同様のコマンドラインインタフェースをサポートします; 使用法についての詳細を見るには:
pygettext.py --help
を起動してください。
これら .pot ファイルのコピーは次に、サポート対象の各自然言語について、言語ごとのバージョンを作成する個々の人間の翻訳者に頒布されます。翻訳者たちはプレースホルダ部分を埋めて言語ごとのバージョンをつくり、 .po ファイルとして返します。(Tools/i18n ディレクトリ内の) msgfmt.py [4] プログラムを使い、翻訳者から返された .po ファイルから機械可読な .mo バイナリカタログファイルを生成します。 .mo ファイルは、 gettext モジュールが実行時に実際の翻訳処理を行うために使われます。
gettext モジュールをソースコード中でどのように使うかは単一のモジュールを国際化するのか、それともアプリケーション全体を国際化するのかによります。次のふたつのセクションで、それぞれについて説明します。
モジュールを地域化する場合、グローバルな変更、例えば組み込み名前空間への変更を行わないように注意しなければなりません。GNU gettext API ではなく、クラスベースの API を使うべきです。
仮に対象のモジュール名を “spam” とし、モジュールの各言語における翻訳が収められた .mo ファイルが /usr/share/locale に GNU gettext 形式で置かれているとします。この場合、モジュールの最初で以下のようにします:
import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.lgettext
翻訳オブジェクトが .po ファイル中の Unicode 文字列を返すようになっているのなら、上の代わりに以下のようにします:
import gettext
t = gettext.translation('spam', '/usr/share/locale')
_ = t.ugettext
アプリケーションを地域化するのなら、関数 _() をグローバルな組み込み名前空間に組み入れなければならず、これは通常アプリケーションの主ドライバ (main driver) ファイルで行います。この操作によって、アプリケーション独自のファイルは明示的に各ファイルで _() の組み入れを行わなくても単に _('...') を使うだけで済むようになります。
単純な場合では、単に以下の短いコードをアプリケーションの主ドライバファイルに追加するだけです:
import gettext
gettext.install('myapplication')
ロケールディレクトリや unicode フラグを設定する必要がある場合、それらの値を install() 関数に渡すことができます:
import gettext
gettext.install('myapplication', '/usr/share/locale', unicode=1)
多くの言語を同時にサポートする必要がある場合、複数の翻訳インスタンスを生成して、例えば以下のコード:
import gettext
lang1 = gettext.translation('myapplication', languages=['en'])
lang2 = gettext.translation('myapplication', languages=['fr'])
lang3 = gettext.translation('myapplication', languages=['de'])
# start by using language1
lang1.install()
# ... time goes by, user selects language 2
lang2.install()
# ... more time goes by, user selects language 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 命令を実行するまで) 一時的に上書きすることができます。もしそれまでに _() をローカルな名前空間に持っていたら注意してください。
二つ目の例における _() の使い方では、”a” は文字列リテラルではないので、 pygettext プログラムが翻訳可能な対象として識別しません。
もう一つの処理法は、以下の例のようなやり方です:
def N_(message): return message
animals = [N_('mollusk'),
N_('albatross'),
N_('rat'),
N_('penguin'),
N_('python'), ]
# ...
for a in animals:
print _(a)
この例の場合では、翻訳可能な文字列を関数 N_() でマーク付けしており [5] 、 _() の定義とは全く衝突しません。しかしメッセージ展開プログラムには翻訳対象の文字列が N_() でマークされていることを教える必要が出てくるでしょう。 pygettext および xpot は両方とも、コマンドライン上のスイッチでこの機能をサポートしています。
Python 2.4 からは、 lgettext() ファミリが導入されました。この関数の目的は、現行の GNU gettext 実装によりよく準拠した別の関数を提供することにあります。翻訳メッセージファイル中で使われているのと同じコードセットを使って文字列をエンコードして返す gettext() と違い、これらの関数は locale.getpreferredencoding() の返す優先システムエンコーディングを使って翻訳メッセージ文字列をエンコードして返します。また、Python 2.4 では、翻訳メッセージ文字列で使われているコードセットを明示的に選べるようにする関数が新たに導入されていることにも注意してください。コードセットを明示的に設定すると、 lgettext() でさえ、指定したコードセットで翻訳メッセージ文字列を返します。これは GNU gettext 実装が期待している仕様と同じです。
以下の人々が、このモジュールのコード、フィードバック、設計に関する助言、過去の実装、そして有益な経験談による貢献をしてくれました:
Footnotes
[1] | 標準でロケールが収められているディレクトリはシステム依存です; 例えば、RedHat Linux では /usr/share/locale ですが、 Solaris では /usr/lib/locale です。 gettext モジュールはこうしたシステム依存の標準設定をサポートしません; その代わりに sys.prefix/share/locale を標準の設定とします。この理由から、常にアプリケーションの開始時に絶対パスで明示的に指定して bindtextdomain() を呼び出すのが最良のやり方ということになります。 |
[2] | 上の bindtextdomain() に関する脚注を参照してください。 |
[3] | 同様の作業を行う xpot と呼ばれるプログラムを François Pinard が書いています。このプログラムは彼の po-utils パッケージの一部で、 http://po-utils.progiciels-bpi.ca/ で入手できます。 |
[4] | msgfmt.py は GNU msgfmt とバイナリ互換ですが、より単純で、Python だけを使った実装がされています。このプログラムと pygettext.py があれば、通常 Python プログラムを国際化するために GNU gettext パッケージをインストールする必要はありません。 |
[5] | この N_() をどうするかは全くの自由です; MarkThisStringForTranslation() などとしてもかまいません。 |