Локализация Identity Asp.Net PublicKeyToken
Я пытаюсь получить локализованные сообщения об ошибках для шведского языка для Asp.Net Identity, используя советы из этого сообщения: Как локализовать сообщения об ошибках ASP.NET Identity UserName и Password?
Используя NuGet, я загрузил пакет немецкого языка, а затем открыл пакет \packages\Microsoft.AspNet.Identity.Core.2.0.0\lib\net45\de\Microsoft.AspNet.Identity.Core.resources.dll в dotPeek, а затем экспортировал это в новый проект VS:
https://github.com/nielsbosma/AspNet.Identity.Resources.Swedish/
Я скопировал сгенерированный файл \Microsoft.AspNet.Identity.Core.resources.dll в новую папку в папке \packages\Microsoft.AspNet.Identity.Core.2.0.0\lib\net45\se.
Когда я запускаю свой сайт локально, я вижу, что файл Microsoft.AspNet.Identity.Core.resources.dll был скопирован на MySite\bin\sv\
Но я не могу заставить его работать: (
Если я установил в свой Web.config:
<system.web>
...
<globalization culture="sv-SE" uiCulture="sv" />
</system.web>
Я по-прежнему получаю английские сообщения об ошибках по умолчанию. Но если я перейду на немецкий язык, который я включил в NuGet, я получаю сообщения об ошибках в Германии.
Используя dotPeek, я сравнил свою dll с немецким, и они одинаковы, за исключением того, что у меня есть PublicKeyToken = null, а для немецкого - 31bf3856ad364e35. Может быть, поэтому я не могу загрузить свою DLL? Есть ли способ установить PublicKeyToken для dll? Любое обходное решение?
Спасибо за любые указатели.
Ответы
Ответ 1
Нет, если у вас есть секретный ключ, который Microsoft использует для подписи dll.
Обновлено: как обходной путь до тех пор, пока мы не добавим поддержку для подключения ваших собственных ресурсов, вы, вероятно, можете просто обернуть все сообщения об ошибках по умолчанию с явным переключением на данный момент, должно быть только около 10-20 ошибок, связанных с пользователем.
Что-то вроде:
public static string Localize(string error) {
switch (error) {
case "<english error>": return "<localized version";
}
}
Ответ 2
Вдохновленный Питером Ответом Я придумал дерьмовое, но быстрое решение.
Мне нужно было локализовать ошибки в шаблоне AccountController по умолчанию, поэтому я написал свой собственный метод AddLocalizedErrors
. Я использую ресурсы для локализации ошибок.
//Original method
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
//My method
private void AddLocalizedErrors(IdentityResult result, ApplicationUser user)
{
foreach (var error in result.Errors)
{
var localizedError = error;
string userName = "";
string email = "";
if (user != null)
{
userName = user.UserName;
email = user.Email;
}
//password errors
localizedError = localizedError.Replace("Passwords must have at least one uppercase ('A'-'Z').", AspNetValidationMessages.password_uppercase);
localizedError = localizedError.Replace("Passwords must have at least one digit ('0'-'9').", AspNetValidationMessages.password_digit);
localizedError = localizedError.Replace("Passwords must have at least one lowercase ('a'-'z').", AspNetValidationMessages.password_lowercase);
localizedError = localizedError.Replace("Passwords must have at least one non letter or digit character.", AspNetValidationMessages.password_nonletter_nondigit);
localizedError = localizedError.Replace("Passwords must have at least one non letter or digit character.", AspNetValidationMessages.password_nonletter_nondigit);
localizedError = localizedError.Replace("Passwords must have at least one non letter or digit character.", AspNetValidationMessages.password_nonletter_nondigit);
//register errors
localizedError = localizedError.Replace("Name "+userName+" is already taken.", AspNetValidationMessages.name_taken.Replace("{0}", userName));
localizedError = localizedError.Replace("Email '" + email + "' is already taken.", AspNetValidationMessages.email_taken.Replace("{0}", email));
ModelState.AddModelError("", localizedError);
}
}
Я использую string.Replace()
, потому что, например, ошибки пароля только что соединены строками одного требования к паролю.
Когда дело доходит до сбора ролей, я должен быть более креативным. Возможно использование string.Contains()
.
Ответ 3
На данный момент это действительно дерьмовое решение, но решение обходное решение тем не менее. Чтобы сэкономить время для тех из нас, кто нуждается в локализации и не работает в Microsoft... вот что я сделал в качестве обходного пути для голландского языка.
public class Demo {
private string LocalizeIdentityError(string error, IdentityUser user)
{
if (error == "User already in role.") return "De gebruiker zit reeds in deze rol.";
else if (error == "User is not in role.") return "De gebruiker zit niet in deze rol.";
//else if (error == "Role {0} does not exist.") return "De rol bestaat nog niet";
//else if (error == "Store does not implement IUserClaimStore<TUser>.") return "";
//else if (error == "No IUserTwoFactorProvider for '{0}' is registered.") return "";
//else if (error == "Store does not implement IUserEmailStore<TUser>.") return "";
else if (error == "Incorrect password.") return "Ongeldig wachtwoord";
//else if (error == "Store does not implement IUserLockoutStore<TUser>.") return "";
//else if (error == "No IUserTokenProvider is registered.") return "";
//else if (error == "Store does not implement IUserRoleStore<TUser>.") return "";
//else if (error == "Store does not implement IUserLoginStore<TUser>.") return "";
else if (error == "User name {0} is invalid, can only contain letters or digits.") return "De gebruikersnaam '"+user.UserName+"' kan alleen letters of cijfers bevatten.";
//else if (error == "Store does not implement IUserPhoneNumberStore<TUser>.") return "";
//else if (error == "Store does not implement IUserConfirmationStore<TUser>.") return "";
else if (error.StartsWith("Passwords must be at least ")) return "Een wachtwoord moet minstens {0} karakters bevatten.";
//else if (error == "{0} cannot be null or empty.") return "";
else if (user != null && error == "Name "+user.UserName+" is already taken.") return "De gebruikersnaam '" + user.UserName + "' is reeds in gebruik.";
else if (error == "User already has a password set.") return "Deze gebruiker heeft reeds een wachtwoord ingesteld.";
//else if (error == "Store does not implement IUserPasswordStore<TUser>.") return "";
else if (error == "Passwords must have at least one non letter or digit character.") return "Wachtwoorden moeten minstens een ander karakter dan een letter of cijfer bevatten.";
else if (error == "UserId not found.") return "De gebruiker kon niet gevonden worden.";
else if (error == "Invalid token.") return "Ongeldig token.";
else if (user != null && error == "Email '" + user.Email + "' is invalid.") return "Het emailadres '" + user.Email + "' is ongeldig.";
else if (user != null && error == "User " + user.UserName + " does not exist.") return "De gebruiker '" + user.UserName + "' bestaat niet.";
else if (error == "Store does not implement IQueryableRoleStore<TRole>.") return "";
else if (error == "Lockout is not enabled for this user.") return "Lockout is niet geactiveerd voor deze gebruiker.";
//else if (error == "Store does not implement IUserTwoFactorStore<TUser>.") return "";
else if (error == "Passwords must have at least one uppercase ('A'-'Z').") return "Wachtwoorden moeten minstens één hoofdletter bevatten. (A-Z)";
else if (error == "Passwords must have at least one digit ('0'-'9').") return "Wachtwoorden moeten minstens één getal bevatten. (0-9)";
else if (error == "Passwords must have at least one lowercase ('a'-'z').") return "Wachtwoorden moeten minstens één kleine letter bevatten. (a-z)";
//else if (error == "Store does not implement IQueryableUserStore<TUser>.") return "";
else if (user != null && error == "Email '" + user.Email + "' is already taken.") return "Het emailadres '" + user.Email + "' is reeds in gebruik. Probeer aan te melden.";
//else if (error == "Store does not implement IUserSecurityStampStore<TUser>.") return "";
else if (error == "A user with that external login already exists.") return "Een gebruiker met deze externe login bestaat reeds.";
else if (error == "An unknown failure has occured.") return "Een onbekende fout is opgetreden. Probeer het later opnieuw.";
return error;
}
}
Ответ 4
Другой вариант - наследовать от Microsoft.AspNet.Identity.PasswordValidator, затем переопределить ValidateAsync.
Затем вы можете использовать свои собственные файлы ресурсов для локализации. Исходный файл ресурсов для английского языка можно найти с помощью DotPeek или аналогичного, тогда вы можете использовать его для английского в качестве шаблона для собственного перевода на другие языки.
Мои файлы resx:
MyLocalization/IdentityResource.resx(то же, что и в Microsoft.AspNet.Identity.Core.Resources)
MyLocalization/IdentityResource.nb-no.resx(мои норвежские переводы)
var manager = new ApplicationUserManager(new UserStore<ApplicationUser> (context.Get<ApplicationDbContext>()));
// Configure validation logic for passwords
manager.PasswordValidator = new MyCustomPasswordValidator(System.Threading.Thread.CurrentThread.CurrentUICulture)
В MyCustomPasswordValidator.cs: (также обратите внимание на исправление, которое мне также нужно было сделать в ValidateAsync)
using Resources = MyLocalization.IdentityResource;
public class MyCustomPasswordValidator : Microsoft.AspNet.Identity.PasswordValidator
{
private readonly CultureInfo _currentUIculture;
public MyCustomPasswordValidator(CultureInfo currentUIculture)
{
_currentUIculture = currentUIculture;
}
/// <summary>
/// Ensures that the string is of the required length and meets the configured requirements
///
/// </summary>
/// <param name="item"/>
/// <returns/>
public override Task<IdentityResult> ValidateAsync(string item)
{
//BUG: CurrentUICulture is not set correctly https://aspnetidentity.codeplex.com/workitem/2060
System.Threading.Thread.CurrentThread.CurrentUICulture = _currentUIculture;
if (item == null)
throw new ArgumentNullException("item");
List<string> list = new List<string>();
if (string.IsNullOrWhiteSpace(item) || item.Length < this.RequiredLength)
list.Add(string.Format((IFormatProvider)CultureInfo.CurrentCulture, Resources.PasswordTooShort, new object[1]
{
(object) this.RequiredLength
}));
if (this.RequireNonLetterOrDigit && Enumerable.All<char>((IEnumerable<char>)item, new Func<char, bool>(this.IsLetterOrDigit)))
list.Add(Resources.PasswordRequireNonLetterOrDigit);
if (this.RequireDigit && Enumerable.All<char>((IEnumerable<char>)item, (Func<char, bool>)(c => !this.IsDigit(c))))
list.Add(Resources.PasswordRequireDigit);
if (this.RequireLowercase && Enumerable.All<char>((IEnumerable<char>)item, (Func<char, bool>)(c => !this.IsLower(c))))
list.Add(Resources.PasswordRequireLower);
if (this.RequireUppercase && Enumerable.All<char>((IEnumerable<char>)item, (Func<char, bool>)(c => !this.IsUpper(c))))
list.Add(Resources.PasswordRequireUpper);
if (list.Count == 0)
return Task.FromResult<IdentityResult>(IdentityResult.Success);
return Task.FromResult<IdentityResult>(IdentityResult.Failed(new string[1]
{
string.Join(" ", (IEnumerable<string>) list)
}));
}
}
`
Ответ 5
Еще одним обходным путем для поля Password
может быть реализация RegisterViewModel.Password
с CustomValidationAttribute
:
public class RegisterViewModel
{
[CustomValidation(typeof(CustomValidations), "ValidatePassword")]
public string Password { get; set; }
}
И иметь метод CustomValidations.ValidatePassword
, который бы имитировал правила проверки пароля, которые вы настроили для PasswordValidator
. То есть:.
public static class CustomValidations
{
public static ValidationResult ValidatePassword(string password)
{
// Implement validation logic here, e.g. require numbers,
// uppercase etc. and create localized ValidationResult.
return new ValidationResult(Resources.PasswordValidation.NoNumbers);
}
}
Здесь вы можете явно локализовать свои сообщения об ошибках в соответствии с вашими личными предпочтениями, используя стандартные файлы ресурсов resx. Итак, чтобы подвести итог, вы просто запретите любые недействительные пароли от достижения PasswordValidator
.
В поле Email
это станет немного уродливым, так как для проверки уникальности и т.д. потребуется округлять до DB, но это должно быть выполнимо.
Дополнительный бонус за использование этого решения заключается в том, что полученные ошибки будут "за поле", т.е. вам не нужно будет отображать все ошибки проверки с помощью @Html.ValidationSummary
, но может сделать:
@Html.ValidationMessageFor(m => m.Password, null, new { @class = "text-danger" })
Ответ 6
Ниже приведен список файлов и значений файла en.-US Resources.resx. Это значения, требующие локализации. Исходный код на http://aspnetidentity.codeplex.com
Идентификатор Asp.Net {name} Validator.cs ErrorMessage Resources.resx Локализация
См.: http://aspnetidentity.codeplex.com/discussions/638351
*DefaultError= An unknown failure has occured.
DuplicateEmail= Email '{0}' is already taken.
DuplicateName= Name {0} is already taken.
ExternalLoginExists= A user with that external login already exists.
InvalidEmail= Email '{0}' is invalid.
InvalidToken= Invalid token.
InvalidUserName= User name {0} is invalid, can only contain letters or digits.
LockoutNotEnabled= Lockout is not enabled for this user.
NoTokenProvider= No IUserTokenProvider is registered.
NoTwoFactorProvider= No IUserTwoFactorProvider for '{0}' is registered.
PasswordMismatch= Incorrect password.
PasswordRequireDigit= Passwords must have at least one digit ('0'-'9').
PasswordRequireLower= Passwords must have at least one lowercase ('a'-'z').
PasswordRequireNonLetterOrDigit= Passwords must have at least one non letter or digit character.
PasswordRequireUpper= Passwords must have at least one uppercase ('A'-'Z').
PasswordTooShort= Passwords must be at least {0} characters.
PropertyTooShort= {0} cannot be null or empty.
RoleNotFound= Role {0} does not exist.
StoreNotIQueryableRoleStore= Store does not implement IQueryableRoleStore<TRole>.
StoreNotIQueryableUserStore= Store does not implement IQueryableUserStore<TUser>.
StoreNotIUserClaimStore= Store does not implement IUserClaimStore<TUser>.
StoreNotIUserConfirmationStore= Store does not implement IUserConfirmationStore<TUser>.
StoreNotIUserEmailStore= Store does not implement IUserEmailStore<TUser>.
StoreNotIUserLockoutStore= Store does not implement IUserLockoutStore<TUser>.
StoreNotIUserLoginStore= Store does not implement IUserLoginStore<TUser>.
StoreNotIUserPasswordStore= Store does not implement IUserPasswordStore<TUser>.
StoreNotIUserPhoneNumberStore= Store does not implement IUserPhoneNumberStore<TUser>.
StoreNotIUserRoleStore= Store does not implement IUserRoleStore<TUser>.
StoreNotIUserSecurityStampStore= Store does not implement IUserSecurityStampStore<TUser>.
StoreNotIUserTwoFactorStore= Store does not implement IUserTwoFactorStore<TUser>.
UserAlreadyHasPassword= User already has a password set.
UserAlreadyInRole= User already in role.
UserIdNotFound= UserId not found.
UserNameNotFound= User {0} does not exist.
UserNotInRole= User is not in role.*