概念
作为一个普通开发者, 我们负责的项目的使用群体大多数是本国的人民, 但不可避免的也有一些做外贸的业务或者给外企做的项目, 这个时候就要求我们的项目有服务全球客户的能力, 而一个支持国际化能力的框架会让我们项目的体验变得更好.
关于本地化我们听到最多的是I18N、L10N、G11N, 那它们分别代表了什么意思呢?
- I18N: 是"Internationalization" 的缩写, 由于I到N之间间隔了18个字母, 也被简称为"i18n". 使产品或软件具有不同国际市场的普遍适应性, 从而无需重新设计就可适应多种语言和文化习俗的过程. 真正的国际化要在软件设计和文档开发过程中, 使产品或软件的功能和代码设计能处理多种语言和文化习俗, 具有良好的本地化能力. 它让程序具备了支持多种语言的能力
- L10N: 是"Localization"的缩写, 由于L到N之间间隔了10个字母, 也被简称为"L10N". 是将产品或软件针对特定国际语言和文化进行加工, 使之符合特定区域市场的过程. 真正的本地化要考虑目标区域市场的语言、文化、习俗、特征和标准. 通常包括改变软件的书写系统(输入法)、键盘使用、字体、日期、时间和货币格式等
- G11N: 是"Globalization"的缩写, 由于G到N之间间隔了11个字母, 也被简称为"G11n", 是指在全球范围内推出产品的业务方面, 可以简单理解为 "I18N" + "L10N"
本地化的意义
本地化包括:
- 产品本地化
- 技术研发本地化
- 原材料本地化
- 人才本地化
- 企业文化本地化
经过研究表明, 本地化后的产品的销量会比未经过本地化的更好, 75%的人偏好用母语购买产品, 86%的本地化广告在点击率、转换率上超过未经过本地化的广告, 这些数字都说明了人们对自己母语显示的内容更加感兴趣, 通过本地化可以让你更精准的打动客户, 帮你迅速的进入市场, 但本地化不等于直接翻译, 本地化是基于对当地市场情况和文化精心策划的, 在习惯、习俗、日期和货币格式上是不相同的, 本地化的产品能节约沟通成本, 更容易被理解和接受.
举个通俗的例子: 相比找一个不认识的陌生人买东西, 我们更喜欢去找那个知根知底的本地人买东西, 这无关产品的质量, 只是熟悉的人更容易获得我们的认同感
虽然本地化包括很多模块, 但在此我们只考虑产品的本地化, 下面我们就说一下如何做使得自己的产品可以支持本地化
使用
- 安装.NET 6.0
- 新建ASP.NET Core 空项目
Assignment.I18nDemo
,并安装Masa.Contrib.Globalization.I18n.AspNetCore
dotnet add package Masa.Contrib.Globalization.I18n.AspNetCore --version 0.7.0-preview.16
- 注册
I18n
, 并修改Program.cs
builder.Services.AddI18n();
- 使用
I18N
app.UseI18n();//启用本地化中间件
- 添加多语言资源文件, 文件夹结构如下:
- Resources
- I18n
- en-US.json
- zh-CN.json
- supportedCultures.json
- en-US.json
{
"Home":"Home"
}
- zh-CN.json
{
"Home":"首页"
}
- supportedCultures.json
[
{
"Culture":"zh-CN",
"DisplayName":"中文简体",
"Icon": "{Replace-Your-Icon}"
},
{
"Culture":"en-US",
"DisplayName":"English (United States)",
"Icon": "{Replace-Your-Icon}"
}
]
- 使用
I18n
app.Map("/test", (string key) => I18n.T(key));
- 测试多语言, 在浏览器访问"https://localhost:7082/get?key=Home"即可得到对应语言下键名为
Home
的值
是不是感觉用起来十分简单呢, 但这究竟是如何做的呢
进阶
国内的小伙伴根据上面的例子操作下来, 会发现请求响应的内容是中文, 那什么情况下它会变成英文呢?
本地化中间件
由于.NET 提供了本地化的能力, 它提供了本地化的中间件, 通过它使得我们的项目具备解析当前语言的能力
目前它支持了以下三种方式进行语言切换:
- URL 参数 方式: ?culture=en-US,此方式优先级最高,格式为:culture=区域码
- Cookies 方式:cookie 格式为 c=%LANGCODE%|uic=%LANGCODE%,其中 c 是 Culture,uic 是 UICulture, 例如:
c=en-UK|uic=en-US
- 客户端浏览器语言自动匹配:如果前面两种方式都没有设置,支持自动根据客户端浏览器语言进行匹配
语言优先级:
URL 参数 方式 > Cookies方式 > 客户端语言 > 默认语言
默认语言
默认语言有两种配置方式, 它们分别是:
- 约定配置
-
supportedCultures.json
文件中的第一个语言
-
- 手动指定默认语言
- 通过
app.UseI18n("{Replace-Your-DefaultCulture}")
- 通过
它们的优先级是:
手动指定默认语言 > 约定配置
修改默认资源路径
builder.Services.AddI18n(options =>
{
options.ResourcesDirectory = Path.Combine("Resources", "I18n");//修改默认资源路径
options.SupportedCultures = new List<CultureModel>() //支持语言
{
new("zh-CN"),
new("en-US")
};
});
支持资源文件
如果你希望将配置文件嵌入到dll文件中, 不希望被看到修改, 那么你需要将资源json文件的生成操作改为嵌入的资源
, 并修改I18N注册代码为:
builder.Services.AddI18nByEmbedded();
嵌套配置
相信了解前端开发的小伙伴也见到过嵌套的资源配置, 那对于Masa提供的多语言方案而言, 我们也支持这种格式的配置, 例如:
{
"Home":"首页",
"User":{
"Name":"名称"
}
}
我们希望拿到User节点下的Name属性的值, 则可以通过:
var result = I18N.T("User.Name");//其中key的值不区分大小写
当然除此之外, 我们也可以将不同资源的文件分开存放到不同的json文件, 然后通过添加多个资源目录的文件, 最终实现, 但在使用时稍微有区别:
app.Map("/test2", (string key, II18n<CustomResource> i18n) => i18n.T(key));//通过DI获取到自定义资源下Key对应内容
如何对接远程资源配置
多语言的远程资源配置目前仅支持Dcc, 我们可以这样做:
- 注册
MasaConfiguration
并使用Dcc, 修改Program.cs
builder.Services.AddMasaConfiguration(configurationBuilder =>
{
// configurationBuilder.UseDcc();//正确配置好Dcc配置后开启
});
- 配置Dcc配置信息, 修改
appsettings.json
{
"DccOptions": {
"ManageServiceAddress": "{Replace-Your-DccManagerServiceHost}",
"RedisOptions": {
"Servers": [
{
"Host": "{Replace-Your-DccUseRedisHost}",
"Port": 6379
}
],
"DefaultDatabase": 0,
"Password": ""
}
}
}
对MasaConfiguration有疑问点这里
- 注册I18n
builder.Services.AddI18n(
Path.Combine("Resources", "I18n"),
"supportedCultures.json",
options => options.UseDcc());
我们仅需要在/Resources/I18n
目录下存放支持语言的配置即可, 具体的语言配置将从Dcc读取 (读取Dcc语言的配置节点: 在Dcc配置下默认AppId下的"Culture.{语言}"), 例如:
如果支持语言为zh-CN
、en-US
, 则默认读取Dcc配置下默认AppId下Culture.zh-CN
、Culture.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,欢迎联系我们