En este tutorial sobre Opengnsys 2 vamos a explicar el funcionamiento de las traducciones de los plugins para la Consola Web.
Los plugins de la consola web son internacionalizables, lo que implica que podrán ser utilizados por diferentes usuarios en diferentes idiomas a un bajo coste programático. Utilizando las funcionalidades que la consola web nos ofrece para internacionalizar, traducir un plugin y utilizarlo en otro idioma no supone apenas esfuerzo adicional.
Para la internacionalización se utiliza la herramienta gettext, utilizada ampliamente en multitud de proyectos. El uso de gettext consiste básicamente en escribir todas las cadenas del código en un idioma base a partir del cual se realizarán las traducciones (en nuestro caso es el inglés), pero pasadas a través de una función que se encargará de coger la cadena correspondiente en caso de estar viendo otro idioma. Normalmente en código se utiliza la función _() y es común ver líneas de código con cadenas como _("hello world!").
Para facilitar la internacionalización de los plugins, la consola web ofrece un decorador python (pi18n), que lo único que hace es añadir la variable global _ al contexto de la función decorada.
Los decoradores python son modificadores de funciones, y para su utilización se suelen poner con @nombre_decorador justo encima de la definición de la función o método que queremos decorar.
En concreto, la consola web ofrece dos decoradores para las traducciones, dentro del módulo decorators, i18n y pi18n. El primero se usa para las traducciones propias de la consola web, y en teoría no es necesario utilizarlo en los plugins, y el segundo es el indicado para usar en las vistas y funciones de los plugins.
Todo esto puede parecer complicado, pero en la práctica es mucho más simple, no es necesario comprender cómo funcionan los decoradores de python ni cómo funciona gettext. Veámoslo con un ejemplo:
#!python
import web
from decorators import pi18n
class HelloView:
@pi18n
def GET(self):
return web.ctx.render.plugins.hello_world.helloview(_('Hello World!'))
En el código de esta vista del plugin hello_world se puede observar cómo importamos pi18n del módulo decorators y luego cómo se utiliza en la vista, decorando el método GET, situando @pi18n justo antes de su definición. Luego, dentro de la función hacemos uso de _() que es la variable que introduce el decorador dentro del contexto de la función. Toda cadena traducible debe ir dentro de _().
Es así de simple, importar pi18n, decorar la vista con cadenas internacionalizables y pasar toda cadena por la llamada _().
Los templates de los plugins también son internacionalizables, por lo tanto también se pueden escribir cadenas traducibles dentro de los templates HTML.
En los templates es aún más fácil hacer uso de la internacionalización, porque la función ya está en el contexto y tan sólo hay que utilizarla. Para los templates también existen dos funciones de internacionalización, _() y _p(), la primera es para la internacionalización del core de la consola web, y la segunda es para traducciones de plugins. En principio en una plantilla de un plugin sólo se debería usar _p(), aunque es posible utilizar _(), pero sólo es recomendable para aquellos que sepan lo que están haciendo.
Por lo tanto, para traducir cadenas dentro de un plugin lo que hay que hacer es meter esta cadena dentro de la función _p(). Veamos un ejemplo:
#!python
$def with ()
$var title = _p("Hello World!")
$var tab: panel
$var hierarchy = []
$code:
h = "hola hola"
long_text = _p("Hello world, this is a multiline text, "
"that i'm writting in a template "
"multiline should be inside a code sentence "
"and asigned to a variable that you can use "
"later. Also you can use string operations like %(hello)s") % {'hello': h}
<p>
$_p("Hello World!")
</p>
<p>
$long_text
</p>
En este ejemplo se puede ver el uso de _p, para texto largo o compuesto a través de código python se puede utilizar $code. Dentro de $code todo es código python, por lo tanto no hace falta poner el $ delante de la llamada a _p.
A veces necesitamos realizar traducciones que incluyen cadenas que varían si se trata de un plural o no. Por ejemplo si queremos traducir la cadena "Updated 3 days ago", donde los días varían y pueden ser incluso de 0 en adelante. Esto lo podemos hacer con _n(), utilizándolo de la siguiente manera: _n(singular, plural, número). Por ejemplo, con la cadena anterior, tendríamos: _n("Updated %d day ago", "Updated %d days ago", days), siendo days el número de días a mostrar. Dependiendo de days, la función _n() devolverá la primera cadena o la segunda.
Nótese que en el ejemplo anterior _n() devuelve una cadena que en vez de el número de días que queremos, la cadena contiene los caracteres %d. Para finalmente sistituir %d por el número que queremos, es recomendable utilizar el operador % de python, construyendo así la cadena final. Sigue un ejemplo de una función que imprimiría los días que han pasado desde la última actualización:
#!python
@pi18n
def print_time(days):
print _n("Updated %d day ago", "Updated %d days ago", days) % days
Una vez tenemos el plugin internacionalizado, con todas las cadenas pasadas por las funciones _() en las vistas y _p() en los templates, podemos generar los ficheros .po para los idiomas que queramos.
Lo primero es crear el directorio i18n dentro de tu plugin. Por ejemplo, si nuestro plugin está en plugins/hello, debemos crear plugins/hello/i18n. Dentro de i18n debe existir una carpeta por cada idioma al que queramos poder traducir. Supongamos que queremos el español, pues creamos la carpeta es dentro de i18n:
$ cd plugins/hello/
$ mkdir -p i18n/es
$ cd ../..
Una vez creadas todas las carpetas de idiomas, se pueden generar los .po simplemente llamando al script i18n_extract_plugin.sh.
$ ./i18n_extract_plugin.sh plugins/hello
Esta llamada generará tantos ficheros .po como directorios haya dentro de i18n. En nuestro caso, se creará el fichero plugins/hello/i18n/es/LC_MESSAGES/messages.po
Los ficheros .po son muy simples y hay muchas herramientas para trabajar con ellos. Básicamente consisten en pares de cadenas, la original y la traducida debajo, con una serie de metadatos al principio del fichero, que indican el autor, codificación, etc.
Para traducir se puede utilizar cualquier editor de textos, pero es recomendable utilizar una herramienta específicia para este tipo de ficheros, como por ejemplo poedit.
Los ficheros de traducciones .po se compilan a binario .mo por temas de eficiencia. Así pues la aplicación utiliza los ficheros .mo generados a partir de los .po para traducir.
Para compilar las traducciones de un plugin se puede utilizar el script i18n_make_plugin.sh, ten en cuenta que antes de llamar a este script hay que haber realizado los pasos anteriores para tener los ficheros .po ya traducidos.
$ ./i18n_make_plugin.sh plugins/hello
Esta llamada generará los ficheros .mo para cada idioma dentro de la carpeta i18n del plugin hello, en nuestro caso, como sólo tenemos el directorio es, se generará el fichero plugins/hello/i18n/es/LC_MESSAGES/messages.po
Si hemos hecho cambios en el código o queremos añadir una nueva traducción después de tener las traducciones generadas podemos regenerarlas para que incluyan las nuevas cadenas. El procedimiento es el mismo que cuando no hay traducciones, primero hay que ejecutar el script i18n_extract_plugin.sh que cogerá las cadenas nuevas y las mezclará con las existentes en el fichero .po. Si se ha añadido un nuevo directorio dentro de i18n, por ejemplo fr, el script generará el fichero .po correspondiente.
Una vez actualizados los ficheros .po hay que compilar, de la misma manera que antes, ejecutando el script i18n_make_plugin.sh