Updated: Fri Nov 19 09:41:41 2010
GUI のプログラムを作るので、メニューやメッセージに母語を ASCII 以外の文字を使用して表示したい。 また、ついったー のクライアントである以上、ついったーに入力可能な文字は表示、入力できるようにしたい。
ついったーの API は UTF-8 でエンコードされた Unicode の文字を受け付けるので、
データの文字コードセットとして Unicode を使用する。
Python 2.x は Unicode 文字列型のデータを扱えるのでこれを使う。
API 使用時には 'utf-8' コーデックを指定して encode(), decode() メソッドで変換する。
Python の Unicode 文字列型は内部 UTF-16 (16 bit) か UTF-32 (32 bit) かがビルド時に決定され、
Windows 版では内部 UTF-16 であるが BMP 外の文字に対しても内部ではサロゲートペアを使用して 2 文字として扱い、
UTF-8 への変換時には 4 バイトにエンコーディングするのでデータが失われることは無い。
また、WxPython は Unicode ビルドを使用する。
Unicode の文字列を画面に表示するためには適切なフォントを使用する必要がある。
プログラムで指定しないときに使用されるフォントは環境に依存するが、
必ずしも表示したい全ての文字を含んでいるとは限らない。
使用可能なフォントの選択肢も環境に依存するため、必要な文字を含むフォントをプログラムで固定で指定することはできない。
設定で使用するフォントを指定できるようにしてもデフォルトのフォントを決めることもできない。
Windows XP 日本語版の場合、システムのデフォルトで表示される日本語のフォントでは BMP 外の文字は表示できない。
「MSゴシック」に設定すれば BMP 外の「𠮟」を表示できるが、U+005C (REVERSE SOLIDUS)を円記号の字形で表示してしまう。
メニューやメッセージに自分の母語を表示するためには、ただ母語で書いた Unicode 文字列を WxPython のメソッドなり関数なりに渡せばよい。
しかし、それをソースコード中にハードコードしてしまうと他者が理解できなかったり環境により表示できなかったりし得る
これはソースコードを公開する目的に反する。
表示する文字列を言語で切り替えるために gettext モジュールを使う。
ソースコード上は _() 関数でくるんだ形で英語の文字列を書いておき、他言語のメッセージは翻訳をメッセージファイルに入れておく。
_() 関数は、LANG 環境変数の言語パートに対応するメッセージファイルがあれば
_() 関数に渡された文字列をキーにして対応するメッセージを検索して置き換える。
対応するメッセージファイルや文字列がなければ _() 関数に渡された文字列をそのまま表示する。
この方法で英語メッセージも登録できるのでソースコードに書く文字列は英語メッセージそのものである必要は無いが、
不都合が無い限り英語メッセージをそのまま使う。
英語メッセージと異なる文字列を記述する可能性があるのは、複数個所で英語メッセージは同じなのに他国語メッセージで異ならせたいことがある場合である。
_() 関数でくるんだ文字列は pygettext.py ユティリティを用いてメッセージファイルに取り出す。
pygettext.py ユティリティは、Windows の場合 Python のインストールディレクトリの下の Tools の下の i18n ディレクトリにある。
pygettext.py ユティリティで作成したメッセージファイルは言語ごとのディレクトリ locale/<lang>/LC_MESSAGES の下に .po というサフィクスでコピーし翻訳する。
翻訳に際しては、ファイル頭部の POT-Creation-Date, PO-Revision-Date, Last-Translator,
Language-Team, Content-Type, Content-Transfer-Encoding のパラメータの値を正しく設定する。
Unicode を使用するので Content-Type の charset を UTF-8 とし Content-Transfer-Encoding は 8bit とする。
翻訳したメッセージソースは msgfmt.py ユティリティで .mo サフィクスのメッセージファイルに変換する。
msgfmt.py ユティリティは pygettext.py ユティリティと同じディレクトリにある。
pygettext.py ユティリティはソースコードからメッセージを切り出すだけのプログラムで、
ソースコードの更新時に前回との差分だけを抽出したり翻訳メッセージファイルにマージしたり翻訳メッセージファイルの日付を管理したりはしない。
自力で管理するか別のツールを探す必要がある。
LANG 環境変数は <言語>_<地域>.<エンコーディング>の形で指定する。
Windows のコントロールパネルで言語と地域を指定してあっても環境変数での指定が必要である。
_() 関数を利用するためには gettext モジュールの install() 関数でインストールする。
install() すると _() は Python 組み込み名前空間にインストールされ、使用できるようになる。
ソースコード中に _() を定義したり import したりしている箇所が無いため pylint では Undefined variable として報告されるが問題ない。
install() の第 1 (domain) 引数にはプログラムの識別名としてメッセージファイル名のサフィクスを除いた部分を指定する。
第 2 (localedir) 引数には翻訳メッセージファイル用の <言語> ディレクトリを置くディレクトリを
第 3 (unicode) 引数には Unicode を使用する場合には True を指定する。
Python のライブラリで挙動がロカールに依存するものがある。
datetime モジュールの strftime(), strptime() メソッドが該当する。
Python の strftime() メソッドはプラットフォームの C ライブラリの strftime() 関数を呼ぶ
(ライブラリリファレンス 10 datetime -- 基本的な日付型および時間型 の 7 strftime() の振る舞い) が、
C の strftime() 関数はロカールの LC_TIME カテゴリーに依存する (JIS X3010:2003 プログラム言語 C (ISO/IEC 9899:1999) 5.23.3.5 strftime 関数) 。
strftime(), strptime() は ついったー API や HTTP ヘッダの時刻の生成や解析に使用するのでロカールの影響を受けないようにする必要がある。
UNIX 系の OS では、ロカールは LANG 環境変数で決まり、Python プログラム中では
locale モジュールの setlocale() 関数を使用して setlocale(LC_ALL, "") を呼ぶことにより有効となる。
これは、locale モジュールの getlocale() 関数を呼ぶことで確認できる。
setlocale(LC_ALL, "") の前は getlocale(LC_TIME) が (None, None) を返すが、
setlocale(LC_ALL, "") の後では LANG 環境変数の値を反映したタプルを返す。
Microsoft Windows XP の場合は、コントロールパネルの地域と言語の設定が反映される。
LANG 環境変数の値は影響しない。
他の Microsoft Windows の版でも OS の設定がロカールになることが期待される。
WxPython の場合、Linux では wx.App.__init__() メソッドの後で getlocale(LC_TIME) がロカールを反映した値を返すようになるが
Microsoft Windows では wx.App.__init__() メソッドの後でも getlocale(LC_TIME) は (None, None) を返す。
Linux で strftime(), strptime() がロカール依存しないようにするために setlocale(LC_TIME, 'C') を呼んでおく。
Microsoft Windows XP での例
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
C:\Documents and Settings\shota>set LANG=ja_JP.SJIS
C:\Documents and Settings\shota>echo %LANG%
ja_JP.SJIS
C:\Documents and Settings\shota>python
Python 2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from locale import getlocale, setlocale, LC_ALL, LC_TIME
>>> getlocale(LC_TIME)
(None, None)
>>> setlocale(LC_ALL, "")
'Japanese_Japan.932'
>>> getlocale(LC_TIME)
('Japanese_Japan', '932')
>>> setlocale(LC_TIME, (None, None))
'C'
>>> getlocale(LC_TIME)
(None, None)
>>> import wx
>>> a = wx.App(False)
>>> getlocale(LC_TIME)
(None, None)
>>> ^Z
C:\Documents and Settings\shota>
FreeBSD での例
% uname -sr
FreeBSD 8.1-RELEASE
% echo $LANG
ja_JP.eucJP
% python
Python 2.6.5 (r265:79063, Jun 7 2010, 09:23:15)
[GCC 4.2.1 20070719 [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
>>> from locale import setlocale, getlocale, LC_ALL, LC_TIME
>>> getlocale(LC_TIME)
(None, None)
>>> setlocale(LC_ALL, "")
'ja_JP.eucJP'
>>> getlocale(LC_TIME)
('ja_JP', 'eucJP')
>>> setlocale(LC_TIME , (None, None))
'C'
>>> getlocale(LC_TIME)
(None, None)
>>> import wx
>>> a = wx.App(False)
>>> getlocale(LC_TIME)
('ja_JP', 'eucJP')
>>> ^D
%