简体   繁体   中英

Failed to get country specific Localized response

I have a spring boot application and we are trying to support multiple languages and countries.

These are the codes we are planning to support for now. en, es, fr, en-gb, es-mx, zh, ja.

I'm working on getting some fields in response to be localized. When I run the application in my local(eclipse) and check the response from postman I can see that it is picking up country specific files and giving the response correctly, but when I push my changes to AWS server it is not, it is only picking up language specific files and ignoring country codes.

For example, if I give the Accept-Language as da,es-MX;q=0.8 I expect the response in es-MX(we do not support da) but it is just giving the response in es(I have both es and es-MX files) and ignoring the country code MX.

But if i have Accept-Language as fr,es-MX;q=0.8 it is picking up messages_fr.properties file and gives the response in French which is what I expect.

Below are my classes.

@Configuration
@ComponentScan("com.hsf")
@EnableWebMvc
public class ApplicationConfig extends WebMvcConfigurerAdapter {

    @Value("${spring.application.name}")
    String appName;

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

    @Bean
    public DispatcherServlet dispatcherServlet() {
        final DispatcherServlet servlet = new DispatcherServlet();
        servlet.setDispatchOptionsRequest(true);
        return servlet;
    }

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:i18n/messages");
        // If true, the key of the message will be displayed if the key is not
        // found, instead of throwing an exception
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setDefaultEncoding("UTF-8");
        // The value 0 means always reload the messages to be developer friendly
        messageSource.setCacheSeconds(10);
        return messageSource;
    }

}

This class parses Accept-Language header and sets Locale.

public class SmartLocaleResolver extends AcceptHeaderLocaleResolver {

        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            try {
                List<LanguageRange> list = Locale.LanguageRange.parse(request.getHeader("Accept-Language"));
                if (!list.isEmpty()) {
                    for (LanguageRange s : list) {
                        if (ApplicationConstants.LOCALE.contains(s.getRange())) {
                            return Locale.forLanguageTag(s.getRange());
                        }
                    }
                }
            } catch (IllegalArgumentException e) {
                throw e;
            }
            return request.getLocale();
        }
    }

I have the below constant in my ApplicationConstants class

public static final List<String> LOCALE              = Collections
            .unmodifiableList(Arrays.asList("en", "es", "fr", "es-mx", "zh", "ja"));

Here is the MessageHandler class

@Component
public class MessageHandler {
    @Autowired
    private MessageSource messageSource;

    public String localizeMessage(String errorCode, Object args[]) {
        Locale locale = LocaleContextHolder.getLocale();
        String message = messageSource.getMessage(errorCode, args, locale);
        return message;

    }

    public String localizeMessage(String errorCode) {
        return localizeMessage(errorCode, null);
    }

}

Any thoughts on this?

It seems you're just reimplementing the lookup() method of Locale:

    List<Locale> locales = Arrays.asList(new Locale("en"),
                                         new Locale("es"),
                                         new Locale("fr"),
                                         new Locale("es", "MX"),
                                         new Locale("zh"),
                                         new Locale("ja"));
    List<Locale.LanguageRange> ranges = Locale.LanguageRange.parse("da,es-MX;q=0.8");
    Locale best = Locale.lookup(ranges, locales);
    System.out.println("best = " + best); // prints best = es_MX

    ranges = Locale.LanguageRange.parse("fr,es-MX;q=0.8");
    best = Locale.lookup(ranges, locales);
    System.out.println("best = " + best); // prints best = fr

There are few ways to implement the correct locale lookup method (by the way congratulation on following i18n best practices).
One way is to parse the header "manually", as you attempted.
However, please note that HttpServletRequest derives one fancy method from its superclass, that is ServletRequest#getLocales() .
My point is, you'll receive already parsed prioritized Enumeration of Locales. By the way, I would definitely use the HashSet to compare locales against (for performance reasons).

Enough of talking:

public class SmartLocaleResolver extends AcceptHeaderLocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        List<Locale> locales = Collections.list(request.getLocales());
        return getBestMatchingOrDefault(locales);
    }

    private Locale getBestMatchingOrDefault(List<Locale> locales) {
        Optional<Locale> maybeLocale = locales.stream()
            .filter(locale -> ApplicationConstants.LOCALE.contains(locale))
            .findFirst();
        return maybeLocale.orElse(ApplicationConstants.DEFAULT_LOCALE);
    }
}

In this case the ApplicationConstants class needs to be modified:

public static final Set<Locale> LOCALE = Collections.unmodifiableSet(
    new HashSet<Locale>(
        Arrays.asList(Locale.ENGLISH, 
                      Locale.forLanguageTag("es"), 
                      Locale.FRENCH, 
                      Locale.forLanguageTag("es-MX"), 
                      Locale.JAPANESE, 
                      Locale.forLanguageTag("zh-Hans"), 
                      Locale.PRC, 
                      Locale.CHINESE)));
public static final Locale DEFAULT_LOCALE = Locale.ENGLISH; // or something else?

A word on Chinese. There are two flavors of Chinese language - Chinese Simplified (zh-Hans) and Chinese Traditional (zh-Hant). It is best not to mix them. I am not sure if it is good idea to serve Chinese Simplified as default Chinese version. Well, maybe it is, but I have doubts. I would use zh-Hans and zh-CN specifically (even if that means I would copy over the same file during CI build).

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