Compare commits

...

113 Commits

Author SHA1 Message Date
Matthew Wild
7f02746f63 admin: Re-disable 'limited' role (accidentally uncommented in c58ce8450) 2021-05-19 21:59:16 +01:00
Roberto Resoli
f2788aeb36 Translated using Weblate (Italian)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/it/
2021-05-19 15:12:20 +00:00
misiek
536a05b0eb Translated using Weblate (Polish)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-05-19 15:12:20 +00:00
uira
e0226d47e3 Translated using Weblate (Indonesian)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/id/
2021-05-19 15:12:20 +00:00
misiek
0fe10a44ce Translated using Weblate (Polish)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-05-18 13:29:00 +00:00
Jonas Schäfer
e892d81815 Translated using Weblate (German)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/de/
2021-05-18 13:29:00 +00:00
Jonas Schäfer
c58ce8450f Fix type annotations after bumping dependencies 2021-05-18 14:33:06 +02:00
Jonas Schäfer
03573d1f05 Explicitly pass mod_rest JSON as JSON
Otherwise, it can get passed as x-www-form-urlencoded, which
Prosody understandably does not quite like.
2021-05-18 12:35:46 +02:00
Jonas Schäfer
486596f89f Upgrade to quart 0.15 2021-05-18 12:35:31 +02:00
Jonas Schäfer
425b4d4295 Fix dysfunctional password reset button 2021-05-18 12:20:45 +02:00
misiek
87de808046 Translated using Weblate (Polish)
Currently translated at 97.8% (273 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-04-23 20:00:57 +00:00
misiek
05455ac743 Translated using Weblate (Polish)
Currently translated at 96.0% (268 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-04-05 19:01:04 +00:00
Daniel Holmgaard
1e926714cb Translated using Weblate (Danish)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/da/
2021-04-02 19:01:03 +00:00
Tilman Jiménez
e1602f3140 Translated using Weblate (Spanish (Mexico))
Currently translated at 49.1% (137 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/es_MX/
2021-03-31 13:00:53 +00:00
uira
2e89973263 Translated using Weblate (Indonesian)
Currently translated at 99.2% (277 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/id/
2021-03-29 08:00:48 +00:00
Kim Alvefur
a6f1361ddd Translated using Weblate (Swedish)
Currently translated at 100.0% (279 of 279 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/sv/
2021-03-27 00:01:12 +00:00
Weblate
552a3bbd41 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/
2021-03-25 16:35:52 +00:00
misiek
3f2de1e5bf Translated using Weblate (Polish)
Currently translated at 97.6% (248 of 254 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-03-25 16:35:49 +00:00
Jonas Schäfer
059a10f475 Merge pull request #76 from snikket-im/feature/roles
"Edit user" flow, role management
2021-03-25 17:35:42 +01:00
Jonas Schäfer
a48abacf1d Disable restricted role for now
It is not implemented in snikket-server yet, so we don’t want to
put anything misleading out there.
2021-03-25 17:32:03 +01:00
Jonas Schäfer
ea7ed7c030 Add support for roles
Requires patches to prosody trunk which have been submitted
already (2021-03-22) which introduce the set_roles function on
usermanager.

Fixes #42.
2021-03-25 17:31:56 +01:00
Jonas Schäfer
cca899bd8c Create "Edit user" form
This aggregates the user actions behind a single "edit" button on
the list view, making it less crammed. It also offers the
functionality of actually editing the user, mind.

Also in preparation for #42.

Requires https://hg.prosody.im/prosody-modules/rev/5bc706c2db8f.
2021-03-25 17:31:49 +01:00
Jonas Schäfer
359e6b4ce2 Use tertiary style for "back" buttons
This allows us to have two levels of emphasis for the actual
form buttons and is also in line with the global "Log out"
navigational button.
2021-03-25 17:31:43 +01:00
Jonas Schäfer
6650dd2046 Capitalize App Store in the invite for consistency 2021-03-25 17:28:36 +01:00
Jonas Schäfer
97b4a7be0f Merge pull request #77 from Zash/mod_rest-version-change
Update for switch to datamapper in mod_rest
2021-03-24 09:10:22 +01:00
Kim Alvefur
329916e200 Update for switch to datamapper in mod_rest
mod_rest after the switch to the new util.datamapper in
https://hg.prosody.im/prosody-modules/rev/073f5397c1d2 does not accept
boolean True as value for the xep-0092 'version' field. An empty object
is equivalent and compatible with both previous and future versions.
2021-03-23 21:38:34 +01:00
Kim Alvefur
3571b8909b Translated using Weblate (Swedish)
Currently translated at 100.0% (254 of 254 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/sv/
2021-03-23 15:00:36 +00:00
Jonas Schäfer
c6c01b82f5 Merge pull request #74 from snikket-im/feature/invite-from-users-list
Render invite form below user list
2021-03-22 15:30:56 +01:00
Weblate
c4b575f091 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/
2021-03-22 14:09:01 +00:00
Jonas Schäfer
fdb55568ec Change problematic "Back" buttons
Fixes #39.
2021-03-22 15:08:33 +01:00
Jonas Schäfer
a9a651be09 Render invite form below user list
Fixes #73.
2021-03-22 15:03:18 +01:00
Kim Alvefur
d2069289b0 Translated using Weblate (Swedish)
Currently translated at 100.0% (252 of 252 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/sv/
2021-03-21 16:31:20 +00:00
Link Mauve
552b5d2940 Translated using Weblate (French)
Currently translated at 96.8% (244 of 252 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/fr/
2021-03-21 16:31:19 +00:00
Jonas Schäfer
b0f9ae5d57 Translated using Weblate (German)
Currently translated at 100.0% (252 of 252 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/de/
2021-03-21 16:31:19 +00:00
Weblate
dd4a012612 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/
2021-03-20 15:59:19 +00:00
Jonas Schäfer
e7aa0a2c45 Fix more dotless strings 2021-03-20 16:44:44 +01:00
Jonas Schäfer
ad229d6700 Use standard error rendering for the login form
This provides a consistent UX.
2021-03-20 16:30:42 +01:00
Jonas Schäfer
b822000f2e Improve install button layout on narrow screens
This allows the button container to add line breaks between the
buttons when necessary.
2021-03-20 16:30:42 +01:00
Jonas Schäfer
a6b67b3fdd Improve install button layout on narrow screens
This allows the button container to add line breaks between the
buttons when necessary.
2021-03-20 16:20:05 +01:00
Jonas Schäfer
885db355ab Add F-Droid download button
Using the mechanism introduced for iOS to describe the multi-step
process.

Fixes #52.
2021-03-20 16:15:20 +01:00
Jonas Schäfer
c3d5b06313 Add multi-step instructions for iOS installation
Fixes #53.
2021-03-20 16:15:07 +01:00
Jonas Schäfer
2dd8838852 Report validity issues of avatar input right away
This helps discovering the actual error message. Thanks @zash.
2021-03-20 15:56:44 +01:00
Jonas Schäfer
5df2c3945a Use browser API to indicate validity state 2021-03-20 14:40:31 +01:00
Jonas Schäfer
3eb8036ebd Implement size checking for the avatar
This checks the avatar size on the client side (if available) and
on the server side against a configuration-defined limit. The
default limit is set to use the same value as in the original
report, as no sensible limit value is known.

Fixes #67.
2021-03-20 12:57:11 +01:00
Jonas Schäfer
02ed390cd2 Fix type annotation 2021-03-20 12:36:06 +01:00
Jonas Schäfer
2506810b90 Return 404 on expired invite URLs 2021-03-19 16:54:40 +01:00
Kim Alvefur
05d1b42dc4 Hint to the browser that the avatar should be PNG
Should result in that the file picker by default only shows PNG files
for selection.
2021-03-17 15:26:30 +01:00
GodGoldfish
5ef5b93eb9 Translated using Weblate (Russian)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/ru/
2021-03-12 23:04:55 +00:00
GodGoldfish
0ff6e00e9d Translated using Weblate (French)
Currently translated at 90.1% (220 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/fr/
2021-03-12 23:04:54 +00:00
Jonas Schäfer
c04ac4bee0 Make linter happy 2021-03-11 07:32:31 +01:00
Daniel Holmgaard
3e19d42c2a Translated using Weblate (Danish)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/da/
2021-03-10 19:03:47 +00:00
Matthew Wild
03732ac06b Fix health check to declare itself text/plain and match Prosody 2021-03-10 14:54:01 +00:00
Matthew Wild
c70228fed7 Add /_health endpoint 2021-03-10 14:22:38 +00:00
Jonas Schäfer
025172592f Improve error handling in Prosody version retrieval 2021-03-09 22:20:37 +01:00
Kim Alvefur
6de1e5313f Add support for displaying prosody version
This only works for authenticated users even in debug mode because
it requires a session with prosody to send the request.

Fixes #66.
2021-03-09 22:08:58 +01:00
Jonas Schäfer
3083c118a3 Add fully translated language codes 2021-03-09 22:03:08 +01:00
Daniel Holmgaard
fa1b13fbdb Translated using Weblate (Danish)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/da/
2021-03-06 17:03:39 +00:00
Daniel Holmgaard
ba30d728f4 Translated using Weblate (Danish)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/da/
2021-03-03 22:03:08 +00:00
Matthew Wild
af87301fa4 Added translation using Weblate (Danish) 2021-03-01 21:01:54 +00:00
Kim Alvefur
8ee0b0dd30 Translated using Weblate (Swedish)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/sv/
2021-02-25 16:02:18 +00:00
Roberto Resoli
4a27ef9d72 Translated using Weblate (Italian)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/it/
2021-02-25 16:02:18 +00:00
misiek
9e9fdaf8d4 Translated using Weblate (Polish)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-02-25 16:02:17 +00:00
uira
bdb186ca81 Translated using Weblate (Indonesian)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/id/
2021-02-25 16:02:17 +00:00
Jonas Schäfer
4ca9b82bce Translated using Weblate (German)
Currently translated at 100.0% (244 of 244 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/de/
2021-02-25 16:02:16 +00:00
Jonas Schäfer
6dbe2c2d5e Fix missing type annotation 2021-02-23 21:18:14 +01:00
Jonas Schäfer
e410aedfef Improve logging of rejected IQ calls 2021-02-23 20:21:44 +01:00
Jonas Schäfer
1713da61e7 Fix password change
This fixes a regression introduced in e476d9b7 which caused the
token to be incorrectly used when authenticating with mod_rest for
sending the password change IQ stanza.
2021-02-23 20:20:47 +01:00
Kim Alvefur
53aac690df Add health check to dockerfile 2021-02-23 15:56:19 +01:00
Weblate
5e4009ca11 Update translation files
Updated by "Update PO files to match POT (msgmerge)" hook in Weblate.

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/
2021-02-23 06:55:53 +00:00
Jonas Schäfer
80860a3ac6 Extract missing strings 2021-02-23 07:55:38 +01:00
Kim Alvefur
e9d479a78b Translated using Weblate (Swedish)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/sv/
2021-02-22 23:23:17 +00:00
Jonas Schäfer
aac56f49e9 Added translation using Weblate (Swedish) 2021-02-22 18:12:31 +00:00
Jonas Schäfer
52f0bee006 Use buster-slim as base image
This reduces the overall image size, but more importantly,
deduplicates nicely with the other Snikket images which all use
buster-slim as base.

Fixes #63.
2021-02-22 15:02:14 +01:00
misiek
97c91b432d Translated using Weblate (Polish)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-02-22 11:02:09 +00:00
Jonas Schäfer
60647159f3 Merge pull request #62 from mmigel/patch-2
Update admin_invites.html
2021-02-20 15:55:42 +01:00
Michał Mazur
a21730f136 Update admin_invites.html
A better sounding variant "Expires" in place "Valid until".
2021-02-20 15:53:00 +01:00
Jonas Schäfer
e35ab1b723 Merge pull request #61 from snikket-im/enable-italian-translation
Enable Italian translation by default
2021-02-20 10:17:11 +01:00
Matthew Wild
4de4509fc9 Update __init__.py 2021-02-20 07:07:18 +00:00
misiek
93e3b325b1 Translated using Weblate (Polish)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-02-18 20:01:22 +00:00
Matthew Wild
ceecfc861c docker: allow custom bind interface/port from environment 2021-02-17 13:38:10 +00:00
Link Mauve
2467e73781 Translated using Weblate (French)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/fr/
2021-02-16 12:02:25 +00:00
Jonas Schäfer
2f34d39a09 Merge pull request #58 from linkmauve/more-translated-titles
Make more titles translatable
2021-02-14 16:09:39 +01:00
Emmanuel Gil Peyrot
de8589923b Make more titles translatable 2021-02-14 13:11:18 +01:00
Tilman Jiménez
db3a1ac22f Translated using Weblate (Spanish (Mexico))
Currently translated at 41.6% (92 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/es_MX/
2021-02-10 17:01:21 +00:00
Jonas Schäfer
b48d130659 Translated using Weblate (German)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/de/
2021-02-10 17:01:20 +00:00
Jonas Schäfer
1aed573eb2 Bump version number for pre-release
(Sigh, I need to get better at this… Or automate it.)
2021-02-09 16:46:18 +01:00
Jonas Schäfer
d4707196ec Include Link header and element in invite response
This allows future App versions to also work with the invite page
without having to screen scrape the content.

Fixes #56 (at least for the portal side of things).
2021-02-09 16:44:50 +01:00
Jonas Schäfer
8a8d4c54bd Collapse the logout button text on narrow displays
This prevents ugly line wraps on long site names
2021-02-09 16:44:50 +01:00
Jonas Schäfer
ab534e3a59 Fix strange 308 error code when using slash-less invite
That seems to be some Quart-internal redirect which isn’t executed
correctly (probably due to our makeshift error handlers). So I
make this a proper redirect instead.
2021-02-09 16:44:50 +01:00
Jonas Schäfer
4c128f1af2 Clarify "Not on mobile" button text
Tester feedback has shown that desktop client users will also
click that button because they are, in fact, not on mobile.

This button speaks more to the users intent (sending the
invitation to the mobile device) after having (hopefully) read
the text above.

Fixes #38.
2021-02-09 16:44:50 +01:00
Jonas Schäfer
8b551a8946 Fix invite page layout after adding support for flashboxes 2021-02-09 16:44:50 +01:00
Tilman Jiménez
182d2301be Translated using Weblate (Spanish (Mexico))
Currently translated at 21.7% (48 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/es_MX/
2021-02-06 18:02:14 +00:00
misiek
6dba5e3a65 Translated using Weblate (Polish)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/pl/
2021-02-06 18:02:13 +00:00
Jonas Schäfer
713da89445 Add flash message feedback to all relevant user actions
Fixes #40.
2021-02-06 12:00:55 +01:00
Jonas Schäfer
9876e42fb7 Add support for a flash message sidebar 2021-02-06 12:00:45 +01:00
Jonas Schäfer
8b66c5a063 Add alert role to dynamically added message for a11y 2021-02-06 11:31:55 +01:00
Jonas Schäfer
ddf9f89d77 Remove redundant import 2021-02-06 11:31:51 +01:00
Jonas Schäfer
53e023f9ae Protect against invalid domain on the client side
Here we protect the user from themselves if they accidentally
enter their snikket credentials into the wrong instance by
preventing the form from even being submitted and by showing a
nice error message.
2021-02-06 11:20:05 +01:00
Jonas Schäfer
e4d339627e Protect against incorrect domain name on the server side
Instead of processing the input further and forwarding the
credentials to prosody, we catch the error early on to prevent
having to handle the 400 error code specially and to prevent the
password from spilling in other components.

Fixes #55.
2021-02-06 11:20:05 +01:00
Jonas Schäfer
cd3026911b Added translation using Weblate (Spanish (Mexico)) 2021-02-05 14:30:23 +00:00
GodGoldfish
d7da16f780 Translated using Weblate (Russian)
Currently translated at 55.2% (122 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/ru/
2021-02-04 19:02:06 +00:00
Jonas Schäfer
8ed0fbec25 Translated using Weblate (German)
Currently translated at 100.0% (221 of 221 strings)

Translation: Snikket/Web Portal
Translate-URL: https://i18n.sotecware.net/projects/snikket/web-portal/de/
2021-02-04 19:02:05 +00:00
Jonas Schäfer
5b812c773d Fix footer on login page 2021-02-04 15:51:43 +01:00
Michał Mazur
fa61ee4e11 Update __init__.py
Wrong Polish language ISO code. That's probably why it doesn't work.
2021-02-04 14:37:51 +01:00
Jonas Schäfer
7402480c62 Allow / suffix on invite URLs
This makes them a bit more clickable in some user agents (think
email, xmpp) which have to rely on parsing to find and highlight
URLs.

Fixes #48.
2021-02-03 19:00:49 +01:00
Jonas Schäfer
a68a469319 Add extended trademark hints to the about page 2021-02-03 18:57:01 +01:00
Jonas Schäfer
961f285fa5 Add trademark info to the footer
Fixes #45.
2021-02-03 18:55:22 +01:00
Jonas Schäfer
7456295cb6 Make title red if running in debug
This (a) helps developers to not accidentally their production
server and (b) deters user from letting it run that way for long.
2021-02-03 18:50:36 +01:00
Jonas Schäfer
96f4b0d4f8 Make version info only available on admin or debug sessions 2021-02-03 18:47:21 +01:00
Jonas Schäfer
245434126e Bump version number for the next release 2021-02-03 18:44:18 +01:00
Jonas Schäfer
725dffc458 Reduce image size by approximately 65% 2021-02-03 18:36:31 +01:00
Jonas Schäfer
22783b837e Update readme screenshot 2021-02-03 18:30:38 +01:00
Jonas Schäfer
ba18fe692f Fix ClientResponseError if a circle has a deleted user
Eventually, we need to clear that on the backend, but for now we
deal with it in the frontend.

Bonus: this also optimises the display of the circle by removing
O(n) backend requests.
2021-02-03 18:25:29 +01:00
59 changed files with 9685 additions and 1930 deletions

View File

@@ -1,46 +1,31 @@
FROM debian:buster
FROM debian:buster-slim
ARG BUILD_SERIES=dev
ARG BUILD_ID=0
ENV DEBIAN_FRONTEND noninteractive
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
COPY build-requirements.txt /opt/snikket-web-portal/build-requirements.txt
COPY Makefile /opt/snikket-web-portal/Makefile
COPY snikket_web/ /opt/snikket-web-portal/snikket_web
COPY babel.cfg /opt/snikket-web-portal/babel.cfg
# This Dockerfile attempts to strike a balance between image size and time it
# takes to do an incremental build on changes.
# Improvements welcome.
WORKDIR /opt/snikket-web-portal
RUN set -eu; \
export DEBIAN_FRONTEND=noninteractive ; \
apt-get update ; \
apt-get install -y --no-install-recommends \
python3 python3-pip python3-setuptools python3-wheel \
libpython3-dev \
make build-essential \
netcat \
; \
apt-get clean ; rm -rf /var/lib/apt/lists
COPY requirements.txt /opt/snikket-web-portal/requirements.txt
COPY build-requirements.txt /opt/snikket-web-portal/build-requirements.txt
WORKDIR /opt/snikket-web-portal
RUN set -eu; \
pip3 install -r requirements.txt; \
pip3 install -r build-requirements.txt; \
rm -rf /root/.cache;
COPY Makefile /opt/snikket-web-portal/Makefile
COPY snikket_web/ /opt/snikket-web-portal/snikket_web
COPY babel.cfg /opt/snikket-web-portal/babel.cfg
# NOTE: abusing true(1) as a terrible way to disable a specific command. If
# one merged all the RUN commands into one, one would want to run the
# uninstall/remove commands there, but with the split up RUN commands it is
# rather pointless.
RUN set -eu; \
make; \
true pip3 uninstall -yr build-requirements.txt; \
true apt-get remove -y build-essential make libpython3-dev; \
true apt-get autoremove -y; \
pip3 uninstall -yr build-requirements.txt; \
apt-get remove -y build-essential make libpython3-dev; \
apt-get autoremove -y; \
pip3 install hypercorn; \
rm -rf /root/.cache; \
apt-get clean ; rm -rf /var/lib/apt/lists
@@ -50,5 +35,7 @@ ENV SNIKKET_WEB_PYENV=/etc/snikket-web-portal/env.py
ENV SNIKKET_WEB_PROSODY_ENDPOINT=http://127.0.0.1:5280/
HEALTHCHECK CMD nc -zv ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE:-127.0.0.1} ${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT:-5765}
ADD docker/entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]

View File

@@ -2,4 +2,7 @@
export SNIKKET_WEB_DOMAIN="$SNIKKET_DOMAIN"
exec hypercorn -b "127.0.0.1:5765" 'snikket_web:create_app()'
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE-127.0.0.1}"
export SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT="${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT-5765}"
exec hypercorn -b "${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_INTERFACE}:${SNIKKET_TWEAK_PORTAL_INTERNAL_HTTP_PORT}" 'snikket_web:create_app()'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 421 KiB

View File

@@ -1,5 +1,5 @@
aiohttp~=3.6
quart~=0.11
quart~=0.15
flask-wtf~=0.14
hsluv~=0.0.2
flask-babel~=1.0

View File

@@ -17,6 +17,7 @@ from quart import (
redirect,
jsonify,
)
import werkzeug.exceptions
import environ
@@ -40,7 +41,7 @@ async def proc() -> typing.Dict[str, typing.Any]:
try:
user_info = await infra.client.get_user_info()
except (aiohttp.ClientError, quart.exceptions.HTTPException):
except (aiohttp.ClientError, werkzeug.exceptions.HTTPException):
user_info = {}
return {
@@ -48,6 +49,7 @@ async def proc() -> typing.Dict[str, typing.Any]:
"text_to_css": colour.text_to_css,
"lang": infra.selected_locale(),
"user_info": user_info,
"is_in_debug_mode": current_app.debug,
}
@@ -104,16 +106,16 @@ async def backend_error_handler(exc: Exception) -> quart.Response:
async def generic_http_error(
exc: quart.exceptions.HTTPException,
exc: werkzeug.exceptions.HTTPException,
) -> quart.Response:
return quart.Response(
await render_template(
"generic_http_error.html",
status=exc.status_code,
status=exc.code,
description=exc.description,
name=exc.name,
),
status=exc.status_code,
status=exc.code,
)
@@ -144,13 +146,21 @@ class AppConfig:
site_name = environ.var("")
avatar_cache_ttl = environ.var(1800, converter=int)
languages = environ.var([
"da",
"de",
"en",
"fr",
"id",
"po",
"it",
"pl",
"sv",
], converter=autosplit)
apple_store_url = environ.var("")
# Default limit of 1 MiB is what was discovered to be the effective limit
# in #67, hence we set that here for now.
# Future versions may change this default, and the standard deployment
# tools may also very well override it.
max_avatar_size = environ.var(1024*1024, converter=int)
_UPPER_CASE = "".join(map(chr, range(ord("A"), ord("Z")+1)))
@@ -181,29 +191,30 @@ def create_app() -> quart.Quart:
app.config["SITE_NAME"] = config.site_name or config.domain
app.config["AVATAR_CACHE_TTL"] = config.avatar_cache_ttl
app.config["APPLE_STORE_URL"] = config.apple_store_url
app.config["MAX_AVATAR_SIZE"] = config.max_avatar_size
app.context_processor(proc)
app.register_error_handler(
aiohttp.ClientConnectorError,
backend_error_handler, # type:ignore
backend_error_handler,
)
app.register_error_handler(
quart.exceptions.HTTPException,
generic_http_error, # type:ignore
werkzeug.exceptions.HTTPException,
generic_http_error,
)
app.register_error_handler(
Exception,
generic_error_handler, # type:ignore
generic_error_handler,
)
@app.route("/")
@app.route("/") # type: ignore
async def index() -> quart.Response:
if infra.client.has_session:
return redirect(url_for('user.index'))
return redirect(url_for('main.login'))
@app.route("/site.webmanifest")
@app.route("/site.webmanifest") # type: ignore
def site_manifest() -> quart.Response:
# this is needed for icons
return jsonify(

View File

@@ -1,4 +1,4 @@
version_info = (0, 1, 0, "a0")
version_info = (0, 1, 2, "a0")
version = (
".".join(map(str, version_info[:3])) +
(f"-{version_info[3]}" if version_info[3] else "")

View File

@@ -1,4 +1,3 @@
import asyncio
import json
import typing
@@ -18,50 +17,133 @@ from quart import (
url_for,
request,
abort,
flash,
)
import flask_wtf
from flask_babel import lazy_gettext as _l
from flask_babel import lazy_gettext as _l, _
from . import prosodyclient
from .infra import client, circle_name
from .infra import client, circle_name, BaseForm
bp = Blueprint("admin", __name__, url_prefix="/admin")
@bp.route("/")
@bp.route("/") # type:ignore
@client.require_admin_session()
async def index() -> str:
return await render_template("admin_home.html")
class PasswordResetLinkPost(flask_wtf.FlaskForm): # type: ignore
action_create = wtforms.StringField()
class PasswordResetLinkPost(BaseForm):
action_revoke = wtforms.StringField()
@bp.route("/users")
@bp.route("/users") # type:ignore
@client.require_admin_session()
async def users() -> str:
users = sorted(
await client.list_users(),
key=lambda x: x.localpart
)
invite_form = InvitePost()
await invite_form.init_choices()
reset_form = PasswordResetLinkPost()
return await render_template(
"admin_users.html",
users=users,
reset_form=reset_form,
invite_form=invite_form,
)
class DeleteUserForm(flask_wtf.FlaskForm): # type:ignore
_LIMITED_ROLE_NAME = _("Limited")
class EditUserForm(BaseForm):
localpart = wtforms.StringField(
_l("Login name"),
)
display_name = wtforms.StringField(
_l("Display name"),
)
role = wtforms.RadioField(
_l("Access Level"),
choices=[
# NOTE: enable this only after something has been done which
# actually enforces the described restrictions :).
# ("prosody:restricted", _LIMITED_ROLE_NAME),
("prosody:normal", _l("Normal user")),
("prosody:admin", _l("Administrator")),
],
)
action_save = wtforms.SubmitField(
_l("Update user"),
)
action_create_reset = wtforms.SubmitField(
_l("Create password reset link"),
)
@bp.route("/user/<localpart>/", methods=["GET", "POST"]) # type:ignore
@client.require_admin_session()
async def edit_user(localpart: str) -> typing.Union[quart.Response, str]:
target_user_info = await client.get_user_by_localpart(localpart)
form = EditUserForm()
if form.validate_on_submit():
if form.action_create_reset.data:
target_user_info = await client.get_user_by_localpart(localpart)
reset_link = await client.create_password_reset_invite(
localpart=localpart,
ttl=86400,
)
await flash(
_("Password reset link created"),
"success",
)
return redirect(url_for(
".user_password_reset_link",
id_=reset_link.id_,
))
await client.update_user(
localpart,
display_name=form.display_name.data,
roles=[form.role.data],
)
await flash(
_("User information updated."),
"success",
)
return redirect(url_for(".edit_user", localpart=localpart))
elif request.method == "GET":
form.localpart.data = target_user_info.localpart
form.display_name.data = target_user_info.display_name
if target_user_info.roles:
form.role.data = target_user_info.roles[0]
else:
form.role.data = "prosody:normal"
return await render_template(
"admin_edit_user.html",
target_user=target_user_info,
form=form,
)
class DeleteUserForm(BaseForm):
action_delete = wtforms.SubmitField(
_l("Delete user permanently")
)
@bp.route("/user/<localpart>/delete", methods=["GET", "POST"])
@bp.route("/user/<localpart>/delete", methods=["GET", "POST"]) # type:ignore
@client.require_admin_session()
async def delete_user(localpart: str) -> typing.Union[str, quart.Response]:
target_user_info = await client.get_user_by_localpart(localpart)
@@ -69,6 +151,10 @@ async def delete_user(localpart: str) -> typing.Union[str, quart.Response]:
if form.validate_on_submit():
if form.action_delete.data:
await client.delete_user_by_localpart(localpart)
await flash(
_("User deleted"),
"success",
)
return redirect(url_for(".users"))
return await render_template(
@@ -78,7 +164,7 @@ async def delete_user(localpart: str) -> typing.Union[str, quart.Response]:
)
@bp.route("/user/<localpart>/debug")
@bp.route("/user/<localpart>/debug") # type:ignore
@client.require_admin_session()
async def debug_user(localpart: str) -> typing.Union[str, quart.Response]:
target_user_info = await client.get_user_by_localpart(localpart)
@@ -94,37 +180,47 @@ async def debug_user(localpart: str) -> typing.Union[str, quart.Response]:
)
@bp.route("/users/password-reset/-", methods=["POST"])
@bp.route("/users/password-reset/<id_>", methods=["GET", "POST"]) # type:ignore # noqa:E501
@client.require_admin_session()
async def create_password_reset_link() -> typing.Union[str, quart.Response]:
form = PasswordResetLinkPost()
if not form.validate_on_submit():
abort(400)
if form.action_create.data:
localpart = form.action_create.data
target_user_info = await client.get_user_by_localpart(localpart)
reset_link = await client.create_password_reset_invite(
localpart=localpart,
ttl=86400,
async def user_password_reset_link(
id_: str,
) -> typing.Union[str, quart.Response]:
invite_info = await client.get_invite_by_id(
id_,
)
if invite_info.jid is None:
await flash(
_("Password reset link not found"),
"alert",
)
elif form.action_revoke.data:
await client.delete_invite(form.action_revoke.data)
return redirect(url_for(".users"))
localpart = prosodyclient.split_jid(invite_info.jid)[0]
form = PasswordResetLinkPost()
if form.validate_on_submit():
if form.action_revoke.data:
await client.delete_invite(id_)
await flash(
_("Password reset link deleted"),
"success",
)
return redirect(url_for(".edit_user", localpart=localpart))
abort(400)
return await render_template(
"admin_reset_user_password.html",
target_user=target_user_info,
reset_link=reset_link,
localpart=localpart,
reset_link=invite_info,
form=form,
)
class InvitesListForm(flask_wtf.FlaskForm): # type:ignore
class InvitesListForm(BaseForm):
action_revoke = wtforms.StringField()
class InvitePost(flask_wtf.FlaskForm): # type:ignore
class InvitePost(BaseForm):
circles = wtforms.SelectMultipleField(
_l("Invite to circle"),
# NOTE: This is for when/if we ever support multi-group invites.
@@ -178,7 +274,7 @@ class InvitePost(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/invitations", methods=["GET", "POST"])
@bp.route("/invitations", methods=["GET", "POST"]) # type:ignore
@client.require_admin_session()
async def invitations() -> typing.Union[str, quart.Response]:
invites = sorted(
@@ -218,13 +314,13 @@ async def invitations() -> typing.Union[str, quart.Response]:
)
class InviteForm(flask_wtf.FlaskForm): # type:ignore
class InviteForm(BaseForm):
action_revoke = wtforms.SubmitField(
_l("Revoke")
)
@bp.route("/invitation/-/new", methods=["POST"])
@bp.route("/invitation/-/new", methods=["POST"]) # type:ignore
@client.require_admin_session()
async def create_invite() -> typing.Union[str, quart.Response]:
form = InvitePost()
@@ -243,19 +339,27 @@ async def create_invite() -> typing.Union[str, quart.Response]:
group_ids=form.circles.data,
ttl=form.lifetime.data,
)
await flash(
_("Invitation created"),
"success",
)
return redirect(url_for(".edit_invite", id_=invite.id_))
return await render_template("admin_create_invite.html",
invite_form=form)
@bp.route("/invitation/<id_>", methods=["GET", "POST"])
@bp.route("/invitation/<id_>", methods=["GET", "POST"]) # type:ignore
@client.require_admin_session()
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:
if exc.status == 404:
abort(404)
await flash(
_("No such invitation exists"),
"alert",
)
return redirect(url_for(".invitations"))
circles = await client.list_groups()
circle_map = {
circle.id_: circle
@@ -266,6 +370,10 @@ async def edit_invite(id_: str) -> typing.Union[str, quart.Response]:
if form.validate_on_submit():
if form.action_revoke.data:
await client.delete_invite(id_)
await flash(
_("Invitation revoked"),
"success",
)
return redirect(url_for(".invitations"))
return redirect(url_for(".edit_invite", id_=id_))
@@ -278,7 +386,7 @@ async def edit_invite(id_: str) -> typing.Union[str, quart.Response]:
)
class CirclePost(flask_wtf.FlaskForm): # type:ignore
class CirclePost(BaseForm):
name = wtforms.StringField(
_l("Name"),
validators=[wtforms.validators.InputRequired()],
@@ -289,7 +397,7 @@ class CirclePost(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/circles")
@bp.route("/circles") # type:ignore
@client.require_admin_session()
async def circles() -> str:
circles = sorted(
@@ -306,7 +414,7 @@ async def circles() -> str:
)
@bp.route("/circle/-/new", methods=["POST"])
@bp.route("/circle/-/new", methods=["POST"]) # type:ignore
@client.require_admin_session()
async def create_circle() -> typing.Union[str, quart.Response]:
create_form = CirclePost()
@@ -314,6 +422,10 @@ async def create_circle() -> typing.Union[str, quart.Response]:
circle = await client.create_group(
name=create_form.name.data,
)
await flash(
_("Circle created"),
"success",
)
return redirect(url_for(".edit_circle", id_=circle.id_))
return await render_template(
@@ -322,7 +434,7 @@ async def create_circle() -> typing.Union[str, quart.Response]:
)
class EditCircleForm(flask_wtf.FlaskForm): # type:ignore
class EditCircleForm(BaseForm):
name = wtforms.StringField(
_l("Name"),
validators=[wtforms.validators.InputRequired()],
@@ -348,7 +460,7 @@ class EditCircleForm(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/circle/<id_>", methods=["GET", "POST"])
@bp.route("/circle/<id_>", methods=["GET", "POST"]) # type:ignore
@client.require_admin_session()
async def edit_circle(id_: str) -> typing.Union[str, quart.Response]:
async with client.authenticated_session() as session:
@@ -359,28 +471,28 @@ async def edit_circle(id_: str) -> typing.Union[str, quart.Response]:
)
except aiohttp.ClientResponseError as exc:
if exc.status == 404:
await flash(
_("No such circle exists"),
"alert",
)
return redirect(url_for(".circles"))
raise
circle_members = await asyncio.gather(*(
client.get_user_by_localpart(
localpart,
session=session,
)
for localpart in sorted(circle.members)
))
users = await client.list_users()
users = sorted(
await client.list_users(),
key=lambda x: x.localpart
)
circle_members = [
user for user in users
if user.localpart in circle.members
]
form = EditCircleForm()
form.user_to_add.choices = sorted(
(
(u.localpart, u.localpart)
for u in users
if u.localpart not in circle.members
),
key=lambda x: x[1]
)
form.user_to_add.choices = [
(user.localpart, user.localpart)
for user in users
if user.localpart not in circle.members
]
valid_users = [x[0] for x in form.user_to_add.choices]
invite_form = InvitePost()
@@ -396,21 +508,36 @@ async def edit_circle(id_: str) -> typing.Union[str, quart.Response]:
id_,
new_name=form.name.data,
)
await flash(
_("Circle data updated"),
"success",
)
elif form.action_delete.data:
await client.delete_group(id_)
await flash(
_("Circle deleted"),
"success",
)
return redirect(url_for(".circles"))
elif form.action_add_user.data:
if form.user_to_add.data in valid_users:
print("is valid")
await client.add_group_member(
id_,
form.user_to_add.data,
)
await flash(
_("User added to circle"),
"success",
)
elif form.action_remove_user.data:
await client.remove_group_member(
id_,
form.action_remove_user.data,
)
await flash(
_("User removed from circle"),
"success",
)
return redirect(url_for(".edit_circle", id_=id_))
else:

View File

@@ -10,6 +10,7 @@ from quart import (
)
import flask_babel
import flask_wtf
from flask_babel import _
from . import prosodyclient
@@ -25,7 +26,7 @@ babel = flask_babel.Babel()
def selected_locale() -> str:
selected = request.accept_languages.best_match(
current_app.config['LANGUAGES']
)
) or current_app.config['LANGUAGES'][0]
return selected
@@ -55,3 +56,14 @@ def generate_error_id() -> str:
return base64.b32encode(secrets.token_bytes(8)).decode(
"ascii"
).rstrip("=")
class BaseForm(flask_wtf.FlaskForm): # type:ignore
def __init__(self, *args: typing.Any, **kwargs: typing.Any):
meta = kwargs["meta"] = dict(kwargs.get("meta", {}))
if "locales" not in meta:
locale = flask_babel.get_locale()
if locale:
meta["locales"] = [str(locale)]
super().__init__(*args, **kwargs)

View File

@@ -16,10 +16,9 @@ from quart import (
import wtforms
import flask_wtf
from flask_babel import lazy_gettext as _l
from .infra import client, selected_locale
from .infra import client, selected_locale, BaseForm
bp = Blueprint("invite", __name__)
@@ -47,14 +46,21 @@ def context() -> typing.Mapping[str, typing.Any]:
}
@bp.route("/<id_>")
async def view(id_: str) -> str:
@bp.route("/<id_>") # type:ignore
async def view_old(id_: str) -> quart.Response:
return redirect(url_for(".view", id_=id_))
@bp.route("/<id_>/") # type:ignore
async def view(id_: str) -> typing.Union[quart.Response,
typing.Tuple[str, int],
str]:
try:
invite = await client.get_public_invite_by_id(id_)
except aiohttp.ClientResponseError as exc:
if exc.status == 404:
# invite expired
return await render_template("invite_invalid.html")
return await render_template("invite_invalid.html"), 404
raise
if invite.reset_localpart is not None:
@@ -79,16 +85,23 @@ async def view(id_: str) -> str:
)
apple_store_url = current_app.config["APPLE_STORE_URL"]
return await render_template(
body = await render_template(
"invite_view.html",
invite=invite,
play_store_url=play_store_url,
apple_store_url=apple_store_url,
f_droid_url="market://details?id=org.snikket.android",
invite_id=id_,
)
return quart.Response(
body,
headers={
"Link": "<{}> rel=\"alternate\"".format(invite.xmpp_uri),
}
)
class RegisterForm(flask_wtf.FlaskForm): # type:ignore
class RegisterForm(BaseForm):
localpart = wtforms.StringField(
_l("Username"),
)
@@ -102,7 +115,7 @@ class RegisterForm(flask_wtf.FlaskForm): # type:ignore
validators=[wtforms.validators.InputRequired(),
wtforms.validators.EqualTo(
"password",
_l("The passwords must match")
_l("The passwords must match.")
)]
)
@@ -111,7 +124,7 @@ class RegisterForm(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/<id_>/register", methods=["GET", "POST"])
@bp.route("/<id_>/register", methods=["GET", "POST"]) # type:ignore
async def register(id_: str) -> typing.Union[str, quart.Response]:
try:
invite = await client.get_public_invite_by_id(id_)
@@ -134,15 +147,15 @@ async def register(id_: str) -> typing.Union[str, quart.Response]:
except aiohttp.ClientResponseError as exc:
if exc.status == 409:
form.localpart.errors.append(
_l("That username is already taken")
_l("That username is already taken.")
)
elif exc.status == 403:
form.localpart.errors.append(
_l("Registration was declined for unknown reasons")
_l("Registration was declined for unknown reasons.")
)
elif exc.status == 400:
form.localpart.errors.append(
_l("The username is not valid")
_l("The username is not valid.")
)
elif exc.status == 404:
return redirect(url_for(".view", id_=id_))
@@ -159,7 +172,7 @@ async def register(id_: str) -> typing.Union[str, quart.Response]:
)
class ResetForm(flask_wtf.FlaskForm): # type:ignore
class ResetForm(BaseForm):
password = wtforms.PasswordField(
_l("Password"),
)
@@ -169,7 +182,7 @@ class ResetForm(flask_wtf.FlaskForm): # type:ignore
validators=[wtforms.validators.InputRequired(),
wtforms.validators.EqualTo(
"password",
_l("The passwords must match")
_l("The passwords must match.")
)]
)
@@ -178,7 +191,7 @@ class ResetForm(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/<id_>/reset", methods=["GET", "POST"])
@bp.route("/<id_>/reset", methods=["GET", "POST"]) # type:ignore
async def reset(id_: str) -> typing.Union[str, quart.Response]:
try:
invite = await client.get_public_invite_by_id(id_)
@@ -202,7 +215,7 @@ async def reset(id_: str) -> typing.Union[str, quart.Response]:
except aiohttp.ClientResponseError as exc:
if exc.status == 403:
form.localpart.errors.append(
_l("Registration was declined for unknown reasons")
_l("Registration was declined for unknown reasons.")
)
elif exc.status == 404:
return redirect(url_for(".view", id_=id_))
@@ -219,7 +232,7 @@ async def reset(id_: str) -> typing.Union[str, quart.Response]:
)
@bp.route("/success", methods=["GET", "POST"])
@bp.route("/success", methods=["GET", "POST"]) # type:ignore
async def success() -> str:
return await render_template(
"invite_success.html",
@@ -227,7 +240,7 @@ async def success() -> str:
)
@bp.route("/success/reset", methods=["GET", "POST"])
@bp.route("/success/reset", methods=["GET", "POST"]) # type:ignore
async def reset_success() -> str:
return await render_template(
"invite_reset_success.html",
@@ -235,6 +248,6 @@ async def reset_success() -> str:
)
@bp.route("/-")
@bp.route("/-") # type:ignore
async def index() -> quart.Response:
return redirect(url_for("index"))

View File

@@ -15,23 +15,24 @@ from quart import (
render_template,
request,
Response,
flash,
)
import werkzeug.exceptions
import babel
import wtforms
import flask_wtf
from flask_babel import lazy_gettext as _l, _
from . import xmpputil, _version
from .infra import client
from .infra import client, BaseForm
bp = quart.Blueprint("main", __name__)
class LoginForm(flask_wtf.FlaskForm): # type:ignore
class LoginForm(BaseForm):
address = wtforms.TextField(
_l("Address"),
validators=[wtforms.validators.InputRequired()],
@@ -47,12 +48,15 @@ class LoginForm(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/-")
@bp.route("/-") # type:ignore
async def index() -> quart.Response:
return redirect(url_for("index"))
@bp.route("/login", methods=["GET", "POST"])
ERR_CREDENTIALS_INVALID = _l("Invalid username or password.")
@bp.route("/login", methods=["GET", "POST"]) # type:ignore
async def login() -> typing.Union[str, quart.Response]:
if client.has_session and (await client.test_session()):
return redirect(url_for('user.index'))
@@ -63,38 +67,53 @@ async def login() -> typing.Union[str, quart.Response]:
localpart, domain, resource = xmpputil.split_jid(jid)
if not localpart:
localpart, domain = domain, current_app.config["SNIKKET_DOMAIN"]
jid = "{}@{}".format(localpart, domain)
password = form.password.data
try:
await client.login(jid, password)
except quart.exceptions.Unauthorized:
form.password.errors.append(
_("Invalid username or password.")
)
if domain != current_app.config["SNIKKET_DOMAIN"]:
# (a) prosody throws a 400 at us and I prefer to catch that here
# and (b) I dont want to pass on this obviously not-for-here
# password further than necessary.
form.password.errors.append(ERR_CREDENTIALS_INVALID)
else:
return redirect(url_for('user.index'))
jid = "{}@{}".format(localpart, domain)
password = form.password.data
try:
await client.login(jid, password)
except werkzeug.exceptions.Unauthorized:
form.password.errors.append(ERR_CREDENTIALS_INVALID)
else:
await flash(
_("Login successful!"),
"success"
)
return redirect(url_for('user.index'))
return await render_template("login.html", form=form)
@bp.route("/meta/about.html")
@bp.route("/meta/about.html") # type:ignore
async def about() -> str:
version = None
extra_versions = {}
if current_app.debug:
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:
extra_versions["Prosody"] = "unknown"
return await render_template(
"about.html",
version=_version.version,
version=version,
extra_versions=extra_versions,
)
@bp.route("/meta/demo.html")
@bp.route("/meta/demo.html") # type:ignore
async def demo() -> str:
return await render_template("demo.html")
@@ -103,8 +122,9 @@ def repad(s: str) -> str:
return s + "=" * (4 - len(s) % 4)
@bp.route("/avatar/<from_>/<code>")
@bp.route("/avatar/<from_>/<code>") # type:ignore
async def avatar(from_: str, code: str) -> quart.Response:
etag: typing.Optional[str]
try:
etag = request.headers["if-none-match"]
except KeyError:
@@ -144,3 +164,8 @@ async def avatar(from_: str, code: str) -> quart.Response:
response.set_data(data)
return response
@bp.route("/_health") # type:ignore
async def health() -> Response:
return Response("STATUS OK", content_type="text/plain")

View File

@@ -19,7 +19,8 @@ from quart import (
current_app, _app_ctx_stack, session as http_session, abort, redirect,
url_for,
)
import quart.exceptions
import werkzeug.exceptions
import quart
from . import xmpputil
from .xmpputil import split_jid
@@ -44,6 +45,15 @@ class AdminUserInfo:
display_name: typing.Optional[str]
email: typing.Optional[str]
phone: typing.Optional[str]
roles: typing.Optional[typing.List[str]]
@property
def has_admin_role(self) -> bool:
return bool(self.roles and "prosody:admin" in self.roles)
@property
def has_restricted_role(self) -> bool:
return bool(self.roles and "prosody:restricted" in self.roles)
@classmethod
def from_api_response(
@@ -55,6 +65,7 @@ class AdminUserInfo:
display_name=data.get("display_name") or None,
email=data.get("email") or None,
phone=data.get("phone") or None,
roles=data.get("roles"),
)
@@ -261,8 +272,9 @@ class ProsodyClient:
def init_app(self, app: quart.Quart) -> None:
app.config[self.CONFIG_ENDPOINT]
app.teardown_appcontext(self._plain_session.teardown)
app.teardown_appcontext(self._auth_session.teardown)
# the type annotation in quart seems to be wrong here
app.teardown_appcontext(self._plain_session.teardown) # type:ignore
app.teardown_appcontext(self._auth_session.teardown) # type:ignore
@property
def _endpoint_base(self) -> str:
@@ -332,15 +344,18 @@ class ProsodyClient:
)
)
def _store_token_in_session(self, token_info: TokenInfo) -> None:
http_session[self.SESSION_TOKEN] = token_info.token
http_session[self.SESSION_CACHED_SCOPE] = " ".join(token_info.scopes)
async def login(self, jid: str, password: str) -> bool:
async with self._plain_session as session:
token_info = await self._oauth2_bearer_token(
session, jid, password,
)
http_session[self.SESSION_TOKEN] = token_info.token
self._store_token_in_session(token_info)
http_session[self.SESSION_ADDRESS] = jid
http_session[self.SESSION_CACHED_SCOPE] = " ".join(token_info.scopes)
return True
@property
@@ -445,6 +460,13 @@ class ProsodyClient:
headers=final_headers,
data=serialised) as resp:
if resp.status != 200:
self.logger.debug(
"IQ HTTP response (in-reply-to id=%s) with non-OK status "
"%s: %s",
id_,
resp.status,
resp.reason,
)
abort(resp.status)
reply_payload = await resp.read()
self.logger.debug(
@@ -469,7 +491,7 @@ class ProsodyClient:
session=session,
)
avatar_hash = avatar_info["sha1"]
except quart.exceptions.HTTPException:
except werkzeug.exceptions.HTTPException:
avatar_hash = None
return {
@@ -490,9 +512,32 @@ class ProsodyClient:
"to": self.session_address,
}
async with session.post(self._rest_endpoint, data=req) as resp:
async with session.post(self._rest_endpoint, json=req) as resp:
return resp.status == 200
@autosession
async def get_server_version(self, session: aiohttp.ClientSession) -> str:
_, domain, _ = split_jid(self.session_address)
req = {
"kind": "iq",
"type": "get",
"version": {},
"to": domain,
}
async with session.post(self._rest_endpoint, json=req) as resp:
if resp.status != 200:
return "unknwn"
try:
return (await resp.json())["version"]["version"]
except Exception as exc:
self.logger.debug(
"failed to parse prosody version from response"
" (%s: %s)",
type(exc), exc,
)
return "unknown"
@autosession
async def get_user_nickname(
self,
@@ -598,7 +643,7 @@ class ProsodyClient:
new_access_model,
)
))
except quart.exceptions.NotFound:
except werkzeug.exceptions.NotFound:
if ignore_not_found:
return
raise
@@ -728,7 +773,7 @@ class ProsodyClient:
session: aiohttp.ClientSession,
) -> str:
access_models = filter(
lambda x: not isinstance(x, quart.exceptions.NotFound),
lambda x: not isinstance(x, werkzeug.exceptions.NotFound),
await asyncio.gather(
self.get_avatar_access_model(session=session),
self.get_nickname_access_model(session=session),
@@ -767,7 +812,7 @@ class ProsodyClient:
# got there, replacing the current session token on the way.
async with self._plain_session as session:
token = await self._oauth2_bearer_token(
token_info = await self._oauth2_bearer_token(
session,
self.session_address,
current_password,
@@ -779,14 +824,14 @@ class ProsodyClient:
new_password
),
headers={
"Authorization": "Bearer {}".format(token),
"Authorization": "Bearer {}".format(token_info.token),
},
sensitive=True,
)
# TODO: error handling
# TODO: obtain a new token using the new password to allow the
# server to expire/revoke all tokens on password change.
http_session[self.SESSION_TOKEN] = token
self._store_token_in_session(token_info)
def _raise_error_from_response(
self,
@@ -825,6 +870,29 @@ class ProsodyClient:
self._raise_error_from_response(resp)
return AdminUserInfo.from_api_response(await resp.json())
@autosession
async def update_user(
self,
localpart: str,
*,
display_name: typing.Optional[str],
roles: typing.Optional[typing.Collection[str]],
session: aiohttp.ClientSession,
) -> None:
payload: typing.Dict[str, typing.Any] = {
"username": localpart,
}
if display_name is not None:
payload["display_name"] = display_name
if roles is not None:
payload["roles"] = list(roles)
async with session.put(
self._admin_v1_endpoint("/users/{}".format(localpart)),
json=payload,
) as resp:
self._raise_error_from_response(resp)
@autosession
async def get_user_debug_info(
self,

View File

@@ -252,3 +252,4 @@ $h-sizes: [200.0%, 174.11011266%, 151.57165665%, 131.95079108%, 114.8698355%, 10
$h-small-sizes: [150.0%, 138.31618672%, 127.54245006%, 117.60790225%, 108.44717712%, 100.0%];
$small-screen-threshold: 40rem;
$medium-screen-threshold: 60rem;
$large-screen-threshold: 80rem;

View File

@@ -33,13 +33,35 @@ body {
main {
padding: $w-l1;
margin-left: auto;
max-width: 60rem;
margin-right: auto;
}
#mwrap {
flex: 1;
display: flex;
flex-direction: row-reverse;
> .filler, > .flashbox {
flex: 1 1 1rem;
}
> main {
flex: 0 1 60rem;
}
}
@media screen and (max-width: $large-screen-threshold) {
#mwrap {
display: block;
> main {
margin-left: auto;
margin-right: auto;
}
}
}
.flashbox > div.box > :first-child {
margin-top: 0;
}
/* top bar */
@@ -67,6 +89,10 @@ div#topbar {
font-size: $_top-h-size;
line-height: 1.5;
body.debug & {
color: red;
}
@media screen and (max-width: $small-screen-threshold) {
font-size: $_top-h-small-size;
}
@@ -134,22 +160,20 @@ body > footer {
background-color: $gray-100;
color: $gray-800;
padding: 0 $w-l1;
font-size: 92.21079115%;
ul {
display: block;
padding: 0;
margin: 0;
list-style-type: none;
text-align: center;
line-height: 1.6267076567643135;
}
li {
display: inline-block;
margin: $w-l1 0;
}
li:before {
content: '';
padding-right: $w-s2;
display: block;
margin: $w-s1 0;
}
a, a:visited, a:hover, a:active, a:focus {
@@ -330,6 +354,15 @@ div.form.layout-expanded {
display: block;
}
.radio-button-ext > label > p {
margin-left: 1.75rem;
margin-top: 0;
}
.radio-button-ext > label .icon {
margin-left: 0.25em;
}
div.select-wrap {
display: block;
border-bottom: $w-s4 solid $primary-500;
@@ -993,6 +1026,23 @@ div.profile-card {
display: none;
}
}
input[type="submit"], button, .button {
&.slimmify {
> svg.icon {
margin-right: 0;
}
> span {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
top: -100px;
}
}
}
}
/* clipboard button */

View File

@@ -54,6 +54,8 @@ div.install-buttons {
ul {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
list-style-type: none;
margin: $w-l1 0;
padding: 0;
@@ -74,6 +76,10 @@ img.play {
height: $w-l3;
}
img.fdroid {
height: $w-l3;
}
.tabbox {
display: flex;
flex-direction: column;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -37,6 +37,11 @@ licensed under the terms of the Apache 2.0 License -->
<path d="M0 0h24v24H0V0z" fill="none" />
<path d="M10.79 16.29c.39.39 1.02.39 1.41 0l3.59-3.59c.39-.39.39-1.02 0-1.41L12.2 7.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L12.67 11H4c-.55 0-1 .45-1 1s.45 1 1 1h8.67l-1.88 1.88c-.39.39-.38 1.03 0 1.41zM19 3H5c-1.11 0-2 .9-2 2v3c0 .55.45 1 1 1s1-.45 1-1V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v12c0 .55-.45 1-1 1H6c-.55 0-1-.45-1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1v3c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z" />
</symbol>
<!-- from: action/lock/materialiconsround/24px.svg -->
<symbol id="icon-lock" viewBox="0 0 24 24">
<g fill="none"><path d="M0 0h24v24H0V0z" /><path d="M0 0h24v24H0V0z" opacity=".87" /></g>
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z" />
</symbol>
<!-- from: communication/qr_code/materialiconsround/24px.svg -->
<symbol id="icon-qrcode" viewBox="0 0 24 24">
<g><rect fill="none" height="24" width="24" /><rect fill="none" height="24" width="24" /></g>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,5 +3,7 @@
{#- -#}
<li>{% trans about_url=url_for('main.about') %}A <a href="{{ about_url }}">Snikket</a> service{% endtrans %}</li>
{#- -#}
<li>{% trans %}“Snikket” and the parrot logo are trademarks of Snikket Community Interest Company.{% endtrans %}</li>
{#- -#}
</ul>
</footer>

View File

@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% from "library.j2" import standard_button %}
{% block head_lead %}
<title>About Snikket</title>
<title>{% trans %}About Snikket{% endtrans %}</title>
{% endblock %}
{% block body %}
<main>
@@ -14,10 +14,12 @@
<p>{% trans agpl_url="https://www.gnu.org/licenses/agpl.html" %}The web portal software is licensed under the terms of the <a href="{{ agpl_url }}">Affero GNU General Public License, version 3.0 or later</a>. The full terms of the license can be reviewed using the aforementioned link.{% endtrans %}</p>
<p>{% trans source_url="https://github.com/snikket-im/snikket-web-portal/" %}The source code of the web portal can be downloaded and viewed in <a href="{{ source_url }}">its GitHub repository</a>.{% endtrans %}</p>
<p>{% trans source_url="https://material.io/resources/icons/", apache20_url="https://www.apache.org/licenses/LICENSE-2.0.txt" %}The icons used in the web portal are <a href="{{ source_url }}">Googles Material Icons</a>, made available by Google under the terms of the <a href="{{ apache20_url }}">Apache 2.0 License</a>.{% endtrans %}</p>
<h3>{% trans %}Trademarks{% endtrans %}</h3>
<p>{% trans trademarks_url="https://snikket.org/about/trademarks/" %}“Snikket” and the parrot logo are trademarks of Snikket Community Interest Company. For more information about the trademarks, visit the <a href="{{ trademarks_url }}">Snikket Trademarks information page</a>.{% endtrans %}
<h3>{% trans %}Software Versions{% endtrans %}</h3>
<pre>Snikket Server
Domain: {{ config["SNIKKET_DOMAIN"] }}
Snikket Web Portal ({{ version }})
Snikket Web Portal{% if version %} ({{ version }}){% endif %}
{%- if extra_versions -%}
{% for name, version in extra_versions.items() %}
{{ name }} ({{ version }}){% endfor %}

View File

@@ -16,7 +16,7 @@
<p>{% trans %}The user and their data will be deleted irrevocably, permanently and immediately upon pushing the below button. <strong>There is no way back!</strong>{% endtrans %}</p>
{% endcall %}
<div class="f-bbox">
{%- call standard_button("back", url_for(".index"), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call standard_button("back", url_for(".edit_user", localpart=target_user.localpart), class="tertiary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call form_button("delete", form.action_delete, class="primary danger") %}{% endcall -%}
</div>
</form></div>

View File

@@ -40,8 +40,8 @@
{%- endif -%}
</div>
<div class="f-bbox">
{%- call standard_button("back", url_for(".circles"), class="secondary") -%}
{% trans %}Back{% endtrans %}
{%- call standard_button("back", url_for(".circles"), class="tertiary") -%}
{% trans %}Return to circle list{% endtrans %}
{%- endcall -%}
{%- call form_button("done", form.action_save, class="primary") %}{% endcall -%}
</div>

View File

@@ -44,10 +44,10 @@
<dd>{{ invite.created_at | format_date }}</dd>
</dl>
<div class="f-bbox">
{%- call form_button("remove_link", form.action_revoke, class="secondary danger") %}{% endcall -%}
{%- call standard_button("back", url_for(".invitations"), class="primary") %}
{% trans %}Back{% endtrans %}
{%- call standard_button("back", url_for(".invitations"), class="tertiary") %}
{% trans %}Return to invitation list{% endtrans %}
{%- endcall %}
{%- call form_button("remove_link", form.action_revoke, class="primary danger") %}{% endcall -%}
</div>
</div>
</form>

View File

@@ -0,0 +1,78 @@
{% extends "admin_app.html" %}
{% from "library.j2" import box, form_button, standard_button, icon %}
{% macro access_level_description(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% trans %}Limited users can interact with users on the same Snikket service and be members of circles.{% endtrans %}
{%- elif role == "prosody:normal" -%}
{% trans %}Like limited users and can also interact with users on other Snikket services.{% endtrans %}
{%- elif role == "prosody:admin" -%}
{% trans %}Like normal users and can access the admin panel in the web portal.{% endtrans %}
{%- endif -%}
{% endmacro %}
{% macro access_level_icon(role, caller=None) %}
{%- if role == "prosody:restricted" -%}
{% call icon("lock") %}{% endcall %}
{%- elif role == "prosody:admin" -%}
{% call icon("admin") %}{% endcall %}
{%- endif -%}
{% endmacro %}
{% block content %}
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
<form method="POST">{{ form.csrf_token }}<div class="form layout-expanded">
<h2 class="form-title">{% trans %}Edit user{% endtrans %}</h2>
<div class="f-ebox">
{{ form.localpart.label }}
{{ form.localpart(readonly="readonly") }}
<p class="form-desc weak">{% trans %}The login name cannot be changed.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.display_name.label }}
{{ form.display_name }}
</div>
<h3 class="form-title">{% trans %}Access Level{% endtrans %}</h3>
<p class="form-descr weak">{% trans %}The access level of a user determines what interactions are allowed for them on your Snikket service.{% endtrans %}</p>
<div class="f-ebox">
<fieldset>{#- -#}
<legend class="a11y-only">{{ form.role.label.text }}</legend>
{%- for level in form.role -%}
<div class="radio-button-ext">
{{ level }}<label for="{{ level.id }}">
{%- trans title=level.label.text, icon=access_level_icon(level.data), description=access_level_description(level.data) -%}
<strong>{{ title }}{{ icon }}</strong><p>{{ description }}</p>
{%- endtrans -%}
</label>
</div>
{%- endfor -%}
</fieldset>
</div>
<div class="f-bbox">
{%- call standard_button("back", url_for(".users"), class="tertiary") -%}
{%- trans -%}Return to user list{%- endtrans -%}
{%- endcall -%}
{%- call standard_button("delete", url_for(".delete_user", localpart=target_user.localpart), class="secondary") -%}
{%- trans -%}Delete user{%- endtrans -%}
{%- endcall -%}
{%- call form_button("done", form.action_save, class="primary") %}{% endcall -%}
</div>
</div>
<h2>{% trans %}Further actions{% endtrans %}</h2>
<div class="form layout-expanded">
<h2 class="form-title">{% trans %}Reset password{% endtrans %}</h2>
{{ form.csrf_token }}
<p class="form-desc">
{% trans %}If the user has lost their password, you can use the button below to create a special link which allows to change the password of the account, once.{% endtrans %}
</p>
<div class="f-bbox">
{%- call form_button("passwd", form.action_create_reset, class="primary") -%}{%- endcall -%}
</div>
<h2 class="form-title">{% trans %}Debug information{% endtrans %}</h2>
<p class="form-desc">
{% trans %}In some cases, extended information about the user account and the connected devices is necessary to troubleshoot issues. The button below reveals this (sensitive) information.{% endtrans %}
</p>
<div class="f-bbox">
{%- call standard_button("bug_report", url_for(".debug_user", localpart=target_user.localpart), class="primary") -%}
{%- trans -%}Show debug information{%- endtrans -%}
{%- endcall -%}
</div>
</div></form>
{% endblock %}

View File

@@ -18,7 +18,7 @@
<col/>
<thead>
<tr>
<th>{% trans %}Valid until{% endtrans %}</th>
<th>{% trans %}Expires{% endtrans %}</th>
<th class="collapsible">{% trans %}Type{% endtrans %}</th>
<th class="collapsible">{% trans %}Circle{% endtrans %}</th>
<th>{% trans %}Actions{% endtrans %}</th>

View File

@@ -9,7 +9,7 @@
<form method="POST">
{{- form.csrf_token -}}
<div class="form layout-expanded">
<h2 class="form-title">{% trans user_name=target_user.localpart %}Password reset link for {{ user_name }}{% endtrans %}</h2>
<h2 class="form-title">{% trans user_name=localpart %}Password reset link for {{ user_name }}{% endtrans %}</h2>
<p class="form-desc">{% trans %}The following link will allow the user to reset their password on their device, once.{% endtrans %}</p>
<dd>
<dt>{% trans %}Valid until{% endtrans %}</dt>
@@ -21,7 +21,7 @@
{%- call custom_form_button("remove_link", form.action_revoke.name, reset_link.id_, class="secondary danger") -%}
{% trans %}Destroy link{% endtrans %}
{%- endcall -%}
{%- call standard_button("back", url_for(".users"), class="primary") -%}
{%- call standard_button("back", url_for(".edit_user", localpart=localpart), class="primary") -%}
{% trans %}Back{% endtrans %}
{%- endcall -%}
</div>

View File

@@ -1,9 +1,7 @@
{% extends "admin_app.html" %}
{% from "library.j2" import action_button, value_or_hint, custom_form_button %}
{% from "library.j2" import action_button, icon, value_or_hint, custom_form_button %}
{% block content %}
<h1>{% trans %}Manage users{% endtrans %}</h1>
<form method="POST" action="{{ url_for(".create_password_reset_link") }}">
{{- reset_form.csrf_token -}}
<div class="elevated el-2"><table>
<thead>
<tr>
@@ -15,17 +13,19 @@
<tbody>
{% for user in users %}
<tr>
<td>{{ user.localpart }}</td>
<td>
{{- user.localpart -}}
{%- if user.has_admin_role -%}
<span class="with-tooltip above" data-tooltip="{% trans %}The user is an administrator.{% endtrans %}">{% call icon("admin") %}{% trans %} (Administrator){% endtrans %}{% endcall %}</span>
{%- endif -%}
{%- if user.has_restricted_role -%}
<span class="with-tooltip above" data-tooltip="{% trans %}The user is restricted.{% endtrans %}">{% call icon("lock") %}{% trans %} (Restricted){% endtrans %}{% endcall %}</span>
{%- endif -%}
</td>
<td>{% call value_or_hint(user.display_name) %}{% endcall %}</td>
<td class="nowrap">
{%- call action_button("delete", url_for(".delete_user", localpart=user.localpart), class="secondary") -%}
{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}
{%- endcall -%}
{%- call action_button("bug_report", url_for(".debug_user", localpart=user.localpart), class="secondary") -%}
{% trans user_name=user.localpart %}Show debug information for {{ user_name }}{% endtrans %}
{%- endcall -%}
{%- call custom_form_button("passwd", reset_form.action_create.name, user.localpart, class="secondary", slim=True) -%}
{% trans user_name=user.localpart %}Create password reset link for {{ user_name }}{% endtrans %}
{%- call action_button("edit", url_for(".edit_user", localpart=user.localpart), class="primary") -%}
{% trans user_name=user.localpart %}Edit user {{ user_name }}{% endtrans %}
{%- endcall -%}
</form>
</td>
@@ -33,5 +33,5 @@
{% endfor %}
</tbody>
</table></div>
</form>
{%- include "admin_create_invite_form.html" -%}
{% endblock %}

View File

@@ -5,5 +5,5 @@
{% endblock %}
{% block topbar_right %}
{{- super() -}}
{% call standard_button("logout", url_for("user.logout"), class="tertiary") %}{% trans %}Log out{% endtrans %}{% endcall %}
{% call standard_button("logout", url_for("user.logout"), class="tertiary slimmify") %}{% trans %}Log out{% endtrans %}{% endcall %}
{%- endblock %}

View File

@@ -16,5 +16,5 @@
<meta name="msapplication-TileColor" content="#fbd308">
<meta name="theme-color" content="#fbd308">
</head>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %}{% if body_class | default(False) %} class="{{ body_class }}"{% endif %}{% if onload | default(False) %} onload="{{ onload }}"{% endif %}>{% block body %}{% endblock %}</body>
<body{% if body_id | default(False) %} id="{{ body_id }}"{% endif %} class="{% if is_in_debug_mode %}debug{% endif %}{% if body_class | default(False) %} {{ body_class }}{% endif %}"{% if onload | default(False) %} onload="{{ onload }}"{% endif %}>{% block body %}{% endblock %}</body>
</html>

View File

@@ -5,6 +5,6 @@
<link rel="stylesheet" type="text/css" href="{{ url_for("static", filename="css/invite.css") }}">
{% endblock %}
{% block body %}
<div id="mwrap"><main>{% block content %}{% endblock %}</main></div>
<div id="mwrap"><div class="filler"></div><main>{% block content %}{% endblock %}</main><div class="filler"></div></div>
{%- include "_footer.html" -%}
{% endblock %}

View File

@@ -6,6 +6,7 @@
<title>{% trans site_name=config["SITE_NAME"] %}Invite to {{ site_name }} | Snikket{% endtrans %}</title>
<script async type="text/javascript" src="{{ url_for("static", filename="js/invite-magic.js") }}"></script>
<script async type="text/javascript" src="{{ url_for("static", filename="js/qrcode.min.js") }}"></script>
<link rel="alternate" href="{{ invite.xmpp_uri }}">
{% endblock %}
{% block content %}
<div class="elevated box el-3">
@@ -26,11 +27,12 @@
<ul>
<li><a href="{{ play_store_url }}"><img alt='{% trans %}Get it on Google Play{% endtrans %}' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png' class="play"/></a></li>
{%- if apple_store_url -%}
<li><a href="{{ apple_store_url }}"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></li>
<li><a href="{{ apple_store_url }}" class="popover" data-popover-id="apple-popover"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></li>
{%- endif -%}
<li><a href="{{ f_droid_url }}" class="popover" data-popover-id="fdroid-popover"><img alt='{% trans %}Get it on F-Droid{% endtrans %}' src='{{ url_for('static', filename='img/f-droid-badge.png') }}' class="fdroid"/></a></li>
</ul>
{%- call standard_button("qrcode", "#qr-modal", class="primary", onclick="open_modal(this); return false;") -%}
{% trans %}Not on mobile?{% endtrans %}
{% trans %}Send to mobile device{% endtrans %}
{%- endcall -%}
</div>
<p>{% trans %}After installation the app should automatically open and prompt you to create an account. If not, simply click the button below.{% endtrans %}</p>
@@ -83,10 +85,77 @@
{%- endcall -%}
</div>
</div>
{%- if apple_store_url -%}
<div id="apple-popover" class="modal" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;" onclick="close_modal(this); return false;">
<div role="document" class="elevated box el-2" onclick="event.stopPropagation();">
<header class="modal-title">
{#- -#}
<span>{% trans %}Install on iOS{% endtrans %}</span>
{#- -#}
{%- call action_button("close", "#", onclick="close_modal(this.parentNode.parentNode.parentNode); return false;", class="tertiary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</header>
<p>{% trans %}After downloading Snikket from the App Store, you have to return to this invite link and tap on "Open the app" to proceed.{% endtrans %}</p>
<ol>
<li><p>{% trans %}First download Snikket from the App Store using the button below:{% endtrans %}</p>
<p><a href="{{ apple_store_url }}"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></p>
<li><p>{% trans %}After the installation is complete, you can return to this page and tap the "Open the app" button to continue with the setup:{% endtrans %}</p>
<p>
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="primary") -%}
{% trans %}Open the app{% endtrans %}
{%- endcall -%}
</p></li>
</ol>
{#- -#}
{%- call standard_button("close", "#", onclick="close_modal(this.parentNode.parentNode); return false;", class="secondary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</div>
</div>
{%- endif -%}
<div id="fdroid-popover" class="modal" tabindex="-1" role="dialog" aria-hidden="true" style="display: none;" onclick="close_modal(this); return false;">
<div role="document" class="elevated box el-2" onclick="event.stopPropagation();">
<header class="modal-title">
{#- -#}
<span>{% trans %}Install via F-Droid{% endtrans %}</span>
{#- -#}
{%- call action_button("close", "#", onclick="close_modal(this.parentNode.parentNode.parentNode); return false;", class="tertiary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</header>
<p>{% trans %}After installing Snikket via F-Droid, you have to return to this invite link and tap on "Open the app" to proceed.{% endtrans %}</p>
<ol>
<li><p>{% trans %}First install Snikket from F-Droid using the button below:{% endtrans %}</p>
<p><a href="{{ f_droid_url }}" class="popover" data-popover-id="fdroid-popover"><img alt='{% trans %}Install via F-Droid{% endtrans %}' src='{{ url_for('static', filename='img/f-droid-badge.png') }}' class="fdroid"/></a></p></li>
<li><p>{% trans %}After the installation is complete, you can return to this page and tap the "Open the app" button to continue with the setup:{% endtrans %}</p>
<p>
{%- call standard_button("exit_to_app", invite.xmpp_uri, class="primary") -%}
{% trans %}Open the app{% endtrans %}
{%- endcall -%}
</p></li>
</ol>
{#- -#}
{%- call standard_button("close", "#", onclick="close_modal(this.parentNode.parentNode); return false;", class="secondary") -%}
{% trans %}Close{% endtrans %}
{%- endcall -%}
</div>
</div>
<script type="text/javascript">
var catch_popover = function() {
open_modal(this);
return false;
}
var onload = function() {
apply_qr_code(document.getElementById("qr-invite-page"));
apply_qr_code(document.getElementById("qr-uri"));
var popover_as = document.getElementsByClassName("popover");
for (var i = 0; i < popover_as.length; ++i) {
var a = popover_as[i];
a.onclick = catch_popover;
a.href = "#" + a.dataset.popoverId;
}
};
</script>
{% endblock %}

View File

@@ -80,7 +80,7 @@
<div class="box warning">{#- -#}
<header>{% trans %}Invalid input{% endtrans %}</header>
{%- if error_list | length == 1 -%}
<p>{{ error_list[0] }}.</p>
<p>{{ error_list[0] }}</p>
{%- else -%}
<ul>
{%- for error in error_list -%}

View File

@@ -1,5 +1,5 @@
{% extends "base.html" %}
{% from "library.j2" import box, form_button %}
{% from "library.j2" import box, form_button, render_errors %}
{% set body_id = "login" %}
{% block head_lead %}
<title>{{ _("Snikket Login") }}</title>
@@ -9,16 +9,16 @@
{{ super() }}
{% endblock %}
{% block body %}
<div id="mwrap"><main><div class="form layout-expanded">
<div id="mwrap"><div class="filler"></div><main><div class="form layout-expanded">
<h1 class="form-title">{{ config["SITE_NAME"] }}</h1>
<p class="form-desc">{{ _("Enter your Snikket address and password to manage your account.") }}</p>
<form method="POST" action="{{ url_for('.login') }}" name="login">
<form method="POST" action="{{ url_for('.login') }}" name="login" id="login-form" onsubmit="return domainCheck();" data-addressid="{{ form.address.id }}" data-domain="{{ config["SNIKKET_DOMAIN"] }}">
{{ form.csrf_token }}
{% if form.errors %}
{% call box("alert", _("Login failed")) %}
<p>{{ form.errors.values() | flatten | join(", ")}}</p>
{% endcall %}
{% endif %}
{% call render_errors(form) %}{% endcall %}
<div class="box alert" role="alert" style="display: none;" id="id-warning">
<header>{% trans %}Incorrect address{% endtrans %}</header>
<p>{% trans snikket_domain=config["SNIKKET_DOMAIN"] %}This Snikket service only hosts addresses ending in <em>@{{ snikket_domain }}</em>. Your password was not sent.{% endtrans %}</p>
</div>
<div class="f-ebox">
{{ form.address.label(class="a11y-only") }}
{{ form.address(placeholder=form.address.label.text) }}
@@ -31,8 +31,22 @@
{%- call form_button("login", form.action_signin, class="primary") -%}{% endcall -%}
</div>
</from>
</div></main></div>
<footer>
<ul><li>{% trans about_url=url_for('.about') %}A <a href="{{ about_url }}">Snikket</a> service{% endtrans %}</li></ul>
</footer>
<script type="text/javascript">
var domainCheck = function() {
var form = document.getElementById("login-form");
var addressId = form.dataset.addressid;
var addressField = document.getElementById(addressId);
var domain = form.dataset.domain;
var address = addressField.value;
var errorBox = document.getElementById("id-warning");
if (address.includes("@") && !address.endsWith(domain)) {
errorBox.style.display = "block";
return false;
}
errorBox.style.display = "none";
return true;
};
</script>
</div></main><div class="filler"></div></div>
{%- include "_footer.html" -%}
{% endblock %}

View File

@@ -7,6 +7,25 @@
<div class="filler"></div>
{% block topbar_right %}{% endblock %}
</div>
<div id="mwrap"><main>{% block content %}{% endblock %}</main></div>
<div id="mwrap">
{#- -#}
<div class="flashbox" id="flashbox">
{%- for category, message in get_flashed_messages(True) -%}
<div class="box {{ category }} el-5" role="alert">
{% if category == "success" %}
<header>{% trans %}Operation successful{% endtrans %}</header>
{% elif category == "alert" %}
<header>{% trans %}Error{% endtrans %}</header>
{% endif %}
<p>{{ message }}</p>
</div>
{%- endfor -%}
</div>
{#- -#}
<main>{% block content %}{% endblock %}</main>
{#- -#}
<div class="filler"></div>
{#- -#}
</div>
{%- include "_footer.html" -%}
{% endblock %}

View File

@@ -1,15 +1,12 @@
{% extends "app.html" %}
{% from "library.j2" import standard_button, form_button %}
{% block head_lead %}
<title>Snikket Web Portal</title>
{% endblock %}
{% block content %}
<div class="form layout-expanded"><form method="POST">
<h2 class="form-title">{% trans %}Sign out of the Snikket Web Portal{% endtrans %}</h2>
<p class="form-desc">{% trans %}Click below to log yourself out of the web portal. This does not affect any other connected devices.{% endtrans %}</p>
{{ form.csrf_token }}
<div class="f-bbox">
{%- call standard_button("back", url_for("user.index"), class="secondary") -%}
{%- call standard_button("back", url_for("user.index"), class="tertiary") -%}
{% trans %}Back{% endtrans %}
{%- endcall -%}
{%- call form_button("logout", form.action_signout, class="primary") %}{% endcall -%}

View File

@@ -1,8 +1,5 @@
{% extends "app.html" %}
{% from "library.j2" import standard_button, custom_form_button, render_errors %}
{% block head_lead %}
<title>Snikket Web Portal</title>
{% endblock %}
{% block content %}
<div class="form layout-expanded"><form method="POST">
<h1 class="form-title">{% trans %}Change your password{% endtrans %}</h1>
@@ -27,7 +24,7 @@
<p>{% trans %}After changing your password, you will have to enter the new password on all of your devices.{% endtrans %}</p>
</div>
<div class="f-bbox">
{%- call standard_button("back", url_for('.index'), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call standard_button("back", url_for('.index'), class="tertiary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call custom_form_button("passwd", "", "", class="primary") -%}
{% trans %}Change password{% endtrans %}
{%- endcall -%}

View File

@@ -1,13 +1,11 @@
{% extends "app.html" %}
{% from "library.j2" import standard_button, form_button, avatar with context %}
{% block head_lead %}
<title>Snikket Web Portal</title>
{% endblock %}
{% from "library.j2" import standard_button, form_button, render_errors, avatar with context %}
{% block content %}
<h1>{% trans %}Update your profile{% endtrans %}</h1>
<div class="form layout-expanded"><form method="POST" enctype="multipart/form-data">
<h2 class="form-title">{% trans %}Profile{% endtrans %}</h2>
{{ form.csrf_token }}
{% call render_errors(form) %}{% endcall %}
<div class="f-ebox">
{{ form.nickname.label }}
{{ form.nickname(placeholder=user_info.username) }}
@@ -16,7 +14,10 @@
{{ form.avatar.label }}
<div class="avatar-wrap">
{%- call avatar(user_info.address, user_info.avatar_hash ) %}{% endcall -%}
{{ form.avatar }}
{{ form.avatar(accept="image/png",
data_maxsize=max_avatar_size,
data_warning_header=avatar_too_big_warning_header,
data_maxsize_warning=avatar_too_big_warning) }}
</div>
</div>
<h3 class="form-title">{% trans %}Visibility{% endtrans %}</h3>
@@ -28,8 +29,27 @@
</fieldset>
</div>
<div class="f-bbox">
{%- call standard_button("back", url_for('.index'), class="secondary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call standard_button("back", url_for('.index'), class="tertiary") %}{% trans %}Back{% endtrans %}{% endcall -%}
{%- call form_button("done", form.action_save, class="primary") %}{% endcall -%}
</div>
<script type="text/javascript">
document.getElementById("{{ form.avatar.id }}").onchange = function() {
var maxsize_s = this.dataset.maxsize;
var maxsize = parseInt(maxsize_s);
var existing_alert = document.getElementById("avatar-alert");
if (existing_alert) {
existing_alert.parentNode.removeChild(existing_alert);
}
if (this.files[0].size > maxsize) {
var warning_header = this.dataset.warningHeader;
var warning_text = this.dataset.maxsizeWarning;
this.setCustomValidity(warning_text);
this.reportValidity();
this.value = null;
} else {
this.setCustomValidity("");
}
};
</script>
</form></div>
{% endblock %}

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: SnikketWeb 0.1.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-30 12:45+0100\n"
"PO-Revision-Date: 2021-01-31 12:54+0000\n"
"POT-Creation-Date: 2021-03-25 17:32+0100\n"
"PO-Revision-Date: 2021-05-18 13:28+0000\n"
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
"Language-Team: German <https://i18n.sotecware.net/projects/snikket/"
"web-portal/de/>\n"
@@ -17,127 +17,217 @@ msgstr ""
"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.4.2\n"
"X-Generator: Weblate 4.5.1\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:60
#: snikket_web/admin.py:59
msgid "Limited"
msgstr "Eingeschränkt"
#: snikket_web/admin.py:64 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr "Anmeldename"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:61
msgid "Display name"
msgstr "Anzeigename"
#: snikket_web/admin.py:72 snikket_web/templates/admin_edit_user.html:33
msgid "Access Level"
msgstr "Berechtigungen"
#: snikket_web/admin.py:77
msgid "Normal user"
msgstr "Normaler Benutzer"
#: snikket_web/admin.py:78
msgid "Administrator"
msgstr "Administrator"
#: snikket_web/admin.py:83
msgid "Update user"
msgstr "Benutzer ändern"
#: snikket_web/admin.py:87
msgid "Create password reset link"
msgstr "Passwort-Link erzeugen"
#: snikket_web/admin.py:105
msgid "Password reset link created"
msgstr "Link zum Zurücksetzen des Passwortes erzeugt"
#: snikket_web/admin.py:120
msgid "User information updated."
msgstr "Benutzerinformationen gespeichert."
#: snikket_web/admin.py:142
msgid "Delete user permanently"
msgstr "Benutzer endgültig löschen"
#: snikket_web/admin.py:129
#: snikket_web/admin.py:155
msgid "User deleted"
msgstr "Benutzer gelöscht"
#: snikket_web/admin.py:193
msgid "Password reset link not found"
msgstr "Link zum Zurücksetzen des Passwortes nicht gefunden"
#: snikket_web/admin.py:205
msgid "Password reset link deleted"
msgstr "Link gelöscht"
#: snikket_web/admin.py:225
msgid "Invite to circle"
msgstr "In Gemeinschaft einladen"
#: snikket_web/admin.py:135
#: snikket_web/admin.py:231
msgid "At least one circle must be selected"
msgstr "Mindestens eine Gemeinschaft muss ausgewählt sein"
#: snikket_web/admin.py:140
#: snikket_web/admin.py:236
msgid "Valid for"
msgstr "Gültig für"
#: snikket_web/admin.py:142
#: snikket_web/admin.py:238
msgid "One hour"
msgstr "Eine Stunde"
#: snikket_web/admin.py:143
#: snikket_web/admin.py:239
msgid "Twelve hours"
msgstr "Zwölf Stunden"
#: snikket_web/admin.py:144
#: snikket_web/admin.py:240
msgid "One day"
msgstr "Ein Tag"
#: snikket_web/admin.py:145
#: snikket_web/admin.py:241
msgid "One week"
msgstr "Eine Woche"
#: snikket_web/admin.py:146
#: snikket_web/admin.py:242
msgid "Four weeks"
msgstr "Vier Wochen"
#: snikket_web/admin.py:152 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:248 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr "Art der Einladung"
#: snikket_web/admin.py:154 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:250 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr "Einzelperson"
#: snikket_web/admin.py:155 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:251 snikket_web/templates/library.j2:114
msgid "Group"
msgstr "Gruppe"
#: snikket_web/admin.py:161
#: snikket_web/admin.py:257
msgid "New invitation link"
msgstr "Neuer Einladungslink"
#: snikket_web/admin.py:223
#: snikket_web/admin.py:319
msgid "Revoke"
msgstr "Löschen"
#: snikket_web/admin.py:283 snikket_web/admin.py:327
#: snikket_web/admin.py:343
msgid "Invitation created"
msgstr "Einladung angelegt"
#: snikket_web/admin.py:359
msgid "No such invitation exists"
msgstr "Diese Einladung existiert nicht"
#: snikket_web/admin.py:374
msgid "Invitation revoked"
msgstr "Einladung gelöscht"
#: snikket_web/admin.py:391 snikket_web/admin.py:439
msgid "Name"
msgstr "Name"
#: snikket_web/admin.py:288 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:396 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr "Gemeinschaft gründen"
#: snikket_web/admin.py:332
#: snikket_web/admin.py:426
msgid "Circle created"
msgstr "Gemeinschaft gegründet"
#: snikket_web/admin.py:444
msgid "Select user"
msgstr "Benutzer auswählen"
#: snikket_web/admin.py:337
#: snikket_web/admin.py:449
msgid "Update circle"
msgstr "Gemeinschaft ändern"
#: snikket_web/admin.py:341
#: snikket_web/admin.py:453
msgid "Delete circle permanently"
msgstr "Gemeinschaft endgültig löschen"
#: snikket_web/admin.py:347
#: snikket_web/admin.py:459
msgid "Add user"
msgstr "Benutzer hinzufügen"
#: snikket_web/infra.py:40
#: snikket_web/admin.py:475
msgid "No such circle exists"
msgstr "Diese Gemeinschaft existiert nicht"
#: snikket_web/admin.py:512
msgid "Circle data updated"
msgstr "Gemeinschaftsdaten aktualisiert"
#: snikket_web/admin.py:518
msgid "Circle deleted"
msgstr "Gemeinschaft gelöscht"
#: snikket_web/admin.py:529
msgid "User added to circle"
msgstr "Benutzer zur Gemeinschaft hinzugefügt"
#: snikket_web/admin.py:538
msgid "User removed from circle"
msgstr "Benutzer aus der Gemeinschaft entfernt"
#: snikket_web/infra.py:41
msgid "Main"
msgstr "Kern"
#: snikket_web/invite.py:93
#: snikket_web/invite.py:106
msgid "Username"
msgstr "Benutzername"
#: snikket_web/invite.py:97 snikket_web/invite.py:164 snikket_web/main.py:41
#: snikket_web/invite.py:110 snikket_web/invite.py:177 snikket_web/main.py:41
msgid "Password"
msgstr "Passwort"
#: snikket_web/invite.py:101 snikket_web/invite.py:168
#: snikket_web/invite.py:114 snikket_web/invite.py:181
msgid "Confirm password"
msgstr "Passwort (Bestätigung)"
#: snikket_web/invite.py:105 snikket_web/invite.py:172
msgid "The passwords must match"
msgstr "Die Passwörter müssen übereinstimmen"
#: snikket_web/invite.py:118 snikket_web/invite.py:185
msgid "The passwords must match."
msgstr "Die Passwörter müssen übereinstimmen."
#: snikket_web/invite.py:110
#: snikket_web/invite.py:123
msgid "Create account"
msgstr "Konto anlegen"
#: snikket_web/invite.py:137
msgid "That username is already taken"
msgstr "Dieser Benutzername ist bereits belegt"
#: snikket_web/invite.py:150
msgid "That username is already taken."
msgstr "Dieser Benutzername ist bereits belegt."
#: snikket_web/invite.py:141 snikket_web/invite.py:205
msgid "Registration was declined for unknown reasons"
msgstr "Die Registrierung wurde aus unbekannten Gründen abgelehnt"
#: snikket_web/invite.py:154 snikket_web/invite.py:218
msgid "Registration was declined for unknown reasons."
msgstr "Die Registrierung wurde aus unbekannten Gründen abgelehnt."
#: snikket_web/invite.py:145
msgid "The username is not valid"
msgstr "Der Benutzername ist ungültig"
#: snikket_web/invite.py:158
msgid "The username is not valid."
msgstr "Der Benutzername ist ungültig."
#: snikket_web/invite.py:177 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:32
#: snikket_web/invite.py:190 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr "Passwort ändern"
@@ -149,69 +239,96 @@ msgstr "Adresse"
msgid "Sign in"
msgstr "Anmelden"
#: snikket_web/main.py:72
#: snikket_web/main.py:55
msgid "Invalid username or password."
msgstr "Benutzername oder Passwort falsch."
#: snikket_web/user.py:21
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr "Anmeldung erfolgreich!"
#: snikket_web/user.py:27
msgid "Current password"
msgstr "Aktuelles Passwort"
#: snikket_web/user.py:26
#: snikket_web/user.py:32
msgid "New password"
msgstr "Neues Passwort"
#: snikket_web/user.py:31
#: snikket_web/user.py:37
msgid "Confirm new password"
msgstr "Neues Passwort (Bestätigung)"
#: snikket_web/user.py:35
msgid "The new passwords must match"
msgstr "Die neuen Passwörter müssen übereinstimmen"
#: snikket_web/user.py:41
msgid "The new passwords must match."
msgstr "Die neuen Passwörter müssen übereinstimmen."
#: snikket_web/user.py:42
#: snikket_web/user.py:48
msgid "Sign out"
msgstr "Abmelden"
#: snikket_web/user.py:47
#: snikket_web/user.py:53
msgid "Nobody"
msgstr "Niemand"
#: snikket_web/user.py:48
#: snikket_web/user.py:54
msgid "Friends only"
msgstr "Nur Freunde"
#: snikket_web/user.py:49
#: snikket_web/user.py:55
msgid "Everyone"
msgstr "Jeder"
#: snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:55
msgid "Display name"
msgstr "Anzeigename"
#: snikket_web/user.py:59
#: snikket_web/user.py:65
msgid "Avatar"
msgstr "Bild"
#: snikket_web/user.py:63
#: snikket_web/user.py:69
msgid "Profile visibility"
msgstr "Profilsichtbarkeit"
#: snikket_web/user.py:68
#: snikket_web/user.py:74
msgid "Update profile"
msgstr "Profil bearbeiten"
#: snikket_web/user.py:93
msgid "Incorrect password"
msgstr "Ungültiges Passwort"
#: snikket_web/user.py:99
msgid "Incorrect password."
msgstr "Ungültiges Passwort."
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
#: snikket_web/user.py:103
msgid "Password changed"
msgstr "Passwort geändert"
#: snikket_web/user.py:111
msgid ""
"The chosen avatar is too big. To be able to upload larger avatars, please "
"use the app."
msgstr ""
"Das gewählte Profilbild ist zu groß. Benutze die App um größere Bilder "
"hochladen zu können."
#: snikket_web/user.py:159
msgid "Profile updated"
msgstr "Profil gespeichert"
#: snikket_web/templates/unauth.html:18 snikket_web/user.py:167
msgid "Error"
msgstr "Fehler"
#: snikket_web/templates/_footer.html:4
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr "Ein <a href=\"%(about_url)s\">Snikket</a>-Dienst"
#: snikket_web/templates/about.html:9
#: snikket_web/templates/_footer.html:6
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company."
msgstr ""
"„Snikket“ und das Papageienlogo sind Markenzeichen der Snikket Community "
"Interest Company."
#: snikket_web/templates/about.html:4 snikket_web/templates/about.html:9
msgid "About Snikket"
msgstr "Über Snikket"
@@ -271,10 +388,25 @@ msgstr ""
"\"%(apache20_url)s\">Apache 2.0 Lizenz</a> bereitgestellt werden."
#: snikket_web/templates/about.html:17
msgid "Trademarks"
msgstr "Markenzeichen"
#: snikket_web/templates/about.html:18
#, python-format
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company. For more information about the trademarks, visit the <a href="
"\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
msgstr ""
"„Snikket“ und das Papageienlogo sind Markenzeichen der Snikket Community "
"Interest Company. Mehr Informationen über die Markenzeichen gibt es auf der "
"<a href=\"%(trademarks_url)s\">„Snikket Trademarks“ Informationsseite</a>."
#: snikket_web/templates/about.html:19
msgid "Software Versions"
msgstr "Softwareversionen"
#: snikket_web/templates/about.html:27
#: snikket_web/templates/about.html:29
msgid "Back to the main page"
msgstr "Zurück zur Hauptseite"
@@ -315,7 +447,7 @@ msgstr "Mitglieder"
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:12
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
msgstr "Aktionen"
@@ -353,7 +485,7 @@ msgstr "Neue Gemeinschaft"
#: snikket_web/templates/admin_create_invite.html:3
msgid "Create invitation"
msgstr "Gemeinschaft gründen"
msgstr "Einladung erzeugen"
#: snikket_web/templates/admin_create_invite_form.html:5
msgid "Create new invitation"
@@ -373,7 +505,7 @@ msgid "Debug information for %(user_name)s"
msgstr "Debugging-Informationen für %(user_name)s"
#: snikket_web/templates/admin_debug_user.html:11
#: snikket_web/templates/user_passwd.html:26
#: snikket_web/templates/user_passwd.html:23
msgid "Warning"
msgstr "Warnung"
@@ -390,12 +522,12 @@ msgid "Copy complete output"
msgstr "Komplette Ausgabe kopieren"
#: snikket_web/templates/admin_delete_user.html:4
#: snikket_web/templates/admin_users.html:22
#, python-format
msgid "Delete user %(user_name)s"
msgstr "Benutzer %(user_name)s löschen"
#: snikket_web/templates/admin_delete_user.html:6
#: snikket_web/templates/admin_edit_user.html:54
msgid "Delete user"
msgstr "Benutzer löschen"
@@ -403,11 +535,6 @@ msgstr "Benutzer löschen"
msgid "Are you sure you want to delete the following user?"
msgstr "Bist du sicher dass du den folgenden Benutzer löschen willst?"
#: snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:10
msgid "Login name"
msgstr "Anmeldename"
#: snikket_web/templates/admin_delete_user.html:15
msgid "Danger"
msgstr "Gefahr"
@@ -423,12 +550,10 @@ msgstr ""
"keinen Weg zurück!</strong>"
#: snikket_web/templates/admin_delete_user.html:19
#: snikket_web/templates/admin_edit_circle.html:44
#: snikket_web/templates/admin_edit_invite.html:49
#: snikket_web/templates/admin_reset_user_password.html:25
#: snikket_web/templates/user_logout.html:13
#: snikket_web/templates/user_passwd.html:30
#: snikket_web/templates/user_profile.html:31
#: snikket_web/templates/user_logout.html:10
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_profile.html:32
msgid "Back"
msgstr "Zurück"
@@ -462,6 +587,10 @@ msgstr "Gemeinschaftsinformationen"
msgid "This circle has no group chat associated."
msgstr "Diese Gemeinschaft hat keinen zugehörigen Gruppenchat."
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Return to circle list"
msgstr "Zurück zur Gemeinschaftsliste"
#: snikket_web/templates/admin_edit_circle.html:48
msgid "Delete circle"
msgstr "Gemeinschaft löschen"
@@ -506,7 +635,6 @@ msgid "View invitation"
msgstr "Einladung anzeigen"
#: snikket_web/templates/admin_edit_invite.html:13
#: snikket_web/templates/admin_invites.html:21
#: snikket_web/templates/admin_reset_user_password.html:15
msgid "Valid until"
msgstr "Gültig bis"
@@ -552,6 +680,99 @@ msgstr "Der Benutzer wird als Kontakt von %(peer_jid)s hinzugefügt."
msgid "Created"
msgstr "Erzeugt"
#: snikket_web/templates/admin_edit_invite.html:48
msgid "Return to invitation list"
msgstr "Zurück zur Einladungsliste"
#: snikket_web/templates/admin_edit_user.html:5
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
"Eingeschränkte Benutzer können mit anderen Benutzern auf dem selben Snikket-"
"Server interagieren und Mitglieder in Gemeinschaften sein."
#: snikket_web/templates/admin_edit_user.html:7
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
"Wie eingeschränkte Benutzer, zusätzlich können normale Benutzer auch mit "
"Benutzern auf anderen Snikket-Servern interagieren."
#: snikket_web/templates/admin_edit_user.html:9
msgid "Like normal users and can access the admin panel in the web portal."
msgstr ""
"Wie normale Benutzer, zusätzlich können Administratoren auf den Adminbereich "
"zugreifen."
#: snikket_web/templates/admin_edit_user.html:20
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Edit user %(user_name)s"
msgstr "Benutzer %(user_name)s bearbeiten"
#: snikket_web/templates/admin_edit_user.html:23
msgid "Edit user"
msgstr "Benutzer bearbeiten"
#: snikket_web/templates/admin_edit_user.html:27
msgid "The login name cannot be changed."
msgstr "Der Anmeldename kann nicht geändert werden."
#: snikket_web/templates/admin_edit_user.html:34
msgid ""
"The access level of a user determines what interactions are allowed for them "
"on your Snikket service."
msgstr ""
"Die Berechtigungen von Benutzern bestimmen, welche Interaktionen ihnen auf "
"deinem Snikket-Server erlaubt sind."
#: snikket_web/templates/admin_edit_user.html:41
#, python-format
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
msgstr "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
#: snikket_web/templates/admin_edit_user.html:51
msgid "Return to user list"
msgstr "Zurück zur Benutzerliste"
#: snikket_web/templates/admin_edit_user.html:59
msgid "Further actions"
msgstr "Weitere Aktionen"
#: snikket_web/templates/admin_edit_user.html:61
msgid "Reset password"
msgstr "Passwort zurücksetzen"
#: snikket_web/templates/admin_edit_user.html:64
msgid ""
"If the user has lost their password, you can use the button below to create "
"a special link which allows to change the password of the account, once."
msgstr ""
"Wenn ein Benutzer das Passwort verloren oder vergessen hat, kannst du den "
"folgenden Button verwenden um einen Link zu erzeugen. Dieser Link erlaubt es "
"einmalig, das Passwort des Kontos zu ändern."
#: snikket_web/templates/admin_edit_user.html:69
msgid "Debug information"
msgstr "Informationen zur Fehlerbehebung"
#: snikket_web/templates/admin_edit_user.html:71
msgid ""
"In some cases, extended information about the user account and the connected "
"devices is necessary to troubleshoot issues. The button below reveals this "
"(sensitive) information."
msgstr ""
"In seltenen Fällen kann es vorkommen, dass weiterführende Informationen über "
"das Benutzerkonto sowie die verbundenen Geräte erforderlich sind um ein "
"Problem zu beheben. Der folgende Button zeigt diese (teilweise vertraulichen)"
" Informationen an."
#: snikket_web/templates/admin_edit_user.html:75
msgid "Show debug information"
msgstr "Debugging-Informationen anzeigen"
#: snikket_web/templates/admin_home.html:4
msgid "Welcome to the admin panel!"
msgstr "Willkommen im Adminbereich!"
@@ -604,6 +825,10 @@ msgstr "Adminbereich verlassen"
msgid "Pending invitations"
msgstr "Ausstehende Einladungen"
#: snikket_web/templates/admin_invites.html:21
msgid "Expires"
msgstr "Läuft ab"
#: snikket_web/templates/admin_invites.html:22
msgid "Type"
msgstr "Art"
@@ -645,15 +870,21 @@ msgstr ""
msgid "Destroy link"
msgstr "Link zerstören"
#: snikket_web/templates/admin_users.html:25
#, python-format
msgid "Show debug information for %(user_name)s"
msgstr "Debugging-Informationen für %(user_name)s anzeigen"
#: snikket_web/templates/admin_users.html:19
msgid "The user is an administrator."
msgstr "Der Benutzer ist ein Administrator."
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Create password reset link for %(user_name)s"
msgstr "Benutzer %(user_name)s löschen"
#: snikket_web/templates/admin_users.html:19
msgid " (Administrator)"
msgstr " (Administrator)"
#: snikket_web/templates/admin_users.html:22
msgid "The user is restricted."
msgstr "Dieser Benutzer ist eingeschränkt."
#: snikket_web/templates/admin_users.html:22
msgid " (Restricted)"
msgstr " (Eingeschränkt)"
#: snikket_web/templates/app.html:4
msgid "Snikket Web Portal"
@@ -689,7 +920,7 @@ msgid "The web portal encountered an internal error."
msgstr "Das Webportal hatte einen internen Fehler."
#: snikket_web/templates/invite_invalid.html:5
#: snikket_web/templates/invite_view.html:12
#: snikket_web/templates/invite_view.html:13
#, python-format
msgid "Invite to %(site_name)s"
msgstr "Einladung zu %(site_name)s"
@@ -697,7 +928,7 @@ msgstr "Einladung zu %(site_name)s"
#: snikket_web/templates/invite_invalid.html:6
#: snikket_web/templates/invite_register.html:10
#: snikket_web/templates/invite_success.html:11
#: snikket_web/templates/invite_view.html:13
#: snikket_web/templates/invite_view.html:14
#, python-format
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
msgstr "Betrieben mit <img alt=\"Snikket\" src=\"%(logo_url)s\">"
@@ -744,18 +975,20 @@ msgstr ""
"innerhalb der App zu machen. Das geht, indem du den folgenden Knopf drückst:"
#: snikket_web/templates/invite_register.html:14
#: snikket_web/templates/invite_view.html:37
#: snikket_web/templates/invite_view.html:39
msgid "App already installed?"
msgstr "App schon installiert?"
#: snikket_web/templates/invite_register.html:16
#: snikket_web/templates/invite_reset_view.html:21
#: snikket_web/templates/invite_view.html:39
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:106
#: snikket_web/templates/invite_view.html:134
msgid "Open the app"
msgstr "App öffnen"
#: snikket_web/templates/invite_register.html:18
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:43
msgid "This button works only if you have the app installed already!"
msgstr "Dieser Knopf funktioniert nur, wenn du die App schon installiert hast!"
@@ -831,8 +1064,8 @@ msgid ""
"This page allows you to reset the password of your account, <strong>"
"%(account_jid)s</strong>, once."
msgstr ""
"Diese Seite erlaubt es dir einmalig, das Passwort deines Kontos "
"<strong>%(account_jid)s</strong> zurückzusetzen."
"Diese Seite erlaubt es dir einmalig, das Passwort deines Kontos <strong>"
"%(account_jid)s</strong> zurückzusetzen."
#: snikket_web/templates/invite_reset_view.html:17
msgid "Using the app"
@@ -853,7 +1086,7 @@ msgstr ""
"Das geht mit Hilfe des Scan-Knopfes in der oberen Leiste."
#: snikket_web/templates/invite_reset_view.html:26
#: snikket_web/templates/invite_view.html:75
#: snikket_web/templates/invite_view.html:77
msgid ""
"Your camera will turn on. Point it at the square code below until it is "
"within the highlighted square on your screen, and wait until the app "
@@ -869,7 +1102,7 @@ msgstr ""
"Du wirst dann aufgefordert, ein neues Passwort für deinen Account einzugeben."
#: snikket_web/templates/invite_reset_view.html:29
#: snikket_web/templates/invite_view.html:43
#: snikket_web/templates/invite_view.html:45
msgid "Alternatives"
msgstr "Alternativen"
@@ -917,7 +1150,7 @@ msgstr ""
msgid "Invite to %(site_name)s | Snikket"
msgstr "Einladung zu %(site_name)s | Snikket"
#: snikket_web/templates/invite_view.html:15
#: snikket_web/templates/invite_view.html:16
#, python-format
msgid ""
"You have been invited to chat with %(inviter_name)s using Snikket, a secure, "
@@ -926,7 +1159,7 @@ msgstr ""
"%(inviter_name)s hat dich zum Chatten mit Snikket auf %(site_name)s "
"eingeladen, einer sicheren, privatsphärefreundlichen Chat-App."
#: snikket_web/templates/invite_view.html:17
#: snikket_web/templates/invite_view.html:18
#, python-format
msgid ""
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
@@ -935,37 +1168,42 @@ msgstr ""
"Dies ist eine Einladung zum Chatten auf %(site_name)s mit Snikket, einer "
"sicheren, privatsphärefreundlichen Chat-App."
#: snikket_web/templates/invite_view.html:19
#: snikket_web/templates/invite_view.html:20
msgid "Get started"
msgstr "Leg los!"
#: snikket_web/templates/invite_view.html:21
#: snikket_web/templates/invite_view.html:22
msgid "Install the Snikket App on your Android or iOS device."
msgstr "Installiere die Snikket-App auf deinem Android- oder iOS-Gerät."
#: snikket_web/templates/invite_view.html:23
#: snikket_web/templates/invite_view.html:24
#, python-format
msgid ""
"Install the Snikket App on your Android device (<a href=\"%(ios_info_url)s\" "
"rel=\"noopener noreferrer\" target=\"_blank\">iOS coming soon!</a>)."
msgstr ""
"Installiere die Snikket-App auf deinem Android-Gerät (<a href=\""
"%(ios_info_url)s\" rel=\"noopener noreferrer\" target=\"_blank\">iOS folgt "
"Installiere die Snikket-App auf deinem Android-Gerät (<a href="
"\"%(ios_info_url)s\" rel=\"noopener noreferrer\" target=\"_blank\">iOS folgt "
"bald!</a>)."
#: snikket_web/templates/invite_view.html:27
#: snikket_web/templates/invite_view.html:28
msgid "Get it on Google Play"
msgstr "Von Google Play installieren"
#: snikket_web/templates/invite_view.html:29
#: snikket_web/templates/invite_view.html:30
#: snikket_web/templates/invite_view.html:102
msgid "Download on the App Store"
msgstr "Laden im App Store"
#: snikket_web/templates/invite_view.html:33
msgid "Not on mobile?"
msgstr "Nicht am Smartphone?"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr "Hole die App von F-Droid"
#: snikket_web/templates/invite_view.html:36
#: snikket_web/templates/invite_view.html:35
msgid "Send to mobile device"
msgstr "An mobiles Gerät übertragen"
#: snikket_web/templates/invite_view.html:38
msgid ""
"After installation the app should automatically open and prompt you to "
"create an account. If not, simply click the button below."
@@ -974,7 +1212,7 @@ msgstr ""
"ein Benutzerkonto anzulegen. Falls nicht, tippe einfach auf die folgende "
"Schaltfläche."
#: snikket_web/templates/invite_view.html:44
#: snikket_web/templates/invite_view.html:46
#, python-format
msgid ""
"You can connect to Snikket using any XMPP-compatible software. If the button "
@@ -986,16 +1224,20 @@ msgstr ""
"dass du <a href=\"%(register_url)s\">manuell ein Benutzerkonto anlegen</a> "
"musst."
#: snikket_web/templates/invite_view.html:50
#: snikket_web/templates/invite_view.html:52
msgid "Scan invite code"
msgstr "Einladungscode scannen"
#: snikket_web/templates/invite_view.html:53
#: snikket_web/templates/invite_view.html:82
#: snikket_web/templates/invite_view.html:55
#: snikket_web/templates/invite_view.html:84
#: snikket_web/templates/invite_view.html:96
#: snikket_web/templates/invite_view.html:112
#: snikket_web/templates/invite_view.html:124
#: snikket_web/templates/invite_view.html:140
msgid "Close"
msgstr "Schließen"
#: snikket_web/templates/invite_view.html:56
#: snikket_web/templates/invite_view.html:58
msgid ""
"You can transfer this invite to your mobile device by scanning a code with "
"your camera. You can use either a QR scanner app or the Snikket app itself."
@@ -1004,22 +1246,22 @@ msgstr ""
"untenstehenden Code mit deiner Kamera scannst. Dafür kannst du entweder "
"einen normalen QR-Scanner nehmen oder die Snikket-App selbst."
#: snikket_web/templates/invite_view.html:61
#: snikket_web/templates/invite_view.html:63
msgid "Using a QR code scanner"
msgstr "Mit einem QR-Code-Scanner"
#: snikket_web/templates/invite_view.html:63
#: snikket_web/templates/invite_view.html:65
msgid "Using the Snikket app"
msgstr "Mit der Snikket-App"
#: snikket_web/templates/invite_view.html:68
#: snikket_web/templates/invite_view.html:70
msgid ""
"Use a <em>QR code</em> scanner on your mobile device to scan the code below:"
msgstr ""
"Benutze einen <em>QR-Code</em>-Scanner auf deinem mobilen Gerät um den "
"untenstehenden Code zu scannen:"
#: snikket_web/templates/invite_view.html:74
#: snikket_web/templates/invite_view.html:76
msgid ""
"Install the Snikket app on your mobile device, open it, and tap the 'Scan' "
"button at the top."
@@ -1027,6 +1269,51 @@ msgstr ""
"Installiere die Snikket-App auf deinem mobilen Gerät, öffne sie und tippe "
"dann auf den 'Scan'-Knopf in der oberen Leiste."
#: snikket_web/templates/invite_view.html:93
msgid "Install on iOS"
msgstr "Installation auf iOS"
#: snikket_web/templates/invite_view.html:99
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 ""
"Nachdem du Snikket vom App Store heruntergeladen hast musst du zu diesem "
"Einladungslink zurückkehren und \"App öffnen\" antippen um fortzufahren."
#: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:"
msgstr ""
"Lade zunächst Snikket aus dem App Store herunter indem du den folgenden "
"Button benutzt:"
#: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131
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 ""
"Nachdem die Installation abgeschlossen ist kannst du zu dieser Seite "
"zurückkehren und unten auf \"App öffnen\" tippen um die Einrichtung "
"abzuschließen:"
#: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130
msgid "Install via F-Droid"
msgstr "Installation über F-Droid"
#: snikket_web/templates/invite_view.html:127
msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed."
msgstr ""
"Nachdem du Snikket über F-Droid installiert hast, musst du auf diese Seite "
"zurückkehren und \"App öffnen\" antippen um fortzufahren."
#: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:"
msgstr "Installiere Snikket zunächst aus F-Droid mit dem folgenden Button:"
#: snikket_web/templates/library.j2:18
msgid "Copy link"
msgstr "Link kopieren"
@@ -1060,9 +1347,22 @@ msgid "Enter your Snikket address and password to manage your account."
msgstr ""
"Gib deine Snikket-Adresse und -Passwort ein um dein Konto zu verwalten."
#: snikket_web/templates/login.html:18
msgid "Login failed"
msgstr "Anmeldung fehlgeschlagen"
#: snikket_web/templates/login.html:19
msgid "Incorrect address"
msgstr "Ungültige Adresse"
#: snikket_web/templates/login.html:20
#, python-format
msgid ""
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
"em>. Your password was not sent."
msgstr ""
"Dieser Snikket-Server ist nur für Adressen, die auf <em>@%(snikket_domain)s</"
"em> enden zuständig. Dein Passwort wurde nicht abgesendet."
#: snikket_web/templates/unauth.html:16
msgid "Operation successful"
msgstr "Aktion erfolgreich"
#: snikket_web/templates/user_home.html:9
msgid "Welcome!"
@@ -1098,11 +1398,11 @@ msgstr ""
msgid "Admin panel"
msgstr "Adminbereich"
#: snikket_web/templates/user_logout.html:8
#: snikket_web/templates/user_logout.html:5
msgid "Sign out of the Snikket Web Portal"
msgstr "Aus dem Webportal abmelden"
#: snikket_web/templates/user_logout.html:9
#: snikket_web/templates/user_logout.html:6
msgid ""
"Click below to log yourself out of the web portal. This does not affect any "
"other connected devices."
@@ -1110,11 +1410,11 @@ msgstr ""
"Klicke unten um dich aus dem Webportal abzumelden. Dies betrifft keine "
"anderen Geräte von dir."
#: snikket_web/templates/user_passwd.html:8
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"
msgstr "Ändere dein Passwort"
#: snikket_web/templates/user_passwd.html:9
#: snikket_web/templates/user_passwd.html:6
msgid ""
"To change your password, you need to provide the current password as well as "
"the new one. To reduce the chance of typos, we ask for your new password "
@@ -1124,7 +1424,7 @@ msgstr ""
"dein neues Passwort angeben. Damit Tippfehler dich nicht aussperren bitten "
"wir dich, dein neues Passwort zweimal einzutippen."
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_passwd.html:24
msgid ""
"After changing your password, you will have to enter the new password on all "
"of your devices."
@@ -1132,19 +1432,19 @@ msgstr ""
"Nachdem du das Passwort geändert hast, musst du das neue Passwort auf allen "
"Geräten manuell eintragen."
#: snikket_web/templates/user_profile.html:7
#: snikket_web/templates/user_profile.html:4
msgid "Update your profile"
msgstr "Dein Profil bearbeiten"
#: snikket_web/templates/user_profile.html:9
#: snikket_web/templates/user_profile.html:6
msgid "Profile"
msgstr "Profil"
#: snikket_web/templates/user_profile.html:22
#: snikket_web/templates/user_profile.html:23
msgid "Visibility"
msgstr "Sichtbarkeit"
#: snikket_web/templates/user_profile.html:23
#: snikket_web/templates/user_profile.html:24
msgid ""
"This section allows you to control who can see your profile information, "
"like avatar and nickname."
@@ -1152,23 +1452,14 @@ msgstr ""
"Hier kannst du einstellen, wer deine Profilinformationen, wie Bild oder "
"Anzeigename einsehen kann."
#~ msgid "Edit user %(user_name)s"
#~ msgstr "Benutzer %(user_name)s bearbeiten"
#~ msgid "Create password reset link for %(user_name)s"
#~ msgstr "Link zum Zurücksetzen des Passwortes von %(user_name)s erzeugen"
#~ msgid "User information"
#~ msgstr "Benutzerinformationen"
#~ msgid "Login failed"
#~ msgstr "Anmeldung fehlgeschlagen"
#~ msgid ""
#~ "If the user has forgotten their password, use the below button to create "
#~ "a password reset link. The password reset link can be used once to change "
#~ "the password of the account. Transmit the link to the user via a secure "
#~ "channel."
#~ msgstr ""
#~ "Wenn ein Benutzer das Passwort vergessen hat, kannst du den folgenden "
#~ "Button benutzen um einen Link zum Zurücksetzen des Passworts zu erzeugen. "
#~ "Dieser Link kann dann ein einziges Mal verwendet werden um das Passwort "
#~ "des Kontos zu ändern. Lasse den Link dem Benutzer auf einem sicheren "
#~ "Übertragungsweg zukommen."
#~ msgid "Not on mobile?"
#~ msgstr "Nicht am Smartphone?"
#~ msgid "No users left"
#~ msgstr "Keine Benutzer übrig"
@@ -1235,8 +1526,5 @@ msgstr ""
#~ msgid "Copy operation failed"
#~ msgstr "Kopieren fehlgeschlagen"
#~ msgid "Password change failed"
#~ msgstr "Passwortänderung fehlgeschlagen"
#~ msgid "Allow multiple uses"
#~ msgstr "Mehrfach verwendbar"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-30 12:45+0100\n"
"POT-Creation-Date: 2021-03-25 17:32+0100\n"
"PO-Revision-Date: 2021-02-02 21:01+0000\n"
"Last-Translator: riccio <unriccio@email.it>\n"
"Language-Team: English (United Kingdom) <https://i18n.sotecware.net/projects/"
@@ -20,130 +20,256 @@ msgstr ""
"X-Generator: Weblate 4.4.2\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:60
#: snikket_web/admin.py:59
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:64 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr "Login name"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:61
msgid "Display name"
msgstr "Display name"
#: snikket_web/admin.py:72 snikket_web/templates/admin_edit_user.html:33
msgid "Access Level"
msgstr ""
#: snikket_web/admin.py:77
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:78
#, fuzzy
#| msgid "Admin dashboard"
msgid "Administrator"
msgstr "Admin dashboard"
#: snikket_web/admin.py:83
#, fuzzy
#| msgid "Create circle"
msgid "Update user"
msgstr "Create circle"
#: snikket_web/admin.py:87
#, fuzzy
#| msgid "Create password reset links or delete users."
msgid "Create password reset link"
msgstr "Create password reset links or delete users."
#: snikket_web/admin.py:105
#, fuzzy
#| msgid "Password reset link for %(user_name)s"
msgid "Password reset link created"
msgstr "Password reset link for %(user_name)s"
#: snikket_web/admin.py:120
#, fuzzy
#| msgid "User information"
msgid "User information updated."
msgstr "User information"
#: snikket_web/admin.py:142
msgid "Delete user permanently"
msgstr "Delete user permanently"
#: snikket_web/admin.py:129
#: snikket_web/admin.py:155
#, fuzzy
#| msgid "deleted"
msgid "User deleted"
msgstr "deleted"
#: snikket_web/admin.py:193
#, fuzzy
#| msgid "Password reset link for %(user_name)s"
msgid "Password reset link not found"
msgstr "Password reset link for %(user_name)s"
#: snikket_web/admin.py:205
#, fuzzy
#| msgid "Create password reset links or delete users."
msgid "Password reset link deleted"
msgstr "Create password reset links or delete users."
#: snikket_web/admin.py:225
msgid "Invite to circle"
msgstr "Invite to circle"
#: snikket_web/admin.py:135
#: snikket_web/admin.py:231
msgid "At least one circle must be selected"
msgstr "At least one circle must be selected"
#: snikket_web/admin.py:140
#: snikket_web/admin.py:236
msgid "Valid for"
msgstr "Valid for"
#: snikket_web/admin.py:142
#: snikket_web/admin.py:238
msgid "One hour"
msgstr "One hour"
#: snikket_web/admin.py:143
#: snikket_web/admin.py:239
msgid "Twelve hours"
msgstr "Twelve hours"
#: snikket_web/admin.py:144
#: snikket_web/admin.py:240
msgid "One day"
msgstr "One day"
#: snikket_web/admin.py:145
#: snikket_web/admin.py:241
msgid "One week"
msgstr "One week"
#: snikket_web/admin.py:146
#: snikket_web/admin.py:242
msgid "Four weeks"
msgstr "Four weeks"
#: snikket_web/admin.py:152 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:248 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr "Invitation type"
#: snikket_web/admin.py:154 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:250 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr ""
#: snikket_web/admin.py:155 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:251 snikket_web/templates/library.j2:114
msgid "Group"
msgstr ""
#: snikket_web/admin.py:161
#: snikket_web/admin.py:257
msgid "New invitation link"
msgstr "New invitation link"
#: snikket_web/admin.py:223
#: snikket_web/admin.py:319
msgid "Revoke"
msgstr "Revoke"
#: snikket_web/admin.py:283 snikket_web/admin.py:327
#: snikket_web/admin.py:343
#, fuzzy
#| msgid "Invitation type"
msgid "Invitation created"
msgstr "Invitation type"
#: snikket_web/admin.py:359
#, fuzzy
#| msgid "New invitation link"
msgid "No such invitation exists"
msgstr "New invitation link"
#: snikket_web/admin.py:374
#, fuzzy
#| msgid "Invitation type"
msgid "Invitation revoked"
msgstr "Invitation type"
#: snikket_web/admin.py:391 snikket_web/admin.py:439
msgid "Name"
msgstr "Name"
#: snikket_web/admin.py:288 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:396 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr "Create circle"
#: snikket_web/admin.py:332
#: snikket_web/admin.py:426
#, fuzzy
#| msgid "Circle name"
msgid "Circle created"
msgstr "Circle name"
#: snikket_web/admin.py:444
msgid "Select user"
msgstr "Select user"
#: snikket_web/admin.py:337
#: snikket_web/admin.py:449
#, fuzzy
#| msgid "Create circle"
msgid "Update circle"
msgstr "Create circle"
#: snikket_web/admin.py:341
#: snikket_web/admin.py:453
msgid "Delete circle permanently"
msgstr "Delete circle permanently"
#: snikket_web/admin.py:347
#: snikket_web/admin.py:459
msgid "Add user"
msgstr "Add user"
#: snikket_web/infra.py:40
#: snikket_web/admin.py:475
#, fuzzy
#| msgid "No circles"
msgid "No such circle exists"
msgstr "No circles"
#: snikket_web/admin.py:512
#, fuzzy
#| msgid "Circle name"
msgid "Circle data updated"
msgstr "Circle name"
#: snikket_web/admin.py:518
#, fuzzy
#| msgid "deleted"
msgid "Circle deleted"
msgstr "deleted"
#: snikket_web/admin.py:529
#, fuzzy
#| msgid "Invite to circle"
msgid "User added to circle"
msgstr "Invite to circle"
#: snikket_web/admin.py:538
#, fuzzy
#| msgid "Remove user %(username)s from circle"
msgid "User removed from circle"
msgstr "Remove user %(username)s from circle"
#: snikket_web/infra.py:41
msgid "Main"
msgstr "Main"
#: snikket_web/invite.py:93
#: snikket_web/invite.py:106
msgid "Username"
msgstr ""
#: snikket_web/invite.py:97 snikket_web/invite.py:164 snikket_web/main.py:41
#: snikket_web/invite.py:110 snikket_web/invite.py:177 snikket_web/main.py:41
msgid "Password"
msgstr "Password"
#: snikket_web/invite.py:101 snikket_web/invite.py:168
#: snikket_web/invite.py:114 snikket_web/invite.py:181
#, fuzzy
#| msgid "Confirm new password"
msgid "Confirm password"
msgstr "Confirm new password"
#: snikket_web/invite.py:105 snikket_web/invite.py:172
msgid "The passwords must match"
#: snikket_web/invite.py:118 snikket_web/invite.py:185
#, fuzzy
#| msgid "The passwords must match"
msgid "The passwords must match."
msgstr "The passwords must match"
#: snikket_web/invite.py:110
#: snikket_web/invite.py:123
#, fuzzy
#| msgid "Create circle"
msgid "Create account"
msgstr "Create circle"
#: snikket_web/invite.py:137
msgid "That username is already taken"
#: snikket_web/invite.py:150
msgid "That username is already taken."
msgstr ""
#: snikket_web/invite.py:141 snikket_web/invite.py:205
msgid "Registration was declined for unknown reasons"
#: snikket_web/invite.py:154 snikket_web/invite.py:218
msgid "Registration was declined for unknown reasons."
msgstr ""
#: snikket_web/invite.py:145
msgid "The username is not valid"
#: snikket_web/invite.py:158
msgid "The username is not valid."
msgstr ""
#: snikket_web/invite.py:177 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:32
#: snikket_web/invite.py:190 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr "Change password"
@@ -155,71 +281,102 @@ msgstr "Address"
msgid "Sign in"
msgstr "Sign in"
#: snikket_web/main.py:72
#: snikket_web/main.py:55
#, fuzzy
#| msgid "Invalid user name or password."
msgid "Invalid username or password."
msgstr "Invalid user name or password."
#: snikket_web/user.py:21
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr ""
#: snikket_web/user.py:27
msgid "Current password"
msgstr "Current password"
#: snikket_web/user.py:26
#: snikket_web/user.py:32
msgid "New password"
msgstr "New password"
#: snikket_web/user.py:31
#: snikket_web/user.py:37
msgid "Confirm new password"
msgstr "Confirm new password"
#: snikket_web/user.py:35
msgid "The new passwords must match"
#: snikket_web/user.py:41
#, fuzzy
#| msgid "The new passwords must match"
msgid "The new passwords must match."
msgstr "The new passwords must match"
#: snikket_web/user.py:42
#: snikket_web/user.py:48
msgid "Sign out"
msgstr "Sign out"
#: snikket_web/user.py:47
#: snikket_web/user.py:53
msgid "Nobody"
msgstr "Nobody"
#: snikket_web/user.py:48
#: snikket_web/user.py:54
msgid "Friends only"
msgstr "Friends only"
#: snikket_web/user.py:49
#: snikket_web/user.py:55
msgid "Everyone"
msgstr "Everyone"
#: snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:55
msgid "Display name"
msgstr "Display name"
#: snikket_web/user.py:59
#: snikket_web/user.py:65
msgid "Avatar"
msgstr "Avatar"
#: snikket_web/user.py:63
#: snikket_web/user.py:69
msgid "Profile visibility"
msgstr "Profile visibility"
#: snikket_web/user.py:68
#: snikket_web/user.py:74
msgid "Update profile"
msgstr "Update profile"
#: snikket_web/user.py:93
msgid "Incorrect password"
#: snikket_web/user.py:99
#, fuzzy
#| msgid "Incorrect password"
msgid "Incorrect password."
msgstr "Incorrect password"
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
#: snikket_web/user.py:103
#, fuzzy
#| msgid "Password change failed"
msgid "Password changed"
msgstr "Password change failed"
#: snikket_web/user.py:111
msgid ""
"The chosen avatar is too big. To be able to upload larger avatars, please "
"use the app."
msgstr ""
#: snikket_web/user.py:159
#, fuzzy
#| msgid "Profile"
msgid "Profile updated"
msgstr "Profile"
#: snikket_web/templates/unauth.html:18 snikket_web/user.py:167
msgid "Error"
msgstr ""
#: snikket_web/templates/_footer.html:4
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr "A <a href=\"%(about_url)s\">Snikket</a> service"
#: snikket_web/templates/about.html:9
#: snikket_web/templates/_footer.html:6
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company."
msgstr ""
#: snikket_web/templates/about.html:4 snikket_web/templates/about.html:9
msgid "About Snikket"
msgstr "About Snikket"
@@ -279,10 +436,22 @@ msgstr ""
"\"%(apache20_url)s\">Apache 2.0 License</a>."
#: snikket_web/templates/about.html:17
msgid "Trademarks"
msgstr ""
#: snikket_web/templates/about.html:18
#, python-format
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company. For more information about the trademarks, visit the <a href="
"\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
msgstr ""
#: snikket_web/templates/about.html:19
msgid "Software Versions"
msgstr "Software Versions"
#: snikket_web/templates/about.html:27
#: snikket_web/templates/about.html:29
msgid "Back to the main page"
msgstr "Back to the main page"
@@ -320,7 +489,7 @@ msgstr "Members"
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:12
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
msgstr "Actions"
@@ -379,7 +548,7 @@ msgid "Debug information for %(user_name)s"
msgstr "Debug information for %(user_name)s"
#: snikket_web/templates/admin_debug_user.html:11
#: snikket_web/templates/user_passwd.html:26
#: snikket_web/templates/user_passwd.html:23
msgid "Warning"
msgstr "Warning"
@@ -396,12 +565,12 @@ msgid "Copy complete output"
msgstr "Copy complete output"
#: snikket_web/templates/admin_delete_user.html:4
#: snikket_web/templates/admin_users.html:22
#, python-format
msgid "Delete user %(user_name)s"
msgstr "Delete user %(user_name)s"
#: snikket_web/templates/admin_delete_user.html:6
#: snikket_web/templates/admin_edit_user.html:54
msgid "Delete user"
msgstr "Delete user"
@@ -409,11 +578,6 @@ msgstr "Delete user"
msgid "Are you sure you want to delete the following user?"
msgstr "Are you sure you want to delete the following user?"
#: snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:10
msgid "Login name"
msgstr "Login name"
#: snikket_web/templates/admin_delete_user.html:15
msgid "Danger"
msgstr "Danger"
@@ -426,12 +590,10 @@ msgid ""
msgstr ""
#: snikket_web/templates/admin_delete_user.html:19
#: snikket_web/templates/admin_edit_circle.html:44
#: snikket_web/templates/admin_edit_invite.html:49
#: snikket_web/templates/admin_reset_user_password.html:25
#: snikket_web/templates/user_logout.html:13
#: snikket_web/templates/user_passwd.html:30
#: snikket_web/templates/user_profile.html:31
#: snikket_web/templates/user_logout.html:10
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_profile.html:32
msgid "Back"
msgstr "Back"
@@ -467,6 +629,10 @@ msgstr "Circle information"
msgid "This circle has no group chat associated."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Return to circle list"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:48
msgid "Delete circle"
msgstr "Delete circle"
@@ -509,7 +675,6 @@ msgid "View invitation"
msgstr "View invitation"
#: snikket_web/templates/admin_edit_invite.html:13
#: snikket_web/templates/admin_invites.html:21
#: snikket_web/templates/admin_reset_user_password.html:15
msgid "Valid until"
msgstr "Valid until"
@@ -552,6 +717,105 @@ msgstr ""
msgid "Created"
msgstr "Created"
#: snikket_web/templates/admin_edit_invite.html:48
#, fuzzy
#| msgid "New invitation link"
msgid "Return to invitation list"
msgstr "New invitation link"
#: snikket_web/templates/admin_edit_user.html:5
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:7
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:9
msgid "Like normal users and can access the admin panel in the web portal."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:20
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Edit user %(user_name)s"
msgstr "Edit user %(user_name)s"
#: snikket_web/templates/admin_edit_user.html:23
#, fuzzy
#| msgid "Add user"
msgid "Edit user"
msgstr "Add user"
#: snikket_web/templates/admin_edit_user.html:27
msgid "The login name cannot be changed."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:34
msgid ""
"The access level of a user determines what interactions are allowed for them "
"on your Snikket service."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:41
#, python-format
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:51
#, fuzzy
#| msgid "New invitation link"
msgid "Return to user list"
msgstr "New invitation link"
#: snikket_web/templates/admin_edit_user.html:59
msgid "Further actions"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:61
#, fuzzy
#| msgid "Change your password"
msgid "Reset password"
msgstr "Change your password"
#: snikket_web/templates/admin_edit_user.html:64
#, fuzzy
#| msgid ""
#| "If the user has forgotten their password, use the below button to create "
#| "a password reset link. The password reset link can be used once to change "
#| "the password of the account. Transmit the link to the user via a secure "
#| "channel."
msgid ""
"If the user has lost their password, you can use the button below to create "
"a special link which allows to change the password of the account, once."
msgstr ""
"If the user has forgotten their password, use the below button to create a "
"password reset link. The password reset link can be used once to change the "
"password of the account. Transmit the link to the user via a secure channel."
#: snikket_web/templates/admin_edit_user.html:69
#, fuzzy
#| msgid "User information"
msgid "Debug information"
msgstr "User information"
#: snikket_web/templates/admin_edit_user.html:71
msgid ""
"In some cases, extended information about the user account and the connected "
"devices is necessary to troubleshoot issues. The button below reveals this "
"(sensitive) information."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:75
#, fuzzy
#| msgid "Show debug information for %(user_name)s"
msgid "Show debug information"
msgstr "Show debug information for %(user_name)s"
#: snikket_web/templates/admin_home.html:4
#, fuzzy
#| msgid "Welcome to the administration dashboard!"
@@ -611,6 +875,10 @@ msgstr ""
msgid "Pending invitations"
msgstr "Pending invitations"
#: snikket_web/templates/admin_invites.html:21
msgid "Expires"
msgstr ""
#: snikket_web/templates/admin_invites.html:22
msgid "Type"
msgstr ""
@@ -652,15 +920,21 @@ msgstr ""
msgid "Destroy link"
msgstr "Destroy link"
#: snikket_web/templates/admin_users.html:25
#, python-format
msgid "Show debug information for %(user_name)s"
msgstr "Show debug information for %(user_name)s"
#: snikket_web/templates/admin_users.html:19
msgid "The user is an administrator."
msgstr ""
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Create password reset link for %(user_name)s"
msgstr "Create password reset link for %(user_name)s"
#: snikket_web/templates/admin_users.html:19
msgid " (Administrator)"
msgstr ""
#: snikket_web/templates/admin_users.html:22
msgid "The user is restricted."
msgstr ""
#: snikket_web/templates/admin_users.html:22
msgid " (Restricted)"
msgstr ""
#: snikket_web/templates/app.html:4
msgid "Snikket Web Portal"
@@ -694,7 +968,7 @@ msgid "The web portal encountered an internal error."
msgstr "The web portal encountered an internal error."
#: snikket_web/templates/invite_invalid.html:5
#: snikket_web/templates/invite_view.html:12
#: snikket_web/templates/invite_view.html:13
#, python-format
msgid "Invite to %(site_name)s"
msgstr "Invite to %(site_name)s"
@@ -702,7 +976,7 @@ msgstr "Invite to %(site_name)s"
#: snikket_web/templates/invite_invalid.html:6
#: snikket_web/templates/invite_register.html:10
#: snikket_web/templates/invite_success.html:11
#: snikket_web/templates/invite_view.html:13
#: snikket_web/templates/invite_view.html:14
#, python-format
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
msgstr ""
@@ -747,18 +1021,20 @@ msgid ""
msgstr ""
#: snikket_web/templates/invite_register.html:14
#: snikket_web/templates/invite_view.html:37
#: snikket_web/templates/invite_view.html:39
msgid "App already installed?"
msgstr ""
#: snikket_web/templates/invite_register.html:16
#: snikket_web/templates/invite_reset_view.html:21
#: snikket_web/templates/invite_view.html:39
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:106
#: snikket_web/templates/invite_view.html:134
msgid "Open the app"
msgstr ""
#: snikket_web/templates/invite_register.html:18
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:43
msgid "This button works only if you have the app installed already!"
msgstr ""
@@ -851,7 +1127,7 @@ msgid ""
msgstr ""
#: snikket_web/templates/invite_reset_view.html:26
#: snikket_web/templates/invite_view.html:75
#: snikket_web/templates/invite_view.html:77
msgid ""
"Your camera will turn on. Point it at the square code below until it is "
"within the highlighted square on your screen, and wait until the app "
@@ -863,7 +1139,7 @@ msgid "You will then be prompted to enter a new password for your account."
msgstr ""
#: snikket_web/templates/invite_reset_view.html:29
#: snikket_web/templates/invite_view.html:43
#: snikket_web/templates/invite_view.html:45
msgid "Alternatives"
msgstr ""
@@ -907,54 +1183,59 @@ msgstr ""
msgid "Invite to %(site_name)s | Snikket"
msgstr ""
#: snikket_web/templates/invite_view.html:15
#: snikket_web/templates/invite_view.html:16
#, python-format
msgid ""
"You have been invited to chat with %(inviter_name)s using Snikket, a secure, "
"privacy-friendly chat app on %(site_name)s."
msgstr ""
#: snikket_web/templates/invite_view.html:17
#: snikket_web/templates/invite_view.html:18
#, python-format
msgid ""
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
"privacy-friendly chat app."
msgstr ""
#: snikket_web/templates/invite_view.html:19
#: snikket_web/templates/invite_view.html:20
msgid "Get started"
msgstr ""
#: snikket_web/templates/invite_view.html:21
#: snikket_web/templates/invite_view.html:22
msgid "Install the Snikket App on your Android or iOS device."
msgstr ""
#: snikket_web/templates/invite_view.html:23
#: snikket_web/templates/invite_view.html:24
#, python-format
msgid ""
"Install the Snikket App on your Android device (<a href=\"%(ios_info_url)s\" "
"rel=\"noopener noreferrer\" target=\"_blank\">iOS coming soon!</a>)."
msgstr ""
#: snikket_web/templates/invite_view.html:27
#: snikket_web/templates/invite_view.html:28
msgid "Get it on Google Play"
msgstr ""
#: snikket_web/templates/invite_view.html:29
#: snikket_web/templates/invite_view.html:30
#: snikket_web/templates/invite_view.html:102
msgid "Download on the App Store"
msgstr ""
#: snikket_web/templates/invite_view.html:33
msgid "Not on mobile?"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:36
#: snikket_web/templates/invite_view.html:35
msgid "Send to mobile device"
msgstr ""
#: snikket_web/templates/invite_view.html:38
msgid ""
"After installation the app should automatically open and prompt you to "
"create an account. If not, simply click the button below."
msgstr ""
#: snikket_web/templates/invite_view.html:44
#: snikket_web/templates/invite_view.html:46
#, python-format
msgid ""
"You can connect to Snikket using any XMPP-compatible software. If the button "
@@ -962,42 +1243,82 @@ msgid ""
"\">register an account manually</a>."
msgstr ""
#: snikket_web/templates/invite_view.html:50
#: snikket_web/templates/invite_view.html:52
#, fuzzy
#| msgid "Show invite details"
msgid "Scan invite code"
msgstr "Show invite details"
#: snikket_web/templates/invite_view.html:53
#: snikket_web/templates/invite_view.html:82
#: snikket_web/templates/invite_view.html:55
#: snikket_web/templates/invite_view.html:84
#: snikket_web/templates/invite_view.html:96
#: snikket_web/templates/invite_view.html:112
#: snikket_web/templates/invite_view.html:124
#: snikket_web/templates/invite_view.html:140
msgid "Close"
msgstr ""
#: snikket_web/templates/invite_view.html:56
#: snikket_web/templates/invite_view.html:58
msgid ""
"You can transfer this invite to your mobile device by scanning a code with "
"your camera. You can use either a QR scanner app or the Snikket app itself."
msgstr ""
#: snikket_web/templates/invite_view.html:61
#: snikket_web/templates/invite_view.html:63
msgid "Using a QR code scanner"
msgstr ""
#: snikket_web/templates/invite_view.html:63
#: snikket_web/templates/invite_view.html:65
msgid "Using the Snikket app"
msgstr ""
#: snikket_web/templates/invite_view.html:68
#: snikket_web/templates/invite_view.html:70
msgid ""
"Use a <em>QR code</em> scanner on your mobile device to scan the code below:"
msgstr ""
#: snikket_web/templates/invite_view.html:74
#: snikket_web/templates/invite_view.html:76
msgid ""
"Install the Snikket app on your mobile device, open it, and tap the 'Scan' "
"button at the top."
msgstr ""
#: snikket_web/templates/invite_view.html:93
msgid "Install on iOS"
msgstr ""
#: snikket_web/templates/invite_view.html:99
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 ""
#: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:"
msgstr ""
#: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131
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 ""
#: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130
msgid "Install via F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:127
msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed."
msgstr ""
#: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:"
msgstr ""
#: snikket_web/templates/library.j2:18
msgid "Copy link"
msgstr "Copy link"
@@ -1030,9 +1351,22 @@ msgstr "Snikket Login"
msgid "Enter your Snikket address and password to manage your account."
msgstr "Enter your Snikket address and password to manage your account."
#: snikket_web/templates/login.html:18
msgid "Login failed"
msgstr "Login failed"
#: snikket_web/templates/login.html:19
#, fuzzy
#| msgid "Incorrect password"
msgid "Incorrect address"
msgstr "Incorrect password"
#: snikket_web/templates/login.html:20
#, python-format
msgid ""
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
"em>. Your password was not sent."
msgstr ""
#: snikket_web/templates/unauth.html:16
msgid "Operation successful"
msgstr ""
#: snikket_web/templates/user_home.html:9
msgid "Welcome!"
@@ -1079,11 +1413,11 @@ msgstr "Manage users and invitations of this Snikket service."
msgid "Admin panel"
msgstr "Admin dashboard"
#: snikket_web/templates/user_logout.html:8
#: snikket_web/templates/user_logout.html:5
msgid "Sign out of the Snikket Web Portal"
msgstr "Sign out of the Snikket Web Portal"
#: snikket_web/templates/user_logout.html:9
#: snikket_web/templates/user_logout.html:6
msgid ""
"Click below to log yourself out of the web portal. This does not affect any "
"other connected devices."
@@ -1091,11 +1425,11 @@ msgstr ""
"Click below to log yourself out of the web portal. This does not affect any "
"other connected devices."
#: snikket_web/templates/user_passwd.html:8
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"
msgstr "Change your password"
#: snikket_web/templates/user_passwd.html:9
#: snikket_web/templates/user_passwd.html:6
msgid ""
"To change your password, you need to provide the current password as well as "
"the new one. To reduce the chance of typos, we ask for your new password "
@@ -1105,7 +1439,7 @@ msgstr ""
"the new one. To reduce the chance of typos, we ask for your new password "
"twice."
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_passwd.html:24
msgid ""
"After changing your password, you will have to enter the new password on all "
"of your devices."
@@ -1113,21 +1447,21 @@ msgstr ""
"After changing your password, you will have to enter the new password on all "
"of your devices."
#: snikket_web/templates/user_profile.html:7
#: snikket_web/templates/user_profile.html:4
#, fuzzy
#| msgid "Update profile"
msgid "Update your profile"
msgstr "Update profile"
#: snikket_web/templates/user_profile.html:9
#: snikket_web/templates/user_profile.html:6
msgid "Profile"
msgstr "Profile"
#: snikket_web/templates/user_profile.html:22
#: snikket_web/templates/user_profile.html:23
msgid "Visibility"
msgstr "Visibility"
#: snikket_web/templates/user_profile.html:23
#: snikket_web/templates/user_profile.html:24
msgid ""
"This section allows you to control who can see your profile information, "
"like avatar and nickname."
@@ -1135,22 +1469,11 @@ msgstr ""
"This section allows you to control who can see your profile information, "
"like avatar and nickname."
#~ msgid "Edit user %(user_name)s"
#~ msgstr "Edit user %(user_name)s"
#~ msgid "Create password reset link for %(user_name)s"
#~ msgstr "Create password reset link for %(user_name)s"
#~ msgid "User information"
#~ msgstr "User information"
#~ msgid ""
#~ "If the user has forgotten their password, use the below button to create "
#~ "a password reset link. The password reset link can be used once to change "
#~ "the password of the account. Transmit the link to the user via a secure "
#~ "channel."
#~ msgstr ""
#~ "If the user has forgotten their password, use the below button to create "
#~ "a password reset link. The password reset link can be used once to change "
#~ "the password of the account. Transmit the link to the user via a secure "
#~ "channel."
#~ msgid "Login failed"
#~ msgstr "Login failed"
#~ msgid "No users left"
#~ msgstr "No users left"
@@ -1214,6 +1537,3 @@ msgstr ""
#~ msgid "Copy operation failed"
#~ msgstr "Copy operation failed"
#~ msgid "Password change failed"
#~ msgstr "Password change failed"

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-30 12:45+0100\n"
"PO-Revision-Date: 2021-02-02 21:01+0000\n"
"POT-Creation-Date: 2021-03-25 17:32+0100\n"
"PO-Revision-Date: 2021-05-19 15:12+0000\n"
"Last-Translator: uira <inboxriau@andriana.id>\n"
"Language-Team: Indonesian <https://i18n.sotecware.net/projects/snikket/"
"web-portal/id/>\n"
@@ -17,127 +17,217 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 4.4.2\n"
"X-Generator: Weblate 4.5.1\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:60
#: snikket_web/admin.py:59
msgid "Limited"
msgstr "Terbatas"
#: snikket_web/admin.py:64 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr "Nama login"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:61
msgid "Display name"
msgstr "Nama tampilan"
#: snikket_web/admin.py:72 snikket_web/templates/admin_edit_user.html:33
msgid "Access Level"
msgstr "Level akses"
#: snikket_web/admin.py:77
msgid "Normal user"
msgstr "Pengguna normal"
#: snikket_web/admin.py:78
msgid "Administrator"
msgstr "Administrator"
#: snikket_web/admin.py:83
msgid "Update user"
msgstr "Memperbarui pengguna"
#: snikket_web/admin.py:87
msgid "Create password reset link"
msgstr "Buat tautan setel ulang kata sandi"
#: snikket_web/admin.py:105
msgid "Password reset link created"
msgstr "Tautan setel ulang kata sandi dibuat"
#: snikket_web/admin.py:120
msgid "User information updated."
msgstr "Informasi pengguna diperbarui."
#: snikket_web/admin.py:142
msgid "Delete user permanently"
msgstr "Hapus permanen pengguna"
#: snikket_web/admin.py:129
#: snikket_web/admin.py:155
msgid "User deleted"
msgstr "Pengguna dihapus"
#: snikket_web/admin.py:193
msgid "Password reset link not found"
msgstr "Tautan setel ulang kata sandi tidak ditemukan"
#: snikket_web/admin.py:205
msgid "Password reset link deleted"
msgstr "Tautan setel ulang kata sandi dihapus"
#: snikket_web/admin.py:225
msgid "Invite to circle"
msgstr "Undang masuk kelompok"
#: snikket_web/admin.py:135
#: snikket_web/admin.py:231
msgid "At least one circle must be selected"
msgstr "Minimal satu kelompok harus dipilih"
#: snikket_web/admin.py:140
#: snikket_web/admin.py:236
msgid "Valid for"
msgstr "Valid selama"
#: snikket_web/admin.py:142
#: snikket_web/admin.py:238
msgid "One hour"
msgstr "Satu jam"
#: snikket_web/admin.py:143
#: snikket_web/admin.py:239
msgid "Twelve hours"
msgstr "Dua belas jam"
#: snikket_web/admin.py:144
#: snikket_web/admin.py:240
msgid "One day"
msgstr "Satu hari"
#: snikket_web/admin.py:145
#: snikket_web/admin.py:241
msgid "One week"
msgstr "Satu minggu"
#: snikket_web/admin.py:146
#: snikket_web/admin.py:242
msgid "Four weeks"
msgstr "Empat minggu"
#: snikket_web/admin.py:152 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:248 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr "Jenis undangan"
#: snikket_web/admin.py:154 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:250 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr "Individu"
#: snikket_web/admin.py:155 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:251 snikket_web/templates/library.j2:114
msgid "Group"
msgstr "Grup"
#: snikket_web/admin.py:161
#: snikket_web/admin.py:257
msgid "New invitation link"
msgstr "Tautan undangan baru"
#: snikket_web/admin.py:223
#: snikket_web/admin.py:319
msgid "Revoke"
msgstr "Batalkan"
#: snikket_web/admin.py:283 snikket_web/admin.py:327
#: snikket_web/admin.py:343
msgid "Invitation created"
msgstr "Undangan dibuat"
#: snikket_web/admin.py:359
msgid "No such invitation exists"
msgstr "Undangan tidak tersedia"
#: snikket_web/admin.py:374
msgid "Invitation revoked"
msgstr "Undangan dibatalkan"
#: snikket_web/admin.py:391 snikket_web/admin.py:439
msgid "Name"
msgstr "Nama"
#: snikket_web/admin.py:288 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:396 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr "Buat kelompok"
#: snikket_web/admin.py:332
#: snikket_web/admin.py:426
msgid "Circle created"
msgstr "Kelompok dibuat"
#: snikket_web/admin.py:444
msgid "Select user"
msgstr "Pilih pengguna"
#: snikket_web/admin.py:337
#: snikket_web/admin.py:449
msgid "Update circle"
msgstr "Memperbarui kelompok"
#: snikket_web/admin.py:341
#: snikket_web/admin.py:453
msgid "Delete circle permanently"
msgstr "Hapus kelompok secara permanen"
#: snikket_web/admin.py:347
#: snikket_web/admin.py:459
msgid "Add user"
msgstr "Tambah pengguna"
#: snikket_web/infra.py:40
#: snikket_web/admin.py:475
msgid "No such circle exists"
msgstr "Kelompok tersebut tidak ada"
#: snikket_web/admin.py:512
msgid "Circle data updated"
msgstr "Data kelompok diperbarui"
#: snikket_web/admin.py:518
msgid "Circle deleted"
msgstr "Kelompok dihapus"
#: snikket_web/admin.py:529
msgid "User added to circle"
msgstr "Pengguna ditambahkan ke kelompok"
#: snikket_web/admin.py:538
msgid "User removed from circle"
msgstr "Pengguna dihapus dari kelompok"
#: snikket_web/infra.py:41
msgid "Main"
msgstr "Utama"
#: snikket_web/invite.py:93
#: snikket_web/invite.py:106
msgid "Username"
msgstr "Nama pengguna"
#: snikket_web/invite.py:97 snikket_web/invite.py:164 snikket_web/main.py:41
#: snikket_web/invite.py:110 snikket_web/invite.py:177 snikket_web/main.py:41
msgid "Password"
msgstr "Kata sandi"
#: snikket_web/invite.py:101 snikket_web/invite.py:168
#: snikket_web/invite.py:114 snikket_web/invite.py:181
msgid "Confirm password"
msgstr "Konfirmasi kata sandi"
#: snikket_web/invite.py:105 snikket_web/invite.py:172
msgid "The passwords must match"
msgstr "Kata sandi harus cocok"
#: snikket_web/invite.py:118 snikket_web/invite.py:185
msgid "The passwords must match."
msgstr "Kata sandi harus cocok."
#: snikket_web/invite.py:110
#: snikket_web/invite.py:123
msgid "Create account"
msgstr "Buat akun"
#: snikket_web/invite.py:137
msgid "That username is already taken"
msgstr "Nama pengguna itu sudah dipakai"
#: snikket_web/invite.py:150
msgid "That username is already taken."
msgstr "Nama pengguna itu sudah dipakai."
#: snikket_web/invite.py:141 snikket_web/invite.py:205
msgid "Registration was declined for unknown reasons"
msgstr "Pendaftaran ditolak karena alasan yang tidak diketahui"
#: snikket_web/invite.py:154 snikket_web/invite.py:218
msgid "Registration was declined for unknown reasons."
msgstr "Pendaftaran ditolak karena alasan yang tidak diketahui."
#: snikket_web/invite.py:145
msgid "The username is not valid"
msgstr "Nama pengguna tidak valid"
#: snikket_web/invite.py:158
msgid "The username is not valid."
msgstr "Nama pengguna tidak valid."
#: snikket_web/invite.py:177 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:32
#: snikket_web/invite.py:190 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr "Ganti kata sandi"
@@ -149,69 +239,96 @@ msgstr "Alamat"
msgid "Sign in"
msgstr "Masuk"
#: snikket_web/main.py:72
#: snikket_web/main.py:55
msgid "Invalid username or password."
msgstr "Nama pengguna atau kata sandi salah."
#: snikket_web/user.py:21
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr "Login berhasil!"
#: snikket_web/user.py:27
msgid "Current password"
msgstr "Kata sandi sekarang"
#: snikket_web/user.py:26
#: snikket_web/user.py:32
msgid "New password"
msgstr "Kata sandi baru"
#: snikket_web/user.py:31
#: snikket_web/user.py:37
msgid "Confirm new password"
msgstr "Konfirmasi kata sandi"
#: snikket_web/user.py:35
msgid "The new passwords must match"
msgstr "Kata sandi baru harus cocok"
#: snikket_web/user.py:41
msgid "The new passwords must match."
msgstr "Kata sandi baru harus cocok."
#: snikket_web/user.py:42
#: snikket_web/user.py:48
msgid "Sign out"
msgstr "Keluar"
#: snikket_web/user.py:47
#: snikket_web/user.py:53
msgid "Nobody"
msgstr "Tak seorangpun"
#: snikket_web/user.py:48
#: snikket_web/user.py:54
msgid "Friends only"
msgstr "Hanya teman"
#: snikket_web/user.py:49
#: snikket_web/user.py:55
msgid "Everyone"
msgstr "Semua orang"
#: snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:55
msgid "Display name"
msgstr "Nama tampilan"
#: snikket_web/user.py:59
#: snikket_web/user.py:65
msgid "Avatar"
msgstr "Gambar profil"
#: snikket_web/user.py:63
#: snikket_web/user.py:69
msgid "Profile visibility"
msgstr "Visibilitas profil"
#: snikket_web/user.py:68
#: snikket_web/user.py:74
msgid "Update profile"
msgstr "Perbaharui profil"
msgstr "Perbarui profil"
#: snikket_web/user.py:93
msgid "Incorrect password"
msgstr "Kata sandi salah"
#: snikket_web/user.py:99
msgid "Incorrect password."
msgstr "Kata sandi salah."
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
#: snikket_web/user.py:103
msgid "Password changed"
msgstr "Kata sandi diganti"
#: snikket_web/user.py:111
msgid ""
"The chosen avatar is too big. To be able to upload larger avatars, please "
"use the app."
msgstr ""
"Avatar yang dipilih terlalu besar. Untuk dapat mengunggah avatar yang lebih "
"besar, sila gunakan aplikasi."
#: snikket_web/user.py:159
msgid "Profile updated"
msgstr "Profil diperbarui"
#: snikket_web/templates/unauth.html:18 snikket_web/user.py:167
msgid "Error"
msgstr "Kesalahan"
#: snikket_web/templates/_footer.html:4
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr "<a href=\"%(about_url)s\">Layanan</a> Snikket"
#: snikket_web/templates/about.html:9
#: snikket_web/templates/_footer.html:6
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company."
msgstr ""
"“Snikket” dan logo burung beo adalah merek dagang dari Snikket Community "
"Interest Company."
#: snikket_web/templates/about.html:4 snikket_web/templates/about.html:9
msgid "About Snikket"
msgstr "Tentang Snikket"
@@ -271,10 +388,26 @@ msgstr ""
"href=\"%(apache20_url)s\">Lisensi Apache 2.0</a>."
#: snikket_web/templates/about.html:17
msgid "Trademarks"
msgstr "Merek dagang"
#: snikket_web/templates/about.html:18
#, python-format
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company. For more information about the trademarks, visit the <a href="
"\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
msgstr ""
"“Snikket” dan logo burung beo adalah merek dagang dari Snikket Community "
"Interest Company. Untuk informasi lebih lanjut tentang merek dagang, "
"kunjungi <a href=\"%(trademarks_url)s\">halaman informasi merek dagang "
"Snikket</a>."
#: snikket_web/templates/about.html:19
msgid "Software Versions"
msgstr "Versi perangkat lunak"
#: snikket_web/templates/about.html:27
#: snikket_web/templates/about.html:29
msgid "Back to the main page"
msgstr "Kembali ke halaman depan"
@@ -315,7 +448,7 @@ msgstr "Anggota"
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:12
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
msgstr "Tindakan"
@@ -373,7 +506,7 @@ msgid "Debug information for %(user_name)s"
msgstr "Informasi debug untuk %(user_name)s"
#: snikket_web/templates/admin_debug_user.html:11
#: snikket_web/templates/user_passwd.html:26
#: snikket_web/templates/user_passwd.html:23
msgid "Warning"
msgstr "Peringatan"
@@ -390,12 +523,12 @@ msgid "Copy complete output"
msgstr "Salin keluaran lengkap"
#: snikket_web/templates/admin_delete_user.html:4
#: snikket_web/templates/admin_users.html:22
#, python-format
msgid "Delete user %(user_name)s"
msgstr "Hapus pengguna %(user_name)s"
#: snikket_web/templates/admin_delete_user.html:6
#: snikket_web/templates/admin_edit_user.html:54
msgid "Delete user"
msgstr "Hapus pengguna"
@@ -403,11 +536,6 @@ msgstr "Hapus pengguna"
msgid "Are you sure you want to delete the following user?"
msgstr "Apakah anda yakin mau menghapus pengguna tersebut?"
#: snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:10
msgid "Login name"
msgstr "Nama login"
#: snikket_web/templates/admin_delete_user.html:15
msgid "Danger"
msgstr "Berbahaya"
@@ -423,12 +551,10 @@ msgstr ""
"strong>"
#: snikket_web/templates/admin_delete_user.html:19
#: snikket_web/templates/admin_edit_circle.html:44
#: snikket_web/templates/admin_edit_invite.html:49
#: snikket_web/templates/admin_reset_user_password.html:25
#: snikket_web/templates/user_logout.html:13
#: snikket_web/templates/user_passwd.html:30
#: snikket_web/templates/user_profile.html:31
#: snikket_web/templates/user_logout.html:10
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_profile.html:32
msgid "Back"
msgstr "Kembali"
@@ -461,6 +587,10 @@ msgstr "Informasi kelompok"
msgid "This circle has no group chat associated."
msgstr "Kelompok ini tidak memiliki percakapan grup."
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Return to circle list"
msgstr "Kembali ke daftar circle"
#: snikket_web/templates/admin_edit_circle.html:48
msgid "Delete circle"
msgstr "Hapus kelompok"
@@ -505,7 +635,6 @@ msgid "View invitation"
msgstr "Lihat undangan"
#: snikket_web/templates/admin_edit_invite.html:13
#: snikket_web/templates/admin_invites.html:21
#: snikket_web/templates/admin_reset_user_password.html:15
msgid "Valid until"
msgstr "Valid hingga"
@@ -551,6 +680,96 @@ msgstr "Pengguna akan ditambahkan ke kontak %(peer_jid)s."
msgid "Created"
msgstr "Dibuat"
#: snikket_web/templates/admin_edit_invite.html:48
msgid "Return to invitation list"
msgstr "Kembali ke daftar undangan"
#: snikket_web/templates/admin_edit_user.html:5
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
"Pengguna terbatas dapat berinteraksi dengan pengguna di layanan Snikket yang "
"sama dan menjadi anggota kelompok."
#: snikket_web/templates/admin_edit_user.html:7
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
"Seperti pengguna terbatas dan juga dapat berinteraksi dengan pengguna di "
"layanan Snikket lain."
#: snikket_web/templates/admin_edit_user.html:9
msgid "Like normal users and can access the admin panel in the web portal."
msgstr "Seperti pengguna biasa dan dapat mengakses panel admin di portal web."
#: snikket_web/templates/admin_edit_user.html:20
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Edit user %(user_name)s"
msgstr "Edit pengguna %(user_name)s"
#: snikket_web/templates/admin_edit_user.html:23
msgid "Edit user"
msgstr "Edit pengguna"
#: snikket_web/templates/admin_edit_user.html:27
msgid "The login name cannot be changed."
msgstr "Nama login tidak dapat diganti."
#: snikket_web/templates/admin_edit_user.html:34
msgid ""
"The access level of a user determines what interactions are allowed for them "
"on your Snikket service."
msgstr ""
"Level akses pengguna menentukan interaksi apa yang diizinkan untuk mereka di "
"layanan Snikket Anda."
#: snikket_web/templates/admin_edit_user.html:41
#, python-format
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
msgstr "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
#: snikket_web/templates/admin_edit_user.html:51
msgid "Return to user list"
msgstr "Kembali ke daftar pengguna"
#: snikket_web/templates/admin_edit_user.html:59
msgid "Further actions"
msgstr "Tindakan lanjutan"
#: snikket_web/templates/admin_edit_user.html:61
msgid "Reset password"
msgstr "Setel kata sandi"
#: snikket_web/templates/admin_edit_user.html:64
msgid ""
"If the user has lost their password, you can use the button below to create "
"a special link which allows to change the password of the account, once."
msgstr ""
"Jika pengguna lupa kata sandi, Anda dapat menggunakan tombol di bawah ini "
"untuk membuat tautan khusus yang memungkinkan untuk mengubah kata sandi "
"akun, sekali."
#: snikket_web/templates/admin_edit_user.html:69
msgid "Debug information"
msgstr "Informasi debug"
#: snikket_web/templates/admin_edit_user.html:71
msgid ""
"In some cases, extended information about the user account and the connected "
"devices is necessary to troubleshoot issues. The button below reveals this "
"(sensitive) information."
msgstr ""
"Dalam beberapa kasus, informasi tambahan tentang akun pengguna dan perangkat "
"yang terhubung diperlukan untuk memecahkan masalah. Tombol di bawah "
"mengungkapkan informasi (sensitif) ini."
#: snikket_web/templates/admin_edit_user.html:75
msgid "Show debug information"
msgstr "Tampilkan informasi debug"
#: snikket_web/templates/admin_home.html:4
msgid "Welcome to the admin panel!"
msgstr "Selamat datang di panel administrator!"
@@ -602,6 +821,10 @@ msgstr "Keluar panel administrator"
msgid "Pending invitations"
msgstr "Undangan menunggu jawaban"
#: snikket_web/templates/admin_invites.html:21
msgid "Expires"
msgstr "Kadaluarsa"
#: snikket_web/templates/admin_invites.html:22
msgid "Type"
msgstr "Jenis"
@@ -643,15 +866,21 @@ msgstr ""
msgid "Destroy link"
msgstr "Hancurkan tautan"
#: snikket_web/templates/admin_users.html:25
#, python-format
msgid "Show debug information for %(user_name)s"
msgstr "Lihat informasi debug untuk %(user_name)s"
#: snikket_web/templates/admin_users.html:19
msgid "The user is an administrator."
msgstr "Pengguna adalah administrator."
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Create password reset link for %(user_name)s"
msgstr "Buat tautan setel ulang kata sandi untuk %(user_name)s"
#: snikket_web/templates/admin_users.html:19
msgid " (Administrator)"
msgstr " (Admin)"
#: snikket_web/templates/admin_users.html:22
msgid "The user is restricted."
msgstr "Pengguna terbatas."
#: snikket_web/templates/admin_users.html:22
msgid " (Restricted)"
msgstr " (Terbatas)"
#: snikket_web/templates/app.html:4
msgid "Snikket Web Portal"
@@ -685,7 +914,7 @@ msgid "The web portal encountered an internal error."
msgstr "Portal web mengalami kesalahan internal."
#: snikket_web/templates/invite_invalid.html:5
#: snikket_web/templates/invite_view.html:12
#: snikket_web/templates/invite_view.html:13
#, python-format
msgid "Invite to %(site_name)s"
msgstr "Undang ke %(site_name)s"
@@ -693,7 +922,7 @@ msgstr "Undang ke %(site_name)s"
#: snikket_web/templates/invite_invalid.html:6
#: snikket_web/templates/invite_register.html:10
#: snikket_web/templates/invite_success.html:11
#: snikket_web/templates/invite_view.html:13
#: snikket_web/templates/invite_view.html:14
#, python-format
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
msgstr "Didukung oleh <img alt=\"Snikket\" src=\"%(logo_url)s\">"
@@ -740,18 +969,20 @@ msgstr ""
"menggunakan aplikasi dengan mengklik tombol di bawah ini:"
#: snikket_web/templates/invite_register.html:14
#: snikket_web/templates/invite_view.html:37
#: snikket_web/templates/invite_view.html:39
msgid "App already installed?"
msgstr "Aplikasi sudah dipasang?"
#: snikket_web/templates/invite_register.html:16
#: snikket_web/templates/invite_reset_view.html:21
#: snikket_web/templates/invite_view.html:39
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:106
#: snikket_web/templates/invite_view.html:134
msgid "Open the app"
msgstr "Buka aplikasi"
#: snikket_web/templates/invite_register.html:18
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:43
msgid "This button works only if you have the app installed already!"
msgstr "Tombol ini hanya berfungsi jika Anda sudah memasang aplikasinya!"
@@ -827,8 +1058,8 @@ msgid ""
"This page allows you to reset the password of your account, <strong>"
"%(account_jid)s</strong>, once."
msgstr ""
"Halaman ini memungkinkan Anda untuk mengatur ulang kata sandi akun, "
"<strong>%(account_jid)s</strong>, sekali."
"Halaman ini memungkinkan Anda untuk mengatur ulang kata sandi akun, <strong>"
"%(account_jid)s</strong>, sekali."
#: snikket_web/templates/invite_reset_view.html:17
msgid "Using the app"
@@ -849,7 +1080,7 @@ msgstr ""
"tombol Pindai di bagian atas."
#: snikket_web/templates/invite_reset_view.html:26
#: snikket_web/templates/invite_view.html:75
#: snikket_web/templates/invite_view.html:77
msgid ""
"Your camera will turn on. Point it at the square code below until it is "
"within the highlighted square on your screen, and wait until the app "
@@ -864,7 +1095,7 @@ msgid "You will then be prompted to enter a new password for your account."
msgstr "Anda akan diminta untuk memasukkan kata sandi baru untuk akun Anda."
#: snikket_web/templates/invite_reset_view.html:29
#: snikket_web/templates/invite_view.html:43
#: snikket_web/templates/invite_view.html:45
msgid "Alternatives"
msgstr "Alternatif"
@@ -910,7 +1141,7 @@ msgstr ""
msgid "Invite to %(site_name)s | Snikket"
msgstr "Mengundang ke %(site_name)s | Snikket"
#: snikket_web/templates/invite_view.html:15
#: snikket_web/templates/invite_view.html:16
#, python-format
msgid ""
"You have been invited to chat with %(inviter_name)s using Snikket, a secure, "
@@ -919,7 +1150,7 @@ msgstr ""
"Anda diundang untuk bercakap-cakap dengan %(inviter_name)s menggunakan "
"Snikket, aplikasi percakapan yang aman dan ramah privasi di %(site_name)s."
#: snikket_web/templates/invite_view.html:17
#: snikket_web/templates/invite_view.html:18
#, python-format
msgid ""
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
@@ -928,15 +1159,15 @@ msgstr ""
"Anda telah diundang untuk bercakap-cakap di %(site_name)s menggunakan "
"Snikket, aplikasi percakapan yang aman dan ramah privasi."
#: snikket_web/templates/invite_view.html:19
#: snikket_web/templates/invite_view.html:20
msgid "Get started"
msgstr "Mulailah"
#: snikket_web/templates/invite_view.html:21
#: snikket_web/templates/invite_view.html:22
msgid "Install the Snikket App on your Android or iOS device."
msgstr "Instal Snikket di perangkat Android atau iOS Anda."
#: snikket_web/templates/invite_view.html:23
#: snikket_web/templates/invite_view.html:24
#, python-format
msgid ""
"Install the Snikket App on your Android device (<a href=\"%(ios_info_url)s\" "
@@ -945,19 +1176,24 @@ msgstr ""
"Instal aplikasi Snikket di perangkat Android anda (<a href=\"%(ios_info_url)s"
"\" rel=\"noopener noreferrer\" target=\"_blank\">iOS segera tersedia!</a>)."
#: snikket_web/templates/invite_view.html:27
#: snikket_web/templates/invite_view.html:28
msgid "Get it on Google Play"
msgstr "Dapatkan di Google Play"
#: snikket_web/templates/invite_view.html:29
#: snikket_web/templates/invite_view.html:30
#: snikket_web/templates/invite_view.html:102
msgid "Download on the App Store"
msgstr "Unduh di App Store"
#: snikket_web/templates/invite_view.html:33
msgid "Not on mobile?"
msgstr "Bukan di ponsel?"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr "Dapatkan di F-Droid"
#: snikket_web/templates/invite_view.html:36
#: snikket_web/templates/invite_view.html:35
msgid "Send to mobile device"
msgstr "Dikirim ke telepon seluler"
#: snikket_web/templates/invite_view.html:38
msgid ""
"After installation the app should automatically open and prompt you to "
"create an account. If not, simply click the button below."
@@ -965,7 +1201,7 @@ msgstr ""
"Setelah instalasi, aplikasi akan terbuka otomatis dan meminta Anda untuk "
"membuat akun. Jika tidak, cukup klik tombol di bawah."
#: snikket_web/templates/invite_view.html:44
#: snikket_web/templates/invite_view.html:46
#, python-format
msgid ""
"You can connect to Snikket using any XMPP-compatible software. If the button "
@@ -976,16 +1212,20 @@ msgstr ""
"XMPP. Jika tombol di atas tidak berfungsi dengan aplikasi, Anda mungkin "
"perlu <a href=\"%(register_url)s\">mendaftarkan akun secara manual</a>."
#: snikket_web/templates/invite_view.html:50
#: snikket_web/templates/invite_view.html:52
msgid "Scan invite code"
msgstr "Pindai kode undangan"
#: snikket_web/templates/invite_view.html:53
#: snikket_web/templates/invite_view.html:82
#: snikket_web/templates/invite_view.html:55
#: snikket_web/templates/invite_view.html:84
#: snikket_web/templates/invite_view.html:96
#: snikket_web/templates/invite_view.html:112
#: snikket_web/templates/invite_view.html:124
#: snikket_web/templates/invite_view.html:140
msgid "Close"
msgstr "Tutup"
#: snikket_web/templates/invite_view.html:56
#: snikket_web/templates/invite_view.html:58
msgid ""
"You can transfer this invite to your mobile device by scanning a code with "
"your camera. You can use either a QR scanner app or the Snikket app itself."
@@ -994,22 +1234,22 @@ msgstr ""
"kode dengan kamera. Anda dapat menggunakan aplikasi pemindai QR atau "
"aplikasi Snikket."
#: snikket_web/templates/invite_view.html:61
#: snikket_web/templates/invite_view.html:63
msgid "Using a QR code scanner"
msgstr "Menggunakan pemindai kode QR"
#: snikket_web/templates/invite_view.html:63
#: snikket_web/templates/invite_view.html:65
msgid "Using the Snikket app"
msgstr "Menggunakan Snikket"
#: snikket_web/templates/invite_view.html:68
#: snikket_web/templates/invite_view.html:70
msgid ""
"Use a <em>QR code</em> scanner on your mobile device to scan the code below:"
msgstr ""
"Gunakan pemindai <em>kode QR</em> di perangkat seluler Anda untuk memindai "
"kode di bawah:"
#: snikket_web/templates/invite_view.html:74
#: snikket_web/templates/invite_view.html:76
msgid ""
"Install the Snikket app on your mobile device, open it, and tap the 'Scan' "
"button at the top."
@@ -1017,6 +1257,48 @@ msgstr ""
"Instal aplikasi Snikket di perangkat seluler Anda, buka, dan ketuk tombol "
"'Pindai'."
#: snikket_web/templates/invite_view.html:93
msgid "Install on iOS"
msgstr "Pasang di iOS"
#: snikket_web/templates/invite_view.html:99
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 ""
"Setelah mengunduh Snikket dari App Store, Anda harus kembali ke tautan "
"undangan ini dan klik \"Buka aplikasi\" untuk melanjutkan."
#: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:"
msgstr "Pertama, unduh Snikket dari App Store menggunakan tombol di bawah ini:"
#: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131
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 ""
"Setelah instalasi selesai, Anda dapat kembali ke halaman ini dan mengetuk "
"tombol \"Buka aplikasi\" untuk melanjutkan pemasangan:"
#: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130
msgid "Install via F-Droid"
msgstr "Install melalui F-Droid"
#: snikket_web/templates/invite_view.html:127
msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed."
msgstr ""
"Setelah menginstal Snikket melalui F-Droid, Anda harus kembali ke tautan "
"undangan ini dan ketuk \"Buka aplikasi\" untuk melanjutkan."
#: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:"
msgstr "Pertama install Snikket dari F-Droid menggunakan tombol di bawah ini:"
#: snikket_web/templates/library.j2:18
msgid "Copy link"
msgstr "Salin tautan"
@@ -1046,9 +1328,22 @@ msgstr "Login Snikket"
msgid "Enter your Snikket address and password to manage your account."
msgstr "Ketik alamat Snikket dan kata sandinya untuk mengelola akun anda."
#: snikket_web/templates/login.html:18
msgid "Login failed"
msgstr "Percobaan masuk gagal"
#: snikket_web/templates/login.html:19
msgid "Incorrect address"
msgstr "Alamat salah"
#: snikket_web/templates/login.html:20
#, python-format
msgid ""
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
"em>. Your password was not sent."
msgstr ""
"Layanan Snikket ini hanya menghosting alamat yang diakhiri dengan <em>@"
"%(snikket_domain)s</em>. Kata sandi Anda tidak terkirim."
#: snikket_web/templates/unauth.html:16
msgid "Operation successful"
msgstr "Operasi berhasil"
#: snikket_web/templates/user_home.html:9
msgid "Welcome!"
@@ -1083,11 +1378,11 @@ msgstr "Kelola pengguna, undangan dan kelompok pengguna layanan Snikket."
msgid "Admin panel"
msgstr "Panel Administrator"
#: snikket_web/templates/user_logout.html:8
#: snikket_web/templates/user_logout.html:5
msgid "Sign out of the Snikket Web Portal"
msgstr "Keluar dari Portal Web Snikket"
#: snikket_web/templates/user_logout.html:9
#: snikket_web/templates/user_logout.html:6
msgid ""
"Click below to log yourself out of the web portal. This does not affect any "
"other connected devices."
@@ -1095,11 +1390,11 @@ msgstr ""
"Klik tombol untuk keluar dari portal web. Ini tidak mempengaruhi perangkat "
"lain yang terhubung."
#: snikket_web/templates/user_passwd.html:8
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"
msgstr "Ganti kata sandi anda"
#: snikket_web/templates/user_passwd.html:9
#: snikket_web/templates/user_passwd.html:6
msgid ""
"To change your password, you need to provide the current password as well as "
"the new one. To reduce the chance of typos, we ask for your new password "
@@ -1109,7 +1404,7 @@ msgstr ""
"baru. Untuk mengurangi kemungkinan salah ketik, kami meminta anda mengetik "
"sandi baru dua kali."
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_passwd.html:24
msgid ""
"After changing your password, you will have to enter the new password on all "
"of your devices."
@@ -1117,19 +1412,19 @@ msgstr ""
"Sesudah penggantian kata sandi, anda perlu memasukkan kata sandi baru ke "
"semua perangkat anda."
#: snikket_web/templates/user_profile.html:7
#: snikket_web/templates/user_profile.html:4
msgid "Update your profile"
msgstr "Perbaharui profil anda"
msgstr "Perbarui profil anda"
#: snikket_web/templates/user_profile.html:9
#: snikket_web/templates/user_profile.html:6
msgid "Profile"
msgstr "Profil"
#: snikket_web/templates/user_profile.html:22
#: snikket_web/templates/user_profile.html:23
msgid "Visibility"
msgstr "Visibilitas"
#: snikket_web/templates/user_profile.html:23
#: snikket_web/templates/user_profile.html:24
msgid ""
"This section allows you to control who can see your profile information, "
"like avatar and nickname."
@@ -1137,22 +1432,14 @@ msgstr ""
"Bagian ini memungkinkan Anda untuk mengontrol siapa yang bisa melihat profil "
"Anda, seperti avatar dan nama panggilan."
#~ msgid "Edit user %(user_name)s"
#~ msgstr "Edit pengguna %(user_name)s"
#~ msgid "Create password reset link for %(user_name)s"
#~ msgstr "Buat tautan setel ulang kata sandi untuk %(user_name)s"
#~ msgid "User information"
#~ msgstr "Informasi pengguna"
#~ msgid "Login failed"
#~ msgstr "Percobaan masuk gagal"
#~ msgid ""
#~ "If the user has forgotten their password, use the below button to create "
#~ "a password reset link. The password reset link can be used once to change "
#~ "the password of the account. Transmit the link to the user via a secure "
#~ "channel."
#~ msgstr ""
#~ "Jika pengguna lupa kata sandi, gunakan tombol di bawah untuk membuat "
#~ "tautan setel ulang kata sandi. Tautan pengaturan ulang kata sandi dapat "
#~ "digunakan satu kali untuk mengubah kata sandi akun. Kirimkan link ke "
#~ "pengguna melalui saluran aman."
#~ msgid "Not on mobile?"
#~ msgstr "Bukan di ponsel?"
#~ msgid "<label for=\"link-field\">Link</label>"
#~ msgstr "<label for=\"link-field\">Tautan</label>"
@@ -1223,6 +1510,3 @@ msgstr ""
#~ msgid "Copy operation failed"
#~ msgstr "Proses penyalinan gagal"
#~ msgid "Password change failed"
#~ msgstr "Gagal mengganti kata kunci"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-30 12:45+0100\n"
"POT-Creation-Date: 2021-03-25 17:32+0100\n"
"PO-Revision-Date: 2021-01-28 17:55+0000\n"
"Last-Translator: pep <pep@bouah.net>\n"
"Language-Team: Japanese <https://i18n.sotecware.net/projects/snikket/web-"
@@ -20,124 +20,254 @@ msgstr ""
"X-Generator: Weblate 4.4.2\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:60
#: snikket_web/admin.py:59
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:64 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr "ロゲイン名"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:61
msgid "Display name"
msgstr "表示名"
#: snikket_web/admin.py:72 snikket_web/templates/admin_edit_user.html:33
msgid "Access Level"
msgstr ""
#: snikket_web/admin.py:77
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:78
#, fuzzy
#| msgid "Admin area"
msgid "Administrator"
msgstr "管理"
#: snikket_web/admin.py:83
#, fuzzy
#| msgid "Update circle"
msgid "Update user"
msgstr "サークルを更新"
#: snikket_web/admin.py:87
#, fuzzy
#| msgid "Password reset link for %(user_name)s"
msgid "Create password reset link"
msgstr "%(user_name)s のパスワード再設定リンク"
#: snikket_web/admin.py:105
#, fuzzy
#| msgid "Password reset link for %(user_name)s"
msgid "Password reset link created"
msgstr "%(user_name)s のパスワード再設定リンク"
#: snikket_web/admin.py:120
#, fuzzy
#| msgid "User information"
msgid "User information updated."
msgstr "ユーザー詳細"
#: snikket_web/admin.py:142
msgid "Delete user permanently"
msgstr "ユーザーを削除する"
#: snikket_web/admin.py:129
#: snikket_web/admin.py:155
msgid "User deleted"
msgstr ""
#: snikket_web/admin.py:193
#, fuzzy
#| msgid "Password reset link for %(user_name)s"
msgid "Password reset link not found"
msgstr "%(user_name)s のパスワード再設定リンク"
#: snikket_web/admin.py:205
#, fuzzy
#| msgid "Password reset link for %(user_name)s"
msgid "Password reset link deleted"
msgstr "%(user_name)s のパスワード再設定リンク"
#: snikket_web/admin.py:225
msgid "Invite to circle"
msgstr "サークルに紹介する"
#: snikket_web/admin.py:135
#: snikket_web/admin.py:231
msgid "At least one circle must be selected"
msgstr "サークルを選択してください"
#: snikket_web/admin.py:140
#: snikket_web/admin.py:236
msgid "Valid for"
msgstr "有効期限"
#: snikket_web/admin.py:142
#: snikket_web/admin.py:238
msgid "One hour"
msgstr "一時間"
#: snikket_web/admin.py:143
#: snikket_web/admin.py:239
msgid "Twelve hours"
msgstr "12時間"
#: snikket_web/admin.py:144
#: snikket_web/admin.py:240
msgid "One day"
msgstr "一日"
#: snikket_web/admin.py:145
#: snikket_web/admin.py:241
msgid "One week"
msgstr "一週間"
#: snikket_web/admin.py:146
#: snikket_web/admin.py:242
msgid "Four weeks"
msgstr "4週間"
#: snikket_web/admin.py:152 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:248 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr "紹介の種類"
#: snikket_web/admin.py:154 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:250 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr "一回"
#: snikket_web/admin.py:155 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:251 snikket_web/templates/library.j2:114
msgid "Group"
msgstr "複数回"
#: snikket_web/admin.py:161
#: snikket_web/admin.py:257
msgid "New invitation link"
msgstr "新しい紹介状"
#: snikket_web/admin.py:223
#: snikket_web/admin.py:319
msgid "Revoke"
msgstr "取り消す"
#: snikket_web/admin.py:283 snikket_web/admin.py:327
#: snikket_web/admin.py:343
#, fuzzy
#| msgid "Invitation type"
msgid "Invitation created"
msgstr "紹介の種類"
#: snikket_web/admin.py:359
#, fuzzy
#| msgid "New invitation link"
msgid "No such invitation exists"
msgstr "新しい紹介状"
#: snikket_web/admin.py:374
#, fuzzy
#| msgid "Invitation type"
msgid "Invitation revoked"
msgstr "紹介の種類"
#: snikket_web/admin.py:391 snikket_web/admin.py:439
msgid "Name"
msgstr "名"
#: snikket_web/admin.py:288 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:396 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr "サークルを作成"
#: snikket_web/admin.py:332
#: snikket_web/admin.py:426
#, fuzzy
#| msgid "Circle name"
msgid "Circle created"
msgstr "サークル名"
#: snikket_web/admin.py:444
msgid "Select user"
msgstr "ユーザー選択"
#: snikket_web/admin.py:337
#: snikket_web/admin.py:449
msgid "Update circle"
msgstr "サークルを更新"
#: snikket_web/admin.py:341
#: snikket_web/admin.py:453
msgid "Delete circle permanently"
msgstr "サークルを削除"
#: snikket_web/admin.py:347
#: snikket_web/admin.py:459
msgid "Add user"
msgstr "ユーザーを追加する"
#: snikket_web/infra.py:40
#: snikket_web/admin.py:475
#, fuzzy
#| msgid "No circles"
msgid "No such circle exists"
msgstr "なし"
#: snikket_web/admin.py:512
#, fuzzy
#| msgid "Circle name"
msgid "Circle data updated"
msgstr "サークル名"
#: snikket_web/admin.py:518
#, fuzzy
#| msgid "Circle members"
msgid "Circle deleted"
msgstr "サークル会員"
#: snikket_web/admin.py:529
#, fuzzy
#| msgid "Invite to circle"
msgid "User added to circle"
msgstr "サークルに紹介する"
#: snikket_web/admin.py:538
#, fuzzy
#| msgid "Remove user %(username)s from circle"
msgid "User removed from circle"
msgstr "%(username)s をサークルから外す"
#: snikket_web/infra.py:41
msgid "Main"
msgstr "第一サークル"
#: snikket_web/invite.py:93
#: snikket_web/invite.py:106
msgid "Username"
msgstr "ユーザー名"
#: snikket_web/invite.py:97 snikket_web/invite.py:164 snikket_web/main.py:41
#: snikket_web/invite.py:110 snikket_web/invite.py:177 snikket_web/main.py:41
msgid "Password"
msgstr "パスワード"
#: snikket_web/invite.py:101 snikket_web/invite.py:168
#: snikket_web/invite.py:114 snikket_web/invite.py:181
msgid "Confirm password"
msgstr "確認用パスワード"
#: snikket_web/invite.py:105 snikket_web/invite.py:172
msgid "The passwords must match"
#: snikket_web/invite.py:118 snikket_web/invite.py:185
#, fuzzy
#| msgid "The passwords must match"
msgid "The passwords must match."
msgstr "確認用パスワードが一致しません"
#: snikket_web/invite.py:110
#: snikket_web/invite.py:123
msgid "Create account"
msgstr "アカウント作成"
#: snikket_web/invite.py:137
msgid "That username is already taken"
#: snikket_web/invite.py:150
#, fuzzy
#| msgid "That username is already taken"
msgid "That username is already taken."
msgstr "このユーザー名は存在しています"
#: snikket_web/invite.py:141 snikket_web/invite.py:205
msgid "Registration was declined for unknown reasons"
#: snikket_web/invite.py:154 snikket_web/invite.py:218
#, fuzzy
#| msgid "Registration was declined for unknown reasons"
msgid "Registration was declined for unknown reasons."
msgstr "理由不明の登録エラー"
#: snikket_web/invite.py:145
msgid "The username is not valid"
#: snikket_web/invite.py:158
#, fuzzy
#| msgid "The username is not valid"
msgid "The username is not valid."
msgstr "ユーザー名が不正"
#: snikket_web/invite.py:177 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:32
#: snikket_web/invite.py:190 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr "パスワード変更"
@@ -149,69 +279,100 @@ msgstr "アドレス"
msgid "Sign in"
msgstr "サインイン"
#: snikket_web/main.py:72
#: snikket_web/main.py:55
msgid "Invalid username or password."
msgstr "ユーザー名またはパスワードが不正。"
#: snikket_web/user.py:21
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr ""
#: snikket_web/user.py:27
msgid "Current password"
msgstr "現在のパスワード"
#: snikket_web/user.py:26
#: snikket_web/user.py:32
msgid "New password"
msgstr "新しいパスワード"
#: snikket_web/user.py:31
#: snikket_web/user.py:37
msgid "Confirm new password"
msgstr "新しいパスワードの確認"
#: snikket_web/user.py:35
msgid "The new passwords must match"
#: snikket_web/user.py:41
#, fuzzy
#| msgid "The new passwords must match"
msgid "The new passwords must match."
msgstr "新しいパスワードが不一致"
#: snikket_web/user.py:42
#: snikket_web/user.py:48
msgid "Sign out"
msgstr "サインアウト"
#: snikket_web/user.py:47
#: snikket_web/user.py:53
msgid "Nobody"
msgstr ""
#: snikket_web/user.py:48
#: snikket_web/user.py:54
msgid "Friends only"
msgstr "コンタクト限定"
#: snikket_web/user.py:49
#: snikket_web/user.py:55
msgid "Everyone"
msgstr "全員"
#: snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:55
msgid "Display name"
msgstr "表示名"
#: snikket_web/user.py:59
#: snikket_web/user.py:65
msgid "Avatar"
msgstr "アバター"
#: snikket_web/user.py:63
#: snikket_web/user.py:69
msgid "Profile visibility"
msgstr ""
#: snikket_web/user.py:68
#: snikket_web/user.py:74
msgid "Update profile"
msgstr "プロファイル管理"
#: snikket_web/user.py:93
msgid "Incorrect password"
#: snikket_web/user.py:99
#, fuzzy
#| msgid "Incorrect password"
msgid "Incorrect password."
msgstr "パスワード不正"
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
#: snikket_web/user.py:103
#, fuzzy
#| msgid "Password reset"
msgid "Password changed"
msgstr "パスワード再設定"
#: snikket_web/user.py:111
msgid ""
"The chosen avatar is too big. To be able to upload larger avatars, please "
"use the app."
msgstr ""
#: snikket_web/user.py:159
#, fuzzy
#| msgid "Profile"
msgid "Profile updated"
msgstr "プロファイル"
#: snikket_web/templates/unauth.html:18 snikket_web/user.py:167
msgid "Error"
msgstr ""
#: snikket_web/templates/_footer.html:4
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr ""
#: snikket_web/templates/about.html:9
#: snikket_web/templates/_footer.html:6
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company."
msgstr ""
#: snikket_web/templates/about.html:4 snikket_web/templates/about.html:9
msgid "About Snikket"
msgstr "Snikketについて"
@@ -262,10 +423,22 @@ msgid ""
msgstr ""
#: snikket_web/templates/about.html:17
msgid "Trademarks"
msgstr ""
#: snikket_web/templates/about.html:18
#, python-format
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community Interest "
"Company. For more information about the trademarks, visit the <a href="
"\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
msgstr ""
#: snikket_web/templates/about.html:19
msgid "Software Versions"
msgstr "ソフトウェアバーション"
#: snikket_web/templates/about.html:27
#: snikket_web/templates/about.html:29
msgid "Back to the main page"
msgstr "戻る"
@@ -301,7 +474,7 @@ msgstr "サークル員"
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:12
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
msgstr "操作"
@@ -355,7 +528,7 @@ msgid "Debug information for %(user_name)s"
msgstr ""
#: snikket_web/templates/admin_debug_user.html:11
#: snikket_web/templates/user_passwd.html:26
#: snikket_web/templates/user_passwd.html:23
msgid "Warning"
msgstr ""
@@ -372,12 +545,12 @@ msgid "Copy complete output"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:4
#: snikket_web/templates/admin_users.html:22
#, python-format
msgid "Delete user %(user_name)s"
msgstr "%(user_name)s を削除"
#: snikket_web/templates/admin_delete_user.html:6
#: snikket_web/templates/admin_edit_user.html:54
msgid "Delete user"
msgstr "ユーザー削除"
@@ -385,11 +558,6 @@ msgstr "ユーザー削除"
msgid "Are you sure you want to delete the following user?"
msgstr "本当にこのユーザーを削除していいですか?"
#: snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:10
msgid "Login name"
msgstr "ロゲイン名"
#: snikket_web/templates/admin_delete_user.html:15
msgid "Danger"
msgstr "警告"
@@ -404,12 +572,10 @@ msgstr ""
"strong>"
#: snikket_web/templates/admin_delete_user.html:19
#: snikket_web/templates/admin_edit_circle.html:44
#: snikket_web/templates/admin_edit_invite.html:49
#: snikket_web/templates/admin_reset_user_password.html:25
#: snikket_web/templates/user_logout.html:13
#: snikket_web/templates/user_passwd.html:30
#: snikket_web/templates/user_profile.html:31
#: snikket_web/templates/user_logout.html:10
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_profile.html:32
msgid "Back"
msgstr "戻る"
@@ -441,6 +607,10 @@ msgstr "詳細"
msgid "This circle has no group chat associated."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Return to circle list"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:48
msgid "Delete circle"
msgstr "サークル削除"
@@ -483,7 +653,6 @@ msgid "View invitation"
msgstr "紹介状表示"
#: snikket_web/templates/admin_edit_invite.html:13
#: snikket_web/templates/admin_invites.html:21
#: snikket_web/templates/admin_reset_user_password.html:15
msgid "Valid until"
msgstr "有効期限"
@@ -525,6 +694,96 @@ msgstr ""
msgid "Created"
msgstr "作成時"
#: snikket_web/templates/admin_edit_invite.html:48
#, fuzzy
#| msgid "New invitation link"
msgid "Return to invitation list"
msgstr "新しい紹介状"
#: snikket_web/templates/admin_edit_user.html:5
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:7
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:9
msgid "Like normal users and can access the admin panel in the web portal."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:20
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Edit user %(user_name)s"
msgstr "%(user_name)s を更新"
#: snikket_web/templates/admin_edit_user.html:23
#, fuzzy
#| msgid "Add user"
msgid "Edit user"
msgstr "ユーザーを追加する"
#: snikket_web/templates/admin_edit_user.html:27
msgid "The login name cannot be changed."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:34
msgid ""
"The access level of a user determines what interactions are allowed for them "
"on your Snikket service."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:41
#, python-format
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:51
#, fuzzy
#| msgid "New invitation link"
msgid "Return to user list"
msgstr "新しい紹介状"
#: snikket_web/templates/admin_edit_user.html:59
msgid "Further actions"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:61
#, fuzzy
#| msgid "Change your password"
msgid "Reset password"
msgstr "パスワード変更"
#: snikket_web/templates/admin_edit_user.html:64
msgid ""
"If the user has lost their password, you can use the button below to create "
"a special link which allows to change the password of the account, once."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:69
#, fuzzy
#| msgid "User information"
msgid "Debug information"
msgstr "ユーザー詳細"
#: snikket_web/templates/admin_edit_user.html:71
msgid ""
"In some cases, extended information about the user account and the connected "
"devices is necessary to troubleshoot issues. The button below reveals this "
"(sensitive) information."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:75
#, fuzzy
#| msgid "User information"
msgid "Show debug information"
msgstr "ユーザー詳細"
#: snikket_web/templates/admin_home.html:4
#, fuzzy
#| msgid "Back to the main page"
@@ -588,6 +847,10 @@ msgstr ""
msgid "Pending invitations"
msgstr ""
#: snikket_web/templates/admin_invites.html:21
msgid "Expires"
msgstr ""
#: snikket_web/templates/admin_invites.html:22
msgid "Type"
msgstr "種類"
@@ -627,14 +890,24 @@ msgstr "このリンクはパスワードを一回きり再設定可能にする
msgid "Destroy link"
msgstr "リンク取り消す"
#: snikket_web/templates/admin_users.html:25
#, python-format
msgid "Show debug information for %(user_name)s"
#: snikket_web/templates/admin_users.html:19
#, fuzzy
#| msgid "The username is not valid"
msgid "The user is an administrator."
msgstr "ユーザー名が不正"
#: snikket_web/templates/admin_users.html:19
msgid " (Administrator)"
msgstr ""
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Create password reset link for %(user_name)s"
#: snikket_web/templates/admin_users.html:22
#, fuzzy
#| msgid "The username is not valid"
msgid "The user is restricted."
msgstr "ユーザー名が不正"
#: snikket_web/templates/admin_users.html:22
msgid " (Restricted)"
msgstr ""
#: snikket_web/templates/app.html:4
@@ -669,7 +942,7 @@ msgid "The web portal encountered an internal error."
msgstr ""
#: snikket_web/templates/invite_invalid.html:5
#: snikket_web/templates/invite_view.html:12
#: snikket_web/templates/invite_view.html:13
#, python-format
msgid "Invite to %(site_name)s"
msgstr "%(site_name)s に紹介する"
@@ -677,7 +950,7 @@ msgstr "%(site_name)s に紹介する"
#: snikket_web/templates/invite_invalid.html:6
#: snikket_web/templates/invite_register.html:10
#: snikket_web/templates/invite_success.html:11
#: snikket_web/templates/invite_view.html:13
#: snikket_web/templates/invite_view.html:14
#, python-format
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
msgstr "Powered by <img alt=\"Snikket\" src=\"%(logo_url)s\">"
@@ -720,18 +993,20 @@ msgid ""
msgstr ""
#: snikket_web/templates/invite_register.html:14
#: snikket_web/templates/invite_view.html:37
#: snikket_web/templates/invite_view.html:39
msgid "App already installed?"
msgstr ""
#: snikket_web/templates/invite_register.html:16
#: snikket_web/templates/invite_reset_view.html:21
#: snikket_web/templates/invite_view.html:39
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:106
#: snikket_web/templates/invite_view.html:134
msgid "Open the app"
msgstr ""
#: snikket_web/templates/invite_register.html:18
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:43
msgid "This button works only if you have the app installed already!"
msgstr ""
@@ -824,7 +1099,7 @@ msgid ""
msgstr ""
#: snikket_web/templates/invite_reset_view.html:26
#: snikket_web/templates/invite_view.html:75
#: snikket_web/templates/invite_view.html:77
msgid ""
"Your camera will turn on. Point it at the square code below until it is "
"within the highlighted square on your screen, and wait until the app "
@@ -836,7 +1111,7 @@ msgid "You will then be prompted to enter a new password for your account."
msgstr ""
#: snikket_web/templates/invite_reset_view.html:29
#: snikket_web/templates/invite_view.html:43
#: snikket_web/templates/invite_view.html:45
msgid "Alternatives"
msgstr ""
@@ -881,54 +1156,59 @@ msgstr ""
msgid "Invite to %(site_name)s | Snikket"
msgstr "%(site_name)s に紹介 | Snikket"
#: snikket_web/templates/invite_view.html:15
#: snikket_web/templates/invite_view.html:16
#, python-format
msgid ""
"You have been invited to chat with %(inviter_name)s using Snikket, a secure, "
"privacy-friendly chat app on %(site_name)s."
msgstr ""
#: snikket_web/templates/invite_view.html:17
#: snikket_web/templates/invite_view.html:18
#, python-format
msgid ""
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
"privacy-friendly chat app."
msgstr ""
#: snikket_web/templates/invite_view.html:19
#: snikket_web/templates/invite_view.html:20
msgid "Get started"
msgstr ""
#: snikket_web/templates/invite_view.html:21
#: snikket_web/templates/invite_view.html:22
msgid "Install the Snikket App on your Android or iOS device."
msgstr ""
#: snikket_web/templates/invite_view.html:23
#: snikket_web/templates/invite_view.html:24
#, python-format
msgid ""
"Install the Snikket App on your Android device (<a href=\"%(ios_info_url)s\" "
"rel=\"noopener noreferrer\" target=\"_blank\">iOS coming soon!</a>)."
msgstr ""
#: snikket_web/templates/invite_view.html:27
#: snikket_web/templates/invite_view.html:28
msgid "Get it on Google Play"
msgstr ""
#: snikket_web/templates/invite_view.html:29
#: snikket_web/templates/invite_view.html:30
#: snikket_web/templates/invite_view.html:102
msgid "Download on the App Store"
msgstr ""
#: snikket_web/templates/invite_view.html:33
msgid "Not on mobile?"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:36
#: snikket_web/templates/invite_view.html:35
msgid "Send to mobile device"
msgstr ""
#: snikket_web/templates/invite_view.html:38
msgid ""
"After installation the app should automatically open and prompt you to "
"create an account. If not, simply click the button below."
msgstr ""
#: snikket_web/templates/invite_view.html:44
#: snikket_web/templates/invite_view.html:46
#, python-format
msgid ""
"You can connect to Snikket using any XMPP-compatible software. If the button "
@@ -936,40 +1216,80 @@ msgid ""
"\">register an account manually</a>."
msgstr ""
#: snikket_web/templates/invite_view.html:50
#: snikket_web/templates/invite_view.html:52
msgid "Scan invite code"
msgstr "紹介状をスキャン"
#: snikket_web/templates/invite_view.html:53
#: snikket_web/templates/invite_view.html:82
#: snikket_web/templates/invite_view.html:55
#: snikket_web/templates/invite_view.html:84
#: snikket_web/templates/invite_view.html:96
#: snikket_web/templates/invite_view.html:112
#: snikket_web/templates/invite_view.html:124
#: snikket_web/templates/invite_view.html:140
msgid "Close"
msgstr "閉める"
#: snikket_web/templates/invite_view.html:56
#: snikket_web/templates/invite_view.html:58
msgid ""
"You can transfer this invite to your mobile device by scanning a code with "
"your camera. You can use either a QR scanner app or the Snikket app itself."
msgstr ""
#: snikket_web/templates/invite_view.html:61
#: snikket_web/templates/invite_view.html:63
msgid "Using a QR code scanner"
msgstr ""
#: snikket_web/templates/invite_view.html:63
#: snikket_web/templates/invite_view.html:65
msgid "Using the Snikket app"
msgstr ""
#: snikket_web/templates/invite_view.html:68
#: snikket_web/templates/invite_view.html:70
msgid ""
"Use a <em>QR code</em> scanner on your mobile device to scan the code below:"
msgstr ""
#: snikket_web/templates/invite_view.html:74
#: snikket_web/templates/invite_view.html:76
msgid ""
"Install the Snikket app on your mobile device, open it, and tap the 'Scan' "
"button at the top."
msgstr ""
#: snikket_web/templates/invite_view.html:93
msgid "Install on iOS"
msgstr ""
#: snikket_web/templates/invite_view.html:99
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 ""
#: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:"
msgstr ""
#: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131
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 ""
#: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130
msgid "Install via F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:127
msgid ""
"After installing Snikket via F-Droid, you have to return to this invite link "
"and tap on \"Open the app\" to proceed."
msgstr ""
#: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:"
msgstr ""
#: snikket_web/templates/library.j2:18
msgid "Copy link"
msgstr "リンクをコピーする"
@@ -998,8 +1318,21 @@ msgstr "Snikket ログイン"
msgid "Enter your Snikket address and password to manage your account."
msgstr ""
#: snikket_web/templates/login.html:18
msgid "Login failed"
#: snikket_web/templates/login.html:19
#, fuzzy
#| msgid "Incorrect password"
msgid "Incorrect address"
msgstr "パスワード不正"
#: snikket_web/templates/login.html:20
#, python-format
msgid ""
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
"em>. Your password was not sent."
msgstr ""
#: snikket_web/templates/unauth.html:16
msgid "Operation successful"
msgstr ""
#: snikket_web/templates/user_home.html:9
@@ -1045,59 +1378,53 @@ msgstr ""
msgid "Admin panel"
msgstr "管理"
#: snikket_web/templates/user_logout.html:8
#: snikket_web/templates/user_logout.html:5
msgid "Sign out of the Snikket Web Portal"
msgstr "Snikket ホームページからログアウト"
#: snikket_web/templates/user_logout.html:9
#: snikket_web/templates/user_logout.html:6
msgid ""
"Click below to log yourself out of the web portal. This does not affect any "
"other connected devices."
msgstr ""
#: snikket_web/templates/user_passwd.html:8
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"
msgstr "パスワード変更"
#: snikket_web/templates/user_passwd.html:9
#: snikket_web/templates/user_passwd.html:6
msgid ""
"To change your password, you need to provide the current password as well as "
"the new one. To reduce the chance of typos, we ask for your new password "
"twice."
msgstr ""
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_passwd.html:24
msgid ""
"After changing your password, you will have to enter the new password on all "
"of your devices."
msgstr ""
#: snikket_web/templates/user_profile.html:7
#: snikket_web/templates/user_profile.html:4
#, fuzzy
#| msgid "Update profile"
msgid "Update your profile"
msgstr "プロファイル管理"
#: snikket_web/templates/user_profile.html:9
#: snikket_web/templates/user_profile.html:6
msgid "Profile"
msgstr "プロファイル"
#: snikket_web/templates/user_profile.html:22
#: snikket_web/templates/user_profile.html:23
msgid "Visibility"
msgstr ""
#: snikket_web/templates/user_profile.html:23
#: snikket_web/templates/user_profile.html:24
msgid ""
"This section allows you to control who can see your profile information, "
"like avatar and nickname."
msgstr ""
#~ msgid "Edit user %(user_name)s"
#~ msgstr "%(user_name)s を更新"
#~ msgid "User information"
#~ msgstr "ユーザー詳細"
#~ msgid "This circle cannot be modified"
#~ msgstr "このサークルの更新ができません"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2021-01-30 12:45+0100\n"
"POT-Creation-Date: 2021-03-25 17:32+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"
@@ -17,124 +17,214 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.9.0\n"
#: snikket_web/admin.py:60
msgid "Delete user permanently"
#: snikket_web/admin.py:59
msgid "Limited"
msgstr ""
#: snikket_web/admin.py:129
msgid "Invite to circle"
#: snikket_web/admin.py:64 snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:8
msgid "Login name"
msgstr ""
#: snikket_web/admin.py:135
msgid "At least one circle must be selected"
#: snikket_web/admin.py:68 snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:9 snikket_web/user.py:61
msgid "Display name"
msgstr ""
#: snikket_web/admin.py:140
msgid "Valid for"
#: snikket_web/admin.py:72 snikket_web/templates/admin_edit_user.html:33
msgid "Access Level"
msgstr ""
#: snikket_web/admin.py:77
msgid "Normal user"
msgstr ""
#: snikket_web/admin.py:78
msgid "Administrator"
msgstr ""
#: snikket_web/admin.py:83
msgid "Update user"
msgstr ""
#: snikket_web/admin.py:87
msgid "Create password reset link"
msgstr ""
#: snikket_web/admin.py:105
msgid "Password reset link created"
msgstr ""
#: snikket_web/admin.py:120
msgid "User information updated."
msgstr ""
#: snikket_web/admin.py:142
msgid "Delete user permanently"
msgstr ""
#: snikket_web/admin.py:155
msgid "User deleted"
msgstr ""
#: snikket_web/admin.py:193
msgid "Password reset link not found"
msgstr ""
#: snikket_web/admin.py:205
msgid "Password reset link deleted"
msgstr ""
#: snikket_web/admin.py:225
msgid "Invite to circle"
msgstr ""
#: snikket_web/admin.py:231
msgid "At least one circle must be selected"
msgstr ""
#: snikket_web/admin.py:236
msgid "Valid for"
msgstr ""
#: snikket_web/admin.py:238
msgid "One hour"
msgstr ""
#: snikket_web/admin.py:143
#: snikket_web/admin.py:239
msgid "Twelve hours"
msgstr ""
#: snikket_web/admin.py:144
#: snikket_web/admin.py:240
msgid "One day"
msgstr ""
#: snikket_web/admin.py:145
#: snikket_web/admin.py:241
msgid "One week"
msgstr ""
#: snikket_web/admin.py:146
#: snikket_web/admin.py:242
msgid "Four weeks"
msgstr ""
#: snikket_web/admin.py:152 snikket_web/templates/admin_edit_invite.html:17
#: snikket_web/admin.py:248 snikket_web/templates/admin_edit_invite.html:17
msgid "Invitation type"
msgstr ""
#: snikket_web/admin.py:154 snikket_web/templates/library.j2:116
#: snikket_web/admin.py:250 snikket_web/templates/library.j2:116
msgid "Individual"
msgstr ""
#: snikket_web/admin.py:155 snikket_web/templates/library.j2:114
#: snikket_web/admin.py:251 snikket_web/templates/library.j2:114
msgid "Group"
msgstr ""
#: snikket_web/admin.py:161
#: snikket_web/admin.py:257
msgid "New invitation link"
msgstr ""
#: snikket_web/admin.py:223
#: snikket_web/admin.py:319
msgid "Revoke"
msgstr ""
#: snikket_web/admin.py:283 snikket_web/admin.py:327
#: snikket_web/admin.py:343
msgid "Invitation created"
msgstr ""
#: snikket_web/admin.py:359
msgid "No such invitation exists"
msgstr ""
#: snikket_web/admin.py:374
msgid "Invitation revoked"
msgstr ""
#: snikket_web/admin.py:391 snikket_web/admin.py:439
msgid "Name"
msgstr ""
#: snikket_web/admin.py:288 snikket_web/templates/admin_circles.html:47
#: snikket_web/admin.py:396 snikket_web/templates/admin_circles.html:47
msgid "Create circle"
msgstr ""
#: snikket_web/admin.py:332
#: snikket_web/admin.py:426
msgid "Circle created"
msgstr ""
#: snikket_web/admin.py:444
msgid "Select user"
msgstr ""
#: snikket_web/admin.py:337
#: snikket_web/admin.py:449
msgid "Update circle"
msgstr ""
#: snikket_web/admin.py:341
#: snikket_web/admin.py:453
msgid "Delete circle permanently"
msgstr ""
#: snikket_web/admin.py:347
#: snikket_web/admin.py:459
msgid "Add user"
msgstr ""
#: snikket_web/infra.py:40
#: snikket_web/admin.py:475
msgid "No such circle exists"
msgstr ""
#: snikket_web/admin.py:512
msgid "Circle data updated"
msgstr ""
#: snikket_web/admin.py:518
msgid "Circle deleted"
msgstr ""
#: snikket_web/admin.py:529
msgid "User added to circle"
msgstr ""
#: snikket_web/admin.py:538
msgid "User removed from circle"
msgstr ""
#: snikket_web/infra.py:41
msgid "Main"
msgstr ""
#: snikket_web/invite.py:93
#: snikket_web/invite.py:106
msgid "Username"
msgstr ""
#: snikket_web/invite.py:97 snikket_web/invite.py:164 snikket_web/main.py:41
#: snikket_web/invite.py:110 snikket_web/invite.py:177 snikket_web/main.py:41
msgid "Password"
msgstr ""
#: snikket_web/invite.py:101 snikket_web/invite.py:168
#: snikket_web/invite.py:114 snikket_web/invite.py:181
msgid "Confirm password"
msgstr ""
#: snikket_web/invite.py:105 snikket_web/invite.py:172
msgid "The passwords must match"
#: snikket_web/invite.py:118 snikket_web/invite.py:185
msgid "The passwords must match."
msgstr ""
#: snikket_web/invite.py:110
#: snikket_web/invite.py:123
msgid "Create account"
msgstr ""
#: snikket_web/invite.py:137
msgid "That username is already taken"
#: snikket_web/invite.py:150
msgid "That username is already taken."
msgstr ""
#: snikket_web/invite.py:141 snikket_web/invite.py:205
msgid "Registration was declined for unknown reasons"
#: snikket_web/invite.py:154 snikket_web/invite.py:218
msgid "Registration was declined for unknown reasons."
msgstr ""
#: snikket_web/invite.py:145
msgid "The username is not valid"
#: snikket_web/invite.py:158
msgid "The username is not valid."
msgstr ""
#: snikket_web/invite.py:177 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:32
#: snikket_web/invite.py:190 snikket_web/templates/user_home.html:32
#: snikket_web/templates/user_passwd.html:29
msgid "Change password"
msgstr ""
@@ -146,69 +236,92 @@ msgstr ""
msgid "Sign in"
msgstr ""
#: snikket_web/main.py:72
#: snikket_web/main.py:55
msgid "Invalid username or password."
msgstr ""
#: snikket_web/user.py:21
#: snikket_web/main.py:83
msgid "Login successful!"
msgstr ""
#: snikket_web/user.py:27
msgid "Current password"
msgstr ""
#: snikket_web/user.py:26
#: snikket_web/user.py:32
msgid "New password"
msgstr ""
#: snikket_web/user.py:31
#: snikket_web/user.py:37
msgid "Confirm new password"
msgstr ""
#: snikket_web/user.py:35
msgid "The new passwords must match"
msgstr ""
#: snikket_web/user.py:42
msgid "Sign out"
msgstr ""
#: snikket_web/user.py:47
msgid "Nobody"
#: snikket_web/user.py:41
msgid "The new passwords must match."
msgstr ""
#: snikket_web/user.py:48
msgid "Sign out"
msgstr ""
#: snikket_web/user.py:53
msgid "Nobody"
msgstr ""
#: snikket_web/user.py:54
msgid "Friends only"
msgstr ""
#: snikket_web/user.py:49
#: snikket_web/user.py:55
msgid "Everyone"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:12
#: snikket_web/templates/admin_users.html:11 snikket_web/user.py:55
msgid "Display name"
msgstr ""
#: snikket_web/user.py:59
#: snikket_web/user.py:65
msgid "Avatar"
msgstr ""
#: snikket_web/user.py:63
#: snikket_web/user.py:69
msgid "Profile visibility"
msgstr ""
#: snikket_web/user.py:68
#: snikket_web/user.py:74
msgid "Update profile"
msgstr ""
#: snikket_web/user.py:93
msgid "Incorrect password"
#: snikket_web/user.py:99
msgid "Incorrect password."
msgstr ""
#: snikket_web/templates/_footer.html:4 snikket_web/templates/login.html:36
#: snikket_web/user.py:103
msgid "Password changed"
msgstr ""
#: snikket_web/user.py:111
msgid ""
"The chosen avatar is too big. To be able to upload larger avatars, please"
" use the app."
msgstr ""
#: snikket_web/user.py:159
msgid "Profile updated"
msgstr ""
#: snikket_web/templates/unauth.html:18 snikket_web/user.py:167
msgid "Error"
msgstr ""
#: snikket_web/templates/_footer.html:4
#, python-format
msgid "A <a href=\"%(about_url)s\">Snikket</a> service"
msgstr ""
#: snikket_web/templates/about.html:9
#: snikket_web/templates/_footer.html:6
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community "
"Interest Company."
msgstr ""
#: snikket_web/templates/about.html:4 snikket_web/templates/about.html:9
msgid "About Snikket"
msgstr ""
@@ -257,10 +370,22 @@ msgid ""
msgstr ""
#: snikket_web/templates/about.html:17
msgid "Trademarks"
msgstr ""
#: snikket_web/templates/about.html:18
#, python-format
msgid ""
"“Snikket” and the parrot logo are trademarks of Snikket Community "
"Interest Company. For more information about the trademarks, visit the <a"
" href=\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
msgstr ""
#: snikket_web/templates/about.html:19
msgid "Software Versions"
msgstr ""
#: snikket_web/templates/about.html:27
#: snikket_web/templates/about.html:29
msgid "Back to the main page"
msgstr ""
@@ -296,7 +421,7 @@ msgstr ""
#: snikket_web/templates/admin_circles.html:15
#: snikket_web/templates/admin_invites.html:24
#: snikket_web/templates/admin_users.html:12
#: snikket_web/templates/admin_users.html:10
msgid "Actions"
msgstr ""
@@ -350,7 +475,7 @@ msgid "Debug information for %(user_name)s"
msgstr ""
#: snikket_web/templates/admin_debug_user.html:11
#: snikket_web/templates/user_passwd.html:26
#: snikket_web/templates/user_passwd.html:23
msgid "Warning"
msgstr ""
@@ -367,12 +492,12 @@ msgid "Copy complete output"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:4
#: snikket_web/templates/admin_users.html:22
#, python-format
msgid "Delete user %(user_name)s"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:6
#: snikket_web/templates/admin_edit_user.html:54
msgid "Delete user"
msgstr ""
@@ -380,11 +505,6 @@ msgstr ""
msgid "Are you sure you want to delete the following user?"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:10
#: snikket_web/templates/admin_users.html:10
msgid "Login name"
msgstr ""
#: snikket_web/templates/admin_delete_user.html:15
msgid "Danger"
msgstr ""
@@ -397,12 +517,10 @@ msgid ""
msgstr ""
#: snikket_web/templates/admin_delete_user.html:19
#: snikket_web/templates/admin_edit_circle.html:44
#: snikket_web/templates/admin_edit_invite.html:49
#: snikket_web/templates/admin_reset_user_password.html:25
#: snikket_web/templates/user_logout.html:13
#: snikket_web/templates/user_passwd.html:30
#: snikket_web/templates/user_profile.html:31
#: snikket_web/templates/user_logout.html:10
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_profile.html:32
msgid "Back"
msgstr ""
@@ -434,6 +552,10 @@ msgstr ""
msgid "This circle has no group chat associated."
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:44
msgid "Return to circle list"
msgstr ""
#: snikket_web/templates/admin_edit_circle.html:48
msgid "Delete circle"
msgstr ""
@@ -476,7 +598,6 @@ msgid "View invitation"
msgstr ""
#: snikket_web/templates/admin_edit_invite.html:13
#: snikket_web/templates/admin_invites.html:21
#: snikket_web/templates/admin_reset_user_password.html:15
msgid "Valid until"
msgstr ""
@@ -517,6 +638,85 @@ msgstr ""
msgid "Created"
msgstr ""
#: snikket_web/templates/admin_edit_invite.html:48
msgid "Return to invitation list"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:5
msgid ""
"Limited users can interact with users on the same Snikket service and be "
"members of circles."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:7
msgid ""
"Like limited users and can also interact with users on other Snikket "
"services."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:9
msgid "Like normal users and can access the admin panel in the web portal."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:20
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Edit user %(user_name)s"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:23
msgid "Edit user"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:27
msgid "The login name cannot be changed."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:34
msgid ""
"The access level of a user determines what interactions are allowed for "
"them on your Snikket service."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:41
#, python-format
msgid "<strong>%(title)s%(icon)s</strong><p>%(description)s</p>"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:51
msgid "Return to user list"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:59
msgid "Further actions"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:61
msgid "Reset password"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:64
msgid ""
"If the user has lost their password, you can use the button below to "
"create a special link which allows to change the password of the account,"
" once."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:69
msgid "Debug information"
msgstr ""
#: snikket_web/templates/admin_edit_user.html:71
msgid ""
"In some cases, extended information about the user account and the "
"connected devices is necessary to troubleshoot issues. The button below "
"reveals this (sensitive) information."
msgstr ""
#: snikket_web/templates/admin_edit_user.html:75
msgid "Show debug information"
msgstr ""
#: snikket_web/templates/admin_home.html:4
msgid "Welcome to the admin panel!"
msgstr ""
@@ -568,6 +768,10 @@ msgstr ""
msgid "Pending invitations"
msgstr ""
#: snikket_web/templates/admin_invites.html:21
msgid "Expires"
msgstr ""
#: snikket_web/templates/admin_invites.html:22
msgid "Type"
msgstr ""
@@ -607,14 +811,20 @@ msgstr ""
msgid "Destroy link"
msgstr ""
#: snikket_web/templates/admin_users.html:25
#, python-format
msgid "Show debug information for %(user_name)s"
#: snikket_web/templates/admin_users.html:19
msgid "The user is an administrator."
msgstr ""
#: snikket_web/templates/admin_users.html:28
#, python-format
msgid "Create password reset link for %(user_name)s"
#: snikket_web/templates/admin_users.html:19
msgid " (Administrator)"
msgstr ""
#: snikket_web/templates/admin_users.html:22
msgid "The user is restricted."
msgstr ""
#: snikket_web/templates/admin_users.html:22
msgid " (Restricted)"
msgstr ""
#: snikket_web/templates/app.html:4
@@ -649,7 +859,7 @@ msgid "The web portal encountered an internal error."
msgstr ""
#: snikket_web/templates/invite_invalid.html:5
#: snikket_web/templates/invite_view.html:12
#: snikket_web/templates/invite_view.html:13
#, python-format
msgid "Invite to %(site_name)s"
msgstr ""
@@ -657,7 +867,7 @@ msgstr ""
#: snikket_web/templates/invite_invalid.html:6
#: snikket_web/templates/invite_register.html:10
#: snikket_web/templates/invite_success.html:11
#: snikket_web/templates/invite_view.html:13
#: snikket_web/templates/invite_view.html:14
#, python-format
msgid "Powered by <img src=\"%(logo_url)s\" alt=\"Snikket\">"
msgstr ""
@@ -698,18 +908,20 @@ msgid ""
msgstr ""
#: snikket_web/templates/invite_register.html:14
#: snikket_web/templates/invite_view.html:37
#: snikket_web/templates/invite_view.html:39
msgid "App already installed?"
msgstr ""
#: snikket_web/templates/invite_register.html:16
#: snikket_web/templates/invite_reset_view.html:21
#: snikket_web/templates/invite_view.html:39
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:106
#: snikket_web/templates/invite_view.html:134
msgid "Open the app"
msgstr ""
#: snikket_web/templates/invite_register.html:18
#: snikket_web/templates/invite_view.html:41
#: snikket_web/templates/invite_view.html:43
msgid "This button works only if you have the app installed already!"
msgstr ""
@@ -795,7 +1007,7 @@ msgid ""
msgstr ""
#: snikket_web/templates/invite_reset_view.html:26
#: snikket_web/templates/invite_view.html:75
#: snikket_web/templates/invite_view.html:77
msgid ""
"Your camera will turn on. Point it at the square code below until it is "
"within the highlighted square on your screen, and wait until the app "
@@ -807,7 +1019,7 @@ msgid "You will then be prompted to enter a new password for your account."
msgstr ""
#: snikket_web/templates/invite_reset_view.html:29
#: snikket_web/templates/invite_view.html:43
#: snikket_web/templates/invite_view.html:45
msgid "Alternatives"
msgstr ""
@@ -848,29 +1060,29 @@ msgstr ""
msgid "Invite to %(site_name)s | Snikket"
msgstr ""
#: snikket_web/templates/invite_view.html:15
#: snikket_web/templates/invite_view.html:16
#, python-format
msgid ""
"You have been invited to chat with %(inviter_name)s using Snikket, a "
"secure, privacy-friendly chat app on %(site_name)s."
msgstr ""
#: snikket_web/templates/invite_view.html:17
#: snikket_web/templates/invite_view.html:18
#, python-format
msgid ""
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
"privacy-friendly chat app."
msgstr ""
#: snikket_web/templates/invite_view.html:19
#: snikket_web/templates/invite_view.html:20
msgid "Get started"
msgstr ""
#: snikket_web/templates/invite_view.html:21
#: snikket_web/templates/invite_view.html:22
msgid "Install the Snikket App on your Android or iOS device."
msgstr ""
#: snikket_web/templates/invite_view.html:23
#: snikket_web/templates/invite_view.html:24
#, python-format
msgid ""
"Install the Snikket App on your Android device (<a "
@@ -878,25 +1090,30 @@ msgid ""
"target=\"_blank\">iOS coming soon!</a>)."
msgstr ""
#: snikket_web/templates/invite_view.html:27
#: snikket_web/templates/invite_view.html:28
msgid "Get it on Google Play"
msgstr ""
#: snikket_web/templates/invite_view.html:29
#: snikket_web/templates/invite_view.html:30
#: snikket_web/templates/invite_view.html:102
msgid "Download on the App Store"
msgstr ""
#: snikket_web/templates/invite_view.html:33
msgid "Not on mobile?"
#: snikket_web/templates/invite_view.html:32
msgid "Get it on F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:36
#: snikket_web/templates/invite_view.html:35
msgid "Send to mobile device"
msgstr ""
#: snikket_web/templates/invite_view.html:38
msgid ""
"After installation the app should automatically open and prompt you to "
"create an account. If not, simply click the button below."
msgstr ""
#: snikket_web/templates/invite_view.html:44
#: snikket_web/templates/invite_view.html:46
#, python-format
msgid ""
"You can connect to Snikket using any XMPP-compatible software. If the "
@@ -904,42 +1121,82 @@ msgid ""
"href=\"%(register_url)s\">register an account manually</a>."
msgstr ""
#: snikket_web/templates/invite_view.html:50
#: snikket_web/templates/invite_view.html:52
msgid "Scan invite code"
msgstr ""
#: snikket_web/templates/invite_view.html:53
#: snikket_web/templates/invite_view.html:82
#: snikket_web/templates/invite_view.html:55
#: snikket_web/templates/invite_view.html:84
#: snikket_web/templates/invite_view.html:96
#: snikket_web/templates/invite_view.html:112
#: snikket_web/templates/invite_view.html:124
#: snikket_web/templates/invite_view.html:140
msgid "Close"
msgstr ""
#: snikket_web/templates/invite_view.html:56
#: snikket_web/templates/invite_view.html:58
msgid ""
"You can transfer this invite to your mobile device by scanning a code "
"with your camera. You can use either a QR scanner app or the Snikket app "
"itself."
msgstr ""
#: snikket_web/templates/invite_view.html:61
#: snikket_web/templates/invite_view.html:63
msgid "Using a QR code scanner"
msgstr ""
#: snikket_web/templates/invite_view.html:63
#: snikket_web/templates/invite_view.html:65
msgid "Using the Snikket app"
msgstr ""
#: snikket_web/templates/invite_view.html:68
#: snikket_web/templates/invite_view.html:70
msgid ""
"Use a <em>QR code</em> scanner on your mobile device to scan the code "
"below:"
msgstr ""
#: snikket_web/templates/invite_view.html:74
#: snikket_web/templates/invite_view.html:76
msgid ""
"Install the Snikket app on your mobile device, open it, and tap the "
"'Scan' button at the top."
msgstr ""
#: snikket_web/templates/invite_view.html:93
msgid "Install on iOS"
msgstr ""
#: snikket_web/templates/invite_view.html:99
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 ""
#: snikket_web/templates/invite_view.html:101
msgid "First download Snikket from the App Store using the button below:"
msgstr ""
#: snikket_web/templates/invite_view.html:103
#: snikket_web/templates/invite_view.html:131
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 ""
#: snikket_web/templates/invite_view.html:121
#: snikket_web/templates/invite_view.html:130
msgid "Install via F-Droid"
msgstr ""
#: snikket_web/templates/invite_view.html:127
msgid ""
"After installing Snikket via F-Droid, you have to return to this invite "
"link and tap on \"Open the app\" to proceed."
msgstr ""
#: snikket_web/templates/invite_view.html:129
msgid "First install Snikket from F-Droid using the button below:"
msgstr ""
#: snikket_web/templates/library.j2:18
msgid "Copy link"
msgstr ""
@@ -968,8 +1225,19 @@ msgstr ""
msgid "Enter your Snikket address and password to manage your account."
msgstr ""
#: snikket_web/templates/login.html:18
msgid "Login failed"
#: snikket_web/templates/login.html:19
msgid "Incorrect address"
msgstr ""
#: snikket_web/templates/login.html:20
#, python-format
msgid ""
"This Snikket service only hosts addresses ending in "
"<em>@%(snikket_domain)s</em>. Your password was not sent."
msgstr ""
#: snikket_web/templates/unauth.html:16
msgid "Operation successful"
msgstr ""
#: snikket_web/templates/user_home.html:9
@@ -1005,46 +1273,46 @@ msgstr ""
msgid "Admin panel"
msgstr ""
#: snikket_web/templates/user_logout.html:8
#: snikket_web/templates/user_logout.html:5
msgid "Sign out of the Snikket Web Portal"
msgstr ""
#: snikket_web/templates/user_logout.html:9
#: snikket_web/templates/user_logout.html:6
msgid ""
"Click below to log yourself out of the web portal. This does not affect "
"any other connected devices."
msgstr ""
#: snikket_web/templates/user_passwd.html:8
#: snikket_web/templates/user_passwd.html:5
msgid "Change your password"
msgstr ""
#: snikket_web/templates/user_passwd.html:9
#: snikket_web/templates/user_passwd.html:6
msgid ""
"To change your password, you need to provide the current password as well"
" as the new one. To reduce the chance of typos, we ask for your new "
"password twice."
msgstr ""
#: snikket_web/templates/user_passwd.html:27
#: snikket_web/templates/user_passwd.html:24
msgid ""
"After changing your password, you will have to enter the new password on "
"all of your devices."
msgstr ""
#: snikket_web/templates/user_profile.html:7
#: snikket_web/templates/user_profile.html:4
msgid "Update your profile"
msgstr ""
#: snikket_web/templates/user_profile.html:9
#: snikket_web/templates/user_profile.html:6
msgid "Profile"
msgstr ""
#: snikket_web/templates/user_profile.html:22
#: snikket_web/templates/user_profile.html:23
msgid "Visibility"
msgstr ""
#: snikket_web/templates/user_profile.html:23
#: snikket_web/templates/user_profile.html:24
msgid ""
"This section allows you to control who can see your profile information, "
"like avatar and nickname."

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,29 @@
import asyncio
import typing
import quart
import quart.flask_patch
from quart import Blueprint, render_template, request, redirect, url_for
import quart.exceptions
from quart import (
Blueprint,
render_template,
request,
redirect,
url_for,
flash,
current_app,
)
import werkzeug.exceptions
import wtforms
import flask_wtf
from flask_babel import lazy_gettext as _l, _
from .infra import client
from .infra import client, BaseForm
bp = Blueprint('user', __name__)
class ChangePasswordForm(flask_wtf.FlaskForm): # type:ignore
class ChangePasswordForm(BaseForm):
current_password = wtforms.PasswordField(
_l("Current password"),
validators=[wtforms.validators.InputRequired()]
@@ -32,12 +39,12 @@ class ChangePasswordForm(flask_wtf.FlaskForm): # type:ignore
validators=[wtforms.validators.InputRequired(),
wtforms.validators.EqualTo(
"new_password",
_l("The new passwords must match")
_l("The new passwords must match.")
)]
)
class LogoutForm(flask_wtf.FlaskForm): # type:ignore
class LogoutForm(BaseForm):
action_signout = wtforms.SubmitField(
_l("Sign out"),
)
@@ -50,7 +57,7 @@ _ACCESS_MODEL_CHOICES = [
]
class ProfileForm(flask_wtf.FlaskForm): # type:ignore
class ProfileForm(BaseForm):
nickname = wtforms.TextField(
_l("Display name"),
)
@@ -69,14 +76,14 @@ class ProfileForm(flask_wtf.FlaskForm): # type:ignore
)
@bp.route("/")
@bp.route("/") # type:ignore
@client.require_session()
async def index() -> str:
user_info = await client.get_user_info()
return await render_template("user_home.html", user_info=user_info)
@bp.route('/passwd', methods=["GET", "POST"])
@bp.route('/passwd', methods=["GET", "POST"]) # type:ignore
@client.require_session()
async def change_pw() -> typing.Union[str, quart.Response]:
form = ChangePasswordForm()
@@ -86,21 +93,33 @@ async def change_pw() -> typing.Union[str, quart.Response]:
form.current_password.data,
form.new_password.data,
)
except (quart.exceptions.Unauthorized,
quart.exceptions.Forbidden):
except (werkzeug.exceptions.Unauthorized,
werkzeug.exceptions.Forbidden):
# server refused current password, set an appropriate error
form.current_password.errors.append(
_("Incorrect password"),
_("Incorrect password."),
)
else:
await flash(
_("Password changed"),
"success",
)
return redirect(url_for("user.change_pw"))
return await render_template("user_passwd.html", form=form)
@bp.route("/profile", methods=["GET", "POST"])
EAVATARTOOBIG = _l(
"The chosen avatar is too big. To be able to upload larger "
"avatars, please use the app."
)
@bp.route("/profile", methods=["GET", "POST"]) # type:ignore
@client.require_session()
async def profile() -> typing.Union[str, quart.Response]:
max_avatar_size = current_app.config["MAX_AVATAR_SIZE"]
form = ProfileForm()
if request.method != "POST":
user_info = await client.get_user_info()
@@ -114,34 +133,54 @@ async def profile() -> typing.Union[str, quart.Response]:
if form.validate_on_submit():
user_info = await client.get_user_info()
ok = True
file_info = (await request.files).get(form.avatar.name)
if file_info is not None:
mimetype = file_info.mimetype
data = file_info.stream.read()
if len(data) > 0:
if len(data) > max_avatar_size:
print(len(data), max_avatar_size)
form.avatar.errors.append(EAVATARTOOBIG)
ok = False
elif len(data) > 0:
await client.set_user_avatar(data, mimetype)
if user_info.get("nickname") != form.nickname.data:
await client.set_user_nickname(form.nickname.data)
if ok:
if user_info.get("nickname") != form.nickname.data:
await client.set_user_nickname(form.nickname.data)
access_model = form.profile_access_model.data
await asyncio.gather(
client.set_avatar_access_model(access_model),
client.set_vcard_access_model(access_model),
client.set_nickname_access_model(access_model),
)
access_model = form.profile_access_model.data
await asyncio.gather(
client.set_avatar_access_model(access_model),
client.set_vcard_access_model(access_model),
client.set_nickname_access_model(access_model),
)
return redirect(url_for(".profile"))
await flash(
_("Profile updated"),
"success",
)
return redirect(url_for(".profile"))
return await render_template("user_profile.html", form=form)
return await render_template("user_profile.html",
form=form,
max_avatar_size=max_avatar_size,
avatar_too_big_warning_header=_l("Error"),
avatar_too_big_warning=EAVATARTOOBIG)
@bp.route("/logout", methods=["GET", "POST"])
@bp.route("/logout", methods=["GET", "POST"]) # type:ignore
@client.require_session()
async def logout() -> typing.Union[quart.Response, str]:
form = LogoutForm()
if form.validate_on_submit():
await client.logout()
# No flashing here because we dont collect flashes in the login page
# and itd be weird.
# await flash(
# _("Logged out"),
# "success",
# )
return redirect(url_for("main.index"))
return await render_template("user_logout.html", form=form)

View File

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

View File

@@ -5,6 +5,7 @@ action/delete:delete
action/logout:logout
action/login:login
action/exit_to_app:exit_to_app
action/lock:lock
communication/qr_code:qrcode
communication/vpn_key:passwd
content/add_circle_outline:add