Compare commits

..

35 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
b36fc0d5ac Bump hsluv to 5.x
Fixes #134.
2022-05-30 17:38:18 +02:00
Jonas Schäfer
68f72743c5 Bump quart to version 0.17
This is needed because jinja2 had an update which caused the portal to
not work at all:

```
ImportError: cannot import name 'escape' from 'jinja2'
```

Quart needed updating for that.

This update required a lot of typefixes. Apparently, the "canned"
responses (like redirect) are now plain werkzeug responses, while
quart.Response does not inherit from werkzeug.Response (otherwise, we
could've changed the type annotations to werkzeug.Response everywhere,
but that doesn't work because a quart.Response is not a
werkzeug.Response).

P.S.: This time, I *did* check that avatar uploads don't break (see
b007afc).
2022-05-30 17:37:54 +02:00
Daniel Holmgaard
8741efb2c4 Translated using Weblate (Danish)
Currently translated at 100.0% (321 of 321 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/da/
2022-05-30 14:01:08 +00:00
Daniel Holmgaard
a0e8933b64 Translated using Weblate (Danish)
Currently translated at 95.6% (307 of 321 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/da/
2022-05-26 23:01:53 +00:00
Zack Zhou
edb3154127 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (321 of 321 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/zh_Hans/
2022-05-20 00:01:45 +00: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
Jonas Schäfer
c278d4ace9 Merge pull request #132 from Raka-loah/master
Add Simplified Chinese support
2022-05-15 08:14:08 +02:00
Raka-loah
bbfe8624ef Add Simplified Chinese support 2022-05-14 17:53:40 +08:00
David Baraniak
8bcf619cef Translated using Weblate (French)
Currently translated at 100.0% (321 of 321 strings)

Translation: Snikket/Web Portal
Translate-URL: http://i18n.sotecware.net/projects/snikket/web-portal/fr/
2022-04-11 13:00:44 +00: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
Matthew Wild
846a5e49fd Merge pull request #122 from snikket-im/feature/autocomplete-hints
Add autocomplete hints to password forms
2022-02-07 11:43:20 +00:00
Matthew Wild
b3ff7f04b5 Merge pull request #127 from snikket-im/fix/unhealthy
Install netcat in final image instead of build image
2022-02-07 11:42:40 +00:00
Jonas Schäfer
0ac4ab8142 Install netcat in final image instead of build image
`nc` (from netcat) is required for the healthcheck. In
c1cf6ab1e5, the installation was
erroneously moved to the builder image, instead of the final image, so
it was missing since then from the actual application image, causing it
to always show as unhealthy.

Fixes #126.
2022-02-05 13:51:30 +01:00
Matthew Wild
d4a38f5049 Merge pull request #125 from snikket-im/fix/support-requirements-compilation
Dockerfile: Add dev headers required for building deps
2022-02-01 09:09:50 +00:00
Matthew Wild
344a4d3e93 Dockerfile: Add dev headers required for building deps 2022-02-01 09:04:25 +00:00
Matthew Wild
57f1047526 Merge pull request #124 from snikket-im/fix/support-requirements-compilation
Dockerfile: Ensure a compiler is available while pip-installing requirements
2022-02-01 08:16:33 +00:00
Matthew Wild
b036caa85e Dockerfile: Ensure a compiler is available while pip-installing requirements
Dependencies are not necessarily packaged for all architectures. In some cases
(such as aiohttp, and others, on ARM) pip will attempt to compile the
dependency from scratch. Since switching to multi-stage builds, we have been
installing these without a compiler present which caused the build to fail on
ARM architectures.

This commit temporarily installs build-essential packages while running pip,
then removes them again afterwards.
2022-01-31 21:45:39 +00:00
Matthew Wild
08845cb9f0 Merge pull request #123 from snikket-im/hotfix/error-handling
Fix error handling when building image
2022-01-31 18:11:58 +00:00
Jonas Schäfer
6aa6e12680 Fix error handling when building image
Apparently, we managed to publish an image without working aiohttp
because of this.

Partially a regression from 5d7183a.
2022-01-31 19:09:03 +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
22 changed files with 1851 additions and 218 deletions

View File

@@ -50,6 +50,29 @@ jobs:
run: | run: |
python -m flake8 snikket_web 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: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -6,8 +6,7 @@ RUN set -eu; \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
python3 python3-pip python3-setuptools python3-wheel \ python3 python3-pip python3-setuptools python3-wheel \
libpython3-dev \ libpython3-dev \
make build-essential \ make build-essential;
netcat;
COPY requirements.txt /opt/snikket-web-portal/requirements.txt COPY requirements.txt /opt/snikket-web-portal/requirements.txt
COPY build-requirements.txt /opt/snikket-web-portal/build-requirements.txt COPY build-requirements.txt /opt/snikket-web-portal/build-requirements.txt
@@ -17,7 +16,8 @@ COPY babel.cfg /opt/snikket-web-portal/babel.cfg
WORKDIR /opt/snikket-web-portal WORKDIR /opt/snikket-web-portal
RUN pip3 install -r requirements.txt; \ RUN set -eu; \
pip3 install -r requirements.txt; \
pip3 install -r build-requirements.txt; \ pip3 install -r build-requirements.txt; \
make; make;
@@ -33,21 +33,22 @@ ENV SNIKKET_WEB_PYENV=/etc/snikket-web-portal/env.py
ENV SNIKKET_WEB_PROSODY_ENDPOINT=http://127.0.0.1:5280/ ENV SNIKKET_WEB_PROSODY_ENDPOINT=http://127.0.0.1:5280/
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765} COPY requirements.txt /opt/snikket-web-portal/requirements.txt
WORKDIR /opt/snikket-web-portal
RUN set -eu; \ RUN set -eu; \
export DEBIAN_FRONTEND=noninteractive ; \ export DEBIAN_FRONTEND=noninteractive ; \
apt-get update ; \ apt-get update ; \
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
python3 python3-pip python3-setuptools python3-wheel; \ python3 python3-pip python3-setuptools python3-wheel build-essential libpython3-dev netcat; \
pip3 install -r requirements.txt; \
apt-get remove -y --autoremove build-essential libpython3-dev; \
apt-get clean ; rm -rf /var/lib/apt/lists; \ apt-get clean ; rm -rf /var/lib/apt/lists; \
pip3 install hypercorn; \ pip3 install hypercorn; \
rm -rf /root/.cache; rm -rf /root/.cache;
WORKDIR /opt/snikket-web-portal HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
RUN pip3 install -r requirements.txt; rm -rf /root/.cache;
COPY --from=build /opt/snikket-web-portal/snikket_web/ /opt/snikket-web-portal/snikket_web COPY --from=build /opt/snikket-web-portal/snikket_web/ /opt/snikket-web-portal/snikket_web
COPY babel.cfg /opt/snikket-web-portal/babel.cfg COPY babel.cfg /opt/snikket-web-portal/babel.cfg

View File

@@ -1,4 +1,3 @@
[python: snikket_web/**.py] [python: snikket_web/**.py]
[jinja2: snikket_web/templates/**.html] [jinja2: snikket_web/templates/**.html]
[jinja2: snikket_web/templates/**.j2] [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_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}" 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

@@ -1,9 +1,9 @@
aiohttp~=3.6 aiohttp~=3.6
quart~=0.11,<0.15 quart~=0.17
flask-wtf~=0.14 flask-wtf~=0.14
hsluv~=0.0.2 hsluv~=5.0
flask-babel~=1.0 flask-babel~=1.0
email-validator~=1.1 email-validator~=1.1
environ-config~=20.0 environ-config~=20.0
wtforms~=2.3 wtforms~=3.0
typing-extensions typing-extensions

View File

@@ -18,6 +18,8 @@ from quart import (
jsonify, jsonify,
) )
import werkzeug.exceptions
import environ import environ
from . import colour, infra from . import colour, infra
@@ -40,7 +42,7 @@ async def proc() -> typing.Dict[str, typing.Any]:
try: try:
user_info = await infra.client.get_user_info() user_info = await infra.client.get_user_info()
except (aiohttp.ClientError, quart.exceptions.HTTPException): except (aiohttp.ClientError, werkzeug.exceptions.HTTPException):
user_info = {} user_info = {}
return { return {
@@ -105,16 +107,16 @@ async def backend_error_handler(exc: Exception) -> quart.Response:
async def generic_http_error( async def generic_http_error(
exc: quart.exceptions.HTTPException, exc: werkzeug.exceptions.HTTPException,
) -> quart.Response: ) -> quart.Response:
return quart.Response( return quart.Response(
await render_template( await render_template(
"generic_http_error.html", "generic_http_error.html",
status=exc.status_code, status=exc.code,
description=exc.description, description=exc.description,
name=exc.name, name=exc.name,
), ),
status=exc.status_code, status=exc.code,
) )
@@ -145,14 +147,19 @@ class AppConfig:
site_name = environ.var("") site_name = environ.var("")
avatar_cache_ttl = environ.var(1800, converter=int) avatar_cache_ttl = environ.var(1800, converter=int)
languages = environ.var([ 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", "da",
"de", "de",
"en",
"fr", "fr",
"id", "id",
"it", "it",
"pl", "pl",
"sv", "sv",
"zh_Hans_CN",
], converter=autosplit) ], converter=autosplit)
apple_store_url = environ.var( apple_store_url = environ.var(
"https://apps.apple.com/us/app/snikket/id1545164189", "https://apps.apple.com/us/app/snikket/id1545164189",
@@ -199,19 +206,19 @@ def create_app() -> quart.Quart:
app.context_processor(proc) app.context_processor(proc)
app.register_error_handler( app.register_error_handler(
aiohttp.ClientConnectorError, aiohttp.ClientConnectorError,
backend_error_handler, # type:ignore backend_error_handler,
) )
app.register_error_handler( app.register_error_handler(
quart.exceptions.HTTPException, werkzeug.exceptions.HTTPException,
generic_http_error, # type:ignore generic_http_error, # type:ignore
) )
app.register_error_handler( app.register_error_handler(
Exception, Exception,
generic_error_handler, # type:ignore generic_error_handler,
) )
@app.route("/") @app.route("/")
async def index() -> quart.Response: async def index() -> werkzeug.Response:
if infra.client.has_session: if infra.client.has_session:
return redirect(url_for('user.index')) return redirect(url_for('user.index'))

View File

@@ -7,10 +7,11 @@ from datetime import datetime
import aiohttp import aiohttp
import werkzeug.exceptions
import quart.flask_patch import quart.flask_patch
import wtforms import wtforms
import wtforms.fields.html5
from quart import ( from quart import (
Blueprint, Blueprint,
@@ -92,7 +93,7 @@ class EditUserForm(BaseForm):
@bp.route("/user/<localpart>/", methods=["GET", "POST"]) @bp.route("/user/<localpart>/", methods=["GET", "POST"])
@client.require_admin_session() @client.require_admin_session()
async def edit_user(localpart: str) -> typing.Union[quart.Response, str]: async def edit_user(localpart: str) -> typing.Union[werkzeug.Response, str]:
target_user_info = await client.get_user_by_localpart(localpart) target_user_info = await client.get_user_by_localpart(localpart)
form = EditUserForm() form = EditUserForm()
@@ -147,7 +148,7 @@ class DeleteUserForm(BaseForm):
@bp.route("/user/<localpart>/delete", methods=["GET", "POST"]) @bp.route("/user/<localpart>/delete", methods=["GET", "POST"])
@client.require_admin_session() @client.require_admin_session()
async def delete_user(localpart: str) -> typing.Union[str, quart.Response]: async def delete_user(localpart: str) -> typing.Union[str, werkzeug.Response]:
target_user_info = await client.get_user_by_localpart(localpart) target_user_info = await client.get_user_by_localpart(localpart)
form = DeleteUserForm() form = DeleteUserForm()
if form.validate_on_submit(): if form.validate_on_submit():
@@ -186,7 +187,7 @@ async def debug_user(localpart: str) -> typing.Union[str, quart.Response]:
@client.require_admin_session() @client.require_admin_session()
async def user_password_reset_link( async def user_password_reset_link(
id_: str, id_: str,
) -> typing.Union[str, quart.Response]: ) -> typing.Union[str, werkzeug.Response]:
invite_info = await client.get_invite_by_id( invite_info = await client.get_invite_by_id(
id_, id_,
) )
@@ -278,7 +279,7 @@ class InvitePost(BaseForm):
@bp.route("/invitations", methods=["GET", "POST"]) @bp.route("/invitations", methods=["GET", "POST"])
@client.require_admin_session() @client.require_admin_session()
async def invitations() -> typing.Union[str, quart.Response]: async def invitations() -> typing.Union[str, werkzeug.Response]:
invites = sorted( invites = sorted(
( (
invite invite
@@ -324,7 +325,7 @@ class InviteForm(BaseForm):
@bp.route("/invitation/-/new", methods=["POST"]) @bp.route("/invitation/-/new", methods=["POST"])
@client.require_admin_session() @client.require_admin_session()
async def create_invite() -> typing.Union[str, quart.Response]: async def create_invite() -> typing.Union[str, werkzeug.Response]:
form = InvitePost() form = InvitePost()
circles = await client.list_groups() circles = await client.list_groups()
form.circles.choices = [ form.circles.choices = [
@@ -352,7 +353,7 @@ async def create_invite() -> typing.Union[str, quart.Response]:
@bp.route("/invitation/<id_>", methods=["GET", "POST"]) @bp.route("/invitation/<id_>", methods=["GET", "POST"])
@client.require_admin_session() @client.require_admin_session()
async def edit_invite(id_: str) -> typing.Union[str, quart.Response]: async def edit_invite(id_: str) -> typing.Union[str, werkzeug.Response]:
try: try:
invite_info = await client.get_invite_by_id(id_) invite_info = await client.get_invite_by_id(id_)
except aiohttp.ClientResponseError as exc: except aiohttp.ClientResponseError as exc:
@@ -418,7 +419,7 @@ async def circles() -> str:
@bp.route("/circle/-/new", methods=["POST"]) @bp.route("/circle/-/new", methods=["POST"])
@client.require_admin_session() @client.require_admin_session()
async def create_circle() -> typing.Union[str, quart.Response]: async def create_circle() -> typing.Union[str, werkzeug.Response]:
create_form = CirclePost() create_form = CirclePost()
if create_form.validate_on_submit(): if create_form.validate_on_submit():
circle = await client.create_group( circle = await client.create_group(
@@ -464,7 +465,7 @@ class EditCircleForm(BaseForm):
@bp.route("/circle/<id_>", methods=["GET", "POST"]) @bp.route("/circle/<id_>", methods=["GET", "POST"])
@client.require_admin_session() @client.require_admin_session()
async def edit_circle(id_: str) -> typing.Union[str, quart.Response]: async def edit_circle(id_: str) -> typing.Union[str, werkzeug.Response]:
async with client.authenticated_session() as session: async with client.authenticated_session() as session:
try: try:
circle = await client.get_group_by_id( circle = await client.get_group_by_id(
@@ -626,7 +627,7 @@ class AnnouncementForm(BaseForm):
@bp.route("/system/", methods=["GET", "POST"]) @bp.route("/system/", methods=["GET", "POST"])
@client.require_admin_session() @client.require_admin_session()
async def system() -> typing.Union[str, quart.Response]: async def system() -> typing.Union[str, werkzeug.Response]:
form = AnnouncementForm() form = AnnouncementForm()
if form.validate_on_submit(): if form.validate_on_submit():
@@ -657,7 +658,7 @@ async def system() -> typing.Union[str, quart.Response]:
now = time.time() now = time.time()
try: try:
prosody_metrics = await client.get_system_metrics() prosody_metrics = await client.get_system_metrics()
except quart.exceptions.NotFound: except werkzeug.exceptions.NotFound:
# server does not offer the endpoint for whatever reason -- ignore # server does not offer the endpoint for whatever reason -- ignore
prosody_metrics = {} prosody_metrics = {}

View File

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

View File

@@ -15,6 +15,8 @@ from quart import (
session as http_session, session as http_session,
) )
import werkzeug
import wtforms import wtforms
from flask_babel import lazy_gettext as _l, gettext from flask_babel import lazy_gettext as _l, gettext
@@ -46,14 +48,14 @@ def apple_store_badge() -> str:
@bp.context_processor @bp.context_processor
def context() -> typing.Mapping[str, typing.Any]: def context() -> typing.Dict[str, typing.Any]:
return { return {
"apple_store_badge": apple_store_badge, "apple_store_badge": apple_store_badge,
} }
@bp.route("/<id_>") @bp.route("/<id_>")
async def view_old(id_: str) -> quart.Response: async def view_old(id_: str) -> werkzeug.Response:
return redirect(url_for(".view", id_=id_)) return redirect(url_for(".view", id_=id_))
@@ -131,7 +133,7 @@ class RegisterForm(BaseForm):
@bp.route("/<id_>/register", methods=["GET", "POST"]) @bp.route("/<id_>/register", methods=["GET", "POST"])
async def register(id_: str) -> typing.Union[str, quart.Response]: async def register(id_: str) -> typing.Union[str, werkzeug.Response]:
try: try:
invite = await client.get_public_invite_by_id(id_) invite = await client.get_public_invite_by_id(id_)
except aiohttp.ClientResponseError as exc: except aiohttp.ClientResponseError as exc:
@@ -199,7 +201,7 @@ class ResetForm(BaseForm):
@bp.route("/<id_>/reset", methods=["GET", "POST"]) @bp.route("/<id_>/reset", methods=["GET", "POST"])
async def reset(id_: str) -> typing.Union[str, quart.Response]: async def reset(id_: str) -> typing.Union[str, werkzeug.Response]:
try: try:
invite = await client.get_public_invite_by_id(id_) invite = await client.get_public_invite_by_id(id_)
except aiohttp.ClientResponseError as exc: except aiohttp.ClientResponseError as exc:
@@ -300,5 +302,5 @@ async def reset_success() -> str:
@bp.route("/-") @bp.route("/-")
async def index() -> quart.Response: async def index() -> werkzeug.Response:
return redirect(url_for("index")) return redirect(url_for("index"))

View File

@@ -18,6 +18,8 @@ from quart import (
flash, flash,
) )
import werkzeug.exceptions
import babel import babel
import wtforms import wtforms
@@ -32,7 +34,7 @@ bp = quart.Blueprint("main", __name__)
class LoginForm(BaseForm): class LoginForm(BaseForm):
address = wtforms.TextField( address = wtforms.StringField(
_l("Address"), _l("Address"),
validators=[wtforms.validators.InputRequired()], validators=[wtforms.validators.InputRequired()],
) )
@@ -48,7 +50,7 @@ class LoginForm(BaseForm):
@bp.route("/-") @bp.route("/-")
async def index() -> quart.Response: async def index() -> werkzeug.Response:
return redirect(url_for("index")) return redirect(url_for("index"))
@@ -56,7 +58,7 @@ ERR_CREDENTIALS_INVALID = _l("Invalid username or password.")
@bp.route("/login", methods=["GET", "POST"]) @bp.route("/login", methods=["GET", "POST"])
async def login() -> typing.Union[str, quart.Response]: async def login() -> typing.Union[str, werkzeug.Response]:
if client.has_session and (await client.test_session()): if client.has_session and (await client.test_session()):
return redirect(url_for('user.index')) return redirect(url_for('user.index'))
@@ -76,7 +78,7 @@ async def login() -> typing.Union[str, quart.Response]:
password = form.password.data password = form.password.data
try: try:
await client.login(jid, password) await client.login(jid, password)
except quart.exceptions.Unauthorized: except werkzeug.exceptions.Unauthorized:
form.password.errors.append(ERR_CREDENTIALS_INVALID) form.password.errors.append(ERR_CREDENTIALS_INVALID)
else: else:
await flash( await flash(
@@ -91,24 +93,30 @@ async def login() -> typing.Union[str, quart.Response]:
@bp.route("/meta/about.html") @bp.route("/meta/about.html")
async def about() -> str: async def about() -> str:
version = None version = None
core_versions = {}
extra_versions = {} extra_versions = {}
if current_app.debug or client.is_admin_session: if current_app.debug or client.is_admin_session:
version = _version.version version = _version.version
extra_versions["Quart"] = quart.__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["aiohttp"] = aiohttp.__version__
extra_versions["babel"] = babel.__version__ extra_versions["babel"] = babel.__version__
extra_versions["wtforms"] = wtforms.__version__ extra_versions["wtforms"] = wtforms.__version__
extra_versions["flask-wtf"] = flask_wtf.__version__ extra_versions["flask-wtf"] = flask_wtf.__version__
try: try:
extra_versions["Prosody"] = await client.get_server_version() extra_versions["Prosody"] = await client.get_server_version()
except quart.exceptions.Unauthorized: except werkzeug.exceptions.Unauthorized:
extra_versions["Prosody"] = "unknown" extra_versions["Prosody"] = "unknown"
return await render_template( return await render_template(
"about.html", "about.html",
version=version, version=version,
extra_versions=extra_versions, extra_versions=extra_versions,
core_versions=core_versions,
) )

View File

@@ -19,7 +19,9 @@ from quart import (
current_app, _app_ctx_stack, session as http_session, abort, redirect, current_app, _app_ctx_stack, session as http_session, abort, redirect,
url_for, url_for,
) )
import quart.exceptions import quart
import werkzeug.exceptions
from . import xmpputil from . import xmpputil
from .xmpputil import split_jid from .xmpputil import split_jid
@@ -386,16 +388,16 @@ class ProsodyClient:
) -> typing.Callable[ ) -> typing.Callable[
[typing.Callable[..., typing.Awaitable[T]]], [typing.Callable[..., typing.Awaitable[T]]],
typing.Callable[..., typing.Awaitable[ typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response]]]]: typing.Union[T, quart.Response, werkzeug.Response]]]]:
def decorator( def decorator(
f: typing.Callable[..., typing.Awaitable[T]], f: typing.Callable[..., typing.Awaitable[T]],
) -> typing.Callable[..., typing.Awaitable[ ) -> typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response]]]: typing.Union[T, quart.Response, werkzeug.Response]]]:
@functools.wraps(f) @functools.wraps(f)
async def wrapped( async def wrapped(
*args: typing.Any, *args: typing.Any,
**kwargs: typing.Any, **kwargs: typing.Any,
) -> typing.Union[T, quart.Response]: ) -> typing.Union[T, quart.Response, werkzeug.Response]:
if not self.has_session or not (await self.test_session()): if not self.has_session or not (await self.test_session()):
redirect_to_value = redirect_to redirect_to_value = redirect_to
if redirect_to_value is not False: if redirect_to_value is not False:
@@ -415,17 +417,17 @@ class ProsodyClient:
) -> typing.Callable[ ) -> typing.Callable[
[typing.Callable[..., typing.Awaitable[T]]], [typing.Callable[..., typing.Awaitable[T]]],
typing.Callable[..., typing.Awaitable[ typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response]]]]: typing.Union[T, quart.Response, werkzeug.Response]]]]:
def decorator( def decorator(
f: typing.Callable[..., typing.Awaitable[T]], f: typing.Callable[..., typing.Awaitable[T]],
) -> typing.Callable[..., typing.Awaitable[ ) -> typing.Callable[..., typing.Awaitable[
typing.Union[T, quart.Response]]]: typing.Union[T, quart.Response, werkzeug.Response]]]:
@functools.wraps(f) @functools.wraps(f)
@self.require_session(redirect_to=redirect_to) @self.require_session(redirect_to=redirect_to)
async def wrapped( async def wrapped(
*args: typing.Any, *args: typing.Any,
**kwargs: typing.Any, **kwargs: typing.Any,
) -> typing.Union[T, quart.Response]: ) -> typing.Union[T, quart.Response, werkzeug.Response]:
if not self.is_admin_session: if not self.is_admin_session:
raise abort(403, "This is not for you.") raise abort(403, "This is not for you.")
@@ -492,7 +494,7 @@ class ProsodyClient:
session=session, session=session,
) )
avatar_hash = avatar_info["sha1"] avatar_hash = avatar_info["sha1"]
except quart.exceptions.HTTPException: except werkzeug.exceptions.HTTPException:
avatar_hash = None avatar_hash = None
return { return {
@@ -644,7 +646,7 @@ class ProsodyClient:
new_access_model, new_access_model,
) )
)) ))
except quart.exceptions.NotFound: except werkzeug.exceptions.NotFound:
if ignore_not_found: if ignore_not_found:
return return
raise raise
@@ -774,7 +776,7 @@ class ProsodyClient:
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
) -> str: ) -> str:
access_models = filter( access_models = filter(
lambda x: not isinstance(x, quart.exceptions.NotFound), lambda x: not isinstance(x, werkzeug.exceptions.NotFound),
await asyncio.gather( await asyncio.gather(
self.get_avatar_access_model(session=session), self.get_avatar_access_model(session=session),
self.get_nickname_access_model(session=session), self.get_nickname_access_model(session=session),

View File

@@ -17,9 +17,12 @@
<h3>{% trans %}Trademarks{% endtrans %}</h3> <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 %} <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> <h3>{% trans %}Software Versions{% endtrans %}</h3>
<pre>Snikket Server <pre>Domain: {{ config["SNIKKET_DOMAIN"] }}
Domain: {{ config["SNIKKET_DOMAIN"] }} Web Portal{% if version %} ({{ version }}){% endif %}
Snikket Web Portal{% if version %} ({{ version }}){% endif %} {%- if core_versions -%}
{% for name, version in core_versions.items() %}
{{ name }} ({{ version }}){% endfor %}
{%- endif -%}
{%- if extra_versions -%} {%- if extra_versions -%}
{% for name, version in extra_versions.items() %} {% for name, version in extra_versions.items() %}
{{ name }} ({{ version }}){% endfor %} {{ name }} ({{ version }}){% endfor %}

View File

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

View File

@@ -6,18 +6,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: translations@snikket.org\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n" "POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: 2021-04-02 19:01+0000\n" "PO-Revision-Date: 2022-05-30 14:01+0000\n"
"Last-Translator: Daniel Holmgaard <annoncer@protonmail.com>\n" "Last-Translator: Daniel Holmgaard <fovatis@tutanota.com>\n"
"Language-Team: Danish <https://i18n.sotecware.net/projects/snikket/web-" "Language-Team: Danish <http://i18n.sotecware.net/projects/snikket/web-portal/"
"portal/da/>\n" "da/>\n"
"Language: da\n" "Language: da\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.5.1\n" "X-Generator: Weblate 4.8.1\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10 #: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10
@@ -194,23 +194,23 @@ msgstr "Bruger fjernet fra cirkel"
#: snikket_web/admin.py:609 #: snikket_web/admin.py:609
msgid "Message contents" msgid "Message contents"
msgstr "" msgstr "Meddelelsens indhold"
#: snikket_web/admin.py:615 #: snikket_web/admin.py:615
msgid "Only send to online users" msgid "Only send to online users"
msgstr "" msgstr "Send kun til online brugere"
#: snikket_web/admin.py:619 #: snikket_web/admin.py:619
msgid "Post to all users" msgid "Post to all users"
msgstr "" msgstr "Send til alle brugere"
#: snikket_web/admin.py:623 #: snikket_web/admin.py:623
msgid "Send preview to yourself" msgid "Send preview to yourself"
msgstr "" msgstr "Send forhåndsvisning til dig selv"
#: snikket_web/admin.py:645 #: snikket_web/admin.py:645
msgid "Announcement sent!" msgid "Announcement sent!"
msgstr "" msgstr "Bekendgørelse sendt!"
#: snikket_web/infra.py:51 #: snikket_web/infra.py:51
msgid "Main" msgid "Main"
@@ -221,6 +221,8 @@ msgid ""
"The account data you tried to import is too large to upload. Please contact " "The account data you tried to import is too large to upload. Please contact "
"your Snikket operator." "your Snikket operator."
msgstr "" msgstr ""
"De kontodata, du forsøgte at importere, er for store til at uploade. Kontakt "
"din Snikket-operatør."
#: snikket_web/invite.py:112 #: snikket_web/invite.py:112
msgid "Username" msgid "Username"
@@ -261,11 +263,11 @@ msgstr "Ændr adgangskode"
#: snikket_web/invite.py:244 #: snikket_web/invite.py:244
msgid "Account data file" msgid "Account data file"
msgstr "" msgstr "Kontodatafil"
#: snikket_web/invite.py:248 #: snikket_web/invite.py:248
msgid "Import data" msgid "Import data"
msgstr "" msgstr "Importer data"
#: snikket_web/invite.py:269 #: snikket_web/invite.py:269
#, python-format #, python-format
@@ -273,6 +275,8 @@ msgid ""
"The account data you tried to import is in an unknown format. Please upload " "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)." "an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr "" msgstr ""
"De kontodata, du forsøgte at importere, er i et ukendt format. Upload en XML-"
"fil i XEP-0227-format (forudsat format: %(mimetype)s)."
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18 #: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178 #: snikket_web/user.py:178
@@ -341,11 +345,11 @@ msgstr "Opdater profil"
#: snikket_web/user.py:82 #: snikket_web/user.py:82
msgid "Account data" msgid "Account data"
msgstr "" msgstr "Kontodata"
#: snikket_web/user.py:86 #: snikket_web/user.py:86
msgid "Upload" msgid "Upload"
msgstr "" msgstr "Upload"
#: snikket_web/user.py:111 #: snikket_web/user.py:111
msgid "Incorrect password." msgid "Incorrect password."
@@ -369,11 +373,11 @@ msgstr "Profil opdateret"
#: snikket_web/user.py:184 #: snikket_web/user.py:184
msgid "Export" msgid "Export"
msgstr "" msgstr "Exporter"
#: snikket_web/user.py:202 #: snikket_web/user.py:202
msgid "You currently have no account data to export." msgid "You currently have no account data to export."
msgstr "" msgstr "Du har i øjeblikket ingen kontodata at eksportere."
#: snikket_web/templates/_footer.html:4 #: snikket_web/templates/_footer.html:4
#, python-format #, python-format
@@ -662,7 +666,7 @@ msgstr "Cirkel medlemmer"
#: snikket_web/templates/admin_edit_circle.html:71 #: snikket_web/templates/admin_edit_circle.html:71
msgid "The user has been deleted from the server." msgid "The user has been deleted from the server."
msgstr "" msgstr "Brugeren er blevet slettet fra serveren."
#: snikket_web/templates/admin_edit_circle.html:71 #: snikket_web/templates/admin_edit_circle.html:71
#: snikket_web/templates/library.j2:108 #: snikket_web/templates/library.j2:108
@@ -876,22 +880,20 @@ msgstr "Håndter invitationer"
#: snikket_web/templates/admin_home.html:35 #: snikket_web/templates/admin_home.html:35
msgid "System health" msgid "System health"
msgstr "" msgstr "Systemets sundhed"
#: snikket_web/templates/admin_home.html:38 #: snikket_web/templates/admin_home.html:38
msgid "View the server status or send a broadcast message to all users." msgid "View the server status or send a broadcast message to all users."
msgstr "" msgstr "Vis serverstatus, eller send en udsendelsesmeddelelse til alle brugere."
#: snikket_web/templates/admin_home.html:40 #: snikket_web/templates/admin_home.html:40
msgid "Send a broadcast message to all users." msgid "Send a broadcast message to all users."
msgstr "" msgstr "Send en udsendelsesmeddelelse til alle brugere."
#: snikket_web/templates/admin_home.html:43 #: snikket_web/templates/admin_home.html:43
#: snikket_web/templates/admin_system.html:4 #: snikket_web/templates/admin_system.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage system" msgid "Manage system"
msgstr "Håndter brugere" msgstr "Håndter system"
#: snikket_web/templates/admin_home.html:48 #: snikket_web/templates/admin_home.html:48
msgid "Go back to your user's web portal page." msgid "Go back to your user's web portal page."
@@ -952,11 +954,11 @@ msgstr "Ødelæg link"
#: snikket_web/templates/admin_system.html:6 #: snikket_web/templates/admin_system.html:6
msgid "Overall system status" msgid "Overall system status"
msgstr "" msgstr "Samlet systemstatus"
#: snikket_web/templates/admin_system.html:9 #: snikket_web/templates/admin_system.html:9
msgid "System load (5 minute average)" msgid "System load (5 minute average)"
msgstr "" msgstr "Systembelastning (5 minutters gennemsnit)"
#: snikket_web/templates/admin_system.html:14 #: snikket_web/templates/admin_system.html:14
#: snikket_web/templates/admin_system.html:22 #: snikket_web/templates/admin_system.html:22
@@ -967,11 +969,11 @@ msgstr ""
#: snikket_web/templates/admin_system.html:76 #: snikket_web/templates/admin_system.html:76
#: snikket_web/templates/admin_system.html:84 #: snikket_web/templates/admin_system.html:84
msgid "unknown" msgid "unknown"
msgstr "" msgstr "ukendt"
#: snikket_web/templates/admin_system.html:17 #: snikket_web/templates/admin_system.html:17
msgid "Memory use" msgid "Memory use"
msgstr "" msgstr "Forbrug af hukommelse"
#: snikket_web/templates/admin_system.html:20 #: snikket_web/templates/admin_system.html:20
#, python-format #, python-format
@@ -979,54 +981,56 @@ msgid ""
"%(percentage_global)s of %(mem_available)s. Of that, Snikket uses " "%(percentage_global)s of %(mem_available)s. Of that, Snikket uses "
"%(percentage_snikket)s." "%(percentage_snikket)s."
msgstr "" msgstr ""
"%(percentage_global)s af %(mem_available)s. Der af bruger Snikket "
"%(percentage_snikket)s."
#: snikket_web/templates/admin_system.html:27 #: snikket_web/templates/admin_system.html:27
msgid "Web portal status" msgid "Web portal status"
msgstr "" msgstr "Webportalens status"
#: snikket_web/templates/admin_system.html:30 #: snikket_web/templates/admin_system.html:30
#: snikket_web/templates/admin_system.html:53 #: snikket_web/templates/admin_system.html:53
msgid "Version" msgid "Version"
msgstr "" msgstr "Version"
#: snikket_web/templates/admin_system.html:31 #: snikket_web/templates/admin_system.html:31
#: snikket_web/templates/admin_system.html:54 #: snikket_web/templates/admin_system.html:54
msgid "View all versions" msgid "View all versions"
msgstr "" msgstr "Vis alle versioner"
#: snikket_web/templates/admin_system.html:32 #: snikket_web/templates/admin_system.html:32
#: snikket_web/templates/admin_system.html:55 #: snikket_web/templates/admin_system.html:55
msgid "Average CPU use" msgid "Average CPU use"
msgstr "" msgstr "Gennemsnitlig CPU-forbrug"
#: snikket_web/templates/admin_system.html:40 #: snikket_web/templates/admin_system.html:40
#: snikket_web/templates/admin_system.html:63 #: snikket_web/templates/admin_system.html:63
msgid "Current memory use" msgid "Current memory use"
msgstr "" msgstr "Nuværende hukommelsesbrug"
#: snikket_web/templates/admin_system.html:50 #: snikket_web/templates/admin_system.html:50
#, fuzzy
#| msgid "Snikket Web Portal"
msgid "Snikket server status" msgid "Snikket server status"
msgstr "Snikket Webportal" msgstr "Snikket server status"
#: snikket_web/templates/admin_system.html:71 #: snikket_web/templates/admin_system.html:71
msgid "Storage used by shared files" msgid "Storage used by shared files"
msgstr "" msgstr "Lagerplads, der bruges af delte filer"
#: snikket_web/templates/admin_system.html:79 #: snikket_web/templates/admin_system.html:79
msgid "Connected devices" msgid "Connected devices"
msgstr "" msgstr "Forbundet enheder"
#: snikket_web/templates/admin_system.html:90 #: snikket_web/templates/admin_system.html:90
msgid "Broadcast message" msgid "Broadcast message"
msgstr "" msgstr "Send besked"
#: snikket_web/templates/admin_system.html:92 #: snikket_web/templates/admin_system.html:92
msgid "" msgid ""
"This form allows you to send a message to all users currently online on your " "This form allows you to send a message to all users currently online on your "
"Snikket server. Use it wisely." "Snikket server. Use it wisely."
msgstr "" msgstr ""
"Denne formular giver dig mulighed for at sende en besked til alle brugere, "
"der i øjeblikket er online på din Snikket-server. Brug den med omtanke."
#: snikket_web/templates/admin_users.html:19 #: snikket_web/templates/admin_users.html:19
msgid "The user is an administrator." msgid "The user is an administrator."
@@ -1300,22 +1304,20 @@ msgid ""
"You can now safely close this page, or log in to the web portal to <a href=" "You can now safely close this page, or log in to the web portal to <a href="
"\"%(login_url)s\">manage your account</a>." "\"%(login_url)s\">manage your account</a>."
msgstr "" msgstr ""
"Du kan nu trygt lukke denne side eller logge ind på webportalen for at <a "
"href=\"%(login_url)s\">administrere din konto</a>."
#: snikket_web/templates/invite_success.html:21 #: snikket_web/templates/invite_success.html:21
#, fuzzy
#| msgid "Operation successful"
msgid "Import successful" msgid "Import successful"
msgstr "Operation lykkes" msgstr "Importering lykkes"
#: snikket_web/templates/invite_success.html:22 #: snikket_web/templates/invite_success.html:22
msgid "Congratulations! Your account data has been successfully imported." msgid "Congratulations! Your account data has been successfully imported."
msgstr "" msgstr "Tillykke! Dine kontodata er blevet importeret."
#: snikket_web/templates/invite_success.html:26 #: snikket_web/templates/invite_success.html:26
#, fuzzy
#| msgid "Using the Snikket app"
msgid "Moving to Snikket?" msgid "Moving to Snikket?"
msgstr "Bruger Snikket appen" msgstr "Flytte til Snikket?"
#: snikket_web/templates/invite_success.html:27 #: snikket_web/templates/invite_success.html:27
msgid "" msgid ""
@@ -1324,10 +1326,14 @@ msgid ""
"information, etc.) from your previous account. When you have exported the " "information, etc.) from your previous account. When you have exported the "
"data from your previous account, upload it using the form below." "data from your previous account, upload it using the form below."
msgstr "" msgstr ""
"Hvis du flytter fra en anden Snikket-platform eller en anden XMPP-kompatibel "
"tjeneste, kan du eventuelt importere dataene (kontakter, profiloplysninger "
"osv.) fra din tidligere konto. Når du har eksporteret dataene fra din "
"tidligere konto, skal du uploade dem ved hjælp af nedenstående formular."
#: snikket_web/templates/invite_success.html:30 #: snikket_web/templates/invite_success.html:30
msgid "Upload account data" msgid "Upload account data"
msgstr "" msgstr "Upload kontodata"
#: snikket_web/templates/invite_view.html:6 #: snikket_web/templates/invite_view.html:6
#, python-format #, python-format
@@ -1558,10 +1564,8 @@ msgstr "Rediger profil"
#: snikket_web/templates/user_home.html:33 #: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_manage_data.html:4 #: snikket_web/templates/user_manage_data.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage your data" msgid "Manage your data"
msgstr "Håndter brugere" msgstr "Håndter dine data"
#: snikket_web/templates/user_home.html:39 #: snikket_web/templates/user_home.html:39
msgid "Your Snikket" msgid "Your Snikket"
@@ -1588,16 +1592,16 @@ msgstr ""
"af de forbundne enheder." "af de forbundne enheder."
#: snikket_web/templates/user_manage_data.html:8 #: snikket_web/templates/user_manage_data.html:8
#, fuzzy
#| msgid "Your account"
msgid "Export account" msgid "Export account"
msgstr "Din konto" msgstr "Eksporter konto"
#: snikket_web/templates/user_manage_data.html:9 #: snikket_web/templates/user_manage_data.html:9
msgid "" msgid ""
"Download your account data as a file for backup purposes or to move your " "Download your account data as a file for backup purposes or to move your "
"account to another service." "account to another service."
msgstr "" msgstr ""
"Download dine kontodata som en fil til sikkerhedskopieringsformål eller for "
"at flytte din konto til en anden tjeneste."
#: snikket_web/templates/user_passwd.html:5 #: snikket_web/templates/user_passwd.html:5
msgid "Change your password" msgid "Change your password"

View File

@@ -6,18 +6,18 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: translations@snikket.org\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n" "POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: 2021-06-19 15:01+0000\n" "PO-Revision-Date: 2022-04-11 13:00+0000\n"
"Last-Translator: Link Mauve <linkmauve@linkmauve.fr>\n" "Last-Translator: David Baraniak <admin@chipmnk.dev>\n"
"Language-Team: French <https://i18n.sotecware.net/projects/snikket/web-" "Language-Team: French <http://i18n.sotecware.net/projects/snikket/web-portal/"
"portal/fr/>\n" "fr/>\n"
"Language: fr\n" "Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n" "Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 4.5.1\n" "X-Generator: Weblate 4.8.1\n"
"Generated-By: Babel 2.9.0\n" "Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10 #: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:10
@@ -221,6 +221,8 @@ msgid ""
"The account data you tried to import is too large to upload. Please contact " "The account data you tried to import is too large to upload. Please contact "
"your Snikket operator." "your Snikket operator."
msgstr "" msgstr ""
"Les données du compte que vous avez essayé d'importer sont trop volumineuses "
"pour être téléchargées. Veuillez contacter votre opérateur Snikket."
#: snikket_web/invite.py:112 #: snikket_web/invite.py:112
msgid "Username" msgid "Username"
@@ -261,11 +263,11 @@ msgstr "Changer de mot de passe"
#: snikket_web/invite.py:244 #: snikket_web/invite.py:244
msgid "Account data file" msgid "Account data file"
msgstr "" msgstr "Fichier de données du compte"
#: snikket_web/invite.py:248 #: snikket_web/invite.py:248
msgid "Import data" msgid "Import data"
msgstr "" msgstr "Importer les données"
#: snikket_web/invite.py:269 #: snikket_web/invite.py:269
#, python-format #, python-format
@@ -273,6 +275,9 @@ msgid ""
"The account data you tried to import is in an unknown format. Please upload " "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)." "an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr "" msgstr ""
"Les données du compte que vous avez essayé d'importer sont dans un format "
"inconnu. Veuillez télécharger un fichier XML au format XEP-0227 (format "
"fourni : %(mimetype)s)."
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18 #: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178 #: snikket_web/user.py:178
@@ -341,11 +346,11 @@ msgstr "Mettre à jour le profil"
#: snikket_web/user.py:82 #: snikket_web/user.py:82
msgid "Account data" msgid "Account data"
msgstr "" msgstr "Données du compte"
#: snikket_web/user.py:86 #: snikket_web/user.py:86
msgid "Upload" msgid "Upload"
msgstr "" msgstr "Télécharger"
#: snikket_web/user.py:111 #: snikket_web/user.py:111
msgid "Incorrect password." msgid "Incorrect password."
@@ -369,11 +374,11 @@ msgstr "Profil mis à jour"
#: snikket_web/user.py:184 #: snikket_web/user.py:184
msgid "Export" msgid "Export"
msgstr "" msgstr "Exporter"
#: snikket_web/user.py:202 #: snikket_web/user.py:202
msgid "You currently have no account data to export." msgid "You currently have no account data to export."
msgstr "" msgstr "Vous n'avez actuellement aucune donnée de compte à exporter."
#: snikket_web/templates/_footer.html:4 #: snikket_web/templates/_footer.html:4
#, python-format #, python-format
@@ -1014,7 +1019,7 @@ msgstr "Statut du serveur Snikket"
#: snikket_web/templates/admin_system.html:71 #: snikket_web/templates/admin_system.html:71
msgid "Storage used by shared files" msgid "Storage used by shared files"
msgstr "" msgstr "Stockage utilisé par les fichiers partagés"
#: snikket_web/templates/admin_system.html:79 #: snikket_web/templates/admin_system.html:79
msgid "Connected devices" msgid "Connected devices"
@@ -1314,22 +1319,22 @@ msgid ""
"You can now safely close this page, or log in to the web portal to <a href=" "You can now safely close this page, or log in to the web portal to <a href="
"\"%(login_url)s\">manage your account</a>." "\"%(login_url)s\">manage your account</a>."
msgstr "" msgstr ""
"Vous pouvez maintenant fermer cette page en toute sécurité, ou vous "
"connecter au portail web pour <a href=\"%(login_url)s\">gérer votre "
"compte</a>."
#: snikket_web/templates/invite_success.html:21 #: snikket_web/templates/invite_success.html:21
#, fuzzy
#| msgid "Operation successful"
msgid "Import successful" msgid "Import successful"
msgstr "Opération réussie" msgstr "Importation réussie"
#: snikket_web/templates/invite_success.html:22 #: snikket_web/templates/invite_success.html:22
msgid "Congratulations! Your account data has been successfully imported." msgid "Congratulations! Your account data has been successfully imported."
msgstr "" msgstr ""
"Félicitations ! Les données de votre compte ont été importées avec succès."
#: snikket_web/templates/invite_success.html:26 #: snikket_web/templates/invite_success.html:26
#, fuzzy
#| msgid "Using the Snikket app"
msgid "Moving to Snikket?" msgid "Moving to Snikket?"
msgstr "En utilisant lapplication Snikket" msgstr "Nouveau utilisateur Snikket ?"
#: snikket_web/templates/invite_success.html:27 #: snikket_web/templates/invite_success.html:27
msgid "" msgid ""
@@ -1338,10 +1343,15 @@ msgid ""
"information, etc.) from your previous account. When you have exported the " "information, etc.) from your previous account. When you have exported the "
"data from your previous account, upload it using the form below." "data from your previous account, upload it using the form below."
msgstr "" msgstr ""
"Si vous passez d'une autre instance de Snikket ou d'un autre service "
"compatible XMPP, vous pouvez éventuellement importer les données (contacts, "
"informations de profil, etc.) de votre ancien compte. Lorsque vous avez "
"exporté les données de votre ancien compte, téléchargez-les en utilisant le "
"formulaire ci-dessous."
#: snikket_web/templates/invite_success.html:30 #: snikket_web/templates/invite_success.html:30
msgid "Upload account data" msgid "Upload account data"
msgstr "" msgstr "Télécharger les données du compte"
#: snikket_web/templates/invite_view.html:6 #: snikket_web/templates/invite_view.html:6
#, python-format #, python-format
@@ -1396,10 +1406,9 @@ msgstr "Télécharger sur lApp Store"
#: snikket_web/templates/invite_view.html:32 #: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid" msgid "Get it on F-Droid"
msgstr "" msgstr "Obtenez-le sur F-Droid"
#: snikket_web/templates/invite_view.html:35 #: snikket_web/templates/invite_view.html:35
#, fuzzy
msgid "Send to mobile device" msgid "Send to mobile device"
msgstr "Envoyer vers l'appareil" msgstr "Envoyer vers l'appareil"
@@ -1478,10 +1487,14 @@ msgid ""
"After downloading Snikket from the App Store, you have to return to this " "After downloading Snikket from the App Store, you have to return to this "
"invite link and tap on \"Open the app\" to proceed." "invite link and tap on \"Open the app\" to proceed."
msgstr "" msgstr ""
"Après avoir téléchargé Snikket depuis l'App Store, vous devez revenir à ce "
"lien d'invitation et cliquer sur \"Ouvrir l'application\" pour continuer."
#: snikket_web/templates/invite_view.html:101 #: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:" msgid "First download Snikket from the App Store using the button below:"
msgstr "" msgstr ""
"Téléchargez d'abord Snikket depuis l'App Store en utilisant le bouton ci-"
"dessous :"
#: snikket_web/templates/invite_view.html:103 #: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131 #: snikket_web/templates/invite_view.html:131
@@ -1489,6 +1502,9 @@ msgid ""
"After the installation is complete, you can return to this page and tap the " "After the installation is complete, you can return to this page and tap the "
"\"Open the app\" button to continue with the setup:" "\"Open the app\" button to continue with the setup:"
msgstr "" msgstr ""
"Une fois l'installation terminée, vous pouvez revenir à cette page et "
"appuyer sur le bouton \"Ouvrir l'application\" pour poursuivre la "
"configuration :"
#: snikket_web/templates/invite_view.html:121 #: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130 #: snikket_web/templates/invite_view.html:130
@@ -1500,10 +1516,13 @@ msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link " "After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed." "and tap on \"Open the app\" to proceed."
msgstr "" msgstr ""
"Après avoir installé Snikket via F-Droid, vous devez revenir à ce lien "
"d'invitation et appuyer sur \"Ouvrir l'application\" pour continuer."
#: snikket_web/templates/invite_view.html:129 #: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:" msgid "First install Snikket from F-Droid using the button below:"
msgstr "" msgstr ""
"Installez d'abord Snikket depuis F-Droid en utilisant le bouton ci-dessous :"
#: snikket_web/templates/library.j2:18 #: snikket_web/templates/library.j2:18
msgid "Copy link" msgid "Copy link"
@@ -1570,10 +1589,8 @@ msgstr "Éditer votre profil"
#: snikket_web/templates/user_home.html:33 #: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_manage_data.html:4 #: snikket_web/templates/user_manage_data.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage your data" msgid "Manage your data"
msgstr "Gérer les utilisateurs" msgstr "Gérer vos données"
#: snikket_web/templates/user_home.html:39 #: snikket_web/templates/user_home.html:39
msgid "Your Snikket" msgid "Your Snikket"
@@ -1601,16 +1618,16 @@ msgstr ""
"autres appareils connectés." "autres appareils connectés."
#: snikket_web/templates/user_manage_data.html:8 #: snikket_web/templates/user_manage_data.html:8
#, fuzzy
#| msgid "Your account"
msgid "Export account" msgid "Export account"
msgstr "Votre compte" msgstr "Exportation du compte"
#: snikket_web/templates/user_manage_data.html:9 #: snikket_web/templates/user_manage_data.html:9
msgid "" msgid ""
"Download your account data as a file for backup purposes or to move your " "Download your account data as a file for backup purposes or to move your "
"account to another service." "account to another service."
msgstr "" msgstr ""
"Téléchargez les données de votre compte sous forme d'un fichier à des fins "
"de sauvegarde ou pour transférer votre compte vers un autre service."
#: snikket_web/templates/user_passwd.html:5 #: snikket_web/templates/user_passwd.html:5
msgid "Change your password" msgid "Change your password"

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ from quart import (
flash, flash,
current_app, current_app,
) )
import quart.exceptions import werkzeug.exceptions
import wtforms import wtforms
@@ -59,7 +59,7 @@ _ACCESS_MODEL_CHOICES = [
class ProfileForm(BaseForm): class ProfileForm(BaseForm):
nickname = wtforms.TextField( nickname = wtforms.StringField(
_l("Display name"), _l("Display name"),
) )
@@ -96,7 +96,7 @@ async def index() -> str:
@bp.route('/passwd', methods=["GET", "POST"]) @bp.route('/passwd', methods=["GET", "POST"])
@client.require_session() @client.require_session()
async def change_pw() -> typing.Union[str, quart.Response]: async def change_pw() -> typing.Union[str, werkzeug.Response]:
form = ChangePasswordForm() form = ChangePasswordForm()
if form.validate_on_submit(): if form.validate_on_submit():
try: try:
@@ -104,8 +104,8 @@ async def change_pw() -> typing.Union[str, quart.Response]:
form.current_password.data, form.current_password.data,
form.new_password.data, form.new_password.data,
) )
except (quart.exceptions.Unauthorized, except (werkzeug.exceptions.Unauthorized,
quart.exceptions.Forbidden): werkzeug.exceptions.Forbidden):
# server refused current password, set an appropriate error # server refused current password, set an appropriate error
form.current_password.errors.append( form.current_password.errors.append(
_("Incorrect password."), _("Incorrect password."),
@@ -128,7 +128,7 @@ EAVATARTOOBIG = _l(
@bp.route("/profile", methods=["GET", "POST"]) @bp.route("/profile", methods=["GET", "POST"])
@client.require_session() @client.require_session()
async def profile() -> typing.Union[str, quart.Response]: async def profile() -> typing.Union[str, werkzeug.Response]:
max_avatar_size = current_app.config["MAX_AVATAR_SIZE"] max_avatar_size = current_app.config["MAX_AVATAR_SIZE"]
form = ProfileForm() form = ProfileForm()
@@ -221,7 +221,7 @@ async def manage_data() -> typing.Union[str, quart.Response]:
@bp.route("/logout", methods=["GET", "POST"]) @bp.route("/logout", methods=["GET", "POST"])
@client.require_session() @client.require_session()
async def logout() -> typing.Union[quart.Response, str]: async def logout() -> typing.Union[werkzeug.Response, str]:
form = LogoutForm() form = LogoutForm()
if form.validate_on_submit(): if form.validate_on_submit():
await client.logout() await client.logout()

View File

@@ -4,7 +4,7 @@ import typing
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from quart import abort from quart import abort
import quart.exceptions import werkzeug.exceptions
TAG_XMPP_ERROR = "error" TAG_XMPP_ERROR = "error"
@@ -239,7 +239,7 @@ def extract_pubsub_item_get_reply(
) -> typing.Optional[ET.Element]: ) -> typing.Optional[ET.Element]:
try: try:
pubsub = extract_iq_reply(iq_tree, TAG_PUBSUB) pubsub = extract_iq_reply(iq_tree, TAG_PUBSUB)
except quart.exceptions.NotFound: except werkzeug.exceptions.NotFound:
return None return None
if pubsub is None: if pubsub is None: