gettext — Catálogos de mensajes

Propósito:Interfaz de programación de catálogo de mensajes para internacionalización

El módulo gettext proporciona una implementación en Python puro compatible con la biblioteca gettext de GNU para la traducción de mensajes y la gestión de catálogos. Las herramientas disponibles con el código fuente de Python le permiten extraer mensajes de un conjunto de archivos fuente, crear un catálogo de mensajes que contenga traducciones y usar ese catálogo de mensajes para mostrar un mensaje apropiado para el usuario en tiempo de ejecución.

Los catálogos de mensajes se pueden usar para proporcionar interfaces internacionalizadas para un programa, mostrando mensajes en un idioma apropiado para el usuario. También se pueden usar para otras personalizaciones de mensajes, incluyendo «forrar» una interfaz para diferentes contenedores o socios.

Nota

Aunque la documentación de la biblioteca estándar dice que todas las herramientas necesarias están incluidas con Python, pygettext.py no pudo extraer los mensajes envueltos en la llamada ngettext, incluso con las opciones de línea de comando apropiadas. En su lugar, estos ejemplos usan xgettext del conjunto de herramientas gettext de GNU.

Descripción general del flujo de trabajo de traducción

El proceso para configurar y usar traducciones incluye cinco pasos.

  1. Identificar y marcar cadenas literales en el código fuente que contienen mensajes para traducir.

    Comienza identificando los mensajes dentro del código fuente del programa que deben traducirse y marcando las cadenas literales para que el programa de extracción pueda encontrarlos.

  2. Extraer los mensajes.

    Después de identificar las cadenas traducibles en el código fuente, usa xgettext para extraerlas y crear un archivo .pot o plantilla de traducción. La plantilla es un archivo de texto con copias de todas las cadenas identificadas y marcadores de posición para sus traducciones.

  3. Traducir los mensajes.

    Entrega una copia del archivo .pot al traductor, cambiando la extensión a .po. El archivo .po es un archivo fuente editable utilizado como entrada para el paso de compilación. El traductor debe actualizar el texto del encabezado en el archivo y proporcionar traducciones para todas las cadenas.

  4. «Compilar» el catálogo de mensajes de la traducción.

    Cuando el traductor devuelve el archivo .po completo, compila el archivo de texto en el formato de catálogo binario usando msgfmt. El código de búsqueda del catálogo de tiempo de ejecución utiliza el formato binario.

  5. Cargar y activar el catálogo de mensajes apropiado en tiempo de ejecución.

    El paso final es agregar algunas líneas a la aplicación para configurar y cargar el catálogo de mensajes e instalar la función de traducción. Hay un par de formas de hacerlo, con compensaciones asociadas.

El resto de esta sección examinará esos pasos con un poco más de detalle, comenzando con las modificaciones de código fuente necesarias.

Crear catálogos de mensajes a partir del código fuente

gettext funciona buscando cadenas literales en una base de datos de traducciones y extrayendo la cadena traducida adecuada. El patrón habitual es vincular la función de búsqueda apropiada con el nombre «_» (un guión bajo) para que el código no esté lleno de muchas llamadas a funciones con nombres más largos.

El programa de extracción de mensajes, xgettext, busca mensajes incrustados en llamadas a las funciones de búsqueda del catálogo. Entiende diferentes lenguajes de origen y utiliza un analizador apropiado para cada uno. Si las funciones de búsqueda tienen un alias o se agregan funciones adicionales, proporciona a xgettext los nombres de los símbolos adicionales a tener en cuenta al extraer mensajes.

Esta secuencia de comandos tiene un solo mensaje listo para ser traducido.

gettext_example.py
import gettext

# Set up message catalog access
t = gettext.translation(
    'example_domain', 'locale',
    fallback=True,
)
_ = t.gettext

print(_('This message is in the script.'))

El texto "This message is in the script." es el mensaje que se sustituirá del catálogo. El modo de reserva está habilitado, por lo que si se ejecuta la secuencia de comando sin un catálogo de mensajes, se imprime el mensaje en línea.

$ python3 gettext_example.py

This message is in the script.

El siguiente paso es extraer el mensaje y crear el archivo .pot, usando pygettext.py o xgettext.

$ xgettext -o example.pot gettext_example.py

El archivo de salida producido contiene el siguiente contenido.

example.pot
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-18 16:20-0400\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: gettext_example.py:19
msgid "This message is in the script."
msgstr ""

Los catálogos de mensajes se instalan en directorios organizados por dominio e idioma. El dominio es proporcionado por la aplicación o biblioteca, y generalmente es un valor único como el nombre de la aplicación. En este caso, el dominio en gettext_example.py es example_domain. El valor de idioma lo proporciona el entorno del usuario en tiempo de ejecución, a través de una de las variables de entorno LANGUAGE, LC_ALL, LC_MESSAGES o LANG, según su configuración y plataforma. Todos estos ejemplos se ejecutaron con el idioma establecido en en_US.

Ahora que la plantilla está lista, el siguiente paso es crear la estructura de directorio requerida y copiar la plantilla en el lugar correcto. El directorio locale dentro del árbol fuente PyMOTW servirá como la raíz del directorio del catálogo de mensajes para estos ejemplos, pero generalmente es mejor usar un directorio accesible en todo el sistema para que todos los usuarios tengan acceso a los catálogos de mensajes. La ruta completa a la fuente de entrada del catálogo es $localedir/$language/LC_MESSAGES/$domain.po, y el catálogo tiene la extensión de nombre de archivo .mo.

El catálogo se crea copiando example.pot en locale/en_US/LC_MESSAGES/example.po y editándolo para cambiar los valores en el encabezado y establecer los mensajes alternativos. El resultado se muestra a continuación.

locale/en_US/LC_MESSAGES/example.po
# Messages from gettext_example.py.
# Copyright (C) 2009 Doug Hellmann
# Doug Hellmann <doug@doughellmann.com>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: PyMOTW-3\n"
"Report-Msgid-Bugs-To: Doug Hellmann <doug@doughellmann.com>\n"
"POT-Creation-Date: 2016-01-24 13:04-0500\n"
"PO-Revision-Date: 2016-01-24 13:04-0500\n"
"Last-Translator: Doug Hellmann <doug@doughellmann.com>\n"
"Language-Team: US English <doug@doughellmann.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"


#: gettext_example.py:16
msgid "This message is in the script."
msgstr "This message is in the en_US catalog."

El catálogo se construye a partir del archivo .po usando msgformat.

$ cd locale/en_US/LC_MESSAGES; msgfmt -o example.mo example.po

El dominio en gettext_example.py es example_domain, pero el archivo se llama example.pot. Para que gettext encuentre el archivo de traducción correcto, los nombres deben coincidir.

gettext_example_corrected.py
t = gettext.translation(
    'example', 'locale',
    fallback=True,
)

Ahora, cuando se ejecuta la secuencia de comandos, se imprime el mensaje del catálogo en lugar de la cadena en línea.

$ python3 gettext_example_corrected.py

This message is in the en_US catalog.

Encontrar catálogos de mensajes en tiempo de ejecución

Como se describió anteriormente, el directorio de configuración regional que contiene los catálogos de mensajes está organizado según el idioma con los catálogos nombrados para el dominio del programa. Los diferentes sistemas operativos definen su propio valor predeterminado, pero gettext no conoce todos estos valores predeterminados. Utiliza un directorio de configuración regional predeterminado de sys.prefix + '/share/locale'i, pero la mayoría de las veces es más seguro dar siempre explícitamente un valor de localedir que depender de que este valor predeterminado sea válido. La función find() es responsable de localizar un catálogo de mensajes apropiado en tiempo de ejecución.

gettext_find.py
import gettext

catalogs = gettext.find('example', 'locale', all=True)
print('Catalogs:', catalogs)

La parte del idioma de la ruta se toma de una de varias variables de entorno que se pueden usar para configurar las características de localización (LANGUAGE, LC_ALL, LC_MESSAGES y LANG). Se utiliza la primera variable encontrada para establecer. Se pueden seleccionar varios idiomas separando los valores con dos puntos (:). Para ver cómo funciona, use un segundo catálogo de mensajes para ejecutar algunos experimentos.

$ cd locale/en_CA/LC_MESSAGES; msgfmt -o example.mo example.po
$ cd ../../..
$ python3 gettext_find.py

Catalogs: ['locale/en_US/LC_MESSAGES/example.mo']

$ LANGUAGE=en_CA python3 gettext_find.py

Catalogs: ['locale/en_CA/LC_MESSAGES/example.mo']

$ LANGUAGE=en_CA:en_US python3 gettext_find.py

Catalogs: ['locale/en_CA/LC_MESSAGES/example.mo',
'locale/en_US/LC_MESSAGES/example.mo']

$ LANGUAGE=en_US:en_CA python3 gettext_find.py

Catalogs: ['locale/en_US/LC_MESSAGES/example.mo',
'locale/en_CA/LC_MESSAGES/example.mo']

Although find() shows the complete list of catalogs, only the first one in the sequence is actually loaded for message lookups.

$ python3 gettext_example_corrected.py

This message is in the en_US catalog.

$ LANGUAGE=en_CA python3 gettext_example_corrected.py

This message is in the en_CA catalog.

$ LANGUAGE=en_CA:en_US python3 gettext_example_corrected.py

This message is in the en_CA catalog.

$ LANGUAGE=en_US:en_CA python3 gettext_example_corrected.py

This message is in the en_US catalog.

Valores plurales

While simple message substitution will handle most translation needs, gettext treats pluralization as a special case. Depending on the language, the difference between the singular and plural forms of a message may vary only by the ending of a single word, or the entire sentence structure may be different. There may also be different forms depending on the level of plurality. To make managing plurals easier (and, in some cases, possible), there is a separate set of functions for asking for the plural form of a message.

gettext_plural.py
from gettext import translation
import sys

t = translation('plural', 'locale', fallback=False)
num = int(sys.argv[1])
msg = t.ngettext('{num} means singular.',
                 '{num} means plural.',
                 num)

# Still need to add the values to the message ourself.
print(msg.format(num=num))

Use ngettext() to access the plural substitution for a message. The arguments are the messages to be translated and the item count.

$ xgettext -L Python -o plural.pot gettext_plural.py

Since there are alternate forms to be translated, the replacements are listed in an array. Using an array allows translations for languages with multiple plural forms (for example, Polish has different forms indicating the relative quantity).

plural.pot
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-03-18 16:20-0400\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: gettext_plural.py:15
#, python-brace-format
msgid "{num} means singular."
msgid_plural "{num} means plural."
msgstr[0] ""
msgstr[1] ""

In addition to filling in the translation strings, the library needs to be told about the way plurals are formed so it knows how to index into the array for any given count value. The line "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" includes two values to replace manually. nplurals is an integer indicating the size of the array (the number of translations used) and plural is a C language expression for converting the incoming quantity to an index in the array when looking up the translation. The literal string n is replaced with the quantity passed to ungettext().

For example, English includes two plural forms. A quantity of 0 is treated as plural («0 bananas»). The Plural-Forms entry is:

Plural-Forms: nplurals=2; plural=n != 1;

The singular translation would then go in position 0, and the plural translation in position 1.

locale/en_US/LC_MESSAGES/plural.po
# Messages from gettext_plural.py
# Copyright (C) 2009 Doug Hellmann
# This file is distributed under the same license 
# as the PyMOTW package.
# Doug Hellmann <doug@doughellmann.com>, 2016.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PyMOTW-3\n"
"Report-Msgid-Bugs-To: Doug Hellmann <doug@doughellmann.com>\n"
"POT-Creation-Date: 2016-01-24 13:04-0500\n"
"PO-Revision-Date: 2016-01-24 13:04-0500\n"
"Last-Translator: Doug Hellmann <doug@doughellmann.com>\n"
"Language-Team: en_US <doug@doughellmann.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;"

#: gettext_plural.py:15
#, python-format
msgid "{num} means singular."
msgid_plural "{num} means plural."
msgstr[0] "In en_US, {num} is singular."
msgstr[1] "In en_US, {num} is plural."

Running the test script a few times after the catalog is compiled will demonstrate how different values of N are converted to indexes for the translation strings.

$ cd locale/en_US/LC_MESSAGES/; msgfmt -o plural.mo plural.po
$ cd ../../..
$ python3 gettext_plural.py 0

In en_US, 0 is plural.

$ python3 gettext_plural.py 1

In en_US, 1 is singular.

$ python3 gettext_plural.py 2

In en_US, 2 is plural.

Localización de aplicación versus módulos

El alcance de un esfuerzo de traducción define cómo se instala y utiliza gettext con un cuerpo de código.

Localización de aplicaciones

Para las traducciones de toda la aplicación, es aceptable que el autor instale una función como ngettext() globalmente usando el espacio de nombres __builtins__, porque tienen control sobre el nivel superior del código de la aplicación.

gettext_app_builtin.py
import gettext

gettext.install(
    'example',
    'locale',
    names=['ngettext'],
)

print(_('This message is in the script.'))

La función install() une gettext() al nombre _() en el espacio de nombres __builtins__. También agrega ngettext() y otras funciones enumeradas en names.

Localización de módulos

Para una biblioteca o módulo individual, modificar __builtins__ no es una buena idea porque puede generar conflictos con el valor global de una aplicación. En su lugar, importa o vuelve a vincular los nombres de las funciones de traducción a mano en la parte superior del módulo.

gettext_module_global.py
import gettext

t = gettext.translation(
    'example',
    'locale',
    fallback=False,
)
_ = t.gettext
ngettext = t.ngettext

print(_('This message is in the script.'))

Cambio de traducciones

Todos los ejemplos anteriores usan una sola traducción para la duración del programa. Algunas situaciones, especialmente las aplicaciones web, necesitan utilizar diferentes catálogos de mensajes en diferentes momentos, sin salir y restablecer el entorno. Para esos casos, la interfaz de programación basada en la clase proporcionada en gettext será más conveniente. Las llamadas a la interfaz de programación son esencialmente las mismas que las llamadas globales descritas en esta sección, pero el objeto del catálogo de mensajes es expuesto y se puede manipular directamente, de modo que se pueden usar múltiples catálogos.

Ver también

  • Documentación de la biblioteca estándar para gettext
  • locale – Otras herramientas de localización.
  • GNU gettext – Los formatos de catálogo de mensajes, la intefaz de programación, etc. para este módulo se basan en el paquete gettext original de GNU. Los formatos de archivo de catálogo son compatibles y los scripts de línea de comandos tienen opciones similares (si no son idénticas). El manual de GNU gettext. <http://www.gnu.org/software/gettext/manual/gettext.html>`_ tiene una descripción detallada de los formatos de archivo y describe las versiones de GNU de las herramientas para trabajar con ellos.
  • Plural forms – Manejo de formas plurales de palabras y oraciones en diferentes idiomas.
  • Internationalizing Python – Un artículo de Martin von Löwis sobre técnicas para la internacionalización de aplicaciones Python.
  • Django Internationalization – Otra buena fuente de información sobre el uso de gettext, que incluye ejemplos de la vida real.