小引
传统情形:
- Client 向 Server 发送请求 → Server 返回相应的 page → Client 展示 page 。
- 即使 Client 端仅更新 page 局部信息,都需要请求 Server,然后用新返回的 page 替换当前页。
- Server 完成所有逻辑工作,Client 仅展示 page 并接受 input。
- 每次更新,均须 page refresh。
Ajax
- Ajax,为 Asynchronous Javascript and XML 的缩写.
- 非单一技术,而是 a combination of several techniques.
- 将一部分业务逻辑放到前端: Server 发回的 page 中包含 sections of code(通常是JS). Client 接收展示 page 中的 HTML 部分, 并执行 page 中的 相应 code.
- 局部信息更新,无需每次 page refresh。
- Ajax 当前更多使用 JSON 作为前后端数据交互的 format (XML deprecated)
利用 Ajax 完成 post 翻译的思路
利用 Ajax 完成 post (非 preferred language)的翻译。
1、创建 Post Model 中的 language 字段
2、获得 sourceLang:’index’ 页(即 Home)提交 post 时,判断语种,写入 db
3、Server 端:
- 配置 translator service (app.config[‘MS_TRANSLATOR_KEY’])。
- 利用 Microsoft Translator API,创建翻译函数 translate()。
- 视图函数(‘/translate’):封装已创建的 translate()。用于接收 Client 提交的 data,返回调用翻译函数后生成的 JSON 译文。
4、Client 端:
- 在 post 页面(‘_post.html’) 设置 “Translate” 链接入口;链接显示条件:post 的 language 属性已知且不同于 g.locale。
- 底板 ‘base.html’ 中,创建
<script> function translate(){}</script
。 其中translate()
所需参数包括 sourceElem、destElem、sourceLang、destLang。 - 在 post 页面 (’_post.html’ ),利用标签
<span id="></span>
,标注待传参数sourceElem
、destElem
。 - 在 post 页面 (’_post.html’ )调用此
translate()
函数,传入已标注的参数。 - 将 Server 端返回的 JSON 译文,显示出来。
Server-side vs. Client-side
为实时翻译 user posts, Client browser 会向 Server 发送异步请求(asynchronous requests),Server 对此作出回复,但不会刷新页面,页面其他部分保留不变(untouched)。Client 然后会将译文动态地插入页面。
这就是 Ajax (现在常用 JSON 取代 XML)。
Live Translation Workflow
利用 Ajax 实现动态的自动翻译,涉及步骤:
首先,需要一种方法来识别 text 的源语言(source language),并且需要知道用户的语言(只对其他语种的 post 显示“翻译”链接)
First, I need a way to identify the source language of the text to translate. I also need to know the preferred language for each user, because I want to show a “translate” link only for posts written in other languages.
用户点击“翻译”,将 Ajax 请求发送到 Server
- Server 调用第三方 Translation API,完成翻译,将译文回传(response 中含有 translated text)
- Client 将收到的 translated text 动态地插入页面
Language Identification
在 post 创建时,就识别出其语种,然后作为 post 的属性,写入 db。
1、语种识别库 guess_language_spirit
(venv) $ pip install guess-language_spirit
guess_language_spirit,支持 python3, 源自 guess_language
2、Post Model 中添加 language 字段
app/models.py:
add detected language to Post model.
class Post(db.Model):
# ...
language = db.Column(db.String(5))
注:加了字符长度限制
3、Database Migration
(venv) $ flask db migrate -m "add language to posts"
#...
(venv) $ flask db upgrade
#...
4、在 post 创建提交时,检查语种,并储存
app / routes.py:
save language for new posts.
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)
# ...
注:language = ‘’ 时,视其为‘UNKNOWN’
Displaying a “Translate” Link
app / templates / _post.html:
add a translate link to posts.
{% if post.language and post.language != g.locale %}
<br><br>
<a href="#">{{ _('Translate') }}</a>
{% endif %}
- 在
_post.html
中显示,所以显示 posts 的网页,均具有此链接功能。 g.locale
来自 the function decorated by flask-babellocaleselector
的返回值。- post 语种属性不为空,且不同于 g.locale(用户偏好语种)时,链接才显现。
- 用
_()
标记 链接文本“Translate”,因其需要翻译成 g.locale 的语种。
Using a Third-Party Translation Service
1、注册 Microsoft Azure,获得免费 Translator API Service
2、配置 Translation Service Authentication
(venv) $ export MS_TRANSLATOR_KEY=<paste-your-key-here>
config.py
: add Microsoft Translator API key to the configuration.
class Config(object):
# ...
MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY')
3、创建翻译函数 translate
import json
import requests
from flask_babel import _
from app import app
def 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'))
注:
- 引入 requests 包,完成 http 请求
- 因为此 API 版本为 2.0,已经陈旧,改用 3.0 版本
import json
import requests
from flask_babel import _
from app import app
def 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.')
URL = 'https://api.cognitive.microsofttranslator.com/translate'
params = {'api-version': '3.0',
'from': '{}'.format(source_language),
'to': '{}'.format(dest_language)}
requestBody = [{'Text': text}] # dict format
headers = {
'Ocp-Apim-Subscription-Key': app.config['MS_TRANSLATOR_KEY'],
'Content-Type': 'application/json'
}
# requests.post(url, params=params, data=json.dumps(data), headers=headers)
# r is JSON array in Azure v3.0 (unlike in v2.0 where we can directly get the translated result).
r = requests.post(URL,
params = params,
data = json.dumps(requestBody, ensure_ascii=False).encode('utf-8'),
headers = headers)
if r.status_code != 200:
return _('Error: the translation service faild.')
return json.loads(r.content.decode('utf-8-sig'))[0]['translations'][0]['text']
# For text: 'Jon seems to be brooding recently. But that is understandible.'
# Translated text is included in the returned JSON Array:
# r.content.decode('utf-8-sig')= [{"translations":[{"text":"乔恩最近似乎在沉思。但那是 understandible。","to":"zh-Hans"}]}]
# json.loads(r.content.decode('utf-8-sig'))= [{'translations': [{'text': '乔恩最近似乎在沉思。但那是 understandible。', 'to': 'zh-Hans'}]}]
# To obtain translated text exclusively, we suffix it with " ..[0]['translations'][0]['text'] ".
# So, in routes.py, manually create the single dict {'text': translate('~')},
# to correspond to reponse['text'] in the base.html <script>function translate()</script>.
注:
- 不同于 v2.0 的请求为 GET, v3.0 版本 API 的请求为 POST。
- 使用 Python 调用 Microsfot Translation API 的方法,具体参照 Translate text with Python
requests.post(url, params=params, data=json.dumps(data), headers=headers)
,其中data
须由 dict format 转换为 json 格式.requests uses dict to send form-encode data like HTML form. When send data that is not form-encoded, pass in a JSON str instead of a dict. That data in JSON str will be posted directly.
- r 为 response object,包含所有 details provided by the service。
- r 的 content 属性是 bytes object,包含 raw body of the response,。
v2.0 中,r.content decoded 后,是 单个的 JSON string。
json.loads(r.content.decode('utf-8-sig'))
可以直接得到 translated text (a python string with the translated text)v3.0 中,r.content decoded 后,得到的是嵌套的 JSON array(异于v2.0 的单个 JSON string)。
json.loads()
后里面包含 translated text,须逐层...[0]['translations'][0]['text']
,方能得到 等同于 v2.0 的 translated text。
# For text: 'Jon seems to be brooding recently. But that is understandible.'
# Translated text is included in the returned JSON Array:
# r.content.decode('utf-8-sig')= [{"translations":[{"text":"乔恩最近似乎在沉思。但那是 understandible。","to":"zh-Hans"}]}]
# json.loads(r.content.decode('utf-8-sig'))= [{'translations': [{'text': '乔恩最近似乎在沉思。但那是 understandible。', 'to': 'zh-Hans'}]}]
# To obtain translated text exclusively, we suffix it with " ..[0]['translations'][0]['text'] ".
- 直接调用 v2.0 版本 API(直接得到 python str 的 translated text):
>>> translate('Hi, how are you today?', 'en', 'de') # English to German
'Are Hallo, how you heute?'
- 直接调用 v3.0 版本 API(未加 suffixes 时,得到嵌套的 translated text)
>>> translate('Hi, how are you today?', 'en', 'de')
[{'translations': [{'text': 'Hallo, wie geht es Ihnen heute?', 'to': 'de'}]}]
Ajax From The Server
创建视图函数 ‘translate_text’ (封装已创建的 ‘translate’ 函数)
app / routes.py:
text translation view function.
from flask import jsonify
from app.translate import translate
@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'])})
注:
利用 request.form 获取 Client 提交的 data。
The request.form attribute is a dictionary that Flask exposes with all the data that has included in the submission.
translate()
返回的是 python string (包含 translated text)- 构造 single dict,然后用 flask 的 jsonify() 方法,转换为 JSON 并返回形如
{ "text": "Hola, Mundo!" }
) - JSON 作为前后端数据交换的 format,前端视图里,调用 response[‘text’] 获取translated text.
Ajax From The Client
1、在底板 base.html
中创建 <scrip>function translate()</script>
函数,用于实现 translated text 的插入。
{% 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 %}
注:
- JS 大量运用 callback functions (更高级者 promises )
- 语法:
$.post(<url>, <data>).done(function(response) {}).fail(function() {})
2、在 posts 视图中,标记 ID
app / templates / _post.html:
add an ID to each blog post.
<span id="post{{ post.id }}">{{ post.body }}</span>
app / templates / _post.html:
add an ID to the translate link.
<span id="translation{{ post.id }}">
<a href="#">{{ _('Translate') }}</a>
</span>
4、在 posts 视图中,调用 base.html
中创建的 JS 函数 translate()
app / templates / _post.html:
translate link handler.
<span id="translation{{ post.id }}">
<a href="javascript:translate(
'#post{{ post.id }}',
'#translation{{ post.id }}',
'{{ post.language }}',
'{{ g.locale }}');">{{ _('Translate') }}</a>
</span>
5、测试前准备
(venv) $ flask translate update
- 更新
app/translations/
中的 messages.po 文件中的 新添加的 text 的译文。 (venv) $ flask translate compile
注:
- 测试时,必须重新添加几条 posts,因为旧有的 posts,均无 language 属性。
- 测试前,必须将 Microsoft Azure 的 subscription key,导入环境变量。
- 记得 将 loading.gif 导入
app / static/
中。
翻译前
翻译后
鼠标悬置链接上方时的状态