python translationstringでlocalizationに対応したメッセージを作る

# python translationstringでlocalizationに対応したメッセージを作る

localeによってエラーメッセージなど出し分けたい場合がある。
そのような時にどうすれば良いのか分かっていなかったので調べた。

## translationstring

[translationstring](https://pypi.python.org/pypi/translationstring) を使う。
これはpylons project関連のpackageでlocalizationのために使われている模様。
一般的な翻訳の際によく使われているgettextの仕組みを利用しているらしい。

### python側コード

日本語localeでは「失敗」。それ以外では「failure」と表示する文字列を考える。

表記する場所にはtranslationstring.TranslationStringFactoryで生成した関数を使えば良いらしい。
値を変換するにはTranslator Objectで包む。

以下のコードの他に所定の位置に.moファイルが必要。

translationstring_example.py

```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](https://pypi.python.org/pypi/lingua)というパッケージが.poファイルを生成してくれる。
ligua自身はbabelを必要としないがpot-createを実行しようとした際にbabelが要求された。

```bash
pip install lingua babel
```

#### .poファイルの生成

linguaをインストールするとpot-createが使えるようになる。所定の位置に.poファイルを生成する

```bash
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
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を使う。

```bash
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情報を見て、翻訳する際に利用するマッピングを求めている。
```bash
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を利用すると良さそう。

```py
# -*- coding:utf-8 -*-
import gettext
print(gettext.find("sample", "./locale",["ja"]))
# ./locale/ja/LC_MESSAGES/sample.mo
```

.poファイルをどこに作るかというのはgettext.findのコードを見てみれば良い。

gettext.py

```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は引数を取る文章の翻訳も可能。

```py
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}を足してください"
```