asp.net core 實現支援多語言
asp.net core 實現支援多語言
Intro
最近有一個外國友人通過郵件聯絡我,想用我的活動室預約,但是還沒支援多語言,基本上都是寫死的中文,所以最近想支援一下更多語言,於是有了多語言方面的一些實踐
國際化/本地化介紹
國際化(Globalization)和本地化(Localization)是要實現的多語言支援的基礎
Globalization is the process of designing and developing applications that function for multiple cultures.
Localization is the process of customizing your application for a given culture and locale.
國際化是要支援處理多種文化,而本地化是要根據某一個文化和區域的來展示相應的處理。
更多關於國際化與本地化的不同可以參考 Stack Overflow 上的討論 https://stackoverflow.com/questions/2074869/globalization-vs-localization
Localization In Asp.NET Core
微軟官方的 Localization 的實現是基於資原始檔實現的 (*.resx
),我們也可以擴充套件支援更多方式,如 JSON/資料庫 都是可以的,社群已經有實現的示例,只要是可以提供一個文字源的都是可以的,我們先使用預設的基於資原始檔的,下一篇再講一個自定義實現一個 Localization Provider。
.NET Core Localization 的 核心是 IStringLocalizer
,asp.net core 裡擴充套件定義了 IViewLocalizer
和 IHtmlLocalizer
,IViewLocalizer
和 IHtmlLocalizer
主要是為了處理包含 html
的資源,他們不會對資源進行 html encode,相當於 @Html.Raw
的效果,而 IStringLocalizer
則會被 html encode,除此之外 IViewLocalizer
還會根據當前檢視的路徑尋找資原始檔
來看一個示例:
Razor 頁面
瀏覽器效果:
檢視網頁原始碼:
實際案例
服務註冊
註冊 Localization 相關服務:
var supportedCultures = new[]
{
new CultureInfo("zh"),
new CultureInfo("en"),
};
services.Configure<RequestLocalizationOptions>(options =>
{
options.DefaultRequestCulture = new RequestCulture("zh");
// Formatting numbers, dates, etc.
options.SupportedCultures = supportedCultures;
// UI strings that we have localized.
options.SupportedUICultures = supportedCultures;
});
services.AddLocalization(options => options.ResourcesPath = Configuration.GetAppSetting("ResourcesPath"));
配置檢視 Localization
(根據需要如果是 WebAPI 就不需要了)
services.AddControllersWithViews()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc; // 設定時區為 UTC
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
})
.AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix,
opts => { opts.ResourcesPath = Configuration.GetAppSetting("ResourcesPath"); })
.AddDataAnnotationsLocalization()
.SetCompatibilityVersion(CompatibilityVersion.Latest);
中介軟體配置:
app.UseRequestLocalization();
邏輯程式碼中使用示例:
IStringLocalizer
和 IHtmlLocalizer
/IViewLocalizer
都可以直接從依賴注入服務中獲取,IStringLocalizer
和 IHtmlLocalizer
推薦使用強型別的方式,也就是下面示例的使用方式,使用方式和 ILogger
類似
public async Task<ActionResult> MakeReservation(
[FromBody]ReservationViewModel model,
[FromHeader]string captcha,
[FromHeader]string captchaType,
[FromServices]IStringLocalizer<HomeController> localizer)
{
var result = new ResultModel<bool>();
var isCodeValid = await HttpContext.RequestServices.GetService<CaptchaVerifyHelper>()
.ValidateVerifyCodeAsync(captchaType, captcha);
if (!isCodeValid)
{
result.Status = ResultStatus.RequestError;
result.ErrorMsg = localizer["InvalidCaptchaInfo"];
return Json(result);
}
在檢視中使用示例:
localizer["data"]
返回的是一個 LocalizedString
,實現了隱式轉換為 string, 有的時候可能需要強制轉一下string, 或者使用 Value 屬性
@inject IViewLocalizer viewLocalizer
viewLocalizer["About"]
@Html.ActionLink((string)viewLocalizer["About"], "About", "Home")
@Html.ActionLink(viewLocalizer["About"].Value, "About", "Home")
資原始檔配置:
資原始檔的配置和檔案的結構類似,下面是一個示例
準備的來說是和型別的 FullName
有關係,一般的專案名稱就是程式集名稱,就是根名稱空間,檔名稱就是型別名稱,所以一般情況下資原始檔的位置和型別的位置是一致的,但是如果檔案和型別名稱不符合,那就要按照型別的 FullName
來找,檢視也是類似的,如果根名稱空間不是程式集名稱,也是可以配置的具體的參考文件
Controllers.HomeController
=> Controllers/HomeController.zh.resx
/Controllers/HomeController.en.resx
Resource name | Dot or path naming |
---|---|
Resources/Controllers.HomeController.fr.resx | Dot |
Resources/Controllers/HomeController.fr.resx | Path |
- Resources/Views/Home/About.fr.resx
- Resources/Views.Home.About.fr.resx
實際專案中的資原始檔示例:
實際訪問效果:https://reservation.weihanli.xyz/
預設的中文介面:
英文介面:
只是做了幾個前臺的示例,還有很多地方沒改
Docker 部署
現在的專案是基於 docker + k8s 部署的,所以支援 docker 部署很重要
要支援多語言,需要安裝 ICU 相關的包,(這個可不是 996.icu 的 icu 哈,如果不裝真的有可能導致 996.icu)
RUN apk add --no-cache icu-libs # 安裝 icu-libs
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false # 配置 Globalization
完整的 dockerfile 可以參考:
https://github.com/dotnet/dotnet-docker/blob/cb7a9c35dacf6d34fcf7bab7995e60faef55f61f/samples/dotnetapp/Dockerfile.alpine-x64-globalization
More
.net core 的設計真的是很靈活,很優美,基於資原始檔的本地化,感覺不太方便,使用資原始檔的化可能就只能使用 VS 編輯了,雖然也是純文字的,基於 xml 但是編輯起來不如介面看著編輯舒服,如果使用 json 之類的,就比較簡單明瞭,編輯起來也比較方便,所以想把資原始檔替換成 JSON 檔案
下次分享一篇基於 JSON 的 Localization Provider 的實現
Reference
- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-3.1
- https://github.com/dotnet/extensions/tree/master/src/Localization
- https://github.com/dotnet/aspnetcore/tree/master/src/Middleware/Localization
- https://stackoverflow.com/questions/2074869/globalization-vs-localization
- https://github.com/WeihanLi/ActivityReservation