WPFの国際化対応

WinFormでは簡単に対応できるのでWPFも楽勝だと思っていたのですが、Visual Studioはあんま面倒を見てくれないです。やり方は色々ありますが、個人的には、
http://www.codeproject.com/KB/WPF/WPFLocalize.aspx
この方法が良さそうに思いました。

上記の記事を読めば理解できますが、折角なので簡単なサンプルを作ってみます。

完成予定図

コンボボックスで言語を切り替えると、ラベルのテキストも連動して変わるだけの単純なものです。

プロジェクトの構成

こんな感じ。

まずは、リソース管理用のCultureResources.csを見てみましょう。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;

namespace MultiLangSample 
{
    public class CultureResources
    {
        static readonly List<CultureInfo> cultures = new List<CultureInfo> ();

        static CultureResources ()
        {
            // 言語の選択候補として日本語(日本)と英語(米国)を追加する
            cultures.Add(CultureInfo.GetCultureInfo("ja-JP"));
            cultures.Add(CultureInfo.GetCultureInfo("en-US"));
        }

        public static IList<CultureInfo> Cultures
        {
            get { return cultures; }
        }

        public Properties.Resources GetResourceInstance()
        {
            // resxファイルから自動生成されたクラスのインスタンスを返す
            return new Properties.Resources();
        }

        private static ObjectDataProvider provider;
        public static ObjectDataProvider ResourceProvider
        {
            get
            {
                // キー「ResourcesInstance」はApp.xaml内で定義している
                if (provider == null && System.Windows.Application.Current != null)
                    provider = (ObjectDataProvider)System.Windows.Application.Current.FindResource ("ResourcesInstance");
                return provider;
            }
        }

        public static void ChangeCulture(CultureInfo culture)
        {
            // 言語の切り替えメソッド
            Properties.Resources.Culture = culture;
            ResourceProvider.Refresh();
        }
    }
}

resxファイルから生成されたResourcesクラスのインスタンスを返すことと、言語切り替えメソッドがある程度ですね。あと、今回サポートする言語は日本語、英語だけにしています。

次にこれをApplication.Resourcesに定義します。

<Application x:Class="MultiLangSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:MultiLangSample="clr-namespace:MultiLangSample"
    StartupUri="Window1.xaml">
    <Application.Resources>
        <ObjectDataProvider x:Key="ResourcesInstance" 
                            ObjectType="{x:Type MultiLangSample:CultureResources}" 
                            MethodName="GetResourceInstance"/>
        <ObjectDataProvider x:Key="Resources" ObjectType="{x:Type MultiLangSample:CultureResources}"/>
    </Application.Resources>
</Application>

ObjectDataProviderを使っています。ResourcesInstanceに着目すると、MethodNameとしてGetResourceInstanceを定義している通り、メソッドの戻り値(この場合はResourcesクラスのインスタンス)が対象になっていることに注意です。

続いてリソースを登録しましょう。まずは、デフォルト言語用(Resources.resx)。

アクセス修飾子はpublicにします。続いて日本語(Resources.ja-JP.resx)。

これも同じです。

画面(Window1.xaml)は、単純にコンボボックスとラベルを並べるだけ。

<Window x:Class="MultiLangSample.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Label Content="{Binding Path=LastName, Source={StaticResource ResourcesInstance}}" 
               Height="28" Margin="8,36,0,0" Name="label1" 
               VerticalAlignment="Top" HorizontalAlignment="Left" Width="120" />
        <Label Content="{Binding Path=FirstName, Source={StaticResource ResourcesInstance}}" 
               Height="28" Margin="8,68,0,0" Name="label2" 
               VerticalAlignment="Top" HorizontalAlignment="Left" Width="120"/>
        <Label Content="{Binding Path=Age, Source={StaticResource ResourcesInstance}}" 
               Height="28" Margin="8,100,0,0" Name="label3" 
               VerticalAlignment="Top" HorizontalAlignment="Left" Width="120"/>
        <ComboBox Name="cbCulture" Margin="8,8,140,0" VerticalAlignment="Top" Height="24" 
                  IsSynchronizedWithCurrentItem="True" 
                  ItemsSource="{Binding Path=Cultures, Mode=OneWay, Source={StaticResource Resources}}"/>
    </Grid>
</Window>

ただし、コンボボックスの表示候補やラベルのテキストはApplication.Resourcesで定義したObjectDataProviderを使ってバインドします。

最後にWindow1のロジック(Windows1.xaml.cs)を書いて完成。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Globalization;

namespace MultiLangSample
{
    /// <summary>
    /// Window1.xaml の相互作用ロジック
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1 ()
        {
            InitializeComponent ();
            // コンボボックスの選択変更イベント登録
            cbCulture.SelectionChanged += OnSelectionChanged;
            // デフォルトは取りあえずUICultureにしておく
            cbCulture.SelectedItem = CultureInfo.CurrentUICulture;
            CultureResources.ChangeCulture(CultureInfo.CurrentCulture);
        }

        void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            CultureInfo culture = cbCulture.SelectedItem as CultureInfo;
            CultureResources.ChangeCulture(culture);
        }
    }
}

ちょっと面倒ですが、やっていることは単純なので一度書けば使い回し出来るでしょう。