Internazionalizzazione (i18n) e localizzazione (L10n) di Spring MVC: Esempio

Benvenuti al tutorial di internazionalizzazione (i18n) della Primavera. Qualsiasi applicazione web con utenti provenienti da tutto il mondo, l’internazionalizzazione (i18n) o la localizzazione (L10n) sono molto importanti per una migliore interazione utente. La maggior parte dei framework per applicazioni web fornisce modi facili per localizzare l’applicazione in base alle impostazioni locali dell’utente. Anche Spring segue questo modello e fornisce un ampio supporto per l’internazionalizzazione (i18n) attraverso l’uso di interceptors di Spring, risolutori di locale e bundle di risorse per diverse località. Alcuni articoli precedenti sull’i18n in Java.

Internazionalizzazione i18n di Spring

Creiamo un semplice progetto Spring MVC in cui utilizzeremo il parametro di richiesta per ottenere la lingua dell’utente e, in base a questo, impostare i valori delle etichette della pagina di risposta dai bundle di risorse specifici della lingua. Crea un progetto Spring MVC nel Spring Tool Suite per avere il codice di base per la nostra applicazione. Se non sei familiare con Spring Tool Suite o i progetti Spring MVC, leggi Spring MVC Example. Il nostro progetto finale con le modifiche di localizzazione appare come nell’immagine sottostante. Esamineremo tutte le parti dell’applicazione una per una.

Configurazione Maven per Spring i18n

Il nostro file pom.xml per Spring MVC appare come segue.

<?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>

Gran parte del codice è generato automaticamente da STS, tranne che ho aggiornato la versione di Spring per utilizzare l’ultima, ovvero 4.0.2.RELEASE. Possiamo rimuovere le dipendenze o aggiornare le versioni delle altre dipendenze, ma le ho lasciate così come sono per semplicità.

Bundle di Risorse Spring

Per semplicità, supponiamo che la nostra applicazione supporti solo due lingue – inglese e francese. Se non viene specificata alcuna lingua dell’utente, utilizzeremo l’inglese come lingua predefinita. Creiamo i file delle risorse di spring per entrambe queste lingue che verranno utilizzati nella pagina JSP. Codice per messages_en.properties:

label.title=Login Page
label.firstName=First Name
label.lastName=Last Name
label.submit=Login

Codice per messages_fr.properties:

label.title=Connectez-vous page
label.firstName=Pr\u00E9nom
label.lastName=Nom
label.submit=Connexion

Nota che sto utilizzando unicode per i caratteri speciali nei file delle risorse in lingua francese, in modo che vengano interpretati correttamente nell’HTML di risposta inviato alle richieste del client. Un altro punto importante da notare è che entrambi i file delle risorse si trovano nel classpath dell’applicazione e il loro nome ha il formato “messages_{locale}.properties”. Vedremo più avanti perché questi sono importanti.

Classe Controller Spring i18n

La nostra classe controller è molto semplice, semplicemente registra la lingua dell’utente e restituisce la pagina home.jsp come risposta.

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

Pagina JSP Spring i18n

Il codice della nostra pagina home.jsp è come segue.

<%@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 sola parte degna di nota è l’uso di spring:message per recuperare il messaggio con il codice fornito. Assicurati che le librerie dei tag di Spring siano configurate utilizzando la direttiva taglib jsp. Spring si occupa del caricamento dei messaggi del bundle di risorse appropriato e li rende disponibili per le pagine JSP da utilizzare.

Internazionalizzazione Spring i18n – File di configurazione del bean

Il file di configurazione del bean di Spring è il luogo in cui avviene tutta la magia. Questa è la bellezza del framework Spring in quanto ci aiuta a concentrarci maggiormente sulla logica di business piuttosto che sulla codifica di compiti banali. Vediamo come appare il nostro file di configurazione del bean di Spring e guarderemo ciascuno dei bean uno per uno. Codice 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>
  1. annotation-driven tag abilita il modello di programmazione Controller, senza di esso Spring non riconoscerà il nostro HomeController come gestore delle richieste dei client.

  2. context:component-scan fornisce il pacchetto in cui Spring cercherà i componenti annotati e li registrerà automaticamente come bean Spring.

  3. messageSource è configurato per abilitare la localizzazione internazionale nella nostra applicazione. La proprietà basename viene utilizzata per indicare la posizione dei pacchetti di risorse. classpath:messages significa che i pacchetti di risorse si trovano nel classpath e seguono lo schema di denominazione messages_{locale}.properties. La proprietà defaultEncoding viene utilizzata per definire la codifica utilizzata per i messaggi.

  4. Il bean localeResolver di tipo org.springframework.web.servlet.i18n.CookieLocaleResolver viene utilizzato per impostare un cookie nella richiesta del client in modo che le richieste successive possano facilmente riconoscere la località dell’utente. Ad esempio, possiamo chiedere all’utente di selezionare la località quando avvia l’applicazione web per la prima volta e con l’uso del cookie, possiamo identificare la località dell’utente e inviare automaticamente una risposta specifica della località. Possiamo anche specificare la località predefinita, il nome del cookie e l’età massima del cookie prima che venga scaduto e eliminato dal browser del client. Se la tua applicazione mantiene le sessioni degli utenti, allora puoi anche utilizzare org.springframework.web.servlet.i18n.SessionLocaleResolver come localeResolver per utilizzare un attributo di località nella sessione dell’utente. La configurazione è simile a CookieLocaleResolver.

    <bean id="localeResolver"
    	class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    	<property name="defaultLocale" value="en" />
    </bean>
    

    Se non registriamo alcun “localeResolver”, AcceptHeaderLocaleResolver verrà utilizzato per impostazione predefinita, che risolve la località dell’utente controllando l’intestazione accept-language nella richiesta HTTP del client.

  5. L’intercettatore org.springframework.web.servlet.i18n.LocaleChangeInterceptor è configurato per intercettare la richiesta dell’utente e identificare la lingua dell’utente. Il nome del parametro è configurabile e stiamo utilizzando il nome del parametro della richiesta per la lingua come “locale”. Senza questo intercettatore, non saremo in grado di cambiare la lingua dell’utente e inviare la risposta in base alle nuove impostazioni di lingua dell’utente. Deve far parte dell’elemento interceptors altrimenti Spring non lo configurerà come intercettatore.

Se ti stai chiedendo sulla configurazione che indica al framework Spring di caricare le nostre configurazioni di contesto, è presente nel descrittore di distribuzione della nostra applicazione 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>

Possiamo cambiare la posizione o il nome del file di contesto modificando la configurazione web.xml. La nostra applicazione Spring i18n è pronta, basta distribuirla in qualsiasi contenitore servlet. Di solito la esporto come file WAR nella directory webapps di un server web Tomcat autonomo. Ecco gli screenshot della home page della nostra applicazione con diverse lingue. Pagina principale predefinita (lingua inglese): Passaggio della lingua come parametro (lingua francese): Ulteriori richieste senza specificare la lingua: Come puoi vedere nell’immagine sopra, non stiamo passando informazioni sulla lingua nella richiesta del client, ma la nostra applicazione identifica comunque la lingua dell’utente. A questo punto avrai già indovinato che è grazie al bean CookieLocaleResolver che abbiamo configurato nel nostro file di configurazione dei bean Spring. Tuttavia, puoi verificare i dati dei cookie del tuo browser per confermarlo. Sto usando Chrome e l’immagine seguente mostra i dati dei cookie memorizzati dall’applicazione. Nota che il tempo di scadenza del cookie è di un’ora, cioè 3600 secondi, come configurato dalla proprietà cookieMaxAge. Se controlli i log del server, vedrai che la lingua viene registrata.

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.

Ecco tutto per l’applicazione di esempio Spring i18n, scarica il progetto di esempio dal link sottostante e divertiti a esplorarlo per imparare di più.

Scarica il Progetto Spring i18n

Source:
https://www.digitalocean.com/community/tutorials/spring-mvc-internationalization-i18n-and-localization-l10n-example