19.flask博客项目实战十四之 Ajax

配套视频教程

本文B站配套视频教程

PS:获取百度翻译API的appid和secretKey的过程在此略过,请参考文档

因此,本章将学习到如何 使用百度翻译服务和一些JavaScript脚本,添加实时语言翻译功能。

本文将离开服务端开发的“安全区域”,而开发具有同样重要性的 服务器和客户端组件。你可能看过 某些网站在用户生成的内容傍边显示的 “翻译”链接。这些链接触发非用户母语的内容的实时自动翻译。翻译的内容通常插入在原始版本之下。Google将其显示为外语搜索结果;Facebook为帖子做了;Twitter为推文做了。本文将展示如何将相同的功能添加到Microblog。

服务器和客户端

到目前为止所遵循的传统服务器端模型中,有一个客户端(用户命令的Web浏览器)向应用程序服务器发出HTTP请求。请求可简单地询问HTML页面,例如当你单击“Profile”链接时,或它能够触发一个操作,例如在你编辑完个人资料信息后单击“Submit”按钮时。在这两种类型的请求中,服务器通过直接或通过发出重定向向客户端发送新网页来完成请求。然后客户端用新的页面替换当前页面。只要用户停留在应用程序的网站上,此循环就会重复。在这个模型中,服务器完成所有工作,而客户端只显示网页并接受用户输入。

有一种不同的模型,客户端扮演着更积极的角色。在这种模型中,客户端向服务器发出请求,服务器响应网页,但跟前一种情况不同,并非所有页面数据都是HTML,页面上还有代码部分,通常用JavaScript编写。一旦客户端收到页面,它就会显示HTML部分,然后执行代码。从那时起,就将拥有一个活跃的客户端,可以独立完成工作 而不需要与服务器接触。在严格的客户端应用程序中,整个应用程序通过初始页面请求下载客户端,然后应用程序完全在客户端上运行,只接触服务器以检索或存储数据并对其外观进行动态更改而且只有网页。这种类型的应用程序被调用单页应用程序 (SPA)。

大多数应用程序是两种模型之间的混合,并结合了两者的技术。Microblog应用程序主要是服务器端应用程序,但今天将添加一些客户端操作。要对用户帖子进行实时翻译,客户端浏览器将向服务器发送异步请求,服务器将响应这个请求而不会导致页面刷新。然后,客户端将动态地把翻译插入到当前页面。这种技术称为 Ajax,它是异步JavaScriptXML的缩写(Asynchronous Javascript And XML。即使现在XML经常被JSON替换)。

实时翻译工作流程

有了Flask-Babel,这个应用程序对外语可有很好的支持,还可支持尽可能多的语言,因为我们可以找到翻译器。但当然的,这里缺少一个元素。用户将使用他们自己的语言撰写博客帖子内容,因此用户很可能会遇到使用未知语言编写的帖子。自动翻译的质量并不总是很好,但在大多数情况下,如果想要的是对另一种语言的文本大体意思,那就足够了。

这是Ajax服务能实现的理想功能。考虑到 /index 或 /explore 页面可能会显示若干个帖子,其中一些可能是外语。如果使用传统的服务器端技术实现翻译,请求翻译将导致原始页面被新页面替换。事实上,要求翻译许多显示的博客帖子中的一个并不足以要求完整页面更新,如果将翻译后的文本动态插入到原始文本的下方,则这个功能可以更好地工作,同时保留其余部分页面未触发过。

实现实时自动翻译 需要几个步骤。首先,需要一种方法来识别要翻译的文本的源语言。还需要知道每个用户的首选语言,因为我们只想为其他语言编写的帖子显示“翻译”链接。当提供“翻译”链接,用户点击它时,我得将Ajax请求发送到服务器,并且服务器将联系第三方翻译API。一旦服务器发回带有翻译文本的相应,客户端JavaScript代码将动态地将这个文本插入到页面中。正如可以肯定的那样,这将有一些非平凡的问题。接下来一一查看。

语言识别

第一个问题是 确定一篇帖子所写的语言。这可不是一个精确的科学,因为并不总是能明确地检测出一种语言,不过在大多数情况下,自动检测做得还不错了。在Python中,有一个很好的语言检测库,名为 guess_language。这个包的原始版本很旧,从未移植到Python3,因此,将安装支持Python2和3的派生版本:

(venv) D:microblog>pip install guess-language_spirit
Collecting guess-language_spirit
  Downloading https://files.pythonhosted.org/packages/8b/4f/9ed0280b24e9e6875c3870a97659d0106a14e36db0d7d65c5277066fc7d0/guess_language-spirit-0.5.3.tar.bz2 (81kB)
    100% |████████████████████████████████| 81kB 89kB/s
Installing collected packages: guess-language-spirit
  Running setup.py install for guess-language-spirit ... done
Successfully installed guess-language-spirit-0.5.3

计划是将每个帖子提供给这个包,以尝试确定语言。由于进行这个分析有点耗时,因此不希望每次将帖子呈现到页面时都重复这个工作。所以,要做的是在提交帖子时设置帖子的源语言。然后,检测到的语言将存储在posts表中。

第一步:向Post模型添加language字段:
app/models.py:将检测到的语言添加到Post模型

#...
class Post(db.Model):
    # ...
    language = db.Column(db.String(5))

	def __repr__(self):
		return '<Post {}'.format(self.body)

切记,每次对数据库模型进行更改时,都需要发出数据库迁移:

(venv) D:microblog>flask db migrate -m "add language to posts"
[2018-08-24 21:52:57,243] INFO in __init__: Microblog startup
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added column 'post.language'
Generating D:microblogmigrationsversions2c59449fbf9a_add_language_to_posts.py ... done

接着,需要将迁移运用于数据库:

(venv) D:microblog>flask db upgrade
[2018-08-24 21:54:01,981] INFO in __init__: Microblog startup
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 65b4b5c357fa -> 2c59449fbf9a, add language to posts

现在,可以在提交帖子时 检测并存储语言:
app/routes.py:保存新帖子的语言。

#...
from flask_babel import _,get_locale
from guess_language import guess_language
#...

@app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
@login_required
def index():
    form = PostForm()
    if form.validate_on_submit():
        language = guess_language(form.post.data)
        if language == 'UNKNOWN' or len(language) > 5:
            language = ''
        post = Post(body=form.post.data, author=current_user,
                    language=language)
        # ...
#...

通过上述更改,每次提交帖子时,都会通过guess_language()函数运行文本以确定语言。如果语言以 未知方式 或得到意外长的结果,我会将空字符串保存到数据库中以安全地使用它。我们将采用约定,将任何把语言设置为空字符串的帖子假定为未知语言。

不过上述操作意味着在此之前提交的帖子 是没有存储语言的。也就是说:这些帖子不能进行翻译。查看数据库字段就明白了。其中ID为11的帖子。

(venv) D:microblog>sqlite3 app.db
SQLite version 3.16.2 2017-01-06 16:32:41
Enter ".help" for usage hints.
sqlite> select * from post;
1|哈哈哈 测试一下  我是susan2018|2018-08-17 02:03:36.912898|1|
2|Hi!Everyone,I'm belen!very happy I go to China.|2018-08-17 02:11:19.022044|2|
3|我是john!最近延禧攻略贼么火!皇帝是大猪脚蹄子。|2018-08-17 02:12:40.504550|3|
4|mary,mary,mary。今天是七夕,你比无锡多一夕。|2018-08-17 04:16:24.684585|4|
5|mary,晚上吃牛排!自己煎牛排!!|2018-08-17 04:17:47.556267|4|
6|今天七夕,都TM几点了。暗恋我的人真坐得住!!!|2018-08-17 08:07:51.523524|1|
7|我是belen。韭州大地,都是韭菜啊,割啊割!|2018-08-17 09:38:08.329045|2|
8|滴 滴滴 滴 滴滴 天王盖地虎 小鸡炖蘑菇 春风吹扬柳 敢问是段友 啤酒小龙虾 段友是一家|2018-08-17 09:53:28.222920|2|
9|《南歌子~七夕》 七夕年年至,离人岁岁愁。 人间天上又添秋, 几许相思总在梦中留。 遥叹牛郎苦,难分织女忧。 水晶帘外月如钩, 把盏今宵醉听鹊声悠。|2018-08-17 09:55:13.794724|2|
10|大家都叫我老铁!这是我的第一篇帖子。|2018-08-24 02:12:16.819599|6|
11|This artice was very interesting.|2018-08-27 10:11:03.126274|6|en
12|speak english|2018-08-27 13:12:55.657963|2|
sqlite> .quit

(venv) D:microblog>

还有一点注意的,对于长度太短的字符串(帖子),如“speak englisth”、“Of course”等不会进行翻译。原因应该是guess_language()函数对于太短的字符串检测不到语言。如上帖子ID为12是没有存储到语言的。

显示“翻译”的链接

第二步 很简单。现在要做的是 在任何不属于当前用户活动语言的帖子傍边添加“翻译”链接。 app/templates/_post.html:为帖子添加翻译链接

...
            {{ post.body }}

            {% if post.language and post.language != g.locale %}
                <br><br>
                <a href="#">{{ _('Translate') }}</a>
            {% endif %}
        </td>
    </tr>
</table>

_post.html子模板中执行此操作,以便这个功能显示在显示博客帖子的任何页面上。“翻译”仅出现在检测到语言的帖子上,并且此语言与使用Flask-Babel localeselector装饰器修饰的函数选择的语言不匹配。回想一下上一节,所选语言环境存储为 g.locale。链接的文本需要以Flask-Babel可以翻译的方式添加,所以在定义它时用了_()函数。

注意,目前还没有跟这个链接关联的操作。首先,我们得弄清楚如何进行实际的翻译。

使用第三方翻译服务:百度翻译

(venv) D:microblog>set APPID=百度翻译API APPID

(venv) D:microblog>set BD_TRANSLATOR_KEY=百度翻译API 密钥

这个密钥用于通过翻译服务进行身份验证,因此需要将其添加到应用程序配置中:
microblog/config.py:将百度翻译API密钥添加到配置中

class Config(object):
    # ...
    APPID = os.environ.get('APPID')
    MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY')

与配置值一样,我更喜欢将它们安装在环境变量中,并那将其导入到Flask配置中。这对于能够访问第三方服务的敏感信息(如密钥或密码)尤为重要。肯定不希望在代码中明确地编写它们。

下方将看到使用百度翻译 API编写的用于翻译文本的功能。现推出一个新的app/translate.py模块:
app/translate.py:文本翻译功能

import json,random,hashlib,http.client
from flask_babel import _
from app import app
from urllib import parse

def translate(q, fromLang, toLang):
    if 'APPID' not in app.config or not app.config['APPID']:
        return _('Error:the translation service is not configured.')
    if 'BD_TRANSLATOR_KEY' not in app.config or not app.config['BD_TRANSLATOR_KEY']:
        return _('Error:the translation service is not configured.')
    appid = app.config['APPID']
    secretKey = app.config['BD_TRANSLATOR_KEY']

    httpClient = None
    myurl = '/api/trans/vip/translate'
    salt = random.randint(32768, 65536)

    sign = appid+q+str(salt)+secretKey
    m1 = hashlib.md5()
    m1.update(sign.encode(encoding='utf-8'))
    sign = m1.hexdigest()
    myurl = myurl+'?appid='+appid+'&q='+parse.quote(q)+'&from='+fromLang+'&to='+toLang+'&salt='+str(salt)+'&sign='+sign

    try:
        httpClient = http.client.HTTPConnection('api.fanyi.baidu.com')
        httpClient.request('GET', myurl)

        response = httpClient.getresponse()#response是HTTPResponse对象
        r = response.read().decode('utf-8')
        d = json.loads(r)

        l = d['trans_result']
        l1 = l[0]['dst']

        return(l1)
    except Exception as e:
        print(e)
    finally:
        if httpClient:
            httpClient.close()

上述代码中的函数将文本翻译为 源语言和目标语言代码作为参数,并返回带有翻译文本的字符串。首先,检查配置中是否存在翻译服务的密钥,如果不存在,则返回错误。错误也是一个字符串,因此从外部看,它看起来就像翻译的文本。这确保在出现错误的情况下,用户可看到有意义的错误消息。
响应的主体有一个带有翻译的JSON编码字符串,所以需要做的是使用Python标准库中的 json.loads()函数将JSON解码为我可以使用的Python字符串。响应对象的content属性 包含原始主体作为bytes对象,该对象转换为UTF-8字符串并发送到json.loads()

上述代码中myurl变量最终形如:http://api.fanyi.baidu.com/api/trans/vip/translate?appid=20180824000198587&q=apple&from=en&to=zh&salt=60422&sign=92ec698844333d1e9f04b756d14ae95d
浏览器输入上述URL,回车得到JSON格式的数据如下:

{
	from: "en",
	to: "zh",
	trans_result: [
		{
			src: "apple",
			dst: "苹果"
		}
	]
}

下方将看到在Python控制台会话中,使用translate()函数的演示:

(venv) D:microblog>set FLASK_APP=microblog.py

(venv) D:microblog>set MAIL_SERVER=smtp.qq.com

(venv) D:microblog>set MAIL_PORT=465

(venv) D:microblog>set MAIL_USE_SSL=True

(venv) D:microblog>set MAIL_USERNAME=you-qq@qq.com

(venv) D:microblog>set MAIL_PASSWORD=授权码

(venv) D:microblog>set APPID=百度翻译API appid

(venv) D:microblog>set BD_TRANSLATOR_KEY=百度翻译API secretKey

(venv) D:microblog>python
Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from app.translate import translate
[2018-08-27 14:46:15,518] INFO in __init__: Microblog startup
>>> translate('Hi, how are you today?', 'en', 'zh')#英语 To 中文
'嗨,你今天好吗?'
>>> translate('Hi, how are you today?', 'en', 'de')#英语 To 德语
'hi, wie geht es ihnen heute?'
>>> translate('Hi, how are you today?', 'en', 'it')#英语 To 意大利语
'ciao, come va oggi?'
>>> translate('Hi, how are you today?', 'en', 'spa')#英语 To 西班牙语
'¿Hola, cómo estás hoy?'
>>> translate('Hi, how are you today?', 'en', 'fra')#英语 To 法语
"salut, comment vas - tu aujourd'hui?"
>>> quit()

(venv) D:microblog>

太酷了!接下来可将此功能与应用程序集成。

来自服务器的Ajax

从实现服务器端部分开始。当用户单击帖子下方显示的翻译链接时,将向服务器发出异步HTTP请求。将在下一个会话中展示如何执行这个操作,因此现在将专注于实现服务器对此请求的处理。

异步(或Ajax)请求类似于我在应用程序中创建的路由和视图函数,唯一的区别是它不是返回HTML 或重定向,而是返回格式为XML或更常见的JSON的数据。下面将看到翻译视图函数,这个函数将调用 百度翻译API,然后以JSON格式返回翻译后的文本:
app/routes.py:文本翻译视图函数

#...
from flask import jsonify
from app.translate import translate
#...

@app.before_request
def before_request():
    if current_user.is_authenticated:
        current_user.last_seen = datetime.utcnow()
        db.session.commit()
    #g.locale = str(get_locale())
    g.locale = 'zh' if str(get_locale()).startswith('zh') else str(get_locale())
#...

@app.route('/translate', methods=['POST'])
@login_required
def translate_text():
    return jsonify({'text': translate(request.form['text'],request.form['source_language'],request.form['dest_language'])})

如上所见,这很简单。
首先,得将原先g.locale = 'zh_CN' ...改为g.locale = 'zh'...。因为Chrome浏览器上设置的语言,请求获得的是zh_CN。这在百度翻译要求的语言代码(中文 zh)不符,会出错,翻译的结果将是null。另一个解决方案是将translate_text()中的request.form['dest_language'](在这个例子中打印出来将是zh_CN)加上字符串截取为zh,或提前处理。

将这个路由实现为POST请求。关于上面时候使用GETPOST,确实没有绝对的规则。由于客户端将发送数据,决定使用POST请求,因为这类似于提交表单数据的请求。request.form属性是Flask公开的字段,其中包含提交中包含的所有数据。当使用Web表单时,我不需要查看request.form,因为Flask-WTF已为我们做了所有工作,但在这种情况下,实际上没有Web表单,所以我必须直接访问数据。

所以我正在这个函数中做的是 去调用上一小节中的translate()函数,它直接从请求提交的数据中传递三个参数。结果被合并到密钥文本下的单键字典中,字典作为一个参数传递Flask的jsonify()函数,这个函数将字段转换为一个JSON格式的有效负载。来自jsonify()的返回值是将发送会客户端的HTTP响应。

例如,如果客户端想要将字符串 Hello,World!转换为西班牙语,那么这个请求的响应将具有以下有效负载:

{ "text": "Hola, Mundo!" }

来自客户端的Ajax

现在服务器能够通过/translate URL提供翻译,当用户点击上面添加的“翻译”链接时,将文本,以及源语言和目标语言传递给 translate时,我们需要调用这个URL。如果不熟悉在浏览器中使用JavaScript,这将是一个很好的学习体验。

在浏览器中使用JavaScript时,当前显示的页面在内部表示为文档对象模型 或仅作为DOM。这是一个层次结构,它引用页面中存在的所有元素。在此上下文中运行的JavaScript代码可以更改DOM以触发页面中的更改。

我们首先讨论在浏览器中运行的JavaScript代码如何获取我需要发送到在服务器中运行的translate()函数的三个参数。要获取文本,需要在DOM中找到包含博客帖子主体的节点,并读取它的内容。为了便于识别包含博客帖子的DOM节点,我将为它们附加一个唯一的ID。如果你查看_post.html模板,那么呈现帖子主体的行 只会读取{{ post.body}}。我们要做的是将这个内容 包装在一个<span>元素中。这不会在视觉上改变任何东西,但它给了我们一个可以插入标识符的地方:
app/templates/_post.html:为每篇博客帖子添加ID

...
            {{ _('%(username)s said %(when)s', username=user_link, when=moment(post.timestamp).fromNow()) }}
            <br>

            <span id="post{{ post.id }}">{{ post.body }}</span>

            {% if post.language and post.language != g.locale %}
...

这将为每个博客帖子分配一个唯一的标识符,格式为 post1,post2等等,其中数字与每个帖子的数据库标识符匹配。现在每篇博客帖子都有一个唯一的标识符,给定一个ID值,可以使用jQuery来帖子定位<span>元素,并提取其中的文本。例如,如果我想获取ID为123的帖子的文本,如下将是我要做的:

$('#post123').text()

这里的 $ 标志是jQuery库提供的函数的名称。这个库由Bootstrap使用,因此Flask-Bootstrap已包含它。# 是jQuery使用的"selector"语法的一部分,这意味着接下来是 元素的ID。

我将还想有一个地方,一旦我从服务器收到翻译文本,我将插入它。我将要做的是 将“翻译”链接替换为翻译文本,因此我还需要为该节点提供唯一标识符:
app/templates/_post.html:在翻译链接中添加ID

            {% if post.language and post.language != g.locale %}
                <br><br>
                <span id="translation{{ post.id }}">
                    <a href="#">{{ _('Translate') }}</a>
                </span>
            {% endif %}

所以,现在对于给定的帖子ID,我有一个博客帖子的 post<ID>节点,以及一个相应的translation<ID>节点,需用饭以后的文本替换翻译文本。

下一步是 编写一个可以完成所有翻译工作的函数。这个函数将使用输入和输出DOM节点、以及源语言和目标语言,使用所需的三个参数向服务器提出异步请求。最后在服务器响应后将Translate链接替换为已翻译的文本。看起来很多工作,但实现起来相当简单:
app/templates/base.html:客户端翻译功能

...
{% block scripts %}
	{{ super() }}
	{{ moment.include_moment() }}
	{{ moment.lang(g.locale) }}

	<script>
		function translate(sourceElem, destElem, sourceLang, destLang) {
			$(destElem).html('<img src="{{ url_for('static', filename='loading.gif') }}">');
			$.post('/translate', {
				text:$(sourceElem).text(),
				source_language:sourceLang,
				dest_language:destLang
			}).done(function(response) {
				$(destElem).text(response['text'])
			}).fail(function() {
				$(destElem).text("{{ _('Error:Could not contact server.') }}");
			});
		}
	</script>
{% endblock %}

前两个参数是posttranslate链接节点的唯一ID。最后两个参数是源语言和目标语言代码。

这个函数以一个很好的接触开始:它添加一个微调器替换Translate链接,以便用户知道翻译正在进行中。这是通过jQuery完成的,使用$(destElem).html()函数将基于<img>链接的新HTML内容 替换定义翻译链接的原始HTML。对于微调器,将使用一个小动画GIF,并添加到app/static/loading.gif目录下,Flask作为静态文件保留。为了生成引用这个图像的URL,正在使用url_for()函数,传递特殊路基名称static,并将图像的文件名作为参数。这个loading.gif图像可在源码地址中下载。

所以现在有一个很好的微调器取代了Translate链接,因此用户知道等待翻译出现。下一步是将POST请求发送到在上一节中定义的 /translate URL。为此,还将使用jQuery,在这个情况下的$.post()函数。这个函数以类似于浏览器提交Web表单的格式向服务器提交数据,这很方便,因为这允许Flask将这个数据合并到request.form字段中。参数$.post()是两个,首先是发送请求的URL;然后是服务器期望的三个数据项(或者在JavaScript中调用的对象)。

JavaScript可以使用回调函数 或更高级的回调形式(称为 promises)。现在要做的是在请求完成 并且浏览器收到响应后指示我想要做什么。在JavaScript中国没有等待某事的东西,一切都是异步的。我们需要做的是提供一个回调函数,浏览器在收到响应时会调用它。而且作为一种使所有内容尽可能健壮的方法,我想指出在出现错误的情况下该怎么做,这样才能成为处理错误的第二个回调函数。有几种方法可以指定这些回调,但是对于这种情况,使用promises会使代码相当清晰。语法如下:

$.post(<url>, <data>).done(function(response) {
    // success callback
}).fail(function() {
    // error callback
})

promise语法允许我们基本上将回调“链接”到调用的返回值$.post()。在成功回调中,需要做的是 $(destElem).text()使用翻译后的文本进行调用,这个文本位于text密钥下的字典中。在出现错误的情况下,也这样做,但显示的文本是一般错误消息,我确保将其作为可翻译文本输入基本模板中。

所以现在唯一剩下的是 用户点击翻译链接时用正确的参数触发translate()函数。还有一些方法可做到这一点,我要做的是在链接的href属性中嵌入对函数的调用:
app/templates/_post.html:翻译链接处理程序

...
            {% if post.language and post.language != g.locale %}
                <br><br>
                <span id="translation{{ post.id }}">
                    <a href="javascript:translate('#post{{ post.id }}', '#translation{{ post.id }}', '{{ post.language }}', '{{ g.locale }}');">{{ _('Translate') }}</a>
                </span>
            {% endif %}
        </td>
    </tr>
</table>

如果href链接的元素带有前缀 javascript:,那么链接的元素可以接受任何JavaScript代码,因此这是调用翻译函数的便捷方式。因为当客户端请求页面时,将在服务器中呈现此链接,所以我可以使用 {{ }}表达式生成函数的四个参数。每个帖子都有自己的翻译链接,其唯一生成的参数。在post<ID>translation<ID>元素的前缀 #表明,接下来是一个元素ID。

现在,实时翻译功能已经完成!在环境变量中设置有效的APPIDBD_TRANSLATOR_KEY,那么现在就能够触发翻译。如果我将浏览器设置为中文优先,那么需要使用其他语言撰写的帖子以查看“翻译”链接。flask run命令运行程序,效果:略过

在本章中,介绍了一些需要翻译成应用程序支持的所有语言的新文本,因此有必要翻译更新目录:

(venv) D:microblog>flask translate update
[2018-08-27 20:41:56,843] INFO in __init__: Microblog startup
extracting messages from app\__init__.py
extracting messages from appcli.py
extracting messages from appemail.py
extracting messages from apperrors.py
extracting messages from appforms.py
extracting messages from appmodels.py
extracting messages from app
outes.py
extracting messages from app	ranslate.py
extracting messages from app	emplates404.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplates500.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplates\_post.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplatesase.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplatesedit_profile.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplatesindex.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplateslogin.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplates
egister.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplates
eset_password.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplates
eset_password_request.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplatesuser.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
extracting messages from app	emplatesemail
eset_password.html (extensions="jinja2.ext.autoescape,jinja2.ext.with_")
writing PO template file to messages.pot
updating catalog app/translationszhLC_MESSAGESmessages.po based on messages.pot

对于这个项目,需要编辑每个语言存储库中的messages.po文件以包含这些新测试的翻译,但我已经在项目中创建了中文翻译。

(venv) D:microblog>flask translate compile
[2018-08-27 20:42:18,763] INFO in __init__: Microblog startup
compiling catalog app/translationszhLC_MESSAGESmessages.po to app/translationszhLC_MESSAGESmessages.mo

目前为止,项目结构

microblog/
    app/
        static/
	        loading.gif
        templates/
	        email/
		        reset_password.html
		        reset_password.txt
	        _post.html
	        404.html
	        500.html
            base.html
            edit_profile.html
            index.html
            login.html
            register.html
            reset_password.html
            reset_password_request.html
            user.html
        translations/
	        zh/
		        LC_MESSAGES/
		        	messages.mo
		        	messages.po
        __init__.py
        cli.py
        email.py
        errors.py
        forms.py
        models.py
        routes.py
        translate.py
    logs/
        microblog.log
    migrations/
    venv/
    app.db
    babel.cfg
    config.py
    messagee.pot
    microblog.py
    tests.py

参考
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xiv-ajax

原文地址:https://www.cnblogs.com/songboriceboy/p/13852094.html