diff --git a/app/models.py b/app/models.py index 5bf39b9..1e6426e 100644 --- a/app/models.py +++ b/app/models.py @@ -119,6 +119,7 @@ class Post(db.Model): index=True, default=lambda: datetime.now(timezone.utc)) user_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey(User.id), index=True) + language: so.Mapped[Optional[str]] = so.mapped_column(sa.String(5)) author: so.Mapped[User] = so.relationship(back_populates='posts') diff --git a/app/routes.py b/app/routes.py index fe87a63..0888bb0 100644 --- a/app/routes.py +++ b/app/routes.py @@ -4,11 +4,13 @@ from flask import render_template, flash, redirect, url_for, request, g from flask_login import login_user, logout_user, current_user, login_required from flask_babel import _, get_locale import sqlalchemy as sa +from langdetect import detect, LangDetectException from app import app, db from app.forms import LoginForm, RegistrationForm, EditProfileForm, \ EmptyForm, PostForm, ResetPasswordRequestForm, ResetPasswordForm from app.models import User, Post from app.email import send_password_reset_email +from app.translate import translate @app.before_request @@ -25,7 +27,12 @@ def before_request(): def index(): form = PostForm() if form.validate_on_submit(): - post = Post(body=form.post.data, author=current_user) + try: + language = detect(form.post.data) + except LangDetectException: + language = '' + post = Post(body=form.post.data, author=current_user, + language=language) db.session.add(post) db.session.commit() flash(_('Your post is now live!')) @@ -206,3 +213,12 @@ def unfollow(username): return redirect(url_for('user', username=username)) else: return redirect(url_for('index')) + + +@app.route('/translate', methods=['POST']) +@login_required +def translate_text(): + data = request.get_json() + return {'text': translate(data['text'], + data['source_language'], + data['dest_language'])} diff --git a/app/static/loading.gif b/app/static/loading.gif new file mode 100644 index 0000000..d0bce15 Binary files /dev/null and b/app/static/loading.gif differ diff --git a/app/templates/_post.html b/app/templates/_post.html index 7e05990..c1e33c0 100644 --- a/app/templates/_post.html +++ b/app/templates/_post.html @@ -14,7 +14,17 @@ {{ _('%(username)s said %(when)s', username=user_link, when=moment(post.timestamp).fromNow()) }}
- {{ post.body }} + {{ post.body }} + {% if post.language and post.language != g.locale %} +

+ + {{ _('Translate') }} + + {% endif %} diff --git a/app/templates/base.html b/app/templates/base.html index d27b49b..eb1a0f1 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -64,5 +64,22 @@ {{ moment.include_moment() }} {{ moment.lang(g.locale) }} + diff --git a/app/translate.py b/app/translate.py new file mode 100644 index 0000000..c82aa35 --- /dev/null +++ b/app/translate.py @@ -0,0 +1,21 @@ +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'], + 'Ocp-Apim-Subscription-Region': 'westus' + } + r = requests.post( + 'https://api.cognitive.microsofttranslator.com' + '/translate?api-version=3.0&from={}&to={}'.format( + source_language, dest_language), headers=auth, json=[ + {'Text': text}]) + if r.status_code != 200: + return _('Error: the translation service failed.') + return r.json()[0]['translations'][0]['text'] diff --git a/app/translations/es/LC_MESSAGES/messages.po b/app/translations/es/LC_MESSAGES/messages.po index 2e7a00f..d468175 100644 --- a/app/translations/es/LC_MESSAGES/messages.po +++ b/app/translations/es/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2017-10-03 15:49-0700\n" +"POT-Creation-Date: 2017-10-05 15:32-0700\n" "PO-Revision-Date: 2017-09-29 23:25-0700\n" "Last-Translator: FULL NAME \n" "Language: es\n" @@ -82,57 +82,65 @@ msgstr "Dí algo" msgid "Search" msgstr "Buscar" -#: app/routes.py:30 +#: app/routes.py:37 msgid "Your post is now live!" msgstr "¡Tu artículo ha sido publicado!" -#: app/routes.py:66 +#: app/routes.py:73 msgid "Invalid username or password" msgstr "Nombre de usuario o contraseña inválidos" -#: app/routes.py:92 +#: app/routes.py:99 msgid "Congratulations, you are now a registered user!" msgstr "¡Felicitaciones, ya eres un usuario registrado!" -#: app/routes.py:107 +#: app/routes.py:114 msgid "Check your email for the instructions to reset your password" msgstr "Busca en tu email las instrucciones para crear una nueva contraseña" -#: app/routes.py:124 +#: app/routes.py:131 msgid "Your password has been reset." msgstr "Tu contraseña ha sido cambiada." -#: app/routes.py:152 +#: app/routes.py:159 msgid "Your changes have been saved." msgstr "Tus cambios han sido salvados." -#: app/routes.py:157 app/templates/edit_profile.html:5 +#: app/routes.py:164 app/templates/edit_profile.html:5 msgid "Edit Profile" msgstr "Editar Perfil" -#: app/routes.py:166 app/routes.py:182 +#: app/routes.py:173 app/routes.py:189 #, python-format msgid "User %(username)s not found." msgstr "El usuario %(username)s no ha sido encontrado." -#: app/routes.py:169 +#: app/routes.py:176 msgid "You cannot follow yourself!" msgstr "¡No te puedes seguir a tí mismo!" -#: app/routes.py:173 +#: app/routes.py:180 #, python-format msgid "You are following %(username)s!" msgstr "¡Ahora estás siguiendo a %(username)s!" -#: app/routes.py:185 +#: app/routes.py:192 msgid "You cannot unfollow yourself!" msgstr "¡No te puedes dejar de seguir a tí mismo!" -#: app/routes.py:189 +#: app/routes.py:196 #, python-format msgid "You are not following %(username)s." msgstr "No estás siguiendo a %(username)s." +#: app/translate.py:10 +msgid "Error: the translation service is not configured." +msgstr "Error: el servicio de traducciones no está configurado." + +#: app/translate.py:17 +msgid "Error: the translation service failed." +msgstr "Error el servicio de traducciones ha fallado." + #: app/templates/404.html:4 msgid "Not Found" msgstr "Página No Encontrada" @@ -154,6 +162,10 @@ msgstr "El administrador ha sido notificado. ¡Lamentamos la inconveniencia!" msgid "%(username)s said %(when)s" msgstr "%(username)s dijo %(when)s" +#: app/templates/_post.html:19 +msgid "Translate" +msgstr "Traducir" + #: app/templates/base.html:4 msgid "Welcome to Microblog" msgstr "Bienvenido a Microblog" @@ -178,6 +190,10 @@ msgstr "Perfil" msgid "Logout" msgstr "Salir" +#: app/templates/base.html:73 +msgid "Error: Could not contact server." +msgstr "Error: el servidor no pudo ser contactado." + #: app/templates/index.html:5 #, python-format msgid "Hi, %(username)s!" diff --git a/config.py b/config.py index b43f74f..31ccd05 100644 --- a/config.py +++ b/config.py @@ -13,4 +13,5 @@ class Config: MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') ADMINS = ['your-email@example.com'] LANGUAGES = ['en', 'es'] + MS_TRANSLATOR_KEY = os.environ.get('MS_TRANSLATOR_KEY') POSTS_PER_PAGE = 25 diff --git a/migrations/versions/2b017edaa91f_add_language_to_posts.py b/migrations/versions/2b017edaa91f_add_language_to_posts.py new file mode 100644 index 0000000..260cbcb --- /dev/null +++ b/migrations/versions/2b017edaa91f_add_language_to_posts.py @@ -0,0 +1,32 @@ +"""add language to posts + +Revision ID: 2b017edaa91f +Revises: ae346256b650 +Create Date: 2017-10-04 22:48:34.494465 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '2b017edaa91f' +down_revision = 'ae346256b650' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('post', schema=None) as batch_op: + batch_op.add_column(sa.Column('language', sa.String(length=5), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('post', schema=None) as batch_op: + batch_op.drop_column('language') + + # ### end Alembic commands ###