全球化的網站不免都要做多國語言,ASP.NET Core 的多國語言設定方式跟 ASP.NET MVC 有很大的落差。
本篇將介紹 ASP.NET Core 多國語言 (Localization) 的設定方式。
同步發佈至個人部落格:
John Wu's Blog - [鐵人賽 Day21] ASP.NET Core 2 系列 - 多國語言 (Localization)
過去 ASP.NET 語系檔都是用 *.resx
格式,現在 ASP.NET Core 也是沿用此格式,但檔案結構確很不一樣。ASP.NET Core 語系檔命名規則必須要與類別的 namespace
階層相互對應。例如 Controllers、Views、Models 要用的語系檔跟類別對應如下:
多國語言檔建立規則跟 ASP.NET MVC 有很大的差別。
*.resx
檔案必須對應使用的路徑位置。*.resx
檔案的語系帶在後綴。如:*.en-GB.resx
。*.resx
語系檔內容大致如下:
Resources\Controllers.HomeController.en-GB.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Hello">
<value>Hello~ This message from Resources\Controllers.HomeController.en-GB.resx</value>
</data>
</root>
若以 Visual Studio IDE 開發 (如 Visual Studio 2017),可以從 UI 新增資源檔 *.resx
。在網站目錄中建立 Resources 的資料夾,並新增資源檔 *.resx
。如下:
ASP.NET Core 使用多國語言,需要 Microsoft.AspNetCore.Localization
套件。
在此範例中我還需要從 Routing 抓取語系的資訊,所以也需要 Microsoft.AspNetCore.Localization.Routing
套件。
透過 .NET Core CLI 在專案資料夾執行安裝指令:
dotnet add package Microsoft.AspNetCore.Localization
dotnet add package Microsoft.AspNetCore.Routing
ASP.NET Core 2.0 以上版本,預設是參考
Microsoft.AspNetCore.All
,已經包含Microsoft.AspNetCore.Localization
及Microsoft.AspNetCore.Routing
,所以不用再安裝。
在 Startup.ConfigureServices
註冊多國語言需要的服務,以及修改多國語的 Routing 方式。如下:
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.Extensions.DependencyInjection;
namespace MyWebsite
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalization(options => options.ResourcesPath = "Resources");
services.AddMvc()
.AddViewLocalization()
.AddDataAnnotationsLocalization();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{culture=en-GB}/{controller=Home}/{action=Index}/{id?}"
);
});
}
}
}
如果不想用 Routing 的方式,也可以改用 QueryString 帶入語系資訊。
建立一個 CultureMiddleware 來包裝 Localization 的 Middleware,可以做支援語言的管理。
Middlewares\CultureMiddleware.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Localization.Routing;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace MyWebsite.Middlewares
{
public class CultureMiddleware
{
private static readonly List<CultureInfo> _supportedCultures = new List<CultureInfo>
{
new CultureInfo("en-GB"),
new CultureInfo("zh-TW")
};
private static readonly RequestLocalizationOptions _localizationOptions = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture(_supportedCultures.First()),
SupportedCultures = _supportedCultures,
SupportedUICultures = _supportedCultures,
RequestCultureProviders = new[]
{
new RouteDataRequestCultureProvider() { Options = _localizationOptions }
}
};
public void Configure(IApplicationBuilder app)
{
app.UseRequestLocalization(_localizationOptions);
}
}
}
每個 Requset 都會執行 RequestCultureProviders
中的 CultureProvider
,用來判斷語系資訊,套用正確的資源檔。
Microsoft.AspNetCore.Localization
套件支援的 CultureProvider 有三種:
http://localhost:500/?culture=zh-TW
而我是用 Routing 判斷語系資訊,以上三種都不合我用。
Routing 判斷語系可以用 Microsoft.AspNetCore.Localization.Routing
套件的 RouteDataRequestCultureProvider
。
把 CultureMiddleware 註冊在需要用到的 Controller 或 Action。如下:
Controllers\HomeController.cs
[MiddlewareFilter(typeof(CultureMiddleware))]
public class HomeController : Controller
{
// ...
}
通常 ASP.NET Core 網站會伴隨著 API,API 不需要語系資訊,所以不建議註冊在全域。
在 Controller 要使用多國語言的話,需要在建構子加入 IStringLocalizer
參數,執行期間會把 _localizer 的實體注入近來。
把 Resource Key 丟入 _localizer,就可以得到該語系的值。
Controllers\HomeController.cs
using System.Globalization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using MyWebsite.Middlewares;
namespace MyWebsite
{
[MiddlewareFilter(typeof(CultureMiddleware))]
public class HomeController : Controller
{
private readonly IStringLocalizer _localizer;
public HomeController(IStringLocalizer<HomeController> localizer)
{
_localizer = localizer;
}
public IActionResult Index()
{
return View();
}
public IActionResult Content()
{
return Content($"CurrentCulture: {CultureInfo.CurrentCulture.Name}\r\n"
+ $"CurrentUICulture: {CultureInfo.CurrentUICulture.Name}\r\n"
+ $"{_localizer["Hello"]}");
}
}
}
要在 cshtml 使用多國語言的話,要先在 Services 中加入 ViewLocalization
。
注入 IViewLocalizer
,同上把 Resource Key 丟入 Localizer,就可以得到值。
Views\Home\Index.cshtml
@using System.Globalization
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer localizer
CurrentCulture: @CultureInfo.CurrentCulture.Name <br />
CurrentUICulture: @CultureInfo.CurrentUICulture.Name <br />
@localizer["Hello"]<br />
要在 Model 使用多國語言的話,要先在 Services 中加入 DataAnnotationsLocalization
。
Models\SampleModel.cs
using System.ComponentModel.DataAnnotations;
namespace MyWebsite.Models
{
public class SampleModel
{
[Display(Name = "Hello")]
public string Content { get; set; }
}
}
Controllers\HomeController.cs
// ...
[MiddlewareFilter(typeof(CultureMiddleware))]
public class HomeController : Controller
{
public IActionResult Index()
{
return View(model: new SampleModel());
}
}
Views\Home\Index.cshtml
@using System.Globalization
@using MyWebsite.Models
@model SampleModel
CurrentCulture: @CultureInfo.CurrentCulture.Name <br />
CurrentUICulture: @CultureInfo.CurrentUICulture.Name <br />
@Html.DisplayNameFor(m => m.Content)<br />
ASP.NET Core 語系檔命名規則為了與 Controllers、Views、Models 相互對應,可能會產生一大堆檔案,造成維護上的困擾。
因此,可以利用 ASP.NET Core DI 的特性,建立一個共用的語系檔,再將該語系資訊注入至 DI 容器。
建立共用的語系檔 Resources\SharedResource.en-GB.resx,同時建立一個對應的 SharedResource.cs 檔案,內容如下:
Resources\SharedResource.en-GB.resx
<?xml version="1.0" encoding="utf-8"?>
<root>
<data name="Hello">
<value>Hello~ This message from Resources\SharedResource.en-GB.resx</value>
</data>
</root>
SharedResource.cs
using Microsoft.Extensions.Localization;
namespace MyWebsite
{
public class SharedResource
{
private readonly IStringLocalizer _localizer;
public SharedResource(IStringLocalizer<SharedResource> localizer)
{
_localizer = localizer;
}
}
}
IStringLocalizer
注入的型別改成 SharedResource,如下:
Controllers\HomeController.cs
using System.Globalization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using MyWebsite.Middlewares;
using MyWebsite.Models;
namespace MyWebsite
{
[MiddlewareFilter(typeof(CultureMiddleware))]
public class HomeController : Controller
{
private readonly IStringLocalizer _localizer;
private readonly IStringLocalizer _sharedLocalizer;
public HomeController(IStringLocalizer<HomeController> localizer,
IStringLocalizer<SharedResource> sharedLocalizer)
{
_localizer = localizer;
_sharedLocalizer = sharedLocalizer;
}
public IActionResult Index()
{
return View(model: new SampleModel());
}
public string Content()
{
return $"CurrentCulture: {CultureInfo.CurrentCulture.Name}\r\n"
+ $"CurrentUICulture: {CultureInfo.CurrentUICulture.Name}\r\n"
+ $"{_localizer["Hello"]}\r\n"
+ $"{_sharedLocalizer["Hello"]}";
}
}
}
注入 IViewLocalizer
改成注入 IHtmlLocalizer
,並指派型別,如下:
Views\Home\Index.cshtml
@using System.Globalization
@using Microsoft.AspNetCore.Mvc.Localization
@using MyWebsite.Models
@model SampleModel
@inject IViewLocalizer localizer
@inject IHtmlLocalizer<MyWebsite.SharedResource> sharedLocalizer
CurrentCulture: @CultureInfo.CurrentCulture.Name <br />
CurrentUICulture: @CultureInfo.CurrentUICulture.Name <br />
@localizer["Hello"]<br />
@Html.DisplayNameFor(m => m.Content)<br />
@sharedLocalizer["Hello"]<br />
ASP.NET Core Globalization and localization