gettext - Cataloghi di Messaggi
Scopo: API per l'internazionalizzazione di cataloghi di messaggi
Il modulo gettextfornisce una pura implementazione Python compatibile con la omonima libreria GNU per la traduzione di messaggi e la gestione di cataloghi. Gli strumenti disponibili con la distribuzione dei sorgenti di Python consente di estrarre messaggi da un insieme di file sorgente, costruire un catalogo di messaggi contenente traduzioni ed usare il catalogo per visualizzare messaggi appropriati all'utente in fase di esecuzione.
I cataloghi di messaggi possono essere usati per fornire interfacce internazionalizzate per un programma, mostrando messaggi nella lingua appropriata per l'utente. Possono anche essere usati per altr personalizzazioni di messaggi, compreso lo "skinning" di un interfaccia per diversi contenitori o partner.
Sguardo di Insieme del Flusso di Lavoro di Traduzione
Il processo per impostare ed usare le traduzioni consta di cinque passi:
- Marcatura nel codice sorgente delle stringhe letterali che contengono i messaggi da tradurre
Si inizia con l'identificare i messaggi all'interno del sorgente che occorre tradurre, marcando le stringhe letterali in modo che il programma di estrazione le possa trovare. - Estrazione dei messaggi
Dopo avere identificato le stringhe traducibili nel sorgente, si usixgettext
per estrarle 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. - Traduzione dei messaggi.
Passare una copia del file.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. - "Compilare" il catalogo messaggi dalla traduzione.
Quando il traduttore restituisce il file.po
completato, si compila il file di testo nel formato binario del catalogo usandomsgfmt
. Il formato binario viene usato dal codice che cerca nel catalogo in fase di esecuzione. - Caricare ed attivare l'appropriato catalogo di messaggi in fase di esecuzione.
Il passo finale è quello di aggiungere poche righe di codice all'applicazione per configurare e caricare il catalogo di messaggi ed installare la funzione di traduzione. Ci sono un paio di metodi per fare questo, con associati compromessi, ognuno dei quali viene trattato di seguito.
Si analizzano ora questi passi più dettagliatamente, partendo con le modifiche necessarie nel codice.
Creare Cataloghi di Messaggi dal Codice Sorgente
gettext funziona cercando le stringhe letterali incorporate in un database di traduzioni ed estraendo la stringa tradotta appropriata. Il modello tradizionale consiste nel legare la funzione di ricerca appropriata al nome _
(un singolo carattere di sottolineatura) così da non infestare il codice con tante chiamate a funzioni con nomi più lunghi.
Il programma di estrazione dei messaggi xgettext
cerca i messaggi incorporati nelle 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.
Questo script ha un singolo messaggio pronto per essere tradotto.
# gettext_example.py
import gettext
# Set up message catalog access
t = gettext.translation(
'example_domain', 'locale',
fallback=True,
)
_ = t.gettext
print(_("Questo messaggio si trova nello script."))
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.
$ python3 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 -o example.pot gettext_example.py /dati/dev/python/pymotw3restyling/dumpscripts/_pyrunner.sh: line 3: xgettext: command not found
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 <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-11-28 18:18+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: gettext_example.py:12 msgid "Questo messaggio si trova nello script." msgstr ""
I cataloghi di messaggi sono installati in directory organizzate per dominio e linguaggio. Il dominio è fornito dalla applicazione oppure dalla libreria ed in genere è un un valore univoco, tipo il nome dell'applicazione. In questo caso il dominio in gettext_example
è example_domain
. 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. Questi esempi sono stati eseguiti con il linguaggio è impostato a it_IT
(en_US
nell'originale - n.d.t.).
Ora che si ha un modello il prossimo passo è creare la struttura di directory necessaria e copiare il modello al posto giusto. La directory locale
all'interno della cartella dei sorgenti di questo sito serve come radice della directory del catalogo dei messaggi per questi esempi; tipicamente, si vorrà utilizzare una directory accessibile all'intero sistema in modo che tutti gli utenti abbiano accesso ai cataloghi di messaggi.. Il percorso completo della sorgente in input del catalogo è //LC_MESSAGES/.po
, e l'effettivo catalogo ha l'estensione .mo
.
Il catalogo è creato copiando example.pot
in locale/it_IT/LC_MESSAGES/example.po
, quindi modificandone i valori nell'intestazione ad aggiungendo i messaggi sostitutivi. Il risultato è circa questo:
# Messaggi da gettext_example.py # Copyright (C) 2017 # Roberto Pauletto <pymotw-it@robyp.x10host.com>, 2017. # msgid "" msgstr "" "Project-Id-Version: PyMOTW-3\n" "Report-Msgid-Bugs-To: Roberto Pauletto <pymotw-it@robyp.x10host.com>\n" "POT-Creation-Date: 2017-10-20 21:11+0200\n" "PO-Revision-Date: 2017-10-20 21:11+0200\n" "Last-Translator: Roberto Pauletto <pymotw-it@robyp.x10host.com>\n" "Language-Team: Italian <pymotw-it@robyp.x10host.com><LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: gettext_example.py:12 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 /dati/dev/python/pymotw3restyling/dumpscripts/_pyrunner.sh: line 3: msgfmt: command not found
Il dominio in gettext_example.py
è example_domain
ma nel file viene chiamato example.pot
. Per fare in modo che gettext trovi il file di traduzione corretto i nomi devono corrispondere.
# gettext_example_corrected.py
import gettext
t = gettext.translation(
'example', 'locale',
fallback=True,
)
Ora quando viene eseguito lo script, viene stampato il messaggio presente nel catalogo in luogo della stringa in linea nel codice:
$ python gettext_example_corrected.py Questo messaggio si trova nel catalogo it_IT.
Cercare il Catalogo Messaggi in Fase di Esecuzione
Come sopra descritto, la directory sys.prefix
+ '/share/locale'
ma 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. Sarà la funzione find()
che dovrà trovare un catalogo di messaggi appropriati in fase di esecuzione.
# gettext_find.py
import gettext
catalogs = gettext.find('gettext_example', 'locale', all=True)
print 'Cataloghi:', catalogs
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 vedere come funziona, si usa un secondo catalogo per eseguire qualche esperimento.
Si può ora illustrare come questo funziona creando un paio di cataloghi di messaggi ed eseguendo qualche esperimento.
$ cd locale/en_CA/LC_MESSAGES; msgfmt -o example.mo example.po $ cd ../../.. $ python3 gettext_find.py Cataloghi: ['locale/en_US/LC_MESSAGES/example.mo'] $ LANGUAGE=en_CA python3 gettext_find.py Cataloghi: ['locale/en_CA/LC_MESSAGES/example.mo'] $ LANGUAGE=en_CA:en_US python3 gettext_find.py Cataloghi: ['locale/en_CA/LC_MESSAGES/example.mo', 'locale/en_US/LC_MESSAGES/example.mo'] $ LANGUAGE=en_US:en_CA python3 gettext_find.py Cataloghi: ['locale/en_US/LC_MESSAGES/example.mo', 'locale/en_CA/LC_MESSAGES/example.mo']
Sebbene find()
mostri l'elenco completo dei cataloghi, solo il primo nella sequenza viene effettivamente caricato per la ricerca dei messaggi.
$ python3 gettext_example_corrected.py Questo messaggio si trova nel catalogo en_US. $ LANGUAGE=en_CA python3 gettext_example_corrected.py Questo messaggio si trova nel catalogo en_CA. $ LANGUAGE=en_CA:en_US python3 gettext_example_corrected.py Questo messaggio si trova nel catalogo en_CA. $ LANGUAGE=en_US:en_CA python3 gettext_example_corrected.py Questo messaggio si trova nel catalogo en_US.
Valori Plurali
Mentre una semplice sostituzione di messaggi può gestire la maggior parte delle 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 (ed in taluni casi rendere possibile) la gestione dei plurali, ci sono un insieme a se stante di funzioni per ottenere la forma plurale di un messaggio.
# gettext_plural.py
from gettext import translation
import sys
t = translation('plurale', 'locale', fallback=False)
num = int(sys.argv[1])
msg = t.ngettext('{num} significa singolare.',
'{num} significa plurale.',
num)
# Occorre anche aggiungere i valori al messaggio.
print(msg.format(num=num))
Si usi ngettext()
per accedere alla sostituzione nella forma plurale di un messaggio. Gli argomenti sono i messaggi da tradurre ed il contatore dell'elemento.
$ xgettext -L Python -d gettext_plural -o gettext_plural.pot gettext_plural.py /dati/dev/python/pymotw3restyling/dumpscripts/_pyrunner.sh: line 3: xgettext: command not found
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 <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-12-03 16:01+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\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:8 #, python-brace-format msgid "{num} significa singolare." msgid_plural "{num} significa plurale." msgstr[0] "" msgstr[1] ""
Oltre al riempimento delle stringhe di traduzione, la libreria deve anche essere informata circa il modo nel quale sono formati i plurali in modo che 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 traduzioni 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 a ungettext()
.
Ad esempio, l'italiano comprende due forme di plurale. Una quantità di 0
viene trattata come plurale ("0 banane"). L'elemento
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 <pymotw-it@robyp.x10host.com>, 2015. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PyMOTW2\n" "Report-Msgid-Bugs-To: Roberto Pauletto <pymotw-it@robyp.x10host.com>\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 <pymotw-it@robyp.x10host.com>\n" "Language-Team: Italian <pymotw-it@robyp.x10host.com><LL@li.org>\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.
Localizzazione in Applicazione contro Modulo
Il perimetro del proprio sforzo di traduzione definisce come si installa e si usano le funzioni di gettext nel proprio codice.
Localizzazione a Livello di Applicazione
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.
Localizzazione a Livello di Modulo
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:
- gettext
- La documentazione della libreria standard per questo modulo
- locale
- Altri strumenti di localizzazione
- GNU gettext
- I formati del catalogo messaggi, API, ecc. per questo modulo sono tutti basati sul pacchetto gettext originale da GNU. I formati del file catalogo sono compatibili, e gli script da riga di comando hanno opzioni simili (se non identiche). Il manuale GNU gettext (in inglese - n.d.t.) ha una descrizione dettagliata sui formati di file e descrive le versioni GNU degli strumenti per lavorarci.
- Internazionalizzare Python
- Un documento di Martin von Löwis (in inglese - n.d.t.) riguardo alle tecniche per l'internazionalizzazione delle applicazioni Python.
- Internazionalizzare Django
- Un'altra buona fonte di informazione (in inglese - n.d.t.) sull'uso di gettext, inclusi esempi reali.