syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

WPFアプリのローカライズ(OS言語自動一致版)

WPF ローカライズの方法はいくつかあるんですが、今回は英語版をベース(ニュートラル言語)として、日本語にローカライズされた文字列テーブルを追加する手順を説明します。OS の UI 言語設定に合わせてアプリ起動時に自動的に文言をローカライズすることを前提としています。

[1]
[アプリケーション] の [アセンブリ情報] で [ニュートラル言語] が [(なし)] になっていること、また "Resources.resx" という名前のリソース ファイルがすでにプロジェクト内に存在することを確認する。
[新しい項目の追加] で [アセンブリ リソース ファイル] を選び、"Resources.ja-JP.resx" という名前でプロジェクトの Properties フォルダーにファイルを追加する。

ちなみに [プロパティ] の [カスタム ツール] を [PublicResXFileCodeGenerator] にすると、空のファイル "Resources.ja-JP.Designer.cs" が自動生成されるようになるが、これは別に必要ないらしい。

[2]
ソリューション エクスプローラーで "Resources.resx" と "Resources.ja-JP.resx" をそれぞれダブルクリックして、同一の名前(例えば button1_Content)の英語および日本語リソース文字列をそれぞれに追加する。
ベースとなる英語版のリソースは、アクセス修飾子を Internal ではなく Public にしておくこと。

[3]
後は個別に XAML マークアップ拡張を使って YourAppProjName.Properties.Resources クラスの静的プロパティを割り当てれば自動的に文言をローカライズしてくれます。また、ビルド時にサテライト リソース アセンブリが暗黙的に生成されます。

<Window
    x:Class="YourAppProjName.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:YourAppProjName"
    xmlns:props="clr-namespace:YourAppProjName.Properties"
    >
    ...
    <Button Name="button1" Content="{x:Static props:Resources.button1_Content}"/>
    ...
</Window>

エラーメッセージのローカライズなど、C# コード ビハインドで参照する場合は、いったんビルドした後に

global::YourAppProjName.Properties.Resources.button1_Content

のようなラッパープロパティが自動的に生成されているのでそれを使えばよいです。
プロパティ名が小文字で始まっていたり、アンダースコアが含まれていたりするのが気持ち悪い人は .NET 命名規則に合わせて命名しましょう。

ちなみに明示的にカルチャーを指定して、OS 設定によらず任意の言語に動的ローカライズする場合は、(MFC の場合と同様に)別途コード ビハインドでの処理が必要となるんですが、この方法に関してはまた機会があれば解説しましょう。

なお、下記のように App.xaml の Application.Resources に YourAppProjName.Properties.Resources クラスのインスタンスを追加して、バインディングを使ってローカライズする方法(Windows Phone アプリのサンプル含む)を見かけるんですが、これは NG です。

<Application
    x:Class="YourAppProjName.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:YourAppProjName"
    xmlns:props="clr-namespace:YourAppProjName.Properties"    
    StartupUri="MainWindow.xaml">
    <Application.Resources>
        <!-- これは NG。 -->
        <props:Resources x:Key="MyLocalizedResourceKey" />
    </Application.Resources>
</Application>
<Button Name="button1" Content="{Binding Source={StaticResource MyLocalizedResourceKey}, Path=button1_Content}"/>

App.xaml で YourAppProjName.Properties.Resources クラスをインスタンス化してしまうと、起動時にハンドルされない例外 System.Windows.Markup.XamlParseException が発生してアプリが起動できなくなることがあります。
どうも上記例の MyLocalizedResourceKey や button1_Content の名前を変更したり、EXE とは別の関連 DLL アセンブリC# コードを変更してビルドしたりした直後に発生する模様。
その後、なぜか EXE 側の C# コードになんらかの変更を加えて(タイム スタンプを更新して)ビルドし直さないと、ずっと例外が発生し続ける模様。
XAML だけが変更されたとき、リソース ファイルがビルドし直されないせいかと思ったんですが、単にリビルドを実行しても改善しません。出力ファイル・中間ファイルやソリューション ユーザー オプション ファイル(.suo)を削除してクリーン状態からリビルドし直してもダメです。必ず EXE 側 C# コードの編集が要るようになってしまいます。
ちなみに VS 2012 だけでなく、VS 2010 でも同様の現象が発生していたらしいです。
.NET 自体のバグという推測もあります。
(もしくは Visual Studio IDE 側のバグ?)
そもそもアクセスしているのは静的プロパティなので、インスタンス化の必要はないはずなわけですが……

他の方法

今回説明したものは WPFローカライズする方法のうち、最も簡単なもので、LocBaml を使う方法とは違って、MFCWindows Forms に近いです。
他にも、ResourceDictionary を使ってローカライズ済みサテライト リソース アセンブリを明示的に作成する方法があります。
それぞれの手法のメリット・デメリットの比較は下記。