Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring MVC: Fallback for unknown language code in uri parameter value

Im trying to build my first Spring MVC 4 app with i18n support and was thinking how i can use a default/fallback locale in case of the user is manipulating the language uri parameter to a non existing or supported locale For example http://localhost.de?lang=abc

Im using the code

@Bean
public LocaleResolver localeResolver() {
    SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
    sessionLocaleResolver.setDefaultLocale(Locale.GERMAN);
    return sessionLocaleResolver;
}

which works in general if i open the url the very first time but it seems not to work for the case i was describing. I know there is a mechanism which would use the default messages properties file but i would like to set a default/fallback locale for this case. Do i need to implement maybe a custom filter?

like image 741
StephanM Avatar asked Jun 15 '15 08:06

StephanM


People also ask

What is fallback concept in Spring MVC?

Spring mvc will fallback automatically to messages. properties file, when the lang parameter does not match with any of the i18n files availables in your application.

What is webmvc?

The Spring Web model-view-controller (MVC) framework is designed around a DispatcherServlet that dispatches requests to handlers, with configurable handler mappings, view resolution, locale and theme resolution as well as support for uploading files.

Which element has to be used in order to configure Spring MVC?

tag will be use to activate Spring MVC annotation scanning capability which allows to make use of annotations like @Controller and @RequestMapping etc.


2 Answers

My suggestion would be to subclass the SessionLocaleResolver and override the getLocale method:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    private static Set<Locale> allowedLocales;

    static {
        HashSet<Locale> allowed = new HashSet<>();
        allowed.add(Locale.GERMAN);
        allowed.add(Locale.CANADA);
        allowedLocales = Collections.unmodifiableSet(allowed);

    }

    @Bean
    LocaleResolver localeResolver() {
        return new LimitedSessionLocaleResolver();
    }

    class LimitedSessionLocaleResolver extends SessionLocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            Locale locale = super.resolveLocale(request);
            if (!allowedLocales.contains(locale)) {
                return determineDefaultLocale(request);
            }
            return locale;
        }
    }
}

This does not modify the Spring classes in any major way and is probably going to work without issues for the foreseeable future.

like image 113
Matjaž Pečan Avatar answered Sep 21 '22 06:09

Matjaž Pečan


Probably far from being perfect but this is what i built...

I also need to say that i changed the default language select mechanism a bit because of SEO needs and i decided to change the language not by using a get parameter but using instead the first part of my uri path for the selected language. For example: http://myurl.com/en/test.html instead of http://myurl.com/test.html?lang=de

In my annotation based configuration:

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

The locale resolver

public class UriLocaleResolver implements LocaleResolver {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private Locale locale = null;

    @Autowired
    private LocalizationService localizationService;


    @Override
    public Locale resolveLocale(final HttpServletRequest servletRequest) {
        if (locale != null) {
            return locale;
        }

        String languageIsoCode = null;
        try {
            languageIsoCode = ((String)servletRequest.getAttribute(RequestKey.LANGUAGE_ISO_CODE)).toLowerCase();
        }
        catch (Exception e) { }
        if (StringUtils.isBlank(languageIsoCode) || !localizationService.getSupportedLocaleLanguageIsoCodes().contains(languageIsoCode)) {
            logger.trace("Couldn't find valid language iso code. Using default locale '{}'", GlobalConstant.DEFAULT_LOCALE);
            return GlobalConstant.DEFAULT_LOCALE;
        }

        logger.trace("Found language iso code '{}'", languageIsoCode);
        return new Locale(languageIsoCode);
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale)     {
    this.locale = locale;
    }

}

The filter which checks for a valid language code in the uri...

@Component
public class UriLocalizationFilter extends OncePerRequestFilter {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private LocalizationService localizationService;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String uri = request.getRequestURI().substring(request.getContextPath().length());
        String[] pathParts = uri.split("/");

        if (!uri.startsWith(GlobalConstant.FRONTEND_RESOURCE_PATH_PREFIX) && pathParts.length >= 1 && pathParts[1].length() == 2) {
             String originalLanguageIsoCode = pathParts[1];
             String lowerCaseLanguageIsoCode = originalLanguageIsoCode.toLowerCase();

            if (localizationService.getSupportedLocaleLanguageIsoCodes().contains(lowerCaseLanguageIsoCode)) {
                logger.debug("Found valid language iso code {}", lowerCaseLanguageIsoCode);
            }
            else {
                 logger.debug("Found invalid language iso code {}. Using default language iso code {}.", lowerCaseLanguageIsoCode, GlobalConstant.DEFAULT_LOCALE.getLanguage());
                 lowerCaseLanguageIsoCode = GlobalConstant.DEFAULT_LOCALE.getLanguage();
            }

            String newUrl = StringUtils.removeStart(uri, '/' + originalLanguageIsoCode);
            request.setAttribute(RequestKey.LANGUAGE_ISO_CODE, lowerCaseLanguageIsoCode);
            logger.debug("Dispatching to new url '{}'", newUrl);
            request.getRequestDispatcher(newUrl).forward(request, response);
        }
        else {
            filterChain.doFilter(request, response);
        }
    }

    public void setLocalizationService(LocalizationService localizationService) {
        this.localizationService = localizationService;
    }

}

}

Frontend path is "resources" in my case where all static files like js, css, fonts etc are laying. localizationService.getSupportedLocaleLanguageIsoCodes() is a Set containing currently three language codes (en, ru, de). So in case of a wrong language code like abc im doing a forward with my default language "de". For me it was/is acceptable solution cause having a wrong language code in the uri means that the uri was manipulated by the user...

Like i said its maybe not "the" solution; for example im not sure how and if it works with cookies and/or "remember me" authentications (using spring security)...just had no time to test it yet...

like image 24
StephanM Avatar answered Sep 21 '22 06:09

StephanM