ASP.NET MVC Web アプリケーションのデータアノテーション属性を使用する際、リソースファイル(.resx)を使って表示されるメッセージを多言語化する方法を書きます。
先の記事 コレクションのデータアノテーション検証 では、モデルのソースコードに中でメッセージをハードコーディングしました。
この記事では、メッセージをリソースファイルに格納し、そこからメッセージを取得して表示します。さらに、日本語と英語のリソースファイルを追加し、ブラウザの言語設定によって言語を切り替える例も紹介します。
さて、まずリソースファイルの格納場所ですが、どこが適当でしょうか?
ASP.NET には App_GlobalResources, App_LocalResources というリソースファイルを格納する専用のフォルダがあります。しかし、そのフォルダにリソースファイルを置くとユニットテストの際に問題があるそうです。(詳しくは Resource Files and ASP.NET MVC Projects の記事を参照)
ということで、この記事では App_GlobalResources フォルダは使わないで、とりあえずアプリケーションルート直下に直接置きました。(フォルダに入れる場合、名前空間にそのフォルダ名が追加になるので注意)
まず、Visual Studio のソリューションエクスプローラーを操作して[新しい項目の追加]ダイアログを表示し、[アセンブリ リソースファイル]を選んでリソースファイルを追加します。下の画像の例ではリソースファイル名を Resources1.resx という名前にしました。そうすると自動生成される Resource1.Designer.cs に定義される「厳密に型指定されたリソースクラス」において Resources1 がクラス名になります。
リソースファイルを追加したら、下の画像のように、Visual Studio の左側のウィンドウでその内容を設定します。表示するメッセージを[値]欄に、そのメッセージを取得するキー名を[名前]欄に記入します。ここで設定したものがデフォルトのメッセージとなります。
上の画像の例では、データアノテーション属性として DisplayAttribute, RequiredAttribute, StringLengthAttribute の 3 つを使用するという前提で、それぞれ Name, Required, StringLength というキー名(名前は任意です)でメッセージを設定しています。上のメッセージは説明用にテキトー書いたものです。実際にはデフォルトにふさわしいメッセージにしてください。
アクセス修飾子の設定を Public にするのを忘れないでください(上の画像で赤枠で囲んだ部分)。デフォルトでは Internal です。Public にするのを忘れると、後でユニットテストをする際に困ることになるはずです。(ちなみに、Internal ⇒ Public に変更すると、プロパティウィンドウで見て、カスタムツールが ResXFileCodeGenerator ⇒ PublicResXFileCodeGenerator に変わります)
Resources1.resx の設定が終わったら、日本語用に Resources1.ja-JP.resx、英語用に Resources1.en-US.resx という名前のリソースファイルを追加します。下の画像は日本語用の Resources1.ja-JP.resx の設定です。英語用も同様に(もちろんメッセージは英語で)設定します。
ja-JP, en-US というカルチャ名をリソースファイル名に追加するところがミソです。これによってブラウザの言語設定を ASP.NET が検出して(=要求ヘッダ情報を見て)自動的に使用するリソースファイルが切り替わります。
設定が完成すると以下の画像のようなリソースファイルができているはずです。(赤枠で囲った部分)
次に、モデルでデータアノテーション属性を以下のように設定します。この例では Parent2 クラスの Name プロパティのみにリソースファイルからメッセージを取得して表示するようにしています。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace Mvc4App.Models
{
public class Parent2
{
public Parent2()
{
Children = new List<Child2>();
}
public int Id { get; set; }
[Required(
ErrorMessageResourceType=typeof(Mvc4App.Resource1),
ErrorMessageResourceName="Required")]
[StringLength(
5,
ErrorMessageResourceType = typeof(Mvc4App.Resource1),
ErrorMessageResourceName = "StringLength")]
[Display(
Name = "Name",
ResourceType = typeof(Mvc4App.Resource1))]
public string Name { get; set; }
public virtual IList<Child2> Children { get; set; }
}
public class Child2
{
public int Id { get; set; }
[Required(ErrorMessage = "{0} は必須")]
[StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
[Display(Name = "Child Name")]
public string Name { get; set; }
public virtual Parent2 Parent { get; set; }
}
}
RequiredAttribute, StringLengthAttirbute は ErrorMessageResourceType プロパティに Resource1.Designer.cs で定義される「厳密に型指定されたリソースクラス」の型を指定します(この例では typeof(Mvc4App.Resource1) です。Mvc4App が名前空間名、Resource1 がクラス名)。ErrorMessageResourceName プロパティにはリソースファイルに設定したキー名を設定します。
DisplayAttribute は、上の 2 つの属性とは設定するプロパティが異なり、ResourceType プロパティに「厳密に型指定されたリソースクラス」の型、Name プロパティにキー名を設定します。
最後に、web.config で globalization 要素の uiCulture, culture 属性を auto に設定します。これを忘れると、ブラウザの言語指定によるリソースファイルの自動切り替えは行われないので注意してください。(詳しくはこの記事の下の方の「2016/6/17 追記」を見てください)
<system.web>
<globalization uiCulture="auto" culture="auto" />
</system.web>
これにより、例えば IE の言語の優先順位の設定を、下の画像のように日本語を最優先にしておくと、日本語のリソースファイル Resources1.ja-JP.resx からメッセージを取得して表示します。
その結果が一番上の画像です。
-------- 2016/6/17 追記 --------
Culture, UICulture を "auto" に設定すると、ASP.NET は、ブラウザから送信されてくる要求ヘッダに含まれる Accept-Language の設定を調べて、その要求を処理するスレッドのカルチャを Accept-Language に設定されているカルチャに書き換えるようです。
そして、リソースマネージャが実行時に、Thread.CurrentUICulture などで得られる CultureInfo(現在の要求を処理しているスレッドのカルチャ情報)を参照してローカライズされたリソースを検索し、UI に表示されるテキストを取得するという仕組みになっています。
Culture, UICulture を "auto" に設定するのを忘れるとブラウザの言語設定は無視されます。デフォルトではシステムのロケールに該当するカルチャがスレッドに設定されますので、例えば日本語 OS で xxx.ja-JP.resx というリソースがあれば、常にそれから UI に表示されるテキストを取得します。
Web サイトが日本語専用でサーバーも日本にあれば忘れても問題ないかもしれませんが、ホスティングサービス(Azure も含む)でサーバーが外国にある場合は Culture, UICulture を "auto" に設定するのを忘れると問題が出ると思います。
ロケール、カルチャ、Culture と UICulture の違いなどについては、記事「カルチャの基本とカルチャ情報」が参考になりましたので、忘れないようにリンクを張っておきます。