プログラムを中心とした個人的なメモ用のブログです。 タイトルは迷走中。
内容の保証はできませんのであしからずご了承ください。

2017/09/15

ASP.NET Core における多言語対応

update2017/12/13 event_note2017/09/15 2:14

ASP.NET Core において、多言語に対応したサイトを構築すればどうすればいいかについて調べてみました。
基本的には Microsoft のサイトで解説されている通りです。

ちなみに、MSDN では言語コードと国コードの組み合わせのことをカルチャーと表記していたので、ここでもカルチャーという言葉を使用したいと思います。

環境

  • Visual Studio 2017
  • ASP.NET Core 2.0

以下、ASP.NET Core 2.0 のテンプレートプロジェクトをベースに説明しています。

準備

ASP.NET Core でローカライズを行う場合、DI により IStringLocalizer<T>IHtmlLocalizer<T> を注入するようです。

まずは Startup.csConfigureServices メソッドで、以下のように AddLocalization AddViewLocalization AddDataAnnotationsLocalization を追加します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization(options => options.ResourcesPath = "Resources");

    services.AddMvc()
        .AddViewLocalization(
            LanguageViewLocationExpanderFormat.Suffix,
            opts => { opts.ResourcesPath = "Resources"; })
        .AddDataAnnotationsLocalization();
}

ResourcesPath でリソースファイルのある場所を指定しています。
ここではプロジェクトの配下に Resources というフォルダを作成して、そこに後述するリソースファイルを配置するようにしています。

LanguageViewLocationExpanderFormat.Suffix はビューファイル(cshtml ファイル)をカルチャー毎に分ける場合に、そのファイル名をサフィックスに基づいて解決することを指定しています(後述)。

AddDataAnnotationsLocalization は DataAnnotations のローカライズを行うために必要なので、今回の内容の範囲外となります。

リクエストに対するカルチャーの選択

HTTP リクエストに対するカルチャーの設定はミドルウェアで処理されます。

具体的には Startup.csConfigure メソッドで以下のようにして有効化します。

using System.Globalization;
using Microsoft.AspNetCore.Localization;

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // 中略

    var supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("en-AU"),
        new CultureInfo("en-GB"),
        new CultureInfo("en"),
        new CultureInfo("es-ES"),
        new CultureInfo("es-MX"),
        new CultureInfo("es"),
        new CultureInfo("fr-FR"),
        new CultureInfo("fr"),
    };

    app.UseRequestLocalization(new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture("en-US"),
        // Formatting numbers, dates, etc.
        SupportedCultures = supportedCultures,
        // UI strings that we have localized.
        SupportedUICultures = supportedCultures
    });

    app.UseStaticFiles();

    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

リクエストからカルチャーを決定できない場合には、DefaultRequestCulture で指定されたカルチャーが使用されます。
(上記では、未対応のカルチャーに対しては英語が表示されます。)

一応注意として、app.UseMvcWithDefaultRoute() などは内部でリクエストのカルチャーをチェックする可能性があるらしいので、その前に構成する必要があるようです。

リソースファイルの作り方と命名規則

実際に表示される文字は、リソースファイルを作成してそこに記入していきます。
リソースファイルは以下のようにして追加します。

  1. プロジェクトを右クリックし、[追加] > [新しい項目] を選択します。
  2. [リソースファイル]を選択して作成します。

リソースファイルを作成するとデザイナーが表示され、名前と値の組み合わせが記入できるようになっていると思います。

※詳しい手順はMicrosoft のサイトに画像付きで載っています。

名前 にキーを入力し、 にローカライズした場合の文字列を書きます。

尚、リソースファイル名は以下のようなネーミングルールとなっており、フォルダの階層で分ける方法と、ファイル名をドットで区切る方法があるみたいです。

Controller の場合

  • Resources/Controllers/HomeController.ja.resx
  • Resources/Controllers.HomeController.ja.resx

View の場合

  • Resources/Views/Home/About.ja.resx
  • Resources/Views.Home.About.ja.resx

また、View の場合は cshtml ファイル自体をカルチャー毎に分けることもできます。
この場合、リソースファイルと同じように、ファイル名を index.ja.cshtml にして作成するようですが、ここでは扱いません。

上記では日本語の例として ja を指定していますが、カルチャー名は省略することもでき、カルチャー名のないリソースファイルはデフォルトのリソースファイル(未対応のカルチャーが指定された場合に使用されるリソースファイル)として扱われます。
尚、Visual Studio でカルチャー名を省略してリソースファイルを作成すると、各文字列のプロパティを持つ C# クラス (.Designer.cs) が自動で作成されますが、ASP.NET Core ではこれは不要なようです。

また、Microsoft のページではデフォルトのリソースファイル(カルチャー名のないリソースファイル)は多くの場合必要ないと書かれていますが、個人的には英語はカルチャー名のないリソースファイルで作成するのがよいのではないかと思っています。

理由は、長文の場合にキーに直接記述すると大変なので、抽象化した文字列をキーとして使用したほうが良いのではないかと考えているからです。
実際、GitHub でいろいろな人のリソースファイルの書き方を調べてみると、そのように書いている人が多い印象でした。

使用方法

Controller の場合

DI で IStringLocalizer を受けます。
例えば HomeController の場合は以下のようにです。

private readonly IStringLocalizer<HomeController> localizer;

public HomeController(IStringLocalizer<HomeController> localizer)
{
    this.localizer = localizer;
}

この localizer を使って例えば以下のように書きます。

public IActionResult About()
{
    //ViewData["Message"] = "Your application description page.";
    ViewData["Message"] = localizer["hoge"];

    return View();
}

ここで hoge という文字列がローカライズされていれば、ローカライズされた文字列が表示されます。

View の場合

以下のように記述することでローカライズできます。

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

@{
    ViewData["Title"] = Localizer["hoge"];
}

ローカライズされていない場合の挙動

ローカライズされていない場合、以下のように動作しました。

  • デフォルトのリソースファイル(カルチャー名のないリソースファイル)があれば、そのリソースファイルで定義された値が表示される
  • デフォルトのリソースファイルがない、またはデフォルトのリソースファイルでも名前(キー)が定義されていない場合、hoge という値がそのまま表示されるようです。

デフォルトのリソースファイル(カルチャー名のないリソースファイル)は必須ではありませんが、上記のような動作と前述した理由により個人的には作成したほうが良いと思っています。

カルチャーの指定方法

基本的にはブラウザの設定によって適切なカルチャーが設定されて画面が表示されます。
ユーザー側が任意のカルチャーを指定するには、いくつか方法があるようです。

QueryStringRequestCultureProvider

URLにクエリ文字列を追加して指定する方法で、コードのデバッグやテストに役立ちます。

  • http://localhost:5000/?culture=ja-JP&ui-culture=ja-JP

culture は Controller、ui-culture は View 用の指定です。
culture=ja-JP とだけ指定すると、ui-culture でも同じ値が使用されます。

CookieRequestCultureProvider

※追々書くかも

The Accept-Language HTTP header

※追々書くかも

言語が切り替わらないときは?

ここが一番はまりました・・・。

リソースファイルもプログラムも正しいのに言語が切り替わらない。
しかも Debug モードだけ。
Release モードでは上手く切り替わる。

原因不明ですが、bin フォルダの中の Debug フォルダを一度削除したら正しく切り替わるようになりました。

参考 URL