ASP.NET での多言語対応についておさらいしておく - しばやん雑記 の続きです。
ひとまず Razor を使っている場合でもリソースを使って多言語対応を行うことが出来ましたが、リソースの配置などでまだ納得いっていない部分があるのでもう一度ブログに書く予定です。
さて、本題の多言語対応ですがビューエンジンが ASPX だろうと Razor であろうと、基本的な部分は ASP.NET のインフラストラクチャ上に構築されているので関係ありません。違いを挙げるとすればリソース式があるかないかの違いぐらいです。
とりあえず実際のコードを見ていくことにしましょう。まずはグローバルリソースです。
グローバルリソースを使う
まずは resx ファイルを保存するための App_GlobalResources ディレクトリを作成します。ソリューションエクスプローラ右クリックで表示されるメニューから「ASP.NET フォルダの追加」を選択すると簡単に作れます。
そしてリソースファイルを追加するのですが、今回は StringResources.resx という名前を付けました。特定のロケール専用のリソースを作る場合には、ファイル名を以下のような形にするだけです。
[リソースファイル名] + "." + [カルチャ名] + ".resx"
追加が終わると以下のようなフォルダ構成になっているはずです。
グローバルリソースから値を取得するために HttpContext.GetGlobalResourceObject 静的メソッドを利用します。使い方は極めてシンプルで、リソースが格納されたクラス名とキー名を指定するだけです。
@HttpContext.GetGlobalResourceObject("StringResources", "Message")
このメソッドが返す型は object ですが、出力時には基本的に ToString されるようになっているのでキャスト無しでも正常に動作します。
ローカルリソースを使う
まずはグローバルリソースの時と同じように resx ファイルを保存するための App_LocalResources ディレクトリを作る必要があるのですが、Views ディレクトリ以下の cshtml と同じ階層に作る必要があります。
あと resx のファイル名は自由に付けることが出来ません。App_LocalResources にはグローバルリソースの場合とは異なり
[適用したいビューのファイル名] + "." + [カルチャ名] + ".resx"
というファイル名を付ける必要があります。カルチャを省略すると、指定されたカルチャに該当するリソースが存在しなかったときに使われます。
実際に作ってみると以下のような形になります。今回はフォールバック用のリソースと日本語のリソースを追加しました。
ローカルリソースから値を取得するために HttpContext.GetLocalResourceObject メソッドを利用します。
@HttpContext.GetLocalResourceObject("Message") object ですが、グローバルリソースの時と同様に ToString されるので問題ありません。
Web.config の設定
以上でリソースの準備は完了しましたが、これだけではブラウザの言語設定を切り替えても表示が切り替わってくれません。
原因は Web.config にあり!ということで、ちゃんと設定をしてあげましょう。下記の 1 行を
<globalization culture="auto" uiCulture="auto" />
globalization の定義を省略してしまうと、デフォルトのカルチャになってしまうのでサーバの設定に依存してしまいます。auto にするとブラウザの言語設定を見て、自動的に切り替えてくれます。
それでは確認してみましょう。まずは言語設定が日本語の場合です。
ちゃんと日本語でこんにちは世界が表示されていますね。グローバルリソース、ローカルリソース共に問題ないです。
では、言語設定から日本語を削除し、英語を指定した場合です。
ばっちり英語で Hello, world が表示されましたね!こちらもグローバルリソース、ローカルリソース共に問題ありませんでした。
やっぱり楽をしたい
毎回 HttpContext のメソッド呼び出しを書くのはめんどうですよね!そんな時のための HtmlHelper なんですが、既に ASPX 用は ASP.NET MVC - Localization Helpers で公開されています。素晴らしい!
今回はこのヘルパーを Razor に対応させてみます。ついでに少しだけ MVC 3 っぽくもしてみます。
public static class LocalizationHelpers { // MvcHtmlString を返すように変更 public static MvcHtmlString Resource(this HtmlHelper htmlhelper, string expression, params object[] args) { var virtualPath = GetVirtualPath(htmlhelper); return MvcHtmlString.Create(GetResourceString(htmlhelper.ViewContext.HttpContext, expression, virtualPath, args)); } public static string Resource(this Controller controller, string expression, params object[] args) { return GetResourceString(controller.HttpContext, expression, "~/", args); } private static string GetResourceString(HttpContextBase httpContext, string expression, string virtualPath, object[] args) { var context = new ExpressionBuilderContext(virtualPath); var builder = new ResourceExpressionBuilder(); var fields = (ResourceExpressionFields)builder.ParseExpression(expression, typeof(string), context); if (!string.IsNullOrEmpty(fields.ClassKey)) return string.Format((string)httpContext.GetGlobalResourceObject(fields.ClassKey, fields.ResourceKey, CultureInfo.CurrentUICulture), args); return string.Format((string)httpContext.GetLocalResourceObject(virtualPath, fields.ResourceKey, CultureInfo.CurrentUICulture), args); } private static string GetVirtualPath(HtmlHelper htmlhelper) { // WebFormView 決め打ちではなくて基底クラスにキャストするように変更 var view = htmlhelper.ViewContext.View as BuildManagerCompiledView; if (view != null) return view.ViewPath; return null; } }
VirtualPath の取得部分を変更しただけです。HTML ヘルパーなので実際に使うときには
@* グローバルリソース *@ @Html.Resource("StringResources, Message") @* ローカルリソース *@ @Html.Resource("Message")
と書くだけです、凄いスッキリしましたね!
これで一応 MVC 3 + Razor でも多言語対応を行うことが出来ましたが、ローカルリソースの保存ディレクトリが非常に気に食わないので、後日また検証して見たいと思います。