如何翻译身份密码验证消息

How to translate Identity Password validation messages

到目前为止,我已经能够翻译 ASP.Net Core 2.1 Web 应用程序中的所有内容。

事实证明这是一个小挑战,因为脚手架帐户页面需要一些设置。

但我找不到翻译密码验证消息的方法。此外,翻译模型绑定消息是一个小挑战(感谢 Whosebug)。

有什么想法吗?

我包含了我的 Startup.cs 文件的相关部分:

public void ConfigureServices(IServiceCollection services)
{
     ...

     services.AddMvc(options =>
     {
         var type = typeof(SharedResources);
         var assemblyName = new AssemblyName(type.GetTypeInfo().Assembly.FullName);
         var factory = services.BuildServiceProvider().GetService<IStringLocalizerFactory>();
         var L = factory.Create("SharedResources", assemblyName.Name);

         options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(x => L["The value '{0}' is invalid.", x]);
         options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(x => L["The value '{0}' is invalid.", x]);
         options.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(x => L["The field {0} must be a number.", x]);
         options.ModelBindingMessageProvider.SetMissingBindRequiredValueAccessor(x => L["A value for the '{0}' property was not provided.", x]);
         options.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor((x, y) => L["The value '{0}' is not valid for {1}.", x, y]);
         options.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor(() => L["A value is required."]);
         options.ModelBindingMessageProvider.SetUnknownValueIsInvalidAccessor(x => L["The supplied value is invalid for {0}.", x]);
         options.ModelBindingMessageProvider.SetMissingRequestBodyRequiredValueAccessor(() => L["A non-empty request body is required."]);
         options.ModelBindingMessageProvider.SetNonPropertyAttemptedValueIsInvalidAccessor(x => L["The value '{0}' is not valid.", x]);
         options.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(() => L["The supplied value is invalid."]);
         options.ModelBindingMessageProvider.SetNonPropertyValueMustBeANumberAccessor(() => L["NonPropertyValueMustBeNumber"]);
     })
     .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
     .AddDataAnnotationsLocalization(options =>
        {
            options.DataAnnotationLocalizerProvider = (type, factory) =>
            {
                // This is for Account scaffolded pages data annotations
                return factory.Create(typeof(SharedResources));
            };
        });

     ...

}

我不能把这样的东西放在 Register.cshtml.csInputModel 中,因为 ErrorMessage 会被忽略(而且我不会这样做,因为我不想硬编码密码策略描述):

        [Required(ErrorMessage = "The {0} field is required.")]
        [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
        [DataType(DataType.Password, ErrorMessage = "Password must be nice")]
        [Display(Name = "Password")]
        public string Password { get; set; }

这些错误消息是使用 IdentityErrorDescriber 生成的。这是 class 本身的示例:

public class IdentityErrorDescriber
{
    ...

    public virtual IdentityError PasswordTooShort(int length)
    {
        return new IdentityError
        {
            Code = nameof(PasswordTooShort),
            Description = Resources.FormatPasswordTooShort(length)
        };
    }


    ...
}

要自定义特定消息,请创建您自己的 IdentityErrorDescriber 实现。这是一个例子:

public class MyIdentityErrorDescriber : IdentityErrorDescriber
{    
    public override IdentityError PasswordTooShort(int length)
    {
        return new IdentityError
        {
            Code = nameof(PasswordTooShort),
            Description = "Your description goes here."
        };
    }
}

要使用这个新实现,请将其添加到 Startup.ConfigureServices 中的 DI 容器中:

services.AddScoped<IdentityErrorDescriber, MyIdentityErrorDescriber>();

这可以通过本地化标识错误消息来完成,有 22 条消息必须本地化。

首先,创建一个共享资源文件“它的键用 public 访问修饰符定义”并键入所有带有本地化版本的错误消息,如下图所示:

然后创建一个新的 class 实现 IdentityErrorDescriber 并参考共享资源文件覆盖所有默认消息;在此示例中,共享资源文件名为 LocalizedIdentityErrorMessages:

public class LocalizedIdentityErrorDescriber : IdentityErrorDescriber
    {
        public override IdentityError DuplicateEmail(string email)
        {
            return new IdentityError
            {
                Code = nameof(DuplicateEmail),
                Description = string.Format(LocalizedIdentityErrorMessages.DuplicateEmail, email)
            };
        }

        public override IdentityError DuplicateUserName(string userName)
        {
            return new IdentityError
            {
                Code = nameof(DuplicateUserName),
                Description = string.Format(LocalizedIdentityErrorMessages.DuplicateUserName, userName)
            };
        }

        public override IdentityError InvalidEmail(string email)
        {
            return new IdentityError
            {
                Code = nameof(InvalidEmail),
                Description = string.Format(LocalizedIdentityErrorMessages.InvalidEmail, email)
            };
        }

        public override IdentityError DuplicateRoleName(string role)
        {
            return new IdentityError
            {
                Code = nameof(DuplicateRoleName),
                Description = string.Format(LocalizedIdentityErrorMessages.DuplicateRoleName, role)
            };
        }

        public override IdentityError InvalidRoleName(string role)
        {
            return new IdentityError
            {
                Code = nameof(InvalidRoleName),
                Description = string.Format(LocalizedIdentityErrorMessages.InvalidRoleName, role)
            };
        }

        public override IdentityError InvalidToken()
        {
            return new IdentityError
            {
                Code = nameof(InvalidToken),
                Description = LocalizedIdentityErrorMessages.InvalidToken
            };
        }

        public override IdentityError InvalidUserName(string userName)
        {
            return new IdentityError
            {
                Code = nameof(InvalidUserName),
                Description = string.Format(LocalizedIdentityErrorMessages.InvalidUserName, userName)
            };
        }

        public override IdentityError LoginAlreadyAssociated()
        {
            return new IdentityError
            {
                Code = nameof(LoginAlreadyAssociated),
                Description = LocalizedIdentityErrorMessages.LoginAlreadyAssociated
            };
        }

        public override IdentityError PasswordMismatch()
        {
            return new IdentityError
            {
                Code = nameof(PasswordMismatch),
                Description = LocalizedIdentityErrorMessages.PasswordMismatch
            };
        }

        public override IdentityError PasswordRequiresDigit()
        {
            return new IdentityError
            {
                Code = nameof(PasswordRequiresDigit),
                Description = LocalizedIdentityErrorMessages.PasswordRequiresDigit
            };
        }

        public override IdentityError PasswordRequiresLower()
        {
            return new IdentityError
            {
                Code = nameof(PasswordRequiresLower),
                Description = LocalizedIdentityErrorMessages.PasswordRequiresLower
            };
        }

        public override IdentityError PasswordRequiresNonAlphanumeric()
        {
            return new IdentityError
            {
                Code = nameof(PasswordRequiresNonAlphanumeric),
                Description = LocalizedIdentityErrorMessages.PasswordRequiresNonAlphanumeric
            };
        }

        public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)
        {
            return new IdentityError
            {
                Code = nameof(PasswordRequiresUniqueChars),
                Description = string.Format(LocalizedIdentityErrorMessages.PasswordRequiresUniqueChars, uniqueChars)
            };
        }

        public override IdentityError PasswordRequiresUpper()
        {
            return new IdentityError
            {
                Code = nameof(PasswordRequiresUpper),
                Description = LocalizedIdentityErrorMessages.PasswordRequiresUpper
            };
        }

        public override IdentityError PasswordTooShort(int length)
        {
            return new IdentityError
            {
                Code = nameof(PasswordTooShort),
                Description = string.Format(LocalizedIdentityErrorMessages.PasswordTooShort, length)
            };
        }

        public override IdentityError UserAlreadyHasPassword()
        {
            return new IdentityError
            {
                Code = nameof(UserAlreadyHasPassword),
                Description = LocalizedIdentityErrorMessages.UserAlreadyHasPassword
            };
        }

        public override IdentityError UserAlreadyInRole(string role)
        {
            return new IdentityError
            {
                Code = nameof(UserAlreadyInRole),
                Description = string.Format(LocalizedIdentityErrorMessages.UserAlreadyInRole, role)
            };
        }

        public override IdentityError UserNotInRole(string role)
        {
            return new IdentityError
            {
                Code = nameof(UserNotInRole),
                Description = string.Format(LocalizedIdentityErrorMessages.UserNotInRole, role)
            };
        }

        public override IdentityError UserLockoutNotEnabled()
        {
            return new IdentityError
            {
                Code = nameof(UserLockoutNotEnabled),
                Description = LocalizedIdentityErrorMessages.UserLockoutNotEnabled
            };
        }

        public override IdentityError RecoveryCodeRedemptionFailed()
        {
            return new IdentityError
            {
                Code = nameof(RecoveryCodeRedemptionFailed),
                Description = LocalizedIdentityErrorMessages.RecoveryCodeRedemptionFailed
            };
        }

        public override IdentityError ConcurrencyFailure()
        {
            return new IdentityError
            {
                Code = nameof(ConcurrencyFailure),
                Description = LocalizedIdentityErrorMessages.ConcurrencyFailure
            };
        }

        public override IdentityError DefaultError()
        {
            return new IdentityError
            {
                Code = nameof(DefaultError),
                Description = LocalizedIdentityErrorMessages.DefaultIdentityError
            };
        }
    }

最后,将本地化错误描述符添加到启动时 ConfigureServices 方法下的标识设置中 class:

services.AddIdentity<AppUser, AppRole>()
        // localize identity error messages
        .AddErrorDescriber<LocalizedIdentityErrorDescriber>()
        .AddEntityFrameworkStores()
        .AddDefaultTokenProviders();

资源:http://www.ziyad.info/en/articles/20-Localizing_Identity_Error_Messages

此外,您可能需要阅读分步本地化文章: http://www.ziyad.info/en/articles/10-Developing_Multicultural_Web_Application

更新 - 2020 年 12 月

最近我开发了一个新的nuget包(XLocalizer),它简化了Asp.Net核心网络应用程序的本地化设置,它支持自动在线翻译和自动资源创建。此外,所有身份错误、模型绑定错误和验证错误都可以在 json 文件中轻松自定义。

参考文献:

您可以使用 ViewModel 而不是只使用 DataAnnotations 这是 ControllerViewModelView 在这种情况下的样子(翻译是克罗地亚语)。

控制器

    [HttpGet]
    public IActionResult Login()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        if (ModelState.IsValid)
        {
            var result = await signInManager.PasswordSignInAsync(
                model.Email, model.Password, model.RememberMe, false);

            if (result.Succeeded)
            {
                return RedirectToAction("index", "home");
            }

            ModelState.AddModelError(string.Empty, "Neuspješni pokušaj");
        }

        return View(model);
    }

视图模型

ErrorMessage下放置你的错误信息

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace MyApplication.ViewModels
{
    public class LoginViewModel
    {
        [Required(ErrorMessage = "Email je obavezan")]
        [EmailAddress]
        public string Email { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "Lozinka")]
        [Required(ErrorMessage = "Lozinka je obavezna")]
        public string Password { get; set; }

        [Display(Name = "Zapamti me")]
        public bool RememberMe { get; set; }
    }
}

登录查看

@model LoginViewModel

@{
    ViewBag.Title = "Prijava";
}

<h2>Prijava</h2>

<div class="row">
    <div class="col-md-12">
        <form method="post">
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Email"></label>
                <input asp-for="Email" class="form-control" />
                <span asp-validation-for="Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Password"></label>
                <input asp-for="Password" class="form-control" />
                <span asp-validation-for="Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <div class="checkbox">
                    <label asp-for="RememberMe">
                        <input asp-for="RememberMe" />
                        @Html.DisplayNameFor(m => m.RememberMe)
                    </label>
                </div>
            </div>
            <button type="submit" class="btn btn-primary">Prijava</button>
        </form>
    </div>
</div>