不流泪的翻译
#python #sqlite #i18n #l10n

如果我们查看了一个典型的国际化Web应用程序的教程,则需要记住很多概念和命令。例如,这是您可以找到python/flask web的最短教程:https://medium.com/i18n-and-l10n-resources-for-developers/localization-for-flask-applications-3bef6d6aaf52

它引入了很多新事物:安装此软件包,配置I18N工具,标记所有字符串,将消息提取到.pot(便携式对象模板)文件中,为每种语言生成单个语言环境文件,然后编译翻译。这是在实际翻译消息之前!而且,我们甚至不要考虑随着时间的推移维护此应用程序,因为我们需要添加,删除和更新消息(即字符串)。

这种复杂性的固有是多少,偶然的是多少?我一直在努力解决这个问题一段时间,并认为我们实际上可以在某些开箱即用的思维和SQLite, the developer's best friend的帮助下剃掉很多手动错误的工作。

目标

我们可以为国际化典型的WebApp做出的最低工作量是多少?出于本文的目的,我将忽略不同类型的WebApp,即服务器渲染或客户端渲染,并假装服务器渲染的案例是典型的示例。我也有些怀疑您想在前端应用程序上加载所有语言的翻译字符串...嗯,除非您不关心性能ð

所以让我们看看是否可以:

  1. 声明我们要支持的语言列表
  2. 在请求响应周期上设置一些最小的I18N支持
  3. 标记应用程序中的所有字符串
  4. 运行应用程序并生成所有可翻译字符串的列表
  5. 翻译字符串并重新运行应用程序使用翻译

输入sqlite

通常,I18N/L10N使用许多不同的工具在不同的技术中完成。在开源世界,尤其是在Python生态系统中,最受欢迎的是GNU GetText套件的工具及其Python支持。不幸的是,这些工具需要正确使用几种不同的文件格式和CLI工具才能工作。我对此的看法是,这些文件格式和工具来自早期的时代,当时必须对工具进行自定义设计以处理结构化内容,例如翻译字符串。但是,今天不必那样。

而不是用于管理它们的.pot文件,.po文件和多个命令,而不是Mish-Mash,如果我们使用了近乎宇宙的SQLite库怎么办?这就是podb背后的思考过程,podb是一个小概念验证库,它使用SQLite自动处理I18N的所有无聊部分。

podb

这是它的工作原理。您在应用程序中创建并打开一个Podb数据库。这将磁盘上的SQLite数据库初始化。然后,您创建一些语言对象。这些是呼叫量,它们返回其输入字符串的翻译。然后在整个应用程序中,您都使用这些语言对象。图书馆指出了所有语言和它们翻译的所有字符串的用法,并将它们记录在SQLite数据库中。最后,当应用程序退出时,您关闭了Podb数据库,该数据库还关闭了SQLite数据库,并为所有需要翻译的语言和字符串保存.po文件。您填写翻译并重新运行应用程序。在下一次运行中,podb注意到翻译已填充,并将其记录在SQLite数据库中。因此,sqlite数据库是真相的来源。

.pot文件,没有运行不同的命令来提取消息,编译翻译等。所有这些都是自动照顾的。实际上,这与编写snapshot tests的经历非常可比。

在交换中,您必须:

  1. 将SQLITE数据库提交您的存储库。或至少设置一些管道,以便在部署时间与您的应用程序一起使用。
  2. ,将其直接投入回购很简单。
  3. 实际上至少在您需要翻译成的语言中使用该应用程序(甚至像en-GB计数一样!)。仅使用 hit 的代码路径才能触发要记录在数据库中的翻译。

应用程序演练

让我们使用podb漫步到裸露的最小python烧瓶应用程序。这是散布在我的评论的代码:

# app.py

from typing import Optional
from flask import Flask, g, render_template, request
from podb import Podb
import signal
import sys

pos = Podb().__enter__()

Podb类被设计为上下文管理器,因此可以用作资源。但是,烧瓶应用程序并不能真正支持这种样式。因此,我们在脚本开始时手动打开它。

def _shutdown(signum, frame):
    pos._close()
    sys.exit(0)

signal.signal(signal.SIGINT, _shutdown)
signal.signal(signal.SIGTERM, _shutdown)

再次,由于我们手动打开了数据库,因此我们必须手动安排它的关闭,而在WebApp中,唯一现实的方法是通过处理信号。

app = Flask(__name__)

LANGUAGES = {'fr-CA', 'fr', 'it', 'en-GB', 'en'}

最后,我们正在设置语言。当然,这套集可以在应用程序的生命周期内成长或收缩。

@app.before_request
def accept_language():
    # Important: construct language objects only from statically-known set of
    # language names. The best_match method will return one of the languages in
    # the set.
    lang_name = request.accept_languages.best_match(LANGUAGES, default='en')
    g.lang_name = lang_name
    g.t = pos.lang(lang_name)

@app.after_request
def content_language(resp):
    resp.content_language.add(g.lang_name)
    return resp

在这里,我们使用Blask的重新要求和重新要求挂钩来使用HTTP headers进行语言内容谈判。旁注:我发现几乎每个人都使用此Web标准转向并手动实现语言下拉列表有些不幸。我们可以使用内置在浏览器内置的标准语言选择器来避免大量开发时间,例如chrome://设置/语言

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name: Optional[str]=None):
    return render_template(
        'hello.html',
        lang=g.lang_name,
        hello_from=g.t('Hello from'),
        hello=g.t('Hello'),
        name=name)

最后,实际的请求处理程序呈现模板。请注意,我们将翻译的字符串(使用使用正确的语言转换器为请求设置的g.t对象)传递到模板中:

<!-- templates/hello.html -->

<!doctype html>
<html lang="{{ lang }}">
  <head>
    <title>Hello</title>
  </head>
  <body>
{% if name %}
    {{ hello }}, {{ name }}!
{% else %}
    {{ hello_from }} Flask!
{% endif %}
  </body>
</html>

在模板中,我们正在插值传递的变量。

如果您想运行此操作,我建议以下设置:

$ mkdir -p flask_podb_test/po # po subdirectory used by podb
$ cd flask_podb_test
$ python3 -m venv env
$ source env/bin/activate
$ python -m pip install --upgrade pip
$ python -m pip install Flask polib

然后,将上面链接的回购文件复制到此目录中。运行:

python -m flask --app app run # app argument refers to app.py

向服务器发送一些请求,例如:

curl -i -H 'Accept-Language: fr' 'http://127.0.0.1:5000/hello/'

使用CTRL-C退出服务器,并注意到使用SQLITE数据库和fr.po文件填充po子目录:

msgid ""
msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: https://github.com/yawaramin/podb\n"
"Project-Id-Version: podb\n"
"Language: fr\n"

#: podb
msgid "Hello from"
msgstr ""

#: podb
msgid "Hello"
msgstr ""

该自动化文件旨在发送给翻译人员,例如SaaS平台,组织或服务提供商中的翻译人员(甚至是您自己进行测试),填充并放回po子目录中。在下一次运行中,该应用将摄入SQLite数据库中的新翻译。再次退出后,它将记录.po文件中剩余的任何丢失的翻译。冲洗并重复。

请注意,无论您处于应用程序的生命周期中哪个阶段,该过程都保持不变。设置了基本的podb基础架构后,无论您是添加新字符串还是要翻译的新语言,它都以相同的方式运行应用程序,将自动化文件转换为翻译,再次运行该应用程序。无需记住该过程不同阶段的不同命令。

我确实想强调,在交换中,您正在放弃对可翻译消息的静态提取。”只有实际练习这些代码路径,例如,您才能获得消息提取。使用手动或自动测试。我认为这是一个合理的权衡。毕竟,谁将新功能发行了生产新功能,至少没有尝试它们? ð