for WPF developers
Home Profile Tips 全記事一覧

多言語対応にする

(2017/06/30 8:12:03 created.)

日本語だけでアプリケーションを作っていたら、突然「英語に対応してくれ」と言われたらどうしましょう。固定のテキストを XAML に直接書いていた場合、それを英語で直接書き直すでしょうか。ところが、そうすると日本語版と英語版で異なるソースを管理しないといけなくなります。

WPF では、多言語化されたリソースを用意することで、ひとつのソースで管理できるようになる他、アプリケーション起動後にも動的に言語を切り替えられるようになります。

多言語化されたリソース

WPF ではアセンブリリソースを使用します。次のように、プロジェクトを作成すると Properties の中に Resources.resx がデフォルトで生成されているはずなので、これを使います。


アクセス修飾子を Public にしたら準備完了。例えば次のように名前とそれに対する値を設定します。


次に、例えば英語版のリソースファイルを追加しましょう。別の言語のリソースファイル名は Resources.en-US.resx というように、カルチャ名を間に入れて定義します。Properties 直下には直接ファイルを追加できないので、既にある Resources.resx ファイルをコピー&ペーストしてリネームするか、一度プロジェクト直下にファイルを追加してから Properties 下に移動するなど工夫してください。Resources.en-US.resx ファイルを追加した後のツリーは次のようになります。


こちらのファイルには英語のリソースを定義します。例えば次のようにします。ここで、アクセス修飾子はコード生成なしを選択するようにする必要があります。また、先ほど日本語リソースで定義した名前と同じ名前で、値を英語にしたものを定義しておきます。


XAML からリソースを参照する

前節で定義したリソースファイルを XAML から参照するには次のように x:Static を用いて記述します。

MainView.xaml
  1. <Window x:Class="Tips_MultiLanguage.Views.MainView"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:p="clr-namespace:Tips_MultiLanguage.Properties"
  5.         Title="MainView" Height="100" Width="200">
  6.     <StackPanel HorizontalAlignment="Center"
  7.                 VerticalAlignment="Center">
  8.         <Button Content="{x:Static p:Resources.Hello_World}" />
  9.     </StackPanel>
  10. </Window>

上記のコードを実行すると、次のようにリソースで定義した文字列が表示されるようになりました。


ところで、リソースは日本語版の Resource.resx と英語版の Resource.en-US.resx を用意していたはずですが、なぜ日本語版のリソースが参照されているのでしょうか。

これは、使用している Windows OS の環境に依存します。このサンプルを起動した環境は日本語版の Windows OS なので、特に指定しない場合は日本語カルチャのリソースを探します。日本語のカルチャ名は ja-JP なので、Resource.ja-JP.resx を探すわけですが、そのようなリソースファイルは存在しません。この場合、実行ファイルに埋め込まれている Resource.resx を参照するようになります。したがって、今回は Resource.resx に日本語の値を定義していたので、結果的に日本語の値が表示されるようになったわけです。

残念ながら英語版の Windows 環境が手元にないため、上記のコードで英語が表示されるという結果を確認できません。ただし、逆に Resource.resx に英語の値、Resource.en-US.resx ではなく、Resource.ja-JP.resx を作成してその中に日本語の値を定義すると、今度は Resource.ja-JP.resx が参照されるため、これもまた日本語が表示されるようになることは確認できますので、やってみてください。

動的な言語切り替え

アプリケーション実行中に言語を切り替えるようにするために、次のようなクラスを用意します。

ResourceServices.cs
  1. namespace Tips_MultiLanguage
  2. {
  3.     using System.Globalization;
  4.     using Tips_MultiLanguage.Properties;
  5.  
  6.     /// <summary>
  7.     /// 多言語化されたリソースと、言語の切り替え機能を提供するシングルトンクラスを表します。
  8.     /// </summary>
  9.     public class ResourceServices : NotificationObject
  10.     {
  11.         #region シングルトン
  12.  
  13.         /// <summary>
  14.         /// 現在のインスタンスを保持します。
  15.         /// </summary>
  16.         private static readonly ResourceServices _current = new ResourceServices();
  17.  
  18.         /// <summary>
  19.         /// 現在のインスタンスを取得します。
  20.         /// </summary>
  21.         public static ResourceServices Current
  22.         {
  23.             get { return _current; }
  24.         }
  25.  
  26.         /// <summary>
  27.         /// 静的なコンストラクタ
  28.         /// </summary>
  29.         static ResourceServices()
  30.         {
  31.         }
  32.  
  33.         /// <summary>
  34.         /// private なコンストラクタを定義することで
  35.         /// 外部からこのクラスが生成されることを回避します。
  36.         /// </summary>
  37.         private ResourceServices()
  38.         {
  39.         }
  40.  
  41.         #endregion
  42.  
  43.         /// <summary>
  44.         /// リソースを保持します。
  45.         /// </summary>
  46.         private readonly Resources _resources = new Resources();
  47.  
  48.         /// <summary>
  49.         /// 多言語化されたリソースを取得します。
  50.         /// </summary>
  51.         public Resources Resources
  52.         {
  53.             get { return this._resources; }
  54.         }
  55.  
  56.         /// <summary>
  57.         /// 指定されたカルチャ名を使用して、リソースのカルチャを変更します。
  58.         /// </summary>
  59.         /// <param name="name">カルチャの名前を指定します。</param>
  60.         public void ChangeCulture(string name)
  61.         {
  62.             Resources.Culture = CultureInfo.GetCultureInfo(name);
  63.             this.RaisePropertyChanged("Resources");
  64.         }
  65.     }
  66. }

ChangeCulture() メソッドにカルチャ名を渡すことで、現在のカルチャを変更するようにしています。また、リソースは Resources プロパティで公開するようにしています。

このクラスを利用するために、まず XAML が参照するリソースを ResourceServices クラスの Resources プロパティに変更します。

MainView.xaml
  1. <Window x:Class="Tips_MultiLanguage.Views.MainView"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:app="clr-namespace:Tips_MultiLanguage"
  5.         Title="MainView" Height="100" Width="200">
  6.     <StackPanel HorizontalAlignment="Center"
  7.                 VerticalAlignment="Center">
  8.         <Button Content="{Binding Source={x:Static app:ResourceServices.Current}, Path=Resources.Hello_World}" />
  9.     </StackPanel>
  10. </Window>

この時点では、先ほどの実行結果と同様、Windows OS の環境によって日本語あるいは英語が表示されます。ここで、このボタンに言語切り替えコマンドをデータバインドします。

MainView.xaml
  1. <Window x:Class="Tips_MultiLanguage.Views.MainView"
  2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.         xmlns:app="clr-namespace:Tips_MultiLanguage"
  5.         Title="MainView" Height="100" Width="200">
  6.     <StackPanel HorizontalAlignment="Center"
  7.                 VerticalAlignment="Center">
  8.         <Button Content="{Binding Source={x:Static app:ResourceServices.Current}, Path=Resources.Hello_World}"
  9.                 Command="{Binding ChangeLanguageCommand}"
  10.                 />
  11.     </StackPanel>
  12. </Window>
MainViewModel.cs
  1. namespace Tips_MultiLanguage.ViewModels
  2. {
  3.     public class MainViewModel : NotificationObject
  4.     {
  5.         private DelegateCommand _changeLanguageCommand;
  6.         /// <summary>
  7.         /// 言語を英語にするコマンドを取得します。
  8.         /// </summary>
  9.         public DelegateCommand ChangeLanguageCommand
  10.         {
  11.             get
  12.             {
  13.                 return this._changeLanguageCommand ?? (this._changeLanguageCommand = new DelegateCommand(_ =>
  14.                 {
  15.                     // カルチャを英語に変更する
  16.                     ResourceServices.Current.ChangeCulture("en-US");
  17.                 }));
  18.             }
  19.         }
  20.     }
  21. }

起動直後

ボタンを押した後

途中の説明でも書きましたが、Resource.resx というリソースファイルは実行ファイルの中に埋め込まれますが、後から追加した Resource.en-US.resx などは、ビルドすると次のように en-US というフォルダが作られます。


en-US フォルダの中身

このように実行ファイルに埋め込まれない .dll をサテライトアセンブリといい、カルチャに固有のリソースのみを含んだアセンブリとなっています。つまり、実行ファイルにはニュートラルカルチャのリソース Resources.resx のみが含まれており、多言語化されたリソースについては都度サテライトアセンブリを参照することになります。

したがって、サテライトアセンブリを削除して実行ファイルを起動した場合、削除したアセンブリに該当する言語の表示ができなくなることになります。