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

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


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

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

GNU gettext API

gettext モジュールでは、以下の GNU gettext API に非常に良く似た API を提供しています。 この API を使う場合、アプリケーション全体の翻訳に影響します。 アプリケーションが単一の言語しか扱わず、ユーザのロケールに従って言語が選ばれるのなら、たいていはこの API が求めているものです。 Python モジュールを地域化していたり、アプリケーションの実行中に言語を切り替える必要がある場合は、この API ではなくおそらくクラス形式の API を使いたくなるでしょう。

gettext.bindtextdomain(domain, localedir=None)

domain をロケールディレクトリ localedir に対応付けます。 具体的には、 gettext は与えられたドメインに対するバイナリ形式の .mo ファイルを探しに、(Unixでは) localedir/language/LC_MESSAGES/domain.mo というパスを見に行きます。 ここで language はそれぞれ環境変数 LANGUAGELC_ALLLC_MESSAGESLANG の中から検索されます。

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

gettext.textdomain(domain=None)

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

gettext.gettext(message)

Return the localized translation of message, based on the current global domain, language, and locale directory. This function is usually aliased as _() in the local namespace (see examples below).

gettext.dgettext(domain, message)

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

gettext.ngettext(singular, plural, n)

gettext() と同様ですが、複数形を考慮しています。 翻訳が見つかった場合、複数形の選択公式を n に適用し、その結果得られたメッセージを返します (言語によっては二つ以上の複数形があります)。 翻訳が見つからなかった場合、 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)

Similar to the corresponding functions without the p in the prefix (that is, gettext(), dgettext(), ngettext(), dngettext()), but the translation is restricted to the given message context.

バージョン 3.8 で追加.

Note that GNU gettext also defines a dcgettext() method, but this was deemed not useful and so it is currently unimplemented.

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

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

クラス形式の 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 GNUTranslations class which implements the parsing of GNU .mo format files, and has methods for returning strings. Instances of this 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)

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 class_ if provided, otherwise GNUTranslations. The class's constructor must take a single file object argument.

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

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

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

バージョン 3.11 で変更: codeset parameter is removed.

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

This installs the function _() in Python's builtins namespace, based on domain and localedir which are passed to the function translation().

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

As seen below, you usually mark the strings in your application that are candidates for translation, by wrapping them in a call to the _() function, like this:

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

For convenience, you want the _() function to be installed in Python's builtins namespace, so it is easily accessible in all modules of your application.

バージョン 3.11 で変更: names is now a keyword-only parameter.

NullTranslations クラス

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

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

ngettext(singular, plural, n)

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

pgettext(context, message)

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

バージョン 3.8 で追加.

npgettext(context, singular, plural, n)

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

バージョン 3.8 で追加.

info()

Return a dictionary containing the metadata found in the message catalog file.

charset()

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

install(names=None)

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

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', 'pgettext', and 'npgettext'.

Note that this is only one way, albeit the most convenient way, to make the _() function available to your application. Because it affects the entire application globally, and specifically the built-in namespace, localized modules should never install _(). Instead, they should use this code to make _() available to their module:

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

This puts _() only in the module's global namespace and so only affects calls within this module.

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

GNUTranslations クラス

The gettext module provides one additional class derived from NullTranslations: GNUTranslations. This class overrides _parse() to enable reading GNU gettext format .mo files in both big-endian and little-endian format.

GNUTranslations parses optional metadata out of the translation catalog. It is convention with GNU gettext to include metadata as the translation for the empty string. This metadata is in RFC 822-style key: value pairs, and should contain the Project-Id-Version key. If the key Content-Type is found, then the charset property is used to initialize the "protected" _charset instance variable, defaulting to None if not found. If the charset encoding is specified, then all message ids and message strings read from the catalog are converted to Unicode using this encoding, else ASCII is assumed.

Since message ids are read as Unicode strings too, all *gettext() methods will assume message ids as Unicode strings, not byte strings.

The entire set of key/value pairs are placed into a dictionary and set as the "protected" _info instance variable.

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

カタログから contextmessage id を検索して、対応するメッセージ文字列を、 Unicode でエンコードして返します。 message id と context に対するエントリがカタログに存在せず、フォールバックが設定されている場合、フォールバック検索はオブジェクトの pgettext() メソッドに転送されます。そうでない場合、 message id 自体が返されます。

バージョン 3.8 で追加.

npgettext(context, singular, plural, n)

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

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

バージョン 3.8 で追加.

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

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

Catalog コンストラクタ

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

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

For compatibility with this older module, the function Catalog() is an alias for the translation() function described above.

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

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

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

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

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

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

  4. メッセージ文字列を適切に変換するために gettext モジュールを使います

In order to prepare your code for I18N, you need to look at all the strings in your files. Any string that needs to be translated should be marked by wrapping it in _('...') --- that is, a call to the function _. For example:

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

モジュールを地域化する

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

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

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

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

If you are localizing your application, you can install the _() function globally into the built-in namespace, usually in the main driver file of your application. This will let all your application-specific files just use _('...') without having to explicitly install it in each file.

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

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

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

This works because the dummy definition of _() simply returns the string unchanged. And this dummy definition will temporarily override any definition of _() in the built-in namespace (until the del command). Take care, though if you have a previous definition of _() in the local namespace.

Note that the second use of _() will not identify "a" as being translatable to the gettext program, because the parameter is not a string literal.

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

def N_(message): return message

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

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

In this case, you are marking translatable strings with the function N_(), which won't conflict with any definition of _(). However, you will need to teach your message extraction program to look for translatable strings marked with N_(). xgettext, pygettext, pybabel extract, and xpot all support this through the use of the -k command-line switch. The choice of N_() here is totally arbitrary; it could have just as easily been MarkThisStringForTranslation().

謝辞

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

  • Peter Funk

  • James Henstridge

  • Juan David Ibáñez Palomar

  • Marc-André Lemburg

  • Martin von Löwis

  • François Pinard

  • Barry Warsaw

  • Gustavo Niemeyer

脚注