18. i18n

18. i18n

概念

作为一个普通开发者, 我们负责的项目的使用群体大多数是本国的人民, 但不可避免的也有一些做外贸的业务或者给外企做的项目, 这个时候就要求我们的项目有服务全球客户的能力, 而一个支持国际化能力的框架会让我们项目的体验变得更好.

关于本地化我们听到最多的是I18N (国际化)、L10N (本地化)、G11N (全球化), 那它们分别代表了什么意思呢?

  • 国际化: 使产品或软件具有不同国际市场的普遍适应性, 从而无需重新设计就可适应多种语言和文化习俗

  • 本地化: 将产品或软件针对特定国际语言和文化进行加工, 使之符合特定区域市场.

  • 全球化: 是指在产品的业务拓展到世界上其他地方的过程, 可以简单理解为: 全球化 = “国际化” + “本地化”

MasaFramework提供的国际化能力使得我们可以通过修改资源获得不同区域的内容, 而不必要修改源码, 除此之外, 在框架中使用的时间也是UTC时间, 不管项目被应用到哪个地区, 只需要做一次UTC时间向本地时间的转换即可, 为项目以后的本地化奠定了基础

使用

  1. 新建ASP.NET Core 空项目Assignment.I18nDemo,并安装Masa.Contrib.Globalization.I18n.AspNetCore
1
dotnet add package Masa.Contrib.Globalization.I18n.AspNetCore --version 0.7.0-preview.16
  1. 注册I18n, 并修改Program.cs
1
builder.Services.AddI18n();
  1. 使用I18N
1
app.UseI18n();//启用中间件, 完成对请求的解析并为当前请求设置区域
  1. 添加多语言资源文件, 文件夹结构如下:
1
2
3
4
5
- Resources
- I18n
- en-US.json
- zh-CN.json
- supportedCultures.json
  • en-US.json
1
2
3
{
"Home":"Home"
}
  • zh-CN.json
1
2
3
{
"Home":"首页"
}
  • supportedCultures.json
1
2
3
4
5
6
7
8
9
10
11
12
[
{
"Culture":"zh-CN",
"DisplayName":"中文简体",
"Icon": "{Replace-Your-Icon}"
},
{
"Culture":"en-US",
"DisplayName":"English (United States)",
"Icon": "{Replace-Your-Icon}"
}
]
  1. 使用I18n
1
app.Map("/test", (string key) => I18n.T(key));
  1. 测试多语言, 在浏览器访问 “https://localhost:7082/get?key=Home" 即可得到对应语言下键名为Home的值

是不是感觉用起来十分简单呢, 但这究竟是如何做的呢

进阶

国内的小伙伴根据上面的例子操作下来, 会发现请求响应的内容是中文, 那什么情况下它会变成英文呢?

I18n中间件

这里我们借助了微软提供的本地化的中间件的能力, 通过它使得我们的项目具备解析当前语言的能力, 如果希望自定义规则也可以自己按照自己的规则去解析请求并设置区域

目前它支持了以下三种方式进行语言切换:

  • URL 参数 方式: ?culture=en-US,此方式优先级最高,格式为:culture=区域码
  • Cookies 方式:cookie 格式为 c=%LANGCODE%|uic=%LANGCODE%,其中 c 是 Culture,uic 是 UICulture, 例如:
1
c=en-UK|uic=en-US
  • 客户端浏览器语言自动匹配:如果前面两种方式都没有设置,支持自动根据客户端浏览器语言进行匹配

如果当前请求的语言是不支持的, 则使用默认语言, 其中语言优先级为:

URL 参数 方式 > Cookies方式 > 客户端语言 > 默认语言

默认语言

默认语言有两种配置方式, 它们分别是:

  • 手动指定默认语言
    • 通过app.UseI18n("{Replace-Your-DefaultCulture}")
  • 约定配置
    • supportedCultures.json文件中的第一个语言

它们的优先级是:

手动指定默认语言 > 约定配置

修改默认资源路径

1
2
3
4
5
6
7
8
9
builder.Services.AddI18n(options =>
{
options.ResourcesDirectory = Path.Combine("Resources", "I18n");//修改默认资源路径
options.SupportedCultures = new List<CultureModel>() //支持语言
{
new("zh-CN"),
new("en-US")
};
});

支持资源文件

如果你希望将配置文件嵌入到dll文件中, 不希望被看到修改, 那么你需要将资源json文件的生成操作改为嵌入的资源 (EmbeddedResource), 并修改I18N注册代码为:

1
builder.Services.AddI18nByEmbedded();

嵌套配置

相信了解前端开发的小伙伴也见到过嵌套的资源配置, 那对于Masa提供的多语言方案而言, 我们也支持这种格式的配置, 例如:

1
2
3
4
5
6
{
"Home":"首页",
"User":{
"Name":"名称"
}
}

我们希望拿到User节点下的Name属性的值, 则可以通过:

1
var result = I18N.T("User.Name");//其中key的值不区分大小写

当然除此之外, 我们也可以将不同资源的文件分开存放到不同的json文件, 然后通过添加多个资源目录的文件, 最终实现, 但在使用时稍微有区别:

1
app.Map("/test2", (string key, II18n<CustomResource> i18n) => i18n.T(key));//通过DI获取到自定义资源下Key对应内容

如何对接远程资源配置

多语言的远程资源配置目前仅支持DCC, 我们可以这样做:

  1. 注册MasaConfiguration并使用DCC, 修改Program.cs
1
2
3
4
builder.Services.AddMasaConfiguration(configurationBuilder =>
{
configurationBuilder.UseDcc();
});
  1. 配置DCC配置信息, 修改appsettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"DccOptions": {
"ManageServiceAddress": "{Replace-Your-DccManagerServiceHost}",
"RedisOptions": {
"Servers": [
{
"Host": "{Replace-Your-DccUseRedisHost}",
"Port": 6379
}
],
"DefaultDatabase": 0,
"Password": ""
}
}
}

对MasaConfiguration有疑问点这里

  1. 注册I18n
1
2
3
4
builder.Services.AddI18n(
Path.Combine("Resources", "I18n"),
"supportedCultures.json",
options => options.UseDcc());

我们仅需要在/Resources/I18n目录下存放支持语言的配置即可, 具体的语言配置将从DCC读取 (读取DCC语言的配置节点: 在DCC配置下默认AppId下的”Culture.{语言}”), 例如:

如果支持语言为zh-CNen-US, 则默认读取DCC配置下默认AppId下Culture.zh-CNCulture.en-Us两个配置对象的值, 我们仅需要修改它们的值即可, 并且如果对应的内容发生更改, 项目无需重启即可完成自动更新 如何管理对象

源码解读

MasaFramework中, 抽象了多语言的能力, 它提供了

  • string this[string name]: 获取指定name的值 (如果name不存在, 则返回name的值)
  • string? this[string name, bool returnKey]: 获取指定name的值 (如果returnKey为false, 且name不存在, 则返回null)
  • string this[string name, params object[] arguments]: 获取指定name的值, 并根据文化、输入参数格式化响应信息返回 (如果name不存在, 则返回name的值)
  • string? this[string name, bool returnKey, params object[] arguments]: 获取指定name的值, 并根据文化、输入参数格式化响应信息返回 (如果returnKey为false, 且name不存在, 则返回null)
  • string T(string name): 获取指定name的值, 如果name不存在, 则返回name的值
  • string? T(string name, bool returnKey): 获取指定name的值 (如果returnKey为false, 且name不存在, 则返回null)
  • string T(string name, params object[] arguments): 获取指定name的值, 并根据文化、输入参数格式化响应信息返回 (如果name不存在, 则返回name的值)
  • string? T(string name, bool returnKey, params object[] arguments): 获取指定name的值, 并根据文化、输入参数格式化响应信息返回 (如果returnKey为false, 且name不存在, 则返回null)
  • CultureInfo GetCultureInfo(): 获取当前线程的区域性 (被用于需要格式化响应信息的方法)
  • void SetCulture(string cultureName, bool useUserOverride = true): 设置当前线程的区域性
  • void SetCulture(CultureInfo culture): 设置当前线程的区域性
  • CultureInfo GetUiCultureInfo(): 获取资源管理器使用的当前区域性以便在运行时查找区域性特定的资源
  • void SetUiCulture(string cultureName, bool useUserOverride = true): 设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源
  • void SetUiCulture(CultureInfo culture): 设置资源管理器使用的当前区域性以便在运行时查找区域性特定的资源

我们可以通过DI获取到II18n进而来使用它提供的这些能力, 当然它使用的是资源类型为DefaultResource的资源 (在服务注册时所指向的资源 services.AddI18n()), 除此之外, 我们也可以通过DI获取到II18n<DefaultResource>来使用, 也可以通过I18n(全局静态类)来使用它提供的能力, 但是如果你使用了自定义资源类型, 则只能通过DI获取II18n<{Replace-Your-ResourceType}>来使用.

除此之外, 我们还提供了支持的语言列表的能力, 它被抽象在ILanguageProvider

  • IReadOnlyList GetLanguages(): 获取支持语言集合

我们可以通过DI获取ILanguageProvider来使用, 也可以通过I18n.GetLanguages()来使用

总结

当然, MasaFramework绝不仅仅只是提供了这么简单的多语言的能力, 目前全局异常、Caller也已经支持了多语言, 其它模块也在逐步完善多语言支持, 等之后框架的错误信息也将会支持多语言, 我们的开发体验也将会变得更加友好, 但这只是为我们项目的本地化提供了可能, 绝不意味着这么简单就完成了本地化, 在项目中我们还需要针对不同区域定制部分功能, 以便让它更符合规定, 以便于让不同地区的人接受它, 最好的本地化就是产品或讯息与特定目标文化产生共鸣的过程,就如同内容是在当地创建的一样, 但这个是一件知易行难的事

参考

本章源码

Assignment18

https://github.com/zhenlei520/MasaFramework.Practice

开源地址

MASA.Framework:https://github.com/masastack/MASA.Framework

MASA.EShop:https://github.com/masalabs/MASA.EShop

MASA.Blazor:https://github.com/BlazorComponent/MASA.Blazor

如果你对我们的 MASA Framework 感兴趣,无论是代码贡献、使用、提 Issue,欢迎联系我们

16373211753064.png

作者

MASA

发布于

2022-12-21

更新于

2023-05-26

许可协议