[Blender] Blenderスクリプトを多言語対応させる方法

Blenderスクリプトの多言語対応の方法について、Blenderのドキュメントが分かりづらいとTwitterで少し話題になりました。
色々調べた結果なんとか多言語対応できたので、サンプル付きで紹介します。

# サンプル

多言語対応のサンプルを紹介します。
今回はシンプルに以下の2つの言語に対応してみました。

* 英語
* 日本語

```py:translation.py
import bpy

bl_info = {
    "name" : "Translation Test",
    "author" : "Nutti",
    "version" : (0, 1),
    "blender" : (2, 7, 0),
    "location" : "UV > Translation Test",
    "description" : "Translation Test",
    "warning" : "",
    "wiki_url" : "",
    "tracker_url" : "",
    "category" : "UV"
}

# 翻訳用辞書
translation_dict = {
    "en_US" :
        {("*", "Test: ") : "Test: %d"},
    "ja_JP" :
        {("*", "Test: ") : "てすと: %d"}
}


class TranslationTest(bpy.types.Operator):
    bl_idname = "uv.translation_test"
    bl_label = "Translation Test"
    bl_description = "Translation Test"
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        num = 50
        # 翻訳結果を通知バーに表示
        self.report({'INFO'}, bpy.app.translations.pgettext("Test: ") % (num))
        return {'FINISHED'}


def menu_func(self, context):
    self.layout.separator()
    self.layout.operator(TranslationTest.bl_idname)


def register():
    bpy.utils.register_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.append(menu_func)
    bpy.app.translations.register(__name__, translation_dict)   # 辞書の登録


def unregister():
    bpy.app.translations.unregister(__name__)   # 辞書の削除
    bpy.utils.unregister_module(__name__)
    bpy.types.VIEW3D_MT_uv_map.remove(menu_func)


if __name__ == "__main__":
    register()

```

# 使い方

インストールの方法は以下に従って行なってください。
[Blender Wiki](http://wiki.blender.org/index.php/Doc:JA/2.6/Manual/Extensions/Python/Add-Ons)

1. Blenderを日本語化します
2. Editモードで'U'キーを押して表示されるメニューから、「Translation Test」を実行します
3. 通知バーに「テスト: 50」と表示されることを確認します
4. Blenderを英語化します
5. Editモードで'U'キーを押して表示されるメニューから、「Translation Test」を実行します
6. 通知バーに「Test: 50」と表示されることを確認します

## Blenderの言語を変更する方法

Blenderの言語を変更するには以下が参考になるでしょう。
[blender.jp](blender.jp/modules/docs/index.php?content_id=2)

# サンプルの解説

基本的な部分については、[\[Blender\] Blenderプラグインの作り方](http://qiita.com/nutti/items/a836391723bd28cd3e4c)を参照してください。
今回は多言語対応の部分について集中して解説していきます。

## 翻訳用辞書の定義

翻訳用に辞書を定義します。
辞書の形式は以下の通りです。

```py
{locale: {(context, key): translated_str, ...}, ...}
```

それぞれのパラメータを以下に示します。

|パラメータ|意味|
|---|---|
|locale|ロケール<br>日本語なら **jp_JP** 、英語なら **en_EN** となります。<br>ロケールに指定する文字列は下に記載しています。|
|context|実行されるコンテキスト(？)<br>とりあえず **"*"** にしておけば問題ないでしょう。|
|key|翻訳時に指定するキー文字列<br>**デフォルトで表示したい文字列**を指定すると良いでしょう。<br>個人的には **文字化けしない英語** がお勧めです。|
|translated_str|現在のロケールが `locale` と同じで、 `key` が指定された時に表示される文字列です。<br>この部分に **翻訳後の文字列を指定** します。<br>指定した `locale` と `key` が存在しない場合は、 **translated_keyに指定した文字列が表示** されます。|

今回のサンプルでは、以下のように辞書を定義しました。



|言語|表示|
|---|---|
|英語|Test: 50|
|日本語|てすと: 50|
|その他|Test: 50|

```py
translation_dict = {
    "en_US" :
        {("*", "Test: ") : "Test: %d"},
    "ja_JP" :
        {("*", "Test: ") : "てすと: %d"}
}
```

### 豆知識: ロケールの取得方法

ロケールとは言っても何を指定したらよいかわからない方もいるでしょう。
そんな時はBlenderに備わっているPython Consoleから以下を実行してみましょう。
Blenderの現在のロケールを調べることができます。

```py
>>> bpy.app.translations.locale
'en_US'
```

## 翻訳用辞書の登録

翻訳用の辞書登録はregister関数内で以下を実行します。

```py
bpy.app.translations.register(__name__, translation_dict)
```

## 翻訳の実行

指定した`key`から翻訳された文字列を取得するためには以下のように、`bpy.app.translations.pgettext`を使います。

```py
bpy.app.translations.pgettext("Test: ") 
```

もし **文字列フォーマット** を使用したいのであれば、`bpy.app.translations.pgettext_iface`を使います。

```py
bpy.app.translations.pgettext("Test: ") % (num)
```

## 翻訳用辞書の削除

登録した翻訳用の辞書はアドオンを停止した時に解放する必要があります。
辞書の登録解除は、unregister関数内で以下を実行します。

```py
bpy.app.translations.unregister(__name__)
```

# おわりに

Blenderスクリプトを多言語対応する方法を示しました。
スクリプトを多言語対応させることで、対応させた言語分だけスクリプトを使ってもらえる可能性が高くなります。
Blenderは英語圏での利用が未だに多い状況ですので、日本語向けにスクリプトを作っていた人はこの機会に英語もサポートしてみてはいかがでしょうか。
**英語をサポートすることで海外の人からフィードバックが来て、知り合いになれる** かもしれませんよ。

# 参考情報
* [Blender 2.69.1 - API documentation-](www.blender.org/api/blender_python_api_2_69_1/bpy.app.translations.html)
* [Blender翻訳を実際に行っているプロジェクト](http://code.metager.de/source/xref/blender/release/scripts/addons/render_copy_settings/)