语言环境是Spring MVC中URL的一部分 [英] Locales as part of the URL in Spring MVC
问题描述
我现在正在寻找用于多语言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屋!