本文目前仅提供英文版本。翻译即将推出。
A French customer receives an Odoo invoice. The customer is set to French. The browser renders the invoice form in French. But the PDF lands in their inbox in English. Or some labels are translated, others are not. There is no error — just the wrong language. This is one of the most embarrassing customer-facing bugs and applies to Odoo 17.0/18.0/19.0.
Quick Fix
The report renderer needs the customer's language in context. Verify the report action picks it up:
# In the model, override _get_report_values to set lang from partner
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_invoice_send(self):
for order in self:
lang = order.partner_id.lang or 'en_US'
report = self.env.ref('account.account_invoices')
pdf = report.with_context(lang=lang)._render_qweb_pdf(
report.report_name, [order.invoice_ids.ids[0]]
)
# send pdf...
For most use cases, Odoo handles this automatically when the report is invoked through the standard mail/print flow. If your custom code triggers reports, always set the lang context explicitly.
Why This Happens
Odoo translations live in PO files inside each module's i18n/ directory. At runtime, Odoo loads them into ir.translation and resolves _("text") strings against the active language. The five common failures:
- No
langin context. The report renders in the user's language (admin English) instead of the recipient's (customer French). - PO files not installed. The language is enabled in
Settings > Languagesbut the PO files for the relevant modules were never imported. - Hardcoded English in the template.
<t t-esc="'Invoice'"/>is a literal string, untranslatable. The right form is<span>Invoice</span>ort-translate. - Field-level translations not loaded for the active language. Some fields (
nameonproduct.product,descriptiononsale.order.line) are translatable per record; the right translation may not exist. - Cache. The translation cache is per-process; recently-imported translations may not appear until restart.
Step-by-Step Diagnosis
1. Check the language is installed.
SELECT id, code, name, active FROM res_lang
WHERE code IN ('fr_FR', 'es_ES', 'de_DE');
active = true means installed. If false, install via Settings → Languages.
2. Check translations exist for the relevant modules.
SELECT lang, count(*)
FROM ir_translation
WHERE module = 'sale' GROUP BY lang;
fr_FR row should have thousands of entries. If much less, PO files were not imported.
3. Check the customer's lang.
SELECT id, name, lang FROM res_partner WHERE id = <pid>;
If lang is empty, Odoo falls back to the system default. Set the partner's language to fix.
4. Test report rendering with explicit lang.
report = env.ref('sale.action_report_saleorder')
pdf = report.with_context(lang='fr_FR')._render_qweb_pdf(
report.report_name, [14]
)
print(len(pdf[0])) # PDF bytes; should be non-zero
If this returns French content, the lang flow works — your earlier render was just missing the lang in context.
5. Check field-level translations.
SELECT res_id, lang, value
FROM ir_translation
WHERE name = 'product.template,name'
AND res_id = <product_id>;
If only en_US exists, the product name has no French translation.
Permanent Fix
Always set lang from the recipient on outbound communications:
class SaleOrder(models.Model):
_inherit = 'sale.order'
def action_send_quote(self):
self.ensure_one()
lang = self.partner_id.lang or self.env.user.lang
# The mail template handles lang automatically when partner_ids are set,
# but custom RPC flows must be explicit.
compose = self.env['mail.compose.message'].with_context(
default_model='sale.order',
default_res_id=self.id,
default_use_template=True,
default_template_id=self.env.ref('sale.email_template_edi_sale').id,
mail_post_autofollow=True,
force_email=True,
lang=lang,
).create({})
compose.send_mail()
For direct PDF generation, always:
pdf, _ext = report.with_context(lang=recipient_lang)._render_qweb_pdf(...)
For untranslated PO content, import:
sudo -u odoo /opt/odoo/odoo-bin -c odoo.conf \
-d <db> --i18n-import=/opt/odoo/addons/sale/i18n/fr.po \
--language=fr_FR --stop-after-init
Or programmatically:
env['base.language.import'].create({
'name': 'French',
'code': 'fr_FR',
'overwrite': True,
}).import_lang()
For hardcoded strings in templates, use proper translation markers:
<!-- WRONG — literal string, untranslatable -->
<h2>Invoice</h2>
<!-- RIGHT — translatable -->
<h2><span>Invoice</span></h2>
<!-- Or in Python -->
title = _("Invoice")
QWeb auto-translates content within tags. Inline literals via t-esc='"text"' are NOT translatable.
For translatable field values, ensure each record has translations populated:
product = env['product.template'].browse(45)
product.with_context(lang='fr_FR').name = 'Produit prioritaire'
Or import via PO files for the model.
How to Prevent It
- Always set
langin mail flows. Make this part of the team's checklist for any outbound email or PDF send. Audit existing custom send-mail code regularly. - CI test that reports render in non-default language. A test that generates a PDF for a French customer and asserts French strings appear catches lang-context regressions.
- Import PO files in CI. When deploying, run
--i18n-importfor all enabled languages. Without this, your production stays at English even if the source has translations. - No literal English in templates. A regex check in CI for
t-esc='"..."'patterns catches literal-string mistakes. - Lang fallback chain. When
partner_id.langis empty, fall back tocompany.partner_id.lang, then the system lang. Always have an explicit fallback so you do not silently render in the user's language. - Translatable fields convention. Document which model fields are translatable. New custom fields default to non-translatable; flip explicitly when needed.
Related Errors
- PDF report renders blank page — adjacent rendering bug.
- wkhtmltopdf protocol error — sibling toolchain failure.
- QWeb template not found at runtime — pre-render lookup.
- Page break not working — layout-level report bug.
Frequently Asked Questions
Why does my email render in the right language but the PDF attachment in English?
Different code paths. Email body uses the mail template's language resolution, which respects the partner. The PDF attachment is generated separately and may not pick up the lang context. Always pass lang when generating the PDF for an email send.
How do I add translations for a custom field?
Mark the field translatable in the Python definition:
description = fields.Text(translate=True)
Then export the PO file (Settings → Translations → Export), have it translated, import. Each record can also have language-specific translations stored directly via with_context(lang='...').write().
My non-English language is installed but reports still print English. What's wrong?
Two common causes. First, the partner does not have lang set — Odoo falls back to user lang. Second, the report action does not pass through partner.lang to its render context. Override _get_report_values to inject lang if needed.
Can I render a single report in two languages?
Yes, but it requires two render calls — there is no single render that produces a bilingual document. For dual-language reports (common in EU shipping documents), build a QWeb template that conditionally shows both languages by reading both translations and rendering side by side.
How do I export translations for review?
Settings → Translations → Export Translations. Choose the language and module, get a PO file. Send it to a translator. Re-import via Settings → Translations → Import Translation. The PO format is industry-standard; any translation tool handles it.
Why are some report strings translated and others not?
Three causes. First, the string was not marked translatable in the source — wrap with _("..."). Second, the PO file does not have a translation for that key — add one. Third, the cache holds a stale translation — restart Odoo. Verify with the SQL query in step 2 of the diagnosis.
Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.
作者
ECOSIRE TeamTechnical Writing
The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.
相关文章
如何将自定义按钮添加到 Odoo 表单视图 (2026)
将自定义操作按钮添加到 Odoo 19 表单视图:Python 操作方法、视图继承、条件可见性、确认对话框。经过生产测试。
如何在没有 Studio 的情况下在 Odoo 中添加自定义字段 (2026)
通过 Odoo 19 中的自定义模块添加自定义字段:模型继承、视图扩展、计算字段、存储/非存储决策。代码优先,版本控制。
如何使用外部布局在 Odoo 中添加自定义报告
使用 web.external_layout 在 Odoo 19 中构建品牌 PDF 报告:QWeb 模板、paperformat、操作绑定。带有印刷徽标+页脚覆盖。