Guía¶
Esta página es la referencia de tiempo de ejecución: todo lo que hace el código de tu aplicación con esta biblioteca una vez que existen los catálogos. Si aún no has visto el ciclo completo —marcar, extraer, traducir, compilar, ejecutar—, el tutorial lo recorre una vez en cinco minutos; la creación y validación de catálogos se trata en Extracción, y cómo un equipo mantiene el ciclo en marcha —ciclos de actualización, CI, plataformas de traducción— se explica en En producción.
¿Qué punto de entrada debo usar?¶
El paquete exporta varias formas de traducir un mensaje porque las aplicaciones vinculan un idioma de maneras muy distintas. Elige según cómo decide tu programa en qué idioma está:
| Tu situación | Usa |
|---|---|
| Un solo idioma para todo el proceso: una CLI, una app de escritorio, un script | Translator, invocado como _ |
| Un idioma por petición o por tarea asíncrona: una aplicación web | use_translations() alrededor del trabajo y después tr() |
| Un mensaje definido durante el import: una etiqueta de formulario, un enum, una constante | lazy_gettext() o lazy_pgettext() |
| Una cantidad decide la redacción | ngettext() / npgettext(), en cualquiera de las formas anteriores |
| Renderizar un pattern sin catálogo de por medio | compile_template() |
Todo lo que sigue son esos cinco casos, en ese orden.
Vincular un catálogo¶
La forma recomendada refleja el uso de gettext basado en clases: vincula una vez
un objeto de traducción estándar y utiliza el procesador invocable como _.
import gettext
from gettext_tstrings import Translator
translations = gettext.translation("messages", localedir="locales", languages=["ja"])
_ = Translator(translations)
name = "Ada"
print(_(t"Hello {name}")) # こんにちは Ada
n = 3
print(_.ngettext(t"One file", t"{n} files", n)) # picks the right plural form for n
filename = "report.txt"
print(_.pgettext("button", t"Open {filename}")) # "button" disambiguates homonyms
Las funciones de módulo siguen los nombres y la convención de argumentos solo posicionales de la biblioteca estándar:
from gettext_tstrings import gettext, ngettext, npgettext, pgettext
gettext(t"Hello {name}", translations=translations)
ngettext(t"One file", t"{n} files", n, translations=translations)
pgettext("button", t"Open {filename}", translations=translations)
npgettext("inbox", t"One message", t"{n} messages", n, translations=translations)
tr y ntr son alias exactos de gettext y ngettext.
Idioma por petición¶
Un framework web elige un idioma para cada petición. Vincula las traducciones de la petición al contexto actual y todas las llamadas de módulo se resolverán en ese idioma, de forma segura incluso entre peticiones concurrentes:
from gettext_tstrings import tr, use_translations
def handle(request):
name = request.user.display_name
translations = load_translations(request.locale)
with use_translations(translations):
return render(tr(t"Hello {name}"))
set_translations(translations) vincula sin bloque with para los frameworks
que administran el ciclo de vida por su cuenta; get_translations() lee la
vinculación actual. Un argumento translations= explícito siempre prevalece
sobre el contexto. Si no hay vinculación, se utilizan las funciones gettext
instaladas globalmente en la biblioteca estándar. Hay ejemplos completos para
Flask y middleware ASGI en la página
En producción.
Traducción diferida¶
Una t-string captura sus valores de inmediato. Eso no sirve para una cadena definida durante el import —una etiqueta de formulario, un valor enum o una constante de módulo— que debe renderizarse en el idioma activo cuando se usa.
from gettext_tstrings import lazy_gettext, lazy_pgettext, use_translations
SAVE = lazy_gettext(t"Save changes") # defined once, at import
OPEN = lazy_pgettext("button", t"Open file")
with use_translations(japanese):
assert str(SAVE) == "変更を保存" # rendered here, in this language
Un LazyString se renderiza mediante str(), format() y f-strings, y se
compara como igual a su texto renderizado.
Deliberadamente sin hash
El texto de un LazyString depende del idioma activo. Si su hash cambiara al
cambiar de idioma, corrompería silenciosamente cualquier set o dict que lo
contuviera. Llama primero a str() si necesitas una clave.
strict se decide donde se escribe el mensaje, no donde se renderiza:
Una cadena diferida se renderiza allí donde acaba usándose —dentro de una
plantilla, un formulario o una línea de log— y ese lugar rara vez sabe si se
trata de una ejecución de pruebas o de producción. Pasar strict=True en la
definición es lo que permite aplicar la misma elección
estricta en CI, permisiva en producción
a una cadena que no se renderiza en su punto de llamada.
Las formas plurales dependen de una cantidad en tiempo de ejecución, así que se
renderizan inmediatamente con ngettext donde se conoce esa cantidad.
Varios idiomas a la vez¶
Una misma petición necesita a menudo más de un idioma: una página renderizada para el lector que además encola una notificación a una cuenta configurada en otro, o un resumen que cita a cada participante en el suyo. Las vinculaciones se anidan, y al salir del bloque interior se restaura la del exterior.
with use_translations(reader):
page = tr(t"Hello {name}")
with use_translations(recipient):
notice = tr(t"Hello {name}") # the recipient's language
footer = tr(t"Hello {name}") # the reader's again
Sobre una lista de destinatarios, el trabajo lo hacen las cadenas diferidas: el mensaje se escribe una sola vez, en el import, y se renderiza una vez por idioma.
SUBJECT = lazy_gettext(t"Your order shipped")
for user in users:
with use_translations(load_translations(user.locale)):
send(user.email, str(SUBJECT))
La vinculación es un ContextVar, no una pila guardada en un objeto compartido,
así que las peticiones que se solapan no pueden tomar el idioma de otra
—incluido el caso en que salen de sus bloques en el mismo orden en que
entraron, que es el entrelazado que una pila LIFO resuelve mal—. Cargar un
catálogo por idioma es barato: gettext.translation() analiza cada .mo una
sola vez y entrega copias que comparten el catálogo ya analizado.
Que un hilo de trabajo herede la vinculación depende de la build
Un threading.Thread sin más, o ThreadPoolExecutor.submit, arranca o bien
desde una copia del contexto de quien llama o bien desde uno vacío, y cuál de
las dos cosas lo decide sys.flags.thread_inherit_context —verdadero por
omisión en las builds free-threaded, falso en todo lo demás—. Por eso el
mismo código renderiza el idioma vinculado en 3.14t y el catálogo global del
proceso en 3.14. Pasa el contexto en lugar de depender del valor por omisión:
asyncio.to_thread ya lo hace por ti.
Valores dependientes del locale¶
Esta biblioteca decide dónde aparece un valor dentro de un mensaje traducido.
No localiza el valor en sí. {amount:,.2f} es una especificación de formato de
Python con un comportamiento fijo —una coma cada tres dígitos y un punto antes
de los decimales— y produce los mismos caracteres sea cual sea el idioma del
mensaje:
El alemán escribe ese número 1.234,50; el francés, 1 234,50; y el hindi
agrupa 1234567 como 12,34,567 en lugar de 1,234,567. Los números, las
monedas, las fechas, las horas y las unidades son cosa de Babel.
Formatea primero el valor y coloca después la cadena ya terminada:
from babel.numbers import format_currency
total = format_currency(amount, "EUR", locale=locale)
tr(t"Your order comes to {total}")
En un mensaje con cantidad, el número hace dos trabajos —selecciona la forma plural y aparece en el texto— y solo el segundo se localiza. Conserva la cantidad en bruto para la selección y pasa la cadena formateada para mostrarla:
from babel.numbers import format_decimal
shown = format_decimal(n, locale=locale)
_.ngettext(t"One file", t"{shown} files", n)
Formatear antes de la llamada es también lo que mantiene una especificación de formato fuera del catálogo: lo que ve un traductor es un fragmento de texto ya terminado, no un número acompañado de instrucciones para representarlo.
Qué ocurre cuando un catálogo es incorrecto¶
Si los marcadores de una traducción no coinciden con el origen —un campo ausente, desconocido o reformateado que eludió la validación, procedente de un MO editado a mano, un catálogo de un proveedor o un pipeline que omite el checker—, el comportamiento predeterminado es renderizar el mensaje de origen en vez de lanzar una excepción. Así se respeta el contrato de gettext: un catálogo incorrecto nunca debe romper la aplicación.
Si Hello {name} se traduce como こんにちは {nombre}, el renderizado tiene
éxito y se envía una advertencia al logger gettext_tstrings:
WARNING gettext_tstrings: invalid translation for msgid 'Hello {name}'; using
source text: translation does not match the source placeholders: {name} is
missing; {nombre} is not in the source message
La advertencia se emite una vez por combinación de mensaje y pattern, no una vez por renderizado, para que una entrada defectuosa no inunde el registro.
En pruebas y CI se puede optar por fallar de inmediato:
strict = Translator(translations, strict=True)
tr(t"Hello {name}", translations=translations, strict=True)
La misma búsqueda lanza entonces una excepción con la misma frase, pero sin la parte «using source text»:
>>> strict(t"Hello {name}")
Traceback (most recent call last):
...
gettext_tstrings.errors.InvalidTranslationError: translation does not match the
source placeholders: {name} is missing; {nombre} is not in the source message
Estos mensajes están escritos para quien pueda resolver el problema, que en un
catálogo suele ser un traductor más que un programador; por eso, cuando un
marcador parece estar presente pero no lo está, el mensaje explica el motivo en
lugar de repetir que falta. Llaves de ancho completo, un {{name}} duplicado,
un espacio de no separación invisible, una letra cirílica entre latinas: cada
caso tiene su propia redacción, y todos están enumerados con ejemplos en
Para traductores. Esa página está
escrita para entregársela a quien edita el .po.
Renderizar un pattern sin catálogo¶
compile_template expone el mismo mecanismo un nivel más abajo: convierte una
t-string en su msgid y un conjunto vinculado de valores, y renderiza cualquier
pattern proporcionado.
from gettext_tstrings import compile_template
name = "Ada"
compiled = compile_template(t"Hello {name}")
compiled.msgid # "Hello {name}"
compiled.placeholders # ("name",)
compiled.render("こんにちは {name}") # "こんにちは Ada"
render valida con las mismas reglas y siempre lanza si no coincide. Aquí no
existe modo lenient: se utiliza para que una búsqueda de catálogo pueda volver
al texto de origen, y un pattern proporcionado directamente no tiene origen al
que volver.
Seguridad y alcance¶
Esto es válido:
Esto se rechaza deliberadamente:
Calcula primero un valor con significado:
La restricción produce claves de catálogo estables, ofrece nombres útiles a los traductores y evita que una cadena traducida se convierta en un lenguaje de expresiones.
La garantía se limita a la estructura y el formato: una traducción nunca se evalúa ni puede añadir accesos a atributos, llamadas, conversiones o especificaciones de formato. Dos responsabilidades siguen correspondiendo al llamador, igual que con gettext de la biblioteca estándar: escapar el resultado para su destino (HTML, shell o terminal) y mantener la integridad del catálogo. Un catálogo hostil puede repetir un marcador para amplificar el tamaño de la salida, algo inherente a cualquier i18n basado en marcadores.