addons-cm/docs/TRANSLATIONS.md
GitHub Copilot 5de7573fba [IMP] website_sale_aplicoop: accessibility and responsive polish — phase 8
Quantity control layout: changed from wrapping flex to deliberate two-row
grid so the add-to-cart button sits full-width underneath the stepper,
avoiding accidental line breaks and giving the primary action more pulsing
area. Also removed the /Kg suffix's redundant font rules.

Order card delivery row: reordered to badge-then-date (visually centred),
removed the "Delivery" label, and swapped bg-primary to text-bg-primary for
proper contrast on the home-delivery badge (3.13:1 minimum).

Tooltip translations: moved hardcoded static tooltips from data-bs-title
(untranslatable) to title (QWeb-translatable). Handled the edge case of
"Save Cart" and "Back to Cart" which had translations but no model_terms
reference in the POT, causing the merge to discard them — added the view
reference so translations now apply.

Load-from-history page: added accessibility: a visible status message
("Loading your order…"), a <noscript> fallback with link to the group
order, lang and viewport meta tags, and localised strings for all three
languages. Gave the page a minimal inline style block since it does not
inherit the token system (no website.layout).

Translations: 11 new entries (es/eu/ca) for new/reworded UI strings, plus
two code catalogue entries (products found, Close) that needed the
#. odoo-python comment for _() resolution. Documented the silent-failure
pattern in docs/TRANSLATIONS.md: the POT merge, untranslatable attributes,
and missing code comments.

Tests: 246 passing. Pre-commit: clean.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-16 11:34:38 +02:00

12 KiB

Sistema de Traducciones - Guía Completa

Estado Actual

El sistema de traducciones está funcionando correctamente

Todos los addons custom incluyen traducciones para:

  • Español (es) - Obligatorio
  • Euskera (eu) - Obligatorio
  • Catalán (ca), Gallego (gl), Portugués (pt), Francés (fr), Italiano (it) - Opcional

Estructura de Archivos

Cada addon debe tener la siguiente estructura:

addon_name/
├── i18n/
│   ├── es.po              # Español (obligatorio)
│   ├── eu.po              # Euskera (obligatorio)
│   ├── ca.po              # Catalán (opcional)
│   ├── gl.po              # Gallego (opcional)
│   ├── pt.po              # Portugués (opcional)
│   ├── fr.po              # Francés (opcional)
│   ├── it.po              # Italiano (opcional)
│   └── addon_name.pot     # Template (generado automáticamente)

Reglas Importantes

NO Hacer

NUNCA usar _() en definiciones de campos a nivel de módulo:

# ❌ INCORRECTO - Causa warnings
from odoo import _

class MyModel(models.Model):
    _name = 'my.model'

    name = fields.Char(string=_("Name"))  # ❌ MAL
    description = fields.Text(string=_("Description"))  # ❌ MAL

Por qué está mal: _() se ejecuta en tiempo de importación del módulo, antes de que el sistema de traducciones esté inicializado, causando warnings.

Hacer

Usar strings literales en definiciones de campos:

# ✅ CORRECTO
from odoo import models, fields

class MyModel(models.Model):
    _name = 'my.model'

    name = fields.Char(string="Name")  # ✅ BIEN
    description = fields.Text(string="Description")  # ✅ BIEN

Usar _() solo en métodos y código ejecutable:

# ✅ CORRECTO
from odoo import _, models

class MyModel(models.Model):
    _name = 'my.model'

    def action_confirm(self):
        message = _("Order confirmed successfully")  # ✅ BIEN
        return {
            'type': 'ir.actions.client',
            'tag': 'display_notification',
            'params': {
                'title': _('Success'),  # ✅ BIEN
                'message': message,
                'type': 'success',
            }
        }

    def _compute_display_name(self):
        for record in self:
            record.display_name = _("Order %s") % record.name  # ✅ BIEN

Cómo Generar/Actualizar Traducciones

1. Exportar Términos Traducibles

NO HACER:

# Exportar términos del addon
docker-compose exec odoo odoo \
    --addons-path=/mnt/extra-addons \
    --i18n-export=/tmp/addon_name.pot \
    --modules=addon_name \
    --db=odoo \
    --stop-after-init
De alguna forma, exporta todas las cadenas de Odoo.
PEDIR AL USUARIO QUE GENERE EL POT DESDE LA UI DE ODOO

# Copiar el archivo generado
docker-compose cp odoo:/tmp/addon_name.pot ./addon_name/i18n/

El .pot no es opcional: Odoo fusiona los .po contra él

El .pot es un artefacto generado y está en .gitignore (*.pot), pero no es prescindible: al importar, Odoo fusiona cada .po contra el .pot del módulo antes de leerlo.

# odoo/tools/translate.py, PoFileReader.__init__
if pot_path:
    self.pofile.merge(polib.pofile(pot_path))

merge() de polib sustituye las ocurrencias de cada entrada por las del POT, y marca como obsoletas las entradas que el POT no tiene (el lector se salta las obsoletas). Consecuencias prácticas:

  • Añadir una ocurrencia model_terms:ir.ui.view,... solo al .po no sirve de nada: el merge la descarta y la traducción nunca llega a la vista.
  • Una cadena nueva que no esté en el POT se ignora entera, por muy traducida que esté en los tres idiomas.
  • Por eso, tras mover una cadena a un atributo traducible o añadir una nueva, hay que regenerar el POT antes de que la traducción surta efecto.

⚠️ Al exportar contra una BD con datos demo, el POT se lleva los nombres de los registros demo (productos, impuestos, nombres de pedidos). Genera el POT contra una BD limpia.

2. Actualizar Archivos .po Existentes

no usar msmerge, corrompe el po. Usa polib.

Al crear entradas con polib hay dos detalles que fallan en silencio:

entry = polib.POEntry(
    msgid="products found",
    msgstr="produktu aurkitu dira",
    # 1. Sin `module:` el lector revienta; sin `odoo-python` la cadena se lee
    #    del .po y se descarta acto seguido (ver Troubleshooting).
    comment="module: website_sale_aplicoop\nodoo-python",
    # 2. El número de línea es obligatorio en ocurrencias `code:`.
    occurrences=[("code:addons/website_sale_aplicoop/models/js_translations.py", "0")],
)

3. Traducir Términos Nuevos

Editar los archivos .po y completar las traducciones:

#: models/my_model.py:25
msgid "Order confirmed successfully"
msgstr "Pedido confirmado correctamente"  # Español

#: models/my_model.py:30
msgid "Success"
msgstr "Éxito"

4. Cargar Traducciones en Odoo

# Actualizar addon con nuevas traducciones
docker-compose exec odoo odoo -d odoo -u addon_name --stop-after-init

# O reiniciar Odoo para que cargue los cambios
docker-compose restart odoo

Formato de Archivos .po

Cabecera Requerida

# Translation of Odoo Server.
# This file contains the translation of the following modules:
#	* addon_name
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-12 10:00+0000\n"
"PO-Revision-Date: 2026-02-12 10:00+0000\n"
"Last-Translator: Your Name <your.email@example.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

Campos Traducibles

Odoo automáticamente detecta y exporta:

  1. String en definiciones de campos: fields.Char(string="Name")
  2. Help text: fields.Char(help="Enter the product name")
  3. Selection options: fields.Selection([('draft', 'Draft'), ('done', 'Done')])
  4. Texto en vistas XML: <label string="Customer Name"/>
  5. Botones en vistas: <button string="Confirm"/>
  6. Mensajes en código: _("Message text")
  7. Excepciones: raise ValidationError(_("Invalid value"))

Verificación

Comprobar que las Traducciones Funcionan

  1. Activar el idioma en Odoo:

    • Settings > Translations > Load a Translation
    • Seleccionar el idioma (ej: Español)
  2. Cambiar idioma de usuario:

    • Settings > Users > [Usuario]
    • Language: Español
  3. Verificar en la interfaz:

    • Abrir el módulo
    • Verificar que los textos aparecen traducidos

Logs de Carga

Al actualizar el módulo, verificar en logs:

docker-compose logs odoo | grep "loading translation"

Debe mostrar:

odoo.modules.loading: loading translation file for language 'es': addon_name/i18n/es.po
odoo.modules.loading: loading translation file for language 'eu': addon_name/i18n/eu.po

Addons con Traducciones Completas

Addons Custom

Addon es eu Otros
account_invoice_triple_discount_readonly -
product_price_category_supplier -
product_sale_price_from_pricelist - -
website_sale_aplicoop ca, gl, pt, fr, it

Addons OCA

Los addons OCA ya incluyen traducciones en múltiples idiomas.

Troubleshooting

Warning: "_() called at import time"

Síntoma:

WARNING: _() called at import time at module.path:line

Causa: _() usado en definición de campo a nivel de módulo

Solución: Eliminar _() de la definición del campo:

# Cambiar:
name = fields.Char(string=_("Name"))
# Por:
name = fields.Char(string="Name")

Traducciones No Aparecen

Posibles causas:

  1. Archivo .po mal formado: Verificar encoding UTF-8
  2. Módulo no actualizado: docker-compose exec odoo odoo -d odoo -u addon_name --stop-after-init
  3. Idioma no activado: Settings > Translations > Load Translation
  4. Usuario con idioma incorrecto: Verificar preferencias de usuario
  5. Cache: Reiniciar Odoo: docker-compose restart odoo

La cadena SÍ está traducida en el .po y sigue saliendo en inglés

Los tres casos siguientes fallan sin dar ningún error, ni en el log ni al importar.

a) El atributo no es traducible. QWeb solo traduce los de esta lista (odoo/tools/translate.py, TRANSLATED_ATTRS), más sus variantes t-attf-:

string  help  placeholder  alt  title  label  data-tooltip  confirm
aria-label  aria-keyshortcuts  aria-placeholder  aria-roledescription  aria-valuetext

data-bs-title no está: un tooltip escrito ahí nunca se traduce. Usa title (Bootstrap lo respeta igual) o pasa el texto desde el endpoint i18n. Tampoco están aria-description ni data-bs-* en general.

b) Falta la referencia a la vista en el POT. Para que una traducción se aplique a una vista, su entrada necesita una ocurrencia model_terms:ir.ui.view,arch_db:modulo.xmlid en el POT (ver el apartado del merge más arriba). Síntoma típico: dos botones contiguos, uno traducido y el otro no, con las dos cadenas presentes en el .po. Se comprueba así:

grep -B 4 '^msgid "Save Cart"$' addon/i18n/addon.pot   # ¿tiene model_terms?

c) Falta el comentario #. odoo-python. Las cadenas de _() se leen del .po directamente (no de la BD), y CodeTranslations._load_python_translations filtra por ese comentario:

def filter_func(row):
    return row.get('value') and PYTHON_TRANSLATION_COMMENT in row['comments']

Sin él la entrada se lee y se descarta. La entrada tiene que quedar así:

#. module: website_sale_aplicoop
#. odoo-python
#: code:addons/website_sale_aplicoop/models/js_translations.py:0
msgid "products found"
msgstr "produktu aurkitu dira"

Y ojo con el :0 final: el lector hace int(line_number) sobre las ocurrencias code:, así que una sin número de línea lanza ValueError y tumba la carga del catálogo entero, no solo la de esa entrada.

Estas cadenas se cachean por proceso, así que después de tocarlas hay que reiniciar Odoo (docker-compose restart odoo), no basta con -u.

Caracteres Especiales

Usar escape correcto en .po:

# Comillas
msgstr "Haga clic en \"Confirmar\""

# Saltos de línea
msgstr "Primera línea\n"
"Segunda línea"

# Caracteres especiales (á, é, í, ó, ú, ñ, ç)
# Usar directamente, el archivo debe ser UTF-8
msgstr "Configuración"

Herramientas Útiles

Editores de .po

  • Poedit: https://poedit.net/ (GUI, recomendado)
  • VS Code: Con extensión "gettext" para syntax highlighting
  • Lokalize: Editor KDE para traducciones

Comandos Útiles

# Verificar formato de archivo .po
msgfmt -c -v -o /dev/null addon_name/i18n/es.po

# Estadísticas de traducción
msgfmt --statistics addon_name/i18n/es.po

# Extraer solo términos sin traducir
msgattrib --untranslated addon_name/i18n/es.po

Mejores Prácticas

  1. Mantener es.po y eu.po siempre actualizados
  2. Usar términos consistentes en todo el proyecto
  3. Incluir contexto en comentarios cuando sea necesario
  4. Verificar traducciones antes de commit
  5. Nunca usar _() en definiciones de campos
  6. Usar encoding UTF-8 en todos los archivos .po
  7. Generar .pot después de cambios importantes
  8. Documentar términos técnicos específicos del dominio

Referencias


Última Actualización: 2026-02-12