Start translating the web portal

This commit is contained in:
Jonas Schäfer
2020-03-07 16:55:12 +01:00
parent 1ab3fac939
commit e07fbb0c97
13 changed files with 341 additions and 37 deletions

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@
/.local
__pycache__
/snikket_web/static/css/*.css
/snikket_web/translations/messages.pot
/snikket_web/translations/*/LC_MESSAGES/*.mo

View File

@@ -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

3
babel.cfg Normal file
View File

@@ -0,0 +1,3 @@
[python: snikket_web/**.py]
[jinja2: snikket_web/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View File

@@ -2,3 +2,4 @@ aiohttp~=3.6
quart~=0.11
flask-wtf~=0.14
hsluv~=0.0.2
flask-babel~=1.0

View File

@@ -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():

View File

@@ -15,6 +15,6 @@
</div>
<main>{% block content %}{% endblock %}</main>
<footer>
<ul><li>A <a href="{{ url_for('about') }}">Snikket</a> server</li></ul>
<ul><li>{% trans about_url=url_for('about') %}A <a href="{{ about_url }}">Snikket</a> server{% endtrans %}</li></ul>
</footer>
{% endblock %}

View File

@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% set body_id = "login" %}
{% block head_lead %}
<title>Snikket Web Portal</title>
<title>{{ _("Snikket Login") }}</title>
{% endblock %}
{% block style %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/app.css') }}">
@@ -10,22 +10,22 @@
{% block body %}
<main><div class="form layout-expanded">
<h1 class="form-title">{{ config["SNIKKET_DOMAIN"] }}</h1>
<p class="form-desc">Enter your Snikket address and password to manage your account.</p>
<p class="form-desc">{{ _("Enter your Snikket address and password to manage your account.") }}</p>
<form method="POST" action="{{ url_for('login') }}" name="login">
<div class="f-ebox">
<label for="address" class="a11y-only">Address:</label>
<input type="text" name="address" id="address" required="required" placeholder="Address">
<label for="address" class="a11y-only">{{ _("Address") }}:</label>
<input type="text" name="address" id="address" required="required" placeholder="{{ _("Address") }}">
</div>
<div class="f-ebox">
<label for="password" class="a11y-only"`>Password:</label>
<input type="password" name="password" required="required" placeholder="Password">
<label for="password" class="a11y-only">{{ _("Password") }}:</label>
<input type="password" name="password" id="password" required="required" placeholder="{{ _("Password") }}">
</div>
<div class="f-bbox">
<button type="submit" class="primary">Login</button>
<button type="submit" class="primary">{{ _("Log in") }}</button>
</div>
</from>
</div></main>
<footer>
<ul><li>A <a href="{{ url_for('about') }}">Snikket</a> server</li></ul>
<ul><li>{% trans about_url=url_for('about') %}A <a href="{{ about_url }}">Snikket</a> server{% endtrans %}</li></ul>
</footer>
{% endblock %}

View File

@@ -1,19 +1,18 @@
{% extends "app.html" %}
{% block content %}
<h1>Welcome!</h1>
<p>Welcome home, {{ user_info.display_name }}.</p>
<h1>{% trans %}Welcome!{% endtrans %}</h1>
<p>{% trans user_name=user_info.display_name %}Welcome home, {{ user_name }}.{% endtrans %}</p>
<div class="welcome-cards">
<a class="card" href="{{ url_for('user.profile') }}">
<h2>Update profile</h2>
<p>Change display name, set avatar and configure visibility of your
personal data to others.</p>
<h2>{% trans %}Update profile{% endtrans %}</h2>
<p>{% trans %}Change display name, set avatar and configure visibility of your personal data to others.{% endtrans %}</p>
</a>
<a class="card" href="{{ url_for('user.change_pw') }}">
<h2>Change password</h2>
<h2>{% trans %}Change password{% endtrans %}</h2>
</a>
<a class="card" href="{{ url_for('user.logout') }}">
<h2>Log out</h2>
<p>Exit the Snikket Web Portal, without logging out your other devices.</p>
<h2>{% trans %}Log out{% endtrans %}</h2>
<p>{% trans %}Exit the Snikket Web Portal, without logging out your other devices.{% endtrans %}</p>
</a>
</div>
{% endblock %}

View File

@@ -4,12 +4,12 @@
{% endblock %}
{% block content %}
<div class="form layout-expanded"><form method="POST">
<h2 class="form-title">Change your password</h2>
<p class="form-desc weak">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.</p>
<h2 class="form-title">{% trans %}Change your password{% endtrans %}</h2>
<p class="form-desc weak">{% 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 %}</p>
{{ form.csrf_token }}
{% if form.errors %}
<div class="box alert">
<header>Password change failed</header>
<header>{% trans %}Password change failed{% endtrans %}</header>
<ul>
{% 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 "")) }}
</div>
<div class="f-bbox">
<a href="{{ url_for('user.index') }}" class="button secondary">Back</a>
<button type="submit" class="primary">Change password</button>
<a href="{{ url_for('user.index') }}" class="button secondary">{% trans %}Back{% endtrans %}</a>
<button type="submit" class="primary">{% trans %}Change password{% endtrans %}</button>
</div>
</form></div>
{% endblock %}

View File

@@ -4,7 +4,7 @@
{% endblock %}
{% block content %}
<div class="form layout-expanded"><form method="POST" enctype="multipart/form-data">
<h2 class="form-title">Profile</h2>
<h2 class="form-title">{% trans %}Profile{% endtrans %}</h2>
{{ form.csrf_token }}
<div class="f-ebox">
{{ form.nickname.label }}
@@ -15,7 +15,7 @@
{{ form.avatar }}
</div>
<div class="f-bbox">
<a href="{{ url_for('user.index') }}" class="button secondary">Back</a><button type="submit" class="primary">Update</button>
<a href="{{ url_for('user.index') }}" class="button secondary">{% trans %}Back{% endtrans %}</a><button type="submit" class="primary">{% trans %}Apply{% endtrans %}</button>
</div>
</form></div>
{% endblock %}

View File

@@ -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 <EMAIL@ADDRESS>, 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 <jonas@zombofant.net>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\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 <a href=\"%(about_url)s\">Snikket</a> server"
msgstr "Ein <a href=\"%(about_url)s\">Snikket</a>-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"

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\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 <a href=\"%(about_url)s\">Snikket</a> server"
msgstr "A <a href=\"%(about_url)s\">Snikket</a> 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"

View File

@@ -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"))