Internazionalization e localization
Internazionalization (da ora abbreviato in i18n) e localization (da ora l10n) sono due termini che spesso vengono confusi.
I18n significa sviluppare una applicazione che supporta la possibilità di adattarsi a diversi linguaggi e regioni geografiche senza cambiamenti nel codice; in questo caso che non basta "tradurre" le stringhe in una seconda lingua, spesso ci sono alcune criticità come il fuso orario, il formato di scrittura dei numeri fino ad arrivare al modo di leggere un testo, per esempio da destra a sinistra per le lingue arabe.
L10n invece rappresenta il processo di adattare una applicazione (che ovviamente lo supporti) ad
una nuova lingua.
I18n e l10n in Spring MVC
Spring MVC gestisce queste tematiche sfruttando alcuni file di properties per permettere il mapping tra messaggi appartenenti a differenti linguaggi.
Innanzitutto è necessario definire alcuni componenti, bean Spring, che ci verranno in
aiuto durante lo sviluppo. Questi sono bean particolari che richiedono un determinato id per essere
identificati da Spring MVC:
Bean | Descrizione |
---|---|
messageSource | Rappresenta la sorgente di tutti i messaggi per tutte le lingue. |
localeChangeInterceptor | Sorta di filtro Web che permette di identificare i cambiamenti di lingua durante il processo di vita dell'applicazione. |
localeResolver | Componente che permette di identificare la lingua richiesta dall'utente. |
Per ciascuno di questi bean esistono diverse implementazioni, ognuna con la sua particolarità: per
quanto riguarda ad esempio il
messageSource
, ne esiste uno che permette di rileggere "a caldo" l'elenco dei messaggi senza un riavvio dell'applicazione e un un'altro che utilizza come file sorgente un XML.
Piccola nota: non abbiamo ancora parlato degli interceptor
, ma ci arriveremo nella prossima lezione. Per il momento consideriamoli una sorta di filtro Web dal quale passano tutte le richieste HTTP.
Nel nostro esempio useremo questi bean:
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="/WEB-INF/message/message"/>
</bean>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<property name="defaultLocale" value="en"/>
</bean>
<mvc:interceptors>
<bean id="localeChangeInterceptor"
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang" />
</bean>
</mvc:interceptors>
Abbiamo cosi definito un messageSource
, un localeResolver
che persiste il valore selezionato su un cookie HTTP e un interceptor
che controlla la presenza di eventuali parametri "lang" nella request. Ora definiamo i due (o più) file contenenti le risorse tradotte: secondo la configurazione del messageSource
i file devono essere creati in WEB-INF/message/
e devono avere come nome message_{codicelingua}.properties
; nel nostro esempio creiamoli in questo modo:
/WEB-INF/message/message_it.properties
message.hello=Ciao
/WEB-INF/message/message_en.properties
message.hello=Hello
Passiamo quindi occuparci del componente view
. Anche in questo caso Spring ci viene in aiuto con una tag library (chiamata appunto Spring
). Riprendiamo una jsp usata qualche lezione fa e modifichiamola:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<h1>
<spring:message code="message.hello"/>
<c:out value="${nome }" /> <c:out value="${cognome}" />
</h1>
La modifica è abbastanza semplice, abbiamo modificato la stringa statica "Ciao" con un placeholder
dal nome "message.hello" che, guarda caso, è esattamente la chiave utilizzata nei due file di
properties definiti in precedenza. Avviando ora l'applicazione potremmo vedere la pagina nella
versione inglese grazie all'utilizzo del tag spring:message
che si occupa di fare il mapping. Per passare alla versione italiana basterà invece creare un link che richiamerà una pagina
appendendo il parametro ?lang=it
. Anche in questo caso il nome del parametro lang non è casuale, è infatti quello definito prima come property
dell'interceptor
.
I18n e l10n personalizzati
L'esempio analizzato nel precedente capitolo rispecchia i canoni di una applicazione Web standard.
Non è però sempre detto che queste convenzioni possano sempre rispecchiare le nostre esigenze.
Come detto in precedenza Spring MVC fornisce già una serie di componenti alternativi a quelli visti
in precedenza utilizzabili in maniera rapidissima semplicemente cambiando il puntamento alla
classe nel file XML. Anche questi nuovi componenti possono però risultare fuori target rispetto al
nostro obiettivo.
Se la situazione è davvero così particolare non ci resta quindi che implementare un componente custom. Come vedremo anche successivamente questo è uno degli aspetti più interessanti di Spring, cioè la possibilità di estendere in maniera quasi banale i componenti base sfruttando il meccanismo delle interfacce. Oltretutto grazie alla dipendency injection controllata in maniera dichiarativa tramite file XML, è davvero immediato sostituire un "mattoncino" nativo con un altro scritto da noi.
Ritornando al mondo dell'i18n, per estendere uno dei tre componenti visti in precedenza è
necessario creare una nuova classe e modificare il file XML per far puntare ad essa. La maggior
parte delle volte può bastare estendere una classe già presente per aggiungere una funzionalità
mancante.
Spring MVC e le property esterne
Rimanendo in tema i18n e messaggi esterni al codice, un'ulteriore funzionalità di Spring MVC è
quella di permettere l'utilizzo in maniera facilitata delle cosiddette properties
. Spesso sviluppando abbiamo bisogno di accedere a dei valori in formato stringa o numerici per effettuare logiche di business specifiche per il tipo di applicazione che stiamo realizzando.
Non è mai buona norma inserire questi "magic numbers" all'interno del codice, questo per il semplice
motivo che se dovessero per qualche ragione cambiare, avremmo bisogno di ricompilare tutta
l'applicazione. I file di properties servono proprio a questo scopo.
Spring MVC ci mette a disposizione la classe PropertyPlaceholderConfigurer
per gestire in maniera trasparente e tramite annotations questo aspetto, spesso abbastanza scomodo e noioso. Supponiamo di avere una applicazione che deve effettuare una chiamata remota verso un altro server utilizzando HTTP; per poterlo fare abbiamo senza dubbio bisogno di una URL da invocare il cui valore potrebbe (o meglio dovrebbe) risiedere fuori dal codice.
Creiamo quindi un nuovo file di properties, per esempio sotto WEB-INF/properties
, con il nome application.properties
e inseriamo la riga:
remoteServer.URL=http://remoteserver.com/service/myservice
Ora all'interno di un nostro bean definiamo una nuova proprietà annotata con @Value:
@Value("${remoteServer.URL}")
private String remoteServerURL;
Una volta definito il mapping non basta che comunicare a Spring di occuparsi di tutto ciò inserendo
nel suo file XML di configurazione:
<context:property-placeholder location="WEB-INF/properties/*.properties"/>
In questo modo Spring leggerà tutti i file properties nella cartella indicata e li mapperà sulle
proprietà annotate con @Value
rendendo di fatto disponibile il valore all'interno del codice senza dover scrivere nessuna riga di codice aggiuntivo. Ma non basta, Spring si occuperà anche di convertire, eventualmente, il valore di una stringa in interi, decimali, boolean e date.