Scopo | API per catalogo di messaggi per internazionalizzazione. |
Versione Python | 2.1.3 e successive |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo gettext
Il modulo gettext fornisce una implementazione in puro Python compatibile con la libreria GNU gettext per traduzione di messaggi e gestione di catalogo. Gli strumenti disponibili con la distribuzione del sorgente Python consente di estrarre messaggi dalla propria sorgente, costruire un catalogo di messaggi contenente le traduzioni, ed usarlo per stampare un messaggio appropriato per l'utente in fase di esecuzione.
I cataloghi di messaggi possono essere usati per fornire interfacce internazionalizzate per il proprio programma, mostrando messaggi nella lingua appropriata per l'utente. Essi possono anche essere usati per altre personalizzazioni di messaggi, inclusa lo " skinning " di una interfaccia per diversi wrappers o partner.
Il processo per impostare ed usare le traduzioni consta di cinque passi:
xgettext
per estrarre le stringhe e creare un file con estensione
.pot
, o modello di traduzione. Il modello è un file di testo con una copia di tutte le stringhe che sono state identificate ed i segnaposto per le loro traduzioni.
.pot
al traduttore, modificando l'estensione in
.po
. Il file
.po
è un file sorgente modificabile usato come input per la fase di compilazione. Il traduttore dovrebbe aggiornare i dati di intestazione nel file e fornire le traduzioni per tutte le stringhe.
.po
completato, si compila il file di testo nel formato binario del catalogo usando
msgfmt
. Il formato binario viene usato dal codice che cerca nel catalogo in fase di esecuzione.
Si analizzano ora questi passi più dettagliatamente, partendo con le modifiche che occorre fare al proprio codice.
gettext
funziona cercando le stringhe letterali incorporate nel proprio programma in un database di traduzioni ed estraendo la stringa tradotta appropriata. Ci sono diverse varianti delle funzionI per accedere al catalogo, a seconda che si lavori con stringhe Unicode o meno. Il modello tradizionale consiste nel legare la funzione di ricerca che si vuole usare al nome
_
così da non infestare il proprio codice con tante chiamate a funzioni con nomi più lunghi.
Il programma di estrazione dei messaggi
xgettext
cerca i messaggi incorporati in chiamate alle funzioni di ricerca del catalogo. Comprende diversi linguaggi sorgente, ed usa un appropriato analizzatore per ognuno. Se si usano degli alias per le funzioni di ricerca od occorre aggiungere funzioni supplementari, si possono passare a
xgettext
i nomi dei simboli aggiuntivi da considerare quando si estraggono i messaggi.
Ecco un semplice script con un singolo messaggio pronto per essere tradotto.
import gettext
# Imposta l'accesso al catalogo di messaggi
t = gettext.translation('gettext_example', 'locale', fallback=True)
_ = t.ugettext
print _("Questo messaggio si trova nello script.")
In questo caso viene usata la versione Unicode della funzione di ricerca
ugettext
. Il testo "Questo messaggio si trova nello script." è il messaggio da sostituire dal catalogo. E' stata abilitata la modalità
fallback
, in modo che se lo script viene eseguito senza un catalogo di messaggi viene stampato il messaggio in linea
$ python gettext_example.py Questo messaggio si trova nello script.
Il passo successivo è l'estrazione del(i) messaggio(i) e la creazione del file
.pot
, usando
pygettext.py
$ xgettext -d gettext_example -o gettext_example.pot gettext_example.py
Il file in uscita (gettext_example.pot) assomiglia a questo:
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2010-04-12 21:11+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: gettext_example.py:11 msgid "Questo messaggio si trova nello script." msgstr ""
I cataloghi di messaggi sono installati in directory organizzate per
dominio
e
linguaggio
. In genere il dominio è un valore univoco, tipo il nome della propria applicazione. In questo caso è stato usato
gettext_example
. Il valore del linguaggio viene fornito dall'ambiente dell'utente in fase di esecuzione, attraverso una delle variabili di ambiente
LANGUAGE
,
LC_ALL
,
LC_MESSAGES
o
LANG
, a seconda della configurazione e piattaforma. Il linguaggio è impostato a
it_IT
(
en_US
nell'originale - n.d.t.) e sarà quello usato in tutti gli esempi che seguono.
Ora che si ha un modello il prossimo passo è creare la struttura di directory necessaria e copiare il modello al posto giusto. Si utilizzerà la directory
locale
all'interno della cartella dei sorgenti di questo sito come radice della directory del catalogo dei messaggi; tipicamente, si vorrà utilizzare una directory accessibile all'intero sistema. Il percorso completo della sorgente in input del catalogo è
//LC_MESSAGES/.po
, e l'effettivo catalogo ha l'estensione
.mo
.
Per questa configurazione, occorre copiare
gettext_example.pot
in
locale/it_IT/LC_MESSAGES/gettext_example.po
, quindi modificare i valori nell'intestazione ad aggiungere i messaggi alternativi. Il risultato è circa questo:
# Messaggi da gettext_example.py # Copyright (C) 2015 # Roberto Pauletto, 2015. # msgid "" msgstr "" "Project-Id-Version: PyMOT\n" "Report-Msgid-Bugs-To: Roberto Pauletto \n" "POT-Creation-Date: 2015-10-20 21:11+0200\n" "PO-Revision-Date: 2015-10-20 21:11+0200\n" "Last-Translator: Roberto Pauletto \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: gettext_example.py:11 msgid "Questo messaggio si trova nello script." msgstr "Questo messaggio si trova nel catalogo it_IT"
Il catalogo viene costruito dal file
.po
utilizzando
msgformat
$ cd locale/it_IT/LC_MESSAGES/; msgfmt -o gettext_example.mo gettext_example.po
Ora quando viene eseguito lo script, viene stampato il messaggio presente nel catalogo in luogo della stringa in linea nel codice:
$ python gettext_example.py Questo messaggio si trova nel catalogo it_IT
Come sopra descritto, la directory
locale
contiene i cataloghi di messaggi organizzati in base alla lingua con i nomi dei cataloghi attribuiti per il
dominio
del programma. I diversi sistemi operativi definiscono i loro propri valori predefiiniti, tuttavia
gettext
non conosce tutti quei valori. Per la maggior parte delle volte è più sicuro attribuire esplicitamente un valore a
localedir
piuttosto che dover dipendere dal fatto che il valore predefinito sia valido.
La porzione lingua nel percorso viene presa da una delle diverse variabili di ambiente che possono essere usate per configurare le caratteristiche di localizzazione (
LANGUAGE
,
LC_ALL
,
LC_MESSAGES
e
LANG
). La prima variabile che viene trovata impostata viene utilizzata. Lingue multiple possono essere selezionate separandone i valori tramite i due punti (
:
). Per trovare i cataloghi corrispondenti alla lingua utilizziamo questo script:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gettext
catalogs = gettext.find('gettext_example', 'locale', all=True)
print 'Cataloghi:', catalogs
Si può ora illustrare come questo funziona creando un paio di cataloghi di messaggi ed eseguendo qualche esperimento.
$ cd locale/fr_FR/LC_MESSAGES/; msgfmt -o gettext_example.mo gettext_example.po $ cd locale/es_ES/LC_MESSAGES/; msgfmt -o gettext_example.mo gettext_example.po $ LANGUAGE=fr_FR $ python gettext_find.py Cataloghi: ['locale/fr_FR/LC_MESSAGES/gettext_example.mo'] $ LANGUAGE=es_ES $ python gettext_find.py Cataloghi: ['locale/es_ES/LC_MESSAGES/gettext_example.mo'] LANGUAGE=es_ES:fr_FR $ python gettext_find.py Cataloghi: ['locale/es_ES/LC_MESSAGES/gettext_example.mo', 'locale/fr_FR/LC_MESSAGES/gettext_example.mo']
Sebbene
find()
mostri l'elenco completo dei cataloghi, solo il primo nella sequenza viene effettivamente caricato per la ricerca dei messaggi.
$ LANGUAGE=fr_FR $ python gettext_example.py Questo messaggio si trova nel catalogo fr_FR $ LANGUAGE=fr_FR:es_ES $ python gettext_example.py Questo messaggio si trova nel catalogo fr_FR $ LANGUAGE=es_ES:fr_FR $ python gettext_example.py Questo messaggio si trova nel catalogo es_ES
Mentre una semplice sostituzione di messaggi può gestire la maggior parte delle proprie esigenze di traduzione, gettext tratta la gestione dei plurali come caso speciale. A seconda della lingua, la differenza tra le forme singolari e plurali di un messaggio potrebbero variare solo nella parte finale di una singola parola, oppure l'intera struttura della frase potrebbe essere diversa. Potrebbero anche esserci diverse forme a seconda del livello di pluralità . Per facilitare (e rendrere possibile) la gestione dei plurali, si ha un insieme a se stante di funzioni per ottenere la forma plurale di un messaggio.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from gettext import translation
import sys
t = translation('gettext_plural', 'locale', fallback=True)
num = int(sys.argv[1])
msg = t.ungettext('%(num)d significa singolare.', '%(num)d significa plurale.', num)
# Ci si deve comunque occupare di aggiungere i valori al messaggio
print msg % {'num':num}
$ xgettext -L Python -d gettext_plural -o gettext_plural.pot gettext_plural.py
Visto che ci sono forme alternative da tradurre, le sostituizioni sono elencate in un array. Utilizzare un array consente traduzioni per lingue con forme multiple di plurali (il polacco, ad esempio ) ha diverse forme che indicano le relative quantità.
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-24 21:33+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: gettext_plural.py:9 #, python-format msgid "%(num)d significa singolare." msgid_plural "%(num)d significa plurale." msgstr[0] "" msgstr[1] ""
Oltre al riempimento delle stringhe di traduzione, si deve anche descrivere il modo nel quale sono formati i plurali in modo che la libreria sappia come muoversi nell'indice dell'array per qualsiasi valore passato. La riga:
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
include due valori da rimpiazzare manualmente,
nplurals
è un intero che indica la dimensione dell'array (il numero di transazioni utilizzate) e
plural
è una espressione in linguaggio C per convertire la quantità ricevuta in un indice nell'array quando si cerca la traduzione. La stringa letterale
n
viene sostituita con la quantità passata in
ungettext()
.
Ad esempio, l'italiano comprende due forme di plurale. Una quantità di
0
viene trattata come plurale ("0 banane"). L'elemento Plural-Forms dovrebbe essere tipo questo:
Plural-Forms: nplurals=2; plural=n != 1;
La traduzione della forma singolare andrebbe in posizione 0 e quella plurale in posizione 1.
# SOME DESCRIPTIVE TITLE. # Copyright (C) 2015 # This file is distributed under the same license as the PACKAGE package. # Roberto Pauletto, 2015. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PyMOTW2\n" "Report-Msgid-Bugs-To: Roberto Pauletto \n" "POT-Creation-Date: 2015-10-24 21:33+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Roberto Pauletto \n" "Language-Team: Italian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;" #: gettext_plural.py:9 #, python-format msgid "%(num)d significa singolare." msgid_plural "%(num)d significa plurale." msgstr[0] "In it_IT, %(num)d è singolare." msgstr[1] "In it_IT, %(num)d è plurale."
Se si esegue qualche volta lo script di prova dopo che il catalogo è stato compilato, si può notare come diversi valori di N sono convertiti come indici per le stringhe di traduzione.
$ cd locale/it_IT/LC_MESSAGES/; msgfmt -o gettext_plural.mo gettext_plural.po $ python gettext_plural.py 0 In it_IT, 0 è plurale. $ python gettext_plural.py 1 In it_IT, 1 è singolare. $ python gettext_plural.py 2 In it_IT, 2 è plurale.
Il perimetro del proprio sforzo di traduzione definisce come si installa e si usano le funzioni di gettext nel proprio codice.
Per traduzioni a livello di applicazione, potrebbe essere accettabile installare una funzione tipo
unggettext()
globalmente utilizzando lo spazio dei nomi
__builtins__
visto che si ha il controllo sul livello più alto del codice dell'applicazione
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gettext
gettext.install('gettext_example', 'locale', unicode=True, names=['ngettext'])
print _('Questo messaggio è nello script.')
La funzione
install()
lega
gettext()
al nome
_()
nello spazio dei nomi
__builtins__
. Aggiunge anche
ngettext()
ed altre funzioni elencate in
names
. Se
unicode
è vero, le versioni Unicode delle funzioni sono utilizzate in luogo delle versioni ASCII predefinite.
Per una libreria, o modulo singolo, non è una buona idea modificare
__builtins__
in quanto non si conosce quali conflitti si possono introdurre con valore globale di applicazione. Si possono importare o riagganciare il nomi delle funzioni di traduzione a mano all'inizio del proprio modulo.
import gettext
t = gettext.translation('gettext_example', 'locale', fallback=True)
_ = t.ugettext
ngettext = t.ungettext
print _('Questo messaggio è nello script.')
Vedere anche: