しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ちょっと変わった ASP.NET MVC 3 でのローカライズ方法

ローカライズって面倒ですよね。

ASP.NET MVC 3 の Razor でも多言語対応を試してみる - しばやん雑記 でリソースを使った Razor でのローカライズ方法を書きましたが、App_GlobalResources と App_LocalResources というディレクトリを作るのは面倒です。特に App_LocalResources をビューのディレクトリごとに作らないといけないとか苦痛です。

折角 ASP.NET MVC では CoC が使われているので、リソースも規約でいろいろ出来たらいいなと思ってたら出来ました。ディレクトリ構成は以下のような感じになってます。

みにもば用に書いていたので

Minimoba/Resources/Views/Shared/_Layout.resx

というパスになっています。名前空間も直接対応しているので

Minimoba.Resources.Views.Shared._Layout

という形です。Resources 以下が Views の構成と一致していることが分かって貰えると思います。単純に言えば、ビューの仮想パスからこの形のパスを作るだけです。パスを作ってしまえば ResourceManager に突っ込めばあとはお任せです。

しかし、今回は HTML ヘルパーという形を選択したのでビューの仮想パスを取得するのに迷いました。仮想パスを取得するために Razor 限定になってしまったのですが、詳しくはソース読んでください。

追記

ついったーで TemplateInfo を使った方がいいよと指摘を受けたので修正しました。TemplateFileInfo には VirtualPath というそのまんまのプロパティがあったんですね・・・。知りませんでした。

public static class ResourceExtensions
{
    private static readonly string RootNamespace = typeof(MvcApplication).Namespace;
    private static readonly Dictionary<string, ResourceManager> ResourceManagers = new Dictionary<string, ResourceManager>();

    public static string Resource(this TemplateFileInfo info, string name, params object[] args)
    {
        // ビューの仮想パスから resx のパスを作成する
        var resourcePath = GetResourcePath(info.VirtualPath);

        if (resourcePath == null)
        {
            return null;
        }

        ResourceManager resourceManager;

        // 既にリソースマネージャを作成しているか確認する
        if (!ResourceManagers.TryGetValue(resourcePath, out resourceManager))
        {
            resourceManager = new ResourceManager(resourcePath, Assembly.GetExecutingAssembly());

            ResourceManagers.Add(resourcePath, resourceManager);
        }

        // リソースから文字列を取得して、フォーマットして返す
        return string.Format(resourceManager.GetString(name, Thread.CurrentThread.CurrentUICulture), args);
    }

    private static string GetResourcePath(string virtualPath)
    {
        if (virtualPath == null)
        {
            return null;
        }

        // ~/Views/Shared/_Layout.cshtml のような文字列から ~/ と .cshtml を削除する
        var tokens = virtualPath.Substring(2, virtualPath.Length - 9).Split('/');

        if (tokens.Length == 0)
        {
            return null;
        }

        var textInfo = CultureInfo.InvariantCulture.TextInfo;

        // 先頭文字を大文字にしつつ、リソースのパスを作成
        return string.Format("{0}.Resources.{1}", RootNamespace, string.Join(".", tokens.Select(textInfo.ToTitleCase)));
    }
}

普通なら WebViewPage を継承したクラス作るべきなんでしょうけど、あまりやりたくなかったので HTML ヘルパーです。コピペするだけでたぶん使えます。

使い方はいつも通りのヘルパーなので正直説明要らないですよね。とりあえず書いておきます。

@* リソースから取得 *@
<h1>@TemplateInfo.Resource("TitleString")</h1>

@* フォーマットして取得 *@
<h2>@TemplateInfo.Resource("FormatString", 100, 3.14)</h2>

ちゃんと WebViewPage から仮想パスを取得しているので、レイアウトを指定している場合でも正しいリソースを参照することが出来ます。自分的には納得できるものが出来たので、ようやくみにもばのローカライズが進みそうです。