Compare commits

...

18 Commits

Author SHA1 Message Date
Jonas Schäfer
f2c79044e0 Clean up post-merge lint
I am a *bit* sorry for this commit, because ideally this would've been
folded into 6d50b1c2c7 and whatever the
source of the other "conflict" was.

However, as the things have been merged in a batch, I can't do much more
than this.
2022-06-06 19:52:24 +02:00
Jonas Schäfer
13bc4bb227 Merge branch 'fix/babel-extraction' into premerge 2022-06-06 19:46:59 +02:00
Jonas Schäfer
f1351eb5cc Merge branch 'fix/use-english-default' into premerge 2022-06-06 19:46:49 +02:00
Jonas Schäfer
41573569af Merge branch 'feature/export-back-button' into premerge 2022-06-06 19:46:43 +02:00
Jonas Schäfer
b1f3026b8a Merge branch 'feature/wtforms-3' into premerge 2022-06-06 19:46:32 +02:00
Jonas Schäfer
6794314a59 Merge branch 'feature/vary-accept-language' into premerge 2022-06-06 19:46:25 +02:00
Jonas Schäfer
077e957a00 Merge branch 'feature/ci-extract-translations' into premerge 2022-06-06 19:46:19 +02:00
Jonas Schäfer
4902941145 Merge branch 'feature/strip-versions' into premerge 2022-06-06 19:46:09 +02:00
Jonas Schäfer
5222c8eafe Merge branch 'feature/hypercorn-stdout' into premerge 2022-06-06 19:44:39 +02:00
Jonas Schäfer
03ca7ac5bb Unbreak translation text extraction
It was broken because of the same jinja2 update (presumably) which
prompted 68f72743c5.
2022-05-30 20:51:37 +02:00
Matthew Wild
56cee8bab6 Merge pull request #135 from snikket-im/feature/update-dependencies
Update dependencies
2022-05-30 16:59:22 +01:00
Jonas Schäfer
eb22688302 Use english as default language instead of danish
It is more likely that a user for whose language no translation exists
can read english than danish.

The fallback to english was apparently introduced in c58ce845, though it
is possible that `best_match` did that internally before.

Fixes #131.
2022-05-15 14:12:51 +02:00
Kim Alvefur
73fda3d623 Add a Back button from export panel for consistency
The other user related sections all have a Back button so it makes sense
that this one ought to have one as well.
2022-02-19 16:28:38 +01:00
Jonas Schäfer
a998348804 Make hypercorn log to stdout in Docker
This may help with debugging issues, because we now actually see
incoming requests also on the hypercorn layer.

Fixes #97.
2022-01-22 15:20:36 +01:00
Jonas Schäfer
20abe4b903 Add Vary: Accept-Language to all pages using that information
It was found during testing that some user agents cache aggressively
even between switches of the UI language. To properly indicate that the
pages actually depend on that information, we add the correct Vary
header.

Fixes #106.
2022-01-22 15:19:29 +01:00
Jonas Schäfer
a1ecb4ce80 Port to WTForms 3.x
Fixes #103.
2022-01-22 15:17:48 +01:00
Jonas Schäfer
b84b84b394 Add check for a missing make extract_translations
Forgetting to run that causes weblate (or other translation tools) to
show outdated strings and not import new strings, which is bad for the
collaboration.

Fixes #118.
2022-01-22 14:57:59 +01:00
Jonas Schäfer
6d50b1c2c7 Do not show dependency versions even to admins by default
Dependency versions are generally not useful, unless you are developing
or otherwise outside of a normal release situation: If you are on a
normal release, we can figure out the dep versions by looking at the
docker image.

To reduce the amount of information displayed and the amount of
information which needs to be conveyed in case of problems, we only show
the web portal and prosody versions to admins, unless debug mode is
enabled.

The behaviour that versions are only shown to logged in admins (unless
debug mode is enabled) remains unchanged.

Fixes #115.
2022-01-20 18:11:47 +01:00
12 changed files with 127 additions and 80 deletions

View File

@@ -50,6 +50,29 @@ jobs:
run: |
python -m flake8 snikket_web
translation-check:
runs-on: ubuntu-latest
name: 'lint: i18n'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.9'
- name: Install
run: |
set -euo pipefail
pip install flask-babel
- name: Linting
run: |
sed -ri '/^"POT-Creation-Date: /d' snikket_web/translations/messages.pot
git add snikket_web/translations/messages.pot
make extract_translations
sed -ri '/^"POT-Creation-Date: /d' snikket_web/translations/messages.pot
git diff --exit-code --color -- snikket_web/translations/messages.pot
build:
runs-on: ubuntu-latest

View File

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

View File

@@ -5,4 +5,4 @@ export SNIKKET_WEB_DOMAIN="$SNIKKET_DOMAIN"
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE-127.0.0.1}"
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT-5765}"
exec hypercorn -b "${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE}:${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT}" 'snikket_web:create_app()'
exec hypercorn -b "${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE}:${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT}" --access-logfile=- --log-file=- 'snikket_web:create_app()'

View File

@@ -5,5 +5,5 @@ hsluv~=5.0
flask-babel~=1.0
email-validator~=1.1
environ-config~=20.0
wtforms~=2.3
wtforms~=3.0
typing-extensions

View File

@@ -147,9 +147,13 @@ class AppConfig:
site_name = environ.var("")
avatar_cache_ttl = environ.var(1800, converter=int)
languages = environ.var([
# Keep `en` as the first language, because it is used as a fallback
# if the language negotiation cannot find another match. It is more
# likely that users are able to read english (or find a suitable
# online translator) than, for instance, danish.
"en",
"da",
"de",
"en",
"fr",
"id",
"it",

View File

@@ -12,7 +12,6 @@ import werkzeug.exceptions
import quart.flask_patch
import wtforms
import wtforms.fields.html5
from quart import (
Blueprint,

View File

@@ -8,6 +8,7 @@ import quart.flask_patch # noqa:F401
from quart import (
current_app,
request,
g,
)
import flask_babel
@@ -34,6 +35,7 @@ BYTE_UNIT_SCALE_MAP = [
@babel.localeselector # type:ignore
def selected_locale() -> str:
g.language_header_accessed = True
selected = request.accept_languages.best_match(
current_app.config['LANGUAGES']
) or current_app.config['LANGUAGES'][0]
@@ -68,6 +70,12 @@ def format_bytes(n: float) -> str:
return "{}{}".format(n, unit)
def add_vary_language_header(resp: quart.Response) -> quart.Response:
if getattr(g, "language_header_accessed", False):
resp.vary.add("Accept-Language")
return resp
def init_templating(app: quart.Quart) -> None:
app.template_filter("repr")(repr)
app.template_filter("format_datetime")(flask_babel.format_datetime)
@@ -78,6 +86,7 @@ def init_templating(app: quart.Quart) -> None:
app.template_filter("format_bytes")(format_bytes)
app.template_filter("flatten")(flatten)
app.template_filter("circle_name")(circle_name)
app.after_request(add_vary_language_header)
def generate_error_id() -> str:

View File

@@ -34,7 +34,7 @@ bp = quart.Blueprint("main", __name__)
class LoginForm(BaseForm):
address = wtforms.TextField(
address = wtforms.StringField(
_l("Address"),
validators=[wtforms.validators.InputRequired()],
)
@@ -93,10 +93,16 @@ async def login() -> typing.Union[str, werkzeug.Response]:
@bp.route("/meta/about.html")
async def about() -> str:
version = None
core_versions = {}
extra_versions = {}
if current_app.debug or client.is_admin_session:
version = _version.version
try:
core_versions["Prosody"] = await client.get_server_version()
except werkzeug.exceptions.Unauthorized:
core_versions["Prosody"] = "unknown"
if current_app.debug:
extra_versions["aiohttp"] = aiohttp.__version__
extra_versions["babel"] = babel.__version__
extra_versions["wtforms"] = wtforms.__version__
@@ -110,6 +116,7 @@ async def about() -> str:
"about.html",
version=version,
extra_versions=extra_versions,
core_versions=core_versions,
)

View File

@@ -17,9 +17,12 @@
<h3>{% trans %}Trademarks{% endtrans %}</h3>
<p>{% trans trademarks_url="https://snikket.org/about/trademarks/" %}“Snikket” and the parrot logo are trademarks of Snikket Community Interest Company. For more information about the trademarks, visit the <a href="{{ trademarks_url }}">Snikket Trademarks information page</a>.{% endtrans %}
<h3>{% trans %}Software Versions{% endtrans %}</h3>
<pre>Snikket Server
Domain: {{ config["SNIKKET_DOMAIN"] }}
Snikket Web Portal{% if version %} ({{ version }}){% endif %}
<pre>Domain: {{ config["SNIKKET_DOMAIN"] }}
Web Portal{% if version %} ({{ version }}){% endif %}
{%- if core_versions -%}
{% for name, version in core_versions.items() %}
{{ name }} ({{ version }}){% endfor %}
{%- endif -%}
{%- if extra_versions -%}
{% for name, version in extra_versions.items() %}
{{ name }} ({{ version }}){% endfor %}

View File

@@ -11,6 +11,8 @@
{% call render_errors(form) %}{% endcall %}
<div class="f-bbox">
{%- call standard_button("back", url_for('.index'), class="tertiary") %}{% trans %}Back{% endtrans %}{% endcall -%}
<form method="POST">
{{ form.csrf_token }}
{%- call form_button("download", form.action_export, class="primary") %}{% endcall -%}

View File

@@ -8,287 +8,287 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n"
"POT-Creation-Date: 2022-06-06 19:52+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.1\n"
"Generated-By: Babel 2.10.1\n"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_edit_circle.html:59
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr ""
#: snikket_web/admin.py:72 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/admin.py:73 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_edit_circle.html:60
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:63
msgid "Display name"
msgstr ""
#: snikket_web/admin.py:76 snikket_web/templates/admin_edit_user.html:32
#: snikket_web/admin.py:77 snikket_web/templates/admin_edit_user.html:32
msgid "Access Level"
msgstr ""
#: snikket_web/admin.py:78
#: snikket_web/admin.py:79
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:79
#: snikket_web/admin.py:80
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:80
#: snikket_web/admin.py:81
msgid "Administrator"
msgstr ""
#: snikket_web/admin.py:85
#: snikket_web/admin.py:86
msgid "Update user"
msgstr ""
#: snikket_web/admin.py:89
#: snikket_web/admin.py:90
msgid "Create password reset link"
msgstr ""
#: snikket_web/admin.py:107
#: snikket_web/admin.py:108
msgid "Password reset link created"
msgstr ""
#: snikket_web/admin.py:122
#: snikket_web/admin.py:123
msgid "User information updated."
msgstr ""
#: snikket_web/admin.py:144
#: snikket_web/admin.py:145
msgid "Delete user permanently"
msgstr ""
#: snikket_web/admin.py:157
#: snikket_web/admin.py:158
msgid "User deleted"
msgstr ""
#: snikket_web/admin.py:195
#: snikket_web/admin.py:196
msgid "Password reset link not found"
msgstr ""
#: snikket_web/admin.py:207
#: snikket_web/admin.py:208
msgid "Password reset link deleted"
msgstr ""
#: snikket_web/admin.py:227
#: snikket_web/admin.py:228
msgid "Invite to circle"
msgstr ""
#: snikket_web/admin.py:233
#: snikket_web/admin.py:234
msgid "At least one circle must be selected"
msgstr ""
#: snikket_web/admin.py:238
#: snikket_web/admin.py:239
msgid "Valid for"
msgstr ""
#: snikket_web/admin.py:240
#: snikket_web/admin.py:241
msgid "One hour"
msgstr ""
#: snikket_web/admin.py:241
#: snikket_web/admin.py:242
msgid "Twelve hours"
msgstr ""
#: snikket_web/admin.py:242
#: snikket_web/admin.py:243
msgid "One day"
msgstr ""
#: snikket_web/admin.py:243
#: snikket_web/admin.py:244
msgid "One week"
msgstr ""
#: snikket_web/admin.py:244
#: snikket_web/admin.py:245
msgid "Four weeks"
msgstr ""
#: snikket_web/admin.py:250 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:251 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr ""
#: snikket_web/admin.py:252 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:253 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr ""
#: snikket_web/admin.py:253 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:254 snikket_web/templates/library.j2:114
msgid "Group"
msgstr ""
#: snikket_web/admin.py:259
#: snikket_web/admin.py:260
msgid "New invitation link"
msgstr ""
#: snikket_web/admin.py:321
#: snikket_web/admin.py:322
msgid "Revoke"
msgstr ""
#: snikket_web/admin.py:345
#: snikket_web/admin.py:346
msgid "Invitation created"
msgstr ""
#: snikket_web/admin.py:361
#: snikket_web/admin.py:362
msgid "No such invitation exists"
msgstr ""
#: snikket_web/admin.py:376
#: snikket_web/admin.py:377
msgid "Invitation revoked"
msgstr ""
#: snikket_web/admin.py:393 snikket_web/admin.py:441
#: snikket_web/admin.py:394 snikket_web/admin.py:442
msgid "Name"
msgstr ""
#: snikket_web/admin.py:398 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:399 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr ""
#: snikket_web/admin.py:428
#: snikket_web/admin.py:429
msgid "Circle created"
msgstr ""
#: snikket_web/admin.py:446
#: snikket_web/admin.py:447
msgid "Select user"
msgstr ""
#: snikket_web/admin.py:451
#: snikket_web/admin.py:452
msgid "Update circle"
msgstr ""
#: snikket_web/admin.py:455
#: snikket_web/admin.py:456
msgid "Delete circle permanently"
msgstr ""
#: snikket_web/admin.py:461
#: snikket_web/admin.py:462
msgid "Add user"
msgstr ""
#: snikket_web/admin.py:477
#: snikket_web/admin.py:478
msgid "No such circle exists"
msgstr ""
#: snikket_web/admin.py:514
#: snikket_web/admin.py:515
msgid "Circle data updated"
msgstr ""
#: snikket_web/admin.py:520
#: snikket_web/admin.py:521
msgid "Circle deleted"
msgstr ""
#: snikket_web/admin.py:531
#: snikket_web/admin.py:532
msgid "User added to circle"
msgstr ""
#: snikket_web/admin.py:540
#: snikket_web/admin.py:541
msgid "User removed from circle"
msgstr ""
#: snikket_web/admin.py:609
#: snikket_web/admin.py:610
msgid "Message contents"
msgstr ""
#: snikket_web/admin.py:615
#: snikket_web/admin.py:616
msgid "Only send to online users"
msgstr ""
#: snikket_web/admin.py:619
#: snikket_web/admin.py:620
msgid "Post to all users"
msgstr ""
#: snikket_web/admin.py:623
#: snikket_web/admin.py:624
msgid "Send preview to yourself"
msgstr ""
#: snikket_web/admin.py:645
#: snikket_web/admin.py:646
msgid "Announcement sent!"
msgstr ""
#: snikket_web/infra.py:51
#: snikket_web/infra.py:53
msgid "Main"
msgstr ""
#: snikket_web/invite.py:33
#: snikket_web/invite.py:35
msgid ""
"The account data you tried to import is too large to upload. Please "
"contact your Snikket operator."
msgstr ""
#: snikket_web/invite.py:112
#: snikket_web/invite.py:114
msgid "Username"
msgstr ""
#: snikket_web/invite.py:116 snikket_web/invite.py:184 snikket_web/main.py:41
#: snikket_web/invite.py:118 snikket_web/invite.py:186 snikket_web/main.py:43
msgid "Password"
msgstr ""
#: snikket_web/invite.py:120 snikket_web/invite.py:188
#: snikket_web/invite.py:122 snikket_web/invite.py:190
msgid "Confirm password"
msgstr ""
#: snikket_web/invite.py:124 snikket_web/invite.py:192
#: snikket_web/invite.py:126 snikket_web/invite.py:194
msgid "The passwords must match."
msgstr ""
#: snikket_web/invite.py:129
#: snikket_web/invite.py:131
msgid "Create account"
msgstr ""
#: snikket_web/invite.py:156
#: snikket_web/invite.py:158
msgid "That username is already taken."
msgstr ""
#: snikket_web/invite.py:160 snikket_web/invite.py:225
#: snikket_web/invite.py:162 snikket_web/invite.py:227
msgid "Registration was declined for unknown reasons."
msgstr ""
#: snikket_web/invite.py:164
#: snikket_web/invite.py:166
msgid "The username is not valid."
msgstr ""
#: snikket_web/invite.py:197 snikket_web/templates/user_home.html:32
#: snikket_web/invite.py:199 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr ""
#: snikket_web/invite.py:244
#: snikket_web/invite.py:246
msgid "Account data file"
msgstr ""
#: snikket_web/invite.py:248
#: snikket_web/invite.py:250
msgid "Import data"
msgstr ""
#: snikket_web/invite.py:269
#: snikket_web/invite.py:271
#, python-format
msgid ""
"The account data you tried to import is in an unknown format. Please "
"upload an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/invite.py:291 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178
msgid "Error"
msgstr ""
#: snikket_web/main.py:36
#: snikket_web/main.py:38
msgid "Address"
msgstr ""
#: snikket_web/main.py:46
#: snikket_web/main.py:48
msgid "Sign in"
msgstr ""
#: snikket_web/main.py:55
#: snikket_web/main.py:57
msgid "Invalid username or password."
msgstr ""
#: snikket_web/main.py:83
#: snikket_web/main.py:85
msgid "Login successful!"
msgstr ""
@@ -445,7 +445,7 @@ msgstr ""
msgid "Software Versions"
msgstr ""
#: snikket_web/templates/about.html:29
#: snikket_web/templates/about.html:32
msgid "Back to the main page"
msgstr ""
@@ -580,6 +580,7 @@ msgstr ""
#: snikket_web/templates/admin_delete_user.html:19
#: snikket_web/templates/admin_reset_user_password.html:25
#: snikket_web/templates/user_logout.html:10
#: snikket_web/templates/user_manage_data.html:14
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_profile.html:32
msgid "Back"

View File

@@ -59,7 +59,7 @@ _ACCESS_MODEL_CHOICES = [
class ProfileForm(BaseForm):
nickname = wtforms.TextField(
nickname = wtforms.StringField(
_l("Display name"),
)