The Flask Mega-Tutorial 之 Chapter 14: Ajax

小引

传统情形

  • 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() 所需参数包括 sourceElemdestElemsourceLangdestLang
  • 在 post 页面 (’_post.html’ ),利用标签 <span id="></span>,标注待传参数 sourceElemdestElem
  • 在 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’


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-babel localeselector 的返回值。
  • post 语种属性不为空,且不同于 g.locale(用户偏好语种)时,链接才显现。
  • _() 标记 链接文本“Translate”,因其需要翻译成 g.locale 的语种。

Using a Third-Party Translation Service

1、注册 Microsoft Azure,获得免费 Translator API Service
register azure

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/ 中。

翻译前
before translate

翻译后
after translate

鼠标悬置链接上方时的状态
js status

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值