core 实现支持多语言
Intro
最近有一个外国友人通过邮件联系我,想用我的活动室预约,但是还没支持多语言,基本上都是写死的中文,所以最近想支持一下更多语言,于是有了多语言方面的一些实践
国际化/本地化介绍
国际化(Globalization)和本地化(Localization)是要实现的多语言支持的基础
Globalization is the process of designing and developing applications that function formultiple cultures.
Localization is the process ofcustomizingyour application for agiven culture and locale.
国际化是要支持处理多种文化,而本地化是要根据某一个文化和区域的来展示相应的处理。
更多关于国际化与本地化的不同可以参考 Stack Overflow 上的讨论 /questions/2074869/globalization-vs-localization
Localization In Core
微软官方的Localization的实现是基于资源文件实现的 (*.resx
),我们也可以扩展支持更多方式,如 JSON/数据库 都是可以的,社区已经有实现的示例,只要是可以提供一个文本源的都是可以的,我们先使用默认的基于资源文件的,下一篇再讲一个自定义实现一个 Localization Provider。
.NET Core Localization 的 核心是IStringLocalizer
, 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; // 设置时区为 UTCoptions.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 viewLocalizerviewLocalizer["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
实际访问效果:https://reservation.weihanli.xyz/
默认的中文界面:
英文界面:
只是做了几个前台的示例,还有很多地方没改
Docker 部署
现在的项目是基于 docker + k8s 部署的,所以支持 docker 部署很重要
要支持多语言,需要安装 ICU 相关的包,(这个可不是 996.icu 的 icu 哈,如果不装真的有可能导致 996.icu)
RUN apk add --no-cache icu-libs # 安装 icu-libsENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false # 配置 Globalization
完整的 dockerfile 可以参考:/dotnet/dotnet-docker/blob/cb7a9c35dacf6d34fcf7bab7995e60faef55f61f/samples/dotnetapp/Dockerfile.alpine-x64-globalization
More
.net core 的设计真的是很灵活,很优美,基于资源文件的本地化,感觉不太方便,使用资源文件的化可能就只能使用 VS 编辑了,虽然也是纯文本的,基于 xml 但是编辑起来不如界面看着编辑舒服,如果使用 json 之类的,就比较简单明了,编辑起来也比较方便,所以想把资源文件替换成 JSON 文件
下次分享一篇基于 JSON 的 Localization Provider 的实现
Reference
/en-us/aspnet/core/fundamentals/localization?view=aspnetcore-3.1
/dotnet/extensions/tree/master/src/Localization
/dotnet/aspnetcore/tree/master/src/Middleware/Localization
/questions/2074869/globalization-vs-localization
/WeihanLi/ActivityReservation