今天复习了下之前写的 ASP.NET Core 基础教程 发现少了国际化这个章节。而最近的项目有可能要用到这个特性,于是花点时间来了解下。
因为我喜欢使用轻量级的编辑器,比如 Sublime Text 4 或者 Visual Studio Code 做开发,所以对官方给丁的默认资源文件格式 resx 实在是不好编辑和添加。因此,本文我们使用 JSON 文件作为 I18N 国际化的文件格式。
1. 创建项目
我们使用命令行来创建使用 webapp 模板,一个名为 i18n 的项目
dotnet new webapp -n i18n
输出结果如下
正在准备... 已成功创建模板“ASP.NET Core Web App”。 此模板包含除 Microsoft 以外其他方的技术,请参阅 https://aka.ms/aspnetcore/5.0-third-party-notices 以获取详细信息。 正在处理创建后操作... 在 i18n/i18n.csproj 上运行 “dotnet restore”... 正在确定要还原的项目… 已还原 /Users/yufei/Downloads/dotnet/i18n/i18n.csproj (用时 102 ms)。 已成功还原。
然后使用 tree i18n 看看目录结果,输出结果如下
.
├── Pages
│ ├── Error.cshtml
│ ├── Error.cshtml.cs
│ ├── Index.cshtml
│ ├── Index.cshtml.cs
│ ├── Privacy.cshtml
│ ├── Privacy.cshtml.cs
│ ├── Shared
│ │ ├── _Layout.cshtml
│ │ └── _ValidationScriptsPartial.cshtml
│ ├── _ViewImports.cshtml
│ └── _ViewStart.cshtml
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Startup.cs
├── appsettings.Development.json
├── appsettings.json
├── i18n.csproj
├── obj
│ ├── i18n.csproj.nuget.dgspec.json
│ ├── i18n.csproj.nuget.g.props
│ ├── i18n.csproj.nuget.g.targets
│ ├── project.assets.json
│ └── project.nuget.cache
└── wwwroot
├── css
│ └── site.css
├── favicon.ico
├── js
│ └── site.js
└── lib
├── bootstrap
│ ├── LICENSE
│ └── dist
│ ├── css
│ │ ├── bootstrap-grid.css
│ │ ├── bootstrap-grid.css.map
│ │ ├── bootstrap-grid.min.css
│ │ ├── bootstrap-grid.min.css.map
│ │ ├── bootstrap-reboot.css
│ │ ├── bootstrap-reboot.css.map
│ │ ├── bootstrap-reboot.min.css
│ │ ├── bootstrap-reboot.min.css.map
│ │ ├── bootstrap.css
│ │ ├── bootstrap.css.map
│ │ ├── bootstrap.min.css
│ │ └── bootstrap.min.css.map
│ └── js
│ ├── bootstrap.bundle.js
│ ├── bootstrap.bundle.js.map
│ ├── bootstrap.bundle.min.js
│ ├── bootstrap.bundle.min.js.map
│ ├── bootstrap.js
│ ├── bootstrap.js.map
│ ├── bootstrap.min.js
│ └── bootstrap.min.js.map
├── jquery
│ ├── LICENSE.txt
│ └── dist
│ ├── jquery.js
│ ├── jquery.min.js
│ └── jquery.min.map
├── jquery-validation
│ ├── LICENSE.md
│ └── dist
│ ├── additional-methods.js
│ ├── additional-methods.min.js
│ ├── jquery.validate.js
│ └── jquery.validate.min.js
└── jquery-validation-unobtrusive
├── LICENSE.txt
├── jquery.validate.unobtrusive.js
└── jquery.validate.unobtrusive.min.js
17 directories, 57 files
大家在使用 Visual Studio 或者 Visual Studio Code 创建项目的时候,在模板页面选择 Web 应用程序 这个模板。其它的则默认即可。
2. 添加 My.Extensions.Localization.Json 包
可以使用 Nuget 搜索 My.Extensions.Localization.Json 并点击安装即可。或者你可以像我一样使用下面的命令行添加
dotnet add package My.Extensions.Localization.Json
3. 创建 Resources 文件和 Startup.en.json 和 Startup.zh.json
在 118n 目录下创建子目录 Resources,并在 Resources 目录下创建 Startup.en.json 和 Startup.zh.json 文件
mkdir Resources touch Resources/Startup.en.json touch Resources/Startup.zh.json
创建好后的目录结构如下
.
├── Pages
│ ├── ...
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Resources
│ ├── Startup.en.json
│ ├── Startup.zh.json
├── Startup.cs
├── appsettings.Development.json
├── appsettings.json
├── bin
│ └── Debug
│ └── net5.0
│ └── ref
├── i18n.csproj
├── i18n.sln
├── obj
│ ....
└── wwwroot
├── css
│ └── site.css
├── favicon.ico
├── js
│ └── site.js
└── lib
├── ....
4. 编辑 Startup.en.json 和 Startup.zh.json 分别输入两个条目
Startup.en.json
{ "hello": "Hello" }
Startup.zh.json
{ "hello": "你好" }
5. 编辑 Startup.cs 添加相关配置
接下来我们要编辑 Startup.cs 文件添加国际化相关的配置
首先引入相关命名空间
using System.Globalization; using Microsoft.Extensions.Localization; using Microsoft.AspNetCore.Localization; using System.Collections.Generic;
告诉系统 JSON 国际化资源文件位置
编辑 public void ConfigureServices(IServiceCollection services) 方法添加国际化资源文件位置
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); // 添加下面这行 // 告诉国际化引擎,所有的国际化资源文件都在 Resources 目录 services.AddJsonLocalization(options => options.ResourcesPath = "Resources"); }
扩展 Configure() 方法配置国际化
把原来的 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 方法扩展成 public void Configure(IApplicationBuilder app, IHostEnvironment env, IStringLocalizer localizer1, IStringLocalizer<Startup> localizer2){
然后添加我们可能要切换到的语言选项
// 添加可能要用到的语言选项列表 var supportedCultures = new List<CultureInfo> { new CultureInfo("en"), new CultureInfo("zh") };
对了,所有的国际化语言选项都是 CultureInfo 的实例。每个区域都有自己的文化,所以 CultureInfo 这个名字还是蛮适合的
下一步,我们要把这个语言选项列表赋值给请求相关的配置上并且设置默认的语言选项
// 设置发起请求可能要用到的语言适配 var options = new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("en"), SupportedCultures = supportedCultures, SupportedUICultures = supportedCultures };
最后,我们使用 app.UseRequestLocalization(options) 把国际化的配置设置到应用程序上。 并注释掉所有模板相关的请求。
上面所有的配置完成后,Startup.cs 类的所有代码如下
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Globalization; using Microsoft.Extensions.Localization; using Microsoft.AspNetCore.Localization; using System.Collections.Generic; using Microsoft.AspNetCore.Http; namespace i18n { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // services.AddRazorPages(); // 告诉国际化引擎,所有的国际化资源文件都在 Resources 目录 services.AddJsonLocalization(options => options.ResourcesPath = "Resources"); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostEnvironment env, IStringLocalizer localizer1, IStringLocalizer<Startup> localizer2) { // 添加可能要用到的语言选项列表 var supportedCultures = new List<CultureInfo> { new CultureInfo("en"), new CultureInfo("zh") }; // 设置发起请求可能要用到的语言适配 var options = new RequestLocalizationOptions { DefaultRequestCulture = new RequestCulture("en"), SupportedCultures = supportedCultures, SupportedUICultures = supportedCultures }; // 将国际化的配置添加到应用程序上 app.UseRequestLocalization(options); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } // app.UseHttpsRedirection(); // app.UseStaticFiles(); // app.UseRouting(); // app.UseAuthorization(); app.Run(async (context) => { await context.Response.WriteAsync($"{localizer1["hello"]}!!"); await context.Response.WriteAsync($"{localizer2["hello"]}!!"); }); /* app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); });*/ } } }
输出结果如下
hello!!你好!!
如果我们全部切换到英文,则输出结果如下
hello!!Hello!!%
很多同学会问 Configure() 方法中的 IStringLocalizer localizer1 和 IStringLocalizer<Startup> localizer2 有什么不同?
这里,我们就要讲讲 ASP.NET Core 如何查找国际化语言文件了,比如对于 en_US 和 zh_CN 两个语言序列和 Startup 这个类。
-
带泛型和不带泛型的区别就是,带泛型首先会查找
[类名].[语言].json和非泛型则查找[语言].json文件。所以localizer1首先查找的是en_US.json或zh_CN.json类,而localizer2查找的是Startup.zh_CN.json或Startup.en_US.json文件。 -
对于语言类,比如
en_US和zh_CN如果找不到精确匹配的语言文件,则会尝试查找_前面那部分对应的语言文件,比如en.json或zh.json文件。en_US这种是[语言]_[国家代码]的组合形式。
泛型类的好处
IStringLocalizer<Startup> localizer2 这种机制的好处,给予我们按功能、按模块组织国际化的机会。比如我们可以把所有和用户相关的国际化资源都放到 User.json 中,然后在方法入口使用 IStringLocalizer<User> userLocalizer 来访问对应的资源