sexta-feira, 30 de setembro de 2016

Internacionalização no Spring-Boot

Conforme prometido, vamos falar sobre I18n no Spring-Boot. Vamos utilizar o mesmo projeto das postagens anteriores:
http://toxinavirtual.blogspot.com.br/2016/09/crud-com-rest-spring-boot-e-jpa.html

Vamos criar os arquivos com as labels em Inglês, Espanhol e Português em arquivos diferentes com as diferentes traduções. Os arquivos deverão ficar dentro de uma pasta nova chamada de i18n que será criada dentro de resources. Os arquivos são de texto do tipo .properties.


No arquivo messages_en.properties, o conteúdo:
  1. field1 = My Field 1
  2. field2 = My Field 2



No arquivo messages_es.properties, o conteúdo:
  1. field1 = El Campo 1
  2. field2 = El Campo 2



No arquivo messages_pt.properties, o conteúdo:
  1. field1 = Meu Campo 1
  2. field2 = Meu Campo 2


Vamos criar agora o template hello.html dentro da pasta resources/template. O arquivo dever[a conter o seguinte:
  1. <!DOCTYPE html>
  2. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
  3. <head lang="en">
  4.     <meta charset="UTF-8" />
  5.     <title>HELLO</title>
  6. </head>
  7. <body>
  8.     <div>
  9.         <label for="field1" th:text="#{field1}"></label>
  10.         <input type="text" id="field1" name="field1" />
  11.     </div>
  12.     <div>
  13.         <label for="field2" th:text="#{field2}"></label>
  14.         <input type="text" id="field2" name="field2" />
  15.     </div>
  16.     <div>
  17.         <button type="submit">Save</button>
  18.     </div>
  19. </body>
  20. </html>

Repare que no template temos os campos em variáveis delimitadas por "#{}". O texto com o nome da variável deverá ser substituído pelas traduções dos arquivos properties.

O arquivo de template está usando um gerenciador de templates para que o Spring-Boot consiga interpretar o arquivo. Será necessário adicionar a dependência dentro do arquivo pom.xml.


  1.         <dependency>
  2.             <groupId>org.springframework.boot</groupId>
  3.             <artifactId>spring-boot-starter-thymeleaf</artifactId>
  4.         </dependency>



O template também depende de um controlador que determine a rota de acesso.

  1. package com.example.controller;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.ui.Model;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. @Controller
  6. public class Hello {
  7.     @RequestMapping("/hello")
  8.     public String gohome(Model model){
  9.         return "hello";
  10.     }
  11.    
  12. }


A classe mapeada para o endereço /hello deverá retornar o nome do template sem a necessidade a da extensão .html. O conteúdo do arquivo será exibido, mas por enquanto sem a tradução.



Para implementar a tradução precisaremos extender o arquivo de inicialização para a classe WebMvcConfigurerAdapter. Como o arquivo de inicialização está extendendo o deploy criaremos um novo arquivo de configuração que funcionará paralelamente.


Com as anotations @Configuration, @EnableAutoConfiguration e @ComponentScan o Spring-Boot já saberá que se trata de um arquivo de configuração que possui componentes de configuração.


Primeiramente, criaremos um Locale. O Locale determina a linguagem através da região em que o site é acessado, assim como ajuda na configuração da tradução. O Locale geralmente fica armazenado na sessão do sistema.

  1.     //I18n do pacote web.servlet
  2.     @Bean
  3.     public LocaleResolver localeResolver() {
  4.         SessionLocaleResolver slr = new SessionLocaleResolver();
  5.         slr.setDefaultLocale(Locale.US);
  6.         return slr;
  7.     }

Agora criamos um interceptador do parâmetro de linguagem.
  1.     @Bean
  2.     public LocaleChangeInterceptor localeChangeInterceptor() {
  3.         LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
  4.         lci.setParamName("lang");
  5.         return lci;
  6.     }
  7.     //I18n
  8.     @Override
  9.     public void addInterceptors(InterceptorRegistry registry) {
  10.         registry.addInterceptor(localeChangeInterceptor());
  11.     }


Em seguida criamos um MessageSource e definimos o arquivo de origem da mensagem. O Validator vai executar a troca.

  1.     @Bean
  2.     public MessageSource messageSource() {
  3.         final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
  4.         messageSource.setBasename("classpath:/i18n/messages");
  5.         messageSource.setUseCodeAsDefaultMessage(true);
  6.         messageSource.setDefaultEncoding("UTF-8");
  7.         return messageSource;
  8.     }
  9.    
  10.     @Override
  11.     public Validator getValidator() {
  12.         LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
  13.         validator.setValidationMessageSource(messageSource() );
  14.         return validator;
  15.     }
  16. }

Segue o arquivo na integra:
  1. package com.example.config;
  2. import java.util.Locale;
  3. import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  4. import org.springframework.context.MessageSource;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.ComponentScan;
  7. import org.springframework.context.annotation.Configuration;
  8. import org.springframework.context.support.ReloadableResourceBundleMessageSource;
  9. import org.springframework.validation.Validator;
  10. import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
  11. import org.springframework.web.servlet.LocaleResolver;
  12. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  13. import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
  14. import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
  15. import org.springframework.web.servlet.i18n.SessionLocaleResolver;
  16. @Configuration
  17. @EnableAutoConfiguration
  18. @ComponentScan
  19. class ApplicationConfig extends WebMvcConfigurerAdapter  {
  20.    
  21.     //I18n do pacote web.servlet
  22.     @Bean
  23.     public LocaleResolver localeResolver() {
  24.         SessionLocaleResolver slr = new SessionLocaleResolver();
  25.         slr.setDefaultLocale(Locale.US);
  26.         return slr;
  27.     }
  28.    
  29.     //I18n
  30.     @Bean
  31.     public LocaleChangeInterceptor localeChangeInterceptor() {
  32.         LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
  33.         lci.setParamName("lang");
  34.         return lci;
  35.     }
  36.     //I18n
  37.     @Override
  38.     public void addInterceptors(InterceptorRegistry registry) {
  39.         registry.addInterceptor(localeChangeInterceptor());
  40.     }
  41.     @Bean
  42.     public MessageSource messageSource() {
  43.         final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
  44.         messageSource.setBasename("classpath:/i18n/messages");
  45.         messageSource.setUseCodeAsDefaultMessage(true);
  46.         messageSource.setDefaultEncoding("UTF-8");
  47.         return messageSource;
  48.     }
  49.    
  50.     @Override
  51.     public Validator getValidator() {
  52.         LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
  53.         validator.setValidationMessageSource(messageSource() );
  54.         return validator;
  55.     }
  56. }

Segue a imagem da label funcionando.





 É possível ainda melhorar o projeto de várias formas, como definir a liguagem na sessão antes do login.

Mais tarde atualizo o post com o código do github.