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

Post più popolari