From b58e3dcf95e14709fe1dfe6ae6050224378c76da Mon Sep 17 00:00:00 2001 From: LazZiya Date: Fri, 21 Aug 2020 18:25:46 +0300 Subject: [PATCH 1/3] overridable default identity and model binding errors --- .../Identity/DefaultIdentityErrorsProvider.cs | 52 ++++++++ XLocalizer/Identity/DependencyInjection.cs | 3 + .../IIdentityErrorMessagesProvider.cs | 120 ++++++++++++++++++ .../Identity/IdentityErrorsLocalizer.cs | 55 ++++---- ...efaultModelBindingErrorMessagesProvider.cs | 32 +++++ .../ModelBinding/DependencyInjection.cs | 16 ++- .../IModelBindingErrorMessagesProvider.cs | 65 ++++++++++ .../ModelBindingErrorsExtensions.cs | 25 ++-- XLocalizer/XLocalizer.xml | 72 ++++++++++- 9 files changed, 397 insertions(+), 43 deletions(-) create mode 100644 XLocalizer/Identity/DefaultIdentityErrorsProvider.cs create mode 100644 XLocalizer/Identity/IIdentityErrorMessagesProvider.cs create mode 100644 XLocalizer/ModelBinding/DefaultModelBindingErrorMessagesProvider.cs create mode 100644 XLocalizer/ModelBinding/IModelBindingErrorMessagesProvider.cs diff --git a/XLocalizer/Identity/DefaultIdentityErrorsProvider.cs b/XLocalizer/Identity/DefaultIdentityErrorsProvider.cs new file mode 100644 index 0000000..591d12c --- /dev/null +++ b/XLocalizer/Identity/DefaultIdentityErrorsProvider.cs @@ -0,0 +1,52 @@ +namespace XLocalizer.Identity +{ + /// + /// Default identity errors provider + /// + public class DefaultIdentityErrorsProvider : IIdentityErrorMessagesProvider + { + string IIdentityErrorMessagesProvider.DuplicateEmail => "Email '{0}' is already taken."; + + string IIdentityErrorMessagesProvider.DuplicateUserName => "User name '{0}' is already taken."; + + string IIdentityErrorMessagesProvider.InvalidEmail => "Email '{0}' is invalid."; + + string IIdentityErrorMessagesProvider.DuplicateRoleName => "Role name '{0}' is already taken."; + + string IIdentityErrorMessagesProvider.InvalidRoleName => "Role name '{0}' is invalid."; + + string IIdentityErrorMessagesProvider.InvalidToken => "Invalid token."; + + string IIdentityErrorMessagesProvider.InvalidUserName => "User name '{0}' is invalid, can only contain letters or digits."; + + string IIdentityErrorMessagesProvider.LoginAlreadyAssociated => "A user with this login already exists."; + + string IIdentityErrorMessagesProvider.PasswordMismatch => "Incorrect password."; + + string IIdentityErrorMessagesProvider.PasswordRequiresDigit => "Passwords must have at least one digit ('0'-'9')."; + + string IIdentityErrorMessagesProvider.PasswordRequiresLower => "Passwords must have at least one lowercase ('a'-'z')."; + + string IIdentityErrorMessagesProvider.PasswordRequiresNonAlphanumeric => "Passwords must have at least one non alphanumeric character."; + + string IIdentityErrorMessagesProvider.PasswordRequiresUniqueChars => "Passwords must use at least {0} different characters."; + + string IIdentityErrorMessagesProvider.PasswordRequiresUpper => "Passwords must have at least one uppercase ('A'-'Z')."; + + string IIdentityErrorMessagesProvider.PasswordTooShort => "Passwords must be at least {0} characters."; + + string IIdentityErrorMessagesProvider.UserAlreadyHasPassword => "User already has a password set."; + + string IIdentityErrorMessagesProvider.UserAlreadyInRole => "User already in role '{0}'."; + + string IIdentityErrorMessagesProvider.UserNotInRole => "User is not in role '{0}'."; + + string IIdentityErrorMessagesProvider.UserLockoutNotEnabled => "Lockout is not enabled for this user."; + + string IIdentityErrorMessagesProvider.RecoveryCodeRedemptionFailed => "Recovery code redemption failed."; + + string IIdentityErrorMessagesProvider.ConcurrencyFailure => "Optimistic concurrency failure, object has been modified."; + + string IIdentityErrorMessagesProvider.DefaultError => "An unknown failure has occurred."; + } +} diff --git a/XLocalizer/Identity/DependencyInjection.cs b/XLocalizer/Identity/DependencyInjection.cs index 2689b11..a50c84f 100644 --- a/XLocalizer/Identity/DependencyInjection.cs +++ b/XLocalizer/Identity/DependencyInjection.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace XLocalizer.Identity { @@ -15,6 +16,8 @@ public static class DependencyInjection /// public static IMvcBuilder AddIdentityErrorsLocalization(this IMvcBuilder builder) { + builder.Services.TryAddSingleton(); + // Add Identity Erros localization builder.Services.AddScoped(); diff --git a/XLocalizer/Identity/IIdentityErrorMessagesProvider.cs b/XLocalizer/Identity/IIdentityErrorMessagesProvider.cs new file mode 100644 index 0000000..bd1870c --- /dev/null +++ b/XLocalizer/Identity/IIdentityErrorMessagesProvider.cs @@ -0,0 +1,120 @@ +namespace XLocalizer.Identity +{ + /// + /// Interface to provide custom default identity error messages. + /// Messages can be provided in any culture, so user can provide localized error messages here, + /// but the default request culture in startup must be configured same as messages culture. + /// + public interface IIdentityErrorMessagesProvider + { + /// + /// "Email '{0}' is already taken." + /// + string DuplicateEmail { get; } + + /// + /// "User name '{0}' is already taken." + /// + string DuplicateUserName { get; } + + /// + /// "Email '{0}' is invalid." + /// + string InvalidEmail { get; } + + /// + /// "Role name '{0}' is already taken." + /// + string DuplicateRoleName { get; } + + /// + /// "Role name '{0}' is invalid." + /// + string InvalidRoleName { get; } + + /// + /// "Invalid token." + /// + string InvalidToken { get; } + + /// + /// "User name '{0}' is invalid, can only contain letters or digits." + /// + string InvalidUserName { get; } + + /// + /// "A user with this login already exists." + /// + string LoginAlreadyAssociated { get; } + + /// + /// "Incorrect password." + /// + string PasswordMismatch { get; } + + /// + /// "Passwords must have at least one digit ('0'-'9')." + /// + string PasswordRequiresDigit { get; } + + /// + /// "Passwords must have at least one lowercase ('a'-'z')." + /// + string PasswordRequiresLower { get; } + + /// + /// "Passwords must have at least one non alphanumeric character." + /// + string PasswordRequiresNonAlphanumeric { get; } + + /// + /// "Passwords must use at least {0} different characters." + /// + string PasswordRequiresUniqueChars { get; } + + /// + /// "Passwords must have at least one uppercase ('A'-'Z')." + /// + string PasswordRequiresUpper { get; } + + /// + /// "Passwords must be at least {0} characters." + /// + string PasswordTooShort { get; } + + /// + /// "User already has a password set." + /// + string UserAlreadyHasPassword { get; } + + /// + /// "User already in role '{0}'." + /// + string UserAlreadyInRole { get; } + + /// + /// "User is not in role '{0}'." + /// + string UserNotInRole { get; } + + /// + /// "Lockout is not enabled for this user." + /// + string UserLockoutNotEnabled { get; } + + /// + /// "Recovery code redemption failed." + /// + string RecoveryCodeRedemptionFailed { get; } + + /// + /// "Optimistic concurrency failure, object has been modified." + /// + string ConcurrencyFailure { get; } + + /// + /// "An unknown failure has occurred." + /// + string DefaultError { get; } + } +} diff --git a/XLocalizer/Identity/IdentityErrorsLocalizer.cs b/XLocalizer/Identity/IdentityErrorsLocalizer.cs index 29fcb5f..7c6bad3 100644 --- a/XLocalizer/Identity/IdentityErrorsLocalizer.cs +++ b/XLocalizer/Identity/IdentityErrorsLocalizer.cs @@ -9,20 +9,23 @@ namespace XLocalizer.Identity /// public class IdentityErrorsLocalizer : IdentityErrorDescriber { - private readonly IStringLocalizer Localizer; + private readonly IStringLocalizer _localizer; + private readonly IIdentityErrorMessagesProvider _errProvider; /// /// Initialize identity erroors localization based on DB locailzer /// /// - public IdentityErrorsLocalizer(IStringLocalizer localizer) + /// + public IdentityErrorsLocalizer(IStringLocalizer localizer, IIdentityErrorMessagesProvider errProvider) { - Localizer = localizer; + _localizer = localizer; + _errProvider = errProvider; } private IdentityError LocalizedIdentityError(string code, params object[] args) { - var msg = Localizer[code, args]; + var msg = _localizer[code, args]; return new IdentityError { Code = code, Description = msg }; } @@ -33,7 +36,7 @@ private IdentityError LocalizedIdentityError(string code, params object[] args) /// /// public override IdentityError DuplicateEmail(string email) - => LocalizedIdentityError("Email '{0}' is already taken.", email); + => LocalizedIdentityError(_errProvider.DuplicateEmail, email); /// /// "User name '{0}' is already taken." @@ -41,7 +44,7 @@ public override IdentityError DuplicateEmail(string email) /// /// public override IdentityError DuplicateUserName(string userName) - => LocalizedIdentityError("User name '{0}' is already taken.", userName); + => LocalizedIdentityError(_errProvider.DuplicateUserName, userName); /// /// "Email '{0}' is invalid." @@ -49,7 +52,7 @@ public override IdentityError DuplicateUserName(string userName) /// /// public override IdentityError InvalidEmail(string email) - => LocalizedIdentityError("Email '{0}' is invalid.", email); + => LocalizedIdentityError(_errProvider.InvalidEmail, email); /// /// "Role name '{0}' is already taken." @@ -57,7 +60,7 @@ public override IdentityError InvalidEmail(string email) /// /// public override IdentityError DuplicateRoleName(string role) - => LocalizedIdentityError("Role name '{0}' is already taken.", role); + => LocalizedIdentityError(_errProvider.DuplicateRoleName, role); /// /// "Role name '{0}' is invalid." @@ -65,14 +68,14 @@ public override IdentityError DuplicateRoleName(string role) /// /// public override IdentityError InvalidRoleName(string role) - => LocalizedIdentityError("Role name '{0}' is invalid.", role); + => LocalizedIdentityError(_errProvider.InvalidRoleName, role); /// /// "Invalid token." /// /// public override IdentityError InvalidToken() - => LocalizedIdentityError("Invalid token."); + => LocalizedIdentityError(_errProvider.InvalidToken); /// /// "User name '{0}' is invalid, can only contain letters or digits." @@ -80,42 +83,42 @@ public override IdentityError InvalidToken() /// /// public override IdentityError InvalidUserName(string userName) - => LocalizedIdentityError("User name '{0}' is invalid, can only contain letters or digits.", userName); + => LocalizedIdentityError(_errProvider.InvalidUserName, userName); /// /// "A user with this login already exists." /// /// public override IdentityError LoginAlreadyAssociated() - => LocalizedIdentityError("A user with this login already exists."); + => LocalizedIdentityError(_errProvider.LoginAlreadyAssociated); /// /// "Incorrect password." /// /// public override IdentityError PasswordMismatch() - => LocalizedIdentityError("Incorrect password."); + => LocalizedIdentityError(_errProvider.PasswordMismatch); /// /// "Passwords must have at least one digit ('0'-'9')." /// /// public override IdentityError PasswordRequiresDigit() - => LocalizedIdentityError("Passwords must have at least one digit ('0'-'9')."); + => LocalizedIdentityError(_errProvider.PasswordRequiresDigit); /// /// "Passwords must have at least one lowercase ('a'-'z')." /// /// public override IdentityError PasswordRequiresLower() - => LocalizedIdentityError("Passwords must have at least one lowercase ('a'-'z')."); + => LocalizedIdentityError(_errProvider.PasswordRequiresLower); /// /// "Passwords must have at least one non alphanumeric character." /// /// public override IdentityError PasswordRequiresNonAlphanumeric() - => LocalizedIdentityError("Passwords must have at least one non alphanumeric character."); + => LocalizedIdentityError(_errProvider.PasswordRequiresNonAlphanumeric); /// /// "Passwords must use at least {0} different characters." @@ -123,14 +126,14 @@ public override IdentityError PasswordRequiresNonAlphanumeric() /// /// public override IdentityError PasswordRequiresUniqueChars(int uniqueChars) - => LocalizedIdentityError("Passwords must use at least {0} different characters.", uniqueChars); + => LocalizedIdentityError(_errProvider.PasswordRequiresUniqueChars, uniqueChars); /// /// "Passwords must have at least one uppercase ('A'-'Z')." /// /// public override IdentityError PasswordRequiresUpper() - => LocalizedIdentityError("Passwords must have at least one uppercase ('A'-'Z')."); + => LocalizedIdentityError(_errProvider.PasswordRequiresUpper); /// /// "Passwords must be at least {0} characters." @@ -138,14 +141,14 @@ public override IdentityError PasswordRequiresUpper() /// /// public override IdentityError PasswordTooShort(int length) - => LocalizedIdentityError("Passwords must be at least {0} characters.", length); + => LocalizedIdentityError(_errProvider.PasswordTooShort, length); /// /// "User already has a password set." /// /// public override IdentityError UserAlreadyHasPassword() - => LocalizedIdentityError("User already has a password set."); + => LocalizedIdentityError(_errProvider.UserAlreadyHasPassword); /// /// "User already in role '{0}'." @@ -153,7 +156,7 @@ public override IdentityError UserAlreadyHasPassword() /// /// public override IdentityError UserAlreadyInRole(string role) - => LocalizedIdentityError("User already in role '{0}'.", role); + => LocalizedIdentityError(_errProvider.UserAlreadyInRole, role); /// /// "User is not in role '{0}'." @@ -161,34 +164,34 @@ public override IdentityError UserAlreadyInRole(string role) /// /// public override IdentityError UserNotInRole(string role) - => LocalizedIdentityError("User is not in role '{0}'.", role); + => LocalizedIdentityError(_errProvider.UserNotInRole, role); /// /// "Lockout is not enabled for this user." /// /// public override IdentityError UserLockoutNotEnabled() - => LocalizedIdentityError("Lockout is not enabled for this user."); + => LocalizedIdentityError(_errProvider.UserLockoutNotEnabled); /// /// "Recovery code redemption failed." /// /// public override IdentityError RecoveryCodeRedemptionFailed() - => LocalizedIdentityError("Recovery code redemption failed."); + => LocalizedIdentityError(_errProvider.RecoveryCodeRedemptionFailed); /// /// "Optimistic concurrency failure, object has been modified." /// /// public override IdentityError ConcurrencyFailure() - => LocalizedIdentityError("Optimistic concurrency failure, object has been modified."); + => LocalizedIdentityError(_errProvider.ConcurrencyFailure); /// /// "An unknown failure has occurred." /// /// public override IdentityError DefaultError() - => LocalizedIdentityError("An unknown failure has occurred."); + => LocalizedIdentityError(_errProvider.DefaultError); } } diff --git a/XLocalizer/ModelBinding/DefaultModelBindingErrorMessagesProvider.cs b/XLocalizer/ModelBinding/DefaultModelBindingErrorMessagesProvider.cs new file mode 100644 index 0000000..1b2c03f --- /dev/null +++ b/XLocalizer/ModelBinding/DefaultModelBindingErrorMessagesProvider.cs @@ -0,0 +1,32 @@ +namespace XLocalizer.ModelBinding +{ + /// + /// Class to provide custom default model binding error messages. + /// Messages can be provided in any culture, so user can provide localized error messages here, + /// but the default request culture in startup must be configured same as messages culture. + /// + public class DefaultModelBindingErrorMessagesProvider : IModelBindingErrorMessagesProvider + { + string IModelBindingErrorMessagesProvider.AttemptedValueIsInvalidAccessor => "The value '{0}' is not valid for {1}."; + + string IModelBindingErrorMessagesProvider.MissingBindRequiredValueAccessor => "A value for the '{0}' parameter or property was not provided."; + + string IModelBindingErrorMessagesProvider.MissingKeyOrValueAccessor => "A value is required."; + + string IModelBindingErrorMessagesProvider.MissingRequestBodyRequiredValueAccessor => "A non-empty request body is required."; + + string IModelBindingErrorMessagesProvider.NonPropertyAttemptedValueIsInvalidAccessor => "The value '{0}' is not valid."; + + string IModelBindingErrorMessagesProvider.NonPropertyUnknownValueIsInvalidAccessor => "The supplied value is invalid."; + + string IModelBindingErrorMessagesProvider.NonPropertyValueMustBeANumberAccessor => "The field must be a number."; + + string IModelBindingErrorMessagesProvider.UnknownValueIsInvalidAccessor => "The supplied value is invalid for {0}."; + + string IModelBindingErrorMessagesProvider.ValueIsInvalidAccessor => "The value '{0}' is invalid."; + + string IModelBindingErrorMessagesProvider.ValueMustBeANumberAccessor => "The field {0} must be a number."; + + string IModelBindingErrorMessagesProvider.ValueMustNotBeNullAccessor => "The value '{0}' is invalid."; + } +} diff --git a/XLocalizer/ModelBinding/DependencyInjection.cs b/XLocalizer/ModelBinding/DependencyInjection.cs index bb9d412..26d272e 100644 --- a/XLocalizer/ModelBinding/DependencyInjection.cs +++ b/XLocalizer/ModelBinding/DependencyInjection.cs @@ -1,6 +1,6 @@ -using XLocalizer.Common; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace XLocalizer.ModelBinding { @@ -16,13 +16,21 @@ public static class DependencyInjection /// public static IMvcBuilder AddModelBindingLocalization(this IMvcBuilder builder) { + // Try register a service for providing default model binding error messages + builder.Services.TryAddSingleton(); + // Add ModelBinding errors localization builder.AddMvcOptions(ops => { - var localizer = builder.Services.BuildServiceProvider().GetService(typeof(IStringLocalizer)) as IStringLocalizer; - ops.ModelBindingMessageProvider.SetLocalizedModelBindingErrorMessages(localizer); + var serviceBuilder = builder.Services.BuildServiceProvider(); + + var localizer = serviceBuilder.GetRequiredService(typeof(IStringLocalizer)) as IStringLocalizer; + var mbErrMsgProvider = serviceBuilder.GetRequiredService(typeof(IModelBindingErrorMessagesProvider)) as IModelBindingErrorMessagesProvider; + + ops.ModelBindingMessageProvider.SetLocalizedModelBindingErrorMessages(localizer, mbErrMsgProvider); }); + return builder; } } diff --git a/XLocalizer/ModelBinding/IModelBindingErrorMessagesProvider.cs b/XLocalizer/ModelBinding/IModelBindingErrorMessagesProvider.cs new file mode 100644 index 0000000..74c5150 --- /dev/null +++ b/XLocalizer/ModelBinding/IModelBindingErrorMessagesProvider.cs @@ -0,0 +1,65 @@ +namespace XLocalizer.ModelBinding +{ + /// + /// Interface to provide custom default model binding error messages. + /// Messages can be provided in any culture, so user can provide localized error messages here, + /// but the default request culture in startup must be configured same as messages culture. + /// + public interface IModelBindingErrorMessagesProvider + { + /// + /// "The value '{0}' is not valid for {1}." + /// + string AttemptedValueIsInvalidAccessor { get; } + + /// + /// "A value for the '{0}' parameter or property was not provided." + /// + string MissingBindRequiredValueAccessor { get; } + + /// + /// "A value is required." + /// + string MissingKeyOrValueAccessor { get; } + + /// + /// "A non-empty request body is required." + /// + string MissingRequestBodyRequiredValueAccessor { get; } + + /// + /// "The value '{0}' is not valid." + /// + string NonPropertyAttemptedValueIsInvalidAccessor { get; } + + /// + /// "The supplied value is invalid." + /// + string NonPropertyUnknownValueIsInvalidAccessor { get; } + + /// + /// "The field must be a number." + /// + string NonPropertyValueMustBeANumberAccessor { get; } + + /// + /// "The supplied value is invalid for {0}." + /// + string UnknownValueIsInvalidAccessor { get; } + + /// + /// "The value '{0}' is invalid." + /// + string ValueIsInvalidAccessor { get; } + + /// + /// "The field {0} must be a number." + /// + string ValueMustBeANumberAccessor { get; } + + /// + /// "The value '{0}' is invalid." + /// + string ValueMustNotBeNullAccessor { get; } + } +} diff --git a/XLocalizer/ModelBinding/ModelBindingErrorsExtensions.cs b/XLocalizer/ModelBinding/ModelBindingErrorsExtensions.cs index 58b3414..93cfc6c 100644 --- a/XLocalizer/ModelBinding/ModelBindingErrorsExtensions.cs +++ b/XLocalizer/ModelBinding/ModelBindingErrorsExtensions.cs @@ -13,40 +13,41 @@ public static class ModelBindingErrorsExtensions /// /// /// localizer factory - public static void SetLocalizedModelBindingErrorMessages(this DefaultModelBindingMessageProvider provider, IStringLocalizer localizer) + /// Model binding errors provider + public static void SetLocalizedModelBindingErrorMessages(this DefaultModelBindingMessageProvider provider, IStringLocalizer localizer, IModelBindingErrorMessagesProvider mbErrors) { provider.SetAttemptedValueIsInvalidAccessor((x, y) - => GetLoclizedModelBindingError(localizer, "The value '{0}' is not valid for {1}.", x, y)); + => GetLoclizedModelBindingError(localizer, mbErrors.AttemptedValueIsInvalidAccessor, x, y)); provider.SetMissingBindRequiredValueAccessor((x) - => GetLoclizedModelBindingError(localizer, "A value for the '{0}' parameter or property was not provided.", x)); + => GetLoclizedModelBindingError(localizer, mbErrors.MissingBindRequiredValueAccessor, x)); provider.SetMissingKeyOrValueAccessor(() - => GetLoclizedModelBindingError(localizer, "A value is required.")); + => GetLoclizedModelBindingError(localizer, mbErrors.MissingKeyOrValueAccessor)); provider.SetMissingRequestBodyRequiredValueAccessor(() - => GetLoclizedModelBindingError(localizer, "A non-empty request body is required.")); + => GetLoclizedModelBindingError(localizer, mbErrors.MissingRequestBodyRequiredValueAccessor)); provider.SetNonPropertyAttemptedValueIsInvalidAccessor((x) - => GetLoclizedModelBindingError(localizer, "The value '{0}' is not valid.", x)); + => GetLoclizedModelBindingError(localizer, mbErrors.NonPropertyAttemptedValueIsInvalidAccessor, x)); provider.SetNonPropertyUnknownValueIsInvalidAccessor(() - => GetLoclizedModelBindingError(localizer, "The supplied value is invalid.")); + => GetLoclizedModelBindingError(localizer, mbErrors.NonPropertyUnknownValueIsInvalidAccessor)); provider.SetNonPropertyValueMustBeANumberAccessor(() - => GetLoclizedModelBindingError(localizer, "The field must be a number.")); + => GetLoclizedModelBindingError(localizer, mbErrors.NonPropertyValueMustBeANumberAccessor)); provider.SetUnknownValueIsInvalidAccessor((x) - => GetLoclizedModelBindingError(localizer, "The supplied value is invalid for {0}.", x)); + => GetLoclizedModelBindingError(localizer, mbErrors.UnknownValueIsInvalidAccessor, x)); provider.SetValueIsInvalidAccessor((x) - => GetLoclizedModelBindingError(localizer, "The value '{0}' is invalid.", x)); + => GetLoclizedModelBindingError(localizer, mbErrors.ValueIsInvalidAccessor, x)); provider.SetValueMustBeANumberAccessor((x) - => GetLoclizedModelBindingError(localizer, "The field {0} must be a number.", x)); + => GetLoclizedModelBindingError(localizer, mbErrors.ValueMustBeANumberAccessor, x)); provider.SetValueMustNotBeNullAccessor((x) - => GetLoclizedModelBindingError(localizer, "The value '{0}' is invalid.", x)); + => GetLoclizedModelBindingError(localizer, mbErrors.ValueMustNotBeNullAccessor, x)); } private static string GetLoclizedModelBindingError(IStringLocalizer localizer, string code, params object[] args) diff --git a/XLocalizer/XLocalizer.xml b/XLocalizer/XLocalizer.xml index e50819e..f6f506a 100644 --- a/XLocalizer/XLocalizer.xml +++ b/XLocalizer/XLocalizer.xml @@ -951,6 +951,13 @@ + + + Class to provide custom default model binding error messages. + Messages can be provided in any culture, so user can provide localized error messages here, + but the default request culture in startup must be configured same as messages culture. + + Extesnions for adding DataAnnotation Localization @@ -963,17 +970,80 @@ + + + Interface to provide custom default model binding error messages. + Messages can be provided in any culture, so user can provide localized error messages here, + but the default request culture in startup must be configured same as messages culture. + + + + + "The value '{0}' is not valid for {1}." + + + + + "A value for the '{0}' parameter or property was not provided." + + + + + "A value is required." + + + + + "A non-empty request body is required." + + + + + "The value '{0}' is not valid." + + + + + "The supplied value is invalid." + + + + + "The field must be a number." + + + + + "The supplied value is invalid for {0}." + + + + + "The value '{0}' is invalid." + + + + + "The field {0} must be a number." + + + + + "The value '{0}' is invalid." + + Original messages obtained from - + Use DB for localization localizer factory + Model binding errors provider From ea5215ebe0b3d204025da4246290fef100e3fca7 Mon Sep 17 00:00:00 2001 From: LazZiya Date: Mon, 24 Aug 2020 09:34:47 +0300 Subject: [PATCH 2/3] DA err msg prvdr --- ...ultDataAnnotationsErrorMessagesProvider.cs | 103 +++++ .../IDataAnnotationsMessagesProvider.cs | 246 +++++++++++ XLocalizer/XLocalizer.xml | 383 +++++++++++++++++- 3 files changed, 727 insertions(+), 5 deletions(-) create mode 100644 XLocalizer/DataAnnotations/DefaultDataAnnotationsErrorMessagesProvider.cs create mode 100644 XLocalizer/DataAnnotations/IDataAnnotationsMessagesProvider.cs diff --git a/XLocalizer/DataAnnotations/DefaultDataAnnotationsErrorMessagesProvider.cs b/XLocalizer/DataAnnotations/DefaultDataAnnotationsErrorMessagesProvider.cs new file mode 100644 index 0000000..d451e1e --- /dev/null +++ b/XLocalizer/DataAnnotations/DefaultDataAnnotationsErrorMessagesProvider.cs @@ -0,0 +1,103 @@ +namespace XLocalizer.DataAnnotations +{ + /// + /// Original messages obtained from + /// + public class DefaultDataAnnotationsErrorMessagesProvider : IDataAnnotationsMessagesProvider + { + string IDataAnnotationsMessagesProvider.ArgumentIsNullOrWhitespace => "The argument '{0}' cannot be null, empty or contain only whitespace."; + + string IDataAnnotationsMessagesProvider.AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties => "The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type."; + + string IDataAnnotationsMessagesProvider.AttributeStore_Unknown_Property => "The type '{0}' does not contain a public property named '{1}'."; + + string IDataAnnotationsMessagesProvider.Common_PropertyNotFound => "The property {0}.{1} could not be found."; + + string IDataAnnotationsMessagesProvider.CompareAttribute_MustMatch => "'{0}' and '{1}' do not match."; + + string IDataAnnotationsMessagesProvider.CompareAttribute_UnknownProperty => "Could not find a property named {0}."; + + string IDataAnnotationsMessagesProvider.CreditCardAttribute_Invalid => "The {0} field is not a valid credit card number."; + + string IDataAnnotationsMessagesProvider.CustomValidationAttribute_Type_Conversion_Failed => "Could not convert the value of type '{0}' to '{1}' as expected by method {2}.{3}."; + + string IDataAnnotationsMessagesProvider.CustomValidationAttribute_Type_Must_Be_Public => "The custom validation type '{0}' must be public."; + + string IDataAnnotationsMessagesProvider.CustomValidationAttribute_ValidationError => "{0} is not valid."; + + string IDataAnnotationsMessagesProvider.DataTypeAttribute_EmptyDataTypeString => "The custom DataType string cannot be null or empty."; + + string IDataAnnotationsMessagesProvider.DisplayAttribute_PropertyNotSet => "The {0} property has not been set. Use the {1} method to get the value."; + + string IDataAnnotationsMessagesProvider.EmailAddressAttribute_Invalid => "The {0} field is not a valid e-mail address."; + + string IDataAnnotationsMessagesProvider.EnumDataTypeAttribute_TypeCannotBeNull => "The type provided for EnumDataTypeAttribute cannot be null."; + + string IDataAnnotationsMessagesProvider.EnumDataTypeAttribute_TypeNeedsToBeAnEnum => "The type '{0}' needs to represent an enumeration type."; + + string IDataAnnotationsMessagesProvider.FileExtensionsAttribute_Invalid => "The {0} field only accepts files with the following extensions: {1}"; + + string IDataAnnotationsMessagesProvider.LengthAttribute_InvalidValueType => "The field of type {0} must be a string, array or ICollection type."; + + string IDataAnnotationsMessagesProvider.MaxLengthAttribute_InvalidMaxLength => "MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length."; + + string IDataAnnotationsMessagesProvider.MaxLengthAttribute_ValidationError => "The field {0} must be a string or array type with a maximum length of '{1}'."; + + string IDataAnnotationsMessagesProvider.MetadataTypeAttribute_TypeCannotBeNull => "MetadataClassType cannot be null."; + + string IDataAnnotationsMessagesProvider.MinLengthAttribute_InvalidMinLength => "MinLengthAttribute must have a Length value that is zero or greater."; + + string IDataAnnotationsMessagesProvider.MinLengthAttribute_ValidationError => "The field {0} must be a string or array type with a minimum length of '{1}'."; + + string IDataAnnotationsMessagesProvider.PhoneAttribute_Invalid => "The {0} field is not a valid phone number."; + + string IDataAnnotationsMessagesProvider.RangeAttribute_ArbitraryTypeNotIComparable => "The type {0} must implement {1}."; + + string IDataAnnotationsMessagesProvider.RangeAttribute_MinGreaterThanMax => "The maximum value '{0}' must be greater than or equal to the minimum value '{1}'."; + + string IDataAnnotationsMessagesProvider.RangeAttribute_Must_Set_Min_And_Max => "The minimum and maximum values must be set."; + + string IDataAnnotationsMessagesProvider.RangeAttribute_Must_Set_Operand_Type => "The OperandType must be set when strings are used for minimum and maximum values."; + + string IDataAnnotationsMessagesProvider.RangeAttribute_ValidationError => "The field {0} must be between {1} and {2}."; + + string IDataAnnotationsMessagesProvider.RegexAttribute_ValidationError => "The field {0} must match the regular expression '{1}'."; + + string IDataAnnotationsMessagesProvider.RegularExpressionAttribute_Empty_Pattern => "The pattern must be set to a valid regular expression."; + + string IDataAnnotationsMessagesProvider.RequiredAttribute_ValidationError => "The {0} field is required."; + + string IDataAnnotationsMessagesProvider.StringLengthAttribute_InvalidMaxLength => "The maximum length must be a nonnegative integer."; + + string IDataAnnotationsMessagesProvider.StringLengthAttribute_ValidationError => "The field {0} must be a string with a maximum length of {1}."; + + string IDataAnnotationsMessagesProvider.StringLengthAttribute_ValidationErrorIncludingMinimum => "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}."; + + + string IDataAnnotationsMessagesProvider.UIHintImplementation_ControlParameterKeyIsNotAString => "The key parameter at position {0} with value '{1}' is not a string. Every key control parameter must be a string."; + + string IDataAnnotationsMessagesProvider.UIHintImplementation_ControlParameterKeyIsNull => "The key parameter at position {0} is null. Every key control parameter must be a string."; + + string IDataAnnotationsMessagesProvider.UIHintImplementation_ControlParameterKeyOccursMoreThanOnce => "The key parameter at position {0} with value '{1}' occurs more than once."; + + string IDataAnnotationsMessagesProvider.UIHintImplementation_NeedEvenNumberOfControlParameters => "The number of control parameters must be even."; + + string IDataAnnotationsMessagesProvider.UrlAttribute_Invalid => "The {0} field is not a valid fully-qualified http, https, or ftp URL."; + + string IDataAnnotationsMessagesProvider.ValidationAttribute_Cannot_Set_ErrorMessage_And_Resource => "Either ErrorMessageString or ErrorMessageResourceName must be set, but not both."; + + string IDataAnnotationsMessagesProvider.ValidationAttribute_IsValid_NotImplemented => "IsValid(object value) has not been implemented by this class. The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context)."; + + string IDataAnnotationsMessagesProvider.ValidationAttribute_NeedBothResourceTypeAndResourceName => "Both ErrorMessageResourceType and ErrorMessageResourceName need to be set on this attribute."; + + string IDataAnnotationsMessagesProvider.ValidationAttribute_ResourcePropertyNotStringType => "The property '{0}' on resource type '{1}' is not a string type."; + + string IDataAnnotationsMessagesProvider.ValidationAttribute_ResourceTypeDoesNotHaveProperty => "The resource type '{0}' does not have an accessible static property named '{1}'."; + + string IDataAnnotationsMessagesProvider.ValidationAttribute_ValidationError => "The field {0} is invalid."; + + string IDataAnnotationsMessagesProvider.Validator_InstanceMustMatchValidationContextInstance => "The instance provided must match the ObjectInstance on the ValidationContext supplied."; + + string IDataAnnotationsMessagesProvider.Validator_Property_Value_Wrong_Type => "The value for property '{0}' must be of type '{1}'."; + } +} diff --git a/XLocalizer/DataAnnotations/IDataAnnotationsMessagesProvider.cs b/XLocalizer/DataAnnotations/IDataAnnotationsMessagesProvider.cs new file mode 100644 index 0000000..ce263e5 --- /dev/null +++ b/XLocalizer/DataAnnotations/IDataAnnotationsMessagesProvider.cs @@ -0,0 +1,246 @@ +namespace XLocalizer.DataAnnotations +{ + /// + /// Interface to provide custom default data annotation error messages. + /// Messages can be provided in any culture, so user can provide localized error messages here, + /// but the default request culture in startup must be configured same as messages culture. + /// + public interface IDataAnnotationsMessagesProvider + { + /// + /// The argument '{0}' cannot be null, empty or contain only whitespace. + /// + string ArgumentIsNullOrWhitespace { get; } + + /// + /// The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type. + /// + string AssociatedMetadataTypeTypeDescriptor_MetadataTypeContainsUnknownProperties { get; } + + /// + /// The type '{0}' does not contain a property named '{1}'. + /// + string AttributeStore_Unknown_Property { get; } + + /// + /// The property {0}.{1} could not be found. + /// + string Common_PropertyNotFound { get; } + + /// + /// '{0}' and '{1}' do not match. + /// + string CompareAttribute_MustMatch { get; } + + /// + /// Could not find a property named {0}. + /// + string CompareAttribute_UnknownProperty { get; } + + /// + /// The {0} field is not a valid credit card number. + /// + string CreditCardAttribute_Invalid { get; } + + /// + /// Could not convert the value of type '{0}' to '{1}' as expected by method {2}.{3}. + /// + string CustomValidationAttribute_Type_Conversion_Failed { get; } + + /// + /// The custom validation type '{0}' must be public. + /// + string CustomValidationAttribute_Type_Must_Be_Public { get; } + + /// + /// {0} is not valid. + /// + string CustomValidationAttribute_ValidationError { get; } + + /// + /// The custom DataType string cannot be null or empty. + /// + string DataTypeAttribute_EmptyDataTypeString { get; } + + /// + /// The {0} property has not been set. Use the {1} method to get the value. + /// + string DisplayAttribute_PropertyNotSet { get; } + + /// + /// The {0} field is not a valid e-mail address. + /// + string EmailAddressAttribute_Invalid { get; } + + /// + /// The type provided for EnumDataTypeAttribute cannot be null. + /// + string EnumDataTypeAttribute_TypeCannotBeNull { get; } + + /// + /// The type '{0}' needs to represent an enumeration type. + /// + string EnumDataTypeAttribute_TypeNeedsToBeAnEnum { get; } + + /// + /// The {0} field only accepts files with the following extensions: {1} + /// + string FileExtensionsAttribute_Invalid { get; } + + /// + /// The field of type {0} must be a string, array or ICollection type. + /// + string LengthAttribute_InvalidValueType { get; } + + + /// + /// MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length. + /// + string MaxLengthAttribute_InvalidMaxLength { get; } + + /// + /// The field {0} must be a string or array type with a maximum length of '{1}'. + /// + string MaxLengthAttribute_ValidationError { get; } + + /// + /// MetadataClassType cannot be null. + /// + string MetadataTypeAttribute_TypeCannotBeNull { get; } + + /// + /// MinLengthAttribute must have a Length value that is zero or greater. + /// + string MinLengthAttribute_InvalidMinLength { get; } + + /// + /// The field {0} must be a string or array type with a minimum length of '{1}'. + /// + string MinLengthAttribute_ValidationError { get; } + + /// + /// The {0} field is not a valid phone number. + /// + string PhoneAttribute_Invalid { get; } + + /// + /// The type {0} must implement {1}. + /// + string RangeAttribute_ArbitraryTypeNotIComparable { get; } + + /// + /// The maximum value '{0}' must be greater than or equal to the minimum value '{1}'. + /// + string RangeAttribute_MinGreaterThanMax { get; } + + /// + /// The minimum and maximum values must be set. + /// + string RangeAttribute_Must_Set_Min_And_Max { get; } + + /// + /// The OperandType must be set when strings are used for minimum and maximum values. + /// + string RangeAttribute_Must_Set_Operand_Type { get; } + + /// + /// The field {0} must be between {1} and {2}. + /// + string RangeAttribute_ValidationError { get; } + + /// + /// The field {0} must match the regular expression '{1}'. + /// + string RegexAttribute_ValidationError { get; } + + /// + /// The pattern must be set to a valid regular expression. + /// + string RegularExpressionAttribute_Empty_Pattern { get; } + + /// + /// The {0} field is required. + /// + string RequiredAttribute_ValidationError { get; } + + /// + /// The maximum length must be a nonnegative integer. + /// + string StringLengthAttribute_InvalidMaxLength { get; } + + /// + /// The field {0} must be a string with a maximum length of {1}. + /// + string StringLengthAttribute_ValidationError { get; } + + /// + /// The field {0} must be a string with a minimum length of {2} and a maximum length of {1}. + /// + string StringLengthAttribute_ValidationErrorIncludingMinimum { get; } + + /// + /// The key parameter at position {0} with value '{1}' is not a string. Every key control parameter must be a string. + /// + string UIHintImplementation_ControlParameterKeyIsNotAString { get; } + + /// + /// The key parameter at position {0} is null. Every key control parameter must be a string. + /// + string UIHintImplementation_ControlParameterKeyIsNull { get; } + + /// + /// The key parameter at position {0} with value '{1}' occurs more than once. + /// + string UIHintImplementation_ControlParameterKeyOccursMoreThanOnce { get; } + + /// + /// The number of control parameters must be even. + /// + string UIHintImplementation_NeedEvenNumberOfControlParameters { get; } + + /// + /// The {0} field is not a valid fully-qualified http, https, or ftp URL. + /// + string UrlAttribute_Invalid { get; } + + /// + /// Either ErrorMessageString or ErrorMessageResourceName must be set, but not both. + /// + string ValidationAttribute_Cannot_Set_ErrorMessage_And_Resource { get; } + + /// + /// IsValid(object value) has not been implemented by this class. The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context). + /// + string ValidationAttribute_IsValid_NotImplemented { get; } + + /// + /// Both ErrorMessageResourceType and ErrorMessageResourceName need to be set on this attribute. + /// + string ValidationAttribute_NeedBothResourceTypeAndResourceName { get; } + + /// + /// The property '{0}' on resource type '{1}' is not a string type. + /// + string ValidationAttribute_ResourcePropertyNotStringType { get; } + + /// + /// The resource type '{0}' does not have an accessible static property named '{1}'. + /// + string ValidationAttribute_ResourceTypeDoesNotHaveProperty { get; } + + /// + /// The field {0} is invalid. + /// + string ValidationAttribute_ValidationError { get; } + + /// + /// The instance provided must match the ObjectInstance on the ValidationContext supplied. + /// + string Validator_InstanceMustMatchValidationContextInstance { get; } + + /// + /// The value for property '{0}' must be of type '{1}'. + /// + string Validator_Property_Value_Wrong_Type { get; } + } +} diff --git a/XLocalizer/XLocalizer.xml b/XLocalizer/XLocalizer.xml index f6f506a..755131d 100644 --- a/XLocalizer/XLocalizer.xml +++ b/XLocalizer/XLocalizer.xml @@ -186,12 +186,13 @@ Adapter for providing a localized error message for - + Initialize a new instance of + @@ -211,12 +212,13 @@ Adapter for providing a localized error message for - + Initialize a new instance of + @@ -526,6 +528,11 @@ The value for property '{0}' must be of type '{1}'. + + + Original messages obtained from + + Extesnions for adding DataAnnotation Localization @@ -545,12 +552,13 @@ A switch to return the relevant Ex attribute or null if no Ex attribute is detected - + Get the ex attribute adapter + @@ -596,7 +604,7 @@ Registeres express valdiation attributes - + Initialize a new instance of @@ -672,6 +680,248 @@ The maximum length of a string. + + + Interface to provide custom default data annotation error messages. + Messages can be provided in any culture, so user can provide localized error messages here, + but the default request culture in startup must be configured same as messages culture. + + + + + The argument '{0}' cannot be null, empty or contain only whitespace. + + + + + The associated metadata type for type '{0}' contains the following unknown properties or fields: {1}. Please make sure that the names of these members match the names of the properties on the main type. + + + + + The type '{0}' does not contain a property named '{1}'. + + + + + The property {0}.{1} could not be found. + + + + + '{0}' and '{1}' do not match. + + + + + Could not find a property named {0}. + + + + + The {0} field is not a valid credit card number. + + + + + Could not convert the value of type '{0}' to '{1}' as expected by method {2}.{3}. + + + + + The custom validation type '{0}' must be public. + + + + + {0} is not valid. + + + + + The custom DataType string cannot be null or empty. + + + + + The {0} property has not been set. Use the {1} method to get the value. + + + + + The {0} field is not a valid e-mail address. + + + + + The type provided for EnumDataTypeAttribute cannot be null. + + + + + The type '{0}' needs to represent an enumeration type. + + + + + The {0} field only accepts files with the following extensions: {1} + + + + + The field of type {0} must be a string, array or ICollection type. + + + + + MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length. + + + + + The field {0} must be a string or array type with a maximum length of '{1}'. + + + + + MetadataClassType cannot be null. + + + + + MinLengthAttribute must have a Length value that is zero or greater. + + + + + The field {0} must be a string or array type with a minimum length of '{1}'. + + + + + The {0} field is not a valid phone number. + + + + + The type {0} must implement {1}. + + + + + The maximum value '{0}' must be greater than or equal to the minimum value '{1}'. + + + + + The minimum and maximum values must be set. + + + + + The OperandType must be set when strings are used for minimum and maximum values. + + + + + The field {0} must be between {1} and {2}. + + + + + The field {0} must match the regular expression '{1}'. + + + + + The pattern must be set to a valid regular expression. + + + + + The {0} field is required. + + + + + The maximum length must be a nonnegative integer. + + + + + The field {0} must be a string with a maximum length of {1}. + + + + + The field {0} must be a string with a minimum length of {2} and a maximum length of {1}. + + + + + The key parameter at position {0} with value '{1}' is not a string. Every key control parameter must be a string. + + + + + The key parameter at position {0} is null. Every key control parameter must be a string. + + + + + The key parameter at position {0} with value '{1}' occurs more than once. + + + + + The number of control parameters must be even. + + + + + The {0} field is not a valid fully-qualified http, https, or ftp URL. + + + + + Either ErrorMessageString or ErrorMessageResourceName must be set, but not both. + + + + + IsValid(object value) has not been implemented by this class. The preferred entry point is GetValidationResult() and classes should override IsValid(object value, ValidationContext context). + + + + + Both ErrorMessageResourceType and ErrorMessageResourceName need to be set on this attribute. + + + + + The property '{0}' on resource type '{1}' is not a string type. + + + + + The resource type '{0}' does not have an accessible static property named '{1}'. + + + + + The field {0} is invalid. + + + + + The instance provided must match the ObjectInstance on the ValidationContext supplied. + + + + + The value for property '{0}' must be of type '{1}'. + + DI @@ -713,6 +963,11 @@ + + + Default identity errors provider + + Extesnions for adding Identity errors localization @@ -731,11 +986,12 @@ Original mesages obtained from: https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Extensions.Core/src/Resources.resx - + Initialize identity erroors localization based on DB locailzer + @@ -879,6 +1135,123 @@ + + + Interface to provide custom default identity error messages. + Messages can be provided in any culture, so user can provide localized error messages here, + but the default request culture in startup must be configured same as messages culture. + + + + + "Email '{0}' is already taken." + + + + + "User name '{0}' is already taken." + + + + + "Email '{0}' is invalid." + + + + + "Role name '{0}' is already taken." + + + + + "Role name '{0}' is invalid." + + + + + "Invalid token." + + + + + "User name '{0}' is invalid, can only contain letters or digits." + + + + + "A user with this login already exists." + + + + + "Incorrect password." + + + + + "Passwords must have at least one digit ('0'-'9')." + + + + + "Passwords must have at least one lowercase ('a'-'z')." + + + + + "Passwords must have at least one non alphanumeric character." + + + + + "Passwords must use at least {0} different characters." + + + + + "Passwords must have at least one uppercase ('A'-'Z')." + + + + + "Passwords must be at least {0} characters." + + + + + "User already has a password set." + + + + + "User already in role '{0}'." + + + + + "User is not in role '{0}'." + + + + + "Lockout is not enabled for this user." + + + + + "Recovery code redemption failed." + + + + + "Optimistic concurrency failure, object has been modified." + + + + + "An unknown failure has occurred." + + Interface to create IHtmlLocalizer with the default (shared) resource type From 4e3821cd759980c864c704729c9391b5cc41f1cc Mon Sep 17 00:00:00 2001 From: LazZiya Date: Mon, 24 Aug 2020 09:40:11 +0300 Subject: [PATCH 3/3] preview3 --- XLocalizer/XLocalizer.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/XLocalizer/XLocalizer.csproj b/XLocalizer/XLocalizer.csproj index 79a0273..5a62b62 100644 --- a/XLocalizer/XLocalizer.csproj +++ b/XLocalizer/XLocalizer.csproj @@ -15,6 +15,8 @@ asp.net,core,razor,mvc,localization,globalization,client side,validation,translation,autokeyadding - Ex validation attributes configured as public + - New: IModelBindingErrorMessagesProvide, use to override default model binding error messages + - New: IIdentityErrorMessagesProvide, use to override default identity describer error messages Release notes: https://github.com/LazZiya/XLocalizer/releases 1.0.0-preview3