Progetti multilingua in Python (parte 2/9).
Attenzione!
Queste guide non sono più aggiornate, e in alcuni punti sono proprio superate.Il mio libro Python in Windows tratta questi temi, e molto altro, in modo molto più completo e aggiornato.
Marcare le stringhe per la traduzione.
Questa è la parte più semplice, ed è anche il solo aspetto di cui vi occuperete per la maggior parte del tempo.Per convenzione, una stringa da tradurre si marca “impacchettandola” nella funzione
_
: proprio così, il nome della funzione è un singolo carattere di undescore. Questo mantiene il codice il più compatto e leggibile possibile. Di conseguenza:print('hello world!') # questa stringa non sara' tradotta
print(_('hello world!')) # questa invece si'
Fin quando siete impegnati nella scrittura del codice, marcare le stringhe in questo modo è tutto quel che dovete fare. Non è il caso di impostare il meccanismo di gettext
fin da subito. In particolare, non è necessario importare gettext
all’inizio dei vostri moduli.L’unica astuzia necessaria, fin quando non decidete di attivare il meccanismo di traduzione di
gettext
, è dare un significato provvisorio alla variabile _
: altrimenti, quando eseguite il codice ottenete una raffica di NameError
. Potete dire a Python che _
è una noop, almeno per il momento: all’inizio di tutti i moduli che contengono stringhe marcate, aggiungete questa riga:_ = lambda i: i #TODO cancellare questo quando gettext entra in funzione.
Una nota a margine: molte guide consigliano di inserire invece snippet come import gettext; _ = gettext.gettext
. Funziona, ma non è molto furbo. Si tratta comunque di una soluzione provvisoria perché, come vedremo, in genere volete usare metodi più raffinati per installare i meccanismi di gettext
.Problemi nella marcatura delle stringhe.
Se volete marcare per la traduzione stringhe interpolate, potete farlo. Ma ricordate che le nuove comodissime “f-string” non funzionano con i meccanismi che useremo più tardi. Quindi, per esempio:name = 'Mario'
age = 20
s = _(f'Sono {name} e ho {age} anni') # questo purtroppo non si puo' fare
s = _('Sono {name} e ho {age} anni').format(name=name, age=age) # ok
Quando interpolate più di una variabile con format
, dovreste sempre indicarle esplicitamente senza fidarvi dell’ordine in cui sono inserite: nella traduzione, in qualche lingua, l’ordine potrebbe non essere mantenuto identico. Quindi:s = _('Ho {} anni.').format(age) # bene
s = _('Ho {age} anni.').format(age=age) # meglio, aiuta i traduttori
s = _('Sono {name} e ho {age} anni').format(name=name, age=age) # ok
s = _('Sono {} e ho {} anni').format(name, age) # decisamente no
In quale lingua dovrebbe essere scritta la stringa “originale” nel codice? Detto francamente: in Inglese. Chiaramente per noi è più comodo pensare all’Italiano come lingua “di default” e poi fornire la traduzione in Inglese e altre lingue. Potete farlo, certo: ma la regola è che il codice andrebbe sempre scritto in Inglese.Se scrivete caratteri non-ASCII (lettere accentate, etc.) nelle stringhe marcate per la traduzione, allora dovete inserire la dichiarazione di encoding all’inizio del modulo. Notate che questo non dovrebbe essere necessario: se manca la dichiarazione di encoding, Python 3 suppone che il modulo sia encodato in utf-8. Purtroppo però i meccanismi di
gettext
si rompono se non trovano la dichiarazione esplicita. Quindi:# -*- coding: utf-8 -*-
s = _('Questa -è- una stringa non-ascii.')
In alcuni casi volete lasciare un commento per i traduttori: è utile quando la stringa è breve e risulta ambigua o enigmatica se vista fuori contesto. Potete inserire un normale commento Python immediatamente prima della stringa marcata:# translators: this is a verb, not a noun.
s = _('Exit')
Questo potrebbe aiutare qui a tradurre “Exit” con “Esci” invece di “Uscita”. Il prefisso “translators:” è convenzionale, e si usa per segnalare a chi legge il codice che il commento è rivolto ai traduttori. Purtroppo però gli strumenti predefiniti di Python (pygettext
, come vedremo tra poco) non rilevano la presenza di questi commenti e non li inseriscono quindi nel file riservato ai traduttori. Se per il vostro progetto i commenti sono fondamentali, dovete installare e usare altri strumenti (xgettext
è l’alternativa più comune, e ne faremo cenno più in là).Marcatura di forme plurali (prima parte: il codice).
Il primo consiglio qui è: cercate di evitare questo problema, se potete. Le forme plurali sono un ginepraio di complicazioni.Se una stringa ha bisogno di essere variata al plurale, e non dovete tradurla, potete naturalmente discriminare con qualcosa del genere:
num = 3
if num == 1:
print('Mario ha {} anno.'.format(num))
else:
print('Mario ha {} anni.'.format(num))
Se questa stringa deve essere tradotta, potete mantenere questa strategia e lasciare due stringhe separate. Tuttavia gettext
supporta le varianti plurali in modo più furbo, con la funzione ngettext
:num = 3
s = ngettext('Mario ha {} anno.', 'Mario ha {} anni.', num)
print(s.format(num))
Come si vede, ngettext
vuole 3 argomenti: la stringa nella versione singolare, la stringa nella versione plurale, e la variabile discriminante. Una volta che ngettext
ha selezionato la versione giusta a seconda di quanto vale num
, occorre naturalmente ancora interpolare la stringa con il consueto metodo format
.Se usate
ngettext
nel vostro codice, occorre evitare i NameError
fin quando non sarete pronti ad attivare il meccanismo di gettext
. Potete importare direttamente il nome: from gettext import ngettext
. Oppure potete provvisoriamente trasformare anche ngettext
in una noop che seleziona sempre il primo elemento:ngettext = lambda i, j, k: i # TODO via questa riga quando attivo gettext
Vale la pena ripeterlo: le stringhe plurali sono complicate e andrebbero evitate quando possibile. Oltre alla necessità di usare una funzione diversa dalla normale _
, i plurali sono più complessi da tradurre, anche perché molte lingue prevedono più di una forma di plurale (ne riparleremo). In genere, semplificare un po’ è sempre possibile: "Anni di Mario: {num}."
risolve il problema con una sola stringa senza ricorrere a meccanismi complicati.Il meccanismo di
ngettext
resta comunque valido anche nei casi in cui, per coincidenza, la forma singolare e plurale sono uguali. Per esempio, dovete comunque scrivere:s = ngettext('Ho visto {} re.', 'Ho visto {} re.', num)
Anche se in Italiano “re” è invariabile, altre lingue faranno distinzione (“king/kings”, etc.).Infine, tenete conto che purtroppo gli strumenti predefiniti di Python non sono in grado di produrre un catalogo con le stringhe plurali (vedremo subito che cosa è un catalogo, e quali sono gli strumenti predefiniti di Python, non preoccupatevi). Quindi, anche se
gettext
supporta senza problemi le stringhe plurali una volta che il catalogo è stato creato, tuttavia se volete usarle dovete installare degli strumenti separati (vedremo quali) per produrre i cataloghi.
Commenti
Posta un commento