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, /)

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.

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 モジュールを使って、メッセージ文字列が適切に翻訳されるようにします。

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

脚注