第十七章 国际化
我们在第一部分简单介绍了系统中的国际化问题, 本章我们将详细介绍odoo中的翻译工作机制和原理.
翻译问题
PO文件详解
我们知道po文件是承载翻译的主要文件,那么这个文件的结构是怎样的呢?
po文件的主要内容格式有以下三种:
注释: 类似代码中的注释,可以编写任意文本内容,不需要被翻译。注释行以 # 开头,具体的注释类型由紧随井号的字符决定,即可有如下几种:(该表格稍作了解即可,实际使用时很少区分)
| 起始符号 | 符号解释 | 包含内容 | | ---- | ---- | ---- | |"# "|紧跟空格|根据提取指令生成,以及翻译者所编写的注释| |"#."|紧跟句点|额外注释,从源代码注释生成 | |"#:"|紧跟冒号|表明待翻译语句的出处,一般标记源代码文件及行数| |"#,"|紧跟逗号|由编译器生成的格式注释| |"#|"|紧跟上一句未翻译文本,较少见|
翻译语句块: 由成对的 msgid 与 msgstr 标记组成
- msgid 后跟一或多个由双引号包围的待翻译字符串,直到出现 msgstr.
- msgstr 后跟一个或多个由双引号包围的翻译字符串
- 如果该文段尚未翻译,msgstr 后跟一个空字符串
额外信息: 文件开头第一个 msgstr 后的双引号包围字符串
- 这个 msgstr 所对应的 msgid 没有内容
- 一般描述了翻译人,时间日期等信息
- 不需要被翻译,可以当作注释对待
除了 #: 以外的所有注释、额外信息都是可选的,即使文件中没有任何这些信息,文件同样是符合规范的
一个典型的翻译文件解析
我们来看一个典型的po文件内容:
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale
#
# Translators:
# Martin Trigaux, 2021
# Ryan Zhang <so_happy_boy@hotmail.com>, 2021
# ljsrzc007 <ljsrzc007@gmail.com>, 2021
# 黎伟杰 <674416404@qq.com>, 2021
# kfx2007@163.com, 2021
# xu aaron, 2021
# Benson <Benson.Dr@Gmail.com>, 2022
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-16 13:19+0000\n"
"PO-Revision-Date: 2021-09-14 12:26+0000\n"
"Last-Translator: Benson <Benson.Dr@Gmail.com>, 2022\n"
"Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: sale
#. openerp-web
#: code:addons/sale/static/src/js/sale.js:0
#, python-format
msgid " / Month"
msgstr ""
#. module: sale
#: model:ir.model.fields,field_description:sale.field_sale_payment_acquirer_onboarding_wizard___data_fetched
msgid " Data Fetched"
msgstr "数据已获取"
这是截取自15.0版本的sale模块中的部分翻译内容, 文件开头的注释标注了本翻译文件的主要目的和翻译人员列表.
文件开头的第一个 msgid, msgstr对 描述了文件的额外信息,包括创建时间,文件格式, 作者信息等.
在第一段最后一行 Plural-Forms: nplurals=1; plural=0;\n 之后开始,是两个独立的 翻译单元。翻译单元由空行分隔,包含 任意数量的注释和一个 msgid, msgstr对。在翻译单元的注释中,一定会以 文件名:行数 的格式描述文本的引用位置。其他可能的信息还包括 语言格式,翻译者注释等。
#. module: sale
表明翻译来自sale模块, model:ir.model.fields,field_description:sale.field_sale_payment_acquirer_onboarding_wizard___data_fetched
表明此翻译是字段的描述, 该字段是模型sale.payment.acquirer.onboarding.wizard中的_data_fetched字段.
翻译举例
下面列举odoo中常见的一些翻译示例:
字段翻译
#. module: sale
#: model:ir.model.fields,field_description:sale.field_sale_payment_acquirer_onboarding_wizard___data_fetched
msgid " Data Fetched"
msgstr "数据已获取"
对于Selection类型的字段来说,翻译注释要写明selection的值:
#. module: mjj_hat
#: model:ir.model.fields.selection,name:mjj_hat.selection__mjj_hat__back_lock_ok__yes
#: model:ir.model.fields.selection,name:mjj_hat.selection__mjj_sale_order_line__back_lock_ok__yes
#: model:ir.model.fields.selection,name:mjj_hat.selection__sale_order_wizard_line__back_lock_ok__yes
msgid "是"
msgstr "Sí"
#. module: mjj_hat
#: model:ir.model.fields.selection,name:mjj_hat.selection__mjj_hat__back_lock_ok__no
#: model:ir.model.fields.selection,name:mjj_hat.selection__mjj_sale_order_line__back_lock_ok__no
#: model:ir.model.fields.selection,name:mjj_hat.selection__sale_order_wizard_line__back_lock_ok__no
msgid "否"
msgstr "No"
在我们的模块mjjhat中有个字段backlockok, 其类型为selection, 同时支持yes/no的值. 我们在编写翻译时,就需要按照**selection_模型名称(mjj_hat)字段名(back_lock_ok)值**的方式进行注释,否则我们的翻译不会生效.
报表动作翻译
#. module: sale
#: model:ir.actions.report,print_report_name:sale.action_report_pro_forma_invoice
msgid "'PRO-FORMA - %s' % (object.name)"
msgstr "'形式发票-%s' % (object.name)"
产品数据翻译
#. module: sale
#: model:product.product,description_sale:sale.product_product_4e
#: model:product.product,description_sale:sale.product_product_4f
msgid "160x80cm, with large legs."
msgstr "160X80cm,大桌腿"
视图翻译
#. module: sale
#: model_terms:ir.ui.view,arch_db:sale.crm_lead_partner_kanban_view
msgid ""
"<i class=\"fa fa-fw fa-usd\" role=\"img\" aria-label=\"Sale orders\" "
"title=\"Sales orders\"/>"
msgstr "<i class=\"fa fa-fw fa-usd\" role=\"img\" aria-label=\"销售订单\" title=\"销售订单\"/>"
翻译的覆盖
翻译也是可以通过代码的方式进行覆盖的, 方法非常简单即在你自己开发的模块下的i18n模块中新建同名的语言翻译文件即可.
然后在启动odoo的时候使用 --i18n-overwrite -u new_Inherited_module
参数对模块进行更新.
模块信息翻译
模块信息需要在base模块中注册,因此#.节点应该写base。model是ir.module.module, 一个典型的模块名字翻译例子如下:
#. module: base
#: model:ir.module.module,shortdesc:base.module_mommy_delivery_sf
msgid "SF Express"
msgstr "顺丰速运"
时区问题
国际化所面临的另外一个问题就是时区问题, odoo在数据库内存储的时间是标准时间(UTC),用户在使用过程中, 系统会将标准时间转化为用户所在时区的本地时间.
但是时区所面临的一个问题是在编程上或者系统对外的接口中, 使用的都是标准时间, 当我们需要传递给另外的系统使用时,可能需要将时间转换为第三方系统所使用的时区时间.
后台接口中的时间格式化
当其他第三方系统与我们进行对接时, 如果其使用的不是UTC时间, 那么我们需要将我们的从数据库中拿到的时间转化为对方系统所使用的时区时间.
这里笔者使用了自己写的第三方库Autils来完成这个工作.
from autils.datetime import DateTime
DateTime(datetime.now()).to_local_time_str()
to_local_time_str()方法会将传入的时间转化为本地时间(默认UTC-8)。