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 里扩展定义了 IViewLocalizerIHtmlLocalizerIViewLocalizerIHtmlLocalizer 主要是为了处理包含 html 的资源,他们不会对资源进行 html encode,相当于 @Html.Raw 的效果,而 IStringLocalizer 则会被 html encode,除此之外 IViewLocalizer 还会根据当前视图的路径寻找资源文件

来看一个示例:

Razor 页面

image

浏览器效果:

image

查看网页源代码:

image

实际案例

服务注册

注册 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();

在控制器中使用示例:

IStringLocalizerIHtmlLocalizer /IViewLocalizer 都可以直接从依赖注入服务中获取,IStringLocalizerIHtmlLocalizer 推荐使用强类型的方式,也就是下面示例的使用方式,使用方式和 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

实际项目中的资源文件示例:

image

实际访问效果:https://reservation.weihanli.xyz/

默认的中文界面:

image

英文界面:

image

只是做了几个前台的示例,还有很多地方没改

More

.net core 的设计真的是很灵活,很优美,基于资源文件的本地化,感觉不太方便,使用资源文件的化可能就只能使用 VS 编辑了,虽然也是纯文本的,基于 xml 但是编辑起来不如界面看着编辑舒服,如果使用 json 之类的,就比较简单明了,编辑起来也比较方便,所以想把资源文件替换成 JSON 文件

下次分享一篇基于 JSON 的 Localization Provider 的实现

Reference

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容