23.1. gettext — 多言語対応に関する国際化サービス

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


gettext モジュールは、 Python によるモジュールやアプリケーションの国際化 (I18N, I-nternationalizatio-N) および地域化 (L10N, L-ocalizatio-N) サービスを提供します。このモジュールは GNU gettext メッセージカタログへの API と、より高水準で Python ファイルに適しているクラスに基づいた API の両方をサポートしてます。以下で述べるインタフェースを使うことで、モジュールやアプリケーションのメッセージをある自然言語で記述しておき、翻訳されたメッセージのカタログを与えて他の異なる自然言語の環境下で動作させることができます。

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

23.1.1. GNU gettext API

gettext モジュールでは、以下の GNU gettext API に非常に良く似た API を提供しています。この API を使う場合、メッセージ翻訳の影響はアプリケーション全体に及ぼすことになります。アプリケーションが単一の言語しか扱わず、各言語に依存する部分をユーザのロケール情報によって選ぶのなら、ほとんどの場合この方法でやりたいことを実現できます。Python モジュールを地域化していたり、アプリケーションの実行中に言語を切り替えたい場合、おそらくクラスに基づいた API を使いたくなるでしょう。

gettext.bindtextdomain(domain, localedir=None)

domain をロケール辞書 localedir に結び付け (bind) ます。具体的には、 gettext は与えられたドメインに対するバイナリ形式の .mo ファイルを、(Unixでは) localedir/language/LC_MESSAGES/domain.mo から探します。ここで languages はそれぞれ環境変数 LANGUAGELC_ALLLC_MESSAGES 、および LANG の中から検索されます。

localedir が省略されるか None の場合、現在 domain に結び付けられている内容が返されます。 [1]

gettext.bind_textdomain_codeset(domain, codeset=None)

Bind the domain to codeset, changing the encoding of byte strings returned by the lgettext(), ldgettext(), lngettext() and ldngettext() functions. If codeset is omitted, then the current binding is returned.

gettext.textdomain(domain=None)

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

gettext.gettext(message)

現在のグローバルドメイン、言語、およびロケール辞書に基づいて、 message の特定地域向けの翻訳を返します。通常、ローカルな名前空間ではこの関数に _() という別名をつけます (下の例を参照してください)。

gettext.dgettext(domain, message)

Like gettext(), but look the message up in the specified domain.

gettext.ngettext(singular, plural, n)

Like gettext(), but consider plural forms. If a translation is found, apply the plural formula to n, and return the resulting message (some languages have more than two plural forms). If no translation is found, return singular if n is 1; return plural otherwise.

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

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

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

gettext.lgettext(message)
gettext.ldgettext(domain, message)
gettext.lngettext(singular, plural, n)
gettext.ldngettext(domain, singular, plural, n)

Equivalent to the corresponding functions without the l prefix (gettext(), dgettext(), ngettext() and dngettext()), but the translation is returned as a byte string encoded in the preferred system encoding if no other encoding was explicitly set with bind_textdomain_codeset().

警告

These functions should be avoided in Python 3, because they return encoded bytes. It’s much better to use alternatives which return Unicode strings instead, since most Python applications will want to manipulate human readable text as strings instead of bytes. Further, it’s possible that you may get unexpected Unicode-related exceptions if there are encoding problems with the translated strings. It is possible that the l*() functions will be deprecated in future Python versions due to their inherent problems and limitations.

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.'))

23.1.2. クラスに基づいた API

The class-based API of the gettext module gives you more flexibility and greater convenience than the GNU gettext API. It is the recommended way of localizing your Python applications and modules. gettext defines a "translations" class which implements the parsing of GNU .mo format files, and has methods for returning strings. Instances of this "translations" class can also install themselves in the built-in namespace as the function _().

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

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

localedir が与えられていない場合、標準のシステムロケールディレクトリが使われます。 [2] languages が与えられなかった場合、以下の環境変数: LANGUAGELC_ALLLC_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, codeset=None)

Return a Translations instance based on the domain, localedir, and languages, which are first passed to find() to get a list of the associated .mo file paths. Instances with identical .mo file names are cached. The actual class instantiated is either class_ if provided, otherwise GNUTranslations. The class’s constructor must take a single file object argument. If provided, codeset will change the charset used to encode translated strings in the lgettext() and lngettext() methods.

複数のファイルが発見された場合、後で見つかったファイルは前に見つかったファイルの代替でと見なされ、後で見つかった方が利用されます。代替の設定を可能にするには、 copy.copy() を使ってキャッシュから翻訳オブジェクトを複製します; こうすることで、実際のインスタンスデータはキャッシュのものと共有されます。

.mo ファイルが見つからなかった場合、 fallback が偽 (標準の設定です) ならこの関数は OSError を送出し、 fallback が真なら NullTranslations インスタンスが返されます。

バージョン 3.3 で変更: 以前は OSError の代わりに IOError が送出されていました。

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

translation()domainlocaledir 、および codeset を渡してできる関数 _() を Python の組み込み名前空間に組み込みます。

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

以下に示すように、通常はアプリケーション中の文字列を関数 _() の呼び出しで包み込んで翻訳対象候補であることを示します:

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

利便性を高めるためには、 _() 関数を Python の組み込み名前空間に組み入れる必要があります。こうすることで、アプリケーション内の全てのモジュールからアクセスできるようになります。

23.1.2.1. NullTranslations クラス

Translation classes are what actually implement the translation of original source file message strings to translated message strings. The base class used by all translation classes is NullTranslations; this provides the basic interface you can use to write your own specialized translation classes. Here are the methods of NullTranslations:

class gettext.NullTranslations(fp=None)

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

_parse(fp)

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

add_fallback(fallback)

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

gettext(message)

If a fallback has been set, forward gettext() to the fallback. Otherwise, return message. Overridden in derived classes.

ngettext(singular, plural, n)

If a fallback has been set, forward ngettext() to the fallback. Otherwise, return singular if n is 1; return plural otherwise. Overridden in derived classes.

lgettext(message)
lngettext(singular, plural, n)

Equivalent to gettext() and ngettext(), but the translation is returned as a byte string encoded in the preferred system encoding if no encoding was explicitly set with set_output_charset(). Overridden in derived classes.

警告

These methods should be avoided in Python 3. See the warning for the lgettext() function.

info()

"protected" の _info 変数を返します。

charset()

Return the encoding of the message catalog file.

output_charset()

Return the encoding used to return translated messages in lgettext() and lngettext().

set_output_charset(charset)

Change the encoding used to return translated messages.

install(names=None)

This method installs gettext() into the built-in namespace, binding it to _.

If the names parameter is given, it must be a sequence containing the names of functions you want to install in the builtins namespace in addition to _(). Supported names are 'gettext', 'ngettext', 'lgettext' and 'lngettext'.

この方法はアプリケーションで _() 関数を利用できるようにするための最も便利な方法ですが、唯一の手段でもあるので注意してください。この関数はアプリケーション全体、とりわけ組み込み名前空間に影響するので、地域化されたモジュールで _() を組み入れることができないのです。その代わりに、以下のコードを使って _() を使えるようにしなければなりません。:

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

この操作は _() をモジュール内だけのグローバル名前空間に組み入れるので、モジュール内の _() の呼び出しだけに影響します。

23.1.2.2. GNUTranslations クラス

gettext モジュールでは NullTranslations から派生したもう一つのクラス: GNUTranslations を提供しています。このクラスはビッグエンディアン、およびリトルエンディアン両方のバイナリ形式の GNU gettext .mo ファイルを読み出せるように _parse() を上書きしています。

GNUTranslations はまた、翻訳カタログ以外に、オプションのメタデータを読み込んで解釈します。GNU gettext では、空の文字列に対する変換先としてメタデータを取り込むことが慣習になっています。このメタデータは RFC 822 形式の key: value のペアになっており、 Project-Id-Version キーを含んでいなければなりません。キー Content-Type があった場合、 charset の特性値 (property) は "保護された" _charset インスタンス変数を初期化するために用いられます。値がない場合には、デフォルトとして None が使われます。エンコードに用いられる文字セットが指定されている場合、カタログから読み出された全てのメッセージ id とメッセージ文字列は、指定されたエンコードを用いて Unicode に変換され、そうでなければ ASCII エンコーディングとみなされます。

メッセージ id もユニコード文字列として解釈されるので、すべての *gettext() メソッドはメッセージ id をバイト文字列ではなくユニコード文字列と仮定するでしょう。

key/value ペアの集合全体は辞書型データ中に配置され、"保護された" _info インスタンス変数に設定されます。

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

class gettext.GNUTranslations

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

gettext(message)

Look up the message id in the catalog and return the corresponding message string, as a Unicode string. If there is no entry in the catalog for the message id, and a fallback has been set, the look up is forwarded to the fallback’s gettext() method. Otherwise, the message id is returned.

ngettext(singular, plural, n)

メッセージ id に対する複数形を検索します。カタログに対する検索では singular がメッセージ id として用いられ、 n にはどの複数形を用いるかを指定します。返されるメッセージ文字列は Unicode 文字列です。

If the message id is not found in the catalog, and a fallback is specified, the request is forwarded to the fallback’s ngettext() method. Otherwise, when n is 1 singular is returned, and plural is returned in all other cases.

以下に例を示します。:

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}
lgettext(message)
lngettext(singular, plural, n)

Equivalent to gettext() and ngettext(), but the translation is returned as a byte string encoded in the preferred system encoding if no encoding was explicitly set with set_output_charset().

警告

These methods should be avoided in Python 3. See the warning for the lgettext() function.

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

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

23.1.2.4. Catalog コンストラクタ

GNOME では、James Henstridge によるあるバージョンの gettext モジュールを使っていますが、このバージョンは少し異なった API を持っています。ドキュメントに書かれている利用法は:

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

となっています。過去のモジュールとの互換性のために、 Catalog() は前述の translation() 関数の別名になっています。

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

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

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

  1. プログラムやモジュールで翻訳対象とする文字列に特殊なマークをつけて準備します
  2. マークづけをしたファイルに一連のツールを走らせ、生のメッセージカタログを生成します
  3. 特定の言語へのメッセージカタログの翻訳を作成します
  4. メッセージ文字列を適切に変換するために gettext モジュールを使います

ソースコードを 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' はされていません。

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

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

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

生成された .po ファイルは翻訳者個々人へ頒布され、サポート対象の各自然言語への訳文が書き込まれます。 ある言語への飜訳が完了した <language-name>.po ファイルは翻訳者により返送され、 msgfmt を使い機械が読み込みやすい .mo バイナリカタログファイルへとコンパイルされます。 この .mogettext モジュールによる実行時の実際の飜訳処理で使われます。

gettext モジュールをソースコード中でどのように使うかは単一のモジュールを国際化するのか、それともアプリケーション全体を国際化するのかによります。次のふたつのセクションで、それぞれについて説明します。

23.1.3.1. モジュールを地域化する

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

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

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

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

アプリケーションを地域化するのなら、関数 _() をグローバルな組み込み名前空間に組み入れなければならず、これは通常アプリケーションの主ドライバ (main driver) ファイルで行います。この操作によって、アプリケーション独自のファイルは明示的に各ファイルで _() の組み入れを行わなくても単に _('...') を使うだけで済むようになります。

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

import gettext
gettext.install('myapplication')

ロケールの辞書を設定する必要がある場合、install() 関数に渡すことが出来ます:

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

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

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

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()

23.1.3.4. 翻訳処理の遅延解決

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

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 命令を実行するまで) 一時的に上書きすることができます。もしそれまでに _() をローカルな名前空間に持っていたら注意してください。

二つ目の例における _() の使い方では、パラメータが文字列リテラルではないので、 gettext プログラムが翻訳可能だとは判定されないことに注意してください。

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

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() という名前にしてしまっても構いません。

23.1.4. 謝辞

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

  • Peter Funk
  • James Henstridge
  • Juan David Ibáñez Palomar
  • Marc-André Lemburg
  • Martin von Löwis
  • François Pinard
  • Barry Warsaw
  • Gustavo Niemeyer

脚注

[1]標準でロケールが収められているディレクトリはシステム依存です; 例えば、RedHat Linux では /usr/share/locale ですが、 Solaris では /usr/lib/locale です。 gettext モジュールはこうしたシステム依存の標準設定をサポートしません; その代わりに sys.prefix/share/locale を標準の設定とします。この理由から、常にアプリケーションの開始時に絶対パスで明示的に指定して bindtextdomain() を呼び出すのが最良のやり方ということになります。
[2]上の bindtextdomain() に関する脚注を参照してください。