url - 语言环境作为 Spring MVC 中 URL 的一部分

标签 url spring-mvc locale

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

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

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

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

谢谢。

更新:我找到了以下解决方案。它还没有完成,但是可以工作。解决方案由两部分组成 - servlet 过滤器和语言环境解析器 bean。它看起来有点骇人听闻,但我看不到解决此问题的其他方法。

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);
    }

}

因此,无需在 Controller 的每个操作中映射语言环境。以下示例将正常工作:
@Controller
@RequestMapping("/test")
public class TestController
{

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

}

最佳答案

我使用过滤器和拦截器的组合实现了一些非常相似的东西。

过滤器提取第一个路径变量,如果它是有效的语言环境,则将其设置为请求属性,将其从请求的 URI 的开头剥离并将请求转发到新的 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 非常相似,它会尝试从请求属性中获取语言环境,如果找到了语言环境,它会将其设置为 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 .
@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() };
}

我还没有彻底测试它,但它似乎到目前为止工作,你不必触摸你的 Controller 来接受 {locale}路径变量,它应该开箱即用。也许在 future 我们会有'语言环境作为路径变量/子文件夹' Spring automagic 解决方案,因为似乎越来越多的网站正在采用它,并且根据一些它是 the way to go .

关于url - 语言环境作为 Spring MVC 中 URL 的一部分,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3221632/

相关文章:

javascript - 如果 URL 具有哈希值,则强制 Firefox 和 Edge 重新加载页面

java - 下载 Spring MVC 生成的文件在 IE 中不起作用

java - 以下代码中 Spring Bean 的工作 - 如何保存/管理 Bean 属性?

php - 如何在 Zend 中缩短 URL

java - 如何使用java从查询字符串中读取参数和值?

c# - 将字符串附加到 URL

spring - 使用纯Java配置且没有web.xml开发spring mvc应用程序时如何设置欢迎文件?

conditional - Thymeleaf 将#locale 表达式对象与字符串进行比较

html - 将 html 实体解码为中文字符

docker - 如何在 Docker Alpine 中设置语言环境?