Como Youtube e Instagram: internacionalizando y localizando una aplicación Python

Python está en el centro de aplicaciones mundialmente famosas como Youtube, Instagram y Pinterest. Para avanzar en el mercado mundial, una aplicación necesita localización, es decir, adaptación a las características de un país en particular e internacionalización: traducción de contenido. En este artículo, compartiremos nuestra experiencia sobre cómo acelerar la automatización de la traducción y resolver algunos problemas típicos en esta área.



Introduccion


Esta es una guía breve para internacionalizar las aplicaciones de Python (i18n). Esta guía será interesante para todos los programadores con experiencia en desarrollo de Python. Leer un artículo tomará entre 10 y 15 minutos.

Utilizaremos la herramienta gettext bien probada incluida en el lenguaje python.

Para empezar, entenderemos qué es la internacionalización:

La internacionalización (I18N) es el proceso de adaptación de una aplicación a los idiomas de diferentes países y regiones además del idioma en el que se desarrolló.

Pero también hay un concepto más amplio:

La localización (L10N) es el proceso de adaptar una aplicación internacionalizada a una región o idioma específico mediante la adición de componentes específicos a un entorno local determinado y la traducción del texto.

Localización significa traducción:

  • formato de fecha y hora;
  • formato de número;
  • zona horaria
  • un calendario
  • representaciones monetarias;
  • impuestos / IVA;
  • temperatura y otras medidas;
  • códigos postales, teléfonos;
  • formato de dirección;
  • Código del acuerdo.



La localización va más allá de traducir contenido a otro idioma. Hay parámetros culturales y funcionales que también requieren atención. Por ejemplo, el formato de fecha en Norteamérica es MM / DD / AAAA, pero en la mayoría de los países asiáticos se escribe DD / MM / AAAA.



Un ejemplo bien conocido de un error de traducción de la aplicación.

Otro ejemplo se refiere a la visualización de nombres en las aplicaciones. En los Estados Unidos, llamar a alguien por su nombre es aceptable e incluso preferible, el nombre del cliente se muestra en el encabezado tan pronto como el cliente inicia sesión. Sin embargo, en Japón sucede lo contrario: llamar a alguien por su nombre es descortés o incluso ofensivo. La localización debe tener esto en cuenta y evitar el uso de nombres para una audiencia japonesa.

En este artículo consideraremos solo la internacionalización, pero los mecanismos de localización se construyen de manera similar. Las bibliotecas mencionadas en este artículo admiten la localización de aplicaciones.

Tipos principales


La internacionalización se divide en:

  1. Traducción de datos directamente en scripts de python.
  2. Traducción de datos en motores de plantillas.
  3. Traducción de datos almacenados en una base de datos.

1. Traducción de datos de script python


Para que nuestra internacionalización funcione, tenemos que ocuparnos de la biblioteca babel y el kit de herramientas distutils para gestionar el montaje del proyecto para la venta y más.

Preparación de traducción


Para empezar, necesitamos crear una lista de traducciones. Para comenzar, instalamos la biblioteca Babel : esta es una biblioteca de Python generalmente reconocida para localizar y convertir fechas, monedas, con adiciones convenientes para construir el proyecto (discutido a continuación).

Python proporciona un conjunto de herramientas para el multilingüismo: gettext. GNU gettext es en realidad una solución de localización universal que proporciona soporte para otros lenguajes de programación en mensajes multilingües. Gettext se utiliza no solo en muchos lenguajes de programación, sino también en la traducción de sistemas operativos; es un software bien probado y de distribución gratuita disponible en github .

Para que funcionen las traducciones, debe importar el módulo gettext y pasar los scripts con las traducciones a la entrada. Primero, marcamos todas las cadenas traducidas con la función especial _ ('some_text'). La llamada a esta función en el proyecto se verá así:

import gettext import os localedir = os.path.join(os.path.abspath('/path/to/locales'), 'locales') translate = gettext.translation('domain_name', localedir, ['ru']) _ = translate.gettext print(_('some_text')) print(_('some_text_2')) 

En un pequeño fragmento de código, cree un objeto de internacionalización que use el directorio 'locales' como fuente de frases traducidas. El directorio 'locales' aún no se ha creado, pero es allí donde la aplicación buscará traducciones en tiempo de ejecución.

Por brevedad, la función translate.gettext se indicará a continuación como _. El subrayado es el nombre común para esta función, que es reconocida por la comunidad Python.

La función _ () marca las líneas a traducir. El módulo gettext está acompañado por la herramienta xgettext, que analiza los marcadores de cadena _ () por código y forma una plantilla de objeto portátil (archivo pot). Para crear el archivo pot, volvamos a la biblioteca de Babel instalada, que tiene muchas características para apoyar la internacionalización. Babel amplía el script de compilación setup.py, que puede escribirse utilizando la biblioteca estándar de python distutils o el paquete de herramientas de configuración de terceros de su elección. El ensamblaje de los módulos Python está más allá del alcance de nuestro artículo; para más detalles, consulte la documentación . Todo lo que se necesita es crear un archivo setup.py con el siguiente contenido:

 from babel.messages import frontend as babel from distutils.core import setup setup(name='foo', version='1.0', cmdclass = {'extract_messages': babel.extract_messages, 'init_catalog': babel.init_catalog, 'update_catalog': babel.update_catalog, 'compile_catalog': babel.compile_catalog,} ) 

Por lo tanto, creamos instrucciones para construir el proyecto y agregamos cuatro equipos de internacionalización de la biblioteca de babel. Considere estos comandos con más detalle en orden de uso.

mensajes_extractos

Este comando es un contenedor sobre la herramienta GNU xgettext, que analiza _ () etiquetas traducibles en un archivo pot. Para ejecutar, necesita varias configuraciones para el ensamblaje. Para hacer esto, en el directorio raíz, cree el archivo setup.cfg con el contenido:

 [extract_messages] input_dirs = foobar output_file = foobar/locales/messages.pot 


  • input_dirs: el nombre del directorio desde el que se seleccionarán todas las etiquetas en el código _ () para las traducciones.
  • archivo_salida: ruta del archivo .pot resultante

Para ejecutar el comando, ejecute en la consola:

 $ python setup.py extract_messages 


 running extract_messages extracting messages from foobar/__init__.py extracting messages from foobar/core.py ... writing PO template file to foobar/locales/messages.pot 

En el archivo pot, las líneas marcadas se recopilan en una lista desde la cual los traductores pueden crear traducciones para cada uno de los idiomas deseados.

 # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2018-01-28 16:47+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: src/main.py:5 msgid "some_text" msgstr "" #: src/main.py:6 msgid "some_text_2" msgstr "" 

A continuación, debe crear traducciones para varios idiomas. Para hacer esto, use los siguientes comandos de babel.

init_catalog

Este comando es un contenedor sobre la herramienta msginit de GNU, que crea un nuevo directorio de traducción basado en el archivo pot.

 $ python setup.py init_catalog -l en -i foobar/locales/messages.pot \ -o foobar/locales/en/LC_MESSAGES/base.po 

 running init_catalog creating catalog 'foobar/locales/en/LC_MESSAGES/messages.po' based on 'foobar/locales/messages.pot' 

Importante! Los archivos de localización se almacenan de forma específica, de acuerdo con la convención:

locales // LC_MESSAGES / .po

- un directorio con traducciones a un idioma específico, en nuestro caso es inglés (en). También puede haber un directorio con traducciones no solo a un idioma específico, sino también teniendo en cuenta características adicionales. Por ejemplo, una traducción al inglés para los Estados Unidos es en_US;

- dominio con traducciones. Si nuestra aplicación crece, las traducciones se dividirán en dominios para no sobrecargar un archivo.

update_catalog

Este comando es un contenedor sobre la herramienta GNU msgmerge, que actualiza los directorios de traducción existentes para archivos * .po.

Al agregar nuevas traducciones, simplemente ejecutamos el comando:

 $ python setup.py update_catalog -l en -i foobar/locales/messages.pot \ -o foobar/locales/en/LC_MESSAGES/base.po 

 running update_catalog updating catalog 'foobar/locales/en/LC_MESSAGES/base.po' based on 'foobar/locales/messages.pot' 

También podemos especificar la localización en ruso especificando ru en lugar de en.

compile_catalog

El comando final es un contenedor sobre la herramienta msgfmt de GNU. Toma mensajes traducibles de archivos * .po y los compila en archivos binarios * .mo para optimizar el rendimiento.

 $ python setup.py compile_catalog --directory foobar/locales --domain base 

 running compile_catalog compiling catalog to foobar/locales/en/LC_MESSAGES/base.mo 

--directory - ruta al directorio con localización,
--dominio - una bandera para especificar un dominio de traducción, lo especificamos de acuerdo con los dominios de aplicación existentes.

Los scripts de Python solo funcionan con traducciones optimizadas * .mo. Por lo tanto, con cualquier cambio, para que aparezca en la aplicación, es necesario volver a compilar los archivos con localización. Para trabajar con archivos de traducción, puede usar la aplicación poedit: está disponible para todos los sistemas operativos y es un software de distribución gratuita.



poedit - aplicación de traducción

Cada traducción se muestra como una línea separada, y esto es conveniente. Al finalizar el trabajo con las traducciones, al guardar los cambios, se compila automáticamente un archivo binario * .mo con todos los cambios.

Como resultado, la estructura de los catálogos de traducción se verá así:

 locales ├── en │ └── LC_MESSAGES │ ├── base.mo │ └── base.po ├── ru │ └── LC_MESSAGES │ ├── base.mo │ └── base.po └── messages.pot 


Convención de nombres de marcadores de traducción

Los archivos po contienen traducciones de texto y se combinan lógicamente en un archivo con un nombre común. Estos grupos se llaman dominios. En el ejemplo anterior, solo hay un dominio denominado base. En aplicaciones grandes, habrá más dominios, y las listas de traducción deben escribirse teniendo en cuenta la estructura de la aplicación.

Es necesario mantener la uniformidad de los nombres de los tokens de traducción para eliminar más confusión en las traducciones. Por ejemplo, tenemos un formulario para guardar datos de usuario en la página de perfil de usuario:

profile.user_form.component.title: Datos del usuario
profile.user_form.component.save: Guardar
profile.user_form.field.username: Nombre de usuario
profile.user_form.field.password: Contraseña


Implementación de aplicaciones

Para implementar y desplegar la aplicación en Docker, debe compilar los archivos de traducción en archivos binarios * .mo con el siguiente comando:

 $ python setup.py compile_catalog --domain <> 

Recomendamos excluir los archivos * .mo y * .pot en .gitignore:

# Traducciones
* .mo
* .pot

2. Traducción de datos en motores de plantillas


Con la localización en plantillas, todo es un poco más fácil. Considere el motor de plantillas de python más popular: jinja. Para este motor de plantillas, el soporte para la localización de gettext a través de complementos ya está implementado. Para activar el complemento, debe especificar la ruta al módulo del complemento en el constructor Entorno. Para plataformas multilingües, debe descargar las traducciones una vez y agregar objetos de traducción al objeto Environment durante la inicialización de la aplicación:

 translations = get_gettext_translations() env = Environment(extensions=['jinja2.ext.i18n']) env.install_gettext_translations(translations) 

Luego, en las plantillas, solo usamos las construcciones:

 {{ gettext('some_text') }} {{ gettext('Hello %(name)s!')|format(name='World') }} 

3. Traducción de los datos almacenados en la base de datos.


Consideremos opciones para trabajar con traducciones en las bases de datos relacionales más comunes. Cabe señalar que la implementación de traducciones y localización para las bases de datos noSQL y newSQL es similar.

Nota: no consideraremos el caso cuando la traducción para cada idioma se almacene en una columna separada. Dicha implementación conlleva limitaciones de escala y otros riesgos con un mayor soporte de aplicaciones.

1) líneas separadas para cada idioma


Con este enfoque, para cada idioma, la traducción a un idioma específico en las filas se basa en el valor de la columna, por ejemplo, language_code. Si el valor en está en esta columna, todos los valores traducidos deben referirse al país y la región dados.



Para el esquema descrito, los datos en la tabla deberían verse así:



Ventajas:

  • Implementación simple y eficiente.
  • Consultas simples cuando se utiliza un código de idioma específico.


Desventaja
  • Falta de centralización.

Las traducciones a diferentes idiomas se pueden almacenar en diferentes tablas. Por lo tanto, no sabe cuántos idiomas está traducida completamente su aplicación.

Esta solución es adecuada para aplicaciones que inicialmente no requieren una internacionalización completa de todos los datos. Pero es posible agregar traducciones para nuevas regiones a medida que el negocio se expande.

La solicitud de datos será la siguiente:

 SELECT p.product_name, p.price, p.description FROM product p WHERE p.language_code = @language_code; 

2) Tablas separadas con traducciones


En este enfoque, para cada tabla que requiere localización, creamos tablas con traducciones.



Pros:

  • No hay necesidad de unir tablas para datos no traducidos.
  • Las consultas se vuelven fáciles ya que hay tablas separadas para la traducción.
  • No hay discrepancias en los datos.
  • Además de las traducciones, es posible localizar efectivamente el resto de los datos en la tabla de idiomas.

Desventaja

  • En aplicaciones grandes, la tabla de traducción está hinchada y se ralentiza. Al optimizar la aplicación, será necesario implementar la migración de datos en tablas separadas.

La solicitud de datos será la siguiente:

 SELECT tp.text, p.price, tc.text, c.contact_name FROM order_line o, product p, customer c, translation tp, translation tc, language l WHERE o.product_id = p.id AND o.customer_id = c.id AND p.name_translation_id = tp.id AND c.name_translation_id = tc.id AND tp.language_id = l.id AND tc.language_id = l.id AND l.name = @language_code AND o.id = ***; 

3) Crear entidades para campos traducidos y no traducidos


En esta solución, las tablas de entidad que contienen uno o más campos traducidos amplían los datos con los no traducidos.



Pros:

  • No es necesario combinar tablas de traducción con tablas que contienen datos que no requieren traducción. Por lo tanto, el muestreo de dichos datos tendrá un mejor rendimiento,
  • Es fácil escribir consultas ORM,
  • Una simple consulta SQL para obtener texto traducido,
  • Es fácil admitir la traducción de ciertos datos a todos los idiomas disponibles.

Desventaja

  • La relativa complejidad de la implementación.

Aquí hay un ejemplo de una consulta que recuperará el texto traducido:

 SELECT pt.product_name, pt.description, p.price FROM order_line o, product p, product_translation pt, language l WHERE o.product_id = p.id AND AND p.id = pt.product_non_trans_id AND pt.language_id = l.id AND l.name = @language_code; 

Conclusiones


Al localizar e internacionalizar aplicaciones para el mercado internacional, se pueden utilizar varios métodos, cada uno de los cuales tiene ciertas características y limitaciones.

En este artículo, examinamos los siguientes tipos de internacionalización:

  • en código: usamos traducciones al crear un servicio o aplicación con gui;
  • en plantillas: usamos cuando desarrollamos una aplicación web sin una interfaz dinámica;
  • en la base de datos: utilícelo al almacenar datos de usuario o generados dinámicamente.

Esperamos que nuestro artículo lo ayude a elegir el método más adecuado para su proyecto.

Source: https://habr.com/ru/post/479738/


All Articles