MokaByte
Numero 24 - Novembre 98
|
|||
|
un'applicazione Java |
||
Giovanni Puliti |
|
||
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 { |
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 |
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 |
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:
.... |
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 { |
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
[2] "100% Pure Java Program", http://java.sun.com/100pj.
[3] Documentazione Sun JDK 1.1, http://java.sun.com.
MokaByte Web 1998 - www.mokabyte.it MokaByte ricerca nuovi collaboratori. Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it |