From e07fbb0c97d354c7c802fd93975ba561d97157a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sat, 7 Mar 2020 16:55:12 +0100 Subject: [PATCH] Start translating the web portal --- .gitignore | 2 + Makefile | 15 +- babel.cfg | 3 + requirements.txt | 1 + snikket_web/__init__.py | 12 ++ snikket_web/templates/app.html | 2 +- snikket_web/templates/login.html | 16 +- snikket_web/templates/user_home.html | 15 +- snikket_web/templates/user_passwd.html | 10 +- snikket_web/templates/user_profile.html | 4 +- .../translations/de/LC_MESSAGES/messages.po | 140 ++++++++++++++++++ .../translations/en/LC_MESSAGES/messages.po | 138 +++++++++++++++++ snikket_web/user/__init__.py | 20 +-- 13 files changed, 341 insertions(+), 37 deletions(-) create mode 100644 babel.cfg create mode 100644 snikket_web/translations/de/LC_MESSAGES/messages.po create mode 100644 snikket_web/translations/en/LC_MESSAGES/messages.po diff --git a/.gitignore b/.gitignore index 0bd1163..8eb4757 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /.local __pycache__ /snikket_web/static/css/*.css +/snikket_web/translations/messages.pot +/snikket_web/translations/*/LC_MESSAGES/*.mo diff --git a/Makefile b/Makefile index ca09a10..2c37075 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,14 @@ scss_files = $(filter-out snikket_web/scss/_%.scss,$(wildcard snikket_web/scss/* scss_includes = $(filter snikket_web/scss/_%.scss,$(wildcard snikket_web/scss/*.scss)) generated_css_files = $(patsubst snikket_web/scss/%.scss,snikket_web/static/css/%.css,$(scss_files)) +translation_basepath = snikket_web/translations +pot_file = $(translation_basepath)/messages.pot + PYTHON3 ?= python3 SCSSC ?= $(PYTHON3) -m scss --load-path snikket_web/scss/ +all: build_css compile_translations + build_css: $(generated_css_files) $(generated_css_files): snikket_web/static/css/%.css: snikket_web/scss/%.scss $(scss_includes) @@ -13,4 +18,12 @@ $(generated_css_files): snikket_web/static/css/%.css: snikket_web/scss/%.scss $( clean: rm -f $(generated_css_files) -.PHONY: build_css clean +update_translations: + pybabel extract -F babel.cfg -k _l -o $(pot_file) . + pybabel update -i $(pot_file) -d $(translation_basepath) + +compile_translations: + pybabel compile -d $(translation_basepath) + + +.PHONY: build_css clean update_translations compile_translations diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..9ba026f --- /dev/null +++ b/babel.cfg @@ -0,0 +1,3 @@ +[python: snikket_web/**.py] +[jinja2: snikket_web/templates/**.html] +extensions=jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/requirements.txt b/requirements.txt index 76e75ef..9328045 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ aiohttp~=3.6 quart~=0.11 flask-wtf~=0.14 hsluv~=0.0.2 +flask-babel~=1.0 diff --git a/snikket_web/__init__.py b/snikket_web/__init__.py index 0102895..958c29c 100644 --- a/snikket_web/__init__.py +++ b/snikket_web/__init__.py @@ -10,17 +10,29 @@ from quart import ( current_app, ) +from flask_babel import Babel + from . import colour from .prosodyclient import client from ._version import version, version_info app = Quart(__name__) +app.config.setdefault("LANGUAGES", ["de", "en"]) app.config.from_envvar("SNIKKET_WEB_CONFIG") client.init_app(app) client.default_login_redirect = "login" +babel = Babel(app) + + +@babel.localeselector +def select_locale(): + return request.accept_languages.best_match( + current_app.config['LANGUAGES'] + ) + @app.route("/login", methods=["GET", "POST"]) async def login(): diff --git a/snikket_web/templates/app.html b/snikket_web/templates/app.html index 3015e5c..237a6dc 100644 --- a/snikket_web/templates/app.html +++ b/snikket_web/templates/app.html @@ -15,6 +15,6 @@
{% block content %}{% endblock %}
{% endblock %} diff --git a/snikket_web/templates/login.html b/snikket_web/templates/login.html index fdff05a..f94878b 100644 --- a/snikket_web/templates/login.html +++ b/snikket_web/templates/login.html @@ -1,7 +1,7 @@ {% extends "base.html" %} {% set body_id = "login" %} {% block head_lead %} -Snikket Web Portal +{{ _("Snikket Login") }} {% endblock %} {% block style %} @@ -10,22 +10,22 @@ {% block body %}

{{ config["SNIKKET_DOMAIN"] }}

-

Enter your Snikket address and password to manage your account.

+

{{ _("Enter your Snikket address and password to manage your account.") }}

- - + +
- - + +
- +
{% endblock %} diff --git a/snikket_web/templates/user_home.html b/snikket_web/templates/user_home.html index 3c7795b..2723a7a 100644 --- a/snikket_web/templates/user_home.html +++ b/snikket_web/templates/user_home.html @@ -1,19 +1,18 @@ {% extends "app.html" %} {% block content %} -

Welcome!

-

Welcome home, {{ user_info.display_name }}.

+

{% trans %}Welcome!{% endtrans %}

+

{% trans user_name=user_info.display_name %}Welcome home, {{ user_name }}.{% endtrans %}

-

Update profile

-

Change display name, set avatar and configure visibility of your - personal data to others.

+

{% trans %}Update profile{% endtrans %}

+

{% trans %}Change display name, set avatar and configure visibility of your personal data to others.{% endtrans %}

-

Change password

+

{% trans %}Change password{% endtrans %}

-

Log out

-

Exit the Snikket Web Portal, without logging out your other devices.

+

{% trans %}Log out{% endtrans %}

+

{% trans %}Exit the Snikket Web Portal, without logging out your other devices.{% endtrans %}

{% endblock %} diff --git a/snikket_web/templates/user_passwd.html b/snikket_web/templates/user_passwd.html index d13e472..f36f552 100644 --- a/snikket_web/templates/user_passwd.html +++ b/snikket_web/templates/user_passwd.html @@ -4,12 +4,12 @@ {% endblock %} {% block content %}
-

Change your password

-

To change your password, you need to provide the current password as well as the new one. To reduce the chance of typos, we ask for your new password twice.

+

{% trans %}Change your password{% endtrans %}

+

{% trans %}To change your password, you need to provide the current password as well as the new one. To reduce the chance of typos, we ask for your new password twice.{% endtrans %}

{{ form.csrf_token }} {% if form.errors %}
-
Password change failed
+
{% trans %}Password change failed{% endtrans %}
    {% for field, errors in form.errors.items() %} {% for error in errors %} @@ -32,8 +32,8 @@ {{ form.new_password_confirm(class=("has-error" if form.new_password_confirm.name in form.errors else "")) }}
- Back - + {% trans %}Back{% endtrans %} +
{% endblock %} diff --git a/snikket_web/templates/user_profile.html b/snikket_web/templates/user_profile.html index d790f2e..3c079cf 100644 --- a/snikket_web/templates/user_profile.html +++ b/snikket_web/templates/user_profile.html @@ -4,7 +4,7 @@ {% endblock %} {% block content %}
-

Profile

+

{% trans %}Profile{% endtrans %}

{{ form.csrf_token }}
{{ form.nickname.label }} @@ -15,7 +15,7 @@ {{ form.avatar }}
- Back + {% trans %}Back{% endtrans %}
{% endblock %} diff --git a/snikket_web/translations/de/LC_MESSAGES/messages.po b/snikket_web/translations/de/LC_MESSAGES/messages.po new file mode 100644 index 0000000..26721b8 --- /dev/null +++ b/snikket_web/translations/de/LC_MESSAGES/messages.po @@ -0,0 +1,140 @@ +# German translations for SnikketWeb. +# Copyright (C) 2020 ORGANIZATION +# This file is distributed under the same license as the SnikketWeb project. +# FIRST AUTHOR , 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: SnikketWeb 0.1.0\n" +"Report-Msgid-Bugs-To: jonas@zombofant.net\n" +"POT-Creation-Date: 2020-03-07 16:53+0100\n" +"PO-Revision-Date: 2020-03-07 16:32+0100\n" +"Last-Translator: Jonas Schäfer \n" +"Language: de\n" +"Language-Team: de \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.8.0\n" + +#: snikket_web/templates/app.html:18 snikket_web/templates/login.html:29 +#, python-format +msgid "A Snikket server" +msgstr "Ein Snikket-Server" + +#: snikket_web/templates/login.html:4 +msgid "Snikket Login" +msgstr "Snikket Anmeldung" + +#: snikket_web/templates/login.html:13 +msgid "Enter your Snikket address and password to manage your account." +msgstr "Gib deine Snikket-Adresse und -Passwort ein um dein Konto zu verwalten." + +#: snikket_web/templates/login.html:16 snikket_web/templates/login.html:17 +msgid "Address" +msgstr "Adresse" + +#: snikket_web/templates/login.html:20 snikket_web/templates/login.html:21 +msgid "Password" +msgstr "Passwort" + +#: snikket_web/templates/login.html:24 +msgid "Log in" +msgstr "Anmelden" + +#: snikket_web/templates/user_home.html:3 +msgid "Welcome!" +msgstr "Willkommen!" + +#: snikket_web/templates/user_home.html:4 +#, python-format +msgid "Welcome home, %(user_name)s." +msgstr "Willkommen zu Hause, %(user_name)s." + +#: snikket_web/templates/user_home.html:7 +msgid "Update profile" +msgstr "Profil bearbeiten" + +#: snikket_web/templates/user_home.html:8 +msgid "" +"Change display name, set avatar and configure visibility of your personal" +" data to others." +msgstr "" +"Ändere deinen Namen oder dein Bild und wer deine persönlichen Daten " +"einsehen kann." + +#: snikket_web/templates/user_home.html:11 +#: snikket_web/templates/user_passwd.html:36 +msgid "Change password" +msgstr "Passwort ändern" + +#: snikket_web/templates/user_home.html:14 +msgid "Log out" +msgstr "Abmelden" + +#: snikket_web/templates/user_home.html:15 +msgid "Exit the Snikket Web Portal, without logging out your other devices." +msgstr "" +"Verlasse das Snikket Web-Portal, ohne dass deine anderen Geräte " +"beeinträchtigt werden." + +#: snikket_web/templates/user_passwd.html:7 +msgid "Change your password" +msgstr "Ändere dein Passwort" + +#: snikket_web/templates/user_passwd.html:8 +msgid "" +"To change your password, you need to provide the current password as well" +" as the new one. To reduce the chance of typos, we ask for your new " +"password twice." +msgstr "" +"Um dein Passwort zu ändern musst du sowohl dein aktuelles Passwort " +"alsauch dein neues Passwort angeben. Damit Tippfehler dich nicht " +"aussperren bitten wir dich, dein neues Passwort zweimal einzutippen." + +#: snikket_web/templates/user_passwd.html:12 +msgid "Password change failed" +msgstr "Passwortänderung fehlgeschlagen" + +#: snikket_web/templates/user_passwd.html:35 +#: snikket_web/templates/user_profile.html:18 +msgid "Back" +msgstr "Zurück" + +#: snikket_web/templates/user_profile.html:7 +msgid "Profile" +msgstr "Profil" + +#: snikket_web/templates/user_profile.html:18 +msgid "Apply" +msgstr "Übernehmen" + +#: snikket_web/user/__init__.py:21 +msgid "Current password" +msgstr "Aktuelles Passwort" + +#: snikket_web/user/__init__.py:26 +msgid "New password" +msgstr "Neues Passwort" + +#: snikket_web/user/__init__.py:31 +msgid "Confirm new password" +msgstr "Neues Passwort (Bestätigung)" + +#: snikket_web/user/__init__.py:35 +msgid "The new passwords must match." +msgstr "Die neuen Passwörter müssen übereinstimmen." + +#: snikket_web/user/__init__.py:46 +msgid "Display name" +msgstr "Anzeigename" + +#: snikket_web/user/__init__.py:50 +msgid "Avatar" +msgstr "Bild" + +#: snikket_web/user/__init__.py:74 +msgid "Incorrect password" +msgstr "Ungültiges Passwort" + diff --git a/snikket_web/translations/en/LC_MESSAGES/messages.po b/snikket_web/translations/en/LC_MESSAGES/messages.po new file mode 100644 index 0000000..c6a9556 --- /dev/null +++ b/snikket_web/translations/en/LC_MESSAGES/messages.po @@ -0,0 +1,138 @@ +# English translations for PROJECT. +# Copyright (C) 2020 ORGANIZATION +# This file is distributed under the same license as the PROJECT project. +# FIRST AUTHOR , 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: PROJECT VERSION\n" +"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" +"POT-Creation-Date: 2020-03-07 16:53+0100\n" +"PO-Revision-Date: 2020-03-07 16:50+0100\n" +"Last-Translator: FULL NAME \n" +"Language: en\n" +"Language-Team: en \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.8.0\n" + +#: snikket_web/templates/app.html:18 snikket_web/templates/login.html:29 +#, python-format +msgid "A Snikket server" +msgstr "A Snikket server" + +#: snikket_web/templates/login.html:4 +msgid "Snikket Login" +msgstr "Snikket Login" + +#: snikket_web/templates/login.html:13 +msgid "Enter your Snikket address and password to manage your account." +msgstr "Enter your Snikket address and password to manage your account." + +#: snikket_web/templates/login.html:16 snikket_web/templates/login.html:17 +msgid "Address" +msgstr "Address" + +#: snikket_web/templates/login.html:20 snikket_web/templates/login.html:21 +msgid "Password" +msgstr "Password" + +#: snikket_web/templates/login.html:24 +msgid "Log in" +msgstr "Log in" + +#: snikket_web/templates/user_home.html:3 +msgid "Welcome!" +msgstr "Welcome!" + +#: snikket_web/templates/user_home.html:4 +#, python-format +msgid "Welcome home, %(user_name)s." +msgstr "Welcome home, %(user_name)s." + +#: snikket_web/templates/user_home.html:7 +msgid "Update profile" +msgstr "Update profile" + +#: snikket_web/templates/user_home.html:8 +msgid "" +"Change display name, set avatar and configure visibility of your personal" +" data to others." +msgstr "" +"Change display name, set avatar and configure visibility of your personal" +" data to others." + +#: snikket_web/templates/user_home.html:11 +#: snikket_web/templates/user_passwd.html:36 +msgid "Change password" +msgstr "Change password" + +#: snikket_web/templates/user_home.html:14 +msgid "Log out" +msgstr "Log out" + +#: snikket_web/templates/user_home.html:15 +msgid "Exit the Snikket Web Portal, without logging out your other devices." +msgstr "Exit the Snikket Web Portal, without logging out your other devices." + +#: snikket_web/templates/user_passwd.html:7 +msgid "Change your password" +msgstr "Change your password" + +#: snikket_web/templates/user_passwd.html:8 +msgid "" +"To change your password, you need to provide the current password as well" +" as the new one. To reduce the chance of typos, we ask for your new " +"password twice." +msgstr "" +"To change your password, you need to provide the current password as well" +" as the new one. To reduce the chance of typos, we ask for your new " +"password twice." + +#: snikket_web/templates/user_passwd.html:12 +msgid "Password change failed" +msgstr "Password change failed" + +#: snikket_web/templates/user_passwd.html:35 +#: snikket_web/templates/user_profile.html:18 +msgid "Back" +msgstr "Back" + +#: snikket_web/templates/user_profile.html:7 +msgid "Profile" +msgstr "Profile" + +#: snikket_web/templates/user_profile.html:18 +msgid "Apply" +msgstr "Apply" + +#: snikket_web/user/__init__.py:21 +msgid "Current password" +msgstr "Current password" + +#: snikket_web/user/__init__.py:26 +msgid "New password" +msgstr "New password" + +#: snikket_web/user/__init__.py:31 +msgid "Confirm new password" +msgstr "Confirm new password" + +#: snikket_web/user/__init__.py:35 +msgid "The new passwords must match." +msgstr "The new passwords must match." + +#: snikket_web/user/__init__.py:46 +msgid "Display name" +msgstr "Display name" + +#: snikket_web/user/__init__.py:50 +msgid "Avatar" +msgstr "Avatar" + +#: snikket_web/user/__init__.py:74 +msgid "Incorrect password" +msgstr "Incorrect password" + diff --git a/snikket_web/user/__init__.py b/snikket_web/user/__init__.py index 88d80aa..a575ba0 100644 --- a/snikket_web/user/__init__.py +++ b/snikket_web/user/__init__.py @@ -3,6 +3,7 @@ import quart.flask_patch from quart import Blueprint, render_template, request, redirect, url_for import quart.exceptions from flask_wtf import FlaskForm +from flask_babel import lazy_gettext as _l, _ import wtforms from snikket_web.prosodyclient import client @@ -17,25 +18,21 @@ async def proc(): class ChangePasswordForm(FlaskForm): current_password = wtforms.PasswordField( - # TODO(i18n) - "Current password", + _l("Current password"), validators=[wtforms.validators.InputRequired()] ) new_password = wtforms.PasswordField( - # TODO(i18n) - "New password", + _l("New password"), validators=[wtforms.validators.InputRequired()] ) new_password_confirm = wtforms.PasswordField( - # TODO(i18n) - "Confirm new password", + _l("Confirm new password"), validators=[wtforms.validators.InputRequired(), wtforms.validators.EqualTo( "new_password", - # TODO(i18n) - "The new passwords must match." + _l("The new passwords must match.") )] ) @@ -46,11 +43,11 @@ class LogoutForm(FlaskForm): class ProfileForm(FlaskForm): nickname = wtforms.TextField( - "Display name", + _l("Display name"), ) avatar = wtforms.FileField( - "Avatar" + _l("Avatar") ) @@ -74,8 +71,7 @@ async def change_pw(): except quart.exceptions.Unauthorized: # server refused current password, set an appropriate error form.errors.setdefault(form.current_password.name, []).append( - # TODO(i18n) - "Incorrect password", + _("Incorrect password"), ) else: return redirect(url_for("user.change_pw"))