La traduzione del codice con GNU gettext

Autore: Matteo Lucarelli
ultima versione su: matteolucarelli.altervista.org
Documento in costruzione

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.

Introduzione

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).

Generalità

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.

Formato dei 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.po 
che 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.mo
Dove $(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.mo
contierrà 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

Le chiamate nel codice

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

Applicazione a codice già esistente

Per applicare l'internazionalizzazione dei messaggi ad un codice già esistente:

  1. Inserire
    #include <libintl.h>
    
  2. Inserire i necessari settaggi prima della prima chiamata a gettext:
    setlocale( LC_ALL, "linguaggio" );
    bindtextdomain( "nomeprogramma", "path_dei_cataloghi" );
    textdomain( "nomeprogramma" );
    
  3. sostituire tutte le stringhe traducibili con chiamate a gettext:
    printf(gettext("Messaggio da stampare"));
    
  4. utilizzare il programma xgettext per generare automaticamente un file .po contenente tutte le stringhe soggette a traduzione:
    xgettext -o nomeprogramma.mo sorgente1.cpp sorgente2.cpp ...
    
  5. a questo punto il file .po va editato per eliminare eventuali ripetizioni, inserire commenti, ecc.
  6. dovremo poi creare una copia del file .po per ogni linguaggio, inserendo le necessarie traduzioni.
  7. I file .po ottenuti vanno quindi compilati:
    msgfmt -o nomeprogramma.mo nomeprogramma.po 
    
    ed installati nel path desiderato.

Localizzazioni e supporto

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

locale
che 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:

  1. Editare il file
    /etc/locale.gen 
    
    aggiungendo all'elenco le localizzazioni desiderate.
  2. Dare il comando:
    locale-gen 
    
    che rilegge il file di configurazione e genera le nuove localizzazioni
NOTA: le istruzioni precedenti sono testate sulla distribuzione Debian, ma non dovrebbero variare di molto su altri sistemi.

Utilizzo con fluid

L'editor di interfacce FLTK è già predisposto per l'utilizzo dell'internazionalizzazione GNU.

Dal menu

edit->project settings ->internationalization
selezionare: 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 string 
che 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.

matteolucarelli.altervista.org
©opyright info