為應用加上多國語系及本地化
在.NetCore也是使用 .resx 來存放語系相關設定,用法跟.net Framework有些許差異
使用.NetCore 3.1 MVC範本專案,需要的package似乎都有了,就不贅述
要注意的是預設的Resources會根據各個檔案的namespace及檔名搭配對應的 .resx,後續會提出解決方式
ex:
MyProject.Controller.HomeController 預設對應的資源檔是 MyProject.Controller.HomeController.<lang>.resx
1.調整 Startup的 ConfigureServices
-
AddLocalization,並且指定路徑為 "Resources",
-
若未指定Path,預設為專案的根目錄(後續會舉例若無設定,會有什麼結果)
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
}
2.以及 Configure,加入Middleware
- 在這邊設定支援的語系列表
- 預設的文化及UI文化(一個是顯示的語言,一個是資料顯示的格式)
- 語系的選擇預設有三種方式,預設的執行順序也是按照QueryString => Cookie => Header
- QueryStringRequestCultureProvider - 從QueryString
- CookieRequestCultureProvider - 從Cookie
- AcceptLanguageHeaderRequestCultureProvider - 從Header(一般大部分的請求都會有)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var supportedCultures = new List<CultureInfo>()
{
new CultureInfo("zh"),
new CultureInfo("en"),
};
app.UseRequestLocalization(new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture("zh"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures,
});
// anything...
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
3.接著在 Controller 或是其他要使用語系的地方,注入 IStringLocalizer<T>
- 這邊的 <T> 會影響到抓取哪一個語系資源檔
- 一般會填上當下的Class Name,在這邊就是 IStringLocalizer<HomeController>
public HomeController(ILogger<HomeController> logger,IStringLocalizer<HomeController> localizer)
{
_logger = logger;
_localizer = localizer;
}
4.在Action準備一點程式碼,從資源檔讀出幾筆資料丟到前端呈現
- Action
public IActionResult Index() { ViewBag.Account = _localizer["Account"]; ViewBag.Password = _localizer["Password"]; return View(); }
-
View
<div class="text-center"> <h1 class="display-4">Welcome</h1> <h1 class="display-4">@ViewBag.Account</h1> <h1 class="display-4">@ViewBag.Password</h1> </div>
5.接著新增資源檔 .resx
- 路徑及名稱必須匹配相對class
- 在資源檔加入一點資料
6.開啟應用,理應上就能看到顯示的文字是資源檔的Value了
7.上面提到預設有三種設定語系的方式,在這邊可以試著加入QueryString,將顯示不同的語系內容(ex: culture=zh)
- 若是不支援的語系,則會顯示default設定的語系
8.再來讓View也能拿到資源檔的語系值,在Startup加上 AddViewLocalization
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddViewLocalization();
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
}
9.調整View
- 主要需要加上 @inject IViewLocalizer _localizer,讓我們可以拿到語系值
@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer _localizer
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<h1 class="display-4">@ViewBag.Account</h1>
<h1 class="display-4">----------</h1>
<h1 class="display-4">@_localizer["Account"]</h1>
</div>
10.把應用Run起來,很不意外的顯示的是語系的Key值,因為View也需要對應路徑的資源檔
11.新增後再次執行,這次顯示的文字是來自資源檔了
12.接著在View上面,也許有個Model,並且透過HtmlHelper取得他的DisplayName顯示在上面
@model MyClass
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<h1 class="display-4">@ViewBag.Account</h1>
<h1 class="display-4">----------</h1>
<h1 class="display-4">@_localizer["Account"]</h1>
<h1 class="display-4">----------</h1>
<div><label asp-for="Id"></label></div>
<div><label asp-for="Name"></label></div>
</div>
namespace LocalizationSample.Models
{
public class MyClass
{
[Display(Name = "MyClass.Id")]
public string Id { get; set; }
[Display(Name = "MyClass.Name")]
public string Name { get; set; }
}
}
13.再來要讓顯示的內容也來自資源檔
14.一樣先調整Startup,加上 AddDataAnnotationsLocalization
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
.AddViewLocalization()
.AddDataAnnotationsLocalization();
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
}
15.為Model加上資源檔
16.執行應用,這次連Model的DisplayName也來自資源檔了
17.回過頭來看,每個地方都有自己的資源檔,若有共用的資源,在維護上絕對是個災難
18.再來讓所有的地方都讀取同一個資源檔
- 新增一個空的class,我選擇放在Resources底下
- 接著新增對應名稱的 .resx,對應上面新增的class路徑,需要放在Resources下的Resources底下(記得也要新增內容進去)
- 先從Controller開始,把IStringLocalizer<T>的型別參數更改為 SharedResources
public HomeController(ILogger<HomeController> logger, IStringLocalizer<SharedResources> localizer) { _logger = logger; _localizer = localizer; }
- 執行應用,Controller這邊的已經改讀 SharedResources 的設定
- 再來換View
- 把View上的 IViewLocalizer 更改為 IHtmlLocalizer<SharedResources>,執行應用後,也能拿到語系值了
@using Microsoft.AspNetCore.Mvc.Localization @using LocalizationSample.Resources @* @inject IViewLocalizer _localizer *@ @inject IHtmlLocalizer<SharedResources> _localizer @model MyClass <div class="text-center"> <h1 class="display-4">Welcome</h1> <h1 class="display-4">@ViewBag.Account</h1> <h1 class="display-4">----------</h1> <h1 class="display-4">@_localizer["Account"]</h1> <h1 class="display-4">----------</h1> <div><label asp-for="Id"></label></div> <div><label asp-for="Name"></label></div> </div>
- 最後換Model上的DisplayName
- 調整Startup的 AddDataAnnotationsLocalization
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews() .AddViewLocalization() .AddDataAnnotationsLocalization(options => { options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(SharedResources)); } ); services.AddLocalization(options => { options.ResourcesPath = "Resources"; }); }
19.目前爲止Controller、View、Model的語系直都來自 SharedResources了,基本操作到此為止
補充
1.若沒有在Startup上指定 ResourcePath,那專案結構就可能會變成這樣
2.CookieRequestCultureProvider
- 預設的Cookie名稱為 ".AspNetCore.Culture"
- 內容格式為 "c=<lang>|uic=<lang>"
- ex: "c=en|uic=en"
3.若要自定Cookie名稱,可參考下面做法(這邊是ExtensionMethod)
public static void UseLocalization(this IApplicationBuilder app)
{
var cultures = new List<CultureInfo>()
{
new CultureInfo("zh"),
new CultureInfo("en"),
};
var localizationOptions = new RequestLocalizationOptions()
{
DefaultRequestCulture = new RequestCulture("zh"),
SupportedCultures = cultures,
SupportedUICultures = cultures
};
localizationOptions.RequestCultureProviders
.OfType<CookieRequestCultureProvider>()
.First()
.CookieName = "UserCulture";
app.UseRequestLocalization(localizationOptions);
}
4.若要取得語系的所有內容
IEnumerable<LocalizedString> result = _localizer.GetAllStrings();
剛好工作專案上有用到,研究了一下並筆記一下做法
不得不抱怨微軟的這篇文章實在不好閱讀(不然就是我的問題,總是對不上頻率🤣)
Microsoft Docs https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/localization?view=aspnetcore-3.1
SampleCode https://github.com/ianChen806/LocalizationSample