L'articolo di questa settimana torniamo a parlare dell'internazionalizzazione in PHP, argomento che avevamo iniziato a trattare il mese scorso con gli articoli su mbstring e iconv e che mi ero riproposto di continuare dopo una breve pausa. Oggi cercherò di introdurre nel modo più completo possibile una libreria, fornita con la distribuzione standard di PHP, che si occupa per l'appunto di facilitare il processo di internazionalizzazione e traduzione delle stringhe all'interno di un file PHP.
La libreria in questione si chiama gettext, ed è un porting per PHP dell'omonima libreria sviluppata nell'ambito del progetto GNU. Grazie ad una struttura molto semplice ed a un insieme di strumenti aggiuntivi per la gestione e l'aggiornamento delle traduzioni, possiamo indicare gettext come una delle migliori soluzioni per gestire comodamente il problema che stiamo analizzando in questi articoli. Tengo a precisare che, diversamente dalle librerie iconv
e mbstring
, che si occupavano della conversione tra differenti set di caratteri e codifiche, la libreria gettext
ha una funzionalità molto più pratica e direttamente collegata a modificare le stringhe restituite in base alle impostazioni di lingua assegnate dal programmatore.
Ritengo gettext
sia la soluzione migliore disponibile, anche se bisogna ricordare che è necessario il riavvio del Web Server nel momento in cui risultasse necessario variare i file di traduzione a seguito di modifiche.
La libreria gettext
Iniziamo subito con un esempio concreto di come utilizzare la libreria gettext all'interno di un file PHP:
<?php
$lang = "it";
$locale = "it_IT";
if($_GET["lang"] == "en")
{
$lang = "en";
$locale = "en_US";
}
putenv("LC_ALL=$locale");
setlocale(LC_ALL, $locale);
bindtextdomain("messages", "/locale");
textdomain("messages");
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo $lang; ?>" lang="<?php echo $lang; ?>">
<head>
<title>
<?php echo _("Titolo della finestra"); ?>
</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<h1><?php echo _("Prova"); ?></h1>
<p><?php echo _("Le stringhe possono anche essere molto lunghe e
contenere caratteri non Unicode o accentati. Le stringhe possono anche essere molto lunghe
e contenere caratteri non Unicode o accentati. Le stringhe possono anche essere molto
lunghe e contenere caratteri non Unicode o accentati. Le stringhe possono anche essere
molto lunghe e contenere caratteri non Unicode o accentati. Le stringhe possono anche
essere molto lunghe e contenere caratteri non Unicode o accentati. Le stringhe possono
anche essere molto lunghe e contenere caratteri non Unicode o accentati. Le stringhe
possono anche essere molto lunghe e contenere caratteri non Unicode o accentati. "); ?></p>
</body>
</html>
In questo esempio generiamo una pagina con del contenuto testuale che verrà automaticamente tradotto dalla libreria gettext
in base alla lingua impostata precedentemente. In primo luogo ho definito delle righe di codice che permettono di variare la traduzione del contenuto scelto modificando il parametro lang della query string. Successivamente lo script procede con l'assegnazione della variabile d'ambiente LC_ALL
alla lingua da utilizzare, con l'assegnazione del locale utilizzato e con l'identificazione del dominio da utilizzare per la traduzione.
Le chiamate a putenv
e setllocale
non sono specifiche della libreria gettext
, ma permettono a questa ed a tutte le altre funzionalità che si basano sulle variabili d'ambiente per il controllo della lingua e del charset utilizzato di lavorare correttamente durante il processo di internazionalizzazione.
La definizione ed assegnazione di un dominio è un processo fondamentale per il corretto funzionamento dello script: un dominio non è altro che una locazione fisica del filesystem (la cui struttura verrà analizzata in seguito) in cui sono salvati i file compilati contenenti le traduzioni; grazie alla funzione bindtextdomain
viene associato a questo path fisico un nome, che può essere utilizzato come parametro per la funzione textdomain al fine di variare il dominio utilizzato per la generazione dell'output.
Una volta fatto il setup della libreria si può procedere con l'utilizzo di quest'ultima: prima di lavorare con una stringa che necessita di essere tradotta in base alla lingua corrente, la passiamo come argomento alla funzione gettext
(o il suo alias _()
definito per velocizzare la scrittura e rendere meno intrusivo l'utilizzo della libreria) che si occupa di restituirci la versione tradotta oppure la stringa stessa in caso non vi siano traduzioni appropriate.
Localizzare le applicazioni
Una volta impostati i nostri script PHP, è necessario utilizzare degli strumenti distribuiti con la libreria stessa che ci permettono di gestire e manipolare le traduzioni. Per prima cosa è necessario impostare la struttura delle directory rappresentanti il nostro o i nostri domini seguendo un'organizzazione ben precisa: per ogni dominio di interesse creiamo una sotto directory per ogni lingua accettata dalla nostra applicazione; ognuna di queste directory conterrà un'altra sottodirectory chiamata LC_MESSAGES
, contenente a sua volta dei file .mo e .po contenenti le traduzioni sorgenti e compilate. Il nome di questi file dipende da quello utilizzato durante il bind del dominio.
Seguendo il nostro esempio e sapendo che le lingue accettate saranno l'italiano e l'americano, procederemo nell'impostare il dominio nel seguente modo:
/locale /en_US /LC_MESSAGES messages.po messages.mo /it_IT /LC_MESSAGES messages.po messages.mo
I file .po vengono estratti da file sorgenti utilizzando il comando xgettext
:
xgettext -n *.php
In questo modo viene creato un file messages.po testuale contenente le stringhe sorgenti e le traduzioni da applicarvi. Il formato del file è molto semplice e segue la struttura seguente:
#SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSIONn"
"POT-Creation-Date: 2002-04-06 21:44-0500n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONEn"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>n"
"Language-Team: LANGUAGE <LL@li.org>n"
"MIME-Version: 1.0n"
"Content-Type: text/plain; charset=CHARSETn"
"Content-Transfer-Encoding: 8bitn"
#: gettext_example.php:21
msgid "Titolo della finestra"
msgstr ""
I simboli di cancelletto (#) indicano i commenti; il comando msgid
indica l'id a cui farà riferimento la traduzione specificata nel seguente comando msgstr. Ogni coppia di comandi msgid
ed msgstr
deve essere separata dalla precedente da una riga vuota. All'inizio del file è definito un header in cui possono essere specificate alcune informazioni specifiche per la traduzione.
Una volta estratto e modificato il file .po, possiamo occuparci della sua compilazione in file .mo, un formato binario che velocizza le ricerche delle traduzioni da parte della libreria gettext
:
msgfmt messages.po
Una volta creato questo file possiamo inserirlo nella corretta posizione all'interno del dominio, riavviare il server e controllare il corretto funzionamento della nostra applicazione.
Una volta effettuate tutte le traduzioni, compilati i file .po in .mo e posizionatili nella corretta posizione all'interno del dominio, l'applicazione può essere ritenuta pronta per essere distribuita. Ovviamente dopo la distribuzione potrebbe risultare necessario modificare leggermente alcuni file sorgenti aggiungendo nuovo testo e variando quelli presenti. Se in queste situazioni utilizzassimo il comando xgettext per recuperare i file .po avremmo dei file completamente vuoti e dovremmo inserire sempre i contenuti dell'inizio. Fortunatamente un tool gettext
ci viene incontro con msgmerge
.
$ ls test.php $ xgettext -n test.php $ ls test.php messages.po // ... // Traduciamo il file // ... $ msgfmt messages.po $ ls test.php messages.po messages.mo // ... // Modifichiamo il file test.php // ... $ mv messages.po old.po $ xgettext -n *.php $ ls test.php messages.po messages.mo old.po $ msgmerge old.po messages.po --output-file=new.po $ ls test.php messages.po messages.mo new.po old.po // ... // Ora traduciamo il nuovo file new.po e successivamente procediamo con // la compilazione // ... $ msgfmt new.po
Come possiamo notare da questa simulazione di sessione di lavoro, grazie a msgmerge
possiamo unire le traduzioni di due file .po in modo che vengano mantenute quelle già esistenti e vengano aggiunte quelle non ancora presenti.
Gestione dei plurali
Prima di concludere vorrei trattare un'altra funzionalità molto semplice ma molto importante della libreria gettext: la gestione dei plurali. Come tutti sappiamo nella maggior parte delle lingue è necessario apportare modifiche anche sostanziali ai nomi per far sì che rappresentino correttamente il plurale. La libreria gettext
fornisce un sistema molto semplice che permette di gestire la traduzione delle stringhe supportando il singolare ed il plurale:
<?php
// ....
$commenti = 1;
printf(ngettext("%d commento", "%d commenti", $commenti), $commenti);
echo "<br />";
$commenti = 10;
printf(ngettext("%d commento", "%d commenti", $commenti), $commenti);
?>
Nell'esempio precedente, verrà restituita la stringa %d "commento" o %d "commenti" in base al fatto che il valore passato come terzo argomento alla funzione ngettext sia valutato come plurale o singolare in base alla regole della lingua utilizzata correntemente. La regola per decidere se un valore è da identificare come singolare o plurale viene specificata nell'header del file .po utilizzando la seguente sintassi:
Plural-Forms: nplurals=2; plural=n != 1;
Dopo i due punti specifichiamo il numero di plurali accettato dalla lingua rappresentata dal file .po e, successivamente, specifichiamo un'espressione, scritta nella sintassi del linguaggio C, che assegna un valore vero o falso alla variabile plural
in base al valore della variabile n. Nell'esempio precedente verrà identificata come plurale qualunque valore diverso da uno.
Conclusioni
Dopo questa introduzione alla libreria gettext
e all'utilizzo degli strumenti correlati dovreste essere in grado di internazionalizzare in modo completo le vostre applicazioni scritte in PHP. L'importante è ricordarsi che i file sorgenti vanno impostati fin dall'inizio con il supporto per l'internazionalizzazione e la localizzazione; è buona pratica aggiungere sempre il supporto in modo da non doversi trovare a dover modificare centinaia di stringhe successivamente quando le esigenze si faranno più pressanti.