翻译

Frappe 内置了翻译系统,可以从代码中捕获可翻译的字符串,
并将其提取到不同语言的 CSV 文件中。

工作原理

翻译分为三个步骤。

1. 提取可翻译的字符串

许多字符串,如字段 label 和 description,可以很容易地从
DocType 的 JSON 文件中解析出来,它们会被自动标记为可翻译,无需任何
显式提示。

其他字符串则从源代码文件中提取,例如 .json、.js、
.py。只有当作者在 _() 或 __() 方法中
显式标记时,这些字符串才会被提取。

例如:

message = _("You don't have permissions to access this file")

2. 翻译提取出的字符串

一旦我们有了想要翻译的字符串列表,它们会按语言被放入 CSV
文件中。翻译主要由 Frappe 和 ERPNext 的各类用户在翻译门户上贡献。

提交的翻译经过验证后,每周由机器人转换为 Pull Request
并合并到核心代码中。

3. 在应用中翻译字符串

翻译本质上就是由原始字符串及其在某种语言中的译文组成的键值对。
它可能看起来像这样:

{
 "1 hour ago": "एक घंटे पहले",
 "1 minute ago": "1 मिनट पहले",
 "1 month": "1 महीना",
 "1 week": "1 सप्ताह",
 "1 year": "1 साल",
 "3 months": "3 महीने",
 "6 months": "6 महीने",
 "About": "के बारे में",
 "About Us Settings": "हमारे बारे में सेटिंग्स",
 "About Us Team Member": "हमारे बारे में टीम के सदस्य",
 "Academic Term": "शैक्षणिक शब्द",
 "Academic Year": "शैक्षणिक वर्ष",
 "Academics User": "अकादमिक उपयोगकर्ता",
 ...
}

当代码中将字符串写为 __('1 hour ago') 时,__ 方法
会查找字典,看该字符串是否存在翻译。如果存在,
就会返回翻译后的字符串并显示出来。

这是一个 JavaScript 文件中翻译如何工作的示例,但同样的
机制在 Python 中也适用。

编写有效可翻译字符串的技巧

为了让翻译解析器能够正确地从代码中提取字符串,
需要遵循一些规则。

1. 字面量字符串

要翻译的字符串必须始终是字面量字符串,不能是变量或
表达式。

Python 示例

# This will work
message = _('Document submitted successfully')
frappe.msgprint(message)

# This will also work
frappe.msgprint(_('Document submitted successfully'))

# This won't work
message = 'Document submitted successfully'
frappe.msgprint(_(message))

JavaScript 示例

// This will work
message = __('Document submitted successfully')
frappe.msgprint(message)

// This won't work
message = 'Document submitted successfully'
frappe.msgprint(__(message))

2. 变量

如果字符串中包含变量,必须使用位置格式化符
{0},其他类型的格式化符都不起作用。

Python 示例

# This is fine
_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)

# These are not
_('Welcome %s, get started with <a href="https://erpnext.yuannext.com">ERPNext</a> in just a few clicks.' % full_name)

_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})

# This one uses the positional formatter,
# but won't work because the string is formatted before it is passed to _()
_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))

JavaScript 示例

// This is fine
__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name])

// This is not
__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`)

3. 代码块

不要将字符串拆分成多个独立的字符串块再进行拼接。
不要写多行字符串。始终将字符串写在一行中,
即使字符串非常长。

Python 示例

# This is fine
_('You have {0} subscribers in your mailing list.').format(len(subscribers))

# Don't split strings
_('You have ') + len(subscribers) + _(' subscribers in your mailing list.')

# Don't write multiline strings
_('You have {0} subscribers \
 in your mailing list').format(len(subscribers))

JavaScript 示例

// This is fine
__('You have {0} subscribers in your mailing list.', [subscribers.length])

// Don't split strings
__('You have ') + subscribers.length + __(' subscribers in your mailing list.')

// Don't write multiline strings
__('You have {0} subscribers' +
 'in your mailing list', [subscribers.length])

4. 复数

不要试图用逻辑来处理单词的复数形式。每种语言都有不同的复数
形式。

# Don't do this
msg = _("You have {0} pending invoice").format(invoice_count)
if invoice_count > 1:
 msg += _("s")

# Write separate strings
# Every language has different plural forms
if invoice_count > 1:
 msg = _("You have {0} pending invoices").format(invoice_count)
else:
 msg = _("You have {0} pending invoice").format(invoice_count)

5. 不要有尾部空格

不要在句子的开头或结尾添加空格。通过翻译引擎处理时,其他语言的尾部空格会被去除。

如果需要在文本周围添加空格,可以在翻译语法之外进行。

# Don't do this
msg = _(" You have {0} pending invoice ")

# Do this
msg = ' ' + _("You have {0} pending invoices") + ' '

6. 为字符串添加上下文

一个可翻译的字符串在不同上下文中可能有不同的含义。
例如,字符串 “Change” 可以表示 “使不同” 或 “硬币”。
为了解决这个问题,可以使用 context 变量来为
字符串设置上下文,这样在翻译工具中就可以根据不同
语言进行不同的翻译。

JavaScript 中


__("Change", null, "Coins")

// Here "Coins" is the context for text "Change"

Python 中


_("Change", context="Switch")

# Here "Switch" is the context for text "Change"

添加新语言

要添加新语言,请按照以下步骤操作:

第 1 步:导出到文件

$ bench --site sitename get-untranslated [lang] [path-to-file]

第 2 步:翻译

创建另一个包含更新翻译的文件(顺序与源文件
相同)。为此,你可以使用 Google 翻译或
Bing 翻译。

第 3 步:导入你的翻译

$ bench update-translations [lang] [source-path] [translated-path]

会在每个应用的 translations 文件夹中创建一个名为 [lang].csv
的新文件。

第 4 步:更新 languages.json

在 frappe/geo/languages.json 中添加你的语言

第 5 步:提交每个应用并推送

将包含 .csv 文件的更改提交到每个应用,并推送到它们的
代码仓库。