Skip to content

Localize standard DataAnnotations in ASP.NET Core MVC #33073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sdudnic opened this issue May 27, 2021 · 12 comments
Closed

Localize standard DataAnnotations in ASP.NET Core MVC #33073

sdudnic opened this issue May 27, 2021 · 12 comments
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-localization investigate Priority:2 Work that is important, but not critical for the release

Comments

@sdudnic
Copy link

sdudnic commented May 27, 2021

The problem

As far as I see from the docs, DataAnnotation localization asks additional resx files, or hardcoded translation strings in [Attributes], or specific code customization, that seem not really fair versus non-English projects.

In order to make "The XX field is required" string to appear as "Le champ XX est obligatoire" we need to write it hardcoded on all decorated with [Required] properties, or write custom code to implement it on all the [Required] properties, and also for all other DataAnnotation attributes, that seem strange...

DataAnnotation should be translated in a similar way another localized strings are, depend on the current culture, be available in localized .json or res files or similar, without the need for each developer to translate by itself the same "Is required" string with same text in their projects.

Further technical details

  • ASP.NET Core version: 5.0.202
  • VS 2019

Linked to

@javiercn javiercn added the area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates label May 27, 2021
@mkArtakMSFT mkArtakMSFT added the enhancement This issue represents an ask for new feature or an enhancement to an existing one label May 27, 2021
@mkArtakMSFT mkArtakMSFT added this to the Backlog milestone May 27, 2021
@ghost
Copy link

ghost commented May 27, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@sdudnic sdudnic changed the title Localize standard DataAnnotations in Asp.net Core Localize standard DataAnnotations in ASP.NET Core MVC May 28, 2021
@progmars
Copy link

Also, the entire model validation message pipeline should be improved.

If we want to return translated error messages also for the automatic model binding validations (e.g. non-null properties, data types etc.), the workaround becomes very ugly. Just look at this convoluted piece of code I had to use as a workaround to have fully translated binding errors:

public static IMvcBuilder AddModelBindingMessagesLocalizer(this IMvcBuilder mvc,
            IServiceProvider serviceProvider, 
            IValidationErrorLocalizer localizer, Type modelBaseType)
        {
            var VL = localizer.ValidationStringLocalizer;
            var DL = localizer.FieldNameStringLocalizer;

            return mvc.AddMvcOptions(o =>
            {
                // for validation error messages
                o.ModelMetadataDetailsProviders.Add(new LocalizableValidationMetadataProvider(VL, modelBaseType));

                // for field names
                o.ModelMetadataDetailsProviders.Add(new LocalizableInjectingDisplayNameProvider(DL, modelBaseType));

                // does not work for JSON models - Json.Net throws its own error messages into ModelState :(
                // ModelBindingMessageProvider is only for FromForm
                // Json works for FromBody and needs a separate format interceptor
                var provider = o.ModelBindingMessageProvider;

                provider.SetValueIsInvalidAccessor((v) => VL["FormatHtmlGeneration_ValueIsInvalid", v]);
                provider.SetAttemptedValueIsInvalidAccessor((v, x) => VL["FormatModelState_AttemptedValueIsInvalid", v, x]);
                provider.SetMissingBindRequiredValueAccessor((v) => VL["FormatModelBinding_MissingBindRequiredMember", v]);
                provider.SetMissingKeyOrValueAccessor(() => VL["FormatKeyValuePair_BothKeyAndValueMustBePresent" ]);
                provider.SetMissingRequestBodyRequiredValueAccessor(() => VL["FormatModelBinding_MissingRequestBodyRequiredMember"]);
                provider.SetNonPropertyAttemptedValueIsInvalidAccessor((v) => VL["FormatModelState_NonPropertyAttemptedValueIsInvalid", v]);
                provider.SetNonPropertyUnknownValueIsInvalidAccessor(() => VL["FormatModelState_UnknownValueIsInvalid"]);
                provider.SetUnknownValueIsInvalidAccessor((v) => VL["FormatModelState_NonPropertyUnknownValueIsInvalid", v]);
                provider.SetValueMustNotBeNullAccessor((v) => VL["FormatModelBinding_NullValueNotValid", v]);
                provider.SetValueMustBeANumberAccessor((v) => VL["FormatHtmlGeneration_ValueMustBeNumber", v]);
                provider.SetNonPropertyValueMustBeANumberAccessor(() => VL["FormatHtmlGeneration_NonPropertyValueMustBeNumber"]);


                // the code below is to prevent JSON.NET from polluting modelstate with unlocalizable messages, such as:
                /*
                    "errors": {
                    "countryId": [
                      "Input string '111a' is not a valid number. Path 'countryId', line 3, position 23."
                    ]
                  },
                  */

                var customJsonInputFormatter = new LocalizableJsonInputFormatter(localizer,
                     serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger<LocalizableJsonInputFormatter>(),
                     serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value.SerializerSettings,
                     serviceProvider.GetRequiredService<ArrayPool<char>>(),
                     serviceProvider.GetRequiredService<ObjectPoolProvider>(),
                     o,
                     serviceProvider.GetRequiredService<IOptions<MvcJsonOptions>>().Value
                );

                o.InputFormatters.Insert(0, customJsonInputFormatter);

            });
        }

@sdudnic
Copy link
Author

sdudnic commented May 28, 2021

please do a "like" on the issue, if not, they will not treat it in the backlog...

@LazZiya
Copy link

LazZiya commented May 28, 2021

Many developers prefers official solutions, but sometimes when the wait is too long, you will start developing it yourself or find a community solution.

I've developed a nuget (XLocalizer) for simplifing localization in all aspects including auto adding missing keys and online translation support.

In terms of validation there are three main categories:

  • DataAnnotations errors
  • Model binding errors
  • Identity describer errors

Basically there is no need to provide any error message manually, XLocalizer will take care of localizing all automatically. But if you want to customize any error message you can do it simply in startup or json.

  • Option A : In startup

services.AddRazorPages()
        .AddXLocalizer<...>(ops =>
        {
            // Data annotation error messages
            ops.ValidationErrors = new ValidationErrors 
            {
                RequiredAttribute_ValidationError = "The {0} field is required.",
                CompareAttribute_MustMatch = "'{0}' and '{1}' do not match.",
                StringLengthAttribute_ValidationError = "The field {0} must be a string with a maximum length of {1}.",
                // ...
            };

            // Model binding error messages
            ops.ModelBindingErrors = new ModelBindingErrors
            {
                AttemptedValueIsInvalidAccessor = "The value '{0}' is not valid for {1}.",
                MissingBindRequiredValueAccessor = "A value for the '{0}' parameter or property was not provided.",
               // ...
            };

            // Identity Errors
            ops.IdentityErrors = new IdentityErrors
            {
                DuplicateEmail = "Email '{0}' is already taken.",
                DuplicateUserName = "User name '{0}' is already taken.",
                // ...
            }
        });
  • Option B - Json:

{
  "XLocalizerOptions": {

    ....

    "ValidationErrors": {
      "CompareAttribute_MustMatch": "'{0}' and '{1}' do not match. They should not be different!",
      "CreditCardAttribute_Invalid": "The {0} field is not a valid credit card number.",
      "CustomValidationAttribute_ValidationError": "{0} is not valid.",
      ...
    },
    "IdentityErrors": {
      "DuplicateEmail": "Email '{0}' is already taken.",
      "DuplicateUserName": "User name '{0}' is already taken. Please try another one.",
      "InvalidEmail": "Email '{0}' is invalid.",
      ...
    },
    "ModelBindingErrors": {
      "AttemptedValueIsInvalidAccessor": "The value '{0}' is not valid for {1}.",
      "MissingBindRequiredValueAccessor": "A value for the '{0}' parameter or property was not provided.",
      ...
    }
  }
}

@sdudnic
Copy link
Author

sdudnic commented May 28, 2021

great work, @LazZiya ! However when it comes to human translated messages, the actual solution is always to hardcode, and not use a community translated set of messages by language. You take the translation of "field" and in many languages it is translated like "meadow" or "pasture"... this is why I asked MS & Co to move to a stable solution for non-English users, not imposing them to hardcode the same text for each of them.

@ianbrian
Copy link

Localization should be baked in. I've been spending ages replacing hard-coded strings in Identity razor pages. Millions of developers must have done that. What a waste of time.

@pranavkm pranavkm modified the milestones: Backlog, .NET 7 Planning Oct 19, 2021
@pranavkm pranavkm added the Priority:2 Work that is important, but not critical for the release label Oct 28, 2021
@pranavkm pranavkm added the k:2 label Nov 5, 2021
@mkArtakMSFT mkArtakMSFT modified the milestones: .NET 7 Planning, Backlog Nov 11, 2021
@ghost
Copy link

ghost commented Nov 11, 2021

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@vkosciuszko
Copy link

Nice, in the backlog again 😒.

@ghost
Copy link

ghost commented Sep 28, 2022

Thanks for contacting us.

We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@mkArtakMSFT mkArtakMSFT added investigate enhancement This issue represents an ask for new feature or an enhancement to an existing one and removed enhancement This issue represents an ask for new feature or an enhancement to an existing one labels Nov 10, 2022
@mkArtakMSFT mkArtakMSFT added Priority:2 Work that is important, but not critical for the release and removed Priority:2 Work that is important, but not critical for the release labels Nov 10, 2022
@mkArtakMSFT mkArtakMSFT removed the k:2 label Nov 22, 2022
@hishamco
Copy link
Member

@sdudnic could please let me know what's the exact issue in the DataAnnotations localization? I remembered there was an issue with DisplayAttribute then we did a custom fix in Orchard Core

@sdudnic
Copy link
Author

sdudnic commented Aug 16, 2023

@hishamco I don't recall it well, but from description, could we translate a DataAnnotation via JSON localisation file?

@hishamco
Copy link
Member

Localization APIs work well with Data Annotations, but the default implementation is using ResourceManager, but it's doable

You might have a look to my library https://github.com/hishamco/My.Extensions.Localization.Json/

@ghost ghost locked as resolved and limited conversation to collaborators Sep 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-localization investigate Priority:2 Work that is important, but not critical for the release
Projects
None yet
Development

No branches or pull requests