Compare commits

..

1 Commits

Author SHA1 Message Date
Matthew Wild
fda822e9d9 Initial implementation of /policies page 2022-03-21 15:51:58 +00:00
17 changed files with 344 additions and 1781 deletions

View File

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

View File

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

View File

@@ -18,8 +18,6 @@ from quart import (
jsonify,
)
import werkzeug.exceptions
import environ
from . import colour, infra
@@ -42,7 +40,7 @@ async def proc() -> typing.Dict[str, typing.Any]:
try:
user_info = await infra.client.get_user_info()
except (aiohttp.ClientError, werkzeug.exceptions.HTTPException):
except (aiohttp.ClientError, quart.exceptions.HTTPException):
user_info = {}
return {
@@ -107,16 +105,16 @@ async def backend_error_handler(exc: Exception) -> quart.Response:
async def generic_http_error(
exc: werkzeug.exceptions.HTTPException,
exc: quart.exceptions.HTTPException,
) -> quart.Response:
return quart.Response(
await render_template(
"generic_http_error.html",
status=exc.code,
status=exc.status_code,
description=exc.description,
name=exc.name,
),
status=exc.code,
status=exc.status_code,
)
@@ -155,7 +153,6 @@ class AppConfig:
"it",
"pl",
"sv",
"zh_Hans_CN",
], converter=autosplit)
apple_store_url = environ.var(
"https://apps.apple.com/us/app/snikket/id1545164189",
@@ -166,6 +163,9 @@ class AppConfig:
# tools may also very well override it.
max_avatar_size = environ.var(1024*1024, converter=int)
show_metrics = environ.bool_var(True)
retention_days = environ.var(7, converter=int, name="SNIKKET_RETENTION_DAYS")
operator_name = environ.var(None, name="SNIKKET_OPERATOR_NAME")
provider_name = environ.var(None, name="SNIKKET_PROVIDER_NAME")
_UPPER_CASE = "".join(map(chr, range(ord("A"), ord("Z")+1)))
@@ -198,23 +198,26 @@ def create_app() -> quart.Quart:
app.config["APPLE_STORE_URL"] = config.apple_store_url
app.config["MAX_AVATAR_SIZE"] = config.max_avatar_size
app.config["SHOW_METRICS"] = config.show_metrics
app.config["RETENTION_DAYS"] = config.retention_days
app.config["OPERATOR_NAME"] = config.operator_name
app.config["PROVIDER_NAME"] = config.provider_name
app.context_processor(proc)
app.register_error_handler(
aiohttp.ClientConnectorError,
backend_error_handler,
backend_error_handler, # type:ignore
)
app.register_error_handler(
werkzeug.exceptions.HTTPException,
quart.exceptions.HTTPException,
generic_http_error, # type:ignore
)
app.register_error_handler(
Exception,
generic_error_handler,
generic_error_handler, # type:ignore
)
@app.route("/")
async def index() -> werkzeug.Response:
async def index() -> quart.Response:
if infra.client.has_session:
return redirect(url_for('user.index'))

View File

@@ -7,8 +7,6 @@ from datetime import datetime
import aiohttp
import werkzeug.exceptions
import quart.flask_patch
import wtforms
@@ -94,7 +92,7 @@ class EditUserForm(BaseForm):
@bp.route("/user/<localpart>/", methods=["GET", "POST"])
@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)
form = EditUserForm()
@@ -149,7 +147,7 @@ class DeleteUserForm(BaseForm):
@bp.route("/user/<localpart>/delete", methods=["GET", "POST"])
@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)
form = DeleteUserForm()
if form.validate_on_submit():
@@ -188,7 +186,7 @@ async def debug_user(localpart: str) -> typing.Union[str, quart.Response]:
@client.require_admin_session()
async def user_password_reset_link(
id_: str,
) -> typing.Union[str, werkzeug.Response]:
) -> typing.Union[str, quart.Response]:
invite_info = await client.get_invite_by_id(
id_,
)
@@ -280,7 +278,7 @@ class InvitePost(BaseForm):
@bp.route("/invitations", methods=["GET", "POST"])
@client.require_admin_session()
async def invitations() -> typing.Union[str, werkzeug.Response]:
async def invitations() -> typing.Union[str, quart.Response]:
invites = sorted(
(
invite
@@ -326,7 +324,7 @@ class InviteForm(BaseForm):
@bp.route("/invitation/-/new", methods=["POST"])
@client.require_admin_session()
async def create_invite() -> typing.Union[str, werkzeug.Response]:
async def create_invite() -> typing.Union[str, quart.Response]:
form = InvitePost()
circles = await client.list_groups()
form.circles.choices = [
@@ -354,7 +352,7 @@ async def create_invite() -> typing.Union[str, werkzeug.Response]:
@bp.route("/invitation/<id_>", methods=["GET", "POST"])
@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:
invite_info = await client.get_invite_by_id(id_)
except aiohttp.ClientResponseError as exc:
@@ -420,7 +418,7 @@ async def circles() -> str:
@bp.route("/circle/-/new", methods=["POST"])
@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()
if create_form.validate_on_submit():
circle = await client.create_group(
@@ -466,7 +464,7 @@ class EditCircleForm(BaseForm):
@bp.route("/circle/<id_>", methods=["GET", "POST"])
@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:
try:
circle = await client.get_group_by_id(
@@ -628,7 +626,7 @@ class AnnouncementForm(BaseForm):
@bp.route("/system/", methods=["GET", "POST"])
@client.require_admin_session()
async def system() -> typing.Union[str, werkzeug.Response]:
async def system() -> typing.Union[str, quart.Response]:
form = AnnouncementForm()
if form.validate_on_submit():
@@ -659,7 +657,7 @@ async def system() -> typing.Union[str, werkzeug.Response]:
now = time.time()
try:
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
prosody_metrics = {}

View File

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

View File

@@ -18,8 +18,6 @@ from quart import (
flash,
)
import werkzeug.exceptions
import babel
import wtforms
@@ -50,7 +48,7 @@ class LoginForm(BaseForm):
@bp.route("/-")
async def index() -> werkzeug.Response:
async def index() -> quart.Response:
return redirect(url_for("index"))
@@ -58,7 +56,7 @@ ERR_CREDENTIALS_INVALID = _l("Invalid username or password.")
@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()):
return redirect(url_for('user.index'))
@@ -78,7 +76,7 @@ async def login() -> typing.Union[str, werkzeug.Response]:
password = form.password.data
try:
await client.login(jid, password)
except werkzeug.exceptions.Unauthorized:
except quart.exceptions.Unauthorized:
form.password.errors.append(ERR_CREDENTIALS_INVALID)
else:
await flash(
@@ -97,13 +95,14 @@ async def about() -> str:
if current_app.debug or client.is_admin_session:
version = _version.version
extra_versions["Quart"] = quart.__version__
extra_versions["aiohttp"] = aiohttp.__version__
extra_versions["babel"] = babel.__version__
extra_versions["wtforms"] = wtforms.__version__
extra_versions["flask-wtf"] = flask_wtf.__version__
try:
extra_versions["Prosody"] = await client.get_server_version()
except werkzeug.exceptions.Unauthorized:
except quart.exceptions.Unauthorized:
extra_versions["Prosody"] = "unknown"
return await render_template(
@@ -112,6 +111,16 @@ async def about() -> str:
extra_versions=extra_versions,
)
@bp.route("/policies")
async def policies() -> str:
return await render_template(
"policies.html",
snikket_domain=current_app.config["SNIKKET_DOMAIN"],
retention_days=current_app.config["RETENTION_DAYS"],
operator_name=current_app.config["OPERATOR_NAME"],
provider_name=current_app.config["PROVIDER_NAME"],
)
@bp.route("/meta/demo.html")
async def demo() -> str:

View File

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

View File

@@ -0,0 +1,131 @@
{% extends "base.html" %}
{% from "library.j2" import standard_button %}
{% block head_lead %}
<title>{% trans %}Service Policies{% endtrans %}</title>
{% endblock %}
{% block body %}
<main>
<div class="box el-2">
<h1>{% trans %}Service Policies{% endtrans %}</h1>
<p>{% trans %}Here you will find the policies and legal notices that govern your use of the {{ snikket_domain }} communication service.{% endtrans %}</p>
<p>{% trans %}If you do not agree to these policies, or are below the minimum age required to understand and consent to
these terms, you must not use this service.{% endtrans %}</p>
<p>{% trans %}This page contains the following policies:{% endtrans %}</p>
<ul>
<li><strong><a href="#tos">{% trans %}Terms of Service{% endtrans %}</a></strong> {% trans %}The terms describe the acceptable use of our service, what we expect from you and what you can expect from us.{% endtrans %}</li>
<li><strong><a href="#privacy">{% trans %}Privacy Policy{% endtrans %}</a></strong> {% trans %}The what, why and how we handle your personal data here on {{ snikket_domain }}.{% endtrans %}</li>
</ul>
<h2 id="tos">{% trans %}Terms of Service{% endtrans %}</h2>
<h3>{% trans %}1. Introduction{% endtrans %}</h3>
<p>{% trans %}Snikket is a privacy-oriented communication and messaging system that is designed to give you freedom of choice, privacy, and control over your communication and your data.{% endtrans %}</p>
<p>{% trans %}The Snikket software allows anyone to set up their own online communication service, and connect it to other services in the network. There is a global network of Snikket services online run by independent operators. When you read about the "Service" in this document, it refers to this particular Snikket messaging and communication service available at the internet address <em>{{ snikket_domain }}</em>.{% endtrans %}</p>
{%- if operator_name and provider_name -%}
<p>{% trans %}The Service is operated by <em>{{ operator_name }}</em> (us, the "Operator"), using facilities provided by <em>{{ provider_name }}</em>.{% endtrans %}</p>
{%- elif operator_name -%}
<p>{% trans %}The Service is operated by <em>{{ operator_name }}</em> (us, the "Operator").{% endtrans %}</p>
{%- else -%}
<p>{% trans %}The Service is operated privately by us (the "Operator").{% endtrans %}</p>
{%- endif -%}
<p>{% trans %}The Service is using software developed by <em>Snikket Community Interest Company</em> and community contributors (collectively "the Developers"). The Developers are not associated with this Service, and they are not responsible for its reliability, security, maintenance, messages it sends, content it hosts, or the actions and activities of the Operator and users of the Service.{% endtrans %}</p>
<p>{% trans %}"Snikket" and the parrot logo are trademarks of Snikket Community Interest Company.{% endtrans %}</p>
<h3>{% trans %}2. Your Data{% endtrans %}</h3>
<p>{% trans %}Certain data that you provide to us may be stored on your behalf to provide you with a secure, reliable and pleasant communication experience. You can request a copy of your data from the Operator at any time, and you can close your account if you no longer wish to use our service. For more information about the data we store, how long we store it for, and the purposes we store it for, please see the <a href="#privacy">Privacy Policy</a>.{% endtrans %}</p>
<p><strong>{% trans %}Legal basis for data processing.{% endtrans %}</strong> {% trans %}We process your data on the basis of Legitimate Interest. This means that we process your data only as necessary to deliver the Service, and in a manner that you understand and expect. This Legitimate Interest pertains to providing you with a secure communication service through which you may exchange messages, files and other data with other users and services. The processing of user data we undertake is necessary to provide this service.{% endtrans %}</p>
<p><strong>{% trans %}Third parties.{% endtrans %}</strong> {% trans %}Note well that, according to the nature of an open communication network, certain data you exchange with others (including messages and files) in the course of using the Service may be shared with, and possibly stored by, other users and their service operators on the basis of Legitimate Interest or any other applicable legal basis. We have no control over such copies of the data.{% endtrans %}</p>
<h3>{% trans %}3. Third-party services{% endtrans %}</h3>
<p><strong>{% trans %}Communication with third-party services.{% endtrans %}</strong> {% trans %}This Service is part of a global messaging network facilitated by standard technologies such as XMPP. In a similar fashion to the email and phone networks, this network allows people to communicate with each other even if they are using different services managed by different operators. When you communicate with users and groups that reside on other services, certain data may necessarily be exchanged with those services for the purposes of facilitating your communication. This includes your username, profile (e.g. display name and picture), messages and files that you send to the users and groups on those services.{% endtrans %}</p>
<p><strong>{% trans %}Third-party policies.{% endtrans %}</strong> {% trans %}Our Service may allow you to access, use, or interact with third-party websites, apps, content, and other products and services. When you use third-party services, their terms and privacy policies govern your use of those services.{% endtrans %}</p>
<p><strong>{% trans %}Right to be forgotten.{% endtrans %}</strong> {% trans %}Your copy of your data on {{ snikket_domain }} will be erased upon your request to us. You may also make such requests to the operators of third-party services you have communicated with, however these services are not under our control and we cannot guarantee they will forget your data. Such services can be located anywhere in the world, and are subject to local laws and regulations.{% endtrans %}</p>
<h3>{% trans %}4. Acceptable use{% endtrans %}</h3>
<p><strong>{% trans %}Legal and acceptable purposes.{% endtrans %}</strong> {% trans %}You agree to access and use the Service only for legal, authorized, and acceptable purposes. You will not use (or assist others in using) our Service in ways that: (a) violate or infringe the rights of the Operator, users, or others, including privacy, publicity, intellectual property, or other proprietary rights; (b) involve sending illegal or impermissible communications such as unsolicited bulk communications (e.g. spam).{% endtrans %}</p>
<p><strong>{% trans %}Encryption.{% endtrans %}</strong> {% trans %}In the event that you wish to use encrypted communications within the Services, it is your responsibility to ensure this is permitted in under the laws and regulations applicable to you based on where and how you use the Services.{% endtrans %}</p>
<h3>{% trans %}5. Availability of Services{% endtrans %}</h3>
<p><strong>{% trans %}General availability{% endtrans %}</strong> {% trans %}Our Services may be interrupted, including for maintenance, upgrades, or network or equipment failures. We may discontinue some or all of our Services, including certain features and the support for certain devices and platforms, at any time.{% endtrans %}</p>
<p><strong>{% trans %}Termination of access.{% endtrans %}</strong> {% trans %}We may remove your access to the Service at any time, at our sole discretion.{% endtrans %}</p>
<p><strong>{% trans %}Emergency services inaccessibility.{% endtrans %}</strong> {% trans %}The Communication Service is not to be used to make calls to any emergency services. Therefore you must arrange for other communications such as though a mobile phone or otherwise to enable you to contact any emergency services. We disclaim any liability relating to the inability to use the Communication Services in this way.{% endtrans %}</p>
<hr/>
<h2 id="privacy">{% trans %}Privacy Policy{% endtrans %}</h2>
<h3 id="what-information-does-a-snikket-service-collect">{% trans %}What information does a Snikket service collect?{% endtrans %}</h3>
<h4 id="basic-account-information">{% trans %}Basic account information{% endtrans %}</h4>
<p>{% trans %}When you create an account on this service, your username will be stored, along with a hashed version of your password.{% endtrans %}</p>
<p>{% trans %}You may additionally provide a profile picture (avatar) and display name. These will be shared with other users on the network, so they are able to identify you. You can control visibility of this information in the profile section of the {{ snikket_domain }} website.{% endtrans %}</p>
<p>{% trans %}Contacts that you add within the app will be stored in your contact list on the {{ snikket_domain }} server. This is so that the server can identify who you have permitted to view your online status, profile and other information, and to synchronize your Snikket contacts if you have multiple apps or devices.{% endtrans %}</p>
<h4 id="messages">{% trans %}Messages{% endtrans %}</h4>
<p>{% trans %}When you send or receive a message on Snikket, we store this temporarily in your personal &ldquo;message archive&rdquo; on {{ snikket_domain }}. The purpose of your message archive is to enable an app you use with your account to &ldquo;catch up&rdquo; on recent conversations. This allows Snikket to:{% endtrans %}</p>
<ul>
<li>{% trans %}ensure delivery of messages even if you are temporarily offline or
experiencing connectivity issues, and{% endtrans %}</li>
<li>{% trans %}allow synchronization of messages across multiple devices and apps
that you may use.{% endtrans %}</li>
</ul>
<p>{% trans %}The data stored for each entry in the message archive is:{% endtrans %}</p>
<ul>
<li>{% trans %}A unique identifier for the message{% endtrans %}</li>
<li>{% trans %}The time and date that the message was sent/received{% endtrans %}</li>
<li>{% trans %}The sender and recipient of the message{% endtrans %}</li>
<li>{% trans %}The message contents (encrypted according to your app&rsquo;s settings){% endtrans %}</li>
</ul>
<p>{% trans %}Entries in the message archive are stored for a minimum of {{ retention_days }} days. The server will routinely erase all entries after they have been in the archive for this amount of time.{% endtrans %}</p>
<p>{% trans %}We encourage the use of encryption of your message contents, as is the default within the Snikket app.{% endtrans %}</p>
<h4 id="uploaded-files">{% trans %}Uploaded files{% endtrans %}</h4>
<p>{% trans %}You may also use the server to upload files (including images and videos) within your conversations. These files will remain on the server for a minimum of {{ retention_days }} days. This allows your contacts time to retrieve the file, even if they are offline. Similarly to message archives, the server will routinely erase files beyond this age.{% endtrans %}</p>
<p>{% trans %}Uploaded files are assigned a long random identifier, included in the link to the file. This ensures your files can only be viewed by people you share the link with.{% endtrans %}</p>
<p>{% trans %}The server will store the following information for every shared file:{% endtrans %}</p>
<ul>
<li>{% trans %}A unique identifier{% endtrans %}</li>
<li>{% trans %}The time and date that the file was uploaded{% endtrans %}</li>
<li>{% trans %}The file name{% endtrans %}</li>
<li>{% trans %}The file size{% endtrans %}</li>
<li>{% trans %}The file type (as reported by the app){% endtrans %}</li>
<li>{% trans %}The file contents (encrypted according to your app&rsquo;s settings){% endtrans %}</li>
</ul>
<p>{% trans %}The Snikket app will automatically encrypt file contents when sharing a file within an encrypted conversation.{% endtrans %}</p>
<p>{% trans %}Once you share a file with a contact, understand that the contact may store a copy of the file on their device that is beyond our control and may remain even after the file is removed from {{ snikket_domain }}.{% endtrans %}</p>
<h4 id="access-and-network-information">{% trans %}Access and network information{% endtrans %}</h4>
<p>{% trans %}The Snikket server may record the time and general location from which you connect to your account or perform certain security-related actions, such as changing your password.{% endtrans %}</p>
<p>{% trans %}This is to help identify unauthorized access to your account, and detect when your account becomes inactive for administrative purposes (for example, so that it may be erased when no longer needed).{% endtrans %}</p>
<h4 id="cookies">{% trans %}Cookies{% endtrans %}</h4>
<p>{% trans %}When you access your account through the {{ snikket_domain }} website, one or more small pieces of data known as &ldquo;cookies&rdquo; may be stored in your web browser. These essential cookies allow us to securely identify your browser as you move between different pages on {{ snikket_domain }}, and therefore protect your account from unauthorized access. The cookies are not shared with third-parties or used for tracking, advertising or any such purposes.{% endtrans %}</p>
</div>
</main>
{% endblock %}

View File

@@ -6,18 +6,18 @@
msgid ""
msgstr ""
"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"
"PO-Revision-Date: 2022-05-30 14:01+0000\n"
"Last-Translator: Daniel Holmgaard <fovatis@tutanota.com>\n"
"Language-Team: Danish <http://i18n.sotecware.net/projects/snikket/web-portal/"
"da/>\n"
"PO-Revision-Date: 2021-04-02 19:01+0000\n"
"Last-Translator: Daniel Holmgaard <annoncer@protonmail.com>\n"
"Language-Team: Danish <https://i18n.sotecware.net/projects/snikket/web-"
"portal/da/>\n"
"Language: da\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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"
#: 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
msgid "Message contents"
msgstr "Meddelelsens indhold"
msgstr ""
#: snikket_web/admin.py:615
msgid "Only send to online users"
msgstr "Send kun til online brugere"
msgstr ""
#: snikket_web/admin.py:619
msgid "Post to all users"
msgstr "Send til alle brugere"
msgstr ""
#: snikket_web/admin.py:623
msgid "Send preview to yourself"
msgstr "Send forhåndsvisning til dig selv"
msgstr ""
#: snikket_web/admin.py:645
msgid "Announcement sent!"
msgstr "Bekendgørelse sendt!"
msgstr ""
#: snikket_web/infra.py:51
msgid "Main"
@@ -221,8 +221,6 @@ msgid ""
"The account data you tried to import is too large to upload. Please contact "
"your Snikket operator."
msgstr ""
"De kontodata, du forsøgte at importere, er for store til at uploade. Kontakt "
"din Snikket-operatør."
#: snikket_web/invite.py:112
msgid "Username"
@@ -263,11 +261,11 @@ msgstr "Ændr adgangskode"
#: snikket_web/invite.py:244
msgid "Account data file"
msgstr "Kontodatafil"
msgstr ""
#: snikket_web/invite.py:248
msgid "Import data"
msgstr "Importer data"
msgstr ""
#: snikket_web/invite.py:269
#, python-format
@@ -275,8 +273,6 @@ msgid ""
"The account data you tried to import is in an unknown format. Please upload "
"an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
"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/user.py:178
@@ -345,11 +341,11 @@ msgstr "Opdater profil"
#: snikket_web/user.py:82
msgid "Account data"
msgstr "Kontodata"
msgstr ""
#: snikket_web/user.py:86
msgid "Upload"
msgstr "Upload"
msgstr ""
#: snikket_web/user.py:111
msgid "Incorrect password."
@@ -373,11 +369,11 @@ msgstr "Profil opdateret"
#: snikket_web/user.py:184
msgid "Export"
msgstr "Exporter"
msgstr ""
#: snikket_web/user.py:202
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
#, python-format
@@ -666,7 +662,7 @@ msgstr "Cirkel medlemmer"
#: snikket_web/templates/admin_edit_circle.html:71
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/library.j2:108
@@ -880,20 +876,22 @@ msgstr "Håndter invitationer"
#: snikket_web/templates/admin_home.html:35
msgid "System health"
msgstr "Systemets sundhed"
msgstr ""
#: snikket_web/templates/admin_home.html:38
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
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_system.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage system"
msgstr "Håndter system"
msgstr "Håndter brugere"
#: snikket_web/templates/admin_home.html:48
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
msgid "Overall system status"
msgstr "Samlet systemstatus"
msgstr ""
#: snikket_web/templates/admin_system.html:9
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:22
@@ -969,11 +967,11 @@ msgstr "Systembelastning (5 minutters gennemsnit)"
#: snikket_web/templates/admin_system.html:76
#: snikket_web/templates/admin_system.html:84
msgid "unknown"
msgstr "ukendt"
msgstr ""
#: snikket_web/templates/admin_system.html:17
msgid "Memory use"
msgstr "Forbrug af hukommelse"
msgstr ""
#: snikket_web/templates/admin_system.html:20
#, python-format
@@ -981,56 +979,54 @@ msgid ""
"%(percentage_global)s of %(mem_available)s. Of that, Snikket uses "
"%(percentage_snikket)s."
msgstr ""
"%(percentage_global)s af %(mem_available)s. Der af bruger Snikket "
"%(percentage_snikket)s."
#: snikket_web/templates/admin_system.html:27
msgid "Web portal status"
msgstr "Webportalens status"
msgstr ""
#: snikket_web/templates/admin_system.html:30
#: snikket_web/templates/admin_system.html:53
msgid "Version"
msgstr "Version"
msgstr ""
#: snikket_web/templates/admin_system.html:31
#: snikket_web/templates/admin_system.html:54
msgid "View all versions"
msgstr "Vis alle versioner"
msgstr ""
#: snikket_web/templates/admin_system.html:32
#: snikket_web/templates/admin_system.html:55
msgid "Average CPU use"
msgstr "Gennemsnitlig CPU-forbrug"
msgstr ""
#: snikket_web/templates/admin_system.html:40
#: snikket_web/templates/admin_system.html:63
msgid "Current memory use"
msgstr "Nuværende hukommelsesbrug"
msgstr ""
#: snikket_web/templates/admin_system.html:50
#, fuzzy
#| msgid "Snikket Web Portal"
msgid "Snikket server status"
msgstr "Snikket server status"
msgstr "Snikket Webportal"
#: snikket_web/templates/admin_system.html:71
msgid "Storage used by shared files"
msgstr "Lagerplads, der bruges af delte filer"
msgstr ""
#: snikket_web/templates/admin_system.html:79
msgid "Connected devices"
msgstr "Forbundet enheder"
msgstr ""
#: snikket_web/templates/admin_system.html:90
msgid "Broadcast message"
msgstr "Send besked"
msgstr ""
#: snikket_web/templates/admin_system.html:92
msgid ""
"This form allows you to send a message to all users currently online on your "
"Snikket server. Use it wisely."
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
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="
"\"%(login_url)s\">manage your account</a>."
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
#, fuzzy
#| msgid "Operation successful"
msgid "Import successful"
msgstr "Importering lykkes"
msgstr "Operation lykkes"
#: snikket_web/templates/invite_success.html:22
msgid "Congratulations! Your account data has been successfully imported."
msgstr "Tillykke! Dine kontodata er blevet importeret."
msgstr ""
#: snikket_web/templates/invite_success.html:26
#, fuzzy
#| msgid "Using the Snikket app"
msgid "Moving to Snikket?"
msgstr "Flytte til Snikket?"
msgstr "Bruger Snikket appen"
#: snikket_web/templates/invite_success.html:27
msgid ""
@@ -1326,14 +1324,10 @@ msgid ""
"information, etc.) from your previous account. When you have exported the "
"data from your previous account, upload it using the form below."
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
msgid "Upload account data"
msgstr "Upload kontodata"
msgstr ""
#: snikket_web/templates/invite_view.html:6
#, python-format
@@ -1564,8 +1558,10 @@ msgstr "Rediger profil"
#: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_manage_data.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage your data"
msgstr "Håndter dine data"
msgstr "Håndter brugere"
#: snikket_web/templates/user_home.html:39
msgid "Your Snikket"
@@ -1592,16 +1588,16 @@ msgstr ""
"af de forbundne enheder."
#: snikket_web/templates/user_manage_data.html:8
#, fuzzy
#| msgid "Your account"
msgid "Export account"
msgstr "Eksporter konto"
msgstr "Din konto"
#: snikket_web/templates/user_manage_data.html:9
msgid ""
"Download your account data as a file for backup purposes or to move your "
"account to another service."
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
msgid "Change your password"

View File

@@ -6,18 +6,18 @@
msgid ""
msgstr ""
"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"
"PO-Revision-Date: 2022-04-11 13:00+0000\n"
"Last-Translator: David Baraniak <admin@chipmnk.dev>\n"
"Language-Team: French <http://i18n.sotecware.net/projects/snikket/web-portal/"
"fr/>\n"
"PO-Revision-Date: 2021-06-19 15:01+0000\n"
"Last-Translator: Link Mauve <linkmauve@linkmauve.fr>\n"
"Language-Team: French <https://i18n.sotecware.net/projects/snikket/web-"
"portal/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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"
#: 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 "
"your Snikket operator."
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
msgid "Username"
@@ -263,11 +261,11 @@ msgstr "Changer de mot de passe"
#: snikket_web/invite.py:244
msgid "Account data file"
msgstr "Fichier de données du compte"
msgstr ""
#: snikket_web/invite.py:248
msgid "Import data"
msgstr "Importer les données"
msgstr ""
#: snikket_web/invite.py:269
#, python-format
@@ -275,9 +273,6 @@ msgid ""
"The account data you tried to import is in an unknown format. Please upload "
"an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
"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/user.py:178
@@ -346,11 +341,11 @@ msgstr "Mettre à jour le profil"
#: snikket_web/user.py:82
msgid "Account data"
msgstr "Données du compte"
msgstr ""
#: snikket_web/user.py:86
msgid "Upload"
msgstr "Télécharger"
msgstr ""
#: snikket_web/user.py:111
msgid "Incorrect password."
@@ -374,11 +369,11 @@ msgstr "Profil mis à jour"
#: snikket_web/user.py:184
msgid "Export"
msgstr "Exporter"
msgstr ""
#: snikket_web/user.py:202
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
#, python-format
@@ -1019,7 +1014,7 @@ msgstr "Statut du serveur Snikket"
#: snikket_web/templates/admin_system.html:71
msgid "Storage used by shared files"
msgstr "Stockage utilisé par les fichiers partagés"
msgstr ""
#: snikket_web/templates/admin_system.html:79
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="
"\"%(login_url)s\">manage your account</a>."
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
#, fuzzy
#| msgid "Operation successful"
msgid "Import successful"
msgstr "Importation réussie"
msgstr "Opération réussie"
#: snikket_web/templates/invite_success.html:22
msgid "Congratulations! Your account data has been successfully imported."
msgstr ""
"Félicitations ! Les données de votre compte ont été importées avec succès."
#: snikket_web/templates/invite_success.html:26
#, fuzzy
#| msgid "Using the Snikket app"
msgid "Moving to Snikket?"
msgstr "Nouveau utilisateur Snikket ?"
msgstr "En utilisant lapplication Snikket"
#: snikket_web/templates/invite_success.html:27
msgid ""
@@ -1343,15 +1338,10 @@ msgid ""
"information, etc.) from your previous account. When you have exported the "
"data from your previous account, upload it using the form below."
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
msgid "Upload account data"
msgstr "Télécharger les données du compte"
msgstr ""
#: snikket_web/templates/invite_view.html:6
#, python-format
@@ -1406,9 +1396,10 @@ msgstr "Télécharger sur lApp Store"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr "Obtenez-le sur F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:35
#, fuzzy
msgid "Send to mobile device"
msgstr "Envoyer vers l'appareil"
@@ -1487,14 +1478,10 @@ msgid ""
"After downloading Snikket from the App Store, you have to return to this "
"invite link and tap on \"Open the app\" to proceed."
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
msgid "First download Snikket from the App Store using the button below:"
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:131
@@ -1502,9 +1489,6 @@ msgid ""
"After the installation is complete, you can return to this page and tap the "
"\"Open the app\" button to continue with the setup:"
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:130
@@ -1516,13 +1500,10 @@ msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed."
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
msgid "First install Snikket from F-Droid using the button below:"
msgstr ""
"Installez d'abord Snikket depuis F-Droid en utilisant le bouton ci-dessous :"
#: snikket_web/templates/library.j2:18
msgid "Copy link"
@@ -1589,8 +1570,10 @@ msgstr "Éditer votre profil"
#: snikket_web/templates/user_home.html:33
#: snikket_web/templates/user_manage_data.html:4
#, fuzzy
#| msgid "Manage users"
msgid "Manage your data"
msgstr "Gérer vos données"
msgstr "Gérer les utilisateurs"
#: snikket_web/templates/user_home.html:39
msgid "Your Snikket"
@@ -1618,16 +1601,16 @@ msgstr ""
"autres appareils connectés."
#: snikket_web/templates/user_manage_data.html:8
#, fuzzy
#| msgid "Your account"
msgid "Export account"
msgstr "Exportation du compte"
msgstr "Votre compte"
#: snikket_web/templates/user_manage_data.html:9
msgid ""
"Download your account data as a file for backup purposes or to move your "
"account to another service."
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
msgid "Change your password"

View File

@@ -8,204 +8,204 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.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_users.html:8
msgid "Login name"
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_users.html:9 snikket_web/user.py:63
msgid "Display name"
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"
msgstr ""
#: snikket_web/admin.py:80
#: snikket_web/admin.py:78
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:81
#: snikket_web/admin.py:79
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:82
#: snikket_web/admin.py:80
msgid "Administrator"
msgstr ""
#: snikket_web/admin.py:87
#: snikket_web/admin.py:85
msgid "Update user"
msgstr ""
#: snikket_web/admin.py:91
#: snikket_web/admin.py:89
msgid "Create password reset link"
msgstr ""
#: snikket_web/admin.py:109
#: snikket_web/admin.py:107
msgid "Password reset link created"
msgstr ""
#: snikket_web/admin.py:124
#: snikket_web/admin.py:122
msgid "User information updated."
msgstr ""
#: snikket_web/admin.py:146
#: snikket_web/admin.py:144
msgid "Delete user permanently"
msgstr ""
#: snikket_web/admin.py:159
#: snikket_web/admin.py:157
msgid "User deleted"
msgstr ""
#: snikket_web/admin.py:197
#: snikket_web/admin.py:195
msgid "Password reset link not found"
msgstr ""
#: snikket_web/admin.py:209
#: snikket_web/admin.py:207
msgid "Password reset link deleted"
msgstr ""
#: snikket_web/admin.py:229
#: snikket_web/admin.py:227
msgid "Invite to circle"
msgstr ""
#: snikket_web/admin.py:235
#: snikket_web/admin.py:233
msgid "At least one circle must be selected"
msgstr ""
#: snikket_web/admin.py:240
#: snikket_web/admin.py:238
msgid "Valid for"
msgstr ""
#: snikket_web/admin.py:242
#: snikket_web/admin.py:240
msgid "One hour"
msgstr ""
#: snikket_web/admin.py:243
#: snikket_web/admin.py:241
msgid "Twelve hours"
msgstr ""
#: snikket_web/admin.py:244
#: snikket_web/admin.py:242
msgid "One day"
msgstr ""
#: snikket_web/admin.py:245
#: snikket_web/admin.py:243
msgid "One week"
msgstr ""
#: snikket_web/admin.py:246
#: snikket_web/admin.py:244
msgid "Four weeks"
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"
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"
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"
msgstr ""
#: snikket_web/admin.py:261
#: snikket_web/admin.py:259
msgid "New invitation link"
msgstr ""
#: snikket_web/admin.py:323
#: snikket_web/admin.py:321
msgid "Revoke"
msgstr ""
#: snikket_web/admin.py:347
#: snikket_web/admin.py:345
msgid "Invitation created"
msgstr ""
#: snikket_web/admin.py:363
#: snikket_web/admin.py:361
msgid "No such invitation exists"
msgstr ""
#: snikket_web/admin.py:378
#: snikket_web/admin.py:376
msgid "Invitation revoked"
msgstr ""
#: snikket_web/admin.py:395 snikket_web/admin.py:443
#: snikket_web/admin.py:393 snikket_web/admin.py:441
msgid "Name"
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"
msgstr ""
#: snikket_web/admin.py:430
#: snikket_web/admin.py:428
msgid "Circle created"
msgstr ""
#: snikket_web/admin.py:448
#: snikket_web/admin.py:446
msgid "Select user"
msgstr ""
#: snikket_web/admin.py:453
#: snikket_web/admin.py:451
msgid "Update circle"
msgstr ""
#: snikket_web/admin.py:457
#: snikket_web/admin.py:455
msgid "Delete circle permanently"
msgstr ""
#: snikket_web/admin.py:463
#: snikket_web/admin.py:461
msgid "Add user"
msgstr ""
#: snikket_web/admin.py:479
#: snikket_web/admin.py:477
msgid "No such circle exists"
msgstr ""
#: snikket_web/admin.py:516
#: snikket_web/admin.py:514
msgid "Circle data updated"
msgstr ""
#: snikket_web/admin.py:522
#: snikket_web/admin.py:520
msgid "Circle deleted"
msgstr ""
#: snikket_web/admin.py:533
#: snikket_web/admin.py:531
msgid "User added to circle"
msgstr ""
#: snikket_web/admin.py:542
#: snikket_web/admin.py:540
msgid "User removed from circle"
msgstr ""
#: snikket_web/admin.py:611
#: snikket_web/admin.py:609
msgid "Message contents"
msgstr ""
#: snikket_web/admin.py:617
#: snikket_web/admin.py:615
msgid "Only send to online users"
msgstr ""
#: snikket_web/admin.py:621
#: snikket_web/admin.py:619
msgid "Post to all users"
msgstr ""
#: snikket_web/admin.py:625
#: snikket_web/admin.py:623
msgid "Send preview to yourself"
msgstr ""
#: snikket_web/admin.py:647
#: snikket_web/admin.py:645
msgid "Announcement sent!"
msgstr ""
@@ -213,82 +213,82 @@ msgstr ""
msgid "Main"
msgstr ""
#: snikket_web/invite.py:35
#: snikket_web/invite.py:33
msgid ""
"The account data you tried to import is too large to upload. Please "
"contact your Snikket operator."
msgstr ""
#: snikket_web/invite.py:114
#: snikket_web/invite.py:112
msgid "Username"
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"
msgstr ""
#: snikket_web/invite.py:122 snikket_web/invite.py:190
#: snikket_web/invite.py:120 snikket_web/invite.py:188
msgid "Confirm password"
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."
msgstr ""
#: snikket_web/invite.py:131
#: snikket_web/invite.py:129
msgid "Create account"
msgstr ""
#: snikket_web/invite.py:158
#: snikket_web/invite.py:156
msgid "That username is already taken."
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."
msgstr ""
#: snikket_web/invite.py:166
#: snikket_web/invite.py:164
msgid "The username is not valid."
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
msgid "Change password"
msgstr ""
#: snikket_web/invite.py:246
#: snikket_web/invite.py:244
msgid "Account data file"
msgstr ""
#: snikket_web/invite.py:250
#: snikket_web/invite.py:248
msgid "Import data"
msgstr ""
#: snikket_web/invite.py:271
#: snikket_web/invite.py:269
#, python-format
msgid ""
"The account data you tried to import is in an unknown format. Please "
"upload an XML file in XEP-0227 format (provided format: %(mimetype)s)."
msgstr ""
#: snikket_web/invite.py:291 snikket_web/templates/unauth.html:18
#: snikket_web/invite.py:289 snikket_web/templates/unauth.html:18
#: snikket_web/user.py:178
msgid "Error"
msgstr ""
#: snikket_web/main.py:38
#: snikket_web/main.py:36
msgid "Address"
msgstr ""
#: snikket_web/main.py:48
#: snikket_web/main.py:46
msgid "Sign in"
msgstr ""
#: snikket_web/main.py:57
#: snikket_web/main.py:55
msgid "Invalid username or password."
msgstr ""
#: snikket_web/main.py:85
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

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

View File

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