gettext – メッセージカタログ

目的:国際化(Internationalization)のためのメッセージカタログ API
利用できるバージョン:2.1.3 以上

gettext モジュールは GNU gettext ライブラリ互換なメッセージ翻訳とカタログ管理のためにピュア Python の実装を提供します。Python のソースと一緒に配布可能なツールは、ソースからメッセージを展開して、翻訳を含んだメッセージカタログを構築し、実行時にそのメッセージカタログを使用してユーザへ適切なメッセージを表示します。

メッセージカタログは、ユーザへ適切な言語のメッセージを表示するために、プログラムの国際化インタフェースを提供するために使用されます。さらに違うラッパーやパートナーのインタフェースの “スキン” を含めた、その他のカスタマイズされたメッセージを使用することもできます。

Note

標準ライブラリドキュメントでは、必要なものは全て Python で提供されているとありますが、おそらく適切なコマンドラインオプションを使用しても pygettext.pyungettext 呼び出しでラップされたメッセージの展開が拒否されることを私は発見しました。最終的に私は GNU gettext ツールをソースからインストールして代わりに xgettext を使用しました。

翻訳ワークフロー概要

設定と翻訳を使用する工程は5つの手順になります。

  1. 翻訳したいメッセージを含むコード内のリテラル文字列をマークアップする。

    翻訳する必要があるプログラムソース内のメッセージを識別することで始まります。そして、展開プログラムがそういったメッセージを見つけられるようにリテラル文字列をマークします。

  1. メッセージを展開する。

    プログラムソース内の翻訳可能な文字列を識別した後で、その翻訳可能な文字列を抜き出すために xgettext を使用して .pot ファイルか、翻訳テンプレートを作成します。そのテンプレートは全ての識別した文字列や翻訳のためのプレースフォルダのコピーとなるテキストファイルです。

  1. メッセージを翻訳する。

    翻訳者へ .pot ファイルのコピーを渡して、拡張子を .po に変更します。 .po ファイルはコンパイル時の入力として使用する編集可能なソースファイルです。翻訳者はファイル内のヘッダテキストを更新して全ての文字列の翻訳を提供します。

  1. 翻訳されたファイルからメッセージカタログを “コンパイル” する。

    翻訳者が全て翻訳した .po ファイルを返してきたら、 msgfmt を使用してその .po ファイルをバイナリのカタログフォーマットにコンパイルします。バイナリフォーマットは実行時のカタログ検査コードにより使用されます。

  1. 実行時に適切なメッセージカタログをアクティブ化して読み込む。

    最後の手順はアプリケーション設定のために数行追加して、メッセージカタログを読み込み、翻訳機能をインストールします。それを行うにはトレードオフの関係にある2つの方法があります。この後でそれぞれの方法を説明します。

もう少し詳細に触れながらこれらの手順を通してやってみましょう。コードに対して行う必要がある変更から始ます。

ソースコードからメッセージカタログを作成する

gettext はプログラム内に組み込まれた翻訳データベースのリテラル文字列を見つけることにより動作します。そして、適切な翻訳文字列を抜き出します。カタログへのアクセスやユニコード文字列を扱うかどうかで複数の関数があります。通常のパターンは、長い名前で何度も関数呼び出しを行ってコードの可読性が落ちないように、使用したい検査関数を _ という名前に束縛することです。

メッセージ展開プログラム xgettext はカタログ検査関数の呼び出しに組み込まれたメッセージを調べます。そのプログラムは違うソース言語を理解して、それぞれのために適切なパーサを使用します。もし検査関数のエイリアスを使用する、または拡張関数を追加する必要があるなら、メッセージを展開するときに追加のシンボル名を考慮するように xgettext へ与えることができます。

ここに1つのメッセージの翻訳準備が整った簡単なスクリプトがあります。

import gettext

# メッセージカタログへのアクセスを設定する
t = gettext.translation('gettext_example', 'locale', fallback=True)
_ = t.ugettext

print _('This message is in the script.')

このケースでは、検査関数のユニコード文字列バージョン ugettext() を使用しています。 "This message is in the script." というテキストはそのカタログから置き換えられるメッセージです。フォールバックモードを有効にしたので、メッセージカタログ無しでこのスクリプトを実行すると、インラインメッセージを表示します。

$ python gettext_example.py
This message is in the script.

次の手順は pygettext.py でメッセージを展開して .pot ファイルを作成します。

$ xgettext -d gettext_example -o gettext_example.pot gettext_example.py

その出力ファイルは次のようになります。

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-17 11:32-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: gettext_example.py:16
msgid "This message is in the script."
msgstr ""

メッセージカタログは ドメイン言語 で構成されたディレクトリ内へインストールされます。ドメインは通常アプリケーションの名前のようなユニークな値です。このケースでは gettext_example を使用しました。言語の値は実行時にユーザ環境から、その設定やプラットホームに依存する LANGUAGE, LC_ALL, LC_MESSAGES または LANG といった環境変数の1つを通して提供されます。私の言語は en_US にセットされているので、この後のサンプルは全てその設定を使用します。

いまテンプレートができました。次の手順は必要なディレクトリ階層を作成して、正しい場所へテンプレートをコピーします。ここではメッセージカタログディレクトリのルートとして PyMOTW ソースツリー内部の locale ディレクトリを使用します。しかし、通常はシステム全体からアクセスできるディレクトリを使いたくなるでしょう。そのカタログの入力ソースのフルパスは $localedir/$language/LC_MESSAGES/$domain.po です。そして、実際のカタログのファイル名の拡張子は .mo です。

私の設定では gettext_example.potlocale/en_US/LC_MESSAGES/gettext_example.po にコピーする必要があります。そして、そのコピーしたファイルを編集してヘッダの値を変更し、置き換えるメッセージを追加します。その結果は次のようになります。

# Messages from gettext_example.py.
# Copyright (C) 2009 Doug Hellmann
# Doug Hellmann <doug.hellmann@gmail.com>, 2009.
#
msgid ""
msgstr ""
"Project-Id-Version: PyMOTW 1.92\n"
"Report-Msgid-Bugs-To: Doug Hellmann <doug.hellmann@gmail.com>\n"
"POT-Creation-Date: 2009-06-07 10:31+EDT\n"
"PO-Revision-Date: 2009-06-07 10:31+EDT\n"
"Last-Translator: Doug Hellmann <doug.hellmann@gmail.com>\n"
"Language-Team: US English <doug.hellmann@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"


#: gettext_example.py:16
msgid "This message is in the script."
msgstr "This message is in the en_US catalog."

そのカタログは msgformat を使用して .po ファイルから作成されます。

$ cd locale/en_US/LC_MESSAGES/; msgfmt -o gettext_example.mo gettext_example.po

さて、スクリプトを実行すると、インラインの文字列ではなくカタログからのメッセージが表示されます。

$ python gettext_example.py
This message is in the en_US catalog.

実行時にメッセージカタログを見つける

前のセクションで説明したように、メッセージカタログを含む ロケールディレクトリ は、プログラムの ドメイン 向けに名付けられたカタログと共に言語に基づいて構成されます。オペレーティングシステムによって違う独自のデフォルト値を定義しますが、 gettext はそういった全てのデフォルト値が分かりません。それはデフォルトのロケールディレクトリ sys.prefix + '/share/locale' を使用しますが、ほとんどの場合、このデフォルトが有効かどうかに依存するよりも明示的に localedir の値を必ず指定する方が安全です。

そのパスの言語の一部は地域化の機能(LANGUAGE, LC_ALL, LC_MESSAGESLANG)を設定するために使用される複数の環境変数の1つから取得されます。最初の変数がセットされていれば、その値が使用されます。複数の言語はコロン (:) で区切られることにより選択できます。2番目のメッセージカタログを作成して少し実験することで、複数の言語を扱う方法を解説します。

$ cd locale/en_CA/LC_MESSAGES/; msgfmt -o gettext_example.mo gettext_example.po
$ python gettext_find.py
Catalogs: ['locale/en_US/LC_MESSAGES/gettext_example.mo']
$ LANGUAGE=en_CA python gettext_find.py
Catalogs: ['locale/en_CA/LC_MESSAGES/gettext_example.mo']
$ LANGUAGE=en_CA:en_US python gettext_find.py
Catalogs: ['locale/en_CA/LC_MESSAGES/gettext_example.mo', 'locale/en_US/LC_MESSAGES/gettext_example.mo']
$ LANGUAGE=en_US:en_CA python gettext_find.py
Catalogs: ['locale/en_US/LC_MESSAGES/gettext_example.mo', 'locale/en_CA/LC_MESSAGES/gettext_example.mo']

find() は完全なカタログのリストを表示しますが、そのシーケンスの1番目のカタログのみが実際にメッセージを検査するために読み込まれます。

$ python gettext_example.py
This message is in the en_US catalog.
$ LANGUAGE=en_CA python gettext_example.py
This message is in the en_CA catalog.
$ LANGUAGE=en_CA:en_US python gettext_example.py
This message is in the en_CA catalog.
$ LANGUAGE=en_US:en_CA python gettext_example.py
This message is in the en_US catalog.

複数形の値

単純なメッセージの置き換えが翻訳の要求の大半を扱う一方、 gettext は特別なケースとして複数形を扱います。言語によって、メッセージの単数形と複数形との違いは1つの単語の最後が変化するか、全体の文の構造が違うかに分かれます。さらに 複数形のレベルにより違う形 になるかもしれません。(できるだけ)簡単に複数形を管理するために、メッセージの複数形を尋ねるように分離された関数セットがあります。

from gettext import translation
import sys

t = translation('gettext_plural', 'locale', fallback=True)
num = int(sys.argv[1])
msg = t.ungettext('%(num)d means singular.', '%(num)d means plural.', num)

# まだそのメッセージのために値を追加する必要がある
print msg % {'num':num}
$ xgettext -L Python -d gettext_plural -o gettext_plural.pot gettext_plural.py

翻訳されて置き換えられるので、その置き換えられる文字列は1つの配列にリストされます。配列を使用することで、複数形による言語を翻訳ができるようになります(例えば ポーランド語は相対量で違う形になります)。

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-02-17 11:32-0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: gettext_plural.py:15
#, python-format
msgid "%(num)d means singular."
msgid_plural "%(num)d means plural."
msgstr[0] ""
msgstr[1] ""

翻訳文字列を書き込んでいくことに加えて、そのライブラリは与えられた数値で配列内にインデックス化する方法を知っているので、複数形の翻訳文字列も記入する必要があります。 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" という行は手動で置き換える2つの値を含みます。 nplurals は配列のサイズを指す整数(使用される翻訳文字列の数)で、 plural はその翻訳を検査するときに配列のインデックスに対する入力値を変換するための C 言語拡張です。リテラル文字列 nungettext() へ渡された値で置き換えられます。

例えば、英語は2つの複数形があります。 0 は複数形として扱われます(“0 bananas”)。複数形のエントリは次のようになります。

Plural-Forms: nplurals=2; plural=n != 1;

単数形の翻訳は0のインデックスで複数形の翻訳は1のインデックスになります。

# Messages from gettext_plural.py
# Copyright (C) 2009 Doug Hellmann
# This file is distributed under the same license as the PyMOTW package.
# Doug Hellmann <doug.hellmann@gmail.com>, 2009.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PyMOTW 1.92\n"
"Report-Msgid-Bugs-To: Doug Hellmann <doug.hellmann@gmail.com>\n"
"POT-Creation-Date: 2009-06-14 09:29-0400\n"
"PO-Revision-Date: 2009-06-14 09:29-0400\n"
"Last-Translator: Doug Hellmann <doug.hellmann@gmail.com>\n"
"Language-Team: en_US <doug.hellmann@gmail.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;"

#: gettext_plural.py:15
#, python-format
msgid "%(num)d means singular."
msgid_plural "%(num)d means plural."
msgstr[0] "In en_US, %(num)d is singular."
msgstr[1] "In en_US, %(num)d is plural."

そのカタログをコンパイルした後で数回テストスクリプトを実行すると、N の値の違いで翻訳文字列のインデックスに対して変換されることが分かります。

$ cd locale/en_US/LC_MESSAGES/; msgfmt -o gettext_plural.mo gettext_plural.po
$ python gettext_plural.py 0
In en_US, 0 is plural.
$ python gettext_plural.py 1
In en_US, 1 is singular.
$ python gettext_plural.py 2
In en_US, 2 is plural.

アプリケーション対モジュールの地域化(Localization)

翻訳の成果のスコープはコード内で gettext 関数をインストールして使用する方法を定義します。

アプリケーションの地域化(Localization)

アプリケーション全体の翻訳のために、 __builtins__ 名前空間をグローバルに使用して ungettext() のような関数をインストールするためにアクセスされます。その理由はアプリケーションのコードのトップレベルで制御するからです。

import gettext
gettext.install('gettext_example', 'locale', unicode=True, names=['ngettext'])

print _('This message is in the script.')

install() 関数は __builtins__ 名前空間の _() という名前に gettext() を束縛します。さらに ngettext()names にリストされたその他の関数も追加します。 unicode が True の場合、その関数のユニコード文字列バージョンがデフォルトの ASCII バージョンの代わりに使用されます。

モジュールの地域化(Localization)

ライブラリまたは個別のモジュールのために __builtins__ を変更することは良い考えではありません。その理由は何が競合してアプリケーションのグローバルな値を生成するか分からないからです。モジュールの上部で手動で翻訳関数の名前を再束縛するか、インポートすることができます。

import gettext
t = gettext.translation('gettext_example', 'locale', fallback=True)
_ = t.ugettext
ngettext = t.ungettext

print _('This message is in the script.')

See also

gettext
本モジュールの標準ライブラリドキュメント
locale
他の国際化ツール
GNU gettext
メッセージカタログフォーマット、API 等があります。このモジュールの全ては GNU のオリジナル gettext パッケージに基づいています。カタログファイルフォーマットは互換性があり、コマンドラインスクリプトも(全く同じでない場合は)よく似たオプションを持ちます。 GNU gettext マニュアル にファイルフォーマットの詳細とカタログファイルを扱う GNU ツールの説明があります。
Internationalizing Python
Martin von Löwis が Python アプリケーションの国際化のテクニックについて書いた論文です。
Django Internationalization
実際のアプリケーションで gettext を使用するときの良い情報源です。
Bookmark and Share