From 8a56c34ce2b8831e9b3697a9884fae476771f1b6 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 30 Sep 2017 00:21:17 -0700 Subject: [PATCH] Chapter 13: I18n and L10n (v0.13) --- app/__init__.py | 11 +- app/cli.py | 38 +++ app/email.py | 3 +- app/forms.py | 48 ++-- app/routes.py | 44 ++-- app/templates/404.html | 4 +- app/templates/500.html | 6 +- app/templates/_post.html | 11 +- app/templates/base.html | 13 +- app/templates/edit_profile.html | 2 +- app/templates/index.html | 6 +- app/templates/login.html | 8 +- app/templates/register.html | 2 +- app/templates/reset_password.html | 2 +- app/templates/reset_password_request.html | 2 +- app/templates/user.html | 16 +- app/translations/es/LC_MESSAGES/messages.po | 259 ++++++++++++++++++++ babel.cfg | 3 + config.py | 1 + microblog.py | 2 +- 20 files changed, 402 insertions(+), 79 deletions(-) create mode 100644 app/cli.py create mode 100644 app/translations/es/LC_MESSAGES/messages.po create mode 100644 babel.cfg diff --git a/app/__init__.py b/app/__init__.py index 3ed606d..0a6b530 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,13 +1,14 @@ import logging from logging.handlers import SMTPHandler, RotatingFileHandler import os -from flask import Flask +from flask import Flask, request from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_login import LoginManager from flask_mail import Mail from flask_bootstrap import Bootstrap from flask_moment import Moment +from flask_babel import Babel, lazy_gettext as _l from config import Config app = Flask(__name__) @@ -16,9 +17,11 @@ db = SQLAlchemy(app) migrate = Migrate(app, db) login = LoginManager(app) login.login_view = 'login' +login.login_message = _l('Please log in to access this page.') mail = Mail(app) bootstrap = Bootstrap(app) moment = Moment(app) +babel = Babel(app) if not app.debug: if app.config['MAIL_SERVER']: @@ -48,4 +51,10 @@ if not app.debug: app.logger.setLevel(logging.INFO) app.logger.info('Microblog startup') + +@babel.localeselector +def get_locale(): + return request.accept_languages.best_match(app.config['LANGUAGES']) + + from app import routes, models, errors diff --git a/app/cli.py b/app/cli.py new file mode 100644 index 0000000..8c6697a --- /dev/null +++ b/app/cli.py @@ -0,0 +1,38 @@ +import os +import click +from app import app + + +@app.cli.group() +def translate(): + """Translation and localization commands.""" + pass + + +@translate.command() +@click.argument('lang') +def init(lang): + """Initialize a new language.""" + if os.system('pybabel extract -F babel.cfg -k _l -o messages.pot .'): + raise RuntimeError('extract command failed') + if os.system( + 'pybabel init -i messages.pot -d app/translations -l ' + lang): + raise RuntimeError('init command failed') + os.remove('messages.pot') + + +@translate.command() +def update(): + """Update all languages.""" + if os.system('pybabel extract -F babel.cfg -k _l -o messages.pot .'): + raise RuntimeError('extract command failed') + if os.system('pybabel update -i messages.pot -d app/translations'): + raise RuntimeError('update command failed') + os.remove('messages.pot') + + +@translate.command() +def compile(): + """Compile all languages.""" + if os.system('pybabel compile -d app/translations'): + raise RuntimeError('compile command failed') diff --git a/app/email.py b/app/email.py index fc7f929..1f4d46f 100644 --- a/app/email.py +++ b/app/email.py @@ -1,6 +1,7 @@ from threading import Thread from flask import render_template from flask_mail import Message +from flask_babel import _ from app import app, mail @@ -18,7 +19,7 @@ def send_email(subject, sender, recipients, text_body, html_body): def send_password_reset_email(user): token = user.get_reset_password_token() - send_email('[Microblog] Reset Your Password', + send_email(_('[Microblog] Reset Your Password'), sender=app.config['ADMINS'][0], recipients=[user.email], text_body=render_template('email/reset_password.txt', diff --git a/app/forms.py b/app/forms.py index 85b10af..76a3901 100644 --- a/app/forms.py +++ b/app/forms.py @@ -3,51 +3,55 @@ from wtforms import StringField, PasswordField, BooleanField, SubmitField, \ TextAreaField from wtforms.validators import ValidationError, DataRequired, Email, EqualTo, \ Length +from flask_babel import _, lazy_gettext as _l from app.models import User class LoginForm(FlaskForm): - username = StringField('Username', validators=[DataRequired()]) - password = PasswordField('Password', validators=[DataRequired()]) - remember_me = BooleanField('Remember Me') - submit = SubmitField('Sign In') + username = StringField(_l('Username'), validators=[DataRequired()]) + password = PasswordField(_l('Password'), validators=[DataRequired()]) + remember_me = BooleanField(_l('Remember Me')) + submit = SubmitField(_l('Sign In')) class RegistrationForm(FlaskForm): - username = StringField('Username', validators=[DataRequired()]) - email = StringField('Email', validators=[DataRequired(), Email()]) - password = PasswordField('Password', validators=[DataRequired()]) + username = StringField(_l('Username'), validators=[DataRequired()]) + email = StringField(_l('Email'), validators=[DataRequired(), Email()]) + password = PasswordField(_l('Password'), validators=[DataRequired()]) password2 = PasswordField( - 'Repeat Password', validators=[DataRequired(), EqualTo('password')]) - submit = SubmitField('Register') + _l('Repeat Password'), validators=[DataRequired(), + EqualTo('password')]) + submit = SubmitField(_l('Register')) def validate_username(self, username): user = User.query.filter_by(username=username.data).first() if user is not None: - raise ValidationError('Please use a different username.') + raise ValidationError(_('Please use a different username.')) def validate_email(self, email): user = User.query.filter_by(email=email.data).first() if user is not None: - raise ValidationError('Please use a different email address.') + raise ValidationError(_('Please use a different email address.')) class ResetPasswordRequestForm(FlaskForm): - email = StringField('Email', validators=[DataRequired(), Email()]) - submit = SubmitField('Request Password Reset') + email = StringField(_l('Email'), validators=[DataRequired(), Email()]) + submit = SubmitField(_l('Request Password Reset')) class ResetPasswordForm(FlaskForm): - password = PasswordField('Password', validators=[DataRequired()]) + password = PasswordField(_l('Password'), validators=[DataRequired()]) password2 = PasswordField( - 'Repeat Password', validators=[DataRequired(), EqualTo('password')]) - submit = SubmitField('Request Password Reset') + _l('Repeat Password'), validators=[DataRequired(), + EqualTo('password')]) + submit = SubmitField(_l('Request Password Reset')) class EditProfileForm(FlaskForm): - username = StringField('Username', validators=[DataRequired()]) - about_me = TextAreaField('About me', validators=[Length(min=0, max=140)]) - submit = SubmitField('Submit') + username = StringField(_l('Username'), validators=[DataRequired()]) + about_me = TextAreaField(_l('About me'), + validators=[Length(min=0, max=140)]) + submit = SubmitField(_l('Submit')) def __init__(self, original_username, *args, **kwargs): super(EditProfileForm, self).__init__(*args, **kwargs) @@ -57,7 +61,7 @@ class EditProfileForm(FlaskForm): if username.data != self.original_username: user = User.query.filter_by(username=self.username.data).first() if user is not None: - raise ValidationError('Please use a different username.') + raise ValidationError(_('Please use a different username.')) class EmptyForm(FlaskForm): @@ -65,5 +69,5 @@ class EmptyForm(FlaskForm): class PostForm(FlaskForm): - post = TextAreaField('Say something', validators=[DataRequired()]) - submit = SubmitField('Submit') + post = TextAreaField(_l('Say something'), validators=[DataRequired()]) + submit = SubmitField(_l('Submit')) diff --git a/app/routes.py b/app/routes.py index 4d26806..c4e9461 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,7 +1,8 @@ from datetime import datetime -from flask import render_template, flash, redirect, url_for, request +from flask import render_template, flash, redirect, url_for, request, g from flask_login import login_user, logout_user, current_user, login_required from werkzeug.urls import url_parse +from flask_babel import _, get_locale from app import app, db from app.forms import LoginForm, RegistrationForm, EditProfileForm, \ EmptyForm, PostForm, ResetPasswordRequestForm, ResetPasswordForm @@ -14,6 +15,7 @@ def before_request(): if current_user.is_authenticated: current_user.last_seen = datetime.utcnow() db.session.commit() + g.locale = str(get_locale()) @app.route('/', methods=['GET', 'POST']) @@ -25,7 +27,7 @@ def index(): post = Post(body=form.post.data, author=current_user) db.session.add(post) db.session.commit() - flash('Your post is now live!') + flash(_('Your post is now live!')) return redirect(url_for('index')) page = request.args.get('page', 1, type=int) posts = current_user.followed_posts().paginate( @@ -34,7 +36,7 @@ def index(): if posts.has_next else None prev_url = url_for('index', page=posts.prev_num) \ if posts.has_prev else None - return render_template('index.html', title='Home', form=form, + return render_template('index.html', title=_('Home'), form=form, posts=posts.items, next_url=next_url, prev_url=prev_url) @@ -49,8 +51,9 @@ def explore(): if posts.has_next else None prev_url = url_for('explore', page=posts.prev_num) \ if posts.has_prev else None - return render_template('index.html', title='Explore', posts=posts.items, - next_url=next_url, prev_url=prev_url) + return render_template('index.html', title=_('Explore'), + posts=posts.items, next_url=next_url, + prev_url=prev_url) @app.route('/login', methods=['GET', 'POST']) @@ -61,14 +64,14 @@ def login(): if form.validate_on_submit(): user = User.query.filter_by(username=form.username.data).first() if user is None or not user.check_password(form.password.data): - flash('Invalid username or password') + flash(_('Invalid username or password')) return redirect(url_for('login')) login_user(user, remember=form.remember_me.data) next_page = request.args.get('next') if not next_page or url_parse(next_page).netloc != '': next_page = url_for('index') return redirect(next_page) - return render_template('login.html', title='Sign In', form=form) + return render_template('login.html', title=_('Sign In'), form=form) @app.route('/logout') @@ -87,9 +90,9 @@ def register(): user.set_password(form.password.data) db.session.add(user) db.session.commit() - flash('Congratulations, you are now a registered user!') + flash(_('Congratulations, you are now a registered user!')) return redirect(url_for('login')) - return render_template('register.html', title='Register', form=form) + return render_template('register.html', title=_('Register'), form=form) @app.route('/reset_password_request', methods=['GET', 'POST']) @@ -101,10 +104,11 @@ def reset_password_request(): user = User.query.filter_by(email=form.email.data).first() if user: send_password_reset_email(user) - flash('Check your email for the instructions to reset your password') + flash( + _('Check your email for the instructions to reset your password')) return redirect(url_for('login')) return render_template('reset_password_request.html', - title='Reset Password', form=form) + title=_('Reset Password'), form=form) @app.route('/reset_password/', methods=['GET', 'POST']) @@ -118,7 +122,7 @@ def reset_password(token): if form.validate_on_submit(): user.set_password(form.password.data) db.session.commit() - flash('Your password has been reset.') + flash(_('Your password has been reset.')) return redirect(url_for('login')) return render_template('reset_password.html', form=form) @@ -147,12 +151,12 @@ def edit_profile(): current_user.username = form.username.data current_user.about_me = form.about_me.data db.session.commit() - flash('Your changes have been saved.') + flash(_('Your changes have been saved.')) return redirect(url_for('edit_profile')) elif request.method == 'GET': form.username.data = current_user.username form.about_me.data = current_user.about_me - return render_template('edit_profile.html', title='Edit Profile', + return render_template('edit_profile.html', title=_('Edit Profile'), form=form) @@ -163,14 +167,14 @@ def follow(username): if form.validate_on_submit(): user = User.query.filter_by(username=username).first() if user is None: - flash('User {} not found.'.format(username)) + flash(_('User %(username)s not found.', username=username)) return redirect(url_for('index')) if user == current_user: - flash('You cannot follow yourself!') + flash(_('You cannot follow yourself!')) return redirect(url_for('user', username=username)) current_user.follow(user) db.session.commit() - flash('You are following {}!'.format(username)) + flash(_('You are following %(username)s!', username=username)) return redirect(url_for('user', username=username)) else: return redirect(url_for('index')) @@ -183,14 +187,14 @@ def unfollow(username): if form.validate_on_submit(): user = User.query.filter_by(username=username).first() if user is None: - flash('User {} not found.'.format(username)) + flash(_('User %(username)s not found.', username=username)) return redirect(url_for('index')) if user == current_user: - flash('You cannot unfollow yourself!') + flash(_('You cannot unfollow yourself!')) return redirect(url_for('user', username=username)) current_user.unfollow(user) db.session.commit() - flash('You are not following {}.'.format(username)) + flash(_('You are not following %(username)s.', username=username)) return redirect(url_for('user', username=username)) else: return redirect(url_for('index')) diff --git a/app/templates/404.html b/app/templates/404.html index f2e1cee..adc4da2 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% block app_content %} -

Not Found

-

Back

+

{{ _('Not Found') }}

+

{{ _('Back') }}

{% endblock %} diff --git a/app/templates/500.html b/app/templates/500.html index 2e8a06e..5975fde 100644 --- a/app/templates/500.html +++ b/app/templates/500.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% block app_content %} -

An unexpected error has occurred

-

The administrator has been notified. Sorry for the inconvenience!

-

Back

+

{{ _('An unexpected error has occurred') }}

+

{{ _('The administrator has been notified. Sorry for the inconvenience!') }}

+

{{ _('Back') }}

{% endblock %} diff --git a/app/templates/_post.html b/app/templates/_post.html index b5b6022..7e05990 100644 --- a/app/templates/_post.html +++ b/app/templates/_post.html @@ -6,10 +6,13 @@ - - {{ post.author.username }} - - said {{ moment(post.timestamp).fromNow() }}: + {% set user_link %} + + {{ post.author.username }} + + {% endset %} + {{ _('%(username)s said %(when)s', + username=user_link, when=moment(post.timestamp).fromNow()) }}
{{ post.body }} diff --git a/app/templates/base.html b/app/templates/base.html index afb8e94..157e81e 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,7 +1,7 @@ {% extends 'bootstrap/base.html' %} {% block title %} - {% if title %}{{ title }} - Microblog{% else %}Welcome to Microblog{% endif %} + {% if title %}{{ title }} - Microblog{% else %}{{ _('Welcome to Microblog') }}{% endif %} {% endblock %} {% block navbar %} @@ -18,15 +18,15 @@ @@ -52,4 +52,5 @@ {% block scripts %} {{ super() }} {{ moment.include_moment() }} + {{ moment.lang(g.locale) }} {% endblock %} diff --git a/app/templates/edit_profile.html b/app/templates/edit_profile.html index 72cacda..38aecf6 100644 --- a/app/templates/edit_profile.html +++ b/app/templates/edit_profile.html @@ -2,7 +2,7 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Edit Profile

+

{{ _('Edit Profile') }}

{{ wtf.quick_form(form) }} diff --git a/app/templates/index.html b/app/templates/index.html index baf91d8..ea9b4dd 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -2,7 +2,7 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Hi, {{ current_user.username }}!

+

{{ _('Hi, %(username)s!', username=current_user.username) }}

{% if form %} {{ wtf.quick_form(form) }}
@@ -14,12 +14,12 @@ diff --git a/app/templates/login.html b/app/templates/login.html index 5b8d1b8..56b4c03 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -2,16 +2,16 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Sign In

+

{{ _('Sign In') }}

{{ wtf.quick_form(form) }}

-

New User? Click to Register!

+

{{ _('New User?') }} {{ _('Click to Register!') }}

- Forgot Your Password? - Click to Reset It + {{ _('Forgot Your Password?') }} + {{ _('Click to Reset It') }}

{% endblock %} diff --git a/app/templates/register.html b/app/templates/register.html index c87955c..ec7f4b6 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -2,7 +2,7 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Register

+

{{ _('Register') }}

{{ wtf.quick_form(form) }} diff --git a/app/templates/reset_password.html b/app/templates/reset_password.html index 19d5bbf..f85a7b3 100644 --- a/app/templates/reset_password.html +++ b/app/templates/reset_password.html @@ -2,7 +2,7 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Reset Your Password

+

{{ _('Reset Your Password') }}

{{ wtf.quick_form(form) }} diff --git a/app/templates/reset_password_request.html b/app/templates/reset_password_request.html index a5f7d22..32206e8 100644 --- a/app/templates/reset_password_request.html +++ b/app/templates/reset_password_request.html @@ -2,7 +2,7 @@ {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} -

Reset Password

+

{{ _('Reset Password') }}

{{ wtf.quick_form(form) }} diff --git a/app/templates/user.html b/app/templates/user.html index 76dfd60..34d807e 100644 --- a/app/templates/user.html +++ b/app/templates/user.html @@ -5,26 +5,26 @@ -

User: {{ user.username }}

+

{{ _('User') }}: {{ user.username }}

{% if user.about_me %}

{{ user.about_me }}

{% endif %} {% if user.last_seen %} -

Last seen on: {{ moment(user.last_seen).format('LLL') }}

+

{{ _('Last seen on') }}: {{ moment(user.last_seen).format('LLL') }}

{% endif %} -

{{ user.followers.count() }} followers, {{ user.followed.count() }} following.

+

{{ _('%(count)d followers', count=user.followers.count()) }}, {{ _('%(count)d following', count=user.followed.count()) }}

{% if user == current_user %} -

Edit your profile

+

{{ _('Edit your profile') }}

{% elif not current_user.is_following(user) %}

{{ form.hidden_tag() }} - {{ form.submit(value='Follow', class_='btn btn-default') }} + {{ form.submit(value=_('Follow'), class_='btn btn-default') }}

{% else %}

{{ form.hidden_tag() }} - {{ form.submit(value='Unfollow', class_='btn btn-default') }} + {{ form.submit(value=_('Unfollow'), class_='btn btn-default') }}

{% endif %} @@ -38,12 +38,12 @@ diff --git a/app/translations/es/LC_MESSAGES/messages.po b/app/translations/es/LC_MESSAGES/messages.po new file mode 100644 index 0000000..2e7a00f --- /dev/null +++ b/app/translations/es/LC_MESSAGES/messages.po @@ -0,0 +1,259 @@ +# Spanish translations for PROJECT. +# Copyright (C) 2017 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2017. +# +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" +"PO-Revision-Date: 2017-09-29 23:25-0700\n" +"Last-Translator: FULL NAME \n" +"Language: es\n" +"Language-Team: es \n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 2.5.1\n" + +#: app/__init__.py:20 +msgid "Please log in to access this page." +msgstr "Por favor ingrese para acceder a esta página." + +#: app/email.py:21 +msgid "[Microblog] Reset Your Password" +msgstr "[Microblog] Nueva Contraseña" + +#: app/forms.py:12 app/forms.py:19 app/forms.py:50 +msgid "Username" +msgstr "Nombre de usuario" + +#: app/forms.py:13 app/forms.py:21 app/forms.py:43 +msgid "Password" +msgstr "Contraseña" + +#: app/forms.py:14 +msgid "Remember Me" +msgstr "Recordarme" + +#: app/forms.py:15 app/templates/login.html:5 +msgid "Sign In" +msgstr "Ingresar" + +#: app/forms.py:20 app/forms.py:38 +msgid "Email" +msgstr "Email" + +#: app/forms.py:23 app/forms.py:45 +msgid "Repeat Password" +msgstr "Repetir Contraseña" + +#: app/forms.py:24 app/templates/register.html:5 +msgid "Register" +msgstr "Registrarse" + +#: app/forms.py:29 app/forms.py:62 +msgid "Please use a different username." +msgstr "Por favor use un nombre de usuario diferente." + +#: app/forms.py:34 +msgid "Please use a different email address." +msgstr "Por favor use una dirección de email diferente." + +#: app/forms.py:39 app/forms.py:46 +msgid "Request Password Reset" +msgstr "Pedir una nueva contraseña" + +#: app/forms.py:51 +msgid "About me" +msgstr "Acerca de mí" + +#: app/forms.py:52 app/forms.py:67 +msgid "Submit" +msgstr "Enviar" + +#: app/forms.py:66 +msgid "Say something" +msgstr "Dí algo" + +#: app/forms.py:71 +msgid "Search" +msgstr "Buscar" + +#: app/routes.py:30 +msgid "Your post is now live!" +msgstr "¡Tu artículo ha sido publicado!" + +#: app/routes.py:66 +msgid "Invalid username or password" +msgstr "Nombre de usuario o contraseña inválidos" + +#: app/routes.py:92 +msgid "Congratulations, you are now a registered user!" +msgstr "¡Felicitaciones, ya eres un usuario registrado!" + +#: app/routes.py:107 +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 +msgid "Your password has been reset." +msgstr "Tu contraseña ha sido cambiada." + +#: app/routes.py:152 +msgid "Your changes have been saved." +msgstr "Tus cambios han sido salvados." + +#: app/routes.py:157 app/templates/edit_profile.html:5 +msgid "Edit Profile" +msgstr "Editar Perfil" + +#: app/routes.py:166 app/routes.py:182 +#, python-format +msgid "User %(username)s not found." +msgstr "El usuario %(username)s no ha sido encontrado." + +#: app/routes.py:169 +msgid "You cannot follow yourself!" +msgstr "¡No te puedes seguir a tí mismo!" + +#: app/routes.py:173 +#, python-format +msgid "You are following %(username)s!" +msgstr "¡Ahora estás siguiendo a %(username)s!" + +#: app/routes.py:185 +msgid "You cannot unfollow yourself!" +msgstr "¡No te puedes dejar de seguir a tí mismo!" + +#: app/routes.py:189 +#, python-format +msgid "You are not following %(username)s." +msgstr "No estás siguiendo a %(username)s." + +#: app/templates/404.html:4 +msgid "Not Found" +msgstr "Página No Encontrada" + +#: app/templates/404.html:5 app/templates/500.html:6 +msgid "Back" +msgstr "Atrás" + +#: app/templates/500.html:4 +msgid "An unexpected error has occurred" +msgstr "Ha ocurrido un error inesperado" + +#: app/templates/500.html:5 +msgid "The administrator has been notified. Sorry for the inconvenience!" +msgstr "El administrador ha sido notificado. ¡Lamentamos la inconveniencia!" + +#: app/templates/_post.html:9 +#, python-format +msgid "%(username)s said %(when)s" +msgstr "%(username)s dijo %(when)s" + +#: app/templates/base.html:4 +msgid "Welcome to Microblog" +msgstr "Bienvenido a Microblog" + +#: app/templates/base.html:21 +msgid "Home" +msgstr "Inicio" + +#: app/templates/base.html:22 +msgid "Explore" +msgstr "Explorar" + +#: app/templates/base.html:33 +msgid "Login" +msgstr "Ingresar" + +#: app/templates/base.html:35 +msgid "Profile" +msgstr "Perfil" + +#: app/templates/base.html:36 +msgid "Logout" +msgstr "Salir" + +#: app/templates/index.html:5 +#, python-format +msgid "Hi, %(username)s!" +msgstr "¡Hola, %(username)s!" + +#: app/templates/index.html:17 app/templates/user.html:31 +msgid "Newer posts" +msgstr "Artículos siguientes" + +#: app/templates/index.html:22 app/templates/user.html:36 +msgid "Older posts" +msgstr "Artículos previos" + +#: app/templates/login.html:12 +msgid "New User?" +msgstr "¿Usuario Nuevo?" + +#: app/templates/login.html:12 +msgid "Click to Register!" +msgstr "¡Haz click aquí para registrarte!" + +#: app/templates/login.html:14 +msgid "Forgot Your Password?" +msgstr "¿Te olvidaste tu contraseña?" + +#: app/templates/login.html:15 +msgid "Click to Reset It" +msgstr "Haz click aquí para pedir una nueva" + +#: app/templates/reset_password.html:5 +msgid "Reset Your Password" +msgstr "Nueva Contraseña" + +#: app/templates/reset_password_request.html:5 +msgid "Reset Password" +msgstr "Nueva Contraseña" + +#: app/templates/search.html:4 +msgid "Search Results" +msgstr "Resultados de Búsqueda" + +#: app/templates/search.html:12 +msgid "Previous results" +msgstr "Resultados previos" + +#: app/templates/search.html:17 +msgid "Next results" +msgstr "Resultados próximos" + +#: app/templates/user.html:8 +msgid "User" +msgstr "Usuario" + +#: app/templates/user.html:11 +msgid "Last seen on" +msgstr "Última visita" + +#: app/templates/user.html:13 +#, python-format +msgid "%(count)d followers" +msgstr "%(count)d seguidores" + +#: app/templates/user.html:13 +#, python-format +msgid "%(count)d following" +msgstr "siguiendo a %(count)d" + +#: app/templates/user.html:15 +msgid "Edit your profile" +msgstr "Editar tu perfil" + +#: app/templates/user.html:17 +msgid "Follow" +msgstr "Seguir" + +#: app/templates/user.html:19 +msgid "Unfollow" +msgstr "Dejar de seguir" + diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..c40b2e8 --- /dev/null +++ b/babel.cfg @@ -0,0 +1,3 @@ +[python: app/**.py] +[jinja2: app/templates/**.html] +extensions=jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/config.py b/config.py index d3c5435..0e4b11b 100644 --- a/config.py +++ b/config.py @@ -13,4 +13,5 @@ class Config(object): MAIL_USERNAME = os.environ.get('MAIL_USERNAME') MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') ADMINS = ['your-email@example.com'] + LANGUAGES = ['en', 'es'] POSTS_PER_PAGE = 25 diff --git a/microblog.py b/microblog.py index 3c32c5e..637d9a3 100644 --- a/microblog.py +++ b/microblog.py @@ -1,4 +1,4 @@ -from app import app, db +from app import app, db, cli from app.models import User, Post