像Youtube和Instagram:国际化和本地化Python应用程序

Python是举世闻名的应用程序(例如Youtube,Instagram和Pinterest)的核心。 为了在世界市场上前进,应用程序需要本地化,即适应特定国家/地区的特征,以及国际化-内容翻译。 在本文中,我们将分享有关如何加快翻译自动化并解决该领域中一些典型问题的经验。



引言


这是国际化(i18n)python应用程序的简短指南。 对于拥有python开发经验的所有程序员来说,本指南都会很有趣。 阅读文章将需要10到15分钟。

我们将使用python语言中包含的经过良好测试的gettext工具。

首先,我们将了解什么是国际化:

国际化(I18N)是使应用程序适应其开发所在国家/地区以外的其他国家和地区语言的过程。

但是,还有一个更广泛的概念:

本地化(L10N)是通过添加特定于给定语言环境的组件并翻译文本来使国际化应用程序适应特定区域或语言的过程。

本地化意味着翻译:

  • 日期和时间格式;
  • 数字格式;
  • 时区
  • 日历
  • 货币表示;
  • 税金/增值税;
  • 温度等措施;
  • 邮政编码,电话;
  • 地址格式;
  • 结算代码。



本地化不仅限于将内容翻译成另一种语言。 有些文化和功能参数也需要引起注意。 例如,北美的日期格式为“ MM / DD / YYYY”,但在大多数亚洲国家,日期格式为“ DD / MM / YYYY”。



应用程序翻译错误的著名示例

另一个示例涉及应用程序中名称的显示。 在美国,通过姓名呼叫某人是可以接受的,甚至是更好的选择,只要客户登录,该客户的名称就会显示在标题中。 但是,在日本情况恰恰相反:按名字称呼某人是不礼貌甚至令人反感的。 本地化应考虑到这一点,并避免为日语受众使用名称。

在本文中,我们将仅考虑国际化,但是以类似的方式构建本地化机制。 本文提到的库支持应用程序本地化。

主要类型


国际化分为:

  1. 直接在python脚本中转换数据。
  2. 模板引擎中数据的翻译。
  3. 翻译存储在数据库中的数据。

1. python脚本数据的翻译


为了使我们的国际化发挥作用,我们需要了解babel库和distutils工具包,以管理项目的销售和后续组装。

翻译准备


首先,我们需要创建翻译列表。 首先,我们安装Babel库-这是一个公认的python库,用于本地化和转换日期,货币以及用于构建项目的方便添加项(如下所述)。

Python提供了用于多种语言的工具包-gettext。 GNU gettext实际上是一个通用的本地化解决方案,它为多语言消息中的其他编程语言提供支持。 Gettext不仅用于许多编程语言中,而且还用于操作系统的翻译中;它是github上经过良好测试的,免费发行的软件。

为了使翻译正常运行,您需要导入gettext模块,并将包含翻译的脚本传递给输入。 首先,我们用特殊功能_('some_text')标记所有翻译后的字符串。 在项目中对该函数的调用将如下所示:

import gettext import os localedir = os.path.join(os.path.abspath('/path/to/locales'), 'locales') translate = gettext.translation('domain_name', localedir, ['ru']) _ = translate.gettext print(_('some_text')) print(_('some_text_2')) 

在一小段代码中,创建一个国际化对象,该对象使用“ locales”目录作为翻译短语的源。 尚未创建“ locales”目录,但应用程序将在其中查找运行时的翻译。

为简便起见,translate.gettext函数将在下面表示为_。 下划线是此函数的通用名称,Python社区认可该名称。

_()函数标记要翻译的行。 gettext模块附带有xgettext工具,该工具通过代码解析字符串标记_(),并形成可移植对象模板(pot-file)。 要创建pot文件,让我们回到已安装的Babel库,该库具有许多支持国际化的功能。 Babel扩展了setup.py构建脚本,该脚本可以使用标准python distutils库或您选择的第三方setuptools软件包编写。 Python模块的组装超出了本文的范围;有关更多详细信息,请参见文档 。 需要做的就是创建一个具有以下内容的setup.py文件:

 from babel.messages import frontend as babel from distutils.core import setup setup(name='foo', version='1.0', cmdclass = {'extract_messages': babel.extract_messages, 'init_catalog': babel.init_catalog, 'update_catalog': babel.update_catalog, 'compile_catalog': babel.compile_catalog,} ) 

因此,我们创建了构建项目的说明,并从babel库中添加了四个国际化团队。 按使用顺序更详细地考虑这些命令。

extract_messages

该命令是GNU xgettext工具的包装,该工具将_()可翻译标签解析为pot文件。 要运行,您需要对装配进行几个设置。 为此,请在根目录中,使用以下内容创建setup.cfg文件:

 [extract_messages] input_dirs = foobar output_file = foobar/locales/messages.pot 


  • input_dirs-从中选择_()代码中的所有标签进行翻译的目录名称。
  • output_file-生成的.pot文件的路径

要运行命令,请在控制台中执行:

 $ python setup.py extract_messages 


 running extract_messages extracting messages from foobar/__init__.py extracting messages from foobar/core.py ... writing PO template file to foobar/locales/messages.pot 

在pot文件中,标记的行收集在一个列表中,翻译人员可以从列表中为每种所需语言创建翻译。

 # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2018-01-28 16:47+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: pygettext.py 1.5\n" #: src/main.py:5 msgid "some_text" msgstr "" #: src/main.py:6 msgid "some_text_2" msgstr "" 

接下来,您需要为几种语言创建翻译。 为此,请使用以下babel命令。

init_catalog

此命令是GNU msginit工具的包装,该工具基于pot文件创建一个新的转换目录。

 $ python setup.py init_catalog -l en -i foobar/locales/messages.pot \ -o foobar/locales/en/LC_MESSAGES/base.po 

 running init_catalog creating catalog 'foobar/locales/en/LC_MESSAGES/messages.po' based on 'foobar/locales/messages.pot' 

重要! 本地化文件按照约定以特定方式存储:

语言环境// LC_MESSAGES / .po

-包含翻译成特定语言的目录,在本例中为英文(en)。 可能还会有一个目录,不仅包含特定语言的翻译,而且还考虑了其​​他功能。 例如,美国的英文翻译为en_US;

-具有翻译的域名。 如果我们的应用程序扩展,翻译将被划分为多个域,以免使一个文件超载。

update_catalog

此命令是GNU msgmerge工具的包装,该工具将更新* .po文件的现有翻译目录。

添加新翻译时,我们只需运行以下命令:

 $ python setup.py update_catalog -l en -i foobar/locales/messages.pot \ -o foobar/locales/en/LC_MESSAGES/base.po 

 running update_catalog updating catalog 'foobar/locales/en/LC_MESSAGES/base.po' based on 'foobar/locales/messages.pot' 

我们也可以通过指定ru而不是en来指定俄语的本地化。

compile_catalog

最终命令是对GNU msgfmt工具的包装。 它从* .po文件中获取可翻译的消息,并将它们编译为二进制* .mo文件,以优化性能。

 $ python setup.py compile_catalog --directory foobar/locales --domain base 

 running compile_catalog compiling catalog to foobar/locales/en/LC_MESSAGES/base.mo 

--directory-具有本地化的目录的路径,
--domain-用于指定翻译域的标志,我们根据现有的应用程序域对其进行指定。

Python脚本仅适用于优化的* .mo转换。 因此,进行任何更改以便使其出现在应用程序中,有必要使用本地化重新编译文件。 要使用翻译文件,您可以使用poedit应用程序-它适用于所有操作系统,并且是自由分发的软件。



poedit-翻译应用

每个翻译都显示为单独的一行,这很方便。 完成翻译工作后,保存更改时,将自动编译包含所有更改的* .mo二进制文件。

结果,翻译目录的结构将如下所示:

 locales ├── en │ └── LC_MESSAGES │ ├── base.mo │ └── base.po ├── ru │ └── LC_MESSAGES │ ├── base.mo │ └── base.po └── messages.pot 


翻译标记名称约定

po文件包含文本翻译,并在逻辑上合并为具有通用名称的文件。 这些组称为域。 在上面的示例中,只有一个名为base的域。 在大型应用程序中,将有更多的域,并且需要考虑到应用程序的结构来编写翻译列表。

有必要保持翻译标记名称的统一性,以消除翻译中的进一步混乱。 例如,我们有一个表单,用于在用户个人资料页面上保存用户数据:

profile.user_form.component.title:用户数据
profile.user_form.component.save:保存
profile.user_form.field.username:用户名
profile.user_form.field.password:密码


应用部署

要在docker中部署和部署应用程序,您需要使用以下命令将转换文件编译为* .mo二进制文件:

 $ python setup.py compile_catalog --domain <> 

我们建议在.gitignore中排除* .mo和* .pot文件:

#翻译
* .mo
* .pot

2.模板引擎中的数据转换


使用本地化模板时,一切都变得容易一些。 考虑最流行的python模板引擎-Jinja。 对于此模板引擎,已经实现了通过加载项对gettext-localization的支持。 要激活加载项,必须在Environment构造函数中指定加载项模块的路径。 对于多语言平台,您需要下载一次翻译,并在应用程序初始化期间将翻译对象添加到Environment对象:

 translations = get_gettext_translations() env = Environment(extensions=['jinja2.ext.i18n']) env.install_gettext_translations(translations) 

然后,在模板中,我们仅使用以下构造:

 {{ gettext('some_text') }} {{ gettext('Hello %(name)s!')|format(name='World') }} 

3.转换数据库中存储的数据


让我们考虑在最常见的关系数据库中使用翻译的选项。 应当注意,noSQL和newSQL数据库的翻译和本地化的实现是相似的。

注意:当每种语言的翻译存储在单独的列中时,我们将不考虑这种情况。 这样的实现需要扩展的限制以及其他应用程序支持的其他风险。

1)每种语言的单独行


通过这种方法,对于每种语言,行中特定语言的翻译都是基于列的值,例如language_code。 如果值en在此列中,则所有转换后的值均应参考给定的国家和地区。



对于所描述的方案,表中的数据应如下所示:



优点:

  • 简单有效的实现。
  • 使用特定语言代码时的简单查询。


缺点:
  • 缺乏集中化

可以将不同语言的译文存储在不同的表中。 因此,您不知道您的应用程序可以完全翻译多少种语言。

该解决方案适用于最初不需要全部数据国际化的应用。 但是随着业务的扩展,可以为新地区添加翻译。

数据要求如下:

 SELECT p.product_name, p.price, p.description FROM product p WHERE p.language_code = @language_code; 

2)带翻译的单独表格


通过这种方法,对于每个需要本地化的表,我们都会创建带有翻译的表。



优点:

  • 无需为未转换的数据联接表。
  • 由于有单独的翻译表,因此查询变得容易。
  • 数据中没有差异。
  • 除了翻译外,还可以有效地本地化语言表中的其余数据。

缺点:

  • 在大型应用程序中,转换表过大并减慢了速度。 优化应用程序时,有必要在单独的表上实现数据迁移。

数据要求如下:

 SELECT tp.text, p.price, tc.text, c.contact_name FROM order_line o, product p, customer c, translation tp, translation tc, language l WHERE o.product_id = p.id AND o.customer_id = c.id AND p.name_translation_id = tp.id AND c.name_translation_id = tc.id AND tp.language_id = l.id AND tc.language_id = l.id AND l.name = @language_code AND o.id = ***; 

3)为翻译和非翻译字段创建实体


在此解决方案中,包含一个或多个转换字段的实体表将使用未转换的字段扩展数据。



优点:

  • 无需将转换表与包含不需要转换的数据的表合并。 因此,对此类数据进行采样将具有更好的性能,
  • 编写ORM查询很容易,
  • 一个简单的SQL查询即可获取翻译后的文本,
  • 支持将某些数据转换为所有可用语言很容易。

缺点:

  • 实现的相对复杂度。

这是一个查询的示例,它将检索翻译后的文本:

 SELECT pt.product_name, pt.description, p.price FROM order_line o, product p, product_translation pt, language l WHERE o.product_id = p.id AND AND p.id = pt.product_non_trans_id AND pt.language_id = l.id AND l.name = @language_code; 

结论


在对国际市场的应用程序进行本地化和国际化时,可以使用各种方法,每种方法都有某些功能和限制。

在本文中,我们研究了以下类型的国际化:

  • 在代码中:我们在使用gui创建服务或应用程序时使用翻译;
  • 在模板中:我们在开发没有动态前端的Web应用程序时使用;
  • 在数据库中:在存储用户或动态生成的数据时使用。

我们希望本文能帮助您为项目选择最合适的方法。

Source: https://habr.com/ru/post/zh-CN479738/


All Articles