语言环境是Spring MVC中URL的一部分 [英] Locales as part of the URL in Spring MVC

查看:65
本文介绍了语言环境是Spring MVC中URL的一部分的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我现在正在寻找用于多语言Web应用程序的框架.目前,在我看来,最好的选择是Spring MVC.但是我面对这样一个事实,即所有针对开发人员的指南都建议使用LocaleChangeInterceptor以这种方式切换语言:

I'm now looking for a framework for multilingual web-applications. At the moment it seems to me that the best choice is Spring MVC. But I faced the fact that all the guidelines for developers suggests to switch languages using LocaleChangeInterceptor in such way:

http://www.somesite.com/action/?locale=zh_CN

不幸的是,有很多原因使我想避免这种情况.如何使语言代码成为URL的基本组成部分?例如:

Unfortunately, there are a number of reasons why I would like avoid this. How could I make language code to be an essential part of URL? For example:

http://www.somesite.com/en/action

谢谢.

UPD :我找到了以下解决方案.尚未完成,但可以使用.解决方案包括两部分-servlet过滤器和语言环境解析器bean.看起来有点破破烂烂,但我看不到其他解决此问题的方法.

UPD: I've found following solution. It's not complete yet, but works. Solution consists in two parts - servlet filter and locale resolver bean. It's looks little bit hackish, but I do not see other way to solve this problem.

public class LocaleFilter implements Filter
{

    ...

    private static final String DEFAULT_LOCALE = "en";
    private static final String[] AVAILABLE_LOCALES = new String[] {"en", "ru"};

    public LocaleFilter() {} 

    private List<String> getSevletRequestParts(ServletRequest request)
    {
        String[] splitedParts = ((HttpServletRequest) request).getServletPath().split("/");
        List<String> result = new ArrayList<String>();

        for (String sp : splitedParts)
        {
            if (sp.trim().length() > 0)
                result.add(sp);
        }

        return result;
    }

    private Locale getLocaleFromRequestParts(List<String> parts)
    {
        if (parts.size() > 0)
        {
            for (String lang : AVAILABLE_LOCALES)
            {
                if (lang.equals(parts.get(0)))
                {
                    return new Locale(lang);
                }
            }
        }

        return null;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException
    {
        List<String> requestParts = this.getSevletRequestParts(request);
        Locale locale = this.getLocaleFromRequestParts(requestParts);

        if (locale != null)
        {
            request.setAttribute(LocaleFilter.class.getName() + ".LOCALE", locale);

            StringBuilder sb = new StringBuilder();
            for (int i = 1; i < requestParts.size(); i++)
            {
                sb.append('/');
                sb.append((String) requestParts.get(i));
            }

            RequestDispatcher dispatcher = request.getRequestDispatcher(sb.toString());
            dispatcher.forward(request, response);
        }
        else
        {
            request.setAttribute(LocaleFilter.class.getName() + ".LOCALE", new Locale(DEFAULT_LOCALE));
            chain.doFilter(request, response);
        }
    }

    ...
}

public class FilterLocaleResolver implements LocaleResolver
{

    private Locale DEFAULT_LOCALE = new Locale("en");

    @Override
    public Locale resolveLocale(HttpServletRequest request)
    {
        Locale locale = (Locale) request.getAttribute(LocaleFilter.class.getName() + ".LOCALE");
        return (locale != null ? locale : DEFAULT_LOCALE);
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale)
    {
        request.setAttribute(LocaleFilter.class.getName() + ".LOCALE", locale);
    }

}

因此,无需在控制器中的每个操作中映射区域设置.以下示例可以正常工作:

So there is no need to map locale in each action in controllers. The following example will work fine:

@Controller
@RequestMapping("/test")
public class TestController
{

    @RequestMapping("action")
    public ModelAndView action(HttpServletRequest request, HttpServletResponse response)
    {
        ModelAndView mav = new ModelAndView("test/action");
        ...
        return mav;
    }

}

推荐答案

我通过结合使用Filter和Interceptor实现了非常相似的功能.

I implemented something very similar using a combination of Filter and Interceptor.

过滤器提取第一个路径变量,如果它是有效的语言环境,则将其设置为request属性,将其从请求的URI的开头剥离,并将请求转发至新的URI.

The filter extracts the first path variable and, if it's a valid locale it sets it as a request attribute, strips it from the beginning of the requested URI and forward the request to the new URI.

public class PathVariableLocaleFilter extends OncePerRequestFilter {
private static final Logger LOG = LoggerFactory.getLogger(PathVariableLocaleFilter.class);

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

    if (variables.length > 1 && isLocale(variables[1])) {
        LOG.debug("Found locale {}", variables[1]);
        request.setAttribute(LOCALE_ATTRIBUTE_NAME, variables[1]);
        String newUrl = StringUtils.removeStart(url, '/' + variables[1]);
        LOG.trace("Dispatching to new url \'{}\'", newUrl);
        RequestDispatcher dispatcher = request.getRequestDispatcher(newUrl);
        dispatcher.forward(request, response);
    } else {
        filterChain.doFilter(request, response);
    }
}

private boolean isLocale(String locale) {
    //validate the string here against an accepted list of locales or whatever
    try {
        LocaleUtils.toLocale(locale);
        return true;
    } catch (IllegalArgumentException e) {
        LOG.trace("Variable \'{}\' is not a Locale", locale);
    }
    return false;
}
}

拦截器与LocaleChangeInterceptor非常相似,它尝试从request属性获取语言环境,如果找到了语言环境,则将其设置为LocaleResolver.

The interceptor is very similar to the LocaleChangeInterceptor, it tries to get the locale from the request attribute and, if the locale is found, it sets it to the LocaleResolver.

public class LocaleAttributeChangeInterceptor extends HandlerInterceptorAdapter {
public static final String LOCALE_ATTRIBUTE_NAME = LocaleAttributeChangeInterceptor.class.getName() + ".LOCALE";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

    Object newLocale = request.getAttribute(LOCALE_ATTRIBUTE_NAME);
    if (newLocale != null) {
        LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
        if (localeResolver == null) {
            throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
        }
        localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale.toString()));
    }
    // Proceed in any case.
    return true;
}
}

一旦安装好它们,就需要配置Spring以使用拦截器和LocaleResolver.

Once you have them in place you need to configure Spring to use the interceptor and a LocaleResolver.

@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LocaleAttributeChangeInterceptor());
}

@Bean(name = "localeResolver")
public LocaleResolver getLocaleResolver() {
    return new CookieLocaleResolver();
}

并将过滤器添加到AbstractAnnotationConfigDispatcherServletInitializer.

@Override
protected Filter[] getServletFilters() {
    return new Filter[] { new PathVariableLocaleFilter() };
}

我还没有对其进行全面的测试,但是到目前为止它似乎可以正常工作,您不必触摸控制器即可接受{locale}路径变量,它应该可以直接使用.也许将来我们会使用语言环境作为路径变量/子文件夹" Spring automagic解决方案,因为越来越多的网站正在采用它,根据某些说法,它是

I haven't tested it thoroughly but it seems working so far and you don't have to touch your controllers to accept a {locale} path variable, it should just work out of the box. Maybe in the future we'll have 'locale as path variable/subfolder' Spring automagic solution as it seems more and more websites are adopting it and according to some it's the way to go.

这篇关于语言环境是Spring MVC中URL的一部分的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆