第十五章 Ajax

Posted 知之Python

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十五章 Ajax相关的知识,希望对你有一定的参考价值。

这将是国际化和本地化的最后一篇文章,我们将会尽所能使得 microblog 应用程序对非英语用户可用和更加友好。

不知道大家平时有没有见过网站上有一个 “翻译” 的链接,点击后会把翻译后的内容在旁边显示给用户,这些链接触发一个实时自动翻译的内容。谷歌显示这个“翻译” 链接是为了能够显示外国语言的搜索结果。Facebook显示它为了能够翻译 blog 内容。今天我们将要添加同样的功能的“翻译” 链接到我们的 microblog

客户端 VS 服务器端

在传统的沿用至今的服务器端的模型中,有一个客户端(用户的浏览器)发送请求到我们的服务器上。一个请求能够简单地请求一个页面,像当你点击“你的信息” 链接,或者它能够让我们执行一个动作,像当用户编辑他的或者她的用户信息并且点击提交的按钮。在这两种类型的请求中服务器通过发送一个新的网页到客户端,直接或通过发出一个重定向的请求来完成这次请求。客户端接着使用新页代替目前的页面。这个循环就会重复只要用户停留在我们的网页上。我们叫这种模式为服务器端,因为服务器做了所有的工作而客户端只是在它们收到页面的时候显示出来。

在客户端模式中,我们有一个网页浏览器,再次发送请求到服务器上。服务器会像服务器端模式一样回应一个网页,但是不是所有的页面数据都是 html,同样也有代码,典型的就是用 javascript 编写的。一旦客户端接收到页面会把它显示出来并且会执行携带的代码。从此,你有一个活跃的客户端,可以做自己的工作,没有接触外面的服务器。在严格的客户端,整个应用程序被下载到客户端在初始页面请求中,然后应用程序运行在客户端上不会刷新页面,只有向服务器获取或存储数据。这种类型的应用称为 单页应用 或者 SPAs。

大多数应用是这两种模式的结合体。我们的 microblog 应用程序是一个完全的服务器端应用,但是现在我们想要添加些客户端行为。为了实现实时翻译用户的 blog 内容,客户端浏览器将会发送一个请求到服务器,但是服务器将会回应一个翻译文本而且不需要页面刷新。客户端将会动态地插入翻译到当前页面。这种技术叫做 Ajax,这是 Asynchronous Javascript and XML 的简称。

翻译用户生成内容

多亏了 Flask-Babel 我们现在比较好的支持了多语言。假设我们能找到愿意帮助我们的翻译器,我们可以在尽可能多的语言中发布我们的应用程序。

但是还有一个遗漏问题。因为有很多各种语言的用户使用系统,那么用户发布的 blog 内容的语言也是多种的。可能不是本语种的用户不知道内容的含义,如果我们能够提供一种自动翻译的服务这种会不会更好?

这是一个用 Ajax 服务来实现的理想的功能。我们的首页可以显示很多的blog,它们中的一些可能是不同语言的。如果我们使用传统的服务器端模式来实现翻译的话,一个翻译的请求可能会让原始页面被新的只翻译了选择的blog 的页面替换。在用户读完翻译后,我们必须点击回退键去获取 blog 列表。事实上请求一个翻译并不是需要更新全部的页面,这个功能让应用程序更好,因为翻译的文本是动态地插入到原始的文本下面,其他的内容不会发生变化。因此,今天我们将会实现我们的Ajax 服务。

实现实时翻译需要几个步骤。首先,我们需要确定要翻译的文本的原语言类型。一旦我们知道原语言类型我们也就知道需不需要对一个给定的用户翻译,因为我们也知道这个用户选择的语言类型。当翻译被提供,用户希望看到它的时候,将会调用 Ajax 服务。最后一步就是客户端的 javascript 代码将会动态地把翻译文本插入到页面中。

显示 “翻译” 链接

接下来一步就是在 blog 旁边显示 “翻译” 链接(文件 app/templates/post_tmp.html):

<p>
    {{ post.author.nickname }}said {{ momentjs(post.timestamp).fromNow()}}: <b>{{ post.body }}</b>
    <a href="#">{{ _('Translate')}}</a>
</p>

这个链接需要我们添加一个新的翻译文本, “翻译”(‘Translate’) 是需要被包含在翻译文件里面,这里需要执行前面一章介绍的更新翻译文本的流程。

我们现在还不清楚如何触发这个翻译,因此现在链接不会做任何事情。

翻译服务

在我们的应用能够使用实时翻译之前,我们需要找到一个可用的服务。

现在有很多可用的翻译服务,但是很多是需要收费的。

在这里将使用百度的翻译服务,目前百度的翻译服务,每月在200万字以内免费。

使用百度翻译服务

为了使用百度翻译服务,这里有一些流程需要完成:

  • 应用的开发者需要在百度大脑上注册百度翻译应用(http://fanyi-api.baidu.com/api/trans/product/index

  • 接着开者需要注册。注册用将会得客 ID 以及客端密些用于求的一部分。

一旦注册部分完成,接下来处理请求翻译的步骤如下:

  • 入客 ID 和客端密钥,

  • 通过http协议访问百度提高的翻译功能,完成文本的翻译。

这听起来很复杂,因此如果不需要关注细节的话,这里有一个做了很多“脏”工作并且把文本翻译成别的语言的函数(文件 app/controllertranslate.py):

import urllib
import hashlib
import random
import http
from app importapp

def translate(text, destLang='zh'):
    appid = app.config['BAIDU_APP_ID']
    secretKey = app.config['BAIDU_APP_KEY']
    res_value = ''
   
httpClient = None
   
myurl = '/api/trans/vip/translate'
   
q = text
    fromLang = 'auto'
   
toLang = destLang
    salt = random.randint(32768, 65536)

    sign = appid + q + str(salt) + secretKey
    m1 = hashlib.md5()
    m1.update(sign.encode('utf-8'))
    sign = m1.hexdigest()
    myurl = myurl + '?appid=' + appid + '&q='+ urllib.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)

        #responseHTTPResponse对象
       
response =httpClient.getresponse()
        results = response.read().decode('utf-8').split(':')[5]
        result = results.split('}')[0]
        res_value = result.encode('latin-1').decode('unicode_escape')

    except Exception as e:
        print(e)

    finally:
        if httpClient:
            httpClient.close()

        returnres_value

 

这个函数从我们的配置文件中导入了两个新值,id 和密钥代码(文件 config.py):

BAIDU_APP_ID = '' # enter your app id here

BAIDU_APP_KEY = '' # enter your app secret here

上面的 ID 和密钥代码是需要自己去申请,步骤上面已经讲了。即使你只希望测试应用程序,你也能免费地注册这项服务。

因为我们又添加了新的文本,这些也是需要翻译的,请重新运行 tr_update.pypoedit  tr_compile.py 来更新翻译的文件。

让我们翻译一些文本

因此我们该怎样使用翻译服务了?这实际上很简单。这是例子:

from app.controller.translate import translate
res = translate('Today is a beautiful day!', 'zh')
print(res)

 

执行结果:

"今天是美好的一天!"

服务器上的 Ajax

现在我们可以在多种语言之间翻译文本,因此我们准备把这个功能整合到我们应用程序中。

当用户点击 blog 旁的 “翻译” 链接的时候,会有一个 Ajax 调用发送到我们服务器上。我们将看看这个调用是如何生产的,现在让我们集中精力实现服务器端的 Ajax 调用。

服务器上的 Ajax 服务像一个常规的视图函数,不同的是不返回一个HTML 页面或者重定向,它返回的是数据,典型的格式化成 XML 或者 JSON。因为 JSON 对 Javascript 比较友好,我们将使用这种格式(文件 app/views/microblogviews.py):

from app.controller.translateimport translate
from flask importjsonify

 

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

 

这里没有多少新内容。这个路由处理一个携带要翻译的文本以及原语言类型和要翻译的语言类型的 POST 请求。因为这是个 POST 请求,我们获取的是输入到 HTML 表单中的数据,请直接使用 request.form 字典。我们用这些数据调用我们的一个翻译函数,一旦我们获取翻译的文本就用Flask 的 jsonify 函数把它转换成 JSON。客户端看到的这个请求响应的数据类似这个格式:

{"text":"<translated text goes here>" }

端上的 Ajax

现在我们需要从网页浏览器上调用 Ajax 视图函数,因为我们需要回到 post.html 子模板来完成我们最后的工作。

首先我们需要在模版中加入一个有唯一 id 的 span 元素,以便我们在 DOM 中可以找到它并且替换成翻译的文本(文件 app/templates/post.html):

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

同样,我们需要给一个 “翻译” 链接一个唯一的 id,以保证一旦翻译显示我们能隐藏这个链接:

<span id="translation{{post.id }}">
    <a href="#">
        {{ _('Translate') }}
    </a>
</span>

目前我们有一个名为 post<id> 的元素,它包含要翻译的文本,还有一个名为 translation<id> 的元素,它包含一个 “翻译” 链接但是不久就会被翻译后的文本代替,也有一个 id 为 loading<id> 的图片,它将会在翻译服务工作的时候显示。

现在我们需要在 “链接” 链接点击的时候触发 Ajax。与直接从链接上触发调用相反,我们将会创建一个Javascript 函数,这个函数做了所有工作,因为我们有一些事情在那里做并且不希望在每个模板中重复代码。让我们添加一个对这个函数的调用当“翻译” 链接被点击的时候:

<a href="javascript:translate('{{ g.locale }}', '#post{{post.id }}', '#translation{{ post.id }}');">
    {{ _('Translate') }}
</a>

变量看起来有些多,但是函数调用很简单。假设有一篇 id 为 23,使用英语写的 blog,用户想要翻译成中文。这个函数的调用如下:

translate('zh','#post23','#translation23','#loading23')

最后我们需要实现的 translate(),我们将不会在 post.html 子模板中编写这个函数,因为每一篇 blog 内容会有些重复。我们将会在基础模版中实现这个函数,下面就是这个函数(文件 app/templates/base.html):

<script>
    function translate(destLang,sourceId, destId) {
        $(destId).hide();
        $.post('/translate',{
            text:$(sourceId).text(),
            destLang:destLang
        }).done(function (translated) {
            $(destId).text(translated['text'])
            $(destId).show();
        }).fail(function () {
            $(destId).text("{{_('Error: Could not contact server.') }}");
            $(destId).show();
        });
    }
</script>

 

这段代码依赖于 jQuery,需要详细了解上述几个函数的话,请查看 jQuery

最后,为了能够使用多种目标语言的翻译,我们需要将浏览器的语言设置保存在g中。

@babel.localeselector
def get_locale():
    language =request.accept_languages.best_match(app.config['LANGUAGES'].keys())
    g.locale = language
    return language
    #return'zh'

结束语

如果你想要节省时间的话,你可以到https://github.com/weizy1981/microblog下载。我希望能在下一章继续见到各位!


ios大赏通道



以上是关于第十五章 Ajax的主要内容,如果未能解决你的问题,请参考以下文章

WPF学习第十五章 WPF事件

C++ Primer Plus学习:第十五章

java:第十五章

C和指针 第十五章 错误报告perror和exit

Java(第十五章)

java(第十五章)