2010-07-11 34 views
12

Tôi hiện đang tìm kiếm một khuôn khổ cho các ứng dụng web đa ngôn ngữ. Tại thời điểm này có vẻ như với tôi rằng sự lựa chọn tốt nhất là Spring MVC. Nhưng tôi phải đối mặt với thực tế là tất cả các hướng dẫn cho các nhà phát triển cho thấy để chuyển đổi ngôn ngữ sử dụng LocaleChangeInterceptor theo cách như vậy:Địa điểm là một phần của URL trong Spring MVC

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

Thật không may, có một số lý do tại sao tôi muốn tránh điều này. Làm thế nào tôi có thể làm cho mã ngôn ngữ trở thành một phần thiết yếu của URL? Ví dụ:

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

Cảm ơn.

UPD: Tôi đã tìm thấy giải pháp sau. Nó chưa hoàn chỉnh, nhưng hoạt động. Giải pháp bao gồm hai phần - bộ lọc servlet và bean resolver locale. Nó trông hơi chút hackish, nhưng tôi không thấy cách nào khác để giải quyết vấn đề này.

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

} 

Vì vậy, không cần phải ánh xạ miền địa phương trong mỗi hành động trong bộ điều khiển. Ví dụ sau đây sẽ hoạt động tốt:

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

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

} 
+0

Hi @Gris ... Bạn đã đánh bóng giải pháp này? – user683887

+2

Ngoài ra tự hỏi nếu bạn đã đánh bóng giải pháp –

+0

Tôi không hiểu cách bạn đặt ngôn ngữ trong phương thức setLocale, bạn chỉ đặt thuộc tính trong yêu cầu nếu tôi đã hiểu đúng ... Bạn có thể làm rõ? – 0m4r

Trả lời

0

Vào mùa xuân 3.0, bạn có thể yêu cầu bộ điều khiển tìm kiếm path variables. ví dụ.

@RequestMapping("/{locale}/action") 
public void action(@PathVariable String locale) { 
    ... 
} 
+4

Nhưng trong trường hợp này, tôi phải chuyển đổi miền địa phương theo cách thủ công trong mỗi hành động. Có thể thực hiện chuyển đổi tự động, ví dụ, sử dụng bộ lọc không? – Gris

5

Tôi đã triển khai một cái gì đó rất giống nhau bằng cách sử dụng kết hợp Bộ lọc và Bộ chặn. Bộ lọc trích xuất biến đường dẫn đầu tiên và nếu đó là một ngôn ngữ hợp lệ, nó sẽ đặt nó làm thuộc tính yêu cầu, tách nó ra khỏi đầu của URI được yêu cầu và chuyển tiếp yêu cầu tới URI mới.

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

Các đánh chặn là rất giống với LocaleChangeInterceptor, nó sẽ cố gắng để có được những miền địa phương từ các thuộc tính yêu cầu, và nếu miền địa phương được tìm thấy, nó đặt nó vào 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; 
} 
} 

Khi bạn đã đặt chúng vào vị trí bạn cần định cấu hình Spring để sử dụng thiết bị chặn và LocaleResolver.

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

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

Và thêm bộ lọc vào AbstractAnnotationConfigDispatcherServletInitializer.

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

tôi đã không kiểm tra nó kỹ lưỡng nhưng có vẻ như làm việc cho đến nay và bạn không cần phải chạm vào các bộ điều khiển của bạn để chấp nhận một biến {locale} con đường, nó chỉ nên làm việc ra khỏi hộp. Có lẽ trong tương lai chúng ta sẽ có 'locale như biến con đường/thư mục con' giải pháp automagic mùa xuân vì có vẻ như ngày càng có nhiều trang web đang áp dụng nó và theo một số là the way to go.

+0

Giải pháp tốt. Tôi đã có một cái gì đó tương tự trong tâm trí trong khi tôi đang tìm kiếm các giải pháp đã được sử dụng.Cách tiếp cận của bạn xác nhận rằng tôi đang đi đúng hướng. –

+0

@Andrea sẽ là 'RequestMapping' trong' Controller' như thế nào? – exexzian

+0

Đây là giải pháp tốt nhất cho đến nay cho các yêu cầu của tôi. Tôi chỉ cần thêm chuyển hướng khi không có ngôn ngữ được chỉ định (vì vậy tôi không có nhiều url cho cùng một trang), nhưng chỉ cho tài liệu chứ không phải cho tài nguyên (không thay đổi bằng ngôn ngữ). – xtian

3

Tôi thấy mình trong cùng một vấn đề và sau khi thực hiện rất nhiều nghiên cứu, cuối cùng tôi cũng có thể quản lý nó bằng cách sử dụng Bộ lọc và LocaleResolver. Bước để được hướng dẫn từng bước:

Trước tiên hãy đặt Bộ lọc trong 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> 

Trong LocaleUrlFilter.java chúng tôi sử dụng regex để:

  • thêm hai thuộc tính (Mã quốc gia và Mã ngôn ngữ) để yêu cầu rằng chúng tôi sẽ chụp sau trên LocaleResolver:
  • dải ngôn ngữ từ url

    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() {} 
    } 
    

Bây giờ bộ lọc được tiêm vào yêu cầu hai thuộc tính mà chúng ta sẽ sử dụng để tạo Locale và tước ngôn ngữ khỏi url để xử lý chính xác yêu cầu của chúng ta. Bây giờ chúng ta sẽ định nghĩa một LocaleResolver để thay đổi ngôn ngữ. Cho rằng đầu tiên chúng ta sửa đổi servlet.xml của chúng tôi file:

<!-- locale Resolver configuration--> 
<bean id="localeResolver" class="yourCompleteRouteToTheResolver.CustomLocaleResolver"></bean> 

Và trong CustomLocaleResolver.java chúng ta cài đặt ngôn ngữ cho phù hợp. Nếu không có ngôn ngữ trong url chúng tôi tiến hành sử dụng phương pháp getLocale được yêu cầu:

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

} 

Việc làm này bạn sẽ không cần phải thay đổi bất cứ điều gì trong bộ điều khiển của bạn và quý khách đến thăm "/ en/home" sẽ giống nhau khi truy cập "/ home" và sử dụng tệp language_en.properties của bạn. Hy vọng điều này sẽ giúp

+0

Cảm ơn câu trả lời. Vì tôi đang sử dụng chú thích chứ không phải cấu hình xml. Tôi đã chèn số này @Bean (name = "localeResolver") công khai LocaleResolver getLocaleResolver() { trả về CustomLocaleResolver mới(); } vào AppConfig của tôi thay vì servlet.xml và web.xml – Black

2

Gần đây, tôi đã gặp phải vấn đề rất giống nhau. Vì vậy, tôi muốn có miền địa phương không phụ thuộc vào phiên họp hoặc cookie hoặc bất cứ điều gì khác hơn là chỉ URL.

tôi đã cố gắng giải pháp lọc/chặn/localeResolver gợi ý trong câu trả lời trước tuy nhiên những không phù hợp với nhu cầu của tôi như tôi đã có:

  • nội dung tĩnh (hình ảnh vv ..)
  • phần của trang không phụ thuộc Locale (bảng quản trị)
  • RestController bên trong cùng một ứng dụng
  • nhiều phần dữ liệu tập tin tải lên

tôi cũng muốn tránh trùng lặp nội dung vì lý do SEO (Đặc biệt tôi không muốn nội dung tiếng Anh của mình có thể truy cập từ cả hai đường dẫn:/landingPage và/en/landingPage).

Giải pháp hiệu quả nhất đối với tôi là tạo LanguageAwareController và kế thừa từ nó trong tất cả các trình điều khiển mà tôi muốn hỗ trợ nhiều miền địa phương.

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

Cách sử dụng thuộc một trong các bộ điều khiển:

@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"; 
    } 
} 
Các vấn đề liên quan