当你再次点击“Create”按钮时,翻译器API资源将被添加到你的帐户中。几秒钟之后,你将在顶栏中收到通知,说明部署了翻译器资源。点击通知中的“Go to resource”按钮,然后点击左侧栏上的“Keys”选项。你现在将看到两个Key,分别标记为“Key 1”和“Key 2”。将其中一个Key复制到剪贴板,然后将其设置到终端的环境变量中(如果使用的是Microsoft Windows,请用set替换export):
from guess_language import guess_language@app.route('/', methods=['GET', 'POST'])@app.route('/index', methods=['GET', 'POST'])@login_requireddef 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) # ...
该Key用于验证翻译服务,因此需要将其添加到应用配置中:
config.py: 添加Microsoft Translator API key到配置中
class Config(object): # ... MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY')
与很多配置值一样,我更喜欢将它们安装在环境变量中,并从那里将它们导入到Flask配置中。对于允许访问第三方服务的密钥或密码等敏感信息,这一点尤为重要。你绝对不想在代码中明确写出它们。
Microsoft Translator API是一个接受HTTP请求的Web服务。Python中有若干HTTP客户端,但最常用和最简单的就是requests包。所以让我们将其安装到虚拟环境中:
{% if post.language and post.language != g.locale %} <br><br> <a href="#">{{ _('Translate') }}</a> {% endif %}
在下面,你可以看到我使用Microsoft Translator API编写翻译文本的功能。我来新增一个app/translate.py模块:
app/translate.py:文本翻译函数
import jsonimport requestsfrom flask_babel import _from app import appdef translate(text, source_language, dest_language): if 'MS_TRANSLATOR_KEY' not in app.config or not app.config['MS_TRANSLATOR_KEY']: return _('Error: the translation service is not configured.') auth = {'Ocp-Apim-Subscription-Key': app.config['MS_TRANSLATOR_KEY']} r = requests.get('https://api.microsofttranslator.com/v2/Ajax.svc' '/Translate?text={}&from={}&to={}'.format( text, source_language, dest_language), headers=auth) if r.status_code != 200: return _('Error: the translation service failed.') return json.loads(r.content.decode('utf-8-sig'))
该函数定义需要翻译的文本、源语言和目标语言为参数,并返回翻译后文本的字符串。它首先检查配置中是否存在翻译服务的Key,如果不存在,则会返回错误。错误也是一个字符串,所以从外部看,这将看起来像翻译文本。这可确保在出现错误时用户将看到有意义的错误消息。
requests包中的get()方法向作为第一个参数给定的URL发送一个带有GET方法的HTTP请求。我使用*/v2/Ajax.svc/Translate* URL,它是翻译服务中的一个端点,它将翻译内容荷载为JSON返回。文本、源语言和目标语言都需要在URL中分别命名为text,from和to作为查询字符串参数。要使用该服务进行身份验证,我需要将我添加到配置中的Key传递给该服务。该Key需要在名为Ocp-Apim-Subscription-Key的自定义HTTP头中给出。我创建了auth字典,然后将它通过headers参数传递给requests。
requests.get()方法返回一个响应对象,它包含了服务提供的所有细节。我首先需要检查和确认状态码是200,这是成功请求的代码。如果我得到任何其他代码,我就知道发生了错误,所以在这种情况下,我返回一个错误字符串。如果状态码是200,那么响应的主体就有一个带有翻译的JSON编码字符串,所以我需要做的就是使用Python标准库中的json.loads()函数将JSON解码为我可以使用的Python字符串。响应对象的content属性包含作为字节对象的响应的原始主体,该属性是UTF-8编码的字符序列,需要先进行解码,然后发送给json.loads()。
下面你可以看到一个Python控制台会话,我演示了如何使用新的translate()函数:
>>> from app.translate import translate>>> translate('Hi, how are you today?', 'en', 'es') # English to Spanish'Hola, ¿cómo estás hoy?'>>> translate('Hi, how are you today?', 'en', 'de') # English to German'Are Hallo, how you heute?'>>> translate('Hi, how are you today?', 'en', 'it') # English to Italian'Ciao, come stai oggi?'>>> translate('Hi, how are you today?', 'en', 'fr') # English to French"Salut, comment allez-vous aujourd'hui ?"
06来自服务器的 Ajax
我将从实现服务器端部分开始。当用户单击动态下方显示的翻译链接时,将向服务器发出异步HTTP请求。我将在下一节中向你展示如何执行此操作,因此现在我将专注于实现服务器处理此请求的操作。
异步(Ajax)请求类似于我在应用中创建的路由和视图函数,唯一的区别是它不返回HTML或重定向,而是返回数据,格式为XML或更常见的JSON。你可以在下面看到翻译视图函数,该函数调用Microsoft Translator API,然后返回JSON格式的翻译文本:
app/routes.py:文本翻译视图函数
from flask import jsonifyfrom app.translate import translate@app.route('/translate', methods=['POST'])@login_requireddef translate_text(): return jsonify({'text': translate(request.form['text'], request.form['source_language'], request.form['dest_language'])})
如你所见,相当简单。我以POST请求的形式实现了这条路由。关于什么时候使用GET或POST(或者还没有见过的其他请求方法),真的没有绝对的规则。由于客户端将发送数据,因此我决定使用POST请求,因为它与提交表单数据的请求类似。 request.form属性是Flask用提交中包含的所有数据暴露的字典。当我使用Web表单工作时,我不需要查看request.form,因为Flask-WTF可以为我工作,但在这种情况下,实际上没有Web表单,所以我必须直接访问数据。
所以我在这个函数中做的是调用上一节中的translate()函数,直接从通过请求提交的数据中传递三个参数。将结果合并到单个键text下的字典中,字典作为参数传递给Flask的jsonify()函数,该函数将字典转换为JSON格式的有效载荷。 jsonify()返回的值是将被发送回客户端的HTTP响应。
例如,如果客户希望将字符串“Hello,World!”翻译成西班牙语,则来自该请求的响应将具有以下有效载荷:
{ "text": "Hola, Mundo!" }
07 来自客户端的 Ajax
因此,现在服务器能够通过*/translate* URL提供翻译,当用户单击我上面添加的“翻译”链接时,我需要调用此URL,传递需要翻译的文本、源语言和目标语言。如果你不熟悉在浏览器中使用JavaScript,这将是一个很好的学习机会
在浏览器中使用JavaScript时,当前显示的页面在内部被表示为文档对象模型(DOM)。这是一个引用页面中所有元素的层次结构。在此上下文中运行的JavaScript代码可以更改DOM以触发页面中的更改
我们首先需要讨论的是,在浏览器中运行的JavaScript代码如何获取需要发送到服务器中运行的翻译函数的三个参数。为了获得文本,我需要找到包含用户动态正文的DOM内的节点并获取它的内容。为了便于识别包含用户动态的DOM节点,我将为它们附加一个唯一的ID。如果你查看*_post.html*模板,则呈现用户动态正文的行只会读取{{post.body}}。我要做的是将这些内容包装在一个<span>元素中。这不会在视觉上改变任何东西,但它给了我一个可以插入标识符的地方:
app/templates/_post.html:给每条用户动态添加ID
<span id="post{{ post.id }}">{{ post.body }}</span>
这将为每条用户动态分配一个唯一标识符,格式为post1,post2等,其中数字与每条用户动态的数据库标识符相匹配。现在每条用户动态都有一个唯一的标识符,给定一个ID值,我可以使用jQuery定位<span>元素并提取其中的文本。例如,如果我想获得ID为123的用户动态的文本,我可以这样做:
$('#post123').text()
这里的$符号是jQuery库提供的函数的名称。这个库被Bootstrap使用,所以它已经被Flask-Bootstrap包含。 #是jQuery使用的“选择器”语法的一部分,这意味着接下来是元素的ID
我也希望有一个地方可以在我从服务器收到翻译文本后插入翻译文本。我要做的是将“翻译”链接替换为翻译文本,因此我还需要为该节点提供唯一标识符:
app/templates/_post.html:为翻译链接添加ID
<span id="translation{{ post.id }}"> <a href="#">{{ _('Translate') }}</a></span>
因此,现在对于一个给定的用户动态ID,我有一个用于用户动态的post <ID>节点和一个对应的translation <ID>节点,我可以在用翻译后的文本替换翻译链接时用到它们
下一步是编写一个可以完成所有翻译工作的函数。该函数将利用输入和输出DOM节点以及源语言和目标语言,向服务器发出携带必须的三个参数的异步请求,并在服务器响应后用翻译后的文本替换翻译链接。这听起来像很多工作,但实现相当简单:
app/templates/base.html:客户端翻译函数
{% block scripts %} ... <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 %}
前两个参数是用户动态和翻译链接节点的唯一ID,后两个参数是源语言和目标语言代码
该函数从一个很好的接触开始:它添加一个加载器替换翻译链接,以便用户知道翻译正在进行中。这是通过使用$(destElem).html()函数完成的,它用基于<img>元素的新HTML内容替换定义为翻译链接的原始HTML。对于加载器,我将使用一个小的动画GIF,它已添加到Flask为静态文件保留的app/static目录中。为了生成引用这个图像的URL,我使用url_for()函数,传递特殊的路由名称static并给出图像的文件名作为参数。你可以在本章的下载包中找到loading.gif图像
现在我用一个优雅的加载器代替了翻译链接,以便用户知道要等待翻译出现。下一步是将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:翻译链接处理器
<span id="translation{{ post.id }}"> <a href="javascript:translate( '#post{{ post.id }}', '#translation{{ post.id }}', '{{ post.language }}', '{{ g.locale }}');">{{ _('Translate') }}</a></span>
链接的href元素可以接受任何JavaScript代码,如果它带有javascript:前缀的话,那么这是一种方便的方式来调用翻译函数。因为这个链接将在客户端请求页面时在服务器端渲染,所以我可以使用{{}}表达式来为函数生成四个参数。每条用户动态都有自己的翻译链接,以及其唯一生成的参数。 post <ID>和translation <ID>需要渲染具体的ID,它们都需要在被使用时加上#前缀
现在实时翻译功能已经完成!如果你在环境中设置了有效的Microsoft Translator API Key,则现在应该能够触发翻译。假设你的浏览器设置为偏好英语,则需要使用其他语言撰写文章以查看“翻译”链接。下面你可以看到一个例子:
在本章中,我介绍了一些需要翻译成应用支持的所有语言的新文本,因此有必要更新翻译目录:
(venv) $ flask translate update
对于你自己的项目,需要编辑每个语言存储库中的messages.po文件以包含这些新测试的翻译,不过我已经在本章的下载包或GitHub存储库中创建了西班牙语翻译。
要完成新的翻译,还需要执行编译:
(venv) $ flask translate compile