ASP.Net核心本地化

Ted*_*sen 12 asp.net-core-mvc asp.net-core asp.net-core-localization asp.net-core-identity

ASP.Net核心为本地化提供了新的支持.

在我的项目中,我只需要一种语言.对于大多数文本和注释,我可以用我的语言指定内容,但对于来自ASP.Net Core本身的文本,语言是英语.

例子:

  • 密码必须至少有一个大写字母('A' - 'Z').
  • 密码必须至少有一位数('0' - '9').
  • 用户名'x@x.com'已被占用.
  • 电子邮件字段不是有效的电子邮件地址.
  • 值''无效.

我已经尝试手动设置文化,但语言仍然是英语.

app.UseRequestLocalization(new RequestLocalizationOptions
{
    DefaultRequestCulture = new RequestCulture("nb-NO"),
    SupportedCultures = new List<CultureInfo> { new CultureInfo("nb-NO") },
    SupportedUICultures = new List<CultureInfo> { new CultureInfo("nb-NO") }
});
Run Code Online (Sandbox Code Playgroud)

如何更改ASP.Net Core的语言,或覆盖其默认文本?

Ral*_*ing 14

列出的错误消息在ASP.NET Core Identity中定义,并由IdentityErrorDescriber.到目前为止我没有找到翻译的资源文件,我认为它们没有本地化.在我的系统(德语语言环境)上,尽管CultureInfo设置正确,但它们也没有被翻译.

您可以配置自定义IdentityErrorDescriber以返回您自己的消息及其翻译.例如, 如何更改MVC Core ValidationSummary的默认错误消息?

在您的Configure方法中,Startup.cs您可以连接从IdentityErrorDescriberLike 继承的Error Describer类

 services.AddIdentity<ApplicationUser, IdentityRole>()
        .AddErrorDescriber<TranslatedIdentityErrorDescriber>();
Run Code Online (Sandbox Code Playgroud)

对于其他默认模型错误消息(如无效数字),您可以在中提供自己的访问者功能ModelBindingMessageProvider.此提供程序由ASP.NET Core MVC使用,也可以在其中进行配置Startup.cs.

services.AddMvc(
            options => 
            options.ModelBindingMessageProvider.ValueIsInvalidAccessor = s => $"My not valid text for {s}");
Run Code Online (Sandbox Code Playgroud)


Moh*_*deh 10

让我对这个问题做一个全面的回答,并描述我在阅读 .net core 源代码后是如何想出一个解决方案的。

在进一步描述之前,首先使用 NuGet Microsoft.Extensions.Localization安装这个包

你们可能还记得在 asp.net full framework 中,文化之间的切换非常简单

System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Run Code Online (Sandbox Code Playgroud)

但在 .net core 中,文化不再与当前线程相关联。asp.net 核心引擎有一个管道,我们可以向这个管道添加不同的 MiddleWare,所以RequestLocalizationMiddleware是处理本地化的中间件类,我们可能有多个提供者,因此它将遍历所有文化提供者,如QueryStringRequestCultureProviderCookieRequestCultureProviderAcceptLanguageHeaderRequestCultureProvider , ...

一旦请求本地化中间件可以从第一个提供者那里获取当前语言环境,它就会忽略其他人并将请求传递给管道中的下一个中间件,因此列表中提供者的顺序非常重要。

我个人更喜欢将文化存储在浏览器 cookie 中,因此由于CookieRequestCultureProvider不是列表中的第一个文化提供者,我将其移至列表顶部,Startup.cs > ConfigureServices 中这部分的配置如下

services.Configure<RequestLocalizationOptions>(options =>
             {
                 var supportedCultures = new[]
                 {
                    new CultureInfo("en-US"),
                    new CultureInfo("fa-IR"),
                    new CultureInfo("de-DE")
                };
                options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
                options.SupportedCultures = supportedCultures;
                options.SupportedUICultures = supportedCultures;

                var defaultCookieRequestProvider =
                    options.RequestCultureProviders.FirstOrDefault(rcp =>
                        rcp.GetType() == typeof(CookieRequestCultureProvider));
                if (defaultCookieRequestProvider != null)
                    options.RequestCultureProviders.Remove(defaultCookieRequestProvider);

                options.RequestCultureProviders.Insert(0,
                    new CookieRequestCultureProvider()
                    {
                        CookieName = ".AspNetCore.Culture",
                        Options = options
                    });
            });
Run Code Online (Sandbox Code Playgroud)

让我来描述一下上面的代码,我们的应用程序默认文化是美国,我们只支持英语、波斯语、德国,所以如果浏览器有不同的语言环境或者你设置的语言不是这 3 种语言,那么应用程序必须切换到默认文化。在上面的代码中,我只是从列表中删除 CookieCultureProvider 并将其添加为列表中的第一个提供者(* 我已经描述了它必须是第一个的原因*)。默认 CookieName 对我有用,您可以根据需要更改它。

不要忘记在 Startup.cs的Configure(IApplicationBuilder app, IHostingEnvironment env)下面添加以下代码

   var options = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
   app.UseRequestLocalization(options.Value);
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都很好。要有资源文件必须指定资源的路径,我在web项目根路径分别指定了Controller、Views、ViewModels和SharedResources的资源,层次如下

|-Resoures
|---Controllers
|---Views
|---Models
|---SharedResource.resx
|---SharedResource.fa-IR.resx
|---SharedResource.de-DE.resx
Run Code Online (Sandbox Code Playgroud)

请记住,对于 SharedResource 在 Web 项目根路径中创建一个同名的空类,我的意思是一个SharedResource.cs 里面有一个同名的类。

在 Startup.cs 中添加以下代码片段以指定资源路径和其余部分。

services.AddLocalization(options => options.ResourcesPath = "Resources");

services.AddMvc()
    AddViewLocalization(
                LanguageViewLocationExpanderFormat.Suffix,
                opts => { opts.ResourcesPath = "Resources/Views"; }) // this line is not required, it is just for more clarification
            .AddDataAnnotationsLocalization();
Run Code Online (Sandbox Code Playgroud)

使用此配置,例如,当您在控制器中注入IStringLocalizer 时,您必须在 Resources > Controllers 文件夹(即 HomeController.resx、HomeController.fa-IR.resx、HomeController.de-DE.resx)中有相应的资源文件,我们也可以用 分隔路径(比如点)在文件名中我的意思是 Resources/Controllers/HomeController.resx 可以是 Resources/Controllers.HomeController.resx 中的一个文件,您必须将 IViewLocalizer 注入视图中才能在视图中进行本地化,因此您必须拥有相应的资源文件资源/视图文件夹中的视图,对于 ViewModels,因为我们将文件夹命名为 Models,请将所有 ViewModels 放在 Web 项目根路径中名为 Models 的预先创建的文件夹中,如果该文件夹有其他名称或者您更喜欢其他名称,请不要不要忘记重命名资源文件夹下的模型文件夹。那么你只需要对模型进行注释,例如,用 [DisplayName("User Emailaddress"“用户电子邮件地址”)。

让我们从我们已经开始的地方结束,我的意思是CookieRequestCultureProvider。正如我之前所说,我更喜欢将它存储在 cookie 中,但这有点棘手,因为cookie 解析与您可能期望的有点不同,只需在要更改文化的地方添加以下代码,只需将 preferredCulture 替换为您的文化偏好

var preferedCulture = "fa-IR"; // get the culture from user, i just mock it here in a variable
if (HttpContext.Response.Cookies.ContainsKey(".AspNetCore.Culture"))
{
    HttpContext.Response.Cookies.Delete(".AspNetCore.Culture");
}

HttpContext.Response.Cookies.Append(".AspNetCore.Culture",
    $"c={preferedCulture}|uic={preferedCulture}", new CookieOptions {Expires = DateTime.UtcNow.AddYears(1)});
Run Code Online (Sandbox Code Playgroud)

好了,我们的 sp.net 核心 Web 应用程序现已本地化 :)


Pie*_*oux 5

基于 rboe 和 Tedd Hansen 的出色回答,我写了一个供我自己使用的IStringLocalizer基础IdentityErrorDescriber,我想我会在这里分享,以防有人需要多语言支持:)

基本思想是以通常的方式为您的默认语言和任何其他语言创建资源文件。( https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization )

位于(如果您保留我的班级名称):

/Resources/ yournamespace .LocalizedIdentityErrorDescriber.resx
/Resources/ yournamespace .LocalizedIdentityErrorDescriber.fr.resx
等...

在这些中,您将使用错误代码(例如:DefaultError、ConcurrencyError)作为键。

然后添加下面的类

LocalizedIdentityErrorDescriber.cs

public class LocalizedIdentityErrorDescriber : IdentityErrorDescriber
{
    /// <summary> 
    /// The <see cref="IStringLocalizer{LocalizedIdentityErrorDescriber}"/>
    /// used to localize the strings
    /// </summary>
    private readonly IStringLocalizer<LocalizedIdentityErrorDescriber> localizer;

    /// <summary>
    /// Initializes a new instance of the <see cref="LocalizedIdentityErrorDescriber"/> class.
    /// </summary>
    /// <param name="localizer">
    /// The <see cref="IStringLocalizer{LocalizedIdentityErrorDescriber}"/>
    /// that we will use to localize the strings
    /// </param>
    public LocalizedIdentityErrorDescriber(IStringLocalizer<LocalizedIdentityErrorDescriber> localizer)
    {
        this.localizer = localizer;
    }

    /// <summary>
    /// Returns the default <see cref="IdentityError" />.
    /// </summary>
    /// <returns>The default <see cref="IdentityError" /></returns>
    public override IdentityError DefaultError()
    {
        return this.GetErrorByCode("DefaultError");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a concurrency failure.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a concurrency failure.</returns>
    public override IdentityError ConcurrencyFailure()
    {
        return this.GetErrorByCode("ConcurrencyFailure");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a password mismatch.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a password mismatch.</returns>
    public override IdentityError PasswordMismatch()
    {
        return this.GetErrorByCode("PasswordMismatch");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating an invalid token.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating an invalid token.</returns>
    public override IdentityError InvalidToken()
    {
        return this.GetErrorByCode("InvalidToken");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating an external login is already associated with an account.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating an external login is already associated with an account.</returns>
    public override IdentityError LoginAlreadyAssociated()
    {
        return this.GetErrorByCode("LoginAlreadyAssociated");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating the specified user <paramref name="userName" /> is invalid.
    /// </summary>
    /// <param name="userName">The user name that is invalid.</param>
    /// <returns>An <see cref="IdentityError" /> indicating the specified user <paramref name="userName" /> is invalid.</returns>
    public override IdentityError InvalidUserName(string userName)
    {
        return this.FormatErrorByCode("InvalidUserName", (object)userName);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating the specified <paramref name="email" /> is invalid.
    /// </summary>
    /// <param name="email">The email that is invalid.</param>
    /// <returns>An <see cref="IdentityError" /> indicating the specified <paramref name="email" /> is invalid.</returns>
    public override IdentityError InvalidEmail(string email)
    {
        return this.FormatErrorByCode("InvalidEmail", (object)email);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating the specified <paramref name="userName" /> already exists.
    /// </summary>
    /// <param name="userName">The user name that already exists.</param>
    /// <returns>An <see cref="IdentityError" /> indicating the specified <paramref name="userName" /> already exists.</returns>
    public override IdentityError DuplicateUserName(string userName)
    {
        return this.FormatErrorByCode("DuplicateUserName", (object)userName);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating the specified <paramref name="email" /> is already associated with an account.
    /// </summary>
    /// <param name="email">The email that is already associated with an account.</param>
    /// <returns>An <see cref="IdentityError" /> indicating the specified <paramref name="email" /> is already associated with an account.</returns>
    public override IdentityError DuplicateEmail(string email)
    {
        return this.FormatErrorByCode("DuplicateEmail", (object)email);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating the specified <paramref name="role" /> name is invalid.
    /// </summary>
    /// <param name="role">The invalid role.</param>
    /// <returns>An <see cref="IdentityError" /> indicating the specific role <paramref name="role" /> name is invalid.</returns>
    public override IdentityError InvalidRoleName(string role)
    {
        return this.FormatErrorByCode("InvalidRoleName", (object)role);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating the specified <paramref name="role" /> name already exists.
    /// </summary>
    /// <param name="role">The duplicate role.</param>
    /// <returns>An <see cref="IdentityError" /> indicating the specific role <paramref name="role" /> name already exists.</returns>
    public override IdentityError DuplicateRoleName(string role)
    {
        return this.FormatErrorByCode("DuplicateRoleName", (object)role);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a user already has a password.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a user already has a password.</returns>
    public override IdentityError UserAlreadyHasPassword()
    {
        return this.GetErrorByCode("UserAlreadyHasPassword");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating user lockout is not enabled.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating user lockout is not enabled..</returns>
    public override IdentityError UserLockoutNotEnabled()
    {
        return this.GetErrorByCode("UserLockoutNotEnabled");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a user is already in the specified <paramref name="role" />.
    /// </summary>
    /// <param name="role">The duplicate role.</param>
    /// <returns>An <see cref="IdentityError" /> indicating a user is already in the specified <paramref name="role" />.</returns>
    public override IdentityError UserAlreadyInRole(string role)
    {
        return this.FormatErrorByCode("UserAlreadyInRole", (object)role);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a user is not in the specified <paramref name="role" />.
    /// </summary>
    /// <param name="role">The duplicate role.</param>
    /// <returns>An <see cref="IdentityError" /> indicating a user is not in the specified <paramref name="role" />.</returns>
    public override IdentityError UserNotInRole(string role)
    {
        return this.FormatErrorByCode("UserNotInRole", (object)role);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a password of the specified <paramref name="length" /> does not meet the minimum length requirements.
    /// </summary>
    /// <param name="length">The length that is not long enough.</param>
    /// <returns>An <see cref="IdentityError" /> indicating a password of the specified <paramref name="length" /> does not meet the minimum length requirements.</returns>
    public override IdentityError PasswordTooShort(int length)
    {
        return this.FormatErrorByCode("PasswordTooShort", (object)length);
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a password entered does not contain a non-alphanumeric character, which is required by the password policy.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a password entered does not contain a non-alphanumeric character.</returns>
    public override IdentityError PasswordRequiresNonAlphanumeric()
    {
        return this.GetErrorByCode("PasswordRequiresNonAlphanumeric");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a password entered does not contain a numeric character, which is required by the password policy.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a password entered does not contain a numeric character.</returns>
    public override IdentityError PasswordRequiresDigit()
    {
        return this.GetErrorByCode("PasswordRequiresDigit");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a password entered does not contain a lower case letter, which is required by the password policy.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a password entered does not contain a lower case letter.</returns>
    public override IdentityError PasswordRequiresLower()
    {
        return this.GetErrorByCode("PasswordRequiresLower");
    }

    /// <summary>
    /// Returns an <see cref="IdentityError" /> indicating a password entered does not contain an upper case letter, which is required by the password policy.
    /// </summary>
    /// <returns>An <see cref="IdentityError" /> indicating a password entered does not contain an upper case letter.</returns>
    public override IdentityError PasswordRequiresUpper()
    {
        return this.GetErrorByCode("PasswordRequiresUpper");
    }

    /// <summary>Returns a localized <see cref="IdentityError"/> for the provided code.</summary>
    /// <param name="code">The error's code.</param>
    /// <returns>A localized <see cref="IdentityError"/>.</returns>
    private IdentityError GetErrorByCode(string code)
    {
        return new IdentityError()
        {
            Code = code,
            Description = this.localizer.GetString(code)
        };
    }

    /// <summary>Formats a localized <see cref="IdentityError"/> for the provided code.</summary>
    /// <param name="code">The error's code.</param>
    /// <param name="parameters">The parameters to format the string with.</param>
    /// <returns>A localized <see cref="IdentityError"/>.</returns>
    private IdentityError FormatErrorByCode(string code, params object[] parameters)
    {
        return new IdentityError
        {
            Code = code,
            Description = string.Format(this.localizer.GetString(code, parameters))
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

并初始化一切:

启动文件

    [...]

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddErrorDescriber<LocalizedIdentityErrorDescriber>();

        services.AddLocalization(options => options.ResourcesPath = "Resources");

        // Your service configuration code

        services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
            .AddDataAnnotationsLocalization();

        // Your service configuration code cont.
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        // your configuration code

        app.UseRequestLocalization(
            new RequestLocalizationOptions()
                {
                    DefaultRequestCulture = new RequestCulture("fr"),
                    SupportedCultures = SupportedCultures,
                    SupportedUICultures = SupportedCultures
                });

        app.UseStaticFiles();

        app.UseIdentity();

        // your configuration code
    }

    [...]
Run Code Online (Sandbox Code Playgroud)