简体   繁体   中英

Spring Boot, Hibernate validator language based on LocaleContextHolder

I've a Spring Boot 2.4.2 REST application using JPA, Hibernate, etc. So far I use a MessageSource for applications errors (located in i18n/messages), and the default ValidationMessages for bean validations.

This is part of my configuration:

 public static Set<Locale> LOCALES = Set.of(new Locale("en"), new Locale("it"));


@Bean
    public LocaleResolver localeResolver() {
        SmartLocaleResolver localeResolver = new SmartLocaleResolver();
        return localeResolver;
    }

    public class SmartLocaleResolver extends AcceptHeaderLocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            if (StringUtils.isBlank(request.getHeader("Accept-Language"))) {
                return Locale.getDefault();
            }
            List<Locale.LanguageRange> list = Locale.LanguageRange.parse(request.getHeader("Accept-Language"));
            Locale locale = Locale.lookup(list, LOCALES);
            return locale;
        }
    }

    @Primary
    @Bean("messageSource")
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setAlwaysUseMessageFormat(true);
        messageSource.setBasenames("classpath:/i18n/messages");
        // set to true only for debugging
        messageSource.setUseCodeAsDefaultMessage(false);
        messageSource.setFallbackToSystemLocale(false);
        return messageSource;
    }

My application supports 2 languages so far: it and en . The problem right now is that application's messages are correctly localized in the agent's language (browser) but Validations errors are not.

I found out that Hibernate uses the default locale (Locale.getDefault()) and to customize the behaviour I should customize the locale resolution.

So I tried creating a custom hibernateValidator (that I set in my entityFactory):

  @Bean
public MessageSource validationMessageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setAlwaysUseMessageFormat(true);
    messageSource.setBasenames("classpath:/ValidationMessages");
    // set to true only for debugging
    messageSource.setUseCodeAsDefaultMessage(false);
    messageSource.setFallbackToSystemLocale(false);
    return messageSource;
}

 @Bean("hibernateValidator")
    public LocalValidatorFactoryBean hibernateValidator() {
        LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
        factoryBean.setValidationMessageSource(validationMessageSource());           
        return factoryBean;
    }

and the resolver:

public class HibernateLocaleResolver implements LocaleResolver {

    @Override
    public Locale resolve(LocaleResolverContext context) {
        return LocaleContextHolder.getLocale();
    }
}

Doing this, the locale resolution works fine, but the parameter replacement doesn't. What I mean is for messages like this:

server.validators.ArraySize.message = The number of values must be between [{min}] and [{max}].

I've an exception:

"exception": "java.lang.IllegalArgumentException", "message": "can't parse argument number: min"

So I changed the configuration above adding the MessageInterpolator:

factoryBean.setMessageInterpolator(new ResourceBundleMessageInterpolator(LOCALES, Locale.ENGLISH, new HibernateLocaleResolver(), false));

At this point the parameters are resolved correctly, but again the locale resolution doesn't work.

Can you point me out in the right direction, trying to explain the best practice to follow for the combination Spring Boot - Hibernate Validator?

I solved the problem. I hope this can help someone else. This is my configuration file:

@Primary
@Bean("messageSource")
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setAlwaysUseMessageFormat(true);
    messageSource.setBasenames("classpath:/i18n/messages");
    // set to true only for debugging
    messageSource.setUseCodeAsDefaultMessage(false);
    messageSource.setFallbackToSystemLocale(false);
    return messageSource;
}

@Bean
public MessageSource validationMessageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setAlwaysUseMessageFormat(true);
    messageSource.setBasenames("classpath:/ValidationMessages");
    // set to true only for debugging
    messageSource.setUseCodeAsDefaultMessage(false);
    messageSource.setFallbackToSystemLocale(false);
    return messageSource;
}

@Bean
public LocalValidatorFactoryBean validator() {
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    factoryBean.setValidationMessageSource(validationMessageSource());
    MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
    factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
    return factoryBean;
}

and the LocaleConfiguration:

@Configuration
public class LocaleConfiguration implements WebMvcConfigurer {

    @Bean
    public LocaleResolver localeResolver() {
        SmartLocaleResolver localeResolver = new SmartLocaleResolver();
        return localeResolver;
    }

    public class SmartLocaleResolver extends AcceptHeaderLocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            if (StringUtils.isBlank(request.getHeader("Accept-Language"))) {
                return Locale.getDefault();
            }
            List<Locale.LanguageRange> list = Locale.LanguageRange.parse(request.getHeader("Accept-Language"));
            Locale locale = Locale.lookup(list, Constants.LOCALES);
            return locale;
        }
    }
}

the important part I saw made the difference are these lines:

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

In this way the interpolator works fine as well as the localization of the message.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM