spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.15k stars 40.68k forks source link

Provide a configuration property for Hibernate Validator's default locale #20673

Closed aaguilera closed 4 years ago

aaguilera commented 4 years ago

Spring Boot 2.3 will upgrade Hibernate Validator to version 6.1.

This version allows for customization of locale resolution. See Custiomizing the locale resolution.

It would be nice if Spring Boot automatically configured Hibernate Validator to use the locale defined in spring.mvc.locale, if this property has been set.

wilkinsona commented 4 years ago

Thanks for the suggestion. I can see the value in providing a configuration property for Hibernate Validator's default locale, as doing it yourself requires quite a bit of code at the moment. For example, the equivalent of the current auto-configuration but with the default locale set to German, requires the following:

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static LocalValidatorFactoryBean defaultValidator() {
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean() {

        @Override
        protected void postProcessConfiguration(javax.validation.Configuration<?> configuration) {
            if (configuration instanceof HibernateValidatorConfiguration) {
                ((HibernateValidatorConfiguration) configuration).defaultLocale(Locale.GERMAN);
            }
        }

    };
    MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
    factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
    return factoryBean;
}

I don't think we should use spring.mvc.locale to control Hibernate Validator's locale. There's nothing MVC- or even web-specific about Hibernate Validator so I don't think its default locale should be tied to the spring.mvc.locale property.

aaguilera commented 4 years ago

Thanks @wilkinsona. You're right about the name of the property name.

Anyway, I'm trying to use your example as a workaround, but it's not working for me. Hibernate Validator still falls back to the system locale (es_ES, in my computer), ignoring the custom configuration. Am I missing something else?

wilkinsona commented 4 years ago

Sorry, I should have looked more closely at how Hibernate Validator uses the default locale and the effect that you were hoping for. LocalValidatorFactoryBean will already wrap the MessageInterpolator in a org.springframework.validation.beanvalidation.LocaleContextMessageInterpolator that uses LocaleContextHolder to determine the locale that should be used for message interpolation.

Having taken a step back, I'm no longer sure that there's much benefit in providing a configuration property that targets Hibernate validator. To help us to figure out what, if anything, we should do, can you please provide some more details about what behaviour you want to configure. A small sample zipped up and attached to this issue or pushed to a separate repository would be the ideal way to do that.

aaguilera commented 4 years ago

I have prepared a little demo at https://github.com/aaguilera/demo-springboot-issue-20673

This demo is a simple HTML form which shows a validation error message when submitted.

I need the error message to be show in the language of the user's browser, falling back to English whenever the user's browser is not any of the accepted languages (English, Spanish or Catalan).

Steps to reproduce:

Launch the application with mvn spring-boot:run and connect to http://localhost:8080 If you want to simulate the application running on a different system locale, you can do it by launching the application like this:

LANG=es_ES.UTF-8 mvn spring-boot:run              (simulates spanish system locale)
LANG=ca_ES.UTF-8 mvn spring-boot:run              (simulates catalan system locale)
LANG=en_US.UTF-8 mvn spring-boot:run              (simulates english system locale)

You will see an HTML form. Leave the field empty, and press the «submit» button.

The form will be validated, and an error message will appear next to the text field.

That error message is resolved by Spring via Hibernate Validator, using the Accept-Language HTTP header to decide the Locale, obtaining messages from several files at src/main/resources:

ValidationMessages_en.properties
ValidationMessages_es.properties
ValidationMessages_ca.properties

I'd like to be able to force the fallback locale as "english" (for example, en_US) regardless of the current system locale.

You can simulate the form submission with different "browser" languages with curl. For example, the following requests work as expected regardless of the system locale:

curl -s -X POST http://localhost:8080 -H "Accept-Language: es" | grep name.errors
curl -s -X POST http://localhost:8080 -H "Accept-Language: ca" | grep name.errors
curl -s -X POST http://localhost:8080 -H "Accept-Language: en" | grep name.errors

But if you launch the server with LANG=es_ES.UTF-8 mvn spring-boot:run, when you make a request in french:

curl -s -X POST http://localhost:8080 -H "Accept-Language: fr" | grep name.errors

The error message will be in spanish, and not in english.

Desired behavior: I want to be able to configure Spring Boot to force a fallback Locale for Hibernate Validator error messages.

wilkinsona commented 4 years ago

Thanks for the sample. Unfortunately, I think it has shown that Hibernate Validator cannot easily be configured to do what you'd like. Hibernate Validator's default locale only comes into play when message interpolation is being performed without a locale. That doesn't happen here as, when a request is made with Accept-Language: fr, Hibernate Validator's ResourceBundleMessageInterpolator is called with fr as the locale.

This call to the interpolator results in a call to PlatformResourceBundleLocator.getResourceBundle(Locale) and in turn a call to the JDK's java.util.ResourceBundle.getBundle(String, Locale, ClassLoader). When no bundle is found for the fr locale, it falls back to using Locale.getDefault() which is picked up from the environment when the JVM is launched.

I think you have a few options, but I'm not sure how suitable they may be for your situation:

  1. Launch the JVM with -Duser.language=en to cause Locale.getDefault() to be en.
  2. Call Locale.setDefault(Locale.ENGLISH) in your application's main method.
  3. Write your own MessageInterpolator that uses a different fallback strategy. I believe you could implement that by calling java.util.ResourceBundle.getBundle(String, Locale, Control) with a custom Control sub-class that overrides getFallbackLocale(String, Locale) to return Locale.ENGLISH rather than Locale.getDefault().

I'm going to close this issue as I don't think there's anything that we can reasonably do about this from a Spring Boot perspective. I'd recommend exploring option 1 or 2 above.

aaguilera commented 4 years ago

I will try your suggestions. Thank you very much for the follow-up Andy!

spyro2000 commented 1 year ago

Did you ever find a solution for that? I am trying to disable translation of hibernate validation messages and keep them english for hours now... :(