Django té suport complert per la internacionalització del text en el codi i en les plantilles. Aquí expliquem com funciona.
L'objectiu de la internacionalització és permetre a una única aplicació web oferir els seus continguts i funcionalitats en múltiples idiomes.
El desenvolupador Django, pot aconseguir aquest objectiu afegint una mínima quantitat de ganxos en el codi i plantilles Python. Aquests ganxos s'anomenen cadenes de text de traducció. Aquestes li diuen a Django: "Aquest text s'ha de traduir en l'idioma de l'usuari, si existeix una traducció d'aquest text en aquell idioma".
Django s'ocupa d'utilitzar aquests ganxos per traduir les aplicacions web, mentre funciona, d'acord amb les preferències de l'idioma de l'usuari.
Essencialment, Django fa dues coses:
Darrera de l'escenari
La maquinaria de traducció de Django utilitza el mòdul estàndard gettext que vé amb Python.
Els ganxos per la internacionalització a Django hi són per defecte, i això vol dir que hi ha una mica de codi i18n en cert llocs de la framework. Si utilitzes la internacionalització, hauràs de fixar-te dos segons per activar el paràmetre USE_I18N = False del teu fitxer de configuració. Si USE_I18N = False, llavors Django fan alguna optimització al no carregar la maquinària de la internacionalització.
Probablement també podràs eliminar el 'django.core.context_processors.i18n' del paràmetre TEMPLATE_CONTEXT_PROCESSORS.
Les cadenes de traducció especifiquen "Aquest text ha de traduir-se". Aquestes cadenes poden aparèixer en el teu codi Python i en les plantilles. És responsabilitat teva marcar les cadenes traduïbles; el sistema només pot traduir les cadenes que coneix.
Especifica una cadena de traducció utilitzant la funció _(). (Sí, el nom de la funció és un caràcter de guió baix). Aquesta funció està disponible globalment en qualsevol mòdul Python; no l'has d'importar. És una característica de Python.
En aquest exemple, el text "Welcome to my site." està marcat com una cadena de traducció:
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)
La funció django.utils.translation.gettext() és idèntica a _(). Aquest exemple és idèntic a l'anterior:
from django.utils.translation import gettext
def my_view(request):
output = gettext("Welcome to my site.")
return HttpResponse(output)
La traducció funciona sobre valors computats. Aquest exemple és idèntic als dos anteriors:
def my_view(request):
words = ['Welcome', 'to', 'my', 'site.']
output = _(' '.join(words))
return HttpResponse(output)
La traducció funciona sobre variables. Així, aquí hi ha un exemple idèntic:
def my_view(request): sentence = 'Welcome to my site.' output = _(sentence) return HttpResponse(output)
(L'advertència d'usar variables o valors calculats, com en els dos exemples previs, és que l'utilitat de detecció de cadenes a traduir, make-messages.py, no pot trobar aquestes cadenes. Després en parlarem més sobre make-messages.
Les cadenes que passes a _() o gettext() pot tenir buits, especificats en la sintaxi de cadenes amb noms de Python. Per exemple:
def my_view(request, n):
output = _('%(name)s is my name.') % {'name': n}
return HttpResponse(output)
Aquesta tècnica permet a les traduccions específiques reordenar el lloc on situar el text. Per exemple, una traducció en anglès podria ser "Adrian is my name", mentre que una traducció al Català seria "Em dic Adrian" -- amb el nom situat després del text en comptes del principi.
Per aquesta raó, hauries d'usar la interpolació de cadenes amb noms (p.e. %(name)s) en comptes de la interpolació posicional (p.e. %s o %d). Si utilitzes interpolació posicional, les traduccions no podran canviar l'ordre dels forats del text.
Utilitza la funció django.utils.translation.gettext_noop() per marcar una cadena com una traducció sense traduir-la. La cadena es traduirà després des d'una variable.
Utilitza això si tens cadenes constants que poden guardar-se en l'idioma original perquè seran canviats obre sistemes o usuaris -- com cadenes en una base de dades -- però seran traduïdes tant tard com sigui possible, com quan la cadena es presentada a l'usuari.
Utilitza la funció django.utils.translation.gettext_lazy() per traduir les cadenes de forma mandrosa -- quan s'accedeix a la variable en compte de quan la funció es crida a la funció gettext_lazy().
Per exemple, per traduir un help_text del model, fent el següent:
from django.utils.translation import gettext_lazy
class MyThing(models.Model):
name = models.CharField(help_text=gettext_lazy('This is the help text'))
En aquest exemple, gettext_lazy() guarda referències a les cades -- no a la traducció actual. La pròpia traducció es farà quan la cadena s'utilitzi en el context, com en la impressió de la plantilla o en el lloc d'administració de Django.
Si no t'agrada utilitzar el nom de gettext_lazy, pots crear-ne una àlies a un guió baix, així:
from django.utils.translation import gettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
Sempre que utilitzis traduccions mandroses en els models Django. I és una bona idea afegir traduccions per als noms dels camps i els noms de les taules, també. Això significa que s'escriuran opcions verbose_name i verbose_name_plural de forma explícita en la classe Meta:
from django.utils.translation import gettext_lazy as _
class MyThing(models.Model):
name = models.CharField(_('name'), help_text=_('This is the help text'))
class Meta:
verbose_name = _('my thing')
verbose_name_plural = _('mythings')
Utilitza la funció django.utils.translation.ngettext() per especificar els missatges plurals. Exemple:
from django.utils.translation import ngettext
def hello_world(request, count):
page = ngettext('there is %(count)d object', 'there are %(count)d objects', count) % {
'count': count,
}
return HttpResponse(page)
ngettext agafa tres arguments: la cadena a traduir en singular, la cadena a traduir en plural i el número d'objectes (que que serà passada als idiomes a traduir com una variable count).
Utilitzar les traduccions en les plantilles Django utilitza duees etiquetes de plantilla i una sintaxi lleugerament diferent que en el codi Python. Per tenir accés a aquestes etiquetes, posa {% load i18n %} a dalt de tot de la teva plantilla.
L'etiqueta de plantilla {% trans %} tradueix una cadena constant o un contingut d'una variable:
<title>{% trans "This is the title." %}</title>
Si només vols marcar un valor per a la traducció, però traduir-la després des d'una variable, utilitza l'opció noop:
<title>{% trans "value" noop %}</title>
No és possible utilitzar variable de plantilla a {% trans %} -- només es permeten cadenes constants, amb cometes simples o dobles. Si les teves traduccions necessiten variables, utilitza l'etiqueta {% blocktrans %}. Per exemple:
{% blocktrans %}This will have {{ value }} inside.{% endblocktrans %}
Per traduir una expressió de plantilla -- diguem, utilitzant filtres de plantilles -- cal que enllacis l'expressió a la variable local per utilitzar-la dins el bloc de la traducció:
{% blocktrans with value|filter as myvar %}
This will have {{ myvar }} inside.
{% endblocktrans %}
Si necessites enllaçar més d'una expressió en una etiqueta blocktrans, separa les peces amb un and:
{% blocktrans with book|title as book_t and author|title as author_t %}
This is {{ book_t }} by {{ author_t }}
{% endblocktrans %}
Per pluralitzar, especifica tant la forma singular com la plural amb l'etiqueta {% plural %}, que apareix entre {% blocktrans %} i {% endblocktrans %}. Exemple:
{% blocktrans count list|count as counter %}
There is only one {{ name }} object.
{% plural %}
There are {{ counter }} {{ name }} objects.
{% endblocktrans %}
Internament, tots els blocs en les traduccions utilitzen les crides apropiades gettext / ngettext.
Cada RequestContext té accés a dos variables específiques de la traducció:
Si no uses l'extensió RequestContext, pots obtenir aquests valors amb aquestes tres etiquetes:
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
Les etiquetes també necessiten un {% load i18n %}.
Els ganxos de traducció també estan disponibles dins de qualsevol etiqueta de bloc de plantilla que accepta les cadenes constants. En aquells casos, només cal que utilitzis la sintaxi _() per especificar a la cadena de traducció. Per exemple:
{% some_special_tag _("Page not found") value|yesno:_("yes,no") %}
En aquest cas, tant l'etiqueta com el filtre utilitzaran la cadena traduïda, així no cal que et preocupis de les traduccions.
Un cop has etiquetat les teves cadenes per traduir-les, cal escriure (o obtenir) les pròpies traduccions de l'idioma. Aquí s'explica com funciona:
El primer pas és crear un fitxer de missatges per a un nou idioma. Un fitxer de missatges és un fitxer de text pla, que representa un únic idioma, que conté totes les cadenes disponibles per traduir-se i com han de representar-se en un idioma en concret. Els fitxers de missatges tenen l'extensió del fitxer .po.
Django duu una eina, bin/make-messages.py, que automàticament crea un manteniment d'aquests fitxers.
Crear i modificar un fitxer de missatges, executa aquesta comanda:
bin/make-messages.py -l de
... on de és el codi de l'idioma per al fitxer de missatges que vols crear. El codi de l'idioma, en aquest cas, està en format "locale". Per exemple, aquest pt_BR és per al braziler i de_AT per a l'alemany austríac.
L'escript hauria d'executar-se des d'un d'aquests tres llocs:
L'escript s'executa sobre l'arbre complert de Django i extreu totes les cadenes marcades per a la traducció. Crea (o actualitza) un fitxer de missatges en el directori conf/locale. En l'exemple de de, el fitxer serà conf/locale/de/LC_MESSAGES/django.po.
Si l'executem sobre l'arbre del teu projecte o de la teva aplicació, farà el mateix, però el lloc del directori "locale" serà locale/LANG/LC_MESSAGES (fixa't que es perd el prefix conf).
No gettext?
Si no tens l'util·litat gettext instal·lada, make-messages.py crearà fitxers buits.
El format dels fitxers .po és directe. Cada fitxer .po conté un mica de metadades, com la informació de contacte de qui manté la traducció. Però nucli d'aquest fitxer és el llistat de missatges -- simplement assigna les cadenes de traducció amb els textos traduïts per a aquest idioma en particular.
Per exemple, si la teva aplicació Django conté una cadena de traducció per al text "Welcome to my site", com aquest:
_("Welcome to my site.")
... llavors make-messages.py haurà creat un fitxer .po que contindrà el següent tros -- un missatge:
#: path/to/python/module.py:23 msgid "Welcome to my site." msgstr ""
Cal una petita explicació:
Els missatges llargs són un cas especial. Aquí, la primera cadena després de msgstr (o msgid) és una cadena buida. Llavors el propi contingut serà escrit sobre les pròximes línies amb una cadena per línia. Aquestes cadenes de text són directament concatenades. No t'oblidis dels espais dins de les cadenes; sinó, seran situades juntes sense espais!
Pensa en el joc de caràcters
Quan creis un fitxer .po amb el teu editor de text preferit, primer modifica la línia del joc de caràcters (busca per "CHARSET") i indica el joc de caràcters que usaràs a l'editar el contingut. Generalment, utf-8 funciona per la majoria dels idiomes, però gettext podrà agafar qualsevol joc de caràcters que li indiquis.
Per re-examinar tot el codi font i les plantilles per a noves cadenes per traduir i modificar totes els fitxers de missatges per a tots els idiomes, executa això:
make-messages.py -a
Després que creis els teus fitxer de missatges -- i cada cop que en facis canvis -- caldrà que compilis per fer-ho més eficient, a l'usar-lo amb gettext. Fes això amb l'utilitat bin/compile-messages.py.
Aquesta eina s'executa sobre tots els fitxers .po disponibles i crea fitxers .mo, que són fitxers binaris optimitzats per usar amb gettext. En el mateix directori des del que has executat make-messages.py, executa tt>compile-messages.py així:
bin/compile-messages.py
Això és tot. Les teves traduccions ja estan a punt.
Un cop has preparat les teves traduccions -- o, si només vols utilitzar les traduccions que venen amb Django -- només et cat activar la traducció de les teves aplicacions.
Darrera de l'escenari, Django té un model moilt flexible de decisió de quin idioma ha d'usar-se -- per la banda de la instal·lació, per un usuari en particular, o ambdues.
Per activar una preferència d'idioma per a la instal·lació, activa el paràmetre de configuració LANGUAGE_CODE. Django utilitza aquest idioma com la traducció per defecte -- l'últim intent si no es troba cap altre traducció.
Si només vols executar Django amb el teu idioma natura, i el fitxer d'idioma hi és disponible per al teu idioma, tot el que et cal es activar el paràmetre LANGUAGE_CODE.
Si vols permetre que cada usuari especifiqui quin idioma prefereix, utilitza LocaleMiddleware. LocaleMiddleware activa la selecció de l'idioma basat amb les dades de la petició. Aquest contingut personalitzar per a cada usuari.
Per usar LocaleMiddleware, afegeix 'django.middleware.local.LocaleMiddleware' al paràmetre de configuració MIDDLEWARE_CLASSE. Com que l'ordre dels intermediaris és important, hauries de seguir aquestes regles:
Per exemple, el teu MIDDLEWARE_CLASSES serà quelcom així:
MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', )
LocaleMiddleware intenta determinar l'idioma preferit de l'usuari seguint el següent algorisme:
Notes:
LANGUAGES = (
('de', _('German')),
('en', _('English')),
)
Aquest exemple restringeix als idiomes que estan disponibles de la selecció automàtica a Alemany i Anglès (i qualsevol subidioma, com de-ch o en-us).
La solució és usar la funció "tonta" gettext(). Aquí hi ha un exemple de fitxer de configuració:
gettext = lambda s: s
LANGUAGES = (
('de', gettext('German')),
('en', gettext('English')),
)
Amb aquest arranjament, make-messages.py trobarà i marcarà aquestes cadenes per a la traducció, però la traducció no es realitzarà durant l'execució -- així que hauràs de recordar-te d'embolcallar els idiomes amb el gettext() real en qualsevol codi que utilitzi LANGUAGES durant l'execució.
Un bon punt d'inici és copiar el fitxer .po anglès i traduir com a mínim els missatges tècnics -- potser també els missatges de validació.
Els IDs dels missatges tècnics són fàcilment identificables; tots estan en majúscules. No tradueixis el missatge ID com a amb altres missatges, proporciona la variant local correcta d'un valor proporcionat en anglès. Per exemple, amb el DATETIME_FORMAT (o DATE_FORMAT o TIME_FORMAT), aquest hauria de ser la cadena de format que vols utilitzar en el teu idioma. El format és idèntic que la cadena de format usada en l'etiqueta now de la plantilla.
Un cop LocaleMiddleware determinar les preferències de l'usuari, aquest fa que aquesta preferència estigui disponible a request.LANGUAGE_CODE per a cada objecte request. Sigues lliure de llegir aquest valor en el teu codi de la vista. Aquí hi ha un exemple simple:
def hello_world(request, count):
if request.LANGUAGE_CODE == 'de-at':
return HttpResponse("You prefer to read Austrian German.")
else:
return HttpResponse("You prefer to read another language.")
Fixa't que, amb traduccions estàtiques (sense mitjancers), l'idioma és a settings.LANGUAGE_CODE, mentre que en traduccions dinàmiques (amb mitjancers), és a request.LANGUAGE_CODE.
Per una conveniència, Django ve amb una vista, django.views.i18n.set_language, que activar l'idioma preferit de l'usuari i et re-dirigeix a la pàgina anterior.
Activa aquesta vista afegint la següent línia en la teva URLconf:
(r'^i18n/', include('django.conf.urls.i18n')),
(Fixa't que aquest exemple fa que vista estigui disponible a /i18n/setlang/).
Aquesta vista espera que es cridi amb el mètode GET, amb el paràmetre language en una cadena. Si el suport a sessions s'ha activat, la vista guarda d'idioma escollit en la sessió de l'usuari. De totes formes, ho grava a la galeta django_language.
Després d'activar l'opció de l'idioma, Django redirigeix l'usuari, seguint el següent algoritme:
Aquí hi ha un exemple de codi HTML:
<form action="/i18n/setlang/" method="get">
<input name="next" type="hidden" value="/next/page/" />
<select name="language">
{% for lang in LANGUAGES %}
<option value="{{ lang.0 }}">{{ lang.1 }}</option>
{% endfor %}
</select>
<input type="submit" value="Go" />
</form>
Django busca les traduccions seguint aquest algorisme:
D'aquesta forma, pots escriure aplicacions que inclouen les pròpies traduccions, i pots sobre-escriure les traduccions base del teu projecte. O, pots construir un projecte gran per sobre de cada aplicació i posar totes les traducció dins un únic fitxer de missatges del projecte. La decisió és teva.
Nota
Si estàs realitzant manualment la configuració, el directori locale del directori del projecte no s'examinarà, ja que Django perd d'habilitat de treballar fora de la localització del directori del projecte. (Django normalment utilitza la localització del fitxer de configuració per determinar aquesta, i un fitxer de configuració no existeix perquè has realitzat la configuració manualment).
Tots els repositoris dels fitxers de missatges estan estructurats de la mateixa forma. Hi ha:
Per crear fitxers de missatges, utilitza la mateixa eina make-messages.py com amb els fitxers de missatges Django. Només necessiten estar en el lloc correcte -- tant en el directori conf/locale (en cas de l'arbre del codi font) com en el locale/ (en cas dels missatges de les aplicacions o dels projectes). I utilitzaràs el mateix compile-messages.py per produir els fitxers binaris django.mo que s'utilitzen per gettext.
Els fitxers de missatges de l'aplicació són una mica complicats de trobar -- necessiten el paràmetre LocaleMiddleware. Si no utilitzes el mitjancer, només els fitxers de missatges de Django i els fitxers de missatges del projecte es processaran.
Finalment, hauràs de pensar sobre l'estructura dels teus fitxers de traducció. Si les teves aplicacions han de distribuir-se a altres usuaris o han d'usar-se en altres projectes, podries voles usar les traduccions específiques per les aplicacions. Però utilitzant les traduccions específiques per les aplicacions i les traduccions del projecte poden produir problemes amb make-messages: make-messages passa per tots les directoris per sota de path actual i així possat els IDs dels missatges dins del fitxer de missatges del projecte que també contindrà els dels fitxers de missatges de les aplicacions.
La forma més fàcil serà guardar les aplicacions que no formen part del projecte (i així duguin les seves pròpies traduccions) fora de l'arbre del projecte. D'aquesta forma, make-messages a nivell del projecte només traduirà les cadenes que estiguin connectades explícitament en el projecte i no les cadenes que es distribueixen independentment.
Afegir traduccions a JavaScript implica alguns problemes:
Django proporciona una solució integrada per aquests problemes: Aquesta és passar les traduccions dins del JavaScript, així pots cridar a gettext, etc, des de dins de JavaScript.
La solució principal a aquests problemes és la vista javascript_catalog, que envia una llibreria de codi JavaScript amb funcions que imiten la interfície de gettext, més una matriu de cadenes de traducció. Aquestes cadenes de traducció s'agafen de l'aplicació, el projecte o del cor de Django, d'acord amb el que especifiquis a {{{info_dict}}} o la URL.
Mira com queda:
js_info_dict = {
'packages': ('your.app.package',),
}
urlpatterns = patterns('',
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
)
Cada cadena de packages ha d'estar en una sintaxi de paquet de Python (el mateix format que les cadenes de INSTALLED_APPS) i ha de referir-se a un paquet que conté un directori locale. Si especifiques múltiples paquets, tots aquests catàlegs s'afegiran dins un catàleg. Això és útil si tens JavaScript que utilitza cadenen des de diferents aplicacions.
Pots fer una vista dinàmica per posar els paquets dins un patró URL:
urlpatterns = patterns('',
(r'^jsi18n/(?P<packages>\S+?)/$, 'django.views.i18n.javascript_catalog'),
)
Amb això, especifica el paquet com una llista de noms de paquets delimitats per el signe '+' en la URL. Això és especialment útil si els teus paquets utilitzen codi des de diferents aplicacions i aquests canvien sovint i no vols posar-los en un únic fitxer de catàleg gran. Com a mesura de seguretat, aquests valors només poden ser o a django.conf o en qualsevol paquet dels indicats en el paràmetre INSTALLED_APPS.
Usar el catàleg, només posa'ls en l'escript generat dinàmicament així:
<script type="text/javascript" src="/path/to/jsi18n/"></script>
Així és com l'administrador enganxa el catàleg de traducció des del servidor. Quan el catàleg es carrega, el teu codi JavaScript pot usar la interfície gettext estàndard per accedir-hi:
document.write(gettext('this is to be translated'));
Fins i tot hi ha la interfície ngettext i una funció d'interpolació de cadenes:
d = {
count: 10
};
s = interpolate(ngettext('this is %(count)s object', 'this are %(count)s objects', d.count), d);
La funció interpolate permet la interpolació posicional i la interpolació amb nom. Així el codi anterior podria escriure's així:
s = interpolate(ngettext('this is %s object', 'this are %s objects', 11), [11]);
La sintaxi d'interpolació s'ha manllevat de Python. No has de preocupar-te amb la interpolació de cadenes, pensa però: que això del JavaScript és immòbil, així que el codi haurà de repetir les substitucions cada cop. Això no és tant ràpid com ho fa Python, així que evita aquesta casos on realment els necessitis (per exemple, dins una conjunció amb ngettext per produir pluralitzacions).
Crear i actualitzar els catàlegs de traducció es fa de la mateixa manera que amb la traducció de catàlegs de traducció de Django -- amb l'eina {{{make-messages.py}}}. L'única diferència és que has d'indicar el paràmetre -d djangojs, així:
make-messages.py -d djangojs -l de
Això et crearà o actualitzarà el catàleg de traduccions per al JavaScript en Alemany. Després d'actualitzar els catàlegs de traduccions, només has d'executar compile-messages.py de la mateixa manera que ho fas amb els catàlegs normals de traducció de Django.
Si coneixes gettext, notaràs aquestes especialitzats de com Django fa la traducció: