当前位置: 首页 > news >正文

【.NET】本地化

本地化

本地化指的是让同一程序在不同语言与文化环境下,以用户习惯的方式呈现界面文本、日期时间、数字货币、排序规则与区域格式
本文只关注界面文本的本地化,一般的做法是把可见文本从代码中抽离成可翻译资源,并在运行时按当前文化和区域加载对应的语言版本

本地化资源文件

当前示例为使用中文简体和美式英文进行本地化
新建名称为MultiLanguage的资源名称类,以及新建两个特定文化嵌入资源文件类MultiLanguage.zh-CN.resx和MultiLanguage.en-US.resx,命名格式为{资源名称}.{文化}-{国家/区域}

  • MultiLanguage
public class MultiLanguage
{
}
  • MultiLanguage.en-US.resx
<?xml version="1.0" encoding="utf-8"?>
<root><!-- Microsoft ResX Schema Version 2.0The primary goals of this format is to allow a simple XML format that is mostly human readable. The generation and parsing of the various data types are done through the TypeConverter classes associated with the data types.Example:... ado.net/XML headers & schema ...<resheader name="resmimetype">text/microsoft-resx</resheader><resheader name="version">2.0</resheader><resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader><resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader><data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data><data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data><data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"><value>[base64 mime encoded serialized .NET Framework object]</value></data><data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"><value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value><comment>This is a comment</comment></data>There are any number of "resheader" rows that contain simple name/value pairs.Each data row contains a name, and value. The row also contains a type or mimetype. Type corresponds to a .NET class that support text/value conversion through the TypeConverter architecture. Classes that don't support this are serialized and stored with the mimetype set.The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not extensible. For a given mimetype the value must be set accordingly:Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can read any of the formats listed below.mimetype: application/x-microsoft.net.object.binary.base64value   : The object must be serialized with : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter: and then encoded with base64 encoding.mimetype: application/x-microsoft.net.object.soap.base64value   : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter: and then encoded with base64 encoding.mimetype: application/x-microsoft.net.object.bytearray.base64value   : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter: and then encoded with base64 encoding.--><xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"><xsd:import namespace="http://www.w3.org/XML/1998/namespace" /><xsd:element name="root" msdata:IsDataSet="true"><xsd:complexType><xsd:choice maxOccurs="unbounded"><xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element><xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /><xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /><xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /><xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element></xsd:choice></xsd:complexType></xsd:element></xsd:schema><resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>2.0</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><data name="你好,世界" xml:space="preserve"><value>Hello world</value></data><data name="{0}长度只可在{2}到{1}个字符之间" xml:space="preserve"><value>The {0} length can only be between {2} and {1} characters.</value></data>
</root>
  • MultiLanguage.zh-CN.resx
<?xml version="1.0" encoding="utf-8"?>
<root><!-- Microsoft ResX Schema Version 2.0The primary goals of this format is to allow a simple XML format that is mostly human readable. The generation and parsing of the various data types are done through the TypeConverter classes associated with the data types.Example:... ado.net/XML headers & schema ...<resheader name="resmimetype">text/microsoft-resx</resheader><resheader name="version">2.0</resheader><resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader><resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader><data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data><data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data><data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"><value>[base64 mime encoded serialized .NET Framework object]</value></data><data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"><value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value><comment>This is a comment</comment></data>There are any number of "resheader" rows that contain simple name/value pairs.Each data row contains a name, and value. The row also contains a type or mimetype. Type corresponds to a .NET class that support text/value conversion through the TypeConverter architecture. Classes that don't support this are serialized and stored with the mimetype set.The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not extensible. For a given mimetype the value must be set accordingly:Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can read any of the formats listed below.mimetype: application/x-microsoft.net.object.binary.base64value   : The object must be serialized with : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter: and then encoded with base64 encoding.mimetype: application/x-microsoft.net.object.soap.base64value   : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter: and then encoded with base64 encoding.mimetype: application/x-microsoft.net.object.bytearray.base64value   : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter: and then encoded with base64 encoding.--><xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"><xsd:import namespace="http://www.w3.org/XML/1998/namespace" /><xsd:element name="root" msdata:IsDataSet="true"><xsd:complexType><xsd:choice maxOccurs="unbounded"><xsd:element name="metadata"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" /></xsd:sequence><xsd:attribute name="name" use="required" type="xsd:string" /><xsd:attribute name="type" type="xsd:string" /><xsd:attribute name="mimetype" type="xsd:string" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="assembly"><xsd:complexType><xsd:attribute name="alias" type="xsd:string" /><xsd:attribute name="name" type="xsd:string" /></xsd:complexType></xsd:element><xsd:element name="data"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /><xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /><xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /><xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /><xsd:attribute ref="xml:space" /></xsd:complexType></xsd:element><xsd:element name="resheader"><xsd:complexType><xsd:sequence><xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /></xsd:sequence><xsd:attribute name="name" type="xsd:string" use="required" /></xsd:complexType></xsd:element></xsd:choice></xsd:complexType></xsd:element></xsd:schema><resheader name="resmimetype"><value>text/microsoft-resx</value></resheader><resheader name="version"><value>2.0</value></resheader><resheader name="reader"><value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><resheader name="writer"><value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value></resheader><data name="你好,世界" xml:space="preserve"><value>你好,世界</value></data><data name="{0}长度只可在{2}到{1}个字符之间" xml:space="preserve"><value>{0}长度只可在{2}到{1}个字符之间</value></data>
</root>

这样在使用IStringLocalizer对应的实例进行本地化时,才会根据文化找到所有命名为MultiLanguage.{文化}*的嵌入资源文件
可以根据项目需要,创建多个资源名称类以及对应资源名称的文化嵌入资源文件类
推荐将本地化资源Key设置为默认的区域和文化翻译文本,这样可在没有命中特定的文化和区域时,不会向用户界面返回显示一个令用户疑惑的本地化资源Key

本地化配置

新建名称为InternationalizationExtension的扩展类,用于配置本地化支持的文化和区域以及自定义文化和区域确认来源中间件
添加本地化服务,进行ResourceManagerStringLocalizerFactory工厂类和StringLocalizer类的依赖注入,.NET默认的本地化资源文件来源是从嵌入资源文件获取,也就是由ResourceManagerStringLocalizerFactory进行对应的嵌入资源管理类的创建来确定的

public static void AddInternationalization(this IServiceCollection services, IMvcBuilder mvcBuilder)
{services.AddLocalization();services.AddSingleton<IStringLocalizer>((sp) =>{var localizer = sp.GetRequiredService<IStringLocalizer<MultiLanguage>>();return localizer;});// 配置数据注解的本地化资源文件来源mvcBuilder.AddDataAnnotationsLocalization(options =>{options.DataAnnotationLocalizerProvider = (type, factory) =>factory.Create(typeof(MultiLanguage));});
}

设置程序支持的文化和区域列表,以及调整文化和区域处理器的执行先后顺序,可根据项目需要进行执行顺序的调整,当前示例中的执行顺序为

  1. 从从查询字符串参数中的culture键和ui-culture键获取文化(默认已添加)
  2. 从cookie中的.AspNetCore.Culture键获取文化(默认已添加)
  3. 从Accept-Language请求头获取文化(默认已添加)
  4. 从路由数据中的键(可自定义名称,以下示例为culture和ui-culture)获取文化(可选)
  5. 自定义(可选)

一般情况下,不需要添加额外的文化和区域处理器,只使用默认的文化和区域处理器,即可满足项目实际需求
调用UseRequestLocalization方法即是使用.NET中自带的文化和区域确认中间件,在其中按照顺序执行文化和区域处理器,将获取到的文化和区域分别赋值给CultureInfo.CurrentCulture和CultureInfo.CurrentUICulture,以便根据当前文化和区域返回对应的本地化内容

public static IApplicationBuilder UseInternationalization(this IApplicationBuilder app)
{var supportLanguages = new[] { "zh-CN", "en-US" };// 设置文化和区域的默认值和支持的文化和区域列表var localizationOptions = new RequestLocalizationOptions().SetDefaultCulture(supportLanguages[0]).AddSupportedCultures(supportLanguages).AddSupportedUICultures(supportLanguages);// 将当前文化信息自动添加到响应头中localizationOptions.ApplyCurrentCultureToResponseHeaders = true;localizationOptions.RequestCultureProviders.Add(new RouteDataRequestCultureProvider(){Options = localizationOptions,RouteDataStringKey = "culture",UIRouteDataStringKey = "ui-culture"});// localizationOptions.RequestCultureProviders.Clear();localizationOptions.RequestCultureProviders.Add(new AppSettingsRequestCultureProvider(){Options = localizationOptions});app.UseRequestLocalization(localizationOptions);return app;
}

从appsetting.json中确认文化和区域

public class AppSettingsRequestCultureProvider : RequestCultureProvider
{public string CultureKey { get; set; } = "Localization:Culture";public string UICultureKey { get; set; } = "Localization:UICulture";public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext){if (httpContext == null){throw new ArgumentNullException();}string? culture = null;string? uiCulture = null;var configuration = httpContext.RequestServices.GetService<IConfiguration>();if (configuration != null){culture = configuration[CultureKey];uiCulture = configuration[UICultureKey];}if (culture == null && uiCulture == null){return Task.FromResult((ProviderCultureResult?)null);}if (culture != null && uiCulture == null){uiCulture = culture;}if (culture == null && uiCulture != null){culture = uiCulture;}var providerResultCulture = new ProviderCultureResult(culture, uiCulture);return Task.FromResult((ProviderCultureResult?)providerResultCulture);}
}

appsetting.json

{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"AllowedHosts": "*","Localization": {"Culture": "zh-CN","UICulture": "zh-CN"}
}

本地化使用

新建InternationalizationController控制器类和DataAnnotationsTestDto类

[ApiController]
[Route("api/[controller]")]
public class InternationalizationController : Controller
{private readonly IStringLocalizer<MultiLanguage> _stringLocalizer;public InternationalizationController(IStringLocalizer<MultiLanguage> stringLocalizer){_stringLocalizer = stringLocalizer;}[HttpGet("method")]public string GetByMethod(string localizationKey){return _stringLocalizer.GetString(localizationKey);}[HttpGet("index")]public string GetByIndex(string localizationKey){return _stringLocalizer[localizationKey];}[HttpGet("all-culture")]public IEnumerable<string> GetAllCulture(){var cultureNames = CultureInfo.GetCultures(CultureTypes.AllCultures).Select(culture => culture.Name);return cultureNames;}[HttpPost("name")]public string CreateName(DataAnnotationsTestDto input){// logic to create namereturn input.Name;}
}
public class DataAnnotationsTestDto
{[StringLength(64, MinimumLength = 2, ErrorMessage = "{0}长度只可在{2}到{1}个字符之间")]public string Name { get; set; } = null!;
}

常规本地化测试结果如下

DataAnnotations本地化测试结果如下

另外,还有其他本地化资源文件来源,例如从JSON文件中获取、从数据库中获取等来源方式,请自行查阅.NET本地化扩展

http://www.jsqmd.com/news/673812/

相关文章:

  • AI与Agent开始接管重复性工作后,测试岗会不会成为最先被淘汰的岗位?
  • 匠行科技基于AMD Xilinx Kintex UltraScale系列FPGA XCKU060与TI KeyStone架构八核DSP TMS320C6678的6U CPCI异构多核高性能信号处理板卡
  • 3步解锁MusicBee完美歌词体验:网易云音乐插件终极指南
  • # WebGPU实战:从零构建高性能图形渲染管线(附完整代码与流程图)在现代Web应用中,**图形渲染性能
  • 从CentOS迁移到openEuler 22.03 LTS的Dify生产级部署——仅用1份Ansible Playbook+4个国产化补丁,实现零业务中断切换
  • I Have a Dream
  • 软件著作权主体指享有著作权的人,包括公民、法人和其他组织,对主体无行为能力限制,对外国人、无国籍人实行“有条件“国民待遇原则
  • Boost库配置后如何验证?一个多线程测试案例带你玩转VS2019
  • Java响应式编程革命再升级(Loom协程×Virtual Threads×Reactive Streams三重融合白皮书)
  • 告别u8/u16混乱:STM32F407标准库网络驱动向HAL库移植的类型定义避坑指南
  • 制品仓库管理:二进制文件的版本控制与分发策略
  • ArcGIS Pro 3.0 保姆级教程:用ModelBuilder批量处理气象nc文件,12个月数据一键导出为GeoTIFF
  • 如何在10分钟内用BallonsTranslator完成专业漫画翻译?简单三步搞定AI翻译工作流
  • 【12.MyBatis源码剖析与架构实战】19.MyBatis分⻚插件设计与实战
  • 拆解网红小风扇:它的‘边充边放’和‘过路保护’是怎么用一颗FS8A15S8 MCU实现的?
  • OSG+Qt实战:从官方osgviewerQt例子到自定义3D编辑器界面
  • Typora+LaTeX公式保姆级教程:从基础语法到复杂矩阵排版
  • 避坑指南:YOLOv5 v6.2训练分类模型时,关于数据集划分、种子复现和模型导出的几个关键细节
  • CarMaker for Simulink联合仿真实战:如何利用IPGMovie和Data Inspector实时调试你的车辆模型
  • 必看!2026有自主研发技术的GEO服务商推荐,避开外包坑 - 品牌测评鉴赏家
  • 保姆级教程:用Python和Basemap绘制台风‘利奇马’期间的卫星云图(附完整代码)
  • 用Arduino Nano和AD8232模块DIY一个心率监测手环(附完整代码与电路图)
  • 收藏!AI入行指南:小白程序员必备的岗位选择、技能树与学习路径
  • 终极跨平台RGB灯光控制:OpenRGB一站式解决方案彻底告别软件混乱
  • JavaScript的Object.hasOwn:比hasOwnProperty更安全的属性检查
  • 手机变随身Linux服务器:用Termux+Ubuntu搭建个人网盘/博客的踩坑实录
  • idea 插件envfile初体验
  • 如何快速实现音频转文字:免费开源工具完整指南
  • CityEngine规则文件(.cga)完全解读:从‘看不懂’到能改‘屋顶样式’和‘楼层高度’
  • 无线调试中的端口转发问题