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://*.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://*.com/questions/2074869/globalization-vs-localization
- https://github.com/WeihanLi/ActivityReservation