Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Rafael Chavarría (you can also view the original English article)

En mis artículos anteriores cubrí los diversos aspectos de Elixir---un lenguaje de programación moderno y funcional. El día de hoy, sin embargo, me gustaría alejarme un poco del lenguaje mismo y discutir un marco de trabajo MVC muy rápido y confiable llamado Phoenix que está escrito con Elixir.

Este marco de trabajo surgió hace cerca de cinco años y ha recibido algo de tracción desde entonces. Por supuesto, no es tan popular como Rails o Django todavía, pero tiene un gran potencial y realmente me agrada.

En este artículo vamos a ver cómo introducir l18n en aplicaciones Phoenix. ¿Qué es l18n, preguntas? Bueno, es un numerónimo que significa "internacionalización", ya que hay exactamente 18 caracteres entre la primera letra "i" y la última "n". Probablemente, también has conocido un numerónimo L10n el cuál significa "localización". Los desarrolladores estos días son tan flojos que ni siquiera pueden escribir un par de caracteres extra, ¿no?

Internacionalización es un proceso muy importante, especialmente si prevees que la aplicación sea usada por personas de todo el mundo. Después de todo, no todos saben bien Inglés, y tener tu aplicación traducida en el lenguaje nativo del usuario da una buena impresión.

Parece que el proceso de traducir aplicaciones Phoenix es algo diferente de, digamos, traducir aplicaciones Rails (pero bastante similar al mismo proceso en Django). Para traducir aplicaciones Phoenix, usamos una solución bastante popular llamada Gettext, la cuál ha existido por alrededor de más de 25 años. Gettex funciona con tipos especiales de archivo, llamados PO y POT, y soporta características como alcance, pluralización, y otras ventajas.

Así que en este artículo te voy a explicar lo que es Gettext, cómo difiere PO de POT, cómo localizar mensajes en Phoenix, y en donde almacenar traducciones. También vamos a ver cómo cambiar la localización de la aplicación y cómo trabajar con reglas de pluralización y dominios.

¿Comenzamos?

Internacionalización Con Gettext

Gettext es una herramienta probada de código libre para internacionalización introducida inicialmente por Sun Microsystems en 1990. En 1995, GNU lanzó su propia versión de Gettext, la cuál es ahora considerada la más popular ahí afuera (la versión más reciente fue 0.19.8 al momento de escribir este artículo). Gettext podría ser usado para crear sistemas multilingües de cualquier tamaño y tipo, desde aplicaciones web hasta sistemas operativos. Esta solución es bastante compleja, y no vamos a discutir todas sus características, por supuesto. La documentación completa Gettext puede ser encontrada en gnu.org.

Gettext te proporciona todas las herramientas necesarias para realizar localización y presenta algunos requerimientos sobre cómo deberían ser nombrados y organizados los archivos de traducción. Dos tipos de archivos son usados para albergar traducciones: PO y MO.

Los archivos PO (Portable Object, Objeto Portable) almacenan traducciones para cadenas dadas así como reglas de pluralización y metadatos. Estos archivos tienen una estructura simple y pueden ser editados fácilmente por un humano, así que en este artículo nos apegaremos a ellos. Cada archivo PO contiene traducciones (o parte de las traducciones) para un solo lenguaje y debería ser almacenado en un directorio llamado como su lenguaje: en, fr, de, etc.

Los archivos MO (Machine Object, Objeto Máquina) contienen datos binarios que no están hechos para ser editados directamente por un humano. Son más difíciles para trabajar, y discutirlos está fuera del alcance de este artículo.

Para hacer las cosas más complejas, también hay archivos POT (Portable Object Template, Plantilla de Objeto Portable). Estos albergan solo cadenas de texto para traducir, pero no las traducciones mismas. Básicamente, los archivos POT son usados solo como planos para crear archivos PO para varias locaciones.

Aplicación Phoenix Simple

Está bien, ¡ahora procedamos a la práctica! Si quisieras seguir a la par, asegúrate de tener instalado lo siguiente:

Crea una nueva aplicación de muestra sin una base de datos ejecutando:

--no-ecto dice que la base de datos no debería ser utilizada por la aplicación (Ecto es una herramienta para comunicarse con la BD misma). Nota que el generador podría requerir un par de minutos para preparar todo.

Ahora usa cd para ir a la recién creada carpeta i18ndemo y ejecuta el siguiente comando para iniciar el servidor:

Después, abre el navegador y ve a http://localhost:4000, en donde deberías ver un mensaje "Welcome to Phoenix!".

¡Hola, Gettext!

Lo que es interesante acerca de nuestra aplicación Phoenix, específicamente, el mensaje de bienvenida es que Gettext ya está siendo usado por defecto. Continua y abre el archivo demo/lib/demo_web/templates/page/index.html.eex que actúa como una página de inicio por defecto. Quita todo excepto este código:

Este mensaje de bienvenida utiliza una función gettext la cuál acepta una cadena para traducir como el primer argumento. Esta cadena puede ser considerada como una llave de traducción, aunque es algo diferente de las llaves usadas en Rails l18n y algunos otros marcos de trabajo. En Rails habríamos usado una llave como page.welcome, mientras que aquí la cadena traducida es una llave por si misma. Así qué, si la traducción no puede se encontrada, podemos mostrar esta cadena directamente. Incluso si un usuario sabe muy poco Inglés puede al menos tener un entendimiento básico de qué está pasando.

Esta aproximación es de hecho bastante útil---detente por un segundo y piensa sobre eso. Tienes una aplicación en donde todos los mensajes están en Inglés. Si quisieras internacionalizarla, en el caso más simple todo lo que tienes que hacer es envolver tus mensajes con la función gettext y proporcionar traducciones para estos (después veremos que el proceso de extraer las claves puede ser automatizado fácilmente, lo cuál acelera las cosas incluso más).

Está bien, regresemos a nuestro pequeño código y echa un vistazo al segundo argumento pasado a gettext: name: "Phoenix". Esto es un llamado binding---un parámetro envuelto con %{} que nos gustaría interpolar a una traducción dada. En este ejemplo, solo hay un parámetro llamado name.

También podemos agregar un mensaje más a esta página para propósitos demostrativos:

Agregando una Nueva Traducción

Ahora que tenemos dos mensajes en la página raíz, ¿deberíamos agregar traducciones para ellos? Parece que todas las traducciones están almacenadas bajo la carpeta priv/gettext, la cual tiene una estructura predefinida. Tomemos un momento para discutir cómo deberían estar organizados los archivos Gettext (esto aplica no solo a Phonenix sino a cualquier aplicación usando Gettext).

Primero que nada, deberíamos crear una carpeta llamada como la ubicación para la que va a guardar traducciones. Dentro, debería haber una carpeta llamada LC_MESSAGES conteniendo uno o varios archivos .po con las traducciones. En el caso más sencillo, tendrías un archivo default.po por ubicación. default aquí es el nombre del dominio (o alcance). Los dominios son usados para dividir traducciones en varios grupos: por ejemplo, podrías tener dominios nombrados admin, wysiwig, cart, y otro. Esto es conveniente cuando tienes una aplicación grande con cientos de mensajes. Para aplicaciones más pequeñas, sin embargo, tener un solo dominio default es suficiente.

Así que nuestra estructura de archivo luciría así:

  • en
    • LC_MESSAGES
      • default.po
      • admin.po
  • ru
    • LC_MESSAGES
      • default.po
      • admin.po

Para comenzar a crear archivos PO, primero necesitamos la plantilla correspondiente (POT). Podemos crearla de manera manual, pero soy muy flojo para hacerlo de esta manera. Ejecutemos el siguiente comando en su lugar:

Es una herramienta muy útil que escanea los archivos del proyecto y revisa si Gettext es usado en algún lugar. Después de que el script termina su trabajo, un nuevo archivo priv/gettext/default.pot conteniendo cadenas para traducir será creado.

Como ya hemos aprendido, los archivos POT son plantillas, así que almacenan solo las llaves mismas, no las traducciones, así que no modifiques tales archivos de manera manual. Abre un archivo recién creado y echa un vistazo a sus contenidos:

Conveniente, ¿no es así? Todos nuestros mensajes fueron insertados de manera automática, y podemos ver fácilmente en donde están ubicados. msgid, como probablemente adivinaste, es la llave, mientras que msgstr va a contener la traducción.

El siguiente paso es, por supuesto, generar un archivo PO. Ejecuta:

Este script va a utiliza la plantilla default.pot y crear un archivo default.po en la carpeta priv/gettext/en/LC_MESSAGES. Por ahora, tenemos solo una ubicación English, pero será agregado soporte para otros lenguajes en la siguiente sección también.

Por cierto, es posible crear o actualizar la plantilla POT y todos los archivos PO de una vez usando el siguiente comando:

Ahora abramos el archivo priv/gettext/en/LC_MESSAGES/default.po, el cuál tiene los siguientes contenidos:

Este es el archivo en donde deberíamos realizar la traducción. Por supuesto, tiene poco sentido hacerlo porque los mensajes ya están en Inglés, así que procedamos a la siguiente sección y agreguemos soporte para un segundo lenguaje.

Múltiples Ubicaciones

Naturalmente, la ubicación por defecto de aplicaciones Phoenix es Inglés, pero esta configuración puede ser cambiada fácilmente modificando el archivo config/config.exs. Por ejemplo, establezcamos la ubicación por defecto a Ruso (siéntete libre de quedarte con cualquier otro lenguaje de tu elección):

También es una buena idea especificar la lista completa de todas las ubicaciones soportadas:

Ahora lo que necesitamos hacer es generar un nuevo archivo PO que contenga traducciones para la ubicación de Ruso. Puede ser realizado ejecutando el script gettext.merge de nuevo, pero con un cambio de --locale:

Obviamente, una carpeta priv/gettext/ru/LC_MESSAGES con los archivos .po dentro será generada. Nota, por cierto, que además del archivo default.po, también tenemos errors.po. Este es un lugar por defecto para traducir mensajes de error, pero en este artículo vamos a ignorarlo.

Ahora modifica el priv/gettext/ru/LC_MESSAGES/default.po agregando algunas traducciones:

Ahora, dependiendo de la ubicación elegida, Phoenix generará ya sea las traducciones en Inglés o Ruso. ¡Pero espera! ¿Cómo podemos cambiar realmente entre ubicaciones en nuestra aplicación? ¡Procedamos a la siguiente sección y averigüemoslo!

Cambiando Entre Ubicaciones

Ahora que algunas traducciones están presentes, necesitamos habilitar a nuestros usuarios para que cambien entre ubicaciones. Parece que hay un complemento para eso llamado set_locale. Funciona extrayendo la ubicación elegida de la URL o el encabezado HTTP Accept-Language. Así que, para especificar una ubicación en la URL, teclearías http://localhost:4000/en/alguna_ruta. Si la ubicación no es especificada (o si un lenguaje no soportado fue solicitado), una de dos cosas sucederá:

  • Si la petición contiene un encabezado HTTP Accept-Language y su ubicación es soportada, el usuario será redirigido a una página con la ubicación correspondiente.
  • De otro modo, el usuario será redirigido de manera automática a una URL que contenga el código para la ubicación por defecto.

Abre el archivo mix.exs y anexa el set_locale a la función deps:

También debemos agregarlo a la función application:

Después, instala todo:

Nuestro ruteador localizado en lib/demo_web/router.ex requiere algunos cambios también. Específicamente, necesitamos agregar un nuevo enchufe a :browser.

También, crea un nuevo alcance:

¡Y eso es todo! Puedes iniciar el servidor y navegar a http://localhost:4000/ruhttp://localhost:4000/en. Nota que todos los mensajes están traducidos apropiadamente, ¡lo cuál es exactamente lo que necesitamos!

De manera alternativa, podrías codificar una característica similar utilizando un enchufe de Módulo. Un pequeño ejemplo puede ser encontrado en la guía oficial Phonenix.

Una última cosa para mencionar es que en algunos casos podrías necesitar reforzar una ubicación específica. Para hacer eso, simplemente utiliza una función with_locale:

Pluralización

Hemos aprendido los fundamentos de usar Gettext con Phoenix, así que ha llegado la hora de discutir cosas ligeramente más complejas. La Pluralización es una de ellas. Básicamente, trabajar con formas plurales y singulares es una tarea muy común pero potencialmente compleja. Las cosas son memos obvias en Ingles ya que tienes "1 manzana", "2 manzanas", "9000 manzanas" etc. 

Desafortunadamente, en algunos otros lenguajes como Ruso o Polaco, las reglas son más complejas. Por ejemplo, en el caso de manzanas, dirías "1 яблоко", "2 яблока", "9000 яблок". Pero afortunadamente para nosotros, Phoenix tiene un comportamiento Gettext.Plural (podrás ver el comportamiento en acción en uno de mis artículos previos) que soporta muchos lenguajes diferentes. De ahí que todo lo que tenemos que hacer es sacar ventaja de la función ngettext.

Esta función acepta tres argumentos requeridos: una cadena en forma singular, una cadena en forma plural, y cuenta. El cuarto argumento es opcional y puede contener enlaces que deberían ser interpolados en la traducción.

Veamos ngettext en acción diciendo cuánto dinero tiene el usuario modificando el archivo demo/lib/demo_web/templates/page/index.html.eex:

%{count} es una interpolación que será reemplazada con un número (540 en este caso). No olvides actualizar la plantilla y todos los archivo PO después de agregar la cadena de arriba.

Verás que un nuevo bloque ha sido agregado a ambos archivos default.po:

No tenemos una sino dos llaves aquí a la vez: en formas singular y plural. msgstr[0] va a contener algún texto para mostrar cuando solo hay un mensaje. msgstr[1], por supuesto, contiene el texto para mostrar cuando hay varios mensajes. Está bien para inglés. pero no lo suficiente para Ruso en donde necesitamos introducir un tercer caso:

Caso 0 es usado para 1 dólar, y caso 1 para cero o pocos dólares. Caso 2 es usado en caso contrario.

Alcance de Traducciones Con Dominios

Otro tema que quería discutir en este artículo está dedicado a dominios. Como ya sabemos, los dominios son usados para extender traducciones, principalmente en aplicaciones grandes. Básicamente, estos actúan como namespaces.

Después de todo, podrías terminar en una situación cuando la misma llave es usada en múltiples lugares, pero deberían ser traducidas un poco diferente. O cuando tienes demasiadas traducciones en un solo archivo default.po y quisieras partirlas de algún modo. Ahí es cuando los dominios pueden ser realmente útiles.

Gettext soporta múltiples dominios por defeco. Todo lo que tienes que hacer es utilizar la función dgettext, la cuál funciona casi igual que gettext. La única diferencia es que esta acepta el nombre de dominio como primer argumento. Por ejemplo, introduzcamos un dominio de notificación a, buen, mostrar notificaciones. Agrega tres líneas más de código al archivo demo/lib/demo_web/templates/page/index.html.eex:

Ahora necesitamos crear nuevos archivos POT y PO:

Después de que el script termina de hacer su trabajo, notifications.pot así como dos archivos notifications.po serán creados. Nota que una vez más que son nombrados como el dominio. Todo lo que tienes que hacer ahora es agregar traducción para el lenguaje Ruso modificando el archivo priv/ru/LC_MESSAGES/notifications.po:

¿Y si quisieras pluralizar un mensaje almacenado bajo un dominio dado? Esto es tan simple como utilizar una función dngettext. Funciona como ngettext pero también acepta el nombre de un dominio como el primer argumento.

Conclusión

En este artículo, hemos visto cómo introducir internacionalización en una aplicación Phoenix con la ayuda de Gettext. Has aprendido qué es Gettext y con qué tipo de archivos trabaja. Tenemos esta solución en acción, hemos trabajado con archivos PO y POT, y visualizado varias funciones Gettext.

También hemos visto una manera de agregar soporte a múltiples ubicaciones y agregado una manera de cambiar fácilmente entre ellas. Por último, hemos visto como emplear reglas de pluralización y cómo extender traducciones con la ayuda de dominios.

Ojalá, ¡este artículo haya sido de utilidad para ti! Si quisieras aprender más sobre Gettext en el marco de trabajo Phoenix, podrías referirte a la guía oficial, la cuál proporciona ejemplos útiles y referencia de API para todas las funciones disponibles.

¡Te agradezco por quedarte conmigo y te veo pronto!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.