ajax translations

This commit is contained in:
Miguel Grinberg 2013-02-19 22:59:54 -08:00
parent 22ba5eed17
commit 4081db19e1
11 changed files with 232 additions and 72 deletions

View File

@ -84,6 +84,7 @@ class Post(db.Model):
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
language = db.Column(db.String(5))
def __repr__(self):
return '<Post %r>' % (self.body)

BIN
app/static/img/loading.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View File

@ -15,6 +15,25 @@
<script src="/static/js/moment-{{g.locale}}.min.js"></script>
{% endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script>
function translate(sourceLang, destLang, sourceId, destId, loadingId) {
$(destId).hide();
$(loadingId).show();
$.post('/translate', {
text: $(sourceId).text(),
sourceLang: sourceLang,
destLang: destLang
}).done(function(translated) {
$(destId).text(translated['text'])
$(loadingId).hide();
$(destId).show();
}).fail(function() {
$(destId).text("{{ _('Error: Could not contact server.') }}");
$(loadingId).hide();
$(destId).show();
});
}
</script>
</head>
<body>
<div class="container">
@ -48,4 +67,3 @@
</div>
</body>
</html>

View File

@ -5,7 +5,15 @@
{% autoescape false %}
<p>{{ _('%(nickname)s said %(when)s:', nickname = '<a href="%s">%s</a>' % (url_for('user', nickname = post.author.nickname), post.author.nickname), when = momentjs(post.timestamp).fromNow()) }}</p>
{% endautoescape %}
<p><strong>{{post.body}}</strong></p>
<p><strong><span id="post{{post.id}}">{{post.body}}</span></strong></p>
{% if post.language != None and post.language != '' and post.language != g.locale %}
<div>
<span id="translation{{post.id}}">
<a href="javascript:translate('{{post.language}}', '{{g.locale}}', '#post{{post.id}}', '#translation{{post.id}}', '#loading{{post.id}}');">{{ _('Translate') }}</a>
</span>
<img id="loading{{post.id}}" style="display: none" src="/static/img/loading.gif">
</div>
{% endif %}
</td>
</tr>
</table>

56
app/translate.py Executable file
View File

@ -0,0 +1,56 @@
import urllib, httplib
import json
from app import app
from flask.ext.babel import gettext
from config import MS_TRANSLATOR_CLIENT_ID, MS_TRANSLATOR_CLIENT_SECRET
def microsoft_translate(text, sourceLang, destLang):
if MS_TRANSLATOR_CLIENT_ID == "" or MS_TRANSLATOR_CLIENT_SECRET == "":
return gettext('Error: translation service not configured.')
try:
# get access token
params = urllib.urlencode({
'client_id': MS_TRANSLATOR_CLIENT_ID,
'client_secret': MS_TRANSLATOR_CLIENT_SECRET,
'scope': 'http://api.microsofttranslator.com',
'grant_type': 'client_credentials'
})
conn = httplib.HTTPSConnection("datamarket.accesscontrol.windows.net")
conn.request("POST", "/v2/OAuth2-13", params)
response = json.loads (conn.getresponse().read())
token = response[u'access_token']
# translate
conn = httplib.HTTPConnection('api.microsofttranslator.com')
params = {
'appId': 'Bearer ' + token,
'from': sourceLang,
'to': destLang,
'text': text.encode("utf-8")
}
conn.request("GET", '/V2/Ajax.svc/Translate?' + urllib.urlencode(params))
response = json.loads("{\"response\":" + conn.getresponse().read().decode('utf-8-sig') + "}")
return response["response"]
except:
#return gettext('Error: Unexpected error.')
raise
def google_translate(text, sourceLang, destLang):
if not app.debug:
return gettext('Error: translation service not available.')
try:
params = urllib.urlencode({
'client': 't',
'text': text.encode("utf-8"),
'sl': sourceLang,
'tl': destLang,
'ie': 'UTF-8',
'oe': 'UTF-8'
})
conn = httplib.HTTPSConnection("translate.google.com")
conn.request("GET", "/translate_a/t?" + params, headers = { 'User-Agent': 'Mozilla/5.0' })
httpresponse = conn.getresponse().read().replace(",,,", ",\"\",\"\",").replace(",,", ",\"\",")
response = json.loads("{\"response\":" + httpresponse + "}")
return response["response"][0][0][0]
except:
return gettext('Error: Unexpected error.')

View File

@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2013-01-31 23:19-0800\n"
"PO-Revision-Date: 2013-01-31 23:19-0800\n"
"POT-Creation-Date: 2013-02-19 22:11-0800\n"
"PO-Revision-Date: 2013-02-19 22:12-0800\n"
"Last-Translator: Miguel Grinberg <miguel.grinberg@gmail.com>\n"
"Language-Team: es <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -34,47 +34,59 @@ msgstr ""
msgid "This nickname is already in use. Please choose another one."
msgstr "Este nombre de usuario ya esta usado. Por favor elije otro."
#: app/views.py:49
#: app/translate.py:9
msgid "Error: translation service not configured."
msgstr "Error: el servicio de traducción no está configurado."
#: app/translate.py:35 app/translate.py:55
msgid "Error: Unexpected error."
msgstr "Error: Un error inesperado ha ocurrido."
#: app/translate.py:39
msgid "Error: translation service not available."
msgstr "Error: servicio de traducción no disponible."
#: app/views.py:56
msgid "Your post is now live!"
msgstr "¡Tu artículo ha sido publicado!"
#: app/views.py:74
#: app/views.py:81
msgid "Invalid login. Please try again."
msgstr "Credenciales inválidas. Por favor intenta de nuevo."
#: app/views.py:107
#: app/views.py:114
#, python-format
msgid "User %(nickname)s not found."
msgstr "El usuario %(nickname)s no existe."
#: app/views.py:123
#: app/views.py:130
msgid "Your changes have been saved."
msgstr "Tus cambios han sido guardados."
#: app/views.py:139
#: app/views.py:146
msgid "You can't follow yourself!"
msgstr "¡No te puedes seguir a tí mismo!"
#: app/views.py:143
#: app/views.py:150
#, python-format
msgid "Cannot follow %(nickname)s."
msgstr "No se pudo seguir a %(nickname)s."
#: app/views.py:147
#: app/views.py:154
#, python-format
msgid "You are now following %(nickname)s!"
msgstr "¡Ya estás siguiendo a %(nickname)s!"
#: app/views.py:159
#: app/views.py:166
msgid "You can't unfollow yourself!"
msgstr "¡No te puedes dejar de seguir a tí mismo!"
#: app/views.py:163
#: app/views.py:170
#, python-format
msgid "Cannot unfollow %(nickname)s."
msgstr "No se pudo dejar de seguir a %(nickname)s."
#: app/views.py:167
#: app/views.py:174
#, python-format
msgid "You have stopped following %(nickname)s."
msgstr "Ya no sigues más a %(nickname)s."
@ -95,19 +107,23 @@ msgstr "Un error inesperado ha ocurrido"
msgid "The administrator has been notified. Sorry for the inconvenience!"
msgstr "El administrador ha sido notificado. ¡Lo lamento!"
#: app/templates/base.html:30
#: app/templates/base.html:31
msgid "Error: Could not contact server."
msgstr "Error: No es posible contactar al servidor."
#: app/templates/base.html:49
msgid "Home"
msgstr "Inicio"
#: app/templates/base.html:32
#: app/templates/base.html:51
msgid "Your Profile"
msgstr "Tu Perfil"
#: app/templates/base.html:33
#: app/templates/base.html:52
msgid "Logout"
msgstr "Desconectarse"
#: app/templates/base.html:38
#: app/templates/base.html:57
msgid "Search"
msgstr "Buscar"
@ -188,6 +204,10 @@ msgstr "Ingresar"
msgid "%(nickname)s said %(when)s:"
msgstr "%(nickname)s dijo %(when)s:"
#: app/templates/post.html:12
msgid "Translate"
msgstr "Traducir"
#: app/templates/search_results.html:5
#, python-format
msgid "Search results for \"%(query)s\":"

View File

@ -1,4 +1,4 @@
from flask import render_template, flash, redirect, session, url_for, request, g
from flask import render_template, flash, redirect, session, url_for, request, g, jsonify
from flask.ext.login import login_user, logout_user, current_user, login_required
from flask.ext.babel import gettext
from app import app, db, lm, oid, babel
@ -6,8 +6,9 @@ from forms import LoginForm, EditForm, PostForm, SearchForm
from models import User, ROLE_USER, ROLE_ADMIN, Post
from datetime import datetime
from emails import follower_notification
from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS
from config import LANGUAGES
from guess_language import guessLanguage
from translate import microsoft_translate
from config import POSTS_PER_PAGE, MAX_SEARCH_RESULTS, LANGUAGES
@lm.user_loader
def load_user(id):
@ -43,7 +44,13 @@ def internal_error(error):
def index(page = 1):
form = PostForm()
if form.validate_on_submit():
post = Post(body = form.post.data, timestamp = datetime.utcnow(), author = g.user)
language = guessLanguage(form.post.data)
if language == 'UNKNOWN' or len(language) > 5:
language = ''
post = Post(body = form.post.data,
timestamp = datetime.utcnow(),
author = g.user,
language = language)
db.session.add(post)
db.session.commit()
flash(gettext('Your post is now live!'))
@ -182,3 +189,12 @@ def search_results(query):
query = query,
results = results)
@app.route('/translate', methods = ['POST'])
@login_required
def translate():
return jsonify({
'text': microsoft_translate(
request.form['text'],
request.form['sourceLang'],
request.form['destLang']) })

View File

@ -30,6 +30,10 @@ LANGUAGES = {
'es': 'Español'
}
# microsoft translation service
MS_TRANSLATOR_CLIENT_ID = '' # enter your MS translator app id here
MS_TRANSLATOR_CLIENT_SECRET = '' # enter your MS translator app secret here
# administrator list
ADMINS = ['you@example.com']

View File

@ -0,0 +1,29 @@
from sqlalchemy import *
from migrate import *
from migrate.changeset import schema
pre_meta = MetaData()
post_meta = MetaData()
post = Table('post', post_meta,
Column('id', Integer, primary_key=True, nullable=False),
Column('body', String(length=140)),
Column('timestamp', DateTime),
Column('user_id', Integer),
Column('language', String(length=5)),
)
def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine; bind
# migrate_engine to your metadata
pre_meta.bind = migrate_engine
post_meta.bind = migrate_engine
post_meta.tables['post'].columns['language'].create()
def downgrade(migrate_engine):
# Operations to reverse the above upgrade go here.
pre_meta.bind = migrate_engine
post_meta.bind = migrate_engine
post_meta.tables['post'].columns['language'].drop()

View File

@ -18,4 +18,5 @@ subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'sqlalchemy-migra
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-whooshalchemy'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-wtf'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flask-babel'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'guess-language'])
subprocess.call([os.path.join('flask', bin, 'pip'), 'install', 'flup'])

View File

@ -1,4 +1,6 @@
#!flask/bin/python
# -*- coding: utf8 -*-
import os
import unittest
from datetime import datetime, timedelta
@ -6,6 +8,7 @@ from datetime import datetime, timedelta
from config import basedir
from app import app, db
from app.models import User, Post
from app.translate import microsoft_translate
class TestCase(unittest.TestCase):
def setUp(self):
@ -113,5 +116,9 @@ class TestCase(unittest.TestCase):
assert f3 == [p4, p3]
assert f4 == [p4]
def test_translation(self):
assert microsoft_translate(u'English', 'en', 'es') == u'Inglés'
assert microsoft_translate(u'Español', 'es', 'en') == u'Spanish'
if __name__ == '__main__':
unittest.main()