ご好評頂いている「提督業も忙しい!」ですが、実は日本語だけでなく、英語・中国語 (簡体字)・韓国語の4ヶ国語に対応しています。 また、@KatsuYuzu さんから pull request を頂き、実行中に使用言語を動的に切り替えられるようになりました。
(余談ですが、この pull request の内容について、@KatsuYuzu さんと Twitter の DM で会話していました。 そのため、最終的にマージしたコードに至る経緯等が issue から読み取れなくなっていて、惜しいなぁと思ってこのエントリー書いていたりします)
今回は、WPF アプリにおける国際化 (多言語化) と、実行中にアプリの言語を動的に切り替える手法を紹介します。 国際化、と言うと数値や日時の表記方法、UI 設計 (レイアウト時の自動サイズ設定など) まで含まれますが、今回はアプリ内の表示言語に絞ります。
多言語リソースを用意する
何はともあれ、多言語化されたリソースがなければ始まりません。まずはリソースを用意しましょう。 と言っても、WPF アプリケーションの場合は、Windows Phone アプリや Windows ストア アプリのような多言語アプリ ツールキットが使えるわけではありません。 アセンブリ リソース (Resources.resx) を使用します。
プロジェクトを作成すると Resources.resx が Properties 下に入っているはずです。 なければ (あるいは消してしまった場合は) プロジェクトのプロパティのリソース タブから作成できます。
さっそくリソースを追加しましょう。 [名前] にリソース名、[値] にリソース値 (実際に画面に表示したりする文字列) を入力します。 また、編集画面右上で [アクセス修飾子] を Public に設定するのを忘れないでください。
次に、別の言語を追加してみます。 Properties 以下に、Resources.<カルチャ>.resx という名前のリソースを作成し、配置します。 (Properties 以下に直接ファイルを追加できないので、いったんプロジェクト直下に作成し、移動するとよいかも)
カルチャを ja-JP にしたので、日本語です。 先に作った Resources.resx と同じキーを用意し、リソース値に日本語の文字列を指定します。
これで、英語と日本語のリソースができました。 この状態でビルドすると、以下のようなファイルができます。
ja-jp フォルダーの中身はこれ。
この MultilingualApp.resources.dll はサテライト アセンブリと呼ばれ、カルチャに固有のリソースのみを含んだアセンブリです。 MultilingualApp.exe には、ニュートラル カルチャのリソース (Resources.resx) のみが含まれており、多言語化されたリソース (たとえば、Resources.ja-JP.resx) はカルチャごとに作成されたフォルダー内のサテライト アセンブリからロードされます。
「提督業も忙しい!」version 2.3 の場合、KanColleViewer.exe には英語リソースが含まれており、日本語・中国語 (簡体字)・韓国語のサテライト アセンブリが同梱されています。 (なので、サテライト アセンブリを消して実行すると、英語しか表示できなくなる)
なお、Transifex という翻訳プラットフォームを用いて Resources.resx の翻訳・管理を実践した例をまとめたエントリーも書きましたので、ご参考までに。
Transifex でリソース (Resources.resx) の翻訳・管理をする -導入編- http://grabacr.net/archives/1687
XAML からリソースを指定
XAML で多言語化されたリソースを指定し、画面に表示してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<Window x:Class="MultilingualApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:properties="clr-namespace:MultilingualApp.Properties" xmlns:app="clr-namespace:MultilingualApp" Title="MultilingualApp" Width="300" Height="100" FontFamily="Segoe UI, Meiryo" FontSize="20"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{x:Static properties:Resources.HelloWorld}" /> </StackPanel> </Window> |
13 行目、TextBlock の Text プロパティに、先ほど作った Resources.resx の HelloWorld キーを指定しています。 これで、OS のカルチャに対応するサテライト アセンブリがある場合はそちらを使用するようになり、ja-JP の Windows で実行した場合は Resources.ja-JP.resx のリソースが参照されるようになります。
実行したものがこちら。 左が Windows 8.1 Pro (ja-JP)、右が Windows 7 Professional (en-US) です。
動的な言語切り替え
多言語化されたリソースを使用して、アプリケーション実行中に表示言語を切り替えられるようにしてみましょう。
需要があるかどうかは知らない。
下準備
@KatsuYuzu さんから頂いた pull request をベースに、最終的にマージした実装方法です。 まずは、リソースを管理するクラスを別途作成します。 シングルトン パターンになりますが許して…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/// <summary> /// 多言語化されたリソースと、言語の切り替え機能を提供します。 /// </summary> public class ResourceService { #region singleton members private static readonly ResourceService _current = new ResourceService(); public static ResourceService Current { get { return _current; } } #endregion private readonly MultilingualApp.Properties.Resources _resources = new Resources(); /// <summary> /// 多言語化されたリソースを取得します。 /// </summary> public MultilingualApp.Properties.Resources Resources { get { return this._resources; } } } |
XAML 側は、Properties.Resources を直接参照するのではなく、ResourceService にデータ バインディングするように変更します。 先ほどの TextBlock の Text プロパティを、以下のように修正しました。
1 2 3 4 |
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{Binding Source={x:Static app:ResourceService.Current}, Path=Resources.HelloWorld, Mode=OneWay}" /> </StackPanel> |
バインディング ソースに ResourceService の唯一のインスタンス (Current プロパティ)、バインディング パスで ResourceService の Resources プロパティから多言語リソースのキーを指定します。
この時点では、結果は同じです。 OS のカルチャに応じてリソースが自動的に切り替わるだけです。
プロパティ変更通知を利用しよう
次に、ResourceService に以下のコードを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class ResourceService : INotifyPropertyChanged { // 中略... (さっきと同じ部分) #region INotifyPropertyChanged members public event PropertyChangedEventHandler PropertyChanged; protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null) { var handler = this.PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } #endregion /// <summary> /// 指定されたカルチャ名を使用して、リソースのカルチャを変更します。 /// </summary> /// <param name="name">カルチャの名前。</param> public void ChangeCulture(string name) { Resources.Culture = CultureInfo.GetCultureInfo(name); this.RaisePropertyChanged("Resources"); } } |
ResourceService クラスに INotifyPropertyChanged インターフェイスを実装し、プロパティの変更通知機能を持たせます。 そして、ChangeCulture メソッドで外部からカルチャを指定できるようにし、カルチャを変更したら Resources プロパティの変更通知イベントを発砲します。
XAML 側は、Current インスタンスの Resources プロパティにデータ バインディングしています。 そのため、Resources プロパティの変更通知を受け取ると、再度 Resources プロパティを参照し、カルチャが切り替わったリソースを取得できます。
あとは、カルチャを変更させるだけ
ResourceService.Current.ChangeCulture("en-US");
のようにカルチャを指定すると、UI の表示言語が即切り替わります。例として、ドイツ語リソースを追加し、UI からドイツ語に変更するようにしました。
1 2 3 4 5 6 7 8 |
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{x:Static properties:Resources.HelloWorld}" /> <TextBlock Text="{Binding Source={x:Static app:ResourceService.Current}, Path=Resources.HelloWorld, Mode=OneWay}" /> <Button Content="-> ドイツ語" Click="Button_Click" /> </StackPanel> |
最初に紹介した Properties.Resources を直接指定した TextBlock (3 行目) と、ResourceService.Current.Resources にデータ バインディングした TextBlock (4 行目) を両方乗せています。
分離コードは以下。10 行目で ResourceService に対しカルチャの変更を呼び出しています。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class MainWindow { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { ResourceService.Current.ChangeCulture("de"); } } |
ボタンを押すと、以下のようになります。
ResourceService からのプロパティ変更通知を受け取っている 4 行目の TextBlock は、変更されたカルチャに追従し、表示が切り替わりました。 完成。
KanColleViewer もご覧ください
実際に動いているところは、「提督業も忙しい!」で確認して頂くと一目瞭然です。 艦これをプレイしてなくても、起動した直後に設定画面から言語切り替え機能にアクセスできます。
該当するソースはこちら。
ResourceService.cs https://github.com/Grabacr07/KanColleViewer/blob/3e0a8461561a883814569196ac2df34e4ef8e8c2/source/Grabacr07.KanColleViewer/Models/ResourceService.cs
おわりに
というわけで、@KatsuYuzu さんありがとうございましたスペシャルでした。
今後もこんな感じで、「提督業も忙しい!」の開発ネタを放出していければいいなー、と思っています。