Bem-vindo ao tutorial de Internacionalização (i18n) do Spring. Qualquer aplicação web com usuários de todo o mundo, a internacionalização (i18n) ou localização (L10n) é muito importante para uma melhor interação do usuário. A maioria dos frameworks de aplicação web oferece maneiras fáceis de localizar a aplicação com base nas configurações locais do usuário. O Spring também segue esse padrão e oferece suporte extensivo para internacionalização (i18n) por meio do uso de interceptadores do Spring, resolvedores de local e pacotes de recursos para diferentes localidades. Alguns artigos anteriores sobre i18n em java.
Internacionalização i18n do Spring
Vamos criar um projeto simples Spring MVC onde utilizaremos parâmetros de solicitação para obter a localidade do usuário e, com base nisso, definiremos os valores dos rótulos da página de resposta a partir de pacotes de recursos específicos da localidade. Crie um projeto Spring MVC no Spring Tool Suite para ter o código base de nossa aplicação. Se você não estiver familiarizado com o Spring Tool Suite ou projetos Spring MVC, por favor, leia Exemplo Spring MVC. Nosso projeto final com alterações de localização se parece com a imagem abaixo. Vamos analisar todas as partes da aplicação uma por uma.
Configuração do Maven para Spring i18n
O pom.xml do nosso Spring MVC parece o seguinte.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev</groupId>
<artifactId>spring</artifactId>
<name>Springi18nExample</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
Grande parte do código é gerado automaticamente pelo STS, exceto que atualizei a versão do Spring para a mais recente, que é a 4.0.2.RELEASE. Podemos remover dependências ou atualizar as versões de outras dependências também, mas as deixei como estão para simplicidade.
Pacote de Recursos do Spring
Para simplificar, vamos supor que nossa aplicação suporte apenas dois locais – inglês e francês. Se nenhum local do usuário for especificado, usaremos o inglês como local padrão. Vamos criar pacotes de recursos do Spring para ambos esses locais que serão usados na página JSP. Código messages_en.properties:
label.title=Login Page
label.firstName=First Name
label.lastName=Last Name
label.submit=Login
Código messages_fr.properties:
label.title=Connectez-vous page
label.firstName=Pr\u00E9nom
label.lastName=Nom
label.submit=Connexion
Observe que estou usando unicode para caracteres especiais nos pacotes de recursos do local em francês, para que seja interpretado corretamente no HTML de resposta enviado para as solicitações do cliente. Outro ponto importante a observar é que ambos os pacotes de recursos estão no classpath da aplicação e seus nomes seguem o padrão “messages_{local}.properties”. Veremos por que isso é importante mais tarde.
Classe do Controlador Spring i18n
Nossa classe de controlador é muito simples, ela apenas registra o local do usuário e retorna a página home.jsp como resposta.
package com.journaldev.spring;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
return "home";
}
}
Página JSP Spring i18n
O código de nossa página home.jsp se parece com o seguinte.
<%@taglib uri="https://www.springframework.org/tags" prefix="spring"%>
<%@ page session="false"%>
<html>
<head>
<title><spring:message code="label.title" /></title>
</head>
<body>
<form method="post" action="login">
<table>
<tr>
<td><label> <strong><spring:message
code="label.firstName" /></strong>
</label></td>
<td><input name="firstName" /></td>
</tr>
<tr>
<td><label> <strong><spring:message
code="label.lastName" /></strong>
</label></td>
<td><input name="lastName" /></td>
</tr>
<tr>
<spring:message code="label.submit" var="labelSubmit"></spring:message>
<td colspan="2"><input type="submit" value="${labelSubmit}" /></td>
</tr>
</table>
</form>
</body>
</html>
A única parte que vale a pena mencionar é o uso de spring:message para recuperar a mensagem com o código fornecido. Certifique-se de que as bibliotecas de tags do Spring estejam configuradas usando taglib jsp directive. O Spring se encarrega de carregar os pacotes de recursos apropriados e disponibilizá-los para as páginas JSP usarem.
Spring Internacionalização i18n – Arquivo de Configuração Bean
O arquivo de configuração Bean do Spring é o lugar onde toda a magia acontece. Esta é a beleza do framework Spring, pois nos ajuda a focar mais na lógica de negócios em vez de codificar para tarefas triviais. Vamos ver como nosso arquivo de configuração bean do spring parece e vamos olhar para cada um dos beans um por um. Código do servlet-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="classpath:messages" />
<beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>
<beans:bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<beans:property name="defaultLocale" value="en" />
<beans:property name="cookieName" value="myAppLocaleCookie"></beans:property>
<beans:property name="cookieMaxAge" value="3600"></beans:property>
</beans:bean>
<interceptors>
<beans:bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<beans:property name="paramName" value="locale" />
</beans:bean>
</interceptors>
<context:component-scan base-package="com.journaldev.spring" />
</beans:beans>
-
annotation-driven habilita o modelo de programação do Controller, sem ele, o Spring não reconhecerá nosso HomeController como manipulador de solicitações do cliente.
-
context:component-scan fornece o pacote onde o Spring procurará pelos componentes anotados e os registrará automaticamente como bean do Spring.
-
O bean messageSource é configurado para habilitar a internacionalização (i18n) para nossa aplicação. A propriedade basename é usada para fornecer a localização dos pacotes de recursos.
classpath:messages
significa que os pacotes de recursos estão localizados no classpath e seguem o padrão de nome comomessages_{locale}.properties
. A propriedade defaultEncoding é usada para definir a codificação usada para as mensagens. -
O bean localeResolver do tipo
org.springframework.web.servlet.i18n.CookieLocaleResolver
é usado para definir um cookie na solicitação do cliente para que solicitações posteriores possam reconhecer facilmente o locale do usuário. Por exemplo, podemos pedir ao usuário para selecionar o locale quando ele iniciar a aplicação web pela primeira vez e, com o uso do cookie, podemos identificar o locale do usuário e enviar automaticamente uma resposta específica do locale. Também podemos especificar o locale padrão, o nome do cookie e a idade máxima do cookie antes que ele expire e seja excluído pelo navegador do cliente. Se sua aplicação mantém sessões de usuário, então você também pode usarorg.springframework.web.servlet.i18n.SessionLocaleResolver
como localeResolver para usar um atributo locale na sessão do usuário. A configuração é semelhante ao CookieLocaleResolver.<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> <property name="defaultLocale" value="en" /> </bean>
Se não registrarmos nenhum “localeResolver”, AcceptHeaderLocaleResolver será usado por padrão, o qual resolve o locale do usuário verificando o cabeçalho
accept-language
na solicitação HTTP do cliente. -
O interceptor
org.springframework.web.servlet.i18n.LocaleChangeInterceptor
está configurado para interceptar a solicitação do usuário e identificar o idioma do usuário. O nome do parâmetro é configurável, e estamos utilizando o nome do parâmetro da solicitação como “locale”. Sem este interceptor, não conseguiremos alterar o idioma do usuário e enviar a resposta com base nas novas configurações de idioma do usuário. Ele precisa fazer parte do elemento interceptors, caso contrário, o Spring não o configurará como um interceptor.
Se você estiver se perguntando sobre a configuração que diz ao framework Spring para carregar nossas configurações de contexto, ela está presente no descritor de implantação de nossa aplicação MVC.
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
Podemos alterar a localização ou nome do arquivo de contexto alterando a configuração do web.xml. Nosso aplicativo Spring i18n está pronto, basta implantá-lo em qualquer contêiner de servlet. Normalmente, eu o exporto como um arquivo WAR no diretório webapps de um servidor web Tomcat independente. Aqui estão as capturas de tela da página inicial do nosso aplicativo com diferentes configurações regionais. Página Inicial Padrão (configuração regional en): Passando a configuração regional como parâmetro (configuração regional fr):
Mais solicitações sem configuração regional:
Como você pode ver na imagem acima, não estamos passando informações de configuração regional na solicitação do cliente, mas mesmo assim nosso aplicativo identifica a configuração regional do usuário. Você deve ter percebido até agora que isso ocorre por causa do bean CookieLocaleResolver que configuramos em nosso arquivo de configuração de beans do Spring. No entanto, você pode verificar os dados dos cookies do seu navegador para confirmar isso. Estou usando o Chrome e a imagem abaixo mostra os dados dos cookies armazenados pelo aplicativo.
Observe que o tempo de expiração do cookie é de uma hora, ou seja, 3600 segundos, conforme configurado pela propriedade cookieMaxAge. Se você verificar os logs do servidor, verá que a configuração regional está sendo registrada.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is en.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is fr.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is fr.
Aqui está tudo para o exemplo de aplicação de i18n do Spring, baixe o projeto de exemplo no link abaixo e brinque com ele para aprender mais.