倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

はじめての幻獣 Griffon 研 (20) : 国際化 install-plugin i18n

今回は id:kimukou_26 さんにコメント頂いた「I18n Plugin」をいじってみました(一覧)。

I18n プラグインは Spring の MessageSource クラスを使ってメッセージの国際化*1をするプラグインです。 正直、リソースバンドルとかプロパティファイルってエンコーディング等々が面倒なのであんまり使ったことないんですよねぇ。 なので、以下の説明は「とりあえず動く」ってレベルだと思ってくださいな。

  • I18n プラグインのインストール
  • リソース・バンドルの配置
  • リソース・バンドルのサンプル
  • MessageSource クラス
  • MessageSource オブジェクトの使い方

I18n プラグインのインストール


まずは I18n プラグインのインストール。 普通のプラグインのインストールと同じです。

griffon install-plugin i18n

リソース・バンドルの配置


次はリソース・バンドルの配置。 配置するフォルダは「$basedir/griffon-app/i18n」フォルダです。 このフォルダ下に「messages_《言語》.properties」という名前のプロパティ・ファイルを配置します。 例えば日本語 (ja) に対応するメッセージを定義したい場合は

  • $basedir/griffon-app/i18n/messages_ja.properties (日本語 (ja) のメッセージ)

というファイルを作成します。 通常のリソース・バンドルのように国を指定することもできます*2。 デフォルトで読み込まれる(対応する言語がない場合に読み込まれる)リソース・バンドルを定義するには、同フォルダ下に「messages.properties」ファイルを定義します。

  • $basedir/griffon-app/i18n/messages.properties (対応する言語がない場合のメッセージ)

I18n Plugin のドキュメントにはファイル名の「messages」の部分 (basename) を変更する方法が書かれていますが*3、イマイチうまくいかなかったのでここではスルー。

リソース・バンドルのサンプル


では、「関数描画アプリケーション」で使うリソース・バンドルのサンプルを定義してみましょう。 国際化を行うのは以下のものとします:

  • アプリケーション・フレームのタイトル
  • メニュー
  • Controller.showAbout で表示されるメッセージ

メニューアイテム(「関数描画アプリケーション」ではアクションの名前)などは変更していませんが、やり方は同じです。

messages_ja.properties

まずは日本語に対応するリソース・バンドル。 ファイルは

  • FunctionPlotter/griffon-app/i18n/messages_ja.properties

で、内容はこんな感じにしましょう:

app.title=関数描画アプリケーション
app.menu.action=アクション (A)
app.menu.laf=ルック&フィール (L)
app.menu.help=ヘルプ (H)
controller.showAbout.message=倭マン日記「はじめての Griffon 研」にて\nGriffon アプリケーションの例として作成した\n『関数描画アプリケーション』です。\n\nGroovy バージョン \: {0}\nGriffon バージョン \: {1}\nアプリケーション バージョン \: {2}

通常のリソース・バンドルと異なるのは controller.showAbout.message にある {《数字》} の部分です。 この部分はプレースホルダーになっていて、メッセージの呼び出し時に値を挿入することができます。 うむ、便利。 ただし、プロパティ・ファイルで日本語を扱いたい場合はエンコードしないといけなくて(しかも何故か ISO-8859-1)、実際には以下のようなファイルにしないとイケマセン:

app.title=\u95A2\u6570\u63CF\u753B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3
app.menu.action=\u30A2\u30AF\u30B7\u30E7\u30F3 (A)
app.menu.laf=\u30EB\u30C3\u30AF\uFF06\u30D5\u30A3\u30FC\u30EB (L)
app.menu.help=\u30D8\u30EB\u30D7 (H)
controller.showAbout.message=\u502D\u30DE\u30F3\u65E5\u8A18\u300C\u306F\u3058\u3081\u3066\u306E Griffon \u7814\u300D\u306B\u3066\nGriffon \u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u306E\u4F8B\u3068\u3057\u3066\u4F5C\u6210\u3057\u305F\n\u300E\u95A2\u6570\u63CF\u753B\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3\u300F\u3067\u3059\u3002\n\nGroovy \u30D0\u30FC\u30B8\u30E7\u30F3 \: {0}\nGriffon \u30D0\u30FC\u30B8\u30E7\u30F3 \: {1}\n\u30A2\u30D7\u30EA\u30B1\u30FC\u30B7\u30E7\u30F3 \u30D0\u30FC\u30B8\u30E7\u30F3 \: {2}

やっぱりプロパティ・ファイル嫌い。 IDE を使えばなんてこともないかも知れませんが、次回にちょっと改善策を。

messages.properties

次は対応する言語がない場合に使用されるメッセージ。 ファイルは

  • FunctionPlotter/griffon-app/i18n/messages.properties

で、内容はこんな感じ:

app.title=Function Plotter
app.menu.action=Action
app.menu.laf=Look & Feel
app.menu.help=Help
controller.showAbout.message=A Function Plotter\nthat serves as a Griffon example for\n"Waman Diary"\n\nGroovy Version \: {0}\nGriffon Version \: {1}\nApplication Version \: {2}

MessageSource クラス


I18n プラグインのキークラス、MessageSource クラス。 このクラスには getMessage() メソッドがあれこれオーバーロードされています:

getMessage(String code)    // kimukou_26 さんにご指摘頂きました。

getMessage(String code, Object[] args)
getMessage(String code, List args)

getMessage(String code, Object[] args, String defaultMessage)
getMessage(String code, List args, String defaultMessage)

getMessage(String code, Object[] args, Locale locale)
getMessage(String code, List args, Locale locale)

getMessage(String code, Object[] args, String defaultMessage, Locale locale)
getMessage(String code, List args, String defaultMessage, Locale locale)

getMessage(MessageSourceResolvable resolvable)
getMessage(MessageSourceResolvable resolvable, Locale locale)

引数の大雑把な説明は下表の通り:

引数 必須 デフォルト値 説明
code String Yes - リソース・バンドルのキー文字列
args Object[]
List
No [] {《数字》}の部分に挿入するオブジェクト
defaultMessage String No デフォルトのメッセージ
locale java.util.
Locale
No Locale.getDefault() メッセージを取得する際に使用するロケール

MessageSourceResolvable オブジェクトの引数はスルー:-9

MessageSource オブジェクトの使い方


それでは、満を持しての(?) MessageSource オブジェクトの使い方。 View と Controller で別々に見ていきます。

View で使う

View では messageSource プロパティ(もちろん MessageSource オブジェクトが返される)が追加されていて、このオブジェクトの getMessage() メソッドを呼び出すことでメッセージを取得することができます*4。 ただし、app オブジェクト自体(View では this で参照できるオブジェクト)に getMessage() メソッドが追加されているようなので、それを使う方が簡単でしょう:

application(title: getMessage('app.title'), ...){
    menuBar(){
        menu(mnemonic:'A', getMessage('app.menu.action')){
            menuItem(action:paint)
        }
        menu(mnemonic:'L', getMessage('app.menu.laf')){
            menuItem(action:showLaf)
        }
        glue()
        menu(mnemonic:'H', getMessage('app.menu.help')){
            menuItem(action:about)
        }
    }
    ...
}

プロパティとか関係なく、普通に getMessage() メソッドを呼び出せば、リソース・バンドルのメッセージを取得できます。 実行結果は下図のようになります:


いぇーい!

Controller で使う

Controller には messageSource プロパティを定義しても、View のように自動で値をインジェクトしてくれません。 でも、こちらでも app オブジェクトを介すればメッセージを取得するのは簡単です:

class FunctionPlotterController {
    ...
    def showAbout = { evt = null ->
        def meta = app.metadata
        // def meta = Metadata.current でも OK
        messageService.show(
            app.getMessage('controller.showAbout.message', 
                                    [GroovySystem.version, meta['app.griffon.version'], meta['app.version']]))
    }
}

ここではメッセージ中のプレースホルダー({《数字》}の部分)に挿入する文字列も指定しています。 結果はこんな感じ:


結局のところ、リソース・バンドル(プロパティ・ファイル)作るのが一番面倒でございました。
Java国際化プログラミング

Java国際化プログラミング

*1:国際語化? 多言語化? まぁ、細かいことは気にしないでいこう!

*2:例えば日本語 (ja) で日本 (JP) なら「messages_ja_JP.properteis」のようにします。

*3:$basedir/griffon-app/conf/Application.groovy に「griffon.i18n.basenames = ['foo', 'bar']」といった設定を書くそうです。

*4:I18n プラグインのドキュメントには「i18n」というプロパティも使える、みたいなことが書かれてますが、全くそんな気配はありません。