使用 XLocalizer 进行 ASP.NET Core 本地化





5.00/5 (2投票s)
通过在线翻译和自动资源创建本地化 ASP.NET Core...
ASP.NET Core 本地化,通过在线翻译、自动资源创建等功能实现...
引言
开发多语言 Web 应用程序需要构建本地化基础设施来处理请求本地化和视图、错误消息等内容的本地化。另一方面;每种本地化的语言至少需要一个资源文件,其中包含所有本地化的键值对。
构建本地化基础设施和填充资源文件可能需要大量时间和精力。XLocalizer 从头开始开发,以解决这两个问题,让开发者摆脱不必要的工作负担。
XLocalizer 提供什么?
简单的本地化设置:首先,它的目的是帮助开发者轻松创建本地化 Web 应用程序,而无需在开发本地化基础设施上浪费时间。
自动本地化:XLocalizer 最具吸引力的两个功能是自动资源创建和在线翻译。因此,任何遗漏的键都将被翻译并自动添加到相应的资源文件中。
支持多种资源类型:默认情况下,ASP.NET Core 使用“.resx”资源文件来存储本地化字符串。XLocalizer 打破了限制,提供了内置的本地化存储(XML、RESX、DB)。此外,还可以实现任何其他文件或数据库格式的自定义资源类型。
灵活性:XLocalizer
使用标准的本地化接口IStringLocalizer
和IHtmlLocalizer
,因此很容易从默认的 .NET Core 本地化系统切换到XLocalizer
,反之亦然。并且借助内置的资源导出功能,所有本地化资源都可以从任何文件/数据库类型导出到“.resx”文件类型。
可定制性:XLocalizer
可以进行各种细节的定制,以
- 使用自定义资源类型(例如,mysql、json、csv 等),
- 使用自定义资源导出器将资源从任何源导出到任何目标,
- 使用自定义翻译服务来翻译资源。
集中化:一个单一的位置,可以轻松地以简单的方式自定义所有验证错误、模型绑定错误和身份验证错误。
在本教程中,我将展示如何将XLocalizer
与 XML 资源文件和在线翻译结合使用。要了解其他资源类型的设置,如resx、db 或自定义源,请访问 https://DOCS.Ziyad.info。
安装
- 要充分利用
XLocalizer
,需要几个 nuget 包,我将首先列出所有包,然后在接下来的步骤中介绍它们的作用。// The main package PM > Install-Package XLocalizer // Online translation support PM > Install-Package XLocalizer.Translate // Translation service PM > Install-Package XLocalizer.Translate.MyMemoryTranslate // Use html tags to localize views PM > Install-Package XLocalizer.TagHelpers // Additional taghelper package for language dropdown PM > Install-Package LazZiya.TagHelpers
- Resources 文件夹:在项目根目录下,创建一个名为“LocalizationResources”的新文件夹,然后在其中创建一个名为“
LocSource
”的空类。此后,该类将用于从代码访问相关的资源文件。// Dummy class for grouping and accessing resource files public class LocSource { }
无需创建特定语言的资源文件,它们将由XLocalizer
自动创建和填充。
设置 XLocalizer
加速编码的一个小技巧;VS2019 可以自动插入缺失的命名空间(using …)。或者您可以按(Ctrl + .)查看上下文菜单,该菜单将添加缺失的命名空间。
- 打开 startup 文件并像往常一样配置请求本地化选项
services.Configure<RequestLocalizationOptions>(ops => { var cultures = new CultureInfo[] { new CultureInfo("en"), new CultureInfo("tr"), ... }; ops.SupportedCultres = cultures; ops.SupportedUICultures = cultures; ops.DefaultRequestCulture = new RequestCulture("en"); // Optional: add custom provider to support localization // based on route value ops.RequestCultureProviders.Insert (0, new RouteSegmentRequestCultureProvider(cultures)); });
XLocalizer
支持多种资源类型,如 XML、RESX、DB 等。在此示例中,我将使用 XML 文件存储本地化值,因此我们需要注册内置的XmlResourceProvider
,此提供程序将帮助我们使用 XML 文件作为资源文件来存储本地化的键值对。services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
XLocalizer
的主要优点之一是支持在线翻译,因此我们需要在 startup 文件中至少注册一个翻译服务。services.AddHttpClient<ITranslator, MyMemoryTranslateService>();
我在开发
XLocalizer
时使用了MyMemoryTranslateService
,但您可以自由选择任何可用的翻译服务,甚至实现您自己的翻译服务。- 可选地,配置 razor pages 以使用基于路由的本地化提供程序,这样我们可以拥有类似这样的 URL:https://:111/en/Index。然后在同一步骤中配置
XLocalizer
services.AddRazorPages() .AddRazorPagesOptions(ops => { ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages()); }) .AddXLocalizer<LocSource, MyMemoryTranslateService>(ops => { ops.ResourcesPath = "LocalizationResources"; ops.AutoAddKeys = true; ops.AutoTranslate = true; ops.TranslateFromCulture = "en"; });
- 配置应用程序使用本地化中间件
app.UseRequestLocalization();
为翻译服务添加 API 密钥
MyMemory 翻译 API 提供免费的匿名使用,每天最多 1000 个单词(*在撰写本文时*)。因此,基本上,您无需添加任何密钥即可进行测试。无论如何,您可以通过提供电子邮件和免费生成的密钥来增加免费使用量,最高可达每天 30,000 个单词。有关更多详细信息,请参阅MyMemory API 使用限制。
使用MyMemory API Keygen 获取密钥,然后像下面一样将密钥和有效的电子邮件地址添加到用户机密文件
{
"XLocalizer.Translate": {
"MyMemory": {
"Email": "...",
"Key": "..."
}
}
}
不同的翻译服务可能需要不同的设置。有关不同翻译服务设置的详细信息,请参阅翻译服务文档。
完整的 XLocalizer Startup 代码
为简化起见,已省略示例 startup 文件中的不必要代码。
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
// ...
public void ConfigureServices(IServiceCollection services)
{
// Configure request localization
services.Configure<RequestLocalizationOptions>(ops =>
{
var cultures = new CultureInfo[] { new CultureInfo("en"),
new CultureInfo("tr"), new CultureInfo("ar") };
ops.SupportedCultures = cultures;
ops.SupportedUICultures = cultures;
ops.DefaultRequestCulture =
new Microsoft.AspNetCore.Localization.RequestCulture("en");
ops.RequestCultureProviders.Insert
(0, new RouteSegmentRequestCultureProvider(cultures));
});
// Register translation service
services.AddHttpClient<ITranslator, MyMemoryTranslateService>();
// Register XmlResourceProvider
services.AddSingleton<IXResourceProvider, XmlResourceProvider>();
services.AddRazorPages()
.AddRazorPagesOptions(ops =>
{ ops.Conventions.Insert(0, new RouteTemplateModelConventionRazorPages()); })
// Add XLocalizer
.AddXLocalizer<LocSource, MyMemoryTranslateService>(ops =>
{
ops.ResourcesPath = "LocalizationResources";
ops.AutoAddKeys = true;
ops.AutoTranslate = true;
// Optional: Just in case you need to change the source translation culture.
// if not provided, the default culture will be used
ops.TranslateFromCulture = "en";
// Recommended: turn on caching during production for faster localization
ops.UseExpressMemoryCache = true;
});
}
// This method gets called by the runtime.
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ...
// Use request localization middleware
app.UseRequestLocalization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
});
}
}
以上是在 startup 文件中所需的所有设置。接下来,我们将配置视图和后端本地化。
本地化视图
我们安装了一个方便的 nuget 包来本地化视图XLocalizer.TagHelpers
,这个包使得使用 html 标签和 html 属性轻松本地化视图成为可能,这使得 html 代码干净且易于阅读和维护。
- 在_ViewImports.cshtml 文件中添加
taghelper
@addTagHelper *, XLocalizer.TagHelpers
- 在 html 标签内使用
localize-content
属性来本地化内部文本/html<h1 localize-content>Welcome</h1>
- 使用
localize
html 标签本地化内部文本/html 段落<localize> <h1>Welcome</h1> <p>My contents...</p> </localize>
- 本地化带参数的 html 字符串
@{ var args = new object[] { "http://DOCS.Ziyad.info" } } <p localize-args="args"> Visit <a href="{0}">DOCS</a> for more details. </p>
- 本地化 html 属性,例如 title
<img src="../picture.jpg" localize-att-title="Nature picture" />
下面是Register.cshtml 页面的完全本地化示例,请注意,我们只需要将“
localize-content
”属性添加到相关标签,这使得页面代码保持整洁,易于阅读和更新。@page @model RegisterModel @{ ViewData["Title"] = "Register"; } <h1 localize-content>@ViewData["Title"]</h1> <div class="row"> <div class="col-md-4"> <form asp-route-returnUrl="@Model.ReturnUrl" method="post"> <h4 localize-content>Create a new account.</h4> <hr /> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="Input.Email"></label> <input asp-for="Input.Email" class="form-control" /> <span asp-validation-for="Input.Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Input.Password"></label> <input asp-for="Input.Password" class="form-control" /> <span asp-validation-for="Input.Password" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Input.ConfirmPassword"></label> <input asp-for="Input.ConfirmPassword" class="form-control" /> <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span> </div> <button type="submit" class="btn btn-primary" localize-content> Register</button> </form> </div> </div> @section Scripts { <partial name="_ValidationScriptsPartial" /> }
使用 XLocalizer.TagHelpers 进行视图本地化示例
本地化验证属性错误、模型绑定错误和身份验证错误消息
使用XLocalizer
本地化所有框架错误消息不需要任何额外的设置,并且不需要在属性标签内提供任何错误消息!所有错误消息都将由 startup 中XLocalizer
的默认设置分配和本地化。
下面是某些验证属性的用法示例
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
同样对于模型绑定错误和身份验证错误,我们无需进行任何额外的设置,XLocalizer
将默认处理所有错误消息的本地化。
下面是Register.cshtml.cs 文件的后端本地化示例
public class InputModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password")]
public string ConfirmPassword { get; set; }
}
自定义错误消息
在某些情况下,您可能需要自定义验证属性、模型绑定或身份验证的错误消息。或者您可能希望使用“en
”以外的语言提供默认错误消息,这样XLocalizer
就可以从正确的语言进行翻译。
第一个解决方案是在 startup 文件中使用内联选项设置,如下所示提供相关的错误消息
services.AddRazorPages()
.AddXLocalizer<...>(ops =>
{
// ...
ops.ValidationErrors = new ValidationErrors
{
RequiredAttribute_ValidationError = "The {0} field is required.",
CompareAttribute_MustMatch =
"'{0}' and '{1}' do not match.",
StringLengthAttribute_ValidationError =
"The field {0} must be a string with a maximum length of {1}.",
// ...
};
ops.ModelBindingErrors = new ModelBindingErrors
{
AttemptedValueIsInvalidAccessor = "The value '{0}' is not valid for {1}.",
MissingBindRequiredValueAccessor =
"A value for the '{0}' parameter or property was not provided.",
MissingKeyOrValueAccessor = "A value is required.",
// ...
};
ops.IdentityErrors = new IdentityErrors
{
DuplicateEmail = "Email '{0}' is already taken.",
DuplicateUserName = "User name '{0}' is already taken.",
InvalidEmail = "Email '{0}' is invalid.",
// ...
};
});
另一个选项是将所有XLocalizer
设置配置在 json 文件中。
JSON 设置
如果您是一位喜欢保持 startup 文件整洁的开发者(就像我一样 :)),那么您会很高兴知道您可以在 json 文件中进行所有这些定制,并且只需使用一行代码在 startup 中读取配置。
- 将相关配置添加到appsettings.json 或任何您选择的自定义 json 文件。
{ "XLocalizerOptions" : { "AutoAddKeys" : true, "AutoTranslate" : true, // ... } }
- 设置
XLocalizer
以读取相关的配置部分services.AddRaqzorPages() .AddXLocalizer<...> (ops => Configuration.GetSection("XLocalizerOptions").Bind(ops));
- 下面是
XLocalizer
选项的示例 json 设置,以及可自定义的错误消息{ "XLocalizerOptions": { "ResourcesPath": "LocalizationResources", "AutoAddKeys": true, "AutoTranslate": true, "UseExpressMemoryCache": true, "TranslateFromCulture": "en", "ValidationErrors": { "CompareAttribute_MustMatch": "'{0}' and '{1}' do not match. They should not be different!", "CreditCardAttribute_Invalid": "The {0} field is not a valid credit card number.", "CustomValidationAttribute_ValidationError": "{0} is not valid.", "DataTypeAttribute_EmptyDataTypeString": "The custom DataType string cannot be null or empty.", "EmailAddressAttribute_Invalid": "The {0} field is not a valid e-mail address.", "FileExtensionsAttribute_Invalid": "The {0} field only accepts files with the following extensions: {1}", "MaxLengthAttribute_ValidationError": "The field {0} must be a string or array type with a maximum length of '{1}'.", "MinLengthAttribute_ValidationError": "The field {0} must be a string or array type with a minimum length of '{1}'.", "PhoneAttribute_Invalid": "The {0} field is not a valid phone number.", "RangeAttribute_ValidationError": "The field {0} must be between {1} and {2}.", "RegexAttribute_ValidationError": "The field {0} must match the regular expression '{1}'.", "RequiredAttribute_ValidationError": "The {0} field is required. Don't bypass this field!", "StringLengthAttribute_ValidationError": "The field {0} must be a string with a maximum length of {1}.", "StringLengthAttribute_ValidationErrorIncludingMinimum": "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.", "UrlAttribute_Invalid": "The {0} field is not a valid fully-qualified http, https, or ftp URL.", "ValidationAttribute_ValidationError": "The field {0} is invalid." }, "IdentityErrors": { "DuplicateEmail": "Email '{0}' is already taken.", "DuplicateUserName": "User name '{0}' is already taken. Please try another one.", "InvalidEmail": "Email '{0}' is invalid.", "DuplicateRoleName": "Role name '{0}' is already taken.", "InvalidRoleName": "Role name '{0}' is invalid.", "InvalidToken": "Invalid token.", "InvalidUserName": "User name '{0}' is invalid, can only contain letters or digits.", "LoginAlreadyAssociated": "A user with this login already exists.", "PasswordMismatch": "Incorrect password.", "PasswordRequiresDigit": "Passwords must have at least one digit ('0'-'9').", "PasswordRequiresLower": "Passwords must have at least one lowercase ('a'-'z').", "PasswordRequiresNonAlphanumeric": "Passwords must have at least one non alphanumeric character.", "PasswordRequiresUniqueChars": "Passwords must use at least {0} different characters.", "PasswordRequiresUpper": "Passwords must have at least one uppercase ('A'-'Z').", "PasswordTooShort": "Passwords must be at least {0} characters.", "UserAlreadyHasPassword": "User already has a password set.", "UserAlreadyInRole": "User already in role '{0}'.", "UserNotInRole": "User is not in role '{0}'.", "UserLockoutNotEnabled": "Lockout is not enabled for this user.", "RecoveryCodeRedemptionFailed": "Recovery code redemption failed.", "ConcurrencyFailure": "Optimistic concurrency failure, object has been modified.", "DefaultError": "An unknown failure has occurred." }, "ModelBindingErrors": { "AttemptedValueIsInvalidAccessor": "The value '{0}' is not valid for {1}.", "MissingBindRequiredValueAccessor": "A value for the '{0}' parameter or property was not provided.", "MissingKeyOrValueAccessor": "A value is required.", "MissingRequestBodyRequiredValueAccessor": "A non-empty request body is required.", "NonPropertyAttemptedValueIsInvalidAccessor": "The value '{0}' is not valid.", "NonPropertyUnknownValueIsInvalidAccessor": "The supplied value is invalid.", "NonPropertyValueMustBeANumberAccessor": "The field must be a number.", "UnknownValueIsInvalidAccessor": "The supplied value is invalid for {0}.", "ValueIsInvalidAccessor": "The value '{0}' is invalid. You entered something weird!", "ValueMustBeANumberAccessor": "The field {0} must be a number. Don't use letters or special characters.", "ValueMustNotBeNullAccessor": "The value '{0}' is invalid. This can't be null." } } }
在 json 文件中自定义所有 XLocalizer 选项
因此,这是一个单一且简单的方式来定制所有错误消息。这些消息将根据请求的语言由XLocalizer
翻译成其他语言。
添加语言导航
每个多语言 Web 应用程序都必须提供一种切换不同语言的方式。您可能有自己的语言导航实现,但以防您需要轻松添加一个(我们之前安装了LazZiya.TagHelpers
)
- 将
taghelpers
添加到_ViewImports 文件@addTagHelper *, LazZiya.TagHelpers
- 打开_layout.cshtml 并将语言导航添加到您需要显示它的位置
<language-nav></language-nav>
我强烈建议设置语言导航以配置如此处文档页面所述的文化 cookie。这样,文化选择就可以存储在 cookie 中以备将来使用。
运行应用程序
如果您已正确完成所有步骤,并且一旦启动应用程序,请查看 VS 的输出窗口以查看日志,您将看到XLocalizer
已开始自动翻译视图并插入值。此外,所有验证属性、模型绑定和身份验证错误也已本地化。
注意:本地化身份验证页面需要将身份验证脚手架到项目中。
您只需添加新语言即可;在 startup 文件中将该语言添加到支持的语言列表中,其余的一切都由XLocalizer
来完成。 :)
支持的 .NET Core 版本
- 2.x
- 3.x
- 5.0
支持的项目类型
- Razor Pages
- MVC
- Blazor Server
参考文献
- 文档:https://docs.ziyad.info
- 仓库:https://github.com/LazZiya/XLocalizer
- 示例:https://github.com/LazZiya/XLocalizer.Samples
历史
- 2020 年 11 月 13 日:初版