2010-07-11 8 views
12

Sto cercando un framework per applicazioni Web multilingue. Al momento mi sembra che la scelta migliore sia Spring MVC. Ma ho dovuto affrontare il fatto che tutte le linee guida per gli sviluppatori suggeriscono di cambiare lingue utilizzando LocaleChangeInterceptor in tal modo:Locale come parte dell'URL in Spring MVC

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

Purtroppo, ci sono una serie di motivi per cui vorrei evitare questo. Come posso rendere il codice della lingua una parte essenziale dell'URL? Per esempio:

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

Grazie.

UPD: Ho trovato la seguente soluzione. Non è ancora completo, ma funziona. La soluzione consiste in due parti: filtro servlet e bean resolver locale. Sembra un po 'hacker, ma non vedo altro modo per risolvere questo problema.

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

} 

Quindi non è necessario mappare le impostazioni locali in ciascuna azione nei controller. Il seguente esempio funziona bene:

@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 ... Hai lucidato questa soluzione? – user683887

+2

Mi chiedo anche se hai lucidato la soluzione –

+0

Non capisco come si imposta la locale nel metodo setLocale, si imposta un attributo nella richiesta solo se ho capito bene ... Potresti chiarire? – 0m4r

risposta

0

In primavera 3.0 È possibile dire al controller di cercare path variables. per esempio.

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

Ma in questo caso devo cambiare manualmente le impostazioni locali in ogni azione. È possibile effettuare una commutazione automatica, ad esempio utilizzando un filtro? – Gris

5

ho implementato qualcosa di molto simile utilizzando una combinazione di filtro e Interceptor.

Il filtro estrae la prima variabile di percorso e, se è un locale valido, lo imposta come attributo di richiesta, lo spoglia dall'inizio dell'URI richiesto e inoltra la richiesta al nuovo 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; 
} 
} 

L'intercettore è molto simile al LocaleChangeInterceptor, si cerca di ottenere il locale dall'attributo richiesta e, se si trova il locale, imposta al 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; 
} 
} 

Una volta che li hai in atto è necessario configurare primavera per utilizzare l'intercettore e LocaleResolver.

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

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

E aggiungere il filtro alla AbstractAnnotationConfigDispatcherServletInitializer.

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

non ho ancora testato a fondo ma sembra lavorare così lontano e non si deve toccare i controller di accettare una variabile {locale} percorso, dovrebbe funzionare out of the box. Forse in futuro avremo 'locale come percorso variabile/sottocartella' Spring soluzione automagic in quanto sembra che sempre più siti Web lo stanno adottando e secondo alcuni è the way to go.

+0

Bella soluzione. Avevo qualcosa di simile in mente mentre cercavo soluzioni già utilizzate.Il tuo approccio conferma che sono sulla strada giusta. –

+0

@Andrea come sarebbe 'RequestMapping' in' Controller'? – exexzian

+0

Questa è la soluzione migliore finora per le mie esigenze. Devo solo aggiungere un reindirizzamento quando non è specificata la lingua (quindi non ho più url per la stessa pagina), ma solo per i documenti, non per le risorse (che non cambiano con la lingua). – xtian

3

mi sono trovato nello stesso problema e dopo fare un sacco di ricerche ho finalmente riescono a farlo anche utilizzando un filtro e un LocaleResolver. Un passo per passo guida:

Prima impostare il filtro nel 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> 

Nel LocaleUrlFilter.java usiamo regex per:

  • aggiungere due attributi (il prefisso internazionale e Language) per la richiesta che faremo catturare in seguito alla LocaleResolver:
  • striscia la lingua dall'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() {} 
    } 
    

Ora il filtro ha iniettato alla richiesta due attributi che useremo per formare il Locale e rimosso la lingua dall'URL per elaborare correttamente le nostre richieste. Ora definiremo un LocaleResolver per cambiare la locale. Per quel primo modifichiamo il nostro servlet.xml di file:

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

E nel CustomLocaleResolver.java abbiamo impostato la lingua di conseguenza. Se non c'è Lingua nell'URL si procede con il metodo getLocale della richiesta:

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

} 

In questo modo non sarà necessario cambiare qualcosa nel vostro controller e visitare "/ it/home" sarà lo stesso come visitare "/ home" e utilizzare il file language_en.properties. Spero che lo aiuti

+0

Grazie per la risposta. Come sto usando le annotazioni e non la configurazione xml. Ho inserito questo @Bean (name = "localeResolver") public LocaleResolver getLocaleResolver() { return new CustomLocaleResolver(); } nel mio AppConfig invece di servlet.xml e web.xml – Black

2

Mi sono imbattuto in molto lo stesso problema di recente. Quindi mi piacerebbe avere un locale stateless non dipendente da sessione o cookie o altro che semplicemente URL.

ho cercato soluzioni filtro/intercettori/localeResolver suggerito in precedenti risposte ma queste non ideale mie esigenze, come ho avuto:

  • contenuti statici (immagini, ecc ..)
  • parti di pagina non dipende da locale (pannello di amministrazione)
  • RestController dentro stessa applicazione
  • multipart file di uploader

I volevo anche evitare i contenuti duplicati per motivi di SEO (in particolare, non voglio che i miei contenuti in inglese siano accessibili da entrambi i percorsi:/landingPage e/it/landingPage).

La soluzione che ha funzionato meglio per me è stata creare LanguageAwareController e quindi ereditarlo da tutti i controller che volevo supportare più locale.

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

uso in uno dei controllori:

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