diff --git a/app/models.py b/app/models.py index 63c35be..3ba4588 100644 --- a/app/models.py +++ b/app/models.py @@ -73,7 +73,7 @@ class User(db.Model): def followed_posts(self): return Post.query.join(followers, (followers.c.followed_id == Post.user_id)).filter(followers.c.follower_id == self.id).order_by(Post.timestamp.desc()) - + def __repr__(self): return '' % (self.nickname) @@ -84,7 +84,8 @@ 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 '' % (self.body) diff --git a/app/static/img/loading.gif b/app/static/img/loading.gif new file mode 100755 index 0000000..d0bce15 Binary files /dev/null and b/app/static/img/loading.gif differ diff --git a/app/templates/base.html b/app/templates/base.html index 92497a3..358c0e3 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -15,6 +15,25 @@ {% endif %} +
@@ -48,4 +67,3 @@
- diff --git a/app/templates/post.html b/app/templates/post.html index 52b683a..3010746 100644 --- a/app/templates/post.html +++ b/app/templates/post.html @@ -5,7 +5,15 @@ {% autoescape false %}

{{ _('%(nickname)s said %(when)s:', nickname = '%s' % (url_for('user', nickname = post.author.nickname), post.author.nickname), when = momentjs(post.timestamp).fromNow()) }}

{% endautoescape %} -

{{post.body}}

+

{{post.body}}

+ {% if post.language != None and post.language != '' and post.language != g.locale %} +
+ + {{ _('Translate') }} + + +
+ {% endif %} diff --git a/app/translate.py b/app/translate.py new file mode 100755 index 0000000..5c3ea2e --- /dev/null +++ b/app/translate.py @@ -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.') diff --git a/app/translations/es/LC_MESSAGES/messages.po b/app/translations/es/LC_MESSAGES/messages.po index b7d1e30..8865547 100644 --- a/app/translations/es/LC_MESSAGES/messages.po +++ b/app/translations/es/LC_MESSAGES/messages.po @@ -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 \n" "Language-Team: es \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\":" diff --git a/app/views.py b/app/views.py index d55166f..1e405d2 100644 --- a/app/views.py +++ b/app/views.py @@ -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']) }) + diff --git a/config.py b/config.py index 20bb806..ec41171 100644 --- a/config.py +++ b/config.py @@ -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'] diff --git a/db_repository/versions/005_migration.py b/db_repository/versions/005_migration.py new file mode 100644 index 0000000..0ffefa2 --- /dev/null +++ b/db_repository/versions/005_migration.py @@ -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() diff --git a/setup.py b/setup.py index 96fa057..9827752 100755 --- a/setup.py +++ b/setup.py @@ -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']) diff --git a/tests.py b/tests.py index c15e5ab..2e60762 100755 --- a/tests.py +++ b/tests.py @@ -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): @@ -64,54 +67,58 @@ class TestCase(unittest.TestCase): assert u1.followed.count() == 0 assert u2.followers.count() == 0 - def test_follow_posts(self): - # make four users - u1 = User(nickname = 'john', email = 'john@example.com') - u2 = User(nickname = 'susan', email = 'susan@example.com') - u3 = User(nickname = 'mary', email = 'mary@example.com') - u4 = User(nickname = 'david', email = 'david@example.com') - db.session.add(u1) - db.session.add(u2) - db.session.add(u3) - db.session.add(u4) - # make four posts - utcnow = datetime.utcnow() - p1 = Post(body = "post from john", author = u1, timestamp = utcnow + timedelta(seconds = 1)) - p2 = Post(body = "post from susan", author = u2, timestamp = utcnow + timedelta(seconds = 2)) - p3 = Post(body = "post from mary", author = u3, timestamp = utcnow + timedelta(seconds = 3)) - p4 = Post(body = "post from david", author = u4, timestamp = utcnow + timedelta(seconds = 4)) - db.session.add(p1) - db.session.add(p2) - db.session.add(p3) - db.session.add(p4) - db.session.commit() - # setup the followers - u1.follow(u1) # john follows himself - u1.follow(u2) # john follows susan - u1.follow(u4) # john follows david - u2.follow(u2) # susan follows herself - u2.follow(u3) # susan follows mary - u3.follow(u3) # mary follows herself - u3.follow(u4) # mary follows david - u4.follow(u4) # david follows himself - db.session.add(u1) - db.session.add(u2) - db.session.add(u3) - db.session.add(u4) - db.session.commit() - # check the followed posts of each user - f1 = u1.followed_posts().all() - f2 = u2.followed_posts().all() - f3 = u3.followed_posts().all() - f4 = u4.followed_posts().all() - assert len(f1) == 3 - assert len(f2) == 2 - assert len(f3) == 2 - assert len(f4) == 1 - assert f1 == [p4, p2, p1] - assert f2 == [p3, p2] - assert f3 == [p4, p3] - assert f4 == [p4] + def test_follow_posts(self): + # make four users + u1 = User(nickname = 'john', email = 'john@example.com') + u2 = User(nickname = 'susan', email = 'susan@example.com') + u3 = User(nickname = 'mary', email = 'mary@example.com') + u4 = User(nickname = 'david', email = 'david@example.com') + db.session.add(u1) + db.session.add(u2) + db.session.add(u3) + db.session.add(u4) + # make four posts + utcnow = datetime.utcnow() + p1 = Post(body = "post from john", author = u1, timestamp = utcnow + timedelta(seconds = 1)) + p2 = Post(body = "post from susan", author = u2, timestamp = utcnow + timedelta(seconds = 2)) + p3 = Post(body = "post from mary", author = u3, timestamp = utcnow + timedelta(seconds = 3)) + p4 = Post(body = "post from david", author = u4, timestamp = utcnow + timedelta(seconds = 4)) + db.session.add(p1) + db.session.add(p2) + db.session.add(p3) + db.session.add(p4) + db.session.commit() + # setup the followers + u1.follow(u1) # john follows himself + u1.follow(u2) # john follows susan + u1.follow(u4) # john follows david + u2.follow(u2) # susan follows herself + u2.follow(u3) # susan follows mary + u3.follow(u3) # mary follows herself + u3.follow(u4) # mary follows david + u4.follow(u4) # david follows himself + db.session.add(u1) + db.session.add(u2) + db.session.add(u3) + db.session.add(u4) + db.session.commit() + # check the followed posts of each user + f1 = u1.followed_posts().all() + f2 = u2.followed_posts().all() + f3 = u3.followed_posts().all() + f4 = u4.followed_posts().all() + assert len(f1) == 3 + assert len(f2) == 2 + assert len(f3) == 2 + assert len(f4) == 1 + assert f1 == [p4, p2, p1] + assert f2 == [p3, p2] + 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() \ No newline at end of file