# General Translation Knowledge Base: ¿Cómo traduzco archivos PO y POT?
URL: https://generaltranslation.com/es/kb/translate-po-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Rellena cada msgstr, incluidas todas las formas plurales que pide la cabecera Plural-Forms, y deja msgid, msgctxt y los marcadores de posición sin cambios. Ejecuta msgfmt --check para verificar las cabeceras, el número de formas plurales y los marcadores de posición en las entradas con una opción de formato, y comprueba por separado que ningún msgstr esté vacío. La CLI de General Translation genera un archivo PO traducido para cada idioma a partir de tu plantilla POT.
Category: Traducción
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

Un archivo POT (Portable Object Template) contiene las cadenas de origen extraídas de tu código, y cada archivo PO (Portable Object) asocia esas cadenas con sus traducciones a un idioma concreto. Traducir consiste en rellenar cada `msgstr`, uno por cada forma plural que use el idioma, mientras que `msgid`, `msgctxt` y los marcadores de posición se mantienen exactamente como se extrajeron. Después, compilas cada archivo PO en el catálogo binario `.mo` que carga tu programa. El formato es sencillo, pero sus fallos pasan desapercibidos: gettext muestra el texto de origen en lugar de todo lo que no puede usar.

## De dónde provienen los archivos POT y PO

Toda plataforma basada en gettext ejecuta los mismos cuatro pasos, aunque con distintos nombres de comando:

| Paso                                                               | GNU gettext | Babel (Flask, FastAPI) |
| ------------------------------------------------------------------ | ----------- | ---------------------- |
| Extraer las cadenas de origen a una plantilla                      | `xgettext`  | `pybabel extract`      |
| Crear un archivo PO para un nuevo idioma                           | `msginit`   | `pybabel init`         |
| Fusionar los cambios de la plantilla en los archivos PO existentes | `msgmerge`  | `pybabel update`       |
| Compilar un archivo PO a `.mo`                                     | `msgfmt`    | `pybabel compile`      |

Django reúne los tres primeros en `makemessages`, que genera los archivos PO directamente, y compila con `compilemessages`. La traducción se realiza entre la fusión y la compilación: la plantilla no está asociada a ningún idioma, así que solo los archivos PO contienen traducciones. (Consulta [General Translation vs. GNU gettext](/kb/general-translation-vs-gettext) para ver el ciclo completo de gettext, incluido lo que hace `msgmerge` cuando cambia una cadena de origen).

## Qué contiene una entrada PO

Este es el archivo PO en polaco que la CLI de General Translation generó a partir de una plantilla que `xgettext` extrajo de un breve script de Python (con la cabecera abreviada):

```text title="po/pl.po"
msgid ""
msgstr ""
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2;\n"

#: inbox.py:8
#. TRANSLATORS: Heading of the inbox page.
msgid "Inbox"
msgstr "Skrzynka odbiorcza"

#: inbox.py:9
#, python-format
msgid "You have %(num)d unread message."
msgid_plural "You have %(num)d unread messages."
msgstr[0] "Masz %(num)d nieprzeczytaną wiadomość."
msgstr[1] "Masz %(num)d nieprzeczytane wiadomości."
msgstr[2] "Masz %(num)d nieprzeczytanych wiadomości."

#: inbox.py:10
msgctxt "email action"
msgid "Archive"
msgstr "Archiwizuj"
```

El [manual de GNU gettext](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) define cada parte:

* **`msgid` y `msgid_plural`** son el texto de origen, y `msgid` es además la clave de búsqueda. Nunca los edites en un archivo PO: un `msgid` modificado no coincide con nada en el código, así que los usuarios verán el texto de origen.
* **`msgstr`** es la traducción, y es la única línea que tú escribes. Una entrada plural va desde `msgstr[0]` hasta el número de formas plurales del idioma menos uno.
* **`msgctxt`** distingue cadenas de origen idénticas con significados distintos, como &quot;Archive&quot; (la acción de archivar un correo) frente a una carpeta de archivo. Procede de `pgettext`, cada contexto tiene su propia traducción y se conserva tal como se extrajo.
* **Los comentarios extraídos `#.`** son notas de los desarrolladores (`xgettext --add-comments` los extrae del código), **las referencias `#:`** apuntan a líneas del código fuente y **los comentarios del traductor `# `** son para quien traduce.
* **Las opciones `#,`** son de dos tipos. `fuzzy` es una opción del flujo de trabajo que indica que la traducción puede ser incorrecta y que una persona debe revisarla. Las opciones de formato, como `c-format` y `python-format`, las añade `xgettext` e indican a `msgfmt` que compare los marcadores de posición del origen y de la traducción.
* **La cabecera** es la entrada con un `msgid` vacío. Sus campos `Language`, el charset de `Content-Type` y `Plural-Forms` se aplican a todo el archivo.

## Cómo determina Plural-Forms el número de traducciones

`Plural-Forms` indica el número de formas (`nplurals`) y una expresión al estilo de C que asigna a cada recuento `n` un índice de forma ([manual de GNU gettext: formas plurales](https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html)). Con GNU gettext 1.0, `msginit` generaba `nplurals=1; plural=0;` para el japonés, `nplurals=2; plural=(n != 1);` para el alemán, `nplurals=2; plural=(n > 1);` para el francés (donde el cero es singular) y tres formas para el polaco y el ruso. El árabe necesita seis. En el caso del árabe, ese mismo `msginit` dejaba en la cabecera el marcador de posición `nplurals=INTEGER; plural=EXPRESSION;`, así que revisa la cabecera antes de que nadie empiece a traducir.

Una vez compilado el archivo polaco, `ngettext` de Python elige una posición distinta según el recuento:

```text title="Output"
count  slot        output
1      msgstr[0]   Masz 1 nieprzeczytaną wiadomość.
3      msgstr[1]   Masz 3 nieprzeczytane wiadomości.
5      msgstr[2]   Masz 5 nieprzeczytanych wiadomości.
22     msgstr[1]   Masz 22 nieprzeczytane wiadomości.
```

Un texto de origen en inglés tiene dos formas, así que traducirlo entrada por entrada a dos cadenas en polaco deja `msgstr[2]` sin nada correcto que mostrar para 5 o 12.

## Qué falla al traducir un archivo PO

Todos estos problemas compilan o se ejecutan sin errores, y por eso llegan a producción:

* **Las entradas fuzzy nunca llegan a los usuarios.** `msgfmt` las excluye del archivo `.mo` a menos que pases `--use-fuzzy`, algo que el manual califica de &quot;normalmente incorrecto&quot;. Tras marcar como fuzzy la entrada plural, la compilación en polaco mostró &quot;You have 5 unread messages.&quot; en inglés. Revisa cada entrada fuzzy y después quita la opción.
* **Una entrada de plural vacía puede no mostrar nada.** `msgfmt` compiló una entrada con un `msgstr[2]` vacío y la contó como traducida, y Python mostró una línea en blanco para un recuento de 5. `--check` solo la rechaza cuando la entrada tiene una opción de formato, porque en ese caso a la entrada de plural vacía le falta el marcador de posición; sin opción, la entrada pasa la comprobación.
* **Los marcadores de posición se pierden o se renombran.** Una traducción sin `%(num)d` pierde el recuento. `msgfmt --check` solo lo detecta en entradas con una opción de formato.
* **Las comillas y los escapes siguen el estilo de C.** Una `"` dentro de `msgstr` debe escribirse como `\"`; una comilla sin escapar provoca un error de sintaxis, y un salto de línea dentro de una cadena se escribe `\n`.
* **En las plantillas, el charset es un marcador de posición.** `xgettext` escribe `charset=CHARSET` en la cabecera del POT, y `msgfmt --check` rechaza cualquier archivo PO que aún lo conserve. `msginit` y `pybabel` escriben UTF-8; si una herramienta traduce directamente una copia de la plantilla, ejecuta primero `msgconv --to-code=UTF-8` sobre la plantilla.

`msgfmt --check` informa de los problemas que es capaz de detectar, y `--statistics` cuenta lo que llegó a la compilación:

```text title="msgfmt --check"
$ msgfmt --check -o /dev/null missing-placeholder.po
missing-placeholder.po:30: a format specification for argument 'num' doesn't exist in 'msgstr[2]'
msgfmt: found 1 fatal error

$ msgfmt --check -o /dev/null two-forms.po
two-forms.po:7: nplurals = 3...
two-forms.po:30: ...but some messages have only 2 plural forms
msgfmt: found 1 fatal error

$ msgfmt --check --statistics -o inbox.mo fuzzy.po
2 translated messages, 1 fuzzy translation.
```

Ejecútalo en CI con cada archivo PO y considera trabajo pendiente cualquier recuento de entradas fuzzy superior a cero.

Ninguna herramienta de gettext detecta una entrada plural con una sola entrada de plural vacía: `msgfmt --check` la da por válida cuando la entrada no tiene una opción de formato, y `msgattrib --untranslated` la omite. Un script breve con [Babel](https://babel.pocoo.org/) (`pip install babel`) hace fallar la compilación si encuentra alguna traducción vacía:

```python title="check_po.py"
import sys

from babel.messages.pofile import read_po

failed = False
for path in sys.argv[1:]:
    with open(path, "rb") as f:
        catalog = read_po(f)
    for message in catalog:
        if not message.id or message.fuzzy:
            continue  # la cabecera y las entradas fuzzy que msgfmt ya omite
        forms = message.string if isinstance(message.string, tuple) else (message.string,)
        if not all(forms):
            print(f"{path}:{message.lineno}: empty translation for {message.id!r}")
            failed = True
sys.exit(1 if failed else 0)
```

```text title="Output"
$ python check_po.py po/pl.po
po/pl.po:17: empty translation for ('You have %(num)d unread message.', 'You have %(num)d unread messages.')
```

## Formas de traducir archivos PO

| Enfoque                                                                                                            | Quién traduce                                       | Entradas de plural                                                                   | Contexto que recibe el traductor                                                                  | Cuando cambia la plantilla                                                                                                                                |
| ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Edición manual o en [Poedit](https://poedit.com/features/)                                                         | Tú o un traductor                                   | Poedit señala formas plurales incorrectas y tokens que faltan                        | Comentarios y referencias del archivo                                                             | Ejecuta `msgmerge` y luego revisa las entradas fuzzy                                                                                                      |
| [Weblate](https://docs.weblate.org/en/latest/formats/gettext.html) u otro sistema de gestión de traducciones (TMS) | Traductores, con sugerencias automáticas opcionales | Compatibles; las entradas fuzzy se asignan a &quot;Needs editing&quot;               | Comentarios, referencias y las capturas de pantalla o el glosario del TMS                         | Su complemento de msgmerge o la sincronización con el repositorio actualiza los archivos PO                                                               |
| `msgpre` de GNU gettext                                                                                            | Un LLM local mediante ollama, como borrador         | Se generan como borrador para que un traductor las revise                            | Lo que incluya el archivo PO                                                                      | Vuelve a ejecutarlo sobre las entradas nuevas ([pretraducción de gettext 1.0](https://www.gnu.org/software/gettext/manual/html_node/Pretranslating.html)) |
| Un script que llama a una API de traducción automática o a un LLM                                                  | El modelo                                           | Tu script debe generar todas las entradas que exige la cabecera                      | Lo que envíe tu script                                                                            | Vuelve a ejecutarlo y valida la salida por tu cuenta                                                                                                      |
| [CLI de General Translation](/docs/cli/reference/formats/po-pot-files)                                             | El servicio de traducción de General Translation    | Todas las entradas del idioma de destino, con verificación de marcadores de posición | `msgctxt`, comentarios, referencias, opciones, además de tu glosario y tus prompts personalizados | Reutiliza las traducciones que no han cambiado y traduce las entradas nuevas o modificadas                                                                |

La pretraducción automática de Poedit es una función Pro; la edición, la memoria de traducción y las verificaciones están incluidas en la versión gratuita. (Consulta [General Translation vs. Weblate](/kb/general-translation-vs-weblate) para ver cómo se compara el flujo de ida y vuelta con un TMS).

Usa Poedit si traduces tú mismo uno o dos idiomas. Usa Weblate u otro [TMS](/kb/what-is-a-translation-management-system) cuando los catálogos estén a cargo de traductores profesionales que necesiten asignaciones y colas de revisión. Usa un script o `msgpre` solo si vas a encargarte tú de la validación. Usa General Translation cuando los archivos PO deban traducirse a todos los idiomas en cada compilación, con terminología compartida con el resto de tus productos.

## Traducir archivos PO con General Translation

La CLI de General Translation, `gt`, lee un archivo POT (o un archivo PO en el idioma de origen) y genera un archivo PO traducido para cada configuración regional. Agrega una entrada `pot` a `gt.config.json`. Esta configuración se comprobó con `gt` 2.26.11, y la propia resolución de rutas de la CLI convierte `po/messages.pot` en `po/de.po`, `po/pl.po` y `po/pt_BR.po`:

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["de", "pl", "pt_BR"],
  "customMapping": { "pt_BR": { "code": "pt-BR" } },
  "files": {
    "pot": {
      "include": ["po/messages.pot"],
      "transform": { "match": "^po/messages\\.pot$", "replace": "po/{locale}.po" },
      "transformationFormat": "PO"
    }
  }
}
```

* **`pot`** lee tanto archivos PO como POT (`POT` también funciona). La compatibilidad con PO y POT se incorporó en `gt` 2.14.22.
* **`"transformationFormat": "PO"`** es obligatorio. Sin él, la salida sigue siendo una plantilla sin traducir.
* **[`transform`](/docs/cli/reference/config#locale-placeholders)** define el nombre de cada archivo de salida; `{locale}` es la configuración regional tal como aparece en `locales`, y [`customMapping`](/docs/cli/reference/config#custom-mapping) asocia el `pt_BR` de gettext con el código estándar `pt-BR`.
* **Los layouts de Babel y Django** no usan `transform`: apunta `include` al catálogo en inglés con un marcador de posición `[locale]` en su carpeta, como `[locale]/LC_MESSAGES/*.po` dentro de `translations/`, y la CLI escribe el catálogo de cada idioma junto a él, como `pl/LC_MESSAGES/messages.po`.

A continuación, ejecuta la CLI entre la extracción y la compilación:

```bash
xgettext --language=Python --keyword=pgettext:1c,2 --add-comments=TRANSLATORS -o po/messages.pot app.py
msgconv --to-code=UTF-8 -o po/messages.pot po/messages.pot
npx gt translate
msgfmt --check -o locale/pl/LC_MESSAGES/messages.mo po/pl.po
```

Primero, configura las credenciales con [`npx gt login`](/docs/cli/reference/commands/login) y un ID del proyecto, o con una clave de API del proyecto en CI ([credenciales de la CLI](/docs/cli/guides/configuring#credentials)). Si tus archivos PO ya contienen traducciones revisadas, impórtalas con [`npx gt upload`](/docs/cli/reference/commands/upload) antes de ejecutar [`npx gt translate`](/docs/cli/reference/commands/translate) por primera vez.

Para cada entrada, General Translation traduce el texto de `msgid` y envía como contexto su `msgctxt`, los comentarios `#.`, las referencias `#:` y las opciones. Establece la cabecera `Language` del archivo resultante y, si el archivo tiene entradas plurales, escribe la regla `Plural-Forms` del idioma de destino y rellena todos los `msgstr[n]` que exige esa regla: tres para el polaco y seis para el árabe. En cada traducción plural se comprueba que no haya entradas de plural vacías ni falten en ninguna forma marcadores de posición como `%(num)d`, `%s` o `{name}`. Los comentarios, las referencias, las opciones y `msgctxt` se mantienen tal como están en la plantilla, y las entradas sin cambios reutilizan sus traducciones anteriores en la siguiente ejecución.

Como el origen es el `msgid`, esto funciona mejor cuando tu código usa el texto de origen como clave, que es el comportamiento predeterminado de gettext. Los revisores pueden [editar entradas en el panel de control](/docs/platform/dashboard/guides/reviewing-translations), y un glosario y prompts personalizados en un [grupo de contexto](/docs/platform/dashboard/guides/defining-context-for-translations) garantizan que un término como «Archive» se traduzca igual que en tu aplicación web y tu documentación. Si alguien edita un archivo PO localmente, sincronízalo con [`npx gt save-local`](/docs/cli/reference/commands/save-local) antes de la siguiente ejecución. Para configuraciones específicas de cada framework, consulta [General Translation frente al framework de traducción de Django](/kb/general-translation-vs-django-i18n), [General Translation frente a Babel y Flask-Babel](/kb/general-translation-vs-flask-babel) y [¿Cómo traduzco una aplicación Flask o FastAPI?](/kb/translate-python-app).

## Preguntas frecuentes

### ¿Cuál es la diferencia entre un archivo PO y un archivo POT?

Un archivo POT es la plantilla: todos los `msgstr` están vacíos y la cabecera no especifica ningún idioma. Un archivo PO es una copia para un idioma concreto, con una cabecera `Language`, la regla `Plural-Forms` correspondiente a ese idioma y las traducciones.

### ¿Sigo necesitando msgmerge si General Translation escribe los archivos PO?

No para los idiomas de destino cuando la plantilla POT es el origen. Cada ejecución genera el archivo PO de cada idioma a partir de la plantilla actual, por lo que las entradas eliminadas del código desaparecen y las nuevas llegan ya traducidas, sin entradas fuzzy que depurar. En los layouts de Babel y Django, el catálogo en inglés se sigue actualizando con `pybabel update` o `makemessages`.

### ¿Por qué mi aplicación sigue mostrando el texto en inglés después de traducir el archivo PO?

Normalmente se debe a una de estas tres causas: la entrada sigue marcada como `fuzzy`, el archivo `.mo` no se ha vuelto a compilar ni a desplegar, o el `msgid` del archivo PO ya no coincide con la cadena del código. `msgfmt --statistics` muestra cuántas entradas están traducidas y cuántas están marcadas como `fuzzy`.

## Conclusiones clave

* Traduce solo `msgstr`; `msgid`, `msgid_plural` y `msgctxt` son claves de búsqueda y se mantienen tal como se extrajeron.
* Escribe tantas posiciones `msgstr[n]` como indique el `nplurals` de la cabecera: una para japonés, dos para alemán, tres para polaco y seis para árabe.
* `msgfmt` descarta las entradas fuzzy y compila sin avisar una entrada de plural vacía que no imprime nada; `--check` solo lo detecta en entradas con una opción de formato, así que ejecuta `msgfmt --check --statistics` y, por separado, una comprobación de traducciones vacías en cada archivo PO.
* La CLI de General Translation traduce una plantilla POT a un archivo PO por cada configuración regional con `"pot"`, `"transformationFormat": "PO"` y un `transform`, y rellena todas las entradas de plural del idioma de destino.

## Related questions

- [General Translation frente a GNU gettext: ¿necesitas ambos?](https://generaltranslation.com/es/kb/general-translation-vs-gettext.mdx): Conserva GNU gettext para la extracción y el renderizado, y añade General Translation para generar y mantener los archivos PO. General Translation incorpora las notas para traductores y el contexto de los mensajes en la generación, aplica una terminología compartida y ofrece a los revisores un editor en el panel de control. Tus llamadas actuales a gettext y tus catálogos compilados siguen como están.
- [General Translation frente al framework de traducción de Django: ¿necesitas ambos?](https://generaltranslation.com/es/kb/general-translation-vs-django-i18n.mdx): Usa el framework de traducción de Django y General Translation de forma conjunta. Django se encarga de la extracción de mensajes, la selección del idioma y el renderizado. General Translation rellena sus archivos PO, incluidas las formas plurales, con la terminología de tu producto y la revisión en el panel de control. Tus plantillas y patrones de URL siguen igual, y las traducciones se actualizan a la par que tu código.
- [General Translation frente a Babel y Flask-Babel: ¿cuál deberías usar?](https://generaltranslation.com/es/kb/general-translation-vs-flask-babel.mdx): Babel da formato a fechas, números y monedas para cualquier configuración regional y gestiona catálogos de gettext, y Flask-Babel lo integra con Flask, pero ninguno de los dos traduce tus cadenas. La CLI de General Translation rellena los archivos PO de Babel para cada idioma, formas plurales incluidas, para que puedas seguir usando Flask-Babel y dejar de traducir catálogos a mano.
- [¿Cómo traduzco una aplicación de Flask o FastAPI?](https://generaltranslation.com/es/kb/translate-python-app.mdx): Marca las cadenas visibles para el usuario, elige la configuración regional de cada solicitud (normalmente a partir de Accept-Language) y carga las traducciones correspondientes a esa configuración regional. Las aplicaciones de Flask suelen usar Flask-Babel con catálogos de gettext, mientras que las de FastAPI integran Babel en un middleware. La CLI de General Translation rellena esos catálogos, y sus SDK experimentales gt-flask y gt-fastapi traducen cadenas t() inline sin necesidad de catálogos.
- [General Translation vs. Weblate: ¿cuál deberías usar?](https://generaltranslation.com/es/kb/general-translation-vs-weblate.mdx): Weblate es una plataforma de localización autoalojable y basada en Git, pensada para traducir los archivos de un repositorio junto con una comunidad o un equipo. General Translation integra la generación de traducciones en el desarrollo y los lanzamientos mediante JSX marcado y contexto compartido del producto. Conserva tus archivos de gettext o adopta la redacción inline para eliminar también el mantenimiento del catálogo de origen.

## Previous and next questions

- Previous question: [¿Cómo traduzco archivos de configuración regional YAML?](https://generaltranslation.com/es/kb/translate-yaml-files.mdx)
- Next question: [¿Cómo traduzco contenido en Payload CMS?](https://generaltranslation.com/es/kb/translate-payload-content.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/es/kb/sitemap.md) for every article.
