Bienvenido al tutorial de Internacionalización de Spring (i18n). Cualquier aplicación web con usuarios de todo el mundo, la internacionalización (i18n) o la localización (L10n) son muy importantes para una mejor interacción con el usuario. La mayoría de los marcos de aplicación web proporcionan formas sencillas de localizar la aplicación según la configuración regional del usuario. Spring también sigue este patrón y brinda un amplio soporte para la internacionalización (i18n) mediante el uso de interceptores de Spring, resolutores de localización y paquetes de recursos para diferentes configuraciones regionales. Algunos artículos anteriores sobre i18n en Java.
Internacionalización de Spring i18n
Vamos a crear un proyecto simple de Spring MVC donde usaremos el parámetro de solicitud para obtener la configuración regional del usuario y, en función de eso, establecer los valores de etiqueta de página de respuesta desde los paquetes de recursos específicos de la configuración regional. Cree un Proyecto Spring MVC en la Spring Tool Suite para tener el código base para nuestra aplicación. Si no está familiarizado con Spring Tool Suite o los Proyectos Spring MVC, por favor lea Spring MVC Example. Nuestro proyecto final con cambios de localización se ve como la imagen debajo. Analizaremos todas las partes de la aplicación una por una.
Configuración de Maven Spring i18n
Nuestro archivo pom.xml de Spring MVC se ve como el siguiente.
<?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>
La mayoría del código es generado automáticamente por STS, excepto que he actualizado la versión de Spring para usar la última versión, que es 4.0.2.RELEASE. Podemos eliminar dependencias o actualizar las versiones de otras dependencias también, pero las he dejado tal como están por simplicidad.
Paquete de Recursos de Spring
Para simplificar, vamos a suponer que nuestra aplicación solo admite dos locales: inglés y francés. Si no se especifica ningún locale de usuario, utilizaremos el inglés como locale predeterminado. Creemos paquetes de recursos de Spring para ambos locales que se utilizarán en la página JSP. Código de messages_en.properties: `
label.title=Login Page
label.firstName=First Name
label.lastName=Last Name
label.submit=Login
` Código de messages_fr.properties: `
label.title=Connectez-vous page
label.firstName=Pr\u00E9nom
label.lastName=Nom
label.submit=Connexion
`. Ten en cuenta que estoy utilizando Unicode para caracteres especiales en los paquetes de recursos del locale francés, para que se interpreten correctamente en el HTML de respuesta enviado a las solicitudes del cliente. Otro punto importante a tener en cuenta es que ambos paquetes de recursos están en el classpath de la aplicación y su nombre sigue el patrón “messages_{locale}.properties”. Veremos por qué son importantes más adelante. `
`
`Clase Controladora de Spring i18n`
` `
`
`
Nuestra clase controladora es muy simple, simplemente registra el locale del usuario y devuelve la página home.jsp como respuesta. `
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 de Spring i18n`
` `
`
`
El código de nuestra página home.jsp se ve así.
<%@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>
La única parte que vale la pena mencionar es el uso de spring:message para recuperar el mensaje con el código proporcionado. Asegúrate de que las bibliotecas de etiquetas de Spring estén configuradas utilizando taglib jsp directive. Spring se encarga de cargar los mensajes del paquete de recursos apropiado y ponerlos a disposición de las páginas JSP para su uso.
Spring Internationalization i18n – Archivo de configuración de Beans
El archivo de configuración de Beans de Spring es el lugar donde ocurre toda la magia. Esta es la belleza del framework Spring, ya que nos ayuda a enfocarnos más en la lógica empresarial en lugar de programar tareas triviales. Veamos cómo se ve nuestro archivo de configuración de beans de Spring y analizaremos cada uno de los beans uno por uno. Código 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 permite el modelo de programación del controlador; sin él, Spring no reconocerá nuestro HomeController como controlador para las solicitudes del cliente.
-
context:component-scan especifica el paquete donde Spring buscará los componentes anotados y los registrará automáticamente como beans de Spring.
-
messageSource está configurado como bean para habilitar la internacionalización en nuestra aplicación. La propiedad basename se utiliza para proporcionar la ubicación de los paquetes de recursos.
classpath:messages
significa que los paquetes de recursos están ubicados en el classpath y siguen el patrón de nombres comomessages_{locale}.properties
. La propiedad defaultEncoding se utiliza para definir la codificación utilizada para los mensajes. -
El bean
localeResolver
del tipoorg.springframework.web.servlet.i18n.CookieLocaleResolver
se utiliza para establecer una cookie en la solicitud del cliente para que las solicitudes posteriores puedan reconocer fácilmente la configuración regional del usuario. Por ejemplo, podemos pedir al usuario que seleccione la configuración regional cuando lance la aplicación web por primera vez y, con el uso de la cookie, podemos identificar la configuración regional del usuario y enviar automáticamente una respuesta específica de esa configuración regional. También podemos especificar la configuración regional predeterminada, el nombre de la cookie y la edad máxima de la cookie antes de que expire y sea eliminada por el navegador del cliente. Si su aplicación mantiene sesiones de usuario, entonces también puede utilizarorg.springframework.web.servlet.i18n.SessionLocaleResolver
como localeResolver para utilizar un atributo de configuración regional en la sesión del usuario. La configuración es similar a CookieLocaleResolver.<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> <property name="defaultLocale" value="en" /> </bean>
Si no registramos ningún “localeResolver”, por defecto se utilizará AcceptHeaderLocaleResolver, que resuelve la configuración regional del usuario comprobando la cabecera
accept-language
en la solicitud HTTP del cliente. -
El interceptor
org.springframework.web.servlet.i18n.LocaleChangeInterceptor
está configurado para interceptar la solicitud del usuario e identificar la configuración regional del usuario. El nombre del parámetro es configurable y estamos utilizando el nombre del parámetro de solicitud para la configuración regional como “locale”. Sin este interceptor, no podremos cambiar la configuración regional del usuario y enviar la respuesta basada en la nueva configuración regional del usuario. Debe ser parte del elemento interceptors de lo contrario Spring no lo configurará como un interceptor.
Si te preguntas acerca de la configuración que le indica al framework Spring que cargue nuestras configuraciones de contexto, está presente en el descriptor de implementación de nuestra aplicación 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 cambiar la ubicación o el nombre del archivo de contexto cambiando la configuración web.xml. Nuestra aplicación Spring i18n está lista, simplemente despliéguela en cualquier contenedor de servlets. Por lo general, lo exporto como archivo WAR en el directorio webapps de un servidor web tomcat independiente. Aquí están las capturas de pantalla de la página de inicio de nuestra aplicación con diferentes configuraciones regionales. Página de inicio predeterminada (configuración en inglés): Pasando la configuración regional como parámetro (configuración en francés):
Solicitudes adicionales sin configuración regional:
Como puedes ver en la imagen anterior, no estamos pasando información de configuración regional en la solicitud del cliente, pero aún así nuestra aplicación identifica la configuración regional del usuario. Seguramente habrás deducido que se debe al bean CookieLocaleResolver que configuramos en nuestro archivo de configuración de beans de Spring. Sin embargo, puedes verificar los datos de las cookies de tu navegador para confirmarlo. Estoy usando Chrome y la siguiente imagen muestra los datos de las cookies almacenados por la aplicación.
Observa que el tiempo de caducidad de la cookie es de una hora, es decir, 3600 segundos, según lo configurado por la propiedad cookieMaxAge. Si revisas los registros del servidor, verás que se registra la configuración regional.
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.
Eso es todo para el ejemplo de aplicación Spring i18n, descarga el proyecto de ejemplo desde el siguiente enlace y juega con él para aprender más.