• ASP.NET Core – Globalization & Localization


    前言

    之前就写过 2 篇, 只是写的很乱, 这篇作为整理版.

    Asp.net core (学习笔记 路由和语言 route & language)

    Asp.net core 学习笔记之 globalization & localization 复习篇

    我的项目只是做语言而已, 没有做区域, 也没有 Data Annotation 的需求, 所以下面不会提到.

    参考:

    docs – Globalization and localization in ASP.NET Core

    Razor Pages Localisation - SEO-friendly URLs

    Using Resource Files In Razor Pages Localisation

    基本用法:

    Setup Program.cs

    这篇只讲 Razor Pages 的使用, 不会讲到 MVC 和 Data Annotation.

    builder.Services.AddRazorPages()
        .AddViewLocalization();

    Setup Options

    builder.Services.Configure<RequestLocalizationOptions>(options =>
    {
        var supportedCultures = new[] { "en", "zh-Hans" };
        options.SetDefaultCulture(supportedCultures[0])
            .AddSupportedCultures(supportedCultures)
            .AddSupportedUICultures(supportedCultures);
    });

    定义支持的语言, 默认语言. 我项目没有区域性, 所以是 en 而不是 en-US.

    最后启动就可以了

    app.UseRequestLocalization();
    app.MapRazorPages();

    .resx

    这个文件的位置是挺讲究的. .cshtml 在哪里它就在旁边. 取一样的 file name, 配上指定的 language code

    位置虽然是可以改的, 但我觉得默认就很好了, follow 它吧.

    用 Visual Studio 打开 .resx

    Name 其实是 Key, 但是为了方便, 一般上会直接放默认语言的值. 你要放 Key (代号) 也是可以的.

    pure text, HTML 都支持. 也支持 string format 代号 {0}, 调用时传入 parameters.

    .cshtml 调用

    @using Microsoft.AspNetCore.Mvc.Localization
    @inject IViewLocalizer Localizer
    
    <div class="text-center">
      <h1 class="display-4">About Page</h1>
      @Localizer["Hello World"]
      @Localizer["<h1>Hello World {0}</h1>", "parameter1"]
    </div>

    注入 IViewLocalizer, 使用方式是 Localizer["Key"]

    它会返回一个对象, 而不是一个值哦.

    这个对象有一个方法叫 WriteTo. Razor Pages 在 render 的时候会调用它, 最后 encode 成 HTML.

    另外, Localizer["Key"] 如果没有找到 .resx file 它会返回 Key. 这个是为了方便项目提前设计. 以后才支持语言. 非常方便.

    访问

    https://localhost:7078/About?culture=zh-Hans&ui-culture=zh-Hans

    它是通过 query params 来选择语言的哦.

    Use Path Segment as Language Selection

    上面提到, 默认用 query params 作为语言的选择, 但是 SEO 不鼓励这样做.

    通常是用第一个 path segment 作为语言: /zh-Hans/about-us

    builder.Services.Configure<RequestLocalizationOptions>(options =>
    {
        var supportedCultures = new[] { "en", "zh-Hans" };
        options.SetDefaultCulture(supportedCultures[0])
            .AddSupportedCultures(supportedCultures)
            .AddSupportedUICultures(supportedCultures);
    
        options.AddInitialRequestCultureProvider(new CustomRequestCultureProvider(httpContent =>
        {
            // 这里写判断逻辑 (base on httpContent info), 最后返回指定的语言就可以了
            return Task.FromResult(new ProviderCultureResult("zh-Hans"))!;
        }));
    });

    通过 AddInitialRequestCultureProvider 就可以实现了.

    题外话, 用 path first segment 作为语言, 需要调整 Razor Pages 的 routing 匹配哦. 请参考: ASP.NET Core – Razor Pages Routing

    Shared Resource

    上面提到的都是 1 个 .cshtml 对应 1 个 .resx. 但有时候内容一样想做抽象怎么办呢?

    创建一个空的 class 和 .resx. 

    namespace TestLocalization.Pages;
    public class SharedResource { }

    然后, 在 .cshtml 把注入换成 IHtmlLocalizer<ClassName> 就可以了

    @using Microsoft.AspNetCore.Mvc.Localization
    @* @inject IViewLocalizer Localizer *@
    @inject IHtmlLocalizer<SharedResource> Localizer

    注意: resx 的 file name 和位置也是有讲究的哦, 依据 class 的 namespace + class name

    比如 namespace = ProjectName.Pages, class name = SharedResource.

    那么 .resx 必须放在 /Pages/SharedResource.zh-Hans.resx

    详解资料可以看这篇: Resources Search Strategy

    在 Model.cs 使用 Localization

    上面都是讲 View 如何使用 Localization. 想在 Model.cs 里面使用的话, 不可以注入 IViewLocalization

    public class IndexModel : PageModel
    {
        private readonly IStringLocalizer<IndexModel> _stringLocalizer;
        private readonly IHtmlLocalizer<IndexModel> _htmlLocalizer;
    
        public IndexModel(
            IStringLocalizer<IndexModel> stringLocalizer,
            IHtmlLocalizer<IndexModel> htmlLocalizer
        )
        {
            _stringLocalizer = stringLocalizer;
            _htmlLocalizer = htmlLocalizer;
        }
    
        public void OnGet()
        {
            var value1 = _stringLocalizer["Hello World"].Value;
            // var value2 = _htmlLocalizer["Hello World"].WriteTo(TextWriter writer, HtmlEncoder encoder);
        }
    }

    注入 IStringLocalizer 或者 IHtmlLocalizer

    两者的区别是, IString 内容应该是 pure text 不包含 HTML. 使用的时候 _stringLOcalizer["Key"] 返回一个对象, 通过 .Value 获取翻译后的值.

    IHtml 的使用是 _htmlLocalizer["Key"] 返回一个对象, 通过 WriteTo(writer, encoder) 获取翻译后的值, 注意: 它不是用 .Value 哦.

    .rexs 的位置

    仔细看, IStringLocalizer<IndexModel> 的泛型是 IndexModel class, 也就是当前的 class.

    直觉会认为它应该和 View 用同一个 resx.

    但其实不是, 上面有提到 SharedResource, 只要是 class 就是 namespace + class = forlder + file name, 所以是 IndexModel.zh-Hans.resx

    也是醉了...因此我建议当需要这样搞时, 做一个 shared class 让 view 也统一使用 IHtmlLocalizer 会更好.

    动态设定语言

    上面有提到通过 AddInitialRequestCultureProvider 可以控制每一次请求选择的语言. 但如果想动态切换呢?

    比如 path segment 是中文. 但是系统要发一个 enquiry 给网站负责人, 内容要用英文. 这时就需要动态修改它.

    翻看源码 RequestLocalizationMiddleware.cs

    在 middleware 它做了 2 件事

    1. set IRequestCultureFeature (这类 Feature 属于 request 的全局变量, 可以通过 HttpContext 访问到)

    2. set CultureInfo (这个是静态类来的, 也算是全局变量吧)

    而在 ResourceManagerStringLocalizer.cs 里

    翻译就是依据 CultureInfo 这个静态类去做的.

    所以要动态修改语言的话, 就要 re-set 掉 CultureInfo

    public void OnGet()
    {
        CultureInfo.CurrentCulture = new CultureInfo("zh-Hans");
        CultureInfo.CurrentUICulture = new CultureInfo("zh-Hans");
        var value1 = _stringLocalizer["Hello World"].Value;
    }

    .cshtml

    @using System.Globalization
    @{
      CultureInfo.CurrentCulture = new CultureInfo("zh-Hans");
      CultureInfo.CurrentUICulture = new CultureInfo("zh-Hans");
    }

    之前有 Github Issue 讨论过这种 set 方式好不好, 但后来视乎大家都接受了.

    获取语言相关信息

    public class IndexModel : PageModel
    {
        private readonly RequestLocalizationOptions _requestLocalizationOptions;
        public IndexModel(
            IOptionsSnapshot<RequestLocalizationOptions> _requestLocalizationOptionsAccessor
        )
        {
            _requestLocalizationOptions = _requestLocalizationOptionsAccessor.Value;
        }
        public void OnGet()
        {
            var languageDisplayName = HttpContext.Features.Get<IRequestCultureFeature>()!.RequestCulture.Culture.DisplayName; // "中文(简体)"
            var supportLanguageDisplayNames = _requestLocalizationOptions.SupportedCultures!.Select(s => s.DisplayName).ToList(); // base on current language ["英语", "中文(简体)"]
            var supportLanguageNativeNames = _requestLocalizationOptions.SupportedCultures!.Select(s => s.NativeName).ToList(); // ["English", "中文(简体)"]
            var supportLanguageEnglishNames = _requestLocalizationOptions.SupportedCultures!.Select(s => s.EnglishName).ToList(); // ["English", "Chinese (Simplified)"]
        }
    }
  • 相关阅读:
    棋盘问题 POJ
    Fire! UVA
    走迷宫(bfs, 最短路)
    ASP-Command-SQL格式
    ASP连接数据库SQLServer
    Bootstrap学习-导航条-分页导航
    Bootstrap导航栏头部错位问题
    SQLServer判断一个IP是否在一个IP段里
    MySQL合并多行
    CSS图片居中,多余隐藏
  • 原文地址:https://www.cnblogs.com/keatkeat/p/16041130.html
Copyright © 2020-2023  润新知