11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ASP.NET MVC ベリーシンプル多言語対応2020

Last updated at Posted at 2020-03-07

概要

ASP.NET MVC で多言語対応をこころみてみます。多言語対応だけシンプルにこころみます。デモはこちら。

0.png

オライリー本を読んだので、前回より若干洗練されています。(だといいな)

ベリーシンプル多言語対応

前回は Accept-Language に応じた言語切り替えを扱いましたが、今回は URL によって切り替わるパターンも扱います。

1.png

  1. /Home/Index を、 http リクエストの Accept-Language に応じて言語が切り替わるページとする
  2. /Home/About を、 URL によって言語が切り替わるページとする
    • /ja/Home/About のとき日本語、 /en/Home/About のとき英語、ということ
    • /Home/About に直接アクセスすると、勝手に /.../Home/About へリダイレクト
  3. ページを日本語にする(/ja/ に遷移する)リンクと英語にする(/en/ に遷移する)リンクをつくる

興味のあるパターンだけ見てもらえればいいと思います。完成品のプロジェクトはここ↓においてあります。

デモはこちら。(デモは予告なく閉じることがあります。)

今回やることを図にして整理

/Home/Index についてはこう。

2.png

/Home/About と /Home/Contact についてはこう。

3.png

今回使うプロジェクト

今回使うプロジェクトは次のように New しました。

  • ASP.NET Web Application (.NET Framework)
  • .NET Framework 4.7.2
  • MVC
  • Authentication: No Authentication
  • Place solution and project in the same directory のチェックは外す

各言語リソースファイル作成

リソースファイルは拡張子 resx のファイルですね。ここに言語ごとにテキストを書いておきます。リソースファイルは

  • [好きな名前].[culture の名前].resx

という名称でつくります。デフォルトの言語リソースと、多言語のリソースは同じ階層に並べましょう。リソースファイルのアクセス修飾子は忘れず Public にします。(だいじ)

  • I18n.resx: デフォルトで使うもの
  • I18n.ja.resx: ja カルチャで使うもの
  • I18n.en.resx: en カルチャで使うもの

場所は?

場所はどこでもよさそうです。私はたとえば /Home/Index 用のリソースは Resources/Home/Index/I18n.resx みたいなフォルダ階層をつくって置きます。実際に Razor ファイル(cshtml ファイル)で使うときの表記が @I18n.PageTitle みたいになって見やすいからです。 I18n は internationalization の略です。

カルチャ?

カルチャというのは、 http リクエストヘッダ Accept-Language の値のことみたいです。サイトの閲覧者さんが、何語を求めているかということですね。 C# 的には、 CurrentCultureCurrentUICulture にそれをセットすれば、上で作った各リソースが自動で呼び出されます。

// CurrentCulture はフォーマット形式とかに使われる。
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en");
// CurrentUICulture はリソースを切り替えるのに使われる。
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en");

ふたつ挙げましたが、今回はリソースの切り替えしか扱わないので CurrentUICulture しか関係ないです。

resx なくない?

ないですね。

1.png

ないときもあるみたいです。そういうときはリストにとらわれず resx の拡張子を手打ちしてファイルを作成すれば OK です。(えぇー?!
あるいはすでにある resx ファイルをどこかから見つけてきて、 Add Existing Item から追加することもできます。

1.png

リソースに書いたテキストを使うには?

String1 というテキストを用意したなら……

4.png

こういうふうに呼び出します。

@i18nApp.Resources.Home.Index.I18n.String1

あるいは

@using i18nApp.Resources.Home.Index
@I18n.String1

Accept-Language に応じた言語切り替え

/Home/Index を Accept-Language によってリソースが切り替わるページにします。まず /Home/Index の View(cshtml) がリソースを使うようになっていないと話になりません。

Views/Home/Index.cshtml
@using i18nApp.Resources.Home.Index

<div class="jumbotron">
    <h1>
        @I18n.String1
    </h1>
</div>

こう書けばとりあえずデフォルトのリソースを使ってくれます。 Accept-Language に応じたリソースを使って欲しいときはトップディレクトリの Web.config の system.web タグ内に <globalization culture="auto" uiCulture="auto" /> を記述します。

web.config
<system.web>
  <compilation debug="true" targetFramework="4.7.2" />
  <httpRuntime targetFramework="4.7.2" />
  <globalization culture="auto" uiCulture="auto" />  <!-- 追加 -->
</system.web>

リクエスト・ヘッダの Accept-Language に応じて、自動で CurrentUICulture が変わるようになります。 CurrentUICulture に応じたリソースが呼び出されるので、これで /Home/Index は完成です。

Accept-Language はどう変更する?

  • Chrome: メニュー > 設定 > 詳細設定 > 言語
  • Firefox: メニュー > オプション > 言語

URL による言語切り替え

/Home/About を URL によってリソースが切り替わるページにします。 /Home/About の View(cshtml) を次のようにしておきます。リソースはあらかじめ用意してください。

Views/Home/About.cshtml
@using i18nApp.Resources.Home.About

<div class="jumbotron">
    <h1>
        @I18n.String1
    </h1>
</div>

{culture}/{controller}/{action} という URL を受け付けるようにする

そもそも ASP.NET MVC はデフォルトではそんな URL を受け付けません。 RouteConfig.cs を見ればわかるように、受け付けるのは {controller}/{action} という形式だけです。 {culture}/{controller}/{action} も受け付けるように、設定します。

App_Start/RouteConfig.cs
// これを追加。
routes.MapRoute(
    // 名前はてきとー。
    name: "I18n",
    url: "{culture}/{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional, culture = "ja" },
    // {culture} には ja か en しか認めない、という設定。
    constraints: new { culture = "ja|en" }
);

// 最初からあるやつ。
routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

/en/Home/About みたいな URL でアクセスされたら、コントローラで culture = "en" を受け取れるようになりました。受け取った culture を CurrentUICulture にセットすれば、それに応じたリソースが呼ばれます。ヨシ!

URL の culture を CurrentUICulture にセット

で、どこでセットするかなんですが、コントローラの各アクションメソッド内ではやりたくないので、

// やりたくないやつ。
public ActionResult About(string culture)
{
    Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
    return View();
}

アクション実行前に呼び出されるメソッド OnActionExecuting にその処理を追加しましょう。

Helpers/I18nAttribute.cs
using System.Web.Mvc;
using System.Threading;
using System.Globalization;

namespace i18nApp.Helpers
{
    public class I18nAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            // culture が null だったら(/Home/About でアクセスされたということ)
            // /ja/Home/About にリダイレクトしたいところだけどそれは後述。
            string culture = filterContext.ActionParameters["culture"] as string;
            Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);
        }
    }
}

そしてこの OnActionExecuting を /Home/About に適用するには、アクションメソッドにこんなふうにくっつければ OK です。

Controllers/HomeController.cs
using i18nApp.Helpers;

// これで↑の OnActionExecuting を事前に実行してくれる。
[I18nAttribute]
public ActionResult About(string culture)
{
    return View();
}
  1. /ja あるいは /en/Home/About でアクセスされる
  2. OnActionExecuting メソッドで CurrentUICulture を ja あるいは en にセットする
  3. それに応じたリソースが View で表示される

これで /Home/About も完成です。デモページでは /Home/Contact も同様の実装にしてあります。

OnActionExecuting もりもりセット

実際のところ、アクションメソッド実行前にやってほしいことはもっとあります。

  • /Home/About でアクセスされたら /ja/Home/About にリダイレクトしてほしい
  • /en でアクセスされたら次からデフォルトの言語は en にしてほしい

そのへんを盛り込んでおきます。

Helpers/I18nAttribute.cs
public class I18nAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // NOTE:
        // アクションパラメータを取得: filterContext.ActionParameters["culture"]
        // GET クエリを取得: filterContext.HttpContext.Request.QueryString["key"]

        // /{controller}/{action} のアクセスを /{culture}/{controller}/{action} へリダイレクト。
        if (!(filterContext.ActionParameters["culture"] is string culture))  // culture が null だったら、という書き方
        {
            // リクエストされた URI。
            Uri requestUri = filterContext.HttpContext.Request.Url;
            var uriBuilder = new UriBuilder(requestUri);

            // URI を変更。
            // デフォルトは ja だけれど、クッキーに値が入っていればそれをデフォルトとする。
            string initialDefaultCulture = "ja";
            HttpCookie cookieCulture = filterContext.RequestContext.HttpContext.Request.Cookies["culture"];
            if (cookieCulture != null && !string.IsNullOrEmpty(cookieCulture.Value))
            {
                initialDefaultCulture = cookieCulture.Value;
            }
            uriBuilder.Path = initialDefaultCulture + uriBuilder.Path;

            // リダイレクト。
            filterContext.Result = new RedirectResult(uriBuilder.ToString());
            return;
        }

        // RouteConfig で culture を ja か en に絞っているので culure の値チェックは不要。
        // NOTE: CurrentCulture is used to switch formats.
        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(culture);
        // NOTE: CurrentUICulture is used to switch resources.
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(culture);

        // /ja や /en でアクセスされたらその値をクッキーに保存。
        var cookie = new HttpCookie("culture")
        {
            Value = culture
        };
        filterContext.HttpContext.Response.Cookies.Add(cookie);
    }
}

URL を日本語版、あるいは英語版にするリンク

次のように Html.ActionLink を書くことで、現在の URL に /ja や /en を付与したページへ遷移するリンクを作れます。

Views/Shared/_Layout.cshtml
<li>
    @Html.ActionLink(
       "日本語",
       this.ViewContext.RouteData.Values["action"].ToString(),
       this.ViewContext.RouteData.Values["controller"].ToString(),
       new { culture = "ja" },
       null
    )
</li>
<li>
    @Html.ActionLink(
       "English",
       this.ViewContext.RouteData.Values["action"].ToString(),
       this.ViewContext.RouteData.Values["controller"].ToString(),
       new { culture = "en" },
       null
    )
</li>

もともとある Index, About, Contact へのリンクはそのままで OK です。

Views/Shared/_Layout.cshtml
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("About", "About", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>

new { culture = "ja" } とかをつけなくても自動で /ja/Home/About みたいなリンクになります。 ASP.NET MVC は現在の MapRoute 情報とアクションパラメータの値を保持するためです。

おしまい

ASP.NET Core が気になります。 Mac や Visual Studio Code で C# が書けるなんて素敵すぎる。

11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?