Compare commits

..

1 Commits

Author SHA1 Message Date
Jonas Schäfer
a1ecb4ce80 Port to WTForms 3.x
Fixes #103.
2022-01-22 15:17:48 +01:00
20 changed files with 216 additions and 1802 deletions

View File

@@ -6,7 +6,8 @@ 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
@@ -16,8 +17,7 @@ COPY babel.cfg /opt/snikket-web-portal/babel.cfg
WORKDIR /opt/snikket-web-portal WORKDIR /opt/snikket-web-portal
RUN set -eu; \ RUN pip3 install -r requirements.txt; \
pip3 install -r requirements.txt; \
pip3 install -r build-requirements.txt; \ pip3 install -r build-requirements.txt; \
make; make;
@@ -33,22 +33,21 @@ 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/
COPY requirements.txt /opt/snikket-web-portal/requirements.txt HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
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 build-essential libpython3-dev netcat; \ python3 python3-pip python3-setuptools python3-wheel; \
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;
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765} WORKDIR /opt/snikket-web-portal
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,3 +1,4 @@
[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

@@ -1,9 +1,9 @@
aiohttp~=3.6 aiohttp~=3.6
quart~=0.17 quart~=0.11,<0.15
flask-wtf~=0.14 flask-wtf~=0.14
hsluv~=5.0 hsluv~=0.0.2
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,8 +18,6 @@ from quart import (
jsonify, jsonify,
) )
import werkzeug.exceptions
import environ import environ
from . import colour, infra from . import colour, infra
@@ -42,7 +40,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, werkzeug.exceptions.HTTPException): except (aiohttp.ClientError, quart.exceptions.HTTPException):
user_info = {} user_info = {}
return { return {
@@ -107,16 +105,16 @@ async def backend_error_handler(exc: Exception) -> quart.Response:
async def generic_http_error( async def generic_http_error(
exc: werkzeug.exceptions.HTTPException, exc: quart.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.code, status=exc.status_code,
description=exc.description, description=exc.description,
name=exc.name, name=exc.name,
), ),
status=exc.code, status=exc.status_code,
) )
@@ -155,7 +153,6 @@ class AppConfig:
"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",
@@ -202,19 +199,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, backend_error_handler, # type:ignore
) )
app.register_error_handler( app.register_error_handler(
werkzeug.exceptions.HTTPException, quart.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, generic_error_handler, # type:ignore
) )
@app.route("/") @app.route("/")
async def index() -> werkzeug.Response: async def index() -> quart.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,12 +7,9 @@ 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,
@@ -94,7 +91,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[werkzeug.Response, str]: async def edit_user(localpart: str) -> typing.Union[quart.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()
@@ -149,7 +146,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, werkzeug.Response]: async def delete_user(localpart: str) -> typing.Union[str, quart.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():
@@ -188,7 +185,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, werkzeug.Response]: ) -> typing.Union[str, quart.Response]:
invite_info = await client.get_invite_by_id( invite_info = await client.get_invite_by_id(
id_, id_,
) )
@@ -280,7 +277,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, werkzeug.Response]: async def invitations() -> typing.Union[str, quart.Response]:
invites = sorted( invites = sorted(
( (
invite invite
@@ -326,7 +323,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, werkzeug.Response]: async def create_invite() -> typing.Union[str, quart.Response]:
form = InvitePost() form = InvitePost()
circles = await client.list_groups() circles = await client.list_groups()
form.circles.choices = [ form.circles.choices = [
@@ -354,7 +351,7 @@ async def create_invite() -> typing.Union[str, werkzeug.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, werkzeug.Response]: async def edit_invite(id_: str) -> typing.Union[str, quart.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:
@@ -420,7 +417,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, werkzeug.Response]: async def create_circle() -> typing.Union[str, quart.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(
@@ -466,7 +463,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, werkzeug.Response]: async def edit_circle(id_: str) -> typing.Union[str, quart.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(
@@ -628,7 +625,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, werkzeug.Response]: async def system() -> typing.Union[str, quart.Response]:
form = AnnouncementForm() form = AnnouncementForm()
if form.validate_on_submit(): if form.validate_on_submit():
@@ -659,7 +656,7 @@ async def system() -> typing.Union[str, werkzeug.Response]:
now = time.time() now = time.time()
try: try:
prosody_metrics = await client.get_system_metrics() prosody_metrics = await client.get_system_metrics()
except werkzeug.exceptions.NotFound: except quart.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

@@ -15,8 +15,6 @@ 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
@@ -48,14 +46,14 @@ def apple_store_badge() -> str:
@bp.context_processor @bp.context_processor
def context() -> typing.Dict[str, typing.Any]: def context() -> typing.Mapping[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) -> werkzeug.Response: async def view_old(id_: str) -> quart.Response:
return redirect(url_for(".view", id_=id_)) return redirect(url_for(".view", id_=id_))
@@ -133,7 +131,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, werkzeug.Response]: async def register(id_: str) -> typing.Union[str, quart.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:
@@ -201,7 +199,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, werkzeug.Response]: async def reset(id_: str) -> typing.Union[str, quart.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:
@@ -302,5 +300,5 @@ async def reset_success() -> str:
@bp.route("/-") @bp.route("/-")
async def index() -> werkzeug.Response: async def index() -> quart.Response:
return redirect(url_for("index")) return redirect(url_for("index"))

View File

@@ -18,8 +18,6 @@ from quart import (
flash, flash,
) )
import werkzeug.exceptions
import babel import babel
import wtforms import wtforms
@@ -34,7 +32,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()],
) )
@@ -50,7 +48,7 @@ class LoginForm(BaseForm):
@bp.route("/-") @bp.route("/-")
async def index() -> werkzeug.Response: async def index() -> quart.Response:
return redirect(url_for("index")) return redirect(url_for("index"))
@@ -58,7 +56,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, werkzeug.Response]: async def login() -> typing.Union[str, quart.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'))
@@ -78,7 +76,7 @@ async def login() -> typing.Union[str, werkzeug.Response]:
password = form.password.data password = form.password.data
try: try:
await client.login(jid, password) await client.login(jid, password)
except werkzeug.exceptions.Unauthorized: except quart.exceptions.Unauthorized:
form.password.errors.append(ERR_CREDENTIALS_INVALID) form.password.errors.append(ERR_CREDENTIALS_INVALID)
else: else:
await flash( await flash(
@@ -97,13 +95,14 @@ async def about() -> str:
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__
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 werkzeug.exceptions.Unauthorized: except quart.exceptions.Unauthorized:
extra_versions["Prosody"] = "unknown" extra_versions["Prosody"] = "unknown"
return await render_template( return await render_template(

View File

@@ -19,9 +19,7 @@ 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 import quart.exceptions
import werkzeug.exceptions
from . import xmpputil from . import xmpputil
from .xmpputil import split_jid from .xmpputil import split_jid
@@ -388,16 +386,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, werkzeug.Response]]]]: typing.Union[T, quart.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, werkzeug.Response]]]: typing.Union[T, quart.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, werkzeug.Response]: ) -> typing.Union[T, quart.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:
@@ -417,17 +415,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, werkzeug.Response]]]]: typing.Union[T, quart.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, werkzeug.Response]]]: typing.Union[T, quart.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, werkzeug.Response]: ) -> typing.Union[T, quart.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.")
@@ -494,7 +492,7 @@ class ProsodyClient:
session=session, session=session,
) )
avatar_hash = avatar_info["sha1"] avatar_hash = avatar_info["sha1"]
except werkzeug.exceptions.HTTPException: except quart.exceptions.HTTPException:
avatar_hash = None avatar_hash = None
return { return {
@@ -646,7 +644,7 @@ class ProsodyClient:
new_access_model, new_access_model,
) )
)) ))
except werkzeug.exceptions.NotFound: except quart.exceptions.NotFound:
if ignore_not_found: if ignore_not_found:
return return
raise raise
@@ -776,7 +774,7 @@ class ProsodyClient:
session: aiohttp.ClientSession, session: aiohttp.ClientSession,
) -> str: ) -> str:
access_models = filter( access_models = filter(
lambda x: not isinstance(x, werkzeug.exceptions.NotFound), lambda x: not isinstance(x, quart.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

@@ -28,12 +28,12 @@
</div> </div>
<div class="f-ebox"> <div class="f-ebox">
{{ form.password.label }} {{ form.password.label }}
{{ form.password(autocomplete="new-password") }} {{ form.password }}
<p class="field-desc weak">{% trans %}Enter a secure password that you do not use anywhere else.{% endtrans %}</p> <p class="field-desc weak">{% trans %}Enter a secure password that you do not use anywhere else.{% endtrans %}</p>
</div> </div>
<div class="f-ebox"> <div class="f-ebox">
{{ form.password_confirm.label }} {{ form.password_confirm.label }}
{{ form.password_confirm(autocomplete="new-password") }} {{ form.password_confirm }}
</div> </div>
<div class="f-bbox"> <div class="f-bbox">
{%- call form_button("done", form.action_register, class="primary") -%}{%- endcall -%} {%- call form_button("done", form.action_register, class="primary") -%}{%- endcall -%}

View File

@@ -17,11 +17,11 @@
{%- call render_errors(form) %}{% endcall -%} {%- call render_errors(form) %}{% endcall -%}
<div class="f-ebox"> <div class="f-ebox">
{{ form.password.label }} {{ form.password.label }}
{{ form.password(autocomplete="new-password") }} {{ form.password }}
</div> </div>
<div class="f-ebox"> <div class="f-ebox">
{{ form.password_confirm.label }} {{ form.password_confirm.label }}
{{ form.password_confirm(autocomplete="new-password") }} {{ form.password_confirm }}
</div> </div>
<div class="f-bbox"> <div class="f-bbox">
{%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%} {%- call form_button("passwd", form.action_reset, class="primary") -%}{%- endcall -%}

View File

@@ -9,15 +9,15 @@
{%- endcall -%} {%- endcall -%}
<div class="f-ebox"> <div class="f-ebox">
{{ form.current_password.label(class="required") }} {{ form.current_password.label(class="required") }}
{{ form.current_password(class=("has-error" if form.current_password.name in form.errors else ""), autocomplete="current-password") }} {{ form.current_password(class=("has-error" if form.current_password.name in form.errors else "")) }}
</div> </div>
<div class="f-ebox"> <div class="f-ebox">
{{ form.new_password.label(class="required") }} {{ form.new_password.label(class="required") }}
{{ form.new_password(autocomplete="new-password") }} {{ form.new_password }}
</div> </div>
<div class="f-ebox"> <div class="f-ebox">
{{ form.new_password_confirm.label(class="required") }} {{ form.new_password_confirm.label(class="required") }}
{{ form.new_password_confirm(class=("has-error" if form.new_password_confirm.name in form.errors else ""), autocomplete="new-password") }} {{ form.new_password_confirm(class=("has-error" if form.new_password_confirm.name in form.errors else "")) }}
</div> </div>
<div class="box warning"> <div class="box warning">
<header>{% trans %}Warning{% endtrans %}</header> <header>{% trans %}Warning{% endtrans %}</header>

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: translations@snikket.org\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n" "POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: 2022-05-30 14:01+0000\n" "PO-Revision-Date: 2021-04-02 19:01+0000\n"
"Last-Translator: Daniel Holmgaard <fovatis@tutanota.com>\n" "Last-Translator: Daniel Holmgaard <annoncer@protonmail.com>\n"
"Language-Team: Danish <http://i18n.sotecware.net/projects/snikket/web-portal/" "Language-Team: Danish <https://i18n.sotecware.net/projects/snikket/web-"
"da/>\n" "portal/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.8.1\n" "X-Generator: Weblate 4.5.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 "Meddelelsens indhold" msgstr ""
#: snikket_web/admin.py:615 #: snikket_web/admin.py:615
msgid "Only send to online users" msgid "Only send to online users"
msgstr "Send kun til online brugere" msgstr ""
#: snikket_web/admin.py:619 #: snikket_web/admin.py:619
msgid "Post to all users" msgid "Post to all users"
msgstr "Send til alle brugere" msgstr ""
#: snikket_web/admin.py:623 #: snikket_web/admin.py:623
msgid "Send preview to yourself" msgid "Send preview to yourself"
msgstr "Send forhåndsvisning til dig selv" msgstr ""
#: snikket_web/admin.py:645 #: snikket_web/admin.py:645
msgid "Announcement sent!" msgid "Announcement sent!"
msgstr "Bekendgørelse sendt!" msgstr ""
#: snikket_web/infra.py:51 #: snikket_web/infra.py:51
msgid "Main" msgid "Main"
@@ -221,8 +221,6 @@ 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"
@@ -263,11 +261,11 @@ msgstr "Ændr adgangskode"
#: snikket_web/invite.py:244 #: snikket_web/invite.py:244
msgid "Account data file" msgid "Account data file"
msgstr "Kontodatafil" msgstr ""
#: snikket_web/invite.py:248 #: snikket_web/invite.py:248
msgid "Import data" msgid "Import data"
msgstr "Importer data" msgstr ""
#: snikket_web/invite.py:269 #: snikket_web/invite.py:269
#, python-format #, python-format
@@ -275,8 +273,6 @@ 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
@@ -345,11 +341,11 @@ msgstr "Opdater profil"
#: snikket_web/user.py:82 #: snikket_web/user.py:82
msgid "Account data" msgid "Account data"
msgstr "Kontodata" msgstr ""
#: snikket_web/user.py:86 #: snikket_web/user.py:86
msgid "Upload" msgid "Upload"
msgstr "Upload" msgstr ""
#: snikket_web/user.py:111 #: snikket_web/user.py:111
msgid "Incorrect password." msgid "Incorrect password."
@@ -373,11 +369,11 @@ msgstr "Profil opdateret"
#: snikket_web/user.py:184 #: snikket_web/user.py:184
msgid "Export" msgid "Export"
msgstr "Exporter" msgstr ""
#: 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 "Du har i øjeblikket ingen kontodata at eksportere." msgstr ""
#: snikket_web/templates/_footer.html:4 #: snikket_web/templates/_footer.html:4
#, python-format #, python-format
@@ -666,7 +662,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 "Brugeren er blevet slettet fra serveren." msgstr ""
#: 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
@@ -880,20 +876,22 @@ 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 "Systemets sundhed" msgstr ""
#: 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 "Vis serverstatus, eller send en udsendelsesmeddelelse til alle brugere." msgstr ""
#: 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 "Send en udsendelsesmeddelelse til alle brugere." msgstr ""
#: 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 system" msgstr "Håndter brugere"
#: 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."
@@ -954,11 +952,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 "Samlet systemstatus" msgstr ""
#: 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 "Systembelastning (5 minutters gennemsnit)" msgstr ""
#: 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
@@ -969,11 +967,11 @@ msgstr "Systembelastning (5 minutters gennemsnit)"
#: 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 "ukendt" msgstr ""
#: snikket_web/templates/admin_system.html:17 #: snikket_web/templates/admin_system.html:17
msgid "Memory use" msgid "Memory use"
msgstr "Forbrug af hukommelse" msgstr ""
#: snikket_web/templates/admin_system.html:20 #: snikket_web/templates/admin_system.html:20
#, python-format #, python-format
@@ -981,56 +979,54 @@ 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 "Webportalens status" msgstr ""
#: 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 "Version" msgstr ""
#: 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 "Vis alle versioner" msgstr ""
#: 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 "Gennemsnitlig CPU-forbrug" msgstr ""
#: 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 "Nuværende hukommelsesbrug" msgstr ""
#: 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 server status" msgstr "Snikket Webportal"
#: 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 "Lagerplads, der bruges af delte filer" msgstr ""
#: snikket_web/templates/admin_system.html:79 #: snikket_web/templates/admin_system.html:79
msgid "Connected devices" msgid "Connected devices"
msgstr "Forbundet enheder" msgstr ""
#: snikket_web/templates/admin_system.html:90 #: snikket_web/templates/admin_system.html:90
msgid "Broadcast message" msgid "Broadcast message"
msgstr "Send besked" msgstr ""
#: 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."
@@ -1304,20 +1300,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 ""
"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 "Importering lykkes" msgstr "Operation 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 "Tillykke! Dine kontodata er blevet importeret." msgstr ""
#: 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 "Flytte til Snikket?" msgstr "Bruger Snikket appen"
#: snikket_web/templates/invite_success.html:27 #: snikket_web/templates/invite_success.html:27
msgid "" msgid ""
@@ -1326,14 +1324,10 @@ 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 "Upload kontodata" msgstr ""
#: snikket_web/templates/invite_view.html:6 #: snikket_web/templates/invite_view.html:6
#, python-format #, python-format
@@ -1564,8 +1558,10 @@ 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 dine data" msgstr "Håndter brugere"
#: snikket_web/templates/user_home.html:39 #: snikket_web/templates/user_home.html:39
msgid "Your Snikket" msgid "Your Snikket"
@@ -1592,16 +1588,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 "Eksporter konto" msgstr "Din 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: translations@snikket.org\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2022-01-17 17:27+0100\n" "POT-Creation-Date: 2022-01-17 17:27+0100\n"
"PO-Revision-Date: 2022-04-11 13:00+0000\n" "PO-Revision-Date: 2021-06-19 15:01+0000\n"
"Last-Translator: David Baraniak <admin@chipmnk.dev>\n" "Last-Translator: Link Mauve <linkmauve@linkmauve.fr>\n"
"Language-Team: French <http://i18n.sotecware.net/projects/snikket/web-portal/" "Language-Team: French <https://i18n.sotecware.net/projects/snikket/web-"
"fr/>\n" "portal/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.8.1\n" "X-Generator: Weblate 4.5.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,8 +221,6 @@ 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"
@@ -263,11 +261,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 "Fichier de données du compte" msgstr ""
#: snikket_web/invite.py:248 #: snikket_web/invite.py:248
msgid "Import data" msgid "Import data"
msgstr "Importer les données" msgstr ""
#: snikket_web/invite.py:269 #: snikket_web/invite.py:269
#, python-format #, python-format
@@ -275,9 +273,6 @@ 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
@@ -346,11 +341,11 @@ msgstr "Mettre à jour le profil"
#: snikket_web/user.py:82 #: snikket_web/user.py:82
msgid "Account data" msgid "Account data"
msgstr "Données du compte" msgstr ""
#: snikket_web/user.py:86 #: snikket_web/user.py:86
msgid "Upload" msgid "Upload"
msgstr "Télécharger" msgstr ""
#: snikket_web/user.py:111 #: snikket_web/user.py:111
msgid "Incorrect password." msgid "Incorrect password."
@@ -374,11 +369,11 @@ msgstr "Profil mis à jour"
#: snikket_web/user.py:184 #: snikket_web/user.py:184
msgid "Export" msgid "Export"
msgstr "Exporter" msgstr ""
#: 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 "Vous n'avez actuellement aucune donnée de compte à exporter." msgstr ""
#: snikket_web/templates/_footer.html:4 #: snikket_web/templates/_footer.html:4
#, python-format #, python-format
@@ -1019,7 +1014,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 "Stockage utilisé par les fichiers partagés" msgstr ""
#: snikket_web/templates/admin_system.html:79 #: snikket_web/templates/admin_system.html:79
msgid "Connected devices" msgid "Connected devices"
@@ -1319,22 +1314,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 "Importation réussie" msgstr "Opération 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 "Nouveau utilisateur Snikket ?" msgstr "En utilisant lapplication Snikket"
#: snikket_web/templates/invite_success.html:27 #: snikket_web/templates/invite_success.html:27
msgid "" msgid ""
@@ -1343,15 +1338,10 @@ 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 "Télécharger les données du compte" msgstr ""
#: snikket_web/templates/invite_view.html:6 #: snikket_web/templates/invite_view.html:6
#, python-format #, python-format
@@ -1406,9 +1396,10 @@ 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 "Obtenez-le sur F-Droid" msgstr ""
#: 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"
@@ -1487,14 +1478,10 @@ 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
@@ -1502,9 +1489,6 @@ 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
@@ -1516,13 +1500,10 @@ 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"
@@ -1589,8 +1570,10 @@ 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 vos données" msgstr "Gérer les utilisateurs"
#: snikket_web/templates/user_home.html:39 #: snikket_web/templates/user_home.html:39
msgid "Your Snikket" msgid "Your Snikket"
@@ -1618,16 +1601,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 "Exportation du compte" msgstr "Votre 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,204 +8,204 @@ 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-05-30 20:51+0200\n" "POT-Creation-Date: 2022-01-17 17:27+0100\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.10.1\n" "Generated-By: Babel 2.9.1\n"
#: snikket_web/admin.py:70 snikket_web/templates/admin_delete_user.html:10 #: snikket_web/admin.py:68 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:74 snikket_web/templates/admin_delete_user.html:12 #: snikket_web/admin.py:72 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:78 snikket_web/templates/admin_edit_user.html:32 #: snikket_web/admin.py:76 snikket_web/templates/admin_edit_user.html:32
msgid "Access Level" msgid "Access Level"
msgstr "" msgstr ""
#: snikket_web/admin.py:80 #: snikket_web/admin.py:78
msgid "Limited" msgid "Limited"
msgstr "" msgstr ""
#: snikket_web/admin.py:81 #: snikket_web/admin.py:79
msgid "Normal user" msgid "Normal user"
msgstr "" msgstr ""
#: snikket_web/admin.py:82 #: snikket_web/admin.py:80
msgid "Administrator" msgid "Administrator"
msgstr "" msgstr ""
#: snikket_web/admin.py:87 #: snikket_web/admin.py:85
msgid "Update user" msgid "Update user"
msgstr "" msgstr ""
#: snikket_web/admin.py:91 #: snikket_web/admin.py:89
msgid "Create password reset link" msgid "Create password reset link"
msgstr "" msgstr ""
#: snikket_web/admin.py:109 #: snikket_web/admin.py:107
msgid "Password reset link created" msgid "Password reset link created"
msgstr "" msgstr ""
#: snikket_web/admin.py:124 #: snikket_web/admin.py:122
msgid "User information updated." msgid "User information updated."
msgstr "" msgstr ""
#: snikket_web/admin.py:146 #: snikket_web/admin.py:144
msgid "Delete user permanently" msgid "Delete user permanently"
msgstr "" msgstr ""
#: snikket_web/admin.py:159 #: snikket_web/admin.py:157
msgid "User deleted" msgid "User deleted"
msgstr "" msgstr ""
#: snikket_web/admin.py:197 #: snikket_web/admin.py:195
msgid "Password reset link not found" msgid "Password reset link not found"
msgstr "" msgstr ""
#: snikket_web/admin.py:209 #: snikket_web/admin.py:207
msgid "Password reset link deleted" msgid "Password reset link deleted"
msgstr "" msgstr ""
#: snikket_web/admin.py:229 #: snikket_web/admin.py:227
msgid "Invite to circle" msgid "Invite to circle"
msgstr "" msgstr ""
#: snikket_web/admin.py:235 #: snikket_web/admin.py:233
msgid "At least one circle must be selected" msgid "At least one circle must be selected"
msgstr "" msgstr ""
#: snikket_web/admin.py:240 #: snikket_web/admin.py:238
msgid "Valid for" msgid "Valid for"
msgstr "" msgstr ""
#: snikket_web/admin.py:242 #: snikket_web/admin.py:240
msgid "One hour" msgid "One hour"
msgstr "" msgstr ""
#: snikket_web/admin.py:243 #: snikket_web/admin.py:241
msgid "Twelve hours" msgid "Twelve hours"
msgstr "" msgstr ""
#: snikket_web/admin.py:244 #: snikket_web/admin.py:242
msgid "One day" msgid "One day"
msgstr "" msgstr ""
#: snikket_web/admin.py:245 #: snikket_web/admin.py:243
msgid "One week" msgid "One week"
msgstr "" msgstr ""
#: snikket_web/admin.py:246 #: snikket_web/admin.py:244
msgid "Four weeks" msgid "Four weeks"
msgstr "" msgstr ""
#: snikket_web/admin.py:252 snikket_web/templates/admin_edit_invite.html:17 #: snikket_web/admin.py:250 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type" msgid "Invitation type"
msgstr "" msgstr ""
#: snikket_web/admin.py:254 snikket_web/templates/library.j2:116 #: snikket_web/admin.py:252 snikket_web/templates/library.j2:116
msgid "Individual" msgid "Individual"
msgstr "" msgstr ""
#: snikket_web/admin.py:255 snikket_web/templates/library.j2:114 #: snikket_web/admin.py:253 snikket_web/templates/library.j2:114
msgid "Group" msgid "Group"
msgstr "" msgstr ""
#: snikket_web/admin.py:261 #: snikket_web/admin.py:259
msgid "New invitation link" msgid "New invitation link"
msgstr "" msgstr ""
#: snikket_web/admin.py:323 #: snikket_web/admin.py:321
msgid "Revoke" msgid "Revoke"
msgstr "" msgstr ""
#: snikket_web/admin.py:347 #: snikket_web/admin.py:345
msgid "Invitation created" msgid "Invitation created"
msgstr "" msgstr ""
#: snikket_web/admin.py:363 #: snikket_web/admin.py:361
msgid "No such invitation exists" msgid "No such invitation exists"
msgstr "" msgstr ""
#: snikket_web/admin.py:378 #: snikket_web/admin.py:376
msgid "Invitation revoked" msgid "Invitation revoked"
msgstr "" msgstr ""
#: snikket_web/admin.py:395 snikket_web/admin.py:443 #: snikket_web/admin.py:393 snikket_web/admin.py:441
msgid "Name" msgid "Name"
msgstr "" msgstr ""
#: snikket_web/admin.py:400 snikket_web/templates/admin_circles.html:47 #: snikket_web/admin.py:398 snikket_web/templates/admin_circles.html:47
msgid "Create circle" msgid "Create circle"
msgstr "" msgstr ""
#: snikket_web/admin.py:430 #: snikket_web/admin.py:428
msgid "Circle created" msgid "Circle created"
msgstr "" msgstr ""
#: snikket_web/admin.py:448 #: snikket_web/admin.py:446
msgid "Select user" msgid "Select user"
msgstr "" msgstr ""
#: snikket_web/admin.py:453 #: snikket_web/admin.py:451
msgid "Update circle" msgid "Update circle"
msgstr "" msgstr ""
#: snikket_web/admin.py:457 #: snikket_web/admin.py:455
msgid "Delete circle permanently" msgid "Delete circle permanently"
msgstr "" msgstr ""
#: snikket_web/admin.py:463 #: snikket_web/admin.py:461
msgid "Add user" msgid "Add user"
msgstr "" msgstr ""
#: snikket_web/admin.py:479 #: snikket_web/admin.py:477
msgid "No such circle exists" msgid "No such circle exists"
msgstr "" msgstr ""
#: snikket_web/admin.py:516 #: snikket_web/admin.py:514
msgid "Circle data updated" msgid "Circle data updated"
msgstr "" msgstr ""
#: snikket_web/admin.py:522 #: snikket_web/admin.py:520
msgid "Circle deleted" msgid "Circle deleted"
msgstr "" msgstr ""
#: snikket_web/admin.py:533 #: snikket_web/admin.py:531
msgid "User added to circle" msgid "User added to circle"
msgstr "" msgstr ""
#: snikket_web/admin.py:542 #: snikket_web/admin.py:540
msgid "User removed from circle" msgid "User removed from circle"
msgstr "" msgstr ""
#: snikket_web/admin.py:611 #: snikket_web/admin.py:609
msgid "Message contents" msgid "Message contents"
msgstr "" msgstr ""
#: snikket_web/admin.py:617 #: snikket_web/admin.py:615
msgid "Only send to online users" msgid "Only send to online users"
msgstr "" msgstr ""
#: snikket_web/admin.py:621 #: snikket_web/admin.py:619
msgid "Post to all users" msgid "Post to all users"
msgstr "" msgstr ""
#: snikket_web/admin.py:625 #: snikket_web/admin.py:623
msgid "Send preview to yourself" msgid "Send preview to yourself"
msgstr "" msgstr ""
#: snikket_web/admin.py:647 #: snikket_web/admin.py:645
msgid "Announcement sent!" msgid "Announcement sent!"
msgstr "" msgstr ""
@@ -213,82 +213,82 @@ msgstr ""
msgid "Main" msgid "Main"
msgstr "" msgstr ""
#: snikket_web/invite.py:35 #: snikket_web/invite.py:33
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:114 #: snikket_web/invite.py:112
msgid "Username" msgid "Username"
msgstr "" msgstr ""
#: snikket_web/invite.py:118 snikket_web/invite.py:186 snikket_web/main.py:43 #: snikket_web/invite.py:116 snikket_web/invite.py:184 snikket_web/main.py:41
msgid "Password" msgid "Password"
msgstr "" msgstr ""
#: snikket_web/invite.py:122 snikket_web/invite.py:190 #: snikket_web/invite.py:120 snikket_web/invite.py:188
msgid "Confirm password" msgid "Confirm password"
msgstr "" msgstr ""
#: snikket_web/invite.py:126 snikket_web/invite.py:194 #: snikket_web/invite.py:124 snikket_web/invite.py:192
msgid "The passwords must match." msgid "The passwords must match."
msgstr "" msgstr ""
#: snikket_web/invite.py:131 #: snikket_web/invite.py:129
msgid "Create account" msgid "Create account"
msgstr "" msgstr ""
#: snikket_web/invite.py:158 #: snikket_web/invite.py:156
msgid "That username is already taken." msgid "That username is already taken."
msgstr "" msgstr ""
#: snikket_web/invite.py:162 snikket_web/invite.py:227 #: snikket_web/invite.py:160 snikket_web/invite.py:225
msgid "Registration was declined for unknown reasons." msgid "Registration was declined for unknown reasons."
msgstr "" msgstr ""
#: snikket_web/invite.py:166 #: snikket_web/invite.py:164
msgid "The username is not valid." msgid "The username is not valid."
msgstr "" msgstr ""
#: snikket_web/invite.py:199 snikket_web/templates/user_home.html:32 #: snikket_web/invite.py:197 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:246 #: snikket_web/invite.py:244
msgid "Account data file" msgid "Account data file"
msgstr "" msgstr ""
#: snikket_web/invite.py:250 #: snikket_web/invite.py:248
msgid "Import data" msgid "Import data"
msgstr "" msgstr ""
#: snikket_web/invite.py:271 #: snikket_web/invite.py:269
#, 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:291 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
msgid "Error" msgid "Error"
msgstr "" msgstr ""
#: snikket_web/main.py:38 #: snikket_web/main.py:36
msgid "Address" msgid "Address"
msgstr "" msgstr ""
#: snikket_web/main.py:48 #: snikket_web/main.py:46
msgid "Sign in" msgid "Sign in"
msgstr "" msgstr ""
#: snikket_web/main.py:57 #: snikket_web/main.py:55
msgid "Invalid username or password." msgid "Invalid username or password."
msgstr "" msgstr ""
#: snikket_web/main.py:85 #: snikket_web/main.py:83
msgid "Login successful!" msgid "Login successful!"
msgstr "" msgstr ""

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 werkzeug.exceptions import quart.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, werkzeug.Response]: async def change_pw() -> typing.Union[str, quart.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, werkzeug.Response]:
form.current_password.data, form.current_password.data,
form.new_password.data, form.new_password.data,
) )
except (werkzeug.exceptions.Unauthorized, except (quart.exceptions.Unauthorized,
werkzeug.exceptions.Forbidden): quart.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, werkzeug.Response]: async def profile() -> typing.Union[str, quart.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[werkzeug.Response, str]: async def logout() -> typing.Union[quart.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 werkzeug.exceptions import quart.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 werkzeug.exceptions.NotFound: except quart.exceptions.NotFound:
return None return None
if pubsub is None: if pubsub is None: