我现在正在寻找多语言网络应用程序的框架。目前在我看来,最好的选择是Spring MVC。但我面对这样一个事实,即开发人员的所有指南建议使用LocaleChangeInterceptor以这种方式切换语言:
http://www.somesite.com/action/?locale=en
不幸的是,我想避免这种情况有很多原因。我怎样才能使语言代码成为URL的重要组成部分?例如:
http://www.somesite.com/en/action
感谢。
UPD:我找到了以下解决方案。它尚未完成,但有效。解决方案包括两部分 - 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
@RequestMapping("/test")
public class TestController
{
@RequestMapping("action")
public ModelAndView action(HttpServletRequest request, HttpServletResponse response)
{
ModelAndView mav = new ModelAndView("test/action");
...
return mav;
}
}
答案 0 :(得分:6)
我使用Filter和Interceptor的组合实现了非常相似的东西。
过滤器提取第一个路径变量,如果它是一个有效的语言环境,则将其设置为请求属性,将其从请求的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() };
}
我还没有对它进行彻底的测试,但它似乎工作到目前为止并且您不必触摸控制器以接受{locale}
路径变量,它应该只是开箱即用。也许在将来我们将“locale”作为路径变量/子文件夹&#39; Spring自动化解决方案似乎越来越多的网站正在采用它,并根据它的the way to go {<3}}。
答案 1 :(得分:4)
我发现自己遇到了同样的问题,经过大量研究后我终于设法使用了Filter和LocaleResolver。步骤指南的步骤:
首先在 web.xml 中设置过滤器:
<filter>
<filter-name>LocaleFilter</filter-name>
<filter-class>yourCompleteRouteToTheFilter.LocaleUrlFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>LocaleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
在 LocaleUrlFilter.java 中,我们使用正则表达式:
从网址中删除语言
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class LocaleUrlFilter implements Filter{
private static final Pattern localePattern = Pattern.compile("^/([a-z]{2})(?:/([a-z]{2}))?(/.*)?");
public static final String COUNTRY_CODE_ATTRIBUTE_NAME = LocaleUrlFilter.class.getName() + ".country";
public static final String LANGUAGE_CODE_ATTRIBUTE_NAME = LocaleUrlFilter.class.getName() + ".language";
@Override
public void init(FilterConfig arg0) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String url = request.getRequestURI().substring(request.getContextPath().length());
Matcher matcher = localePattern.matcher(url);
if (matcher.matches()) {
// Set the language attributes that we will use in LocaleResolver and strip the language from the url
request.setAttribute(COUNTRY_CODE_ATTRIBUTE_NAME, matcher.group(1));
request.setAttribute(LANGUAGE_CODE_ATTRIBUTE_NAME, matcher.group(2));
request.getRequestDispatcher(matcher.group(3) == null ? "/" : matcher.group(3)).forward(servletRequest, servletResponse);
}
else filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {}
}
现在,过滤器向请求注入了两个属性,我们将使用它们来构建Locale并从url中删除语言以正确处理我们的请求。现在我们将定义一个LocaleResolver来更改语言环境。首先,我们修改 servlet.xml 文件:
<!-- locale Resolver configuration-->
<bean id="localeResolver" class="yourCompleteRouteToTheResolver.CustomLocaleResolver"></bean>
在CustomLocaleResolver.java中,我们相应地设置了语言。如果网址中没有语言,我们将继续使用请求的getLocale方法:
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.LocaleResolver;
/*
* Set the Locale defined in the LocaleUrlFiltes. If none is defined (in the url) return the request locale.
*/
public class CustomLocaleResolver implements LocaleResolver{
@Override
public Locale resolveLocale(HttpServletRequest servletRequest) {
final String countryCode = (String)servletRequest.getAttribute(LocaleUrlFilter.COUNTRY_CODE_ATTRIBUTE_NAME);
if (countryCode != null) {
String languageCode = (String)servletRequest.getAttribute(LocaleUrlFilter.LANGUAGE_CODE_ATTRIBUTE_NAME);
if (languageCode == null) {
return new Locale(countryCode);
}
return new Locale(languageCode, countryCode);
}
return servletRequest.getLocale();
}
@Override
public void setLocale(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse, final Locale locale) {
throw new UnsupportedOperationException();
}
}
这样做你不需要更改控制器中的任何内容并访问&#34; / en / home&#34;将与访问&#34; / home&#34;相同并使用您的language_en.properties文件。希望它有所帮助
答案 2 :(得分:3)
我最近遇到了同样的问题。所以我希望无状态语言环境不依赖于会话或cookie或其他任何东西而不仅仅是URL。
我尝试过以前的答案中提出的过滤器/拦截器/ localeResolver解决方案但是这些解决方案并不符合我的需求:
我还希望避免重复内容出于搜索引擎优化的原因(特别是我不希望我的英文内容可以从两个路径访问:/ landingPage和/ en / landingPage)。
最适合我的解决方案是创建LanguageAwareController,然后在我想要支持多个语言环境的所有控制器中继承它。
@Controller
@RequestMapping(path = "/{lang}")
public class LanguageAwareController {
@Autowired
LocaleResolver localeResolver;
@ModelAttribute(name = "locale")
Locale getLocale(@PathVariable(name = "lang") String lang, HttpServletRequest request,
HttpServletResponse response){
Locale effectiveLocale = Arrays.stream(Locale.getAvailableLocales())
.filter(locale -> locale.getLanguage().equals(lang))
.findFirst()
.orElseGet(Locale::getDefault);
localeResolver.setLocale(request, response, effectiveLocale);
return effectiveLocale;
}
}
在一个控制器中使用:
@Controller
public class LandingPageController extends LanguageAwareController{
private Log log = LogFactory.getLog(LandingPageController.class);
@GetMapping("/")
public String welcomePage(Locale locale, @PathVariable(name = "lang") String lang ){
log.info(lang);
log.info(locale);
return "landing";
}
}
答案 3 :(得分:1)
除了提供的答案之外,这是一种方法,该方法如何通过实现ILinkBuilder
让Thymeleaf在上下文路径之后的路径中自动添加语言环境:
@Bean
public ILinkBuilder pathVariableLocaleLinkBuilder() {
PathVariableLocaleLinkBuilder pathVariableLocaleLinkBuilder = new PathVariableLocaleLinkBuilder();
pathVariableLocaleLinkBuilder.setOrder(1);
return pathVariableLocaleLinkBuilder;
}
@Bean
SpringTemplateEngine templateEngine(ThymeleafProperties properties, ObjectProvider<ITemplateResolver> templateResolvers, ObjectProvider<IDialect> dialects, ObjectProvider<ILinkBuilder> linkBuilders) {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setEnableSpringELCompiler(properties.isEnableSpringElCompiler());
engine.setRenderHiddenMarkersBeforeCheckboxes(properties.isRenderHiddenMarkersBeforeCheckboxes());
templateResolvers.orderedStream().forEach(engine::addTemplateResolver);
dialects.orderedStream().forEach(engine::addDialect);
linkBuilders.orderedStream().forEach(engine::addLinkBuilder);
return engine;
}
这是LinkBuilder本身:
public class PathVariableLocaleLinkBuilder extends AbstractLinkBuilder {
@Autowired
private LocaleResolver localeResolver;
@Override
public String buildLink(IExpressionContext context, String base, Map<String, Object> parameters) {
Validate.notNull(context, "Expression context cannot be null");
if (base == null) {
return null;
}
if (!isLinkBaseContextRelative(base)) {
return base;
}
if (!(context instanceof IWebContext)) {
throw new TemplateProcessingException(
"Link base \"" + base + "\" cannot be context relative (/...) unless the context " +
"used for executing the engine implements the " + IWebContext.class.getName() + " interface");
}
final HttpServletRequest request = ((IWebContext) context).getRequest();
return "/" + localeResolver.resolveLocale(request) + base;
}
private static boolean isLinkBaseContextRelative(final CharSequence linkBase) {
if (linkBase.length() == 0 || linkBase.charAt(0) != '/') {
return false;
}
return linkBase.length() == 1 || linkBase.charAt(1) != '/';
}
}
答案 4 :(得分:0)
在Spring 3.0中,您可以告诉控制器查找path variables。 e.g。
@RequestMapping("/{locale}/action")
public void action(@PathVariable String locale) {
...
}