翻译模块

本节将介绍如何为您的模块提供翻译功能。

注解

如果您想为Odoo本身的翻译做出贡献,请参考 Odoo Wiki页面

导出可翻译术语

你的模块中有许多术语是“隐式可翻译的”,因此,即使你没有进行任何特定的翻译工作,你也可以导出你的模块可翻译的术语,并找到需要处理的内容。

通过登录后台界面并打开 设置 ‣ 翻译 ‣ 导入/导出 ‣ 导出翻译,可以通过管理界面执行翻译导出操作。

  • 将语言保留为默认值(新语言/空模板)

  • 选择 PO 文件 格式

  • 选择您的模块

  • 点击 导出 并下载文件

../../_images/po-export.png

这将给你一个名为 yourmodule.pot 的文件,应该移动到 yourmodule/i18n/ 目录中。该文件是一个 PO 模板,简单地列出可翻译的字符串,可以从中创建实际的翻译文件(PO 文件)。可以使用 msginit 创建 PO 文件,也可以使用专用的翻译工具如 POEdit,或者简单地将模板复制到一个名为 language.po 的新文件中。翻译文件应该放在 yourmodule/i18n/ 目录中,与 yourmodule.pot 并列,并且在对应的语言被安装时,Odoo 会自动加载它们(通过 设置 ‣ 翻译 ‣ 语言

注解

在安装或更新模块时,所有已加载语言的翻译也会被安装或更新。

隐式导出

Odoo会自动从“data”类型的内容中导出可翻译的字符串:

  • 在非 QWeb 视图中,所有文本节点都会被导出,以及 stringhelpsumconfirmplaceholder 属性的内容

  • QWeb模板(包括服务器端和客户端),所有文本节点都会被导出,除非在 t-translation="off" 块内, titlealtlabelplaceholder 属性的内容也会被导出

  • 对于 Field,除非它们的模型被标记为 _translate = False

    • 它们的 stringhelp 属性被导出

    • 如果 selection 存在且为列表(或元组),则导出

    • 如果它们的 translate 属性设置为 True,则会导出它们所有现有的值(在所有记录中)

  • help/error messages of _constraints and _sql_constraints are exported

显式导出

当涉及到Python代码或JavaScript代码中的更”命令式”的情况时,Odoo无法自动导出可翻译的术语,因此必须明确标记以进行导出。这可以通过将文字字符串包装在函数调用中来完成。

In Python, the wrapping function is odoo.api.Environment._() and odoo.tools.translate._():

title = self.env._("Bank Accounts")

# old API for backward-compatibility
from odoo.tools import _
title = _("Bank Accounts")

在JavaScript中,包装函数通常是 odoo.web._t()

title = _t("Bank Accounts");

警告

只有字面字符串可以被标记为导出项,不能是表达式或变量。对于需要格式化的字符串,需要标记格式化字符串而不是格式化后的字符串。

The lazy version of _ and _t is the odoo.tools.translate.LazyTranslate factory in python and odoo.web._lt() in javascript. The translation lookup is executed only at rendering and can be used to declare translatable properties in class methods of global variables.

from odoo.tools import LazyTranslate
_lt = LazyTranslate(__name__)
LAZY_TEXT = _lt("some text")

注解

模块的翻译默认情况下 不会 被暴露给前端,因此无法从JavaScript中访问。为了实现这一点,模块名称必须以 website 为前缀(就像 website_salewebsite_event 等一样),或者通过在 ir.http 模型中实现 _get_translation_frontend_modules_name() 进行显式注册。

这可能看起来像下面这样:

from odoo import models

class IrHttp(models.AbstractModel):
    _inherit = 'ir.http'

    @classmethod
    def _get_translation_frontend_modules_name(cls):
        modules = super()._get_translation_frontend_modules_name()
        return modules + ['your_module']

上下文

To translate, the translation function needs to know the language and the module name. When using Environment._ the language is known and you may pass the module name as a parameter, otherwise it’s extracted from the caller.

In case of odoo.tools.translate._, the language and the module are extracted from the context. For this, we inspect the caller’s local variables. The drawback of this method is that it is error-prone: we try to find the context variable or self.env, however these may not exist if you use translations outside of model methods; i.e. it does not work inside regular functions or python comprehensions.

Lazy translations are bound to the module during their creation and the language is resolved when evaluating using str(). Note that you can also pass a lazy translation to Envionrment._ to translate it without any magic language resolution.

变量

不要 提取可能有效,但它不会正确翻译文本:

_("Scheduled meeting with %s" % invitee.name)

**请**将动态变量设置为翻译查找的参数 (如果翻译中缺少占位符,将会回退到源文本):

_("Scheduled meeting with %s", invitee.name)

**不要**将您的翻译分成多个块或多行:

# bad, trailing spaces, blocks out of context
_("You have ") + len(invoices) + _(" invoices waiting")
_t("You have ") + invoices.length + _t(" invoices waiting");

# bad, multiple small translations
_("Reference of the document that generated ") + \
_("this sales order request.")

**请**保持在一个块中,为翻译者提供完整的上下文:

# good, allow to change position of the number in the translation
_("You have %s invoices wainting") % len(invoices)
_.str.sprintf(_t("You have %s invoices wainting"), invoices.length);

# good, full sentence is understandable
_("Reference of the document that generated " + \
  "this sales order request.")

复数

**不要**按照英语的方式复数化术语:

msg = _("You have %(count)s invoice", count=invoice_count)
if invoice_count > 1:
  msg += _("s")

**请**记住每种语言都有不同的复数形式:

if invoice_count > 1:
  msg = _("You have %(count)s invoices", count=invoice_count)
else:
  msg = _("You have one invoice")

读取时间 vs 运行时间

**不要**在服务器启动时调用翻译查找:

ERROR_MESSAGE = {
  # bad, evaluated at server launch with no user language
  'access_error': _('Access Error'),
  'missing_error': _('Missing Record'),
}

class Record(models.Model):

  def _raise_error(self, code):
    raise UserError(ERROR_MESSAGE[code])

**不要**在读取javascript文件时调用翻译查找:

# bad, js _t is evaluated too early
var core = require('web.core');
var _t = core._t;
var map_title = {
    access_error: _t('Access Error'),
    missing_error: _t('Missing Record'),
};

**请**使用延迟翻译查找方法:

ERROR_MESSAGE = {
  'access_error': _lt('Access Error'),
  'missing_error': _lt('Missing Record'),
}

class Record(models.Model):

  def _raise_error(self, code):
    # translation lookup executed at error rendering
    raise UserError(ERROR_MESSAGE[code])

或者 动态地 评估可翻译的内容:

# good, evaluated at run time
def _get_error_message(self):
  return {
    access_error: _('Access Error'),
    missing_error: _('Missing Record'),
  }

JS 文件 读取 时进行翻译查找的情况下,使用 _lt 而不是 _t 来翻译术语在其 使用 时:

# good, js _lt is evaluated lazily
var core = require('web.core');
var _lt = core._lt;
var map_title = {
    access_error: _lt('Access Error'),
    missing_error: _lt('Missing Record'),
};