python translationstringでlocalizationに対応したメッセージを作る
localeによってエラーメッセージなど出し分けたい場合がある。
そのような時にどうすれば良いのか分かっていなかったので調べた。
translationstring
translationstring を使う。
これはpylons project関連のpackageでlocalizationのために使われている模様。
一般的な翻訳の際によく使われているgettextの仕組みを利用しているらしい。
python側コード
日本語localeでは「失敗」。それ以外では「failure」と表示する文字列を考える。
表記する場所にはtranslationstring.TranslationStringFactoryで生成した関数を使えば良いらしい。
値を変換するにはTranslator Objectで包む。
以下のコードの他に所定の位置に.moファイルが必要。
translationstring_example.py
# -*- coding:utf-8 -*-
import gettext
import translationstring
# .poファイルの生成ツールと上手く連携させるために_という名前にするのが良い
_ = translationstring.TranslationStringFactory('sample')
lang = gettext.translation("sample", "./locale", languages=["ja"], codeset="utf8")
translator = translationstring.Translator(lang)
print(translator(_("failure")))
.moファイルの生成
.moファイルはgettextで使われるフォーマット。.poファイルからmsgfmtを使ってコマンドを使って生成する。
.moファイルの前に.poファイルを生成する。これにはpot-createを利用する。
linguaのinstall
linguaというパッケージが.poファイルを生成してくれる。
ligua自身はbabelを必要としないがpot-createを実行しようとした際にbabelが要求された。
pip install lingua babel
.poファイルの生成
linguaをインストールするとpot-createが使えるようになる。所定の位置に.poファイルを生成する
mkdir -p locale/ja/LC_MESSAGES/
pot-create translation_example.py -o locale/ja/LC_MESSAGES/sample.po
生成したsample.poを書き換えるファイル名はTranslationStringFactoryに渡したドメイン名と同じにするのが良い。
以下の様に書き換える。本来は翻訳者の名前などの部分も書き換えた方が良い。今回は末尾の部分を書き換えただけ
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2014-06-12 15:37+09:00\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"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: ./translation_example.py:11
#, c-format
msgid "failure"
msgstr "失敗値"
差分は以下のとおり
diff --git a/locale/ja/LC_MESSAGES/sample.po b/locale/ja/LC_MESSAGES/sample.po
index 2559ffb..db040c4 100644
--- a/locale/ja/LC_MESSAGES/sample.po
+++ b/locale/ja/LC_MESSAGES/sample.po
@@ -18,4 +18,4 @@ msgstr ""
#: ./translation_example.py:11
#, c-format
msgid "failure"
-msgstr ""
+msgstr "失敗値"
.moファイルの作成
.moファイルの作成にはmsgfmtを使う。
msgfmt locale/ja/LC_MESSAGES/sample.po -o locale/ja/LC_MESSAGES/sample.mo
$ tree
.
├── locale
│ └── ja
│ └── LC_MESSAGES
│ ├── sample.mo
│ └── sample.po
├── requirements.txt
└── translation_example.py
3 directories, 4 files
これで"failure"が"失敗値"に翻訳されて表示される。
$ python translation_example.py
失敗値
ja/LC_MESSAGES/sample.moの場所 について
gettextはlocales情報を見て、翻訳する際に利用するマッピングを求めている。
LANG="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_CTYPE="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_ALL=
上手く.moファイルを読み込むことができているか調べるにはgettext.findを利用すると良さそう。
# -*- coding:utf-8 -*-
import gettext
print(gettext.find("sample", "./locale",["ja"]))
# ./locale/ja/LC_MESSAGES/sample.mo
.poファイルをどこに作るかというのはgettext.findのコードを見てみれば良い。
gettext.py
# Locate a .mo file using the gettext strategy
def find(domain, localedir=None, languages=None, all=False):
# Get some reasonable defaults for arguments that were not supplied
if localedir is None:
localedir = _default_localedir
if languages is None:
languages = []
for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
val = os.environ.get(envar)
if val:
languages = val.split(':')
break
if 'C' not in languages:
languages.append('C')
# now normalize and expand the languages
nelangs = []
for lang in languages:
for nelang in _expand_lang(lang):
if nelang not in nelangs:
nelangs.append(nelang)
# select a language
if all:
result = []
else:
result = None
for lang in nelangs:
if lang == 'C':
break
mofile = os.path.join(localedir, lang, 'LC_MESSAGES', '%s.mo' % domain)
if os.path.exists(mofile):
if all:
result.append(mofile)
else:
return mofile
return result
大雑把に('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG')の中から"ja"などのlanguagesを取得し、
localedir/ja/LC_MESSAGS/以下の.moファイルから値を取り出そうしていることが分かる。
引数を取る文字列の翻訳について
translationstringは引数を取る文章の翻訳も可能。
msg2 = _("add-number", default="Add ${number}", mapping={"number": 1})
lang = gettext.translation("sample", "./locale", languages=["ja"], codeset="utf8")
translator = translationstring.Translator()
print(translator(_("add-number", mapping={"number": 10})))
# 10を足してください
この時.poファイルには以下の様に.プレースホルダーを${}で囲んで書く。
msgid "add-number"
msgstr "${number}を足してください"