ついにベールを脱いだJavaFX

第12回国際化

今回はアプリケーションを作成する上で見過ごされがちではあるのですが、とても重要な要素について紹介します。

もう読者の皆さんにはおわかりのはずですね。そう、国際化です。

アプリケーションを動作させることを、特定の言語や地域だけに限定できるのであれば、国際化は必要ありません。しかし、ネットでつながれた世界では、アプリケーションをどの言語、どの地域で動作させるかを特定することがとても難しくなります。

そのため、できればはじめから国際化を考慮して、アプリケーションを作る必要があります。とはいうものの、アプリケーションを作りはじめてすぐの時には、なかなか国際化まで頭が回らないというのも本音だと思います。

Javaで国際化を行う場合、メニューなどの文字列はリソースバンドルによって切り替えます。最近では、EclipseやNetBeansなどのIDEの機能として、国際化が簡単にできるようになりました。しかし、IDEの力を借りないで既存のアプリケーションの国際化を行うのは、それなりに大変です。

これに対し、JavaFXではとても簡単に国際化を行うことができます。しかも、国際化は言語レベルで扱われるため、とてもスマートに国際化することができるのです。

そこで、今回は次の2つのトピックに関して解説を行っていきます。

  • ロケールによる文字列の切り替え
  • 埋め込み文字列のフォーマット

今回使用したサンプルのソースを含めたNetBeansのプロジェクトは下記のリンクよりダウンロードすることができます。

なお、2月12日にJavaFX 1.1がリリースされました。それと同時にNetBeansのJavaFXプラグインもJavaFX 1.1に対応したバージョンがリリースされています。本連載も今回からJavaFX 1.1を使用していきます。

ロケールによる文字列の切り替え

国際化を行うに当たって、その対象となるアプリケーションを用意しましょう。今回、作成したのは世界時計です。といっても、3つの都市の時間しか表示できませんが。

コンボボックスで都市を選択すると、その都市における時間を表示するというものです。ちょっと長いのですが、以下に世界時計のスクリプトを示します。

リスト1 世界時計スクリプト
// 都市名
var cities = ["New York", "Paris", "Tokyo"];
 
// タイムゾーン
var timezones = [
    TimeZone.getTimeZone("America/New_York"),
    TimeZone.getTimeZone("Europe/Paris"),
    TimeZone.getTimeZone("Asia/Tokyo")
];
 
// 選択項目が変更したら、デフォルトタイムゾーンを変更する
var selectedIndex: Integer = 0 on replace {
    TimeZone.setDefault(timezones[selectedIndex]);
}
 
// 時間を表す変数
var time = new Date();
 
// 1秒ごとに時間を更新するためのタイムライン
Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames: [
        KeyFrame {
            time: 0s
            action: function() {
                time = new Date();
            }
        },
        KeyFrame {
            time: 1s
        }
    ]
}.play();
 
Stage {
    title: "World Clock"
    scene: Scene {
        width: 350 height: 100
        content: [
            SwingLabel {
                translateX: 20 translateY: 13
                text: "City:"
            },
            SwingComboBox {
                translateX: 70 translateY: 7
                width: 250
 
                // 選択項目はselectedIndexに双方向バインドさせる
                selectedIndex:
                    bind selectedIndex with inverse
                    
                items: for (city in cities) {
                    SwingComboBoxItem {
                        text: city
                    }
                }
            },
            Text {
                font: Font {
                    size: 20
                }
                x: 20 y: 70

                // 時間表示
                content: bind "{time}"
            }
        ]
    }
}

コンボボックスで都市名を選択すると、変数selectedIndexの値が変化します。変数selectedIndexには置換トリガが設定しておき、値が変更するとそれに応じたタイムゾーンをデフォルトタイムゾーンに設定します。

タイムゾーンにはJavaのjava.util.TimeZoneクラスを使用しています。デフォルトタイムゾーンを変更すると、Dateクラスの変数timeの表示が変化します。

図1に動作例を示します。Tokyoを選択しているので、時間がJSTで表されていることがわかります。

図1 世界時計の動作例
図1 世界時計の動作例

では、まず世界時計の文字列をロケールに応じて変更するようにしてみましょう。

JavaFXではロケールに応じた文字列の変更はとても簡単に行うことができます。たとえば、ラベルで表示しているCity:という文字列をロケールに応じて切り替えるようにするには、次のように表記します。

リスト2
        text: ##"City:"

文字列の前にシャープを2つ付け足すだけです。

Javaのリソースバンドルではキーに対応した文字列をロケールによって変更させますが、JavaFXでは##がついた文字列自体がデフォルトの文字列になり、しかもキーにもなります。

次に行うのが、リソースファイルの作成です。

リソースファイルはデフォルトではスクリプトのファイル名に言語を付け加え、拡張子をfxpropertiesとします。たとえば、世界時計のスクリプトファイルはworldClock.fxとしたので、日本語のリソースファイルはworldClock_ja.fxpropertiesになります。同じように、フランス語であればworldClock_fr.fxpropertiesとなります。

作成したリソースファイルはスクリプトファイルと同じディレクトリに配置します。したがって、NetBeansでは図2のように表示されます。

図2 worldClock_ja.fxpropertiesの配置
図2 worldClock_ja.fxpropertiesの配置

worldClock_ja.fxpropertiesの内容は次のようにします。

リスト3
@charset "Shift_JIS";

"City:" = "都市:"

先ほど##を付けた文字列をキーとし、イコールで結んで言語に応じた表記を記述します。ここでは、"City:"に対応する日本語の文字列として"都市:"としました。

また、リソースファイルの文字セットを指定することもできます。文字セットを指定する場合、リソースファイルの先頭に@charsetに続けて文字セット名を記述します。最後のセミコロンを忘れずに。ここでは、Shift_JISにしてみました。なお、リソースファイルのデフォルトの文字セットはUTF-8です。

では、実行してみましょう。実行結果を図3に示します。

図3 Cityを変更した結果
図3 Cityを変更した結果

図1では「City:」と表示されていた箇所が「都市:」と表示されるようになりました。

他の文字列に関しても日本語のリソースを記述してみましょう。スクリプトで##を付加する文字列として、都市の名前とStageのtitleアトリビュートがあります。これらの部分も次のようにスクリプトを変更します。

リスト4
// 都市名
var cities = [##"New York", ##"Paris", ##"Tokyo"];

        ...
         
         
Stage {
    title: ##"World Clock"

        ...

これに対応して、リソースファイルworldClock_ja.fxpropertiesは次のようにしました。

リスト5 worldClock_ja.fxproperties
@charset "Shift_JIS";
 
"City:" = "都市:"
"World Clock" = "世界時計"
 
"New York" = "ニューヨーク"
"Paris" = "パリ"
"Tokyo" = "東京"

これだけで日本語に対応することができました。さっそく実行してみましょう。

図4 日本語に対応した結果
図4 日本語に対応した結果

ここでは通常の文字列しか扱いませんでしたが、\n、\t、\r、\f、\"、\\などの文字もリソースファイルで扱うことができます。

このように簡単に各国語に対応できることはわかりました。しかし、ここで気になるのがデフォルトの文字列が同じだった場合です。たとえば、メニューでよく使用されるFileなどは、名詞と動詞の両方で使用されます。同じ訳語を当てるのであれば問題ありませんが、異なる訳語を当てる場合、何らかの方法で区別することが必要です。

また、デフォルトの文字列が長い場合はどうでしょう。何行にも渡るような文字列をリソースファイルに書くのはちょっと面倒です。

このような場合、デフォルトの文字列とは別にキーとなる文字列だけ別に記述することも可能です。キーを別に記述する場合、##の後に[ ]を記述し、その中にキーとなる文字列を記述します。

ここでは、都市名をキーを別にして書き直してみます。

リスト6
// 都市名
var cities = [
    ##[NY]"New York",
    ##[PR]"Paris",
    ##[TK]"Tokyo"
];

"New York"の文字列に対応するキーは"NY"とし、同じように"Paris"は"PR"、"Tokyo"は"TK"としました。

リソースファイルはリスト7のようになります。

リスト7
@charset "Shift_JIS";

"City:" = "都市:"
"World Clock" = "世界時計"

"NY" = "ニューヨーク"
"PR" = "パリ"
"TK" = "東京"

実行結果は図4と同じなので示しませんが、このようにキーとデフォルト文字列を別々に扱うことも可能なのです。

スクリプトとリソースファイルの結びつけ

デフォルトでは1つのスクリプトファイルにつき、1つのリソースファイルが必要ですが、スクリプトファイルが多くなってくるとリソースの扱いが煩雑になりがちです。また、スクリプトファイルと同じディレクトリに配置しなくてはならないことも、ちょっと気になります。

そこで、javafx.util.StringLocalizerクラスを使用して、スクリプトとリソースファイルの結びつきを変更してみましょう。

StringLocalizerクラスにはassociate関数とdissociate関数が定義されています。associate関数がスクリプトとリソースファイルの結びつけ、dissociate関数が切り離しを行います。

associate関数はオーバロードされており、パッケージ単位ごとにリソースファイルを割り当てることと、1つのスクリプトファイルごとにリソースファイルを割り当てることの両方ができます。前者が引数が2つ、後者が引数が3つとなります。

ここでは、後者の引数が3つのものを使用して、図5のようにリソースファイルをデフォルトの位置とは違うresourcesディレクトリに置いてみましょう。

図5 リソースファイルの構成
図5 リソースファイルの構成

この変更に伴うスクリプトの変更は、リスト8にある1行をスクリプトの先頭に付け加えるだけです。

リスト8
// スクリプトファイルとリソースファイルを結びつける
StringLocalizer.associate("resources/worldClock", "", "worldClock.fx");

associate関数の第1引数がリソースファイルの場所を示しています。実際のリソースファイル名はresources/worldClock_xx.fxpropertiesとなります(xxの部分は言語を示します⁠⁠。第2引数がパッケージ、第3引数がスクリプトファイルを示します。ここでは、パッケージを使用していないので、空文字にしてあります。

また、StringLocalizerクラスでも文字列の置き換えを行うことができます。たとえば、都市名をStringLocalizerクラスを使用して言語ごとに置き換えする場合、リスト9のように記述します。

リスト9
var cities = ["New York", "Paris", "Tokyo"];
 
var localizer = StringLocalizer { key: cities[0] }
println("New York: {localizer.localizedString}");
 
localizer.key = cities[1];
println("Paris: {localizer.localizedString}");
 
localizer.key = cities[2];
println("Tokyo: {localizer.localizedString}");

StringLocalizerオブジェクトのkeyアトリビュートにキーとなる文字列を指定すると、localizedStringアトリビュートに言語ごとに置き換えられた文字列を取得することができます。

埋め込み文字列のフォーマット

JavaFX Scriptでは文字列中に{ }を記述することで、オブジェクトを埋め込むことができます。これは読者のみなさんはすでにご存じのはずです。

今までは単にオブジェクトを記述していましたが、フォーマットを指定することもできるのです。たとえば、次のような表記を行うことができます。

リスト10
var x: Number = 10.0;
 
println("x = {%f x}");
println("x = {%6.2f x}");
println("x = {%4.0f x}");
println("x = {%+08.2f x}");
println("x = {%g x}");

%で始まる部分がフォーマット文字列です。ここで使用できるフォーマット文字列は、java.util.Formatterクラスで使用できるフォーマット文字列と同じです。

このスクリプトを実行した結果を図6に示します。

図6 数値のフォーマット
x = 10.000000
x =  10.00
x =   10
x = +0010.00
x = 10.0000

フォーマット文字列を指定しなかった場合は、文字列表記である%sを指定した場合と同じ結果になります。

%dや%fなどの数値フォーマットや%sはロケールに応じて変更することはありませんが、日付/時間はロケールによって変化することがあります。

日付/時間に関するフォーマット文字列はjava.util.Formatterクラスで使えるものだけでなく、POSIXのstrftimeに対応するように拡充されています。表1に拡張されたフォーマット文字列を示します。出力例は日本時間の2009年2月9日(月) 2時56分35秒をフォーマットした場合を示します。

表1 日付/時間に関する拡張フォーマット
フォーマット文字列 説明 出力例
%tx ローカライズされた日付表現 2009年2月9日
%tX ローカライズされた時間表現 2時56分35秒 JST
%tG ISO8601形式の4桁の西暦年。ISO8601で表した週数に対応している 2009
%tg ISO8601形式の2桁の西暦年。ISO8601で表した週数に対応している 09
%tu 曜日の数値表現。月曜が1、日曜が7 1
%tU 週数。1月の第1日曜を第1週とする 06
%tV ISO8601で表した週数。その年に少なくとも4日以上含まれる最初の週を第1週とする 07
%tw 曜日の数値表現。日曜日が0、土曜日が6 1
%tW 週数。1月の第1月曜日を第1週とする 06
%tEc 代替カレンダの日付と時間表現 平成21年2月9日 2時56分35秒 JST
%tEC 代替カレンダの基準年の名前 平成
%tEx 代替カレンダの日付表現 平成21年2月9日
%tEX 代替カレンダの時間表現 2時56分35秒 JST
%Ey 代替カレンダの年 21

代替カレンダというのは、西暦以外の地域に特有のカレンダのことを指します。日本では和暦が代替カレンダとなります。

Javaでは和暦を扱うためにja_JP_JPという特殊なロケールを使う必要がありました。それに比べるとJavaFXでは和暦でもシンプルに表すことができます。

たとえば、先ほどの世界時計で代替カレンダを使用してみましょう。%tEcで時間をフォーマットするとデフォルトでは図7になり、日本語ロケールでは図8となります。

図7 %tEcでフォーマットした世界時計(英語ロケール)
図7 %tEcでフォーマットした世界時計(英語ロケール)
図8 %tEcでフォーマットした世界時計(日本語ロケール)
図8 %tEcでフォーマットした世界時計(日本語ロケール)

英語ロケールでは代替カレンダがないため西暦で表され、日本語ロケールでは和暦で表されました。

しかし、ちょっと違和感があります。たとえば、英語ロケールでは12時間表記で表されているのが、日本語表記では24時間表記で表されている点や、タイムゾーンが日本語ロケールだけ表示されている点などです。そこで、フォーマットをカスタマイズしてみましたリスト11⁠。

リスト11
Text {
    font: Font {
        size: 20
    }
    x: 20 y: 70

    // 時間表示
    content: bind "{%ta time} {%tx time} {%tl time}:{%tM time}:{%tS time} {%Tp time
}

英語ロケールで表示すると、図9のようになります。

図9 カスタマイズ後の時間表記(英語ロケール)
図9 カスタマイズ後の時間表記(英語ロケール)

英語ではこの順番ですが、日本語では曜日は日付の後に表記するなど順番がことなります。そこで、この文字列もロケールによって切り替えてしまいましょう。

つまり、文字列の前に##を付加して記述してしまいます。

リスト12
    // 時間表示
    content: bind ##"{%ta time} {%tx time} {%tl time}:{%tM time}:{%tS time} {%Tp time}"

そして、リソースファイルにリスト13にある行を追加します。

リスト13
"%ta %tx %tl:%tM:%tS %Tp" = "%2$tx(%1$ta) %6$Tp %3$tl:%4$tM:%5$tS"

キーの部分はスクリプトでの文字列から{ }と変数を除いた文字列とします。値には見慣れない数字と$が入っています。これはフォーマット文字列の順番を表しています。最初の%2$txはキーの2番目のフォーマット文字列である%txに対応しています。

これを図示したのが図10です。このように、$の前の数字で表している要素を指定できます。これはFormatterクラスと同じです。

図10 フォーマット文字列の対応
図10 フォーマット文字列の対応

では、これで実行してみましょう。図11に日本語ロケールで実行した結果を示します。

図11 カスタマイズ後の時間表記(日本語ロケール)
図11 カスタマイズ後の時間表記(日本語ロケール)

今回示したようにJavaFXの国際化は、とても簡単に扱うことができます。

残念ながら、現状のJavaFXではデフォルトロケールしか扱うことができません。デフォルトロケールを切り替えることは可能ですが、アプリケーション中で複数のロケールを使用することはできないようです。今後の拡充に期待したいところですね。

また、通貨のフォーマッティングや、アラビア語のように右から左に記述する文字列のサポートなどが、今後行われる予定だそうです。

なお、JavaFX 1.0および1.1では、AppletやJava Web Startでリソースファイルの読み込みを行う場合、JARファイルに署名が必要です。しかし、次のバージョンから署名が不必要になるようです。

おすすめ記事

記事・ニュース一覧