Il presente documento illustra brevemente l'uso dell'internazionalizzazione inclusa nella libc GNU (gettext) ed in particolare la sua applicazione a codice già esistente ed ai progetti fluid/FLTK.
La GNU_libc contiene alcune utili funzioni di internazionalizzazione che facilitano la scrittura di interfacce multilinguaggio. La traduzione è ovviamente affidata all'utente ma l'utilizzo del corretto linguaggio viene effettuato runtime in modo trasparente all'utente. La modalità utilizzata permette inoltre con poche operazioni di rendere multilinguaggio un codice già esistente. Facilita inoltre l'estensione ad altri linguaggio di un codice già internazionalizzato. Non ultimo vantaggio è robusta rispetta all'assenza di traduzioni o a linguaggi non implementati. In tal caso non genererà errori runtime o effetti sgradevoli come ad esempio messaggi vuoti.
Un sistema alternativo è l'uso della direttive POSIX catgets/gencat, per molti aspetti simile.
Per inciso: l'implementazione GNU provvede anche ad altri aspetti dell'internazionalizzazione (valuta, numeri, data, ecc.). Questo documento si occupa solo però dell'internazionalizzazione dei messaggi (LC_MESSAGES).
La traduzione è affidata a differenti file, definiti file catalogo, contenenti coppie id-stringa, che vengono caricati runtime dal programma. In generale ogni programma avrà un catalogo per ogni linguaggio implementato. I file catalogo vengono utilizzati in un formato binario che è generato a partire da un file ASCII tramite l'utility msgfmt (si veda nel seguito).
Per utilizzare le traduzioni si usa nel codice la chiamata gettext. Ad esempio invece di:
printf("Hello Word");si utilizzerà:
printf(gettext("Hello Word");Che stamperà la traduzione associata all'ID "Hello Word" presente nel file catalogo per il linguaggio in uso.
Nel caso in cui l'ID richiesto non venga trovato verrà ritornato l'ID stesso quindi l'ID viene utilizzato come valore di default. Per questo motivo per gli ID si tende a scegliere valori significativi piuttosto che sintetici. Si noti che questo significa che la trasformazione in multilinguaggio del codice avviene in modo completamente indolore, ovvero che la sostistuzione delle stringhe con chiamate a gettext non ha alcun effetto fino alla effettiva applicazione di un file catalogo.
Per creare un nuovo file di catalogo (estensione .mo) è necessario creare un file ASCII (estensione .po) contenente le coppie id/stringa secondo il formato:
msgid "Valore di default" msgstr "Valore tradotto"Dove msgid è l'identificativo della stringa mentre msgstr è la corrispondente traduzione. File contenenti lo stesso elenco di msgid ma stringhe differenti (per i differenti linguaggi) possono essere installati nel sistema ed utilizzati in funzione delle specifiche di esecuzione.
I file catalogo possono contenere commenti nella forma usuale:
# commento
I file ASCII .po non vengono utilizzati direttamente ma devono essere compilati tramite il comando:
msgfmt -o file.mo file.poche genera il vero file catalogo (binario) .mo a partire dal file ASCII .po.
I file .mo sono di default installati nelle cartelle
/usr/share/locale/$(id_linguaggio)/LC_MESSAGES/nomeprogramma.mo /usr/lib/locale/$(id_linguaggio)/LC_MESSAGES/nomeprogramma.moDove $(id_linguaggio) è tipicamente una stringa breve del tipo "it", "en", "it_IT", ecc. L'uso dello stesso nome del programma per il nome del file catalogo è naturalmente una comoda convenzione. Ad esempio il file:
/usr/share/locale/it/LC_MESSAGES/audacity.mocontierrà le stringhe in lingua italiana utilizzate dall'interfaccia del programma audacity.
Un programma può naturalmente utilizzare dei path non standard, ovvero installare i propri cataloghi localmente. Si noti però che l'ultima parte del path è sempre necessaria quindi una installazione non standard dovrà prevedere una gerarchia del tipo:
path_qualsiasi/$(id_linguaggio)/LC_MESSAGES/nomeprogramma.mo
La scelta del linguaggio da utilizzare può essere affidata alle variabili di ambiente (in particolare alla variabile LC_MESSAGES) oppure essere effettuata dal programma stesso tramite la chiamata setlocale:
setlocale(LC_MESSAGES, "it_IT@euro");La chiamata setlocale può anche essere utilizzata per ottenere il settaggio attuale. Es:
printf("LC_MESSAGES=%s\n",setlocale(LC_MESSAGES, NULL));Se la chiamata setlocale avviene con argomento una stringa vuota ha l'effetto di impostare la localizzazione contenuta nelle variabili di ambiente, cioè quella di default per l'utente corrente.
Il file catalogo da utilizzare, denominato dominio, viene indicato dalla chiamata:
textdomain( "nomeprogramma" );come per setlocale anche textdomain può essere utilizzata per ottenere il settaggio attuale.
Il path di ricerca, quando no standard, viene indicato tramite:
bindtextdomain( "nomeprogramma", path_assoluto_o_relativo );Si noti che il path specificato non contiene direttamente i file catalogo ma contiene una gerarchia di directory pensata per i differenti linguaggi e differenti aspetti dell'internazionalizzazione.
Ad esempio con:
setlocale(LC_MESSAGES, "it"); bindtextdomain( "myProgram", "/home/utente/.locale" ); textdomain( "myProgram" );le stringhe tradotte saranno ricercate nel file:
/home/utente/.locale/it/LC_MESSAGES/myProgram.mo
NOTA IMPORTANTE:
In alcuni sistemi GNU la variabile d'ambiente LANGUAGE sovrascrive
l'impostazione setlocale. Per rendere effettiva la setlocale sarà quindi
necessario impostare:
export LANGUAGE=""Questa caratteristica risulta comoda per poter testare un programma senza modificare il sorgente o le impostazioni della workstation in uso. E' infatti possible modificare solo sulla riga di comando l'impostazione di LANGUAGE:
LANGUAGE="fr_FR"; programma_da_testare
Per applicare l'internazionalizzazione dei messaggi ad un codice già esistente:
#include <libintl.h>
setlocale( LC_ALL, "linguaggio" ); bindtextdomain( "nomeprogramma", "path_dei_cataloghi" ); textdomain( "nomeprogramma" );
printf(gettext("Messaggio da stampare"));
xgettext -o nomeprogramma.mo sorgente1.cpp sorgente2.cpp ...
msgfmt -o nomeprogramma.mo nomeprogramma.poed installati nel path desiderato.
La chiamata setlocale serve ad informare il sistema sulla localizzazione da utilizzare. Nel caso in cui non venga effettuata sarà utilizzata un particolare valore di default ("C") che significa "nessuna localizzazione".
Nella maggior parte dei codici la chiamata a setlocale viene effettuata con argomento una stringa vuota:
setlocale( LC_ALL, "" );che ha l'effetto di impostare come localizazione quella predefinita dal sistema per l'utente in uso.
Per verificare quale sia la localizzazione predefinita utilizzare (a console) il comando
localeche stampa in output l'elenco delle variabili d'ambiente che influenzano la localizzazione ed i rispettivi valori.
Per cambiare temporaneamente la localizzazione (ad esempio per testare il nostro codice con differenti localizzazioni) è possibile modificare le variabili di ambiente immediatamente prima di eseguire il programma da testare:
LANG=fr_FR@euro; ./programma_da_testare
Si noti che la chiamata setlocale fallisce se la localizzazione richiesta non è tra quelle supportate dal sistema (e normalmente il numero di localizzazioni supportate è ristretto per motivi di occupazione del disco). Questo impedisce di cambiare temporaneamente la propria localizzazione ad esempio per testare un nuovo file catalogo.
Per avere un elenco delle localizzazioni supportate utilizzare il comando
locale -a
Mentre per estendere il supporto:
/etc/locale.genaggiungendo all'elenco le localizzazioni desiderate.
locale-genche rilegge il file di configurazione e genera le nuove localizzazioni
L'editor di interfacce FLTK è già predisposto per l'utilizzo dell'internazionalizzazione GNU.
Dal menu
edit->project settings ->internationalizationselezionare: GNU gettext. Questo è sufficiente perchè tutte le stringhe "label" vengano sostituite con analoghe chiamate a gettext("label").
Per estrarre le stringhe traducibili dal codice è possibile utlizzare il comando di menu:
file->write stringche salva un file di testo contenente le coppie msgstr-msgid utilizzate nel progetto. Lo stesso risultato è comunque ottenibile con il comando xgettext. Il file va editato per eliminare le eventuali ripetizioni. Il file può essere utlizzato direttamente oppure unito ad altro contenuto se, come probabile, le stringhe da tradurre non si limitano a quelle della finestra.
Alcune versioni di Fluid (almeno fino all 1.1.8) definiscono i menu in modo statico. Questo disabilita la loro traduzione perchè la chiamata a gettext avviene prima del setup della localizzazione. Per risolvere il problema è sufficiente inserire dopo la creazione della finestra e dopo aver inizializzato la localizzazione il seguente codice:
// wMain:puntatore alla finestra FLTK // mMenu:puntatore al menu che deve essere tradotto for (int i=0;i<wMain->mMenu->size();i++){ if (wMain->mMenu->menu()[i].text){ ((Fl_Menu_Item*)&wMain->mMenu->menu()[i])->label(gettext(wMain->mMenu->menu()[i].text)); } }che ha l'effetto di ripassare la traduzione di tutte le label del menu.