MokaByte Numero 24  -  Novembre 98
Internazionalizzare
un'applicazione Java
di 
Giovanni Puliti
Portabilità significa anche tener conto la nazionalità del client, considerando quindi anche le differenze linguistiche

 


Realizzare codice portabile, se si ha a che fare con Internet, non si riduce soltanto a considerare le problematiche tecniche legate alle diverse piattaforme. Un programma che utilizzi le convenzioni linguistiche del luogo di esecuzione ha maggiori possibilità di successo. Ecco come l'Internationalization viene in aiuto per questo scopo



 


Introduzione

Come noto la caratteristica principale di Java è quella di permettere al codice compilato (nel senso del bytecode), di essere eseguito su piattaforme differenti senza necessità di ricompilazioni. Seppur con l'ausilio di alcune accortezze (descritte in [1] ed in [2]), per lo più volte ad eliminare certi problemi derivanti da una cattiva interpretazione del concetto di multipiattaforma piuttosto che a limitare il linguaggio, Java è un linguaggio realmente multipiattaforma. Parlando di portabilità però si finisce spesso per concentrare l'attenzione su aspetti tecnici come il tipo di hardware ed il sistema operativo su cui viene eseguito il programma. È importante notare che, parlando di portabilità, anche alla luce di concetti come Internet e comunità globale, si potrebbe rivedere il tutto da un punto di vista meno tecnico in senso stretto, ma più legato alle caratteristiche dell'utente. In tal senso la portabilità è sicuramente un requisito necessario, ma certamente non basta per ottenere un prodotto finale versatile: ad esempio si devono anche considerare le problematiche, non banali, legate alla lingua ed alle convenzioni adottate nella comunicazione nelle varie zone del mondo. Anche se esistono alcune lingue molto diffuse che permettono di coprire ampie zone del globo, (principalmente l'inglese), è altresì vero che il poter facilitare l'interazione fra uomo e macchina, è da sempre uno degli obiettivi degli informatici: maggiore semplicità di interazione significa meno errori (quindi meno costi di correzione o prevenzione), quindi può aumentare il livello di successo di un determinato software piuttosto che un altro. Per questi motivi, e per molti altri che non starò qui ad elencare, ma che credo siano piuttosto intuitivi, realizzare una applicazione multilingua è sicuramente un grosso vantaggio.

Localizzazione ed internazionalizzazione

Non è certo una novità il fatto che la maggior parte del software (almeno quello più importante) è spesso disponibile in più lingue, permettendo di scegliere la lingua con la quale lavorare, anche se a volte il software tradotto dall'inglese non è perfetto (sia nella traduzione che nella funzionalità), tanto da far preferire la versione originale.

Il procedimento che porta alle differenti versioni del software viene detto a volte localizzazione: esso permette di partire con una versione in grado di essere utilizzata in ogni parte del mondo, e solo successivamente di passare alla versione tradotta. Si tenga presente che, nonostante l'origine comune, una applicazione localizzata per gli Stati Uniti è da considerarsi a tutti gli effetti un prodotto completamente differente dalla versione tradotta in francese o in italiano.

Tale modo di lavorare, partendo dalle indagini di mercato fino alla produzione tecnica del software, permette in genere la traduzione senza particolari problemi legati ai tempi.

Con l'avvento di Internet, nell'ambito della produzione del software, si sono dovute riconsiderare alcune problematiche, ed una di queste è proprio quella legate all'area geografica di esecuzione del programma e quindi delle convenzioni linguistiche ad essa legate. Il meccanismo visto poco sopra della localizzazione del software mostra in questo caso tutti i suoi limiti: nel momento in cui una certa risorsa (un documento, una immagine e quindi anche un software), viene messa on line, la sua visibilità è mondiale ed istantanea.

Si rende necessaria una metodologia differente, più dinamica e flessibile. Dato che Java ci ha abituato a pensare in modo differente ed innovativo in molti casi, perché non puntare a qualcosa di totalmente innovativo anche in questo caso? Perché ad esempio non desiderare una applet mutante, la quale, al variare della zona geografica di esecuzione, cambi totalmente l'interfaccia grafica e la modalità di interfacciamento con l'utente?

L'internazionalizzazione permette proprio questo ed, in perfetto stile Java, in modo molto semplice. Una applicazione che utilizzi meccanismi di internazionalizzazione in maniera del tutto automatica ed istantanea può comunicare con l'utente in francese, tedesco o italiano, senza la necessità di alcuna operazione di ricompilazione.

L'internazionalizzazione

Anche il JDK 1.0, pur non avendo un esplicito supporto all'internazionalizzazione, permetteva, con un po' di lavoro, di ottenere risultati analoghi. Dalla versione 1.1 invece troviamo una serie di classi e metodi appositamente pensati per tale scopo. Rendere una applicazione internazionale significa permettere la modifica dell'aspetto della interfaccia grafica, ma anche della modalità con cui l'output viene prodotto. Anche se certamente questa può apparire una semplificazione troppo spinta, parleremo di area linguistica di esecuzione, intendendo considerare in tal modo sia la lingua che le effettive convenzioni comunicative di una certa zona geografica.

Il JDK 1.1 permette essenzialmente di eseguire tre operazioni quali la localizzazione, la definizione di un set risorse da utilizzare in funzione di una specifica area linguistica (ad esempio i pulsanti o le etichette per l'italianizzazione della GUI), ed una serie di tecniche per la formattazione dell'output.

Dopo questa lunga introduzione, dovrebbe essere chiaro che, sebbene l'internazionalizzazione non sia esclusiva delle applet, è con queste che essa mostra la sua più grande utilità. Vediamo quindi come essa possa essere utilizzata e quali sono gli strumenti che abbiamo a disposizione.

Localizzazione geografica: la classe Locale

Si è detto quindi che una applicazione internazionalizzata deve avere la possibilità di modificare il suo aspetto (lingua utilizzata per l'interfaccia) ma anche il suo comportamento (modalità di formattazione dell'output), in funzione dell'area linguistica in cui il programma viene mandato in esecuzione: la prima cosa da fare quindi è trovare un modo col quale dire alla applicazione quale è l'area da prendere in esame. Come lascia intuire il nome, la classe java.util.Locale ha proprio questo scopo: essa rappresenta una zona dal punto di vista sia linguistico che geografico, offrendo così una maggiore precisione, dato che non sempre la sola lingua permette di individuare univocamente un'area geografica o viceversa: si pensi al francese parlato in Francia, in Canada ed in molti altri paesi dell'Africa, o viceversa si consideri la Svizzera dove si parlano italiano, francese e tedesco a seconda del cantone.

Per semplicità non consideriamo i dialetti, anche se dal punto di vista tecnico niente impedisce ad una applet di parlare in siciliano o piemontese. Il Locale, infine, oltre ad identificare la lingua permette di impostare altre informazioni utili, come la valuta o la modalità con cui data e ora vengono rappresentati.

Per definire una area linguistica si deve creare un oggetto Locale per quella area: questo viene fatto passando al costruttore di classe dei parametri di tipo stringa per identificare univocamente l'area linguistica, specificando lingua ed area geografica.

Java utilizza gli standard internazionali per identificare lingue (standard ISO Language Code) e aree geografiche (standard ISO Country Code): il primo identifica una lingua con due lettere minuscole mentre il secondo utilizza due lettere maiuscole per identificare la regione. Quindi per creare ad esempio un Locale relativo all'Italia si dovrà scrivere
 
new Locale("it", "IT")

La stringa composta dai due codici separati da una sottolineatura si utilizza spesso in nomi di classi di risorse: un oggetto myBtn_ it_IT potrebbe rappresentare la risorsa per definire un bottone relativo all'area geografica italiana; più avanti si approfondirà questo punto. Si è visto un modo per creare un oggetto locale, cioè quello che utilizza le due stringhe lingua+area geografica, ma ne esiste un altro più veloce: essendo infatti molti Locale più usati di altri, la classe Locale mette a disposizione alcune costanti (oggetti static final), come ad esempio Locale.ITALIAN, Locale.US oppure Locale.CANAD_FRENCH. Non sono previsti comunque casi particolari più circoscritti come quello della Svizzera tedesca. È bene a tal punto osservare che la classe Locale ha il solo compito di marcare una certa area linguistica particolare, e di comunicarla al sistema al momento della traduzione on-fly: oltre a questo non offrono nessuna funzionalità o informazione aggiuntiva, come le regole di traduzione o di formattazione dell'output, che invece sono memorizzate in classi come le istanze di ResourceBundle.

Risorse

Dopo aver individuato la zona di esecuzione, o area linguistica, occorre passare alla traduzione vera e propria, ed a modificare alcuni testi di colloquio con l'utente in modo da adattarli alle convenzioni del posto.

Per prima cosa vediamo come sia possibile tradurre automaticamente una interfaccia in funzione della area linguistica. Per ottenere tale scopo, possiamo operare in due modi: un primo consiste nel realizzare una serie di classi Java che verranno caricate dinamicamente in funzione del Locale specificato, oppure in alternativa utilizzare dei normali file di testo nei quali inserire tutte le informazioni necessarie per la traduzione al volo di una interfaccia. Non ci soffermiamo in questa sede su come una applet possa capire quale sia il Locale da utilizzare: brevemente possiamo accennare a varie tecniche che vanno dalla scelta dell'utente per mezzo di una serie di pulsanti ("Scegli una lingua"), a tecniche più sofisticate, ma non sempre sicure, per mezzo delle quali controllare il valore di una qualche variabile di sistema sul client o dal server web. Scelto o impostato il Locale, il costruttore dell'interfaccia può passare ad impostare testi, numeri ed eventualmente immagini.

Vediamo adesso come si possono specificare più versioni per la stessa risorsa: la classe principale è java.util.ResourceBundle. Si tratta di una classe astratta e per questo non può essere utilizzata direttamente, ma invece si deve fare riferimento alle classi concrete java.util.ListResourceBundle (che definisce le risorse utilizzando delle costanti espresse in codice Java) o java.util.PropertyResourceBundle (che consente di specificare le risorse utilizzando dei file di proprietà).

Eventualmente è possibile estendere direttamente la classe ResourceBundle in modo da definire direttamente una sorgente di dati particolare prelevando le informazioni da un qualche database. Consideriamo per prima ListResourceBundle. Per poterla utilizzare la si deve estendere con una classe il cui nome termina con la stringa _<lingua>_<area>, seguendo la convenzione vista in precedenza. Si deve inoltre ridefinire il metodo
 
public Object[][] getContens()

il quale deve ritornare un array di array: ogni elemento dell'array deve essere a sua volta un array il cui primo elemento deve essere una stringa e il secondo un oggetto qualsiasi (solitamente una stringa anch'esso). Per esempio per le risorse per l'italiano occorre creare una classe
 
class Res_it_IT extends ListResourceBundle {

    public Object[][] getContents(){

    return contents;

    }

    static final Object[][] contents={{"m_file", "File"},{"m_open", "Apri"}}

}

In alternativa è possibile, ove non serve o non si desidera specificare anche l'area geografica, creare una risorsa che faccia riferimento alla sola lingua, chiamando semplicemente la classe Res_it: tale risorsa verrebbe caricata nel momento in cui venga specificata la lingua italiana indipendentemente dall'area geografica, e quindi anche nel caso di localizzazione nella Svizzera italiana. A tal proposito si tenga presente che un meccanismo simile viene sempre utilizzato nel caso non venga trovata una risorsa specifica, risalendo verso la risorsa per default il cui nome non deve avere specificatori di regione. Solitamente questa scelta viene riservata alle classi in inglese per gli Stati Uniti. Le risorse devono essere compilate adeguatamente, ottenendo un file .class per ogni risorsa.

A questo punto utilizzando
 
ResourceBundle res = ResourceBundle.getBundle ("Res", locale);

si caricano le risorse. La selezione della classe opportuna viene svolta da ResourceBundle in base al Locale. A questo punto si possono utilizzare le risorse per esempio nella costruzione di un MenuItem scrivendo
 
item = new MenuItem(res.getString("m_open"));

Ottenendo la voce "Apri", "Ouvrir" o "Open" a seconda dei casi. A livello di codice ovviamente si utilizza un linguaggio neutrale, in genere inglese, per identificare il Locale. Abbiamo specificato una classe per la traduzione in italiano del menu, e per aggiungere nuove lingue è sufficiente realizzare nuove classi e compilarle. Abbiamo detto all'inizio del paragrafo che in alternativa è possibile anche utilizzare file di testo per la raccolta delle voci necessarie per tradurre le varie parti dell'interfaccia: tali file, detti file di proprietà, seguono una regola di naming molto simile a quella vista per le risorse, e vengono caricati automaticamente e quindi utilizzati nel momento in cui il file .class non viene trovato. Se ad esempio si desidera inglesizzare il programma, dovremmo provvedere a creare un file Res_en.properties con questo contenuto:
 
m_file=File

m_open=Open

Tale file verrà usato al posto di un file compilato Res_en.class ed otterremo un risultato del tutto analogo a quanto visto in precedenza.

Testi parametrici

La tecnica Locale+Resource permette ad esempio di caricare dinamicamente i testi dei vari pulsanti che compongono l'interfaccia grafica: in tal modo essa assume al momento della istanziazione dei vari componenti un aspetto determinato, aspetto che non potrà essere modificato ulteriormente. In alcuni casi però la semplice traduzione dei testi può non essere sufficiente, oppure può essere solo un aspetto del problema.

Si consideri ad esempio di avere una istruzione del seguente tipo
 
oggettografico.print("il risultato sul valore "+val1+" è il seguente: "+ res1)

e si immagini di dover modificare il testo prodotto in funzione della area linguistica. Indipendentemente dal tipo di oggetto che esegue tale print a video, rispetto al caso precedente abbiamo una sostanziale differenza: non si deve infatti a priori caricare una stringa, ma la si deve anche poter modificare dinamicamente in fase di esecuzione del programma, dato che contiene dei parametri che vengono calcolati dal programma stesso.

Per risolvere un problema di questo tipo si potrebbe pensare di individuare prima la posizione dei parametri, e di considerare successivamente la stringa senza parametri, come una risorsa da internazionalizzare alla stessa stregua della label di un pulsante.

Senza l'ausilio di tecniche apposite si potrebbe, anche se non in maniera semplice, trovare una soluzione al problema: fortunatamente il JDK mette a disposizione una serie di oggetti contenuti all'interno del package java.text. Si tratta di oggetti che derivano dalla classe Format, e che permettono la formattazione di testo, numeri e date. Riprendendo l'esempio appena visto, per risolvere il problema della formattazione di testo, si può utilizzare la classe MessageFormat: il metodo format() permette infatti di eseguire una scansione della stringa in esame, di individuare la posizione in cui inserire i parametri e di effettuarne la sostituzione. Per prima cosa dobbiamo definire come oggetti ResourceBundle le stringhe da parametrizzare; i file di property che otteniamo possono essere i seguenti:
 
parametric_text.it.properties

ris="il risultato sul valore {0} è il seguente: {1}"

parametric_text.uk.properties

ris="the results on value {0} is: {1}"

Il metodo MessageFormat fornisce il metodo statico

format(String str, Object[] valori)

il primo parametro rappresenta la stringa parametrica i cui valori da inserire sono contenuti nel vettore valori:

Object valori ={new Integer(1), new Integer(10)};

ResourceBoundle rb= ResourceBoundle.getBundle();

oggettografico.print(MessageFormat. format(rb.getString(ris), valori);

Si noti il fatto che il vettore è di oggetti, e non di tipi elementari: si utilizzano quindi le classi wrapper Integer e si dovranno poi effettuare opportune operazioni di casting. Queste semplici operazioni permettono di creare una risorsa che però, essendo parametrica, può essere formattata in funzione del Locale scelto.

Formattazione di numeri e date

Nell'esempio precedente abbiamo visto come formattare stringhe di testo in cui i parametri erano tutti di tipo intero; può capitare però di dover eseguire operazioni analoghe sia su numeri di formati differenti che date. Nel caso di numeri possiamo, utilizzando un meccanismo del tutto simile a quello visto fin'ora, scegliere fra varie modalità di formattazione delle cifre, in funzione dell'uso del punto o della virgola come caratteri separatori. La classe da utilizzare in questo caso è la NumberFormat, una cui istanza può essere ottenuta per mezzo del metodo statico getIstance(). Il metodo format poi agisce in maniera del tutto analoga al caso precedente per cui l'esempio di prima diviene
 
oggettografico.print(NumberFormat.getInstance() .format(valori));

permettendo una rappresentazione Locale-sensitive della variabile valori. Nel caso delle date invece si utilizza la classe DateFormat, che offre una serie di opzioni per la visualizzazione della data (mese giorno anno, o rappresentazione sintetica oppure dettagliata). Il procedimento di utilizzazione è del tutto analogo ai casi precedenti, con la sola modifica alla istruzione
 
oggettografico.print(DateFormat.getDateInstance() .format(new Date()));

La classe Collator e la gestione dell'ordinamento

Ultimo punto degno di nota è la gestione dell'ordinamento in funzione dell'area linguistica scelta: esistono infatti delle differenze per esempio per quanto riguarda la precedenza fra numeri e lettere o fra maiuscolo e minuscolo. La classe Collator del package java.text ha proprio lo scopo di supportare operazioni di scansione spelling e quindi ordinamento di elementi: un elemento, nel caso di testo, può essere un carattere, una stringa o addirittura una frase. Collator è una classe astratta, ma le varie sottoclassi permettono di utilizzare particolari strategie di ordinamento: la RuleBasedCollator, attualmente fornita con il JDK, è utilizzabile con un numero molto ampio di linguaggi, ma è possibile implementare classi particolari per esigenze specifiche. Anche in questo caso, come altrove, per ottenere una istanza dell'oggetto si utilizza il metodo getIstance() per ottenere l'oggetto Collator appropriato in funzione del Locale scelto. L'esempio riportato qui di seguito, ispirato ad uno analogo contenuto nella documentazione del JDK permette un confronto fra stringhe in funzione del Locale:
 
....

Collator MyCollator=Collator.getInstance();

if(myCollator.compare("giovanni", "Giovanni") < 0)

    System.out.println("giovanni è minore di Giovanni");

else

    System.out.println("giovanni è maggiore o uguale di Giovanni");

Solo per avere un'idea delle possibilità che si possono ottenere, l'ordinamento può essere effettuato in base a 4 gerarchie di ordinamento (strength in gergo): primario, secondario, terziario e identico. Ad esempio nella convenzione Cecoslovacca le lettere "a" e "b" presentano una differenza primaria, "e" ed "è" una secondaria, "e" ed "E" terziaria, mentre "e" ed "e" sono identiche.

Descrizione dell'esempio

L'esempio, presente presente sul dischetto allegato alla rivista o al sito Ftp di Infomedia, implementa una semplicissima interfaccia grafica composta di un bottone e di una casella di testo. Per il bottone viene fornita una serie di classi risorsa che ne permettono l'internazionalizzazione.

In particolare le classi Bottone_it_IT e Bottone_it_UK permettono di variare la caption del bottone da "OK" a "Si" in italiano a "Oui" in francese.

Se si eliminano i file .class di risorse il programma carica automaticamente i file di proprietà (Bottone_en.properties e Bottone_it.properties che si trovano allegati all'esempio) relativi alla lingua impostata. Ecco la classe che permette la traduzione automatica del bottone in italiano
 
 
public abstract class Bottone_it_IT extends ResourceBundle {

    public Object handleGetObject(String key) {

        if (key.equals("yes")) return "Si";

        else if (key.equals("no")) return "No";

        return null;

    }

}

ed ecco quella in inglese
 
 

public abstract class Bottone_en_UK extends ResourceBundle {

    public Object handleGetObject(String key) {

        if (key.equals("yes")) return "Yes";

        else

        if (key.equals("no")) return "No";

        return null;

    }

}
 
 

Il programma è molto semplice ma permette di fare delle prove e prendere pratica con una interfaccia internazionale.
 
 

Conclusioni

In questo articolo abbiamo presentato i principi fondamentali che sono alla base del processo di internazionalizzazione e che permettono di creare un'applicazione in grado di cavarsela ogni volta che esegue un viaggio all'estero, senza bisogno di ricorrere all'interprete. Quello che è importante notare, oltre all'aspetto tecnico, è che il codice portabile richiede di prendere in considerazione non solo le differenze tecniche che sussistono fra piattaforme differenti, ma anche delle differenze umane che vi sono fra operatori diversi.

Dato che in definitiva un'applicazione ed un computer sono utilizzati da un uomo, ne deriva che il problema non è poi tanto da sottovalutare.

Bibliografia

 
 

MokaByte Web  1998 - www.mokabyte.it 
MokaByte ricerca nuovi collaboratori. 
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it