From 884e6070e80ce19e38382b1013b9375bd66f9049 Mon Sep 17 00:00:00 2001 From: Julian Rother <julianr@fsmpi.rwth-aachen.de> Date: Fri, 30 Jul 2021 14:26:56 +0200 Subject: [PATCH] Completed German translation --- uffd/default_config.cfg | 2 +- uffd/invite/templates/invite/list.html | 60 +- uffd/invite/templates/invite/new.html | 26 +- uffd/invite/templates/invite/use.html | 18 +- uffd/invite/views.py | 26 +- uffd/mail/templates/mail/list.html | 8 +- uffd/mail/templates/mail/show.html | 18 +- uffd/mail/views.py | 7 +- uffd/oauth2/templates/oauth2/logout.html | 16 +- uffd/oauth2/views.py | 5 +- uffd/ratelimit.py | 13 +- uffd/role/templates/role/list.html | 10 +- uffd/role/templates/role/show.html | 6 +- uffd/rolemod/templates/rolemod/list.html | 4 +- uffd/rolemod/templates/rolemod/show.html | 22 +- uffd/rolemod/views.py | 13 +- .../templates/selfservice/self.html | 6 +- uffd/selfservice/views.py | 26 +- .../session/templates/session/deviceauth.html | 24 +- .../templates/session/devicelogin.html | 14 +- uffd/session/templates/session/login.html | 8 +- uffd/session/views.py | 14 +- uffd/signup/templates/signup/confirm.html | 6 +- uffd/signup/templates/signup/start.html | 32 +- uffd/signup/templates/signup/submitted.html | 6 +- uffd/signup/views.py | 24 +- uffd/templates/base.html | 6 +- uffd/translations/de/LC_MESSAGES/messages.mo | Bin 14883 -> 30649 bytes uffd/translations/de/LC_MESSAGES/messages.po | 1347 +++++++++++++---- uffd/user/templates/group/list.html | 6 +- uffd/user/templates/group/show.html | 4 +- uffd/user/templates/user/list.html | 14 +- uffd/user/templates/user/show.html | 30 +- 33 files changed, 1297 insertions(+), 524 deletions(-) diff --git a/uffd/default_config.cfg b/uffd/default_config.cfg index 23b755b5..e56c564d 100644 --- a/uffd/default_config.cfg +++ b/uffd/default_config.cfg @@ -56,7 +56,7 @@ SESSION_COOKIE_SAMESITE='Strict' LANGUAGES={ # Language identifier (see Accept-Language HTTP header) -> Display Name "en": "EN", - #"de": "DE", # Too incomplete right now to enable per default + "de": "DE", # Too incomplete right now to enable per default } ACL_ADMIN_GROUP="uffd_admin" diff --git a/uffd/invite/templates/invite/list.html b/uffd/invite/templates/invite/list.html index 00fd0ee4..6099b21d 100644 --- a/uffd/invite/templates/invite/list.html +++ b/uffd/invite/templates/invite/list.html @@ -3,17 +3,17 @@ {% block body %} <div class="btn-toolbar mb-2"> - <a class="btn btn-primary ml-auto" href="{{ url_for("invite.new") }}"><i class="fa fa-plus" aria-hidden="true"></i> New</a> + <a class="btn btn-primary ml-auto" href="{{ url_for("invite.new") }}"><i class="fa fa-plus" aria-hidden="true"></i> {{_('New')}}</a> </div> <div class="table-responsive"> <table class="table"> <thead> <tr> - <th scope="col">Link</th> - <th scope="col">Created by</th> - <th scope="col">Permissions</th> - <th scope="col">Usages</th> - <th scope="col">Status</th> + <th scope="col">{{_('Link')}}</th> + <th scope="col">{{_('Created by')}}</th> + <th scope="col">{{_('Permissions')}}</th> + <th scope="col">{{_('Usages')}}</th> + <th scope="col">{{_('Status')}}</th> <th scope="col"></th> </tr> </thead> @@ -23,8 +23,8 @@ <td> {% if invite.creator == request.user and invite.active %} <a href="{{ url_for('invite.use', token=invite.token) }}"><code>{{ invite.short_token }}</code></a> - <button type="button" class="btn btn-link btn-sm p-0 copy-clipboard" data-copy="{{ url_for('invite.use', token=invite.token, _external=True) }}" title="Copy link to clipboard"><i class="fas fa-clipboard"></i></button> - <button type="button" class="btn btn-link btn-sm p-0" data-toggle="modal" data-target="#modal-{{ invite.id }}-qrcode" title="Show link as QR code"><i class="fas fa-qrcode"></i></button> + <button type="button" class="btn btn-link btn-sm p-0 copy-clipboard" data-copy="{{ url_for('invite.use', token=invite.token, _external=True) }}" title="{{_('Copy link to clipboard')}}"><i class="fas fa-clipboard"></i></button> + <button type="button" class="btn btn-link btn-sm p-0" data-toggle="modal" data-target="#modal-{{ invite.id }}-qrcode" title="{{_('Show link as QR code')}}"><i class="fas fa-qrcode"></i></button> {% else %} <code>{{ invite.short_token }}</code> {% endif %} @@ -39,7 +39,7 @@ {% endif %} </td> <td> - {{ 'Signup' if invite.allow_signup }}{{ ', ' if invite.allow_signup and invite.roles }} + {{ _('Signup') if invite.allow_signup }}{{ ', ' if invite.allow_signup and invite.roles }} {% for role in invite.roles %}{{ ', ' if loop.index != 1 }}<i class="fas fa-key"></i> {{ role.name }}{% endfor %} </td> <td> @@ -48,19 +48,19 @@ </td> <td> {% if invite.disabled %} - Disabled + {{_('Disabled')}} {% elif invite.voided %} - Voided + {{_('Voided')}} {% elif invite.expired %} - Expired + {{_('Expired')}} {% elif not invite.permitted %} - Invalid, unpermitted creator + {{_('Invalid, unpermitted creator')}} {% elif not invite.active %} - Invalid + {{_('Invalid')}} {% elif invite.single_use %} - Valid once, expires {{ invite.valid_until.strftime('%Y-%m-%d') }} + {{ _('Valid once, expires %(expiry_date)s', expiry_date=invite.valid_until.strftime('%Y-%m-%d')) }} {% else %} - Valid, expires {{ invite.valid_until.strftime('%Y-%m-%d') }} + {{ _('Valid, expires %(expiry_date)s',expiry_date=invite.valid_until.strftime('%Y-%m-%d')) }} {% endif %} </td> <td class="text-right"> @@ -77,38 +77,38 @@ <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> - <h5 class="modal-title">Invite Link Details</h5> + <h5 class="modal-title">{{_('Invite Link Details')}}</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <ul class="list-unstyled"> - <li><b>Type:</b> {% if invite.single_use %}Single-use{% else %}Multi-use{% endif %}</li> - <li><b>Created:</b> {{ invite.created.strftime('%Y-%m-%d %H:%M:%S') }}</li> - <li><b>Expires:</b> {{ invite.valid_until.strftime('%Y-%m-%d %H:%M:%S') }}</li> - <li><b>Permissions:</b> + <li><b>{{_('Type:')}}</b> {% if invite.single_use %}{{_('Single-use')}}{% else %}{{_('Multi-use')}}{% endif %}</li> + <li><b>{{_('Created:')}}</b> {{ invite.created.strftime('%Y-%m-%d %H:%M:%S') }}</li> + <li><b>{{_('Expires:')}}</b> {{ invite.valid_until.strftime('%Y-%m-%d %H:%M:%S') }}</li> + <li><b>{{_('Permissions:')}}</b> <ul> {% if invite.allow_signup %} - <li>Link allows account registration</li> + <li>{{_('Link allows account registration')}}</li> {% else %} - <li>No account registration allowed</li> + <li>{{_('No account registration allowed')}}</li> {% endif %} {% for role in invite.roles %} - <li>Link grants users the role "{{ role.name }}"</li> + <li>{{_('Link grants users the role "%(name)s"', name=role.name)}}</li> {% endfor %} </ul> </li> <li><b>Usages:</b> {% if not invite.signups and not invite.grants %} - Never used + {{_('Never used')}} {% else %} <ul> {% for signup in invite.signups if signup.completed %} - <li>Registration of user <a href="{{ url_for('user.show', uid=signup.user.uid) }}">{{ signup.user.loginname }}</a></li> + <li>{{_('Registration of user <a href="%(user_url)s">%(user_name)s</a>', user_url=url_for('user.show', uid=signup.user.uid)|e, user_name=signup.user.loginname|e)|safe}}</li> {% endfor %} {% for grant in invite.grants if grant.user %} - <li>Roles granted to <a href="{{ url_for('user.show', uid=grant.user.uid) }}">{{ grant.user.loginname }}</a></li> + <li>{{_('Roles granted to <a href="%(user_url)s">%(user_name)s</a>', user_url=url_for('user.show', uid=grant.user.uid)|e, user_name=grant.user.loginname|e)}}|safe</li> {% endfor %} </ul> {% endif %} @@ -119,11 +119,11 @@ <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> {% if invite.active %} <form action="{{ url_for('invite.disable', invite_id=invite.id) }}" method="POST"> - <button type="submit" class="btn btn-primary">Disable Link</button> + <button type="submit" class="btn btn-primary">{{_('Disable Link')}}</button> </form> {% elif invite.creator == request.user and not invite.expired and invite.permitted %} <form action="{{ url_for('invite.reset', invite_id=invite.id) }}" method="POST"> - <button type="submit" class="btn btn-primary">Reenable Link</button> + <button type="submit" class="btn btn-primary">{{_('Reenable Link')}}</button> </form> {% endif %} </div> @@ -137,7 +137,7 @@ <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> - <h5 class="modal-title">Invite</h5> + <h5 class="modal-title">{{_('Invite')}}</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> diff --git a/uffd/invite/templates/invite/new.html b/uffd/invite/templates/invite/new.html index c6731bd8..5c69f52e 100644 --- a/uffd/invite/templates/invite/new.html +++ b/uffd/invite/templates/invite/new.html @@ -3,23 +3,23 @@ {% block body %} <form action="{{ url_for("invite.new_submit") }}" method="POST" class="form"> <div class="form-group"> - <label for="single-use">Link Type</label> + <label for="single-use">{{_('Link Type')}}</label> <select class="form-control" id="single-use" name="single-use"> - <option value="1">Valid for a single successful use</option> - <option value="0">Multi-use</option> + <option value="1">{{_('Valid for a single successful use')}}</option> + <option value="0">{{_('Multi-use')}}</option> </select> </div> <div class="form-group"> - <label for="valid-until">Valid Until</label> + <label for="valid-until">{{_('Valid Until')}}</label> <input class="form-control" type="datetime-local" id="valid-until" name="valid-until" value="{{ (datetime.now() + timedelta(hours=36)).replace(hour=23, minute=59, second=59, microsecond=0).isoformat(timespec='minutes') }}"> - <small class="text-muted">Must be within the next {{ config['INVITE_MAX_VALID_DAYS'] }} days</small> + <small class="text-muted">{{_('Must be within the next %(max_valid_days)d days', max_valid_days=config['INVITE_MAX_VALID_DAYS'])}}</small> </div> {% if allow_signup %} <div class="form-group"> - <label for="allow-signup">Account Registration</label> + <label for="allow-signup">{{_('Account Registration')}}</label> <select class="form-control" id="allow-signup" name="allow-signup"> - <option value="1">Link allows account registration</option> - <option value="0">No account registration allowed</option> + <option value="1">{{_('Link allows account registration')}}</option> + <option value="0">{{_('No account registration allowed')}}</option> </select> </div> {% else %} @@ -27,13 +27,13 @@ {% endif %} {% if roles %} <div class="form-group"> - <label for="valid-until">Granted Roles</label> + <label for="valid-until">{{_('Granted Roles')}}</label> <table class="table table-sm"> <thead> <tr> <th scope="col"></th> - <th scope="col">Name</th> - <th scope="col">Description</th> + <th scope="col">{{_('Name')}}</th> + <th scope="col">{{_('Description')}}</th> </tr> </thead> <tbody> @@ -52,7 +52,7 @@ </table> </div> {% endif %} - <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Create Link</button> - <a href="{{ url_for("invite.index") }}" class="btn btn-secondary">Cancel</a> + <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Create Link')}}</button> + <a href="{{ url_for("invite.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a> </form> {% endblock %} diff --git a/uffd/invite/templates/invite/use.html b/uffd/invite/templates/invite/use.html index c852f3db..344b8b03 100644 --- a/uffd/invite/templates/invite/use.html +++ b/uffd/invite/templates/invite/use.html @@ -8,18 +8,18 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12 mb-3"> - <h2 class="text-center">Invite Link</h2> + <h2 class="text-center">{{_('Invite Link')}}</h2> </div> {% if not request.user %} <p>Welcome to the CCCV Single-Sign-On!</p> {% endif %} {% if invite.roles and invite.allow_signup %} - <p>With this link you can register a new user account with the following roles or add the roles to an existing account:</p> + <p>{{_('With this link you can register a new user account with the following roles or add the roles to an existing account:')}}</p> {% elif invite.roles %} - <p>With this link you can add the following roles to an existing account:</p> + <p>{{_('With this link you can add the following roles to an existing account:')}}</p> {% elif invite.allow_signup %} - <p>With this link you can register a new user account.</p> + <p>{{_('With this link you can register a new user account.')}}</p> {% endif %} {% if invite.roles %} <ul> @@ -31,19 +31,19 @@ {% if request.user %} {% if invite.roles %} <form method="POST" action="{{ url_for("invite.grant", token=invite.token) }}" class="mb-2"> - <button type="submit" class="btn btn-primary btn-block">Add the roles to your account now</button> + <button type="submit" class="btn btn-primary btn-block">{{_('Add the roles to your account now')}}</button> </form> - <a href="{{ url_for("session.logout", ref=url_for("session.login", ref=request.full_path)) }}" class="btn btn-secondary btn-block">Logout and switch to a different account</a> + <a href="{{ url_for("session.logout", ref=url_for("session.login", ref=request.full_path)) }}" class="btn btn-secondary btn-block">{{_('Logout and switch to a different account')}}</a> {% endif %} {% if invite.allow_signup %} - <a href="{{ url_for("session.logout", ref=url_for("invite.signup_start", token=invite.token)) }}" class="btn btn-secondary btn-block">Logout to register a new account</a> + <a href="{{ url_for("session.logout", ref=url_for("invite.signup_start", token=invite.token)) }}" class="btn btn-secondary btn-block">{{_('Logout to register a new account')}}</a> {% endif %} {% else %} {% if invite.allow_signup %} - <a href="{{ url_for("invite.signup_start", token=invite.token) }}" class="btn btn-primary btn-block">Register a new account</a> + <a href="{{ url_for("invite.signup_start", token=invite.token) }}" class="btn btn-primary btn-block">{{_('Register a new account')}}</a> {% endif %} {% if invite.roles %} - <a href="{{ url_for("session.login", ref=request.full_path) }}" class="btn btn-primary btn-block">Login and add the roles to your account</a> + <a href="{{ url_for("session.login", ref=request.full_path) }}" class="btn btn-primary btn-block">{{_('Login and add the roles to your account')}}</a> {% endif %} {% endif %} </div> diff --git a/uffd/invite/views.py b/uffd/invite/views.py index ece81c64..7d1887be 100644 --- a/uffd/invite/views.py +++ b/uffd/invite/views.py @@ -2,6 +2,7 @@ import datetime import functools from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, jsonify +from flask_babel import gettext as _, lazy_gettext import sqlalchemy from uffd.csrf import csrf_protect @@ -52,7 +53,7 @@ def reset_acl_filter(user): return Invite.creator_dn == user.dn @bp.route('/') -@register_navbar('Invites', icon='link', blueprint=bp, visible=invite_acl) +@register_navbar(lazy_gettext('Invites'), icon='link', blueprint=bp, visible=invite_acl) @invite_acl_required def index(): invites = Invite.query.filter(view_acl_filter(request.user)).all() @@ -81,13 +82,13 @@ def new_submit(): if key.startswith('role-') and value == '1': invite.roles.append(Role.query.get(key[5:])) if invite.valid_until > datetime.datetime.now() + datetime.timedelta(days=current_app.config['INVITE_MAX_VALID_DAYS']): - flash('The "Expires After" date is too far in the future') + flash(_('The "Expires After" date is too far in the future')) return redirect(url_for('invite.new')) if not invite.permitted: - flash('You are not allowed to create invite links with these permissions') + flash(_('You are not allowed to create invite links with these permissions')) return redirect(url_for('invite.new')) if not invite.allow_signup and not invite.roles: - flash('Invite link must either allow signup or grant at least one role') + flash(_('Invite link must either allow signup or grant at least one role')) return redirect(url_for('invite.new')) db.session.add(invite) db.session.commit() @@ -115,7 +116,7 @@ def reset(invite_id): def use(token): invite = Invite.query.filter_by(token=token).first_or_404() if not invite.active: - flash('Invalid invite link') + flash(_('Invalid invite link')) return redirect('/') return render_template('invite/use.html', invite=invite) @@ -132,7 +133,7 @@ def grant(token): return redirect(url_for('selfservice.index')) ldap.session.commit() db.session.commit() - flash('Roles successfully updated') + flash(_('Roles successfully updated')) return redirect(url_for('selfservice.index')) @bp.url_defaults @@ -144,10 +145,10 @@ def inject_invite_token(endpoint, values): def signup_start(token): invite = Invite.query.filter_by(token=token).first_or_404() if not invite.active: - flash('Invalid invite link') + flash(_('Invalid invite link')) return redirect('/') if not invite.allow_signup: - flash('Invite link does not allow signup') + flash(_('Invite link does not allow signup')) return redirect('/') return render_template('signup/start.html') @@ -169,13 +170,14 @@ def signup_check(token): def signup_submit(token): invite = Invite.query.filter_by(token=token).first_or_404() if request.form['password1'] != request.form['password2']: - return render_template('signup/start.html', error='Passwords do not match') + return render_template('signup/start.html', error=_('Passwords do not match')) signup_delay = signup_ratelimit.get_delay(request.form['mail']) host_delay = host_ratelimit.get_delay() if signup_delay and signup_delay > host_delay: - return render_template('signup/start.html', error='Too many signup requests with this mail address! Please wait %s.'%format_delay(signup_delay)) + return render_template('signup/start.html', error=_('Too many signup requests with this mail address! Please wait %(delay)s.', + delay=format_delay(signup_delay))) if host_delay: - return render_template('signup/start.html', error='Too many requests! Please wait %s.'%format_delay(host_delay)) + return render_template('signup/start.html', error=_('Too many requests! Please wait %(delay)s.', delay=format_delay(host_delay))) host_ratelimit.log() signup = InviteSignup(invite=invite, loginname=request.form['loginname'], displayname=request.form['displayname'], @@ -188,6 +190,6 @@ def signup_submit(token): db.session.commit() sent = sendmail(signup.mail, 'Confirm your mail address', 'signup/mail.txt', signup=signup) if not sent: - return render_template('signup/start.html', error='Cound not send mail') + return render_template('signup/start.html', error=_('Cound not send mail')) signup_ratelimit.log(request.form['mail']) return render_template('signup/submitted.html', signup=signup) diff --git a/uffd/mail/templates/mail/list.html b/uffd/mail/templates/mail/list.html index f994b140..c61bfda6 100644 --- a/uffd/mail/templates/mail/list.html +++ b/uffd/mail/templates/mail/list.html @@ -5,15 +5,15 @@ <div class="col"> <p class="text-right"> <a class="btn btn-primary" href="{{ url_for("mail.show") }}"> - <i class="fa fa-plus" aria-hidden="true"></i> New + <i class="fa fa-plus" aria-hidden="true"></i> {{_('New')}} </a> </p> <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">name</th> - <th scope="col">receiving address</th> - <th scope="col">destinations</th> + <th scope="col">{{_('Name')}}</th> + <th scope="col">{{_('Receiving addresses')}}</th> + <th scope="col">{{_('Destinations')}}</th> </tr> </thead> <tbody> diff --git a/uffd/mail/templates/mail/show.html b/uffd/mail/templates/mail/show.html index 44f74f47..dea92a2f 100644 --- a/uffd/mail/templates/mail/show.html +++ b/uffd/mail/templates/mail/show.html @@ -4,32 +4,32 @@ <form action="{{ url_for("mail.update", uid=mail.uid) }}" method="POST"> <div class="align-self-center"> <div class="form-group col"> - <label for="mail-name">Name</label> + <label for="mail-name">{{_('Name')}}</label> <input type="text" class="form-control" id="mail-name" name="mail-uid" {% if mail.uid %} value="{{ mail.uid }}" readonly {% else %} value=""{% endif %}> <small class="form-text text-muted"> </small> </div> <div class="form-group col"> - <label for="mail-receivers">Receiving addresses</label> + <label for="mail-receivers">{{_('Receiving addresses')}}</label> <textarea rows="10" class="form-control" id="mail-receivers" name="mail-receivers">{{ mail.receivers|join('\n') }}</textarea> <small class="form-text text-muted"> - One address per line + {{_('One address per line')}} </small> </div> <div class="form-group col"> - <label for="mail-destinations">Destinations</label> + <label for="mail-destinations">{{_('Destinations')}}</label> <textarea rows="10" class="form-control" id="mail-destinations" name="mail-destinations">{{ mail.destinations|join('\n') }}</textarea> <small class="form-text text-muted"> - One address per line + {{_('One address per line')}} </small> </div> <div class="form-group col"> - <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button> - <a href="{{ url_for("mail.index") }}" class="btn btn-secondary">Cancel</a> + <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Save')}}</button> + <a href="{{ url_for("mail.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a> {% if mail.uid %} - <a href="{{ url_for("mail.delete", uid=mail.uid) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a> + <a href="{{ url_for("mail.delete", uid=mail.uid) }}" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}</a> {% else %} - <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> Delete</a> + <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_('Delete')}}</a> {% endif %} </div> </div> diff --git a/uffd/mail/views.py b/uffd/mail/views.py index 6ba83d10..0e5948d6 100644 --- a/uffd/mail/views.py +++ b/uffd/mail/views.py @@ -1,4 +1,5 @@ from flask import Blueprint, render_template, request, url_for, redirect, flash, current_app +from flask_babel import gettext as _, lazy_gettext from uffd.navbar import register_navbar from uffd.csrf import csrf_protect @@ -19,7 +20,7 @@ def mail_acl_check(): return request.user and request.user.is_in_group(current_app.config['ACL_ADMIN_GROUP']) @bp.route("/") -@register_navbar('Mail', icon='envelope', blueprint=bp, visible=mail_acl_check) +@register_navbar(lazy_gettext('Forwardings'), icon='envelope', blueprint=bp, visible=mail_acl_check) def index(): return render_template('mail/list.html', mails=Mail.query.all()) @@ -43,7 +44,7 @@ def update(uid=None): mail.destinations = request.form.get('mail-destinations', '').splitlines() ldap.session.add(mail) ldap.session.commit() - flash('Mail mapping updated.') + flash(_('Mail mapping updated.')) return redirect(url_for('mail.show', uid=mail.uid)) @bp.route("/<uid>/del") @@ -52,5 +53,5 @@ def delete(uid): mail = Mail.query.filter_by(uid=uid).first_or_404() ldap.session.delete(mail) ldap.session.commit() - flash('Deleted mail mapping.') + flash(_('Deleted mail mapping.')) return redirect(url_for('mail.index')) diff --git a/uffd/oauth2/templates/oauth2/logout.html b/uffd/oauth2/templates/oauth2/logout.html index abddfe68..f048ccdf 100644 --- a/uffd/oauth2/templates/oauth2/logout.html +++ b/uffd/oauth2/templates/oauth2/logout.html @@ -7,14 +7,14 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12"> - <h2 class="text-center">Logout</h2> + <h2 class="text-center">{{_('Logout')}}</h2> </div> <div class="col-12"> <noscript> - <div class="alert alert-warning" role="alert">Javascript is required for automatic logout</div> + <div class="alert alert-warning" role="alert">{{_('Javascript is required for automatic logout')}}</div> </noscript> - <p>While you successfully logged out of the Single-Sign-On service, you may still be logged in on these services:</p> + <p>{{_('While you successfully logged out of the Single-Sign-On service, you may still be logged in on these services:')}}</p> <ul> {% for client in clients if client.logout_urls %} <li class="client" data-urls='{{ client.logout_urls|tojson }}'> @@ -27,15 +27,15 @@ </ul> <p> - Please wait until you have been automatically logged out of all services or make sure of this yourself. + {{_('Please wait until you have been automatically logged out of all services or make sure of this yourself.')}} </p> <button id="retry-button" class="btn btn-block btn-primary d-none" disabled> - <span id="cont-text">Logging you out on all services ...</span> + <span id="cont-text">{{_('Logging you out on all services ...')}}</span> </button> <a href="{{ request.values.get('ref') or '/' }}" class="btn btn-block btn-secondary"> - <span>Skip this and continue</span> + <span>{{_('Skip this and continue')}}</span> </a> </div> @@ -79,11 +79,11 @@ function logout_services() { if (result.status == 'rejected') throw result.reason; } - $('#cont-text').text('Done, redirecting ...'); + $('#cont-text').text({{_('Done, redirecting ...')|tojson}}); window.location = {{ (request.values.get('ref') or '/')|tojson }}; }).catch(function (err) { $("#retry-button").prop('disabled', false); - $('#cont-text').text('Log out failed on some services. Retry?'); + $('#cont-text').text({{_('Log out failed on some services. Retry?')|tojson}}); }); } diff --git a/uffd/oauth2/views.py b/uffd/oauth2/views.py index ed39aecd..c3c33af0 100644 --- a/uffd/oauth2/views.py +++ b/uffd/oauth2/views.py @@ -4,6 +4,7 @@ import urllib.parse from flask import Blueprint, request, jsonify, render_template, session, redirect, url_for, flash from flask_oauthlib.provider import OAuth2Provider +from flask_babel import gettext as _ from sqlalchemy.exc import IntegrityError from uffd.ratelimit import host_ratelimit, format_delay @@ -91,7 +92,7 @@ def authorize(*args, **kwargs): # pylint: disable=unused-argument del session['devicelogin_started'] host_delay = host_ratelimit.get_delay() if host_delay: - flash('We received too many requests from your ip address/network! Please wait at least %s.'%format_delay(host_delay)) + flash(_('We received too many requests from your ip address/network! Please wait at least %(delay)s.', delay=format_delay(host_delay))) return redirect(url_for('session.login', ref=request.full_path, devicelogin=True)) host_ratelimit.log() initiation = OAuth2DeviceLoginInitiation(oauth2_client_id=client.client_id) @@ -99,7 +100,7 @@ def authorize(*args, **kwargs): # pylint: disable=unused-argument try: db.session.commit() except IntegrityError: - flash('Device login is currently not available. Try again later!') + flash(_('Device login is currently not available. Try again later!')) return redirect(url_for('session.login', ref=request.values['ref'], devicelogin=True)) session['devicelogin_id'] = initiation.id session['devicelogin_secret'] = initiation.secret diff --git a/uffd/ratelimit.py b/uffd/ratelimit.py index 86bc2f0b..46c97e42 100644 --- a/uffd/ratelimit.py +++ b/uffd/ratelimit.py @@ -3,6 +3,7 @@ import ipaddress import math from flask import request +from flask_babel import gettext as _ from sqlalchemy import Column, Integer, String, DateTime from uffd.database import db @@ -66,16 +67,16 @@ class HostRatelimit(Ratelimit): def format_delay(seconds): if seconds <= 15: - return 'a few seconds' + return _('a few seconds') if seconds <= 30: - return '30 seconds' + return _('30 seconds') if seconds <= 60: - return 'one minute' + return _('one minute') if seconds < 3000: - return '%d minutes'%(math.ceil(seconds/60)+1) + return _('%(minutes)d minutes', minutes=(math.ceil(seconds/60)+1)) if seconds <= 3600: - return 'one hour' - return '%d hours'%math.ceil(seconds/3600) + return _('one hour') + return _('%(hours)d hours', hours=math.ceil(seconds/3600)) # Global host-based ratelimit host_ratelimit = HostRatelimit('host', 1*60*60, 25) diff --git a/uffd/role/templates/role/list.html b/uffd/role/templates/role/list.html index 76e84301..c4825971 100644 --- a/uffd/role/templates/role/list.html +++ b/uffd/role/templates/role/list.html @@ -11,20 +11,16 @@ <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">{{_("roleid")}}</th> - <th scope="col">{{_("name")}}</th> - <th scope="col">{{_("description")}}</th> + <th scope="col">{{_("Name")}}</th> + <th scope="col">{{_("Description")}}</th> </tr> </thead> <tbody> {% for role in roles|sort(attribute="name") %} <tr id="role-{{ role.id }}"> - <td> - {{ role.id }} - </td> <th scope="row"> <a href="{{ url_for("role.show", roleid=role.id) }}"> - {{ role.name or '<empty name>' }} + {{ role.name or _('<empty name>') }} </a> </th> <td> diff --git a/uffd/role/templates/role/show.html b/uffd/role/templates/role/show.html index d46bb429..09315fd5 100644 --- a/uffd/role/templates/role/show.html +++ b/uffd/role/templates/role/show.html @@ -14,11 +14,11 @@ <a href="{{ url_for("role.index") }}" class="btn btn-secondary">{{_("Cancel")}}</a> {% if role.id %} {% if not role.is_default %} - <a href="{{ url_for("role.set_default", roleid=role.id) }}" onClick="return confirm('All non-service users will be removed as members from this role and get its permissions implicitly. Are you sure?');" class="btn btn-secondary">{{_("Set as default")}}</a> + <a href="{{ url_for("role.set_default", roleid=role.id) }}" onClick="return confirm({{_('All non-service users will be removed as members from this role and get its permissions implicitly. Are you sure?')|tojson}});" class="btn btn-secondary">{{_("Set as default")}}</a> {% else %} - <a href="{{ url_for("role.unset_default", roleid=role.id) }}" onClick="return confirm('Are you sure?');" class="btn btn-secondary">{{_("Unset as default")}}</a> + <a href="{{ url_for("role.unset_default", roleid=role.id) }}" onClick="return confirm({{_('Are you sure?')|tojson}});" class="btn btn-secondary">{{_("Unset as default")}}</a> {% endif %} - <a href="{{ url_for("role.delete", roleid=role.id) }}" onClick="return confirm('Are you sure?');" class="btn btn-danger {{ 'disabled' if role.locked }}"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> + <a href="{{ url_for("role.delete", roleid=role.id) }}" onClick="return confirm({{_('Are you sure?')|tojson}});" class="btn btn-danger {{ 'disabled' if role.locked }}"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> {% else %} <a href="#" class="btn btn-secondary disabled">{{_("Set as default")}}</a> <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> diff --git a/uffd/rolemod/templates/rolemod/list.html b/uffd/rolemod/templates/rolemod/list.html index f727ba6c..c43826aa 100644 --- a/uffd/rolemod/templates/rolemod/list.html +++ b/uffd/rolemod/templates/rolemod/list.html @@ -6,8 +6,8 @@ <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">name</th> - <th scope="col">description</th> + <th scope="col">{{_('Name')}}</th> + <th scope="col">{{_('Description')}}</th> </tr> </thead> <tbody> diff --git a/uffd/rolemod/templates/rolemod/show.html b/uffd/rolemod/templates/rolemod/show.html index 72863d91..fbb28c06 100644 --- a/uffd/rolemod/templates/rolemod/show.html +++ b/uffd/rolemod/templates/rolemod/show.html @@ -5,31 +5,31 @@ <form method="POST" action="{{ url_for("rolemod.update", role_id=role.id) }}"> <div class="float-sm-right pb-2"> {% if config['ENABLE_INVITE'] %} - <a href="{{ url_for("invite.new", **{"role-%d"%role.id: 1}) }}" class="btn btn-primary mr-2"><i class="fa fa-link" aria-hidden="true"></i> Invite Members</a> + <a href="{{ url_for("invite.new", **{"role-%d"%role.id: 1}) }}" class="btn btn-primary mr-2"><i class="fa fa-link" aria-hidden="true"></i> {{_('Invite Members')}}</a> {% endif %} - <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> Save</button> - <a href="{{ url_for("rolemod.index") }}" class="btn btn-secondary">Cancel</a> + <button type="submit" class="btn btn-primary"><i class="fa fa-save" aria-hidden="true"></i> {{_('Save')}}</button> + <a href="{{ url_for("rolemod.index") }}" class="btn btn-secondary">{{_('Cancel')}}</a> </div> <ul class="nav nav-tabs pt-2 border-0" id="tablist" role="tablist"> <li class="nav-item"> - <a class="nav-link active" id="overview-tab" data-toggle="tab" href="#overview" role="tab" aria-controls="overview" aria-selected="true">Overview</a> + <a class="nav-link active" id="overview-tab" data-toggle="tab" href="#overview" role="tab" aria-controls="overview" aria-selected="true">{{_('Overview')}}</a> </li> <li class="nav-item"> - <a class="nav-link" id="members-tab" data-toggle="tab" href="#members" role="tab" aria-controls="members" aria-selected="false">Members <span class="badge badge-pill badge-secondary">{{ role.members|length }}</span></a> + <a class="nav-link" id="members-tab" data-toggle="tab" href="#members" role="tab" aria-controls="members" aria-selected="false">{{_('Members')}} <span class="badge badge-pill badge-secondary">{{ role.members|length }}</span></a> </li> </ul> <div class="tab-content border mb-2 pt-2" id="tabcontent"> <div class="tab-pane fade show active" id="overview" role="tabpanel" aria-labelledby="overview-tab"> <div class="form-group col"> - <label for="role-name">Role Name</label> + <label for="role-name">{{_('Role name')}}</label> <input type="text" class="form-control" id="role-name" value="{{ role.name }}" readonly> </div> <div class="form-group col"> - <label for="role-description">Description</label> + <label for="role-description">{{_('Description')}}</label> <textarea class="form-control" id="role-description" rows="5" name="description">{{ role.description }}</textarea> </div> <div class="form-group col"> - <label>Moderators:</label> + <label>{{_('Moderators:')}}</label> <ul> {% for moderator in role.moderator_group.members %} <li>{{ moderator.displayname }} ({{ moderator.loginname }})</li> @@ -39,11 +39,11 @@ </div> <div class="tab-pane fade" id="members" role="tabpanel" aria-labelledby="members-tab"> <div class="col"> - <span>Role members:</span> + <span>{{_('Role members:')}}</span> <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">Name</th> + <th scope="col">{{_('Name')}}</th> <th scope="col"></th> </tr> </thead> @@ -52,7 +52,7 @@ <tr> <td>{{ member.displayname }} ({{ member.loginname }})</td> <td class="text-right"> - <a class="btn btn-danger py-0" href="{{ url_for('rolemod.delete_member', role_id=role.id, member_dn=member.dn) }}">Remove</a> + <a class="btn btn-danger py-0" href="{{ url_for('rolemod.delete_member', role_id=role.id, member_dn=member.dn) }}">{{_('Remove')}}</a> </td> </tr> {% endfor %} diff --git a/uffd/rolemod/views.py b/uffd/rolemod/views.py index d9c5f834..8a9c6051 100644 --- a/uffd/rolemod/views.py +++ b/uffd/rolemod/views.py @@ -1,4 +1,5 @@ from flask import Blueprint, render_template, request, url_for, redirect, flash +from flask_babel import gettext as _, lazy_gettext from uffd.navbar import register_navbar from uffd.csrf import csrf_protect @@ -21,7 +22,7 @@ def acl_check(): #pylint: disable=inconsistent-return-statements return redirect(url_for('index')) @bp.route("/") -@register_navbar('Moderation', icon='user-lock', blueprint=bp, visible=user_is_rolemod) +@register_navbar(lazy_gettext('Moderation'), icon='user-lock', blueprint=bp, visible=user_is_rolemod) def index(): roles = Role.query.filter(Role.moderator_group_dn.in_(request.user.group_dns)).all() return render_template('rolemod/list.html', roles=roles) @@ -32,7 +33,7 @@ def show(role_id): User.query.all() role = Role.query.get_or_404(role_id) if role.moderator_group not in request.user.groups: - flash('Access denied') + flash(_('Access denied')) return redirect(url_for('index')) return render_template('rolemod/show.html', role=role) @@ -41,11 +42,11 @@ def show(role_id): def update(role_id): role = Role.query.get_or_404(role_id) if role.moderator_group not in request.user.groups: - flash('Access denied') + flash(_('Access denied')) return redirect(url_for('index')) if request.form['description'] != role.description: if len(request.form['description']) > 256: - flash('Description too long') + flash(_('Description too long')) return redirect(url_for('.show', role_id=role.id)) role.description = request.form['description'] db.session.commit() @@ -56,12 +57,12 @@ def update(role_id): def delete_member(role_id, member_dn): role = Role.query.get_or_404(role_id) if role.moderator_group not in request.user.groups: - flash('Access denied') + flash(_('Access denied')) return redirect(url_for('index')) member = User.query.get_or_404(member_dn) role.members.discard(member) member.update_groups() ldap.session.commit() db.session.commit() - flash('Member removed') + flash(_('Member removed')) return redirect(url_for('.show', role_id=role.id)) diff --git a/uffd/selfservice/templates/selfservice/self.html b/uffd/selfservice/templates/selfservice/self.html index 66c9d677..49d1c7a9 100644 --- a/uffd/selfservice/templates/selfservice/self.html +++ b/uffd/selfservice/templates/selfservice/self.html @@ -13,7 +13,7 @@ <div class="col-12 col-md-5"> <h5>{{_("Profile")}}</h5> <p>{{_("Your profile information is used by all services that are integrated into the Single-Sign-On. Your e-mail address is also used for password recovery.")}}</p> - <p>{{_(" Changes may take serveral minutes to be visible in all services.")}}</p> + <p>{{_("Changes may take serveral minutes to be visible in all services.")}}</p> </div> <div class="col-12 col-md-7"> <form class="form" action="{{ url_for("selfservice.update_profile") }}" method="POST"> @@ -55,7 +55,7 @@ <div class="form-group"> <input type="password" class="form-control" id="user-password1" name="password1" placeholder="{{_("New Password")}}" required> <small class="form-text text-muted"> - At least 8 and at most 256 characters, no other special requirements. + {{_('At least 8 and at most 256 characters, no other special requirements.')}} </small> </div> <div class="form-group"> @@ -120,7 +120,7 @@ <td>{{ role.description }}</td> <td> {% if config['ENABLE_ROLESELFSERVICE'] %} - <form action="{{ url_for("selfservice.leave_role", roleid=role.id) }}" method="POST" onsubmit="return confirm('Are you sure?');"> + <form action="{{ url_for("selfservice.leave_role", roleid=role.id) }}" method="POST" onsubmit="return confirm({{_('Are you sure?')|tojson}});"> <button type="submit" class="btn btn-sm btn-danger float-right">{{_("Leave")}}</button> </form> {% endif %} diff --git a/uffd/selfservice/views.py b/uffd/selfservice/views.py index fadc8fea..de79999a 100644 --- a/uffd/selfservice/views.py +++ b/uffd/selfservice/views.py @@ -74,13 +74,13 @@ def forgot_password(): host_delay = host_ratelimit.get_delay() if reset_delay or host_delay: if reset_delay > host_delay: - flash('We received too many password reset requests for this user! Please wait at least %s.'%format_delay(reset_delay)) + flash(_('We received too many password reset requests for this user! Please wait at least %(delay)s.', delay=format_delay(reset_delay))) else: - flash('We received too many requests from your ip address/network! Please wait at least %s.'%format_delay(host_delay)) + flash(_('We received too many requests from your ip address/network! Please wait at least %(delay)s.', delay=format_delay(host_delay))) return redirect(url_for('.forgot_password')) reset_ratelimit.log(loginname+'/'+mail) host_ratelimit.log() - flash("We sent a mail to this users mail address if you entered the correct mail and login name combination") + flash(_("We sent a mail to this user's mail address if you entered the correct mail and login name combination")) user = User.query.filter_by(loginname=loginname).one_or_none() if user and user.mail == mail: send_passwordreset(user) @@ -90,7 +90,7 @@ def forgot_password(): def token_password(token): dbtoken = PasswordToken.query.get(token) if not dbtoken or dbtoken.created < (datetime.datetime.now() - datetime.timedelta(days=2)): - flash('Token expired, please try again.') + flash(_('Token expired, please try again.')) if dbtoken: db.session.delete(dbtoken) db.session.commit() @@ -98,17 +98,17 @@ def token_password(token): if request.method == 'GET': return render_template('selfservice/set_password.html', token=token) if not request.values['password1']: - flash('You need to set a password, please try again.') + flash(_('You need to set a password, please try again.')) return render_template('selfservice/set_password.html', token=token) if not request.values['password1'] == request.values['password2']: - flash('Passwords do not match, please try again.') + flash(_('Passwords do not match, please try again.')) return render_template('selfservice/set_password.html', token=token) user = User.query.filter_by(loginname=dbtoken.loginname).one() if not user.set_password(request.values['password1']): - flash('Password ist not valid, please try again.') + flash(_('Password ist not valid, please try again.')) return render_template('selfservice/set_password.html', token=token) db.session.delete(dbtoken) - flash('New password set') + flash(_('New password set')) ldap.session.commit() db.session.commit() return redirect(url_for('session.login')) @@ -118,7 +118,7 @@ def token_password(token): def token_mail(token): dbtoken = MailToken.query.get(token) if not dbtoken or dbtoken.created < (datetime.datetime.now() - datetime.timedelta(days=2)): - flash('Token expired, please try again.') + flash(_('Token expired, please try again.')) if dbtoken: db.session.delete(dbtoken) db.session.commit() @@ -126,7 +126,7 @@ def token_mail(token): user = User.query.filter_by(loginname=dbtoken.loginname).one() user.set_mail(dbtoken.newmail) - flash('New mail set') + flash(_('New mail set')) db.session.delete(dbtoken) ldap.session.commit() db.session.commit() @@ -137,14 +137,14 @@ def token_mail(token): @login_required() def leave_role(roleid): if not current_app.config['ENABLE_ROLESELFSERVICE']: - flash('Leaving roles is disabled') + flash(_('Leaving roles is disabled')) return redirect(url_for('selfservice.index')) role = Role.query.get_or_404(roleid) role.members.discard(request.user) request.user.update_groups() ldap.session.commit() db.session.commit() - flash('You left role "%s"'%role.name) + flash(_('You left role "%(role_name)s"', role_name=role.name)) return redirect(url_for('selfservice.index')) def send_mail_verification(loginname, newmail): @@ -208,5 +208,5 @@ def send_mail(to_address, msg): current_app.last_mail = msg return True except smtplib.SMTPException: - flash('Mail to "{}" could not be sent!'.format(to_address)) + flash(_('Mail to "%(mail_address)s" could not be sent!', mail_address=to_address)) return False diff --git a/uffd/session/templates/session/deviceauth.html b/uffd/session/templates/session/deviceauth.html index 09eb76f2..382cf74a 100644 --- a/uffd/session/templates/session/deviceauth.html +++ b/uffd/session/templates/session/deviceauth.html @@ -14,13 +14,13 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12"> - <h2 class="text-center">Authorize Device Login</h2> + <h2 class="text-center">{{_('Authorize Device Login')}}</h2> </div> <div class="form-group col-12"> - <p>Log into a service on another device without entering your password.</p> + <p>{{_('Log into a service on another device without entering your password.')}}</p> </div> <div class="form-group col-12"> - <label for="initiation-code">Initiation Code</label> + <label for="initiation-code">{{_('Initiation Code')}}</label> {% if not initiation %} <input type="text" class="form-control" id="initiation-code" name="initiation-code" value="{{ initiation_code or '' }}" required="required" tabindex = "1" autofocus> {% else %} @@ -29,36 +29,36 @@ </div> {% if confirmation %} <div class="form-group col-12"> - <label for="confirmation-code">Confirmation Code</label> + <label for="confirmation-code">{{_('Confirmation Code')}}</label> <input type="text" class="form-control" id="confirmation-code" name="confirmation-code" value="{{ confirmation.code }}" readonly> </div> {% endif %} {% if not initiation %} <div class="form-group col-12"> - <p>Start logging into a service on the other device and chose "Device Login" on the login page. Enter the displayed initiation code in the box above.</p> + <p>{{_('Start logging into a service on the other device and chose "Device Login" on the login page. Enter the displayed initiation code in the box above.')}}</p> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block" tabindex = "2">Continue</button> + <button type="submit" class="btn btn-primary btn-block" tabindex = "2">{{_('Continue')}}</button> </div> <div class="form-group col-12"> - <a href="{{ url_for('index') }}" class="btn btn-secondary btn-block" tabindex="0">Cancel</a> + <a href="{{ url_for('index') }}" class="btn btn-secondary btn-block" tabindex="0">{{_('Cancel')}}</a> </div> {% elif not confirmation %} <div class="form-group col-12"> - <p>Authorize the login for service <b>{{ initiation.description }}</b>?</p> + <p>{{_('Authorize the login for service <b>%(service_name)s</b>?', service_name=initiation.description|e)|safe}}</p> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block" tabindex = "2">Authorize Login</button> + <button type="submit" class="btn btn-primary btn-block" tabindex = "2">{{_('Authorize Login')}}</button> </div> <div class="form-group col-12"> - <a href="{{ url_for('index') }}" class="btn btn-secondary btn-block" tabindex="0">Cancel</a> + <a href="{{ url_for('index') }}" class="btn btn-secondary btn-block" tabindex="0">{{_('Cancel')}}</a> </div> {% else %} <div class="form-group col-12"> - <p>Enter the confirmation code on the other device and complete the login. Click <em>Finish</em> afterwards.</p> + <p>{{_('Enter the confirmation code on the other device and complete the login. Click <em>Finish</em> afterwards.')|safe}}</p> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block" tabindex = "2">Finish</button> + <button type="submit" class="btn btn-primary btn-block" tabindex = "2">{{_('Finish')}}</button> </div> {% endif %} </div> diff --git a/uffd/session/templates/session/devicelogin.html b/uffd/session/templates/session/devicelogin.html index 4bd92b15..62578d05 100644 --- a/uffd/session/templates/session/devicelogin.html +++ b/uffd/session/templates/session/devicelogin.html @@ -8,30 +8,30 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12"> - <h2 class="text-center">Device Login</h2> + <h2 class="text-center">{{_('Device Login')}}</h2> </div> <div class="form-group col-12"> - <p>Use a login session on another device (e.g. your laptop) to log into a service without entering your password.</p> + <p>{{_('Use a login session on another device (e.g. your laptop) to log into a service without entering your password.')}}</p> </div> {% if initiation %} <div class="form-group col-12"> - <label for="initiation-code">Initiation Code</label> + <label for="initiation-code">{{_('Initiation Code')}}</label> <input type="text" class="form-control" id="initiation-code" name="initiation-code" value="{{ initiation.code }}" readonly> </div> <input type="hidden" class="form-control" id="initiation-secret" name="initiation-secret" value="{{ initiation.secret }}"> <div class="form-group col-12"> - <label for="confirmation-code">Confirmation Code</label> + <label for="confirmation-code">{{_('Confirmation Code')}}</label> <input type="text" class="form-control" id="confirmation-code" name="confirmation-code" required="required" tabindex = "1" autofocus> </div> <div class="form-group col-12"> - <p>Open <code><a href="{{ url_for('session.deviceauth') }}">{{ url_for('session.deviceauth', _external=True) }}</a></code> on the other device and enter the initiation code there. Then enter the confirmation code in the box above.</p> + <p>{{_('Open <code><a href="%(deviceauth_url)s">%(deviceauth_url)s</a></code> on the other device and enter the initiation code there. Then enter the confirmation code in the box above.', deviceauth_url=url_for('session.deviceauth', _external=True)|e)|safe}}</p> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block" tabindex = "3">Continue</button> + <button type="submit" class="btn btn-primary btn-block" tabindex = "3">{{_('Continue')}}</button> </div> {% endif %} <div class="form-group col-12"> - <a href="{{ url_for('session.login', ref=ref, devicelogin=True) }}" class="btn btn-secondary btn-block" tabindex="0">Cancel</a> + <a href="{{ url_for('session.login', ref=ref, devicelogin=True) }}" class="btn btn-secondary btn-block" tabindex="0">{{_('Cancel')}}</a> </div> </div> </div> diff --git a/uffd/session/templates/session/login.html b/uffd/session/templates/session/login.html index 7f22b3db..f542802b 100644 --- a/uffd/session/templates/session/login.html +++ b/uffd/session/templates/session/login.html @@ -8,10 +8,10 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12"> - <h2 class="text-center">Login</h2> + <h2 class="text-center">{{_("Login")}}</h2> </div> <div class="form-group col-12"> - <label for="user-loginname">{{ _("Login Name") }}</label> + <label for="user-loginname">{{_("Login Name")}}</label> <input type="text" class="form-control" id="user-loginname" name="loginname" required="required" tabindex = "1" autofocus> </div> <div class="form-group col-12"> @@ -22,9 +22,9 @@ <button type="submit" class="btn btn-primary btn-block" tabindex = "3">{{_("Login")}}</button> </div> {% if request.values.get('devicelogin') %} - <div class="text-center text-muted mb-3">- or -</div> + <div class="text-center text-muted mb-3">{{_("- or -")}}</div> <div class="form-group col-12"> - <a href="{{ url_for('session.devicelogin_start', ref=ref) }}" class="btn btn-primary btn-block" tabindex="0">Device Login</a> + <a href="{{ url_for('session.devicelogin_start', ref=ref) }}" class="btn btn-primary btn-block" tabindex="0">{{_("Device Login")}}</a> </div> {% endif %} <div class="clearfix col-12"> diff --git a/uffd/session/views.py b/uffd/session/views.py index b558b274..cde2a3fd 100644 --- a/uffd/session/views.py +++ b/uffd/session/views.py @@ -88,9 +88,9 @@ def login(): host_delay = host_ratelimit.get_delay() if login_delay or host_delay: if login_delay > host_delay: - flash(_('We received too many invalid login attempts for this user! Please wait at least %s.')%format_delay(login_delay)) + flash(_('We received too many invalid login attempts for this user! Please wait at least %(delay)s.', delay=format_delay(login_delay))) else: - flash(_('We received too many requests from your ip address/network! Please wait at least %s.')%format_delay(host_delay)) + flash(_('We received too many requests from your ip address/network! Please wait at least %(delay)s.', delay=format_delay(host_delay))) return render_template('session/login.html', ref=request.values.get('ref')) user = login_get_user(username, password) if user is None: @@ -144,7 +144,7 @@ def devicelogin(): return redirect(url_for('session.login', ref=request.values['ref'], devicelogin=True)) initiation = DeviceLoginInitiation.query.filter_by(id=session['devicelogin_id'], secret=session['devicelogin_secret']).one_or_none() if not initiation or initiation.expired: - flash('Initiation code is no longer valid') + flash(_('Initiation code is no longer valid')) return redirect(url_for('session.login', ref=request.values['ref'], devicelogin=True)) return render_template('session/devicelogin.html', ref=request.values.get('ref'), initiation=initiation) @@ -154,11 +154,11 @@ def devicelogin_submit(): return redirect(url_for('session.login', ref=request.values['ref'], devicelogin=True)) initiation = DeviceLoginInitiation.query.filter_by(id=session['devicelogin_id'], secret=session['devicelogin_secret']).one_or_none() if not initiation or initiation.expired: - flash('Initiation code is no longer valid') + flash(_('Initiation code is no longer valid')) return redirect(url_for('session.login', ref=request.values['ref'], devicelogin=True)) confirmation = DeviceLoginConfirmation.query.filter_by(initiation=initiation, code=request.form['confirmation-code']).one_or_none() if confirmation is None: - flash('Invalid confirmation code') + flash(_('Invalid confirmation code')) return render_template('session/devicelogin.html', ref=request.values.get('ref'), initiation=initiation) session['devicelogin_confirmation'] = confirmation.id return secure_local_redirect(request.values['ref']) @@ -170,7 +170,7 @@ def deviceauth(): return render_template('session/deviceauth.html') initiation = DeviceLoginInitiation.query.filter_by(code=request.values['initiation-code']).one_or_none() if initiation is None or initiation.expired: - flash('Invalid initiation code') + flash(_('Invalid initiation code')) return redirect(url_for('session.deviceauth')) return render_template('session/deviceauth.html', initiation=initiation) @@ -181,7 +181,7 @@ def deviceauth_submit(): DeviceLoginConfirmation.query.filter_by(user_dn=request.user.dn).delete() initiation = DeviceLoginInitiation.query.filter_by(code=request.form['initiation-code']).one_or_none() if initiation is None or initiation.expired: - flash('Invalid initiation code') + flash(_('Invalid initiation code')) return redirect(url_for('session.deviceauth')) confirmation = DeviceLoginConfirmation(user=request.user, initiation=initiation) db.session.add(confirmation) diff --git a/uffd/signup/templates/signup/confirm.html b/uffd/signup/templates/signup/confirm.html index 9e235523..dc81ba7f 100644 --- a/uffd/signup/templates/signup/confirm.html +++ b/uffd/signup/templates/signup/confirm.html @@ -8,17 +8,17 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12"> - <h2 class="text-center">Complete Registration</h2> + <h2 class="text-center">{{_('Complete Registration')}}</h2> </div> {% if error %} <div class="alert alert-danger" role="alert">{{ error }}</div> {% endif %} <div class="form-group col-12"> - <label for="user-password1">Please enter your password to complete the account registration</label> + <label for="user-password1">{{_('Please enter your password to complete the account registration')}}</label> <input type="password" class="form-control" id="user-password1" name="password" required="required"> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block">Complete Account Registration</button> + <button type="submit" class="btn btn-primary btn-block">{{_('Complete Account Registration')}}</button> </div> </div> </div> diff --git a/uffd/signup/templates/signup/start.html b/uffd/signup/templates/signup/start.html index abb225e9..fd96d80f 100644 --- a/uffd/signup/templates/signup/start.html +++ b/uffd/signup/templates/signup/start.html @@ -8,7 +8,7 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12"> - <h2 class="text-center">Account Registration</h2> + <h2 class="text-center">{{_('Account Registration')}}</h2> </div> {% if error %} <div class="form-group col-12"> @@ -16,45 +16,45 @@ </div> {% endif %} <div class="form-group col-12"> - <label for="user-loginname">Login Name</label> + <label for="user-loginname">{{_('Login Name')}}</label> <div class="js-only-input-group"> <input type="text" class="form-control" id="user-loginname" name="loginname" aria-describedby="loginname-feedback" value="{{ request.form.loginname }}" minlength=1 maxlength=32 pattern="[a-z0-9_-]*" required> <div class="js-only-input-group-append d-none"> - <button class="btn btn-outline-secondary rounded-right" type="button" id="check-loginname">Check</button> + <button class="btn btn-outline-secondary rounded-right" type="button" id="check-loginname">{{_('Check')}}</button> </div> - <div id="loginname-feedback" class="invalid-feedback">foobar</div> + <div id="loginname-feedback" class="invalid-feedback"></div> </div> <small class="form-text text-muted"> - At least one and at most 32 lower-case characters, digits, dashes ("-") or underscores ("_"). <b>Cannot be changed later!</b> + {{_('At least one and at most 32 lower-case characters, digits, dashes ("-") or underscores ("_"). <b>Cannot be changed later!</b>')|safe}} </small> </div> <div class="form-group col-12"> - <label for="user-displayname">Display Name</label> + <label for="user-displayname">{{_('Display Name')}}</label> <input type="text" class="form-control" id="user-displayname" name="displayname" value="{{ request.form.displayname }}" minlength=1 maxlength=128 required> <small class="form-text text-muted"> - At least one and at most 128 characters, no other special requirements. + {{_('At least one and at most 128 characters, no other special requirements.')}} </small> </div> <div class="form-group col-12"> - <label for="user-mail">E-Mail Address</label> + <label for="user-mail">{{_('E-Mail Address')}}</label> <input type="email" class="form-control" id="user-mail" name="mail" value="{{ request.form.mail }}" required> <small class="form-text text-muted"> - We will send a confirmation mail to this address that you need to complete the registration. + {{_('We will send a confirmation mail to this address that you need to complete the registration.')}} </small> </div> <div class="form-group col-12"> - <label for="user-password1">Password</label> + <label for="user-password1">{{_('Password')}}</label> <input type="password" class="form-control" id="user-password1" name="password1" minlength=8 maxlength=256 required> <small class="form-text text-muted"> - At least 8 and at most 256 characters, no other special requirements. But please don't be stupid, do use a password manager. + {{_("At least 8 and at most 256 characters, no other special requirements. But please don't be stupid, do use a password manager.")}} </small> </div> <div class="form-group col-12"> - <label for="user-password2">Repeat Password</label> + <label for="user-password2">{{_('Repeat Password')}}</label> <input type="password" class="form-control" id="user-password2" name="password2" required> </div> <div class="form-group col-12"> - <button type="submit" class="btn btn-primary btn-block">Create Account</button> + <button type="submit" class="btn btn-primary btn-block">{{_('Create Account')}}</button> </div> </div> </div> @@ -83,13 +83,13 @@ $("#check-loginname").on("click", function () { $("#user-loginname").addClass("is-valid"); $("#loginname-feedback").text(""); } else if (resp.status == 'exists') { - $("#loginname-feedback").text("The name is already taken"); + $("#loginname-feedback").text({{_("The name is already taken")|tojson}}); $("#user-loginname").addClass("is-invalid"); } else if (resp.status == 'ratelimited') { - $("#loginname-feedback").text("Too many requests! Please wait a bit before trying again!"); + $("#loginname-feedback").text({{_("Too many requests! Please wait a bit before trying again!")|tojson}}); $("#user-loginname").addClass("is-invalid"); } else { - $("#loginname-feedback").text("The name is invalid"); + $("#loginname-feedback").text({{_("The name is invalid")|tojson}}); $("#user-loginname").addClass("is-invalid"); } } diff --git a/uffd/signup/templates/signup/submitted.html b/uffd/signup/templates/signup/submitted.html index 685c1a01..6c85e910 100644 --- a/uffd/signup/templates/signup/submitted.html +++ b/uffd/signup/templates/signup/submitted.html @@ -8,10 +8,10 @@ <img alt="branding logo" src="{{ config.get("BRANDING_LOGO_URL") }}" class="col-lg-8 col-md-12" > </div> <div class="col-12 mb-3"> - <h2 class="text-center">Confirm your E-Mail Address</h2> + <h2 class="text-center">{{_('Confirm your E-Mail Address')}}</h2> </div> - <p>We sent a confirmation mail to <b>{{ signup.mail }}</b>. You need to confirm your mail address within 48 hours to complete the account registration.</p> - <p>If you mistyped your mail address or don't receive the confirmation mail for another reason, retry the registration procedure from the beginning.</p> + <p>{{_('We sent a confirmation mail to <b>%(signup_mail)s</b>. You need to confirm your mail address within 48 hours to complete the account registration.', signup_mail=signup.mail|e)|safe}}</p> + <p>{{_("If you mistyped your mail address or don't receive the confirmation mail for another reason, retry the registration procedure from the beginning.")}}</p> </div> </div> {% endblock %} diff --git a/uffd/signup/views.py b/uffd/signup/views.py index ba0f58c6..b15b14c6 100644 --- a/uffd/signup/views.py +++ b/uffd/signup/views.py @@ -1,6 +1,7 @@ import functools from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app, jsonify +from flask_babel import gettext as _ from uffd.database import db from uffd.ldap import ldap @@ -19,7 +20,7 @@ def signup_enabled(func): @functools.wraps(func) def decorator(*args, **kwargs): if not current_app.config['SELF_SIGNUP']: - flash('Singup not enabled') + flash(_('Singup not enabled')) return redirect(url_for('index')) return func(*args, **kwargs) return decorator @@ -45,13 +46,14 @@ def signup_check(): @signup_enabled def signup_submit(): if request.form['password1'] != request.form['password2']: - return render_template('signup/start.html', error='Passwords do not match') + return render_template('signup/start.html', error=_('Passwords do not match')) signup_delay = signup_ratelimit.get_delay(request.form['mail']) host_delay = host_ratelimit.get_delay() if signup_delay and signup_delay > host_delay: - return render_template('signup/start.html', error='Too many signup requests with this mail address! Please wait %s.'%format_delay(signup_delay)) + return render_template('signup/start.html', error=_('Too many signup requests with this mail address! Please wait %(delay)s.', + delay=format_delay(signup_delay))) if host_delay: - return render_template('signup/start.html', error='Too many requests! Please wait %s.'%format_delay(host_delay)) + return render_template('signup/start.html', error=_('Too many requests! Please wait %(delay)s.', delay=format_delay(host_delay))) host_ratelimit.log() signup = Signup(loginname=request.form['loginname'], displayname=request.form['displayname'], @@ -63,7 +65,7 @@ def signup_submit(): db.session.commit() sent = sendmail(signup.mail, 'Confirm your mail address', 'signup/mail.txt', signup=signup) if not sent: - return render_template('signup/start.html', error='Cound not send mail') + return render_template('signup/start.html', error=_('Cound not send mail')) signup_ratelimit.log(request.form['mail']) return render_template('signup/submitted.html', signup=signup) @@ -72,7 +74,7 @@ def signup_submit(): def signup_confirm(token): signup = Signup.query.get(token) if not signup or signup.expired or signup.completed: - flash('Invalid signup link') + flash(_('Invalid signup link')) return redirect(url_for('index')) return render_template('signup/confirm.html', signup=signup) @@ -80,23 +82,23 @@ def signup_confirm(token): def signup_confirm_submit(token): signup = Signup.query.get(token) if not signup or signup.expired or signup.completed: - flash('Invalid signup link') + flash(_('Invalid signup link')) return redirect(url_for('index')) confirm_delay = confirm_ratelimit.get_delay(token) host_delay = host_ratelimit.get_delay() if confirm_delay and confirm_delay > host_delay: - return render_template('signup/confirm.html', signup=signup, error='Too many failed attempts! Please wait %s.'%format_delay(confirm_delay)) + return render_template('signup/confirm.html', signup=signup, error=_('Too many failed attempts! Please wait %(delay)s.', delay=format_delay(confirm_delay))) if host_delay: - return render_template('signup/confirm.html', signup=signup, error='Too many requests! Please wait %s.'%format_delay(host_delay)) + return render_template('signup/confirm.html', signup=signup, error=_('Too many requests! Please wait %(delay)s.', delay=format_delay(host_delay))) if not signup.check_password(request.form['password']): host_ratelimit.log() confirm_ratelimit.log(token) - return render_template('signup/confirm.html', signup=signup, error='Wrong password') + return render_template('signup/confirm.html', signup=signup, error=_('Wrong password')) user, msg = signup.finish(request.form['password']) if user is None: return render_template('signup/confirm.html', signup=signup, error=msg) db.session.commit() ldap.session.commit() set_session(user, password=request.form['password'], skip_mfa=True) - flash('Your account was successfully created') + flash(_('Your account was successfully created')) return redirect(url_for('selfservice.index')) diff --git a/uffd/templates/base.html b/uffd/templates/base.html index 58ef8d72..63406e58 100644 --- a/uffd/templates/base.html +++ b/uffd/templates/base.html @@ -81,7 +81,7 @@ {% endfor %} </select> <noscript> - <button type="submit" class="bg-dark py-0 pl-0" style="border: 0px; color: rgba(255, 255, 255, 0.5);">Change</button> + <button type="submit" class="bg-dark py-0 pl-0" style="border: 0px; color: rgba(255, 255, 255, 0.5);">{{_('Change')}}</button> </noscript> </form> </li> @@ -89,8 +89,8 @@ {% if request.user %} <li class="nav-item"> <a class="nav-link" href="{{ url_for("session.deviceauth") }}"> - <span aria-hidden="true" class="fas fa-mobile-alt" title="Authorize Device Login"></span> - <span class="d-inline d-md-none">Device Login</span> + <span aria-hidden="true" class="fas fa-mobile-alt" title="{{_('Authorize Device Login')}}"></span> + <span class="d-inline d-md-none">{{_('Device Login')}}</span> </a> </li> <li class="nav-item"> diff --git a/uffd/translations/de/LC_MESSAGES/messages.mo b/uffd/translations/de/LC_MESSAGES/messages.mo index 952c7b0b0f71194d8092cfe0910cde1f4d2dfdc7..e275eb91a683da1156ad7f935e44542a327d4a36 100644 GIT binary patch literal 30649 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 delta 4094 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 JOyBvQ`5y<5aR&eZ diff --git a/uffd/translations/de/LC_MESSAGES/messages.po b/uffd/translations/de/LC_MESSAGES/messages.po index 77a75923..6fb606da 100644 --- a/uffd/translations/de/LC_MESSAGES/messages.po +++ b/uffd/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-07-15 22:28+0200\n" +"POT-Creation-Date: 2021-07-30 19:49+0200\n" "PO-Revision-Date: 2021-05-25 21:18+0200\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language: de\n" @@ -18,19 +18,379 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.9.1\n" -#: mfa/views.py:53 +#: uffd/ratelimit.py:70 +msgid "a few seconds" +msgstr "ein paar Sekunden" + +#: uffd/ratelimit.py:72 +msgid "30 seconds" +msgstr "30 Sekunden" + +#: uffd/ratelimit.py:74 +msgid "one minute" +msgstr "eine Minute" + +#: uffd/ratelimit.py:76 +#, python-format +msgid "%(minutes)d minutes" +msgstr "%(minutes)d Minuten" + +#: uffd/ratelimit.py:78 +msgid "one hour" +msgstr "eine Stunde" + +#: uffd/ratelimit.py:79 +#, python-format +msgid "%(hours)d hours" +msgstr "%(hours)d Stunden\"" + +#: uffd/invite/views.py:56 +msgid "Invites" +msgstr "Einladungslinks" + +#: uffd/invite/views.py:85 +msgid "The \"Expires After\" date is too far in the future" +msgstr "Das Ablaufdatum liegt zu weit in der Zukunft" + +#: uffd/invite/views.py:88 +msgid "You are not allowed to create invite links with these permissions" +msgstr "Dir fehlen Berechtigungen um diesen Einladungslink zu erstellen" + +#: uffd/invite/views.py:91 +msgid "Invite link must either allow signup or grant at least one role" +msgstr "" +"Einladungslink must entweder Account-Registrierung erlauben oder Rollen " +"vergeben" + +#: uffd/invite/views.py:119 uffd/invite/views.py:148 +msgid "Invalid invite link" +msgstr "Ungültiger Einladungslink" + +#: uffd/invite/views.py:136 +msgid "Roles successfully updated" +msgstr "Rollen erfolgreich geändert" + +#: uffd/invite/views.py:151 +msgid "Invite link does not allow signup" +msgstr "Einladungslink erlaubt keine Account-Registrierung" + +#: uffd/invite/views.py:173 uffd/selfservice/views.py:53 +#: uffd/signup/views.py:49 +msgid "Passwords do not match" +msgstr "Die Passwörter stimmen nicht überein" + +#: uffd/invite/views.py:178 uffd/signup/views.py:54 +#, python-format +msgid "Too many signup requests with this mail address! Please wait %(delay)s." +msgstr "" +"Zu viele Account-Registrierungen mit dieser E-Mail-Adresse! Bitte warte " +"%(delay)s." + +#: uffd/invite/views.py:180 uffd/signup/views.py:56 uffd/signup/views.py:92 +#, python-format +msgid "Too many requests! Please wait %(delay)s." +msgstr "Zu viele Anfragen! Bitte warte %(delay)s." + +#: uffd/invite/views.py:193 uffd/signup/views.py:68 +msgid "Cound not send mail" +msgstr "Mailversand fehlgeschlagen" + +#: uffd/invite/templates/invite/list.html:6 +#: uffd/mail/templates/mail/list.html:8 uffd/role/templates/role/list.html:8 +#: uffd/user/templates/user/list.html:8 +msgid "New" +msgstr "Neu" + +#: uffd/invite/templates/invite/list.html:12 +msgid "Link" +msgstr "Link" + +#: uffd/invite/templates/invite/list.html:13 +msgid "Created by" +msgstr "Ersteller" + +#: uffd/invite/templates/invite/list.html:14 +msgid "Permissions" +msgstr "Berechtigungen" + +#: uffd/invite/templates/invite/list.html:15 +msgid "Usages" +msgstr "Verwendungen" + +#: uffd/invite/templates/invite/list.html:16 +msgid "Status" +msgstr "Status" + +#: uffd/invite/templates/invite/list.html:26 +msgid "Copy link to clipboard" +msgstr "Link kopieren" + +#: uffd/invite/templates/invite/list.html:27 +msgid "Show link as QR code" +msgstr "Link als QR-Code anzeigen" + +#: uffd/invite/templates/invite/list.html:42 +msgid "Signup" +msgstr "Account-Registrierung" + +#: uffd/invite/templates/invite/list.html:51 +msgid "Disabled" +msgstr "Deaktiviert" + +#: uffd/invite/templates/invite/list.html:53 +msgid "Voided" +msgstr "Verbraucht" + +#: uffd/invite/templates/invite/list.html:55 +msgid "Expired" +msgstr "Abgelaufen" + +#: uffd/invite/templates/invite/list.html:57 +msgid "Invalid, unpermitted creator" +msgstr "Ungültig, unberechtigter Ersteller" + +#: uffd/invite/templates/invite/list.html:59 +msgid "Invalid" +msgstr "Ungültig" + +#: uffd/invite/templates/invite/list.html:61 +#, python-format +msgid "Valid once, expires %(expiry_date)s" +msgstr "Einmal verwendbar, gültig bis %(expiry_date)s" + +#: uffd/invite/templates/invite/list.html:63 +#, python-format +msgid "Valid, expires %(expiry_date)s" +msgstr "Gültig bis %(expiry_date)s" + +#: uffd/invite/templates/invite/list.html:80 +msgid "Invite Link Details" +msgstr "Details zum Einladungslink" + +#: uffd/invite/templates/invite/list.html:87 +msgid "Type:" +msgstr "Typ:" + +#: uffd/invite/templates/invite/list.html:87 +msgid "Single-use" +msgstr "Einmal verwendbar" + +#: uffd/invite/templates/invite/list.html:87 +#: uffd/invite/templates/invite/new.html:9 +msgid "Multi-use" +msgstr "Mehrfach verwendbar" + +#: uffd/invite/templates/invite/list.html:88 +msgid "Created:" +msgstr "Erstellt:" + +#: uffd/invite/templates/invite/list.html:89 +msgid "Expires:" +msgstr "Ablaufdatum:" + +#: uffd/invite/templates/invite/list.html:90 +msgid "Permissions:" +msgstr "Berechtigungen:" + +#: uffd/invite/templates/invite/list.html:93 +#: uffd/invite/templates/invite/new.html:21 +msgid "Link allows account registration" +msgstr "Link erlaubt Account-Registrierung" + +#: uffd/invite/templates/invite/list.html:95 +#: uffd/invite/templates/invite/new.html:22 +msgid "No account registration allowed" +msgstr "Keine Account-Registrierung möglich" + +#: uffd/invite/templates/invite/list.html:98 +#, python-format +msgid "Link grants users the role \"%(name)s\"" +msgstr "Link gibt Nutzern die Rolle \"%(name)s\"" + +#: uffd/invite/templates/invite/list.html:104 +msgid "Never used" +msgstr "Keine Verwendungen" + +#: uffd/invite/templates/invite/list.html:108 +#, python-format +msgid "Registration of user <a href=\"%(user_url)s\">%(user_name)s</a>" +msgstr "Account-Registrierung von <a href=\"%(user_url)s\">%(user_name)s</a>" + +#: uffd/invite/templates/invite/list.html:111 +#, python-format +msgid "Roles granted to <a href=\"%(user_url)s\">%(user_name)s</a>" +msgstr "Rollen an href=\"%(user_url)s\">%(user_name)s</a> vergeben" + +#: uffd/invite/templates/invite/list.html:122 +msgid "Disable Link" +msgstr "Link deaktivieren" + +#: uffd/invite/templates/invite/list.html:126 +msgid "Reenable Link" +msgstr "Link reaktivieren" + +#: uffd/invite/templates/invite/list.html:140 +msgid "Invite" +msgstr "Einladungslink" + +#: uffd/invite/templates/invite/new.html:6 +msgid "Link Type" +msgstr "Link Typ" + +#: uffd/invite/templates/invite/new.html:8 +msgid "Valid for a single successful use" +msgstr "Für eine erfolgreiche Verwendung gültig" + +#: uffd/invite/templates/invite/new.html:13 +msgid "Valid Until" +msgstr "Ablaufdatum" + +#: uffd/invite/templates/invite/new.html:15 +#, python-format +msgid "Must be within the next %(max_valid_days)d days" +msgstr "Muss innerhalb der nächsten %(max_valid_days)d Tage liegen" + +#: uffd/invite/templates/invite/new.html:19 +#: uffd/signup/templates/signup/start.html:11 +msgid "Account Registration" +msgstr "Account-Registrierung" + +#: uffd/invite/templates/invite/new.html:30 +msgid "Granted Roles" +msgstr "Enthaltene Rollen" + +#: uffd/invite/templates/invite/new.html:35 +#: uffd/mail/templates/mail/list.html:14 uffd/mail/templates/mail/show.html:7 +#: uffd/mfa/templates/mfa/setup.html:98 uffd/mfa/templates/mfa/setup.html:99 +#: uffd/mfa/templates/mfa/setup.html:107 uffd/mfa/templates/mfa/setup.html:157 +#: uffd/mfa/templates/mfa/setup.html:158 uffd/mfa/templates/mfa/setup.html:169 +#: uffd/role/templates/role/list.html:14 +#: uffd/rolemod/templates/rolemod/list.html:9 +#: uffd/rolemod/templates/rolemod/show.html:46 +#: uffd/selfservice/templates/selfservice/self.html:107 +#: uffd/user/templates/group/list.html:10 +#: uffd/user/templates/group/show.html:11 uffd/user/templates/user/show.html:95 +#: uffd/user/templates/user/show.html:127 +msgid "Name" +msgstr "Name" + +#: uffd/invite/templates/invite/new.html:36 +#: uffd/role/templates/role/list.html:15 uffd/role/templates/role/show.html:48 +#: uffd/rolemod/templates/rolemod/list.html:10 +#: uffd/rolemod/templates/rolemod/show.html:28 +#: uffd/selfservice/templates/selfservice/self.html:108 +#: uffd/user/templates/group/list.html:11 uffd/user/templates/user/show.html:96 +#: uffd/user/templates/user/show.html:128 +msgid "Description" +msgstr "Beschreibung" + +#: uffd/invite/templates/invite/new.html:55 +msgid "Create Link" +msgstr "Link erstellen" + +#: uffd/invite/templates/invite/new.html:56 +#: uffd/mail/templates/mail/show.html:28 uffd/mfa/templates/mfa/auth.html:39 +#: uffd/role/templates/role/show.html:14 +#: uffd/rolemod/templates/rolemod/show.html:11 +#: uffd/session/templates/session/deviceauth.html:44 +#: uffd/session/templates/session/deviceauth.html:54 +#: uffd/session/templates/session/devicelogin.html:34 +#: uffd/user/templates/user/show.html:8 +msgid "Cancel" +msgstr "Abbrechen" + +#: uffd/invite/templates/invite/use.html:11 +msgid "Invite Link" +msgstr "Einladungslink" + +#: uffd/invite/templates/invite/use.html:18 +msgid "" +"With this link you can register a new user account with the following " +"roles or add the roles to an existing account:" +msgstr "" +"Mit diesem Link kannst du einen Account mit den folgenden Rollen " +"erstellen oder diese Rollen zu einem existierenden Account hinzufügen:" + +#: uffd/invite/templates/invite/use.html:20 +msgid "With this link you can add the following roles to an existing account:" +msgstr "" +"Mit diesem Link kannst du die folgenden Rollen zu einem existierenden " +"Account hinzufügen:" + +#: uffd/invite/templates/invite/use.html:22 +msgid "With this link you can register a new user account." +msgstr "Mit diesem Link kannst du einen Account registieren." + +#: uffd/invite/templates/invite/use.html:34 +msgid "Add the roles to your account now" +msgstr "Rollen jetzt zu deinem Account hinzufügen" + +#: uffd/invite/templates/invite/use.html:36 +msgid "Logout and switch to a different account" +msgstr "Abmelden und zu einem anderen Account wechseln" + +#: uffd/invite/templates/invite/use.html:39 +msgid "Logout to register a new account" +msgstr "Abmelden um einen neuen Account zu registrieren" + +#: uffd/invite/templates/invite/use.html:43 +msgid "Register a new account" +msgstr "Neuen Account registrieren" + +#: uffd/invite/templates/invite/use.html:46 +msgid "Login and add the roles to your account" +msgstr "Anmelden und die Rollen zu deinem Account hinzufügen" + +#: uffd/mail/views.py:23 +msgid "Forwardings" +msgstr "Weiterleitungen" + +#: uffd/mail/views.py:47 +msgid "Mail mapping updated." +msgstr "Mailweiterleitung geändert." + +#: uffd/mail/views.py:56 +msgid "Deleted mail mapping." +msgstr "Mailweiterleitung gelöscht." + +#: uffd/mail/templates/mail/list.html:15 uffd/mail/templates/mail/show.html:13 +msgid "Receiving addresses" +msgstr "Empfangsadressen" + +#: uffd/mail/templates/mail/list.html:16 uffd/mail/templates/mail/show.html:20 +msgid "Destinations" +msgstr "Zieladressen" + +#: uffd/mail/templates/mail/show.html:16 uffd/mail/templates/mail/show.html:23 +msgid "One address per line" +msgstr "Eine Adresse pro Zeile" + +#: uffd/mail/templates/mail/show.html:27 uffd/role/templates/role/show.html:13 +#: uffd/rolemod/templates/rolemod/show.html:10 +#: uffd/user/templates/user/show.html:7 +msgid "Save" +msgstr "Speichern" + +#: uffd/mail/templates/mail/show.html:30 uffd/mail/templates/mail/show.html:32 +#: uffd/mfa/templates/mfa/setup.html:117 uffd/mfa/templates/mfa/setup.html:179 +#: uffd/role/templates/role/show.html:21 uffd/role/templates/role/show.html:24 +#: uffd/user/templates/user/show.html:11 uffd/user/templates/user/show.html:13 +msgid "Delete" +msgstr "Löschen" + +#: uffd/mfa/views.py:54 msgid "Two-factor authentication was reset" msgstr "Zwei-Faktor-Authentifizierung wurde zurückgesetzt" -#: mfa/views.py:82 +#: uffd/mfa/views.py:83 msgid "Generate recovery codes first!" msgstr "Generiere zuerst die Wiederherstellungscodes!" -#: mfa/views.py:91 +#: uffd/mfa/views.py:92 msgid "Code is invalid" msgstr "Wiederherstellungscode ist ungültig" -#: mfa/views.py:115 +#: uffd/mfa/views.py:116 #, python-format msgid "" "2FA WebAuthn support disabled because import of the fido2 module failed " @@ -39,16 +399,16 @@ msgstr "" "2FA WebAuthn Unterstützung deaktiviert, da das fido2 Modul nicht geladen " "werden konnte (%s)" -#: mfa/views.py:224 +#: uffd/mfa/views.py:225 #, python-format msgid "We received too many invalid attempts! Please wait at least %s." msgstr "Wir haben zu viele fehlgeschlagene Versuche! Bitte warte mindestens %s." -#: mfa/views.py:238 +#: uffd/mfa/views.py:239 msgid "You have exhausted your recovery codes. Please generate new ones now!" msgstr "Du hast keine Wiederherstellungscode mehr. Bitte generiere diese jetzt!" -#: mfa/views.py:241 +#: uffd/mfa/views.py:242 msgid "" "You only have a few recovery codes remaining. Make sure to generate new " "ones before they run out." @@ -56,46 +416,42 @@ msgstr "" "Du hast nur noch wenige Wiederherstellungscodes übrig. Bitte generiere " "diese erneut bevor keine mehr übrig sind." -#: mfa/views.py:245 +#: uffd/mfa/views.py:246 msgid "Two-factor authentication failed" msgstr "Zwei-Faktor-Authentifizierung fehlgeschlagen" -#: mfa/templates/mfa/auth.html:12 +#: uffd/mfa/templates/mfa/auth.html:12 +#: uffd/selfservice/templates/selfservice/self.html:73 msgid "Two-Factor Authentication" msgstr "Zwei-Faktor-Authentifizierung" -#: mfa/templates/mfa/auth.html:17 +#: uffd/mfa/templates/mfa/auth.html:17 msgid "Enable javascript for authentication with U2F/FIDO2 devices" msgstr "Aktiviere Javascript zur Authentifizierung mit U2F/FIDO2 Geräten" -#: mfa/templates/mfa/auth.html:21 +#: uffd/mfa/templates/mfa/auth.html:21 msgid "Authentication with U2F/FIDO2 devices is not supported by your browser" msgstr "" "Authentifizierung mit U2F/FIDO2 Geräten wird von deinem Browser nicht " "unterstützt" -#: mfa/templates/mfa/auth.html:27 +#: uffd/mfa/templates/mfa/auth.html:27 msgid "Authenticate with U2F/FIDO2 device" msgstr "Authentifiziere dich mit einem U2F/FIDO2 Gerät" -#: mfa/templates/mfa/auth.html:30 +#: uffd/mfa/templates/mfa/auth.html:30 msgid "or" msgstr "oder" -#: mfa/templates/mfa/auth.html:33 +#: uffd/mfa/templates/mfa/auth.html:33 msgid "Code from your authenticator app or recovery code" msgstr "Code aus deiner Authentifikator-App oder Wiederherstellungscode" -#: mfa/templates/mfa/auth.html:36 +#: uffd/mfa/templates/mfa/auth.html:36 msgid "Verify" msgstr "Verifizieren" -#: mfa/templates/mfa/auth.html:39 role/templates/role/show.html:14 -#: user/templates/user/show.html:8 -msgid "Cancel" -msgstr "Abbrechen" - -#: mfa/templates/mfa/disable.html:6 +#: uffd/mfa/templates/mfa/disable.html:6 msgid "" "When you proceed, all recovery codes, registered authenticator " "applications and devices will be invalidated.\n" @@ -107,19 +463,23 @@ msgstr "" "Wiederherstellungscodes generieren und das Setup der Anwendungen und " "Geräte erneut durchführen." -#: mfa/templates/mfa/disable.html:11 mfa/templates/mfa/setup.html:32 +#: uffd/mfa/templates/mfa/disable.html:11 uffd/mfa/templates/mfa/setup.html:32 msgid "Disable two-factor authentication" msgstr "Zwei-Faktor-Authentifizierung (2FA) deaktivieren" -#: mfa/templates/mfa/setup.html:18 +#: uffd/mfa/templates/mfa/setup.html:18 +#: uffd/selfservice/templates/selfservice/self.html:79 msgid "Two-factor authentication is currently <strong>enabled</strong>." msgstr "Die Zwei-Faktor-Authentifizierung ist derzeit <strong>aktiviert</strong>." -#: mfa/templates/mfa/setup.html:20 +#: uffd/mfa/templates/mfa/setup.html:20 +#: uffd/selfservice/templates/selfservice/self.html:81 msgid "Two-factor authentication is currently <strong>disabled</strong>." -msgstr "Die Zwei-Faktor-Authentifizierung ist derzeit <strong>deaktiviert</strong>." +msgstr "" +"Die Zwei-Faktor-Authentifizierung ist derzeit " +"<strong>deaktiviert</strong>." -#: mfa/templates/mfa/setup.html:23 +#: uffd/mfa/templates/mfa/setup.html:23 msgid "" "You need to generate recovery codes and setup at least one authentication" " method to enable two-factor authentication." @@ -128,7 +488,7 @@ msgstr "" "Authentifizierungsmethode hinzufügen um Zwei-Faktor-Authentifizierung " "nutzen zu können." -#: mfa/templates/mfa/setup.html:25 +#: uffd/mfa/templates/mfa/setup.html:25 msgid "" "You need to setup at least one authentication method to enable two-factor" " authentication." @@ -136,15 +496,16 @@ msgstr "" "Du musst mindestens eine Authentifizierungsmethode hinzufügen um Zwei-" "Faktor-Authentifizierung nutzen zu können." -#: mfa/templates/mfa/setup.html:36 +#: uffd/mfa/templates/mfa/setup.html:36 msgid "Reset two-factor configuration" msgstr "Zwei-Faktor-Authentifizierung zurücksetzen" -#: mfa/templates/mfa/setup.html:46 mfa/templates/mfa/setup_recovery.html:5 +#: uffd/mfa/templates/mfa/setup.html:46 +#: uffd/mfa/templates/mfa/setup_recovery.html:5 msgid "Recovery Codes" msgstr "Wiederherstellungscodes" -#: mfa/templates/mfa/setup.html:48 +#: uffd/mfa/templates/mfa/setup.html:48 msgid "" "Recovery codes allow you to login and setup new two-factor methods when " "you lost your registered second factor." @@ -152,7 +513,7 @@ msgstr "" "Wiederherstellungscodes erlauben die Anmeldung und das erneute Hinzufügen" " einer Zwei-Faktor-Methode, falls der Zweite Faktor verloren geht." -#: mfa/templates/mfa/setup.html:52 +#: uffd/mfa/templates/mfa/setup.html:52 msgid "" "You need to setup recovery codes before you can setup up authenticator " "apps or U2F/FIDO2 devices." @@ -160,75 +521,63 @@ msgstr "" "Du musst Wiederherstellungscodes generieren bevor du einen " "Authentifikator-App oder ein U2F/FIDO2 Gerät hinzufen kannst." -#: mfa/templates/mfa/setup.html:54 +#: uffd/mfa/templates/mfa/setup.html:54 msgid "Each code can only be used once." msgstr "Jeder Code kann nur einmal verwendet werden." -#: mfa/templates/mfa/setup.html:62 +#: uffd/mfa/templates/mfa/setup.html:62 msgid "Generate recovery codes to enable two-factor authentication" msgstr "" "Generiere Wiederherstellungscodes um die Zwei-Faktor-Authentifizierung zu" " aktivieren" -#: mfa/templates/mfa/setup.html:66 +#: uffd/mfa/templates/mfa/setup.html:66 msgid "Generate new recovery codes" msgstr "Generiere neue Wiederherstellungscodes" -#: mfa/templates/mfa/setup.html:75 +#: uffd/mfa/templates/mfa/setup.html:75 msgid "You have no remaining recovery codes." msgstr "Du hast keine Wiederherstellungscodes übrig." -#: mfa/templates/mfa/setup.html:85 +#: uffd/mfa/templates/mfa/setup.html:85 msgid "Authenticator Apps (TOTP)" msgstr "Authentifikator-Apps (TOTP)" -#: mfa/templates/mfa/setup.html:87 +#: uffd/mfa/templates/mfa/setup.html:87 msgid "Use an authenticator application on your mobile device as a second factor." msgstr "Nutze eine Authentifikator-App auf deinem Mobilgerät als zweiten Faktor." -#: mfa/templates/mfa/setup.html:90 +#: uffd/mfa/templates/mfa/setup.html:90 msgid "" "The authenticator app generates a 6-digit one-time code each time you " "login.\n" "\t\t\tCompatible apps are freely available for most phones." msgstr "" "Die Authentifikator-App generiert ein 6-stelliges Einmalpasswort für " -"jeden Login.Passende Apps sind kostenlos verfügbar für die meisten " +"jeden Login. Passende Apps sind kostenlos verfügbar für die meisten " "Mobilgeräte." -#: mfa/templates/mfa/setup.html:98 mfa/templates/mfa/setup.html:99 -#: mfa/templates/mfa/setup.html:107 mfa/templates/mfa/setup.html:157 -#: mfa/templates/mfa/setup.html:158 mfa/templates/mfa/setup.html:169 -msgid "Name" -msgstr "Name" - -#: mfa/templates/mfa/setup.html:100 +#: uffd/mfa/templates/mfa/setup.html:100 msgid "Setup new app" msgstr "Neue App hinzufügen" -#: mfa/templates/mfa/setup.html:108 mfa/templates/mfa/setup.html:170 +#: uffd/mfa/templates/mfa/setup.html:108 uffd/mfa/templates/mfa/setup.html:170 msgid "Registered On" msgstr "Registriert am" -#: mfa/templates/mfa/setup.html:117 mfa/templates/mfa/setup.html:179 -#: role/templates/role/show.html:21 role/templates/role/show.html:24 -#: user/templates/user/show.html:11 user/templates/user/show.html:13 -msgid "Delete" -msgstr "Löschen" - -#: mfa/templates/mfa/setup.html:122 +#: uffd/mfa/templates/mfa/setup.html:122 msgid "No authenticator apps registered yet" msgstr "Bisher keine Authentifikator-Apps registriert" -#: mfa/templates/mfa/setup.html:134 +#: uffd/mfa/templates/mfa/setup.html:134 msgid "U2F and FIDO2 Devices" msgstr "U2F und FIDO2 Geräte" -#: mfa/templates/mfa/setup.html:136 +#: uffd/mfa/templates/mfa/setup.html:136 msgid "Use an U2F or FIDO2 compatible hardware security key as a second factor." msgstr "Nutze einen U2F oder FIDO2 kompatiblen Key als zweiten Faktor." -#: mfa/templates/mfa/setup.html:139 +#: uffd/mfa/templates/mfa/setup.html:139 msgid "" "U2F and FIDO2 devices are not supported by all browsers and can be " "particularly difficult to use on mobile\n" @@ -241,25 +590,25 @@ msgstr "" "wird dringend empfohlen ebenfalls eine Authentifikator-App " "hinzuzufügen</strong> um einen Login mit allen Browsern zu ermöglichen." -#: mfa/templates/mfa/setup.html:147 +#: uffd/mfa/templates/mfa/setup.html:147 msgid "U2F/FIDO2 support not enabled" msgstr "U2F/FIDO2 Unterstützung nicht aktiviert" -#: mfa/templates/mfa/setup.html:151 +#: uffd/mfa/templates/mfa/setup.html:151 msgid "Enable javascript in your browser to use U2F and FIDO2 devices!" msgstr "" "Aktiviere Javascript in deinem Browser, um U2F und FIDO2 Geräte nutzen zu" " können!" -#: mfa/templates/mfa/setup.html:161 +#: uffd/mfa/templates/mfa/setup.html:161 msgid "Setup new device" msgstr "Neues Gerät hinzufügen" -#: mfa/templates/mfa/setup.html:184 +#: uffd/mfa/templates/mfa/setup.html:184 msgid "No U2F/FIDO2 devices registered yet" msgstr "Bisher kein U2F/FIDO2 Gerät registriert" -#: mfa/templates/mfa/setup_recovery.html:8 +#: uffd/mfa/templates/mfa/setup_recovery.html:8 msgid "" "Recovery codes allow you to login when you lose access to your " "authenticator app or U2F/FIDO device. Each code can\n" @@ -269,7 +618,7 @@ msgstr "" "Authentifikator-App oder das U2F/FIDO2 Gerät verloren geht. Jeder Code " "kann nur einmal verwendet werden." -#: mfa/templates/mfa/setup_recovery.html:21 +#: uffd/mfa/templates/mfa/setup_recovery.html:21 msgid "" "These are your new recovery codes. Make sure to store them in a safe " "place or you risk losing access to your\n" @@ -279,15 +628,15 @@ msgstr "" "Ort, sonst könntest du den Zugriff auf dein Konto verlieren. Alle " "vorherigen Wiederherstellungscodes sind nun ungültig." -#: mfa/templates/mfa/setup_recovery.html:28 +#: uffd/mfa/templates/mfa/setup_recovery.html:28 msgid "Download codes" msgstr "Codes herunterladen" -#: mfa/templates/mfa/setup_recovery.html:30 +#: uffd/mfa/templates/mfa/setup_recovery.html:30 msgid "Print codes" msgstr "Codes ausdrucken" -#: mfa/templates/mfa/setup_totp.html:6 +#: uffd/mfa/templates/mfa/setup_totp.html:6 msgid "" "Install an authenticator application on your mobile device like FreeOTP " "or Google Authenticator and scan this QR\n" @@ -297,7 +646,7 @@ msgstr "" "oder Google Authenticator and scanne diesen QR Code. Auf Geräten von " "Apple kann die App \"Authenticator\" verwendet werden." -#: mfa/templates/mfa/setup_totp.html:18 +#: uffd/mfa/templates/mfa/setup_totp.html:18 msgid "" "If you are on your mobile device and cannot scan the code, you can click " "on it to open it with your\n" @@ -310,452 +659,882 @@ msgstr "" "das nicht funktioniert, gib die folgenden Angaben manuell in die " "Authentifikator-App ein:" -#: mfa/templates/mfa/setup_totp.html:23 +#: uffd/mfa/templates/mfa/setup_totp.html:23 msgid "Issuer" msgstr "Herausgeber" -#: mfa/templates/mfa/setup_totp.html:24 +#: uffd/mfa/templates/mfa/setup_totp.html:24 msgid "Account" msgstr "Konto" -#: mfa/templates/mfa/setup_totp.html:25 +#: uffd/mfa/templates/mfa/setup_totp.html:25 msgid "Secret" msgstr "Geheimnis" -#: mfa/templates/mfa/setup_totp.html:26 +#: uffd/mfa/templates/mfa/setup_totp.html:26 msgid "Type" msgstr "Typ" -#: mfa/templates/mfa/setup_totp.html:27 +#: uffd/mfa/templates/mfa/setup_totp.html:27 msgid "Digits" msgstr "Zeichen" -#: mfa/templates/mfa/setup_totp.html:28 +#: uffd/mfa/templates/mfa/setup_totp.html:28 msgid "Hash algorithm" msgstr "Hash-Algorithmus" -#: mfa/templates/mfa/setup_totp.html:29 +#: uffd/mfa/templates/mfa/setup_totp.html:29 msgid "Interval/period" msgstr "Intervall/Dauer" -#: mfa/templates/mfa/setup_totp.html:29 +#: uffd/mfa/templates/mfa/setup_totp.html:29 msgid "seconds" msgstr "Sekunden" -#: mfa/templates/mfa/setup_totp.html:38 +#: uffd/mfa/templates/mfa/setup_totp.html:38 msgid "Verify and complete setup" msgstr "Verifiziere und beende das Setup" -#: role/views.py:44 session/views.py:126 user/views_group.py:14 -#: user/views_user.py:22 +#: uffd/oauth2/views.py:95 uffd/selfservice/views.py:79 +#: uffd/session/views.py:93 +#, python-format +msgid "" +"We received too many requests from your ip address/network! Please wait " +"at least %(delay)s." +msgstr "" +"Wir haben zu viele Anfragen von deiner IP-Adresses bzw. aus deinem " +"Netzwerk empfangen! Bitte warte mindestens %(delay)s." + +#: uffd/oauth2/views.py:103 +msgid "Device login is currently not available. Try again later!" +msgstr "Geräte-Login ist gerade nicht verfügbar. Versuche es später nochmal!" + +#: uffd/oauth2/templates/oauth2/logout.html:10 uffd/templates/base.html:99 +msgid "Logout" +msgstr "Abmelden" + +#: uffd/oauth2/templates/oauth2/logout.html:15 +msgid "Javascript is required for automatic logout" +msgstr "Für das automatische Abmelden muss Javascript aktiviert sein" + +#: uffd/oauth2/templates/oauth2/logout.html:17 +msgid "" +"While you successfully logged out of the Single-Sign-On service, you may " +"still be logged in on these services:" +msgstr "" +"Während du nun aus dem Single-Sign-On abgemeldet bist, bist du eventuell " +"weiterhin in folgenden Diensten angemeldet:" + +#: uffd/oauth2/templates/oauth2/logout.html:30 +msgid "" +"Please wait until you have been automatically logged out of all services " +"or make sure of this yourself." +msgstr "" +"Bitte warte, bis das automatische Abmelden bei allen Diensten " +"abgeschlossen ist oder melde dich überall manuell ab." + +#: uffd/oauth2/templates/oauth2/logout.html:34 +msgid "Logging you out on all services ..." +msgstr "Melde dich bei allen Diensten ab ..." + +#: uffd/oauth2/templates/oauth2/logout.html:38 +msgid "Skip this and continue" +msgstr "Automatisches Abmelden überspringen" + +#: uffd/oauth2/templates/oauth2/logout.html:82 +msgid "Done, redirecting ..." +msgstr "Abgeschlossen, leite weiter ..." + +#: uffd/oauth2/templates/oauth2/logout.html:86 +msgid "Log out failed on some services. Retry?" +msgstr "" +"Automatisches Abmelden bei einigen Diensten fehlgeschlagen. Nochmal " +"versuchen?" + +#: uffd/role/views.py:44 uffd/rolemod/views.py:36 uffd/rolemod/views.py:45 +#: uffd/rolemod/views.py:60 uffd/session/views.py:130 +#: uffd/user/views_group.py:14 uffd/user/views_user.py:22 msgid "Access denied" msgstr "Zugriff verweigert" -#: role/views.py:51 user/templates/user/show.html:21 -#: user/templates/user/show.html:93 +#: uffd/role/views.py:51 uffd/selfservice/templates/selfservice/self.html:92 +#: uffd/user/templates/user/list.html:20 uffd/user/templates/user/show.html:21 +#: uffd/user/templates/user/show.html:90 msgid "Roles" msgstr "Rollen" -#: role/views.py:101 +#: uffd/role/views.py:101 msgid "Locked roles cannot be deleted" msgstr "Gesperrte Rollen können nicht gelöscht werden" -#: role/templates/role/list.html:8 user/templates/user/list.html:8 -msgid "New" -msgstr "Neu" - -#: role/templates/role/list.html:14 -msgid "roleid" -msgstr "Rollen ID" - -#: role/templates/role/list.html:15 role/templates/role/show.html:86 -#: role/templates/role/show.html:127 user/templates/group/list.html:10 -#: user/templates/group/show.html:11 user/templates/user/show.html:98 -#: user/templates/user/show.html:130 -msgid "name" -msgstr "Name" - -#: role/templates/role/list.html:16 role/templates/role/show.html:87 -#: role/templates/role/show.html:128 user/templates/group/list.html:11 -#: user/templates/user/show.html:99 user/templates/user/show.html:131 -msgid "description" -msgstr "Beschreibung" +#: uffd/role/templates/role/list.html:23 +msgid "<empty name>" +msgstr "<leerer Name>" -#: role/templates/role/show.html:6 +#: uffd/role/templates/role/show.html:6 msgid "" "Name, moderator group, included roles and groups of this role are managed" " externally." msgstr "" -"Name, Moderator:innengruppe, enthaltene Rollen und Gruppen dieser Rolle " +"Name, Moderationsgruppe, enthaltene Rollen und Gruppen dieser Rolle " "werden extern verwaltet." -#: role/templates/role/show.html:13 -#: selfservice/templates/selfservice/self.html:67 -#: user/templates/user/show.html:7 -msgid "Save" -msgstr "Speichern" +#: uffd/role/templates/role/show.html:17 +msgid "" +"All non-service users will be removed as members from this role and get " +"its permissions implicitly. Are you sure?" +msgstr "" +"Alle Benutzer-Accounts, die keine Service-Accounts sind, verlieren diese " +"Rolle und erhalten dessen Berechtigungen implizit." -#: role/templates/role/show.html:17 role/templates/role/show.html:23 +#: uffd/role/templates/role/show.html:17 uffd/role/templates/role/show.html:23 msgid "Set as default" msgstr "Als Default setzen" -#: role/templates/role/show.html:19 +#: uffd/role/templates/role/show.html:19 uffd/role/templates/role/show.html:21 +#: uffd/selfservice/templates/selfservice/self.html:123 +#: uffd/user/templates/user/show.html:11 +msgid "Are you sure?" +msgstr "Wirklich fortfahren?" + +#: uffd/role/templates/role/show.html:19 msgid "Unset as default" msgstr "Nicht mehr als Default setzen" -#: role/templates/role/show.html:29 +#: uffd/role/templates/role/show.html:29 msgid "Settings" msgstr "Einstellungen" -#: role/templates/role/show.html:32 +#: uffd/role/templates/role/show.html:32 msgid "Included roles" msgstr "Enthaltene Rollen" -#: role/templates/role/show.html:35 role/templates/role/show.html:122 +#: uffd/role/templates/role/show.html:35 uffd/role/templates/role/show.html:122 msgid "Included groups" msgstr "Enthaltene Gruppen" -#: role/templates/role/show.html:42 +#: uffd/role/templates/role/show.html:42 msgid "Role Name" msgstr "Rollenname" -#: role/templates/role/show.html:48 -msgid "Description" -msgstr "Beschreibung" - -#: role/templates/role/show.html:54 +#: uffd/role/templates/role/show.html:54 msgid "Moderator Group" -msgstr "Moderator:innengruppe" +msgstr "Moderationsgruppe" -#: role/templates/role/show.html:56 +#: uffd/role/templates/role/show.html:56 msgid "No Moderator Group" -msgstr "Keine Moderator:innengruppe" +msgstr "Keine Moderationsgruppe" -#: role/templates/role/show.html:63 +#: uffd/role/templates/role/show.html:63 msgid "Moderators" -msgstr "Moderator:innen" +msgstr "Moderatoren" -#: role/templates/role/show.html:71 user/templates/group/show.html:15 +#: uffd/role/templates/role/show.html:71 +#: uffd/rolemod/templates/rolemod/show.html:18 +#: uffd/user/templates/group/show.html:15 msgid "Members" msgstr "Mitglieder" -#: role/templates/role/show.html:81 +#: uffd/role/templates/role/show.html:81 msgid "Roles to include groups from recursively" msgstr "Rollen, deren Gruppen rekursiv enthalten sein sollen" -#: role/templates/role/show.html:88 +#: uffd/role/templates/role/show.html:86 uffd/role/templates/role/show.html:127 +msgid "name" +msgstr "Name" + +#: uffd/role/templates/role/show.html:87 uffd/role/templates/role/show.html:128 +msgid "description" +msgstr "Beschreibung" + +#: uffd/role/templates/role/show.html:88 msgid "currently includes groups" msgstr "derzeit enthaltene Gruppen" -#: role/templates/role/show.html:129 +#: uffd/role/templates/role/show.html:129 msgid "2FA required" msgstr "2FA erforderlich" -#: selfservice/views.py:24 +#: uffd/rolemod/views.py:25 +msgid "Moderation" +msgstr "Moderation" + +#: uffd/rolemod/views.py:49 +msgid "Description too long" +msgstr "Beschreibung zu lang" + +#: uffd/rolemod/views.py:67 +msgid "Member removed" +msgstr "Mitglied entfernt" + +#: uffd/rolemod/templates/rolemod/show.html:8 +msgid "Invite Members" +msgstr "Mitglieder einladen" + +#: uffd/rolemod/templates/rolemod/show.html:15 +msgid "Overview" +msgstr "Ãœbersicht" + +#: uffd/rolemod/templates/rolemod/show.html:24 +msgid "Role name" +msgstr "Rollenname" + +#: uffd/rolemod/templates/rolemod/show.html:32 +msgid "Moderators:" +msgstr "Moderatoren:" + +#: uffd/rolemod/templates/rolemod/show.html:42 +msgid "Role members:" +msgstr "Mitglieder:" + +#: uffd/rolemod/templates/rolemod/show.html:55 +msgid "Remove" +msgstr "Entfernen" + +#: uffd/selfservice/views.py:25 msgid "Selfservice" -msgstr "" +msgstr "Selfservice" -#: selfservice/views.py:37 +#: uffd/selfservice/views.py:37 msgid "Display name changed." msgstr "Anzeigename geändert." -#: selfservice/views.py:39 +#: uffd/selfservice/views.py:39 msgid "Display name is not valid." msgstr "Anzeigename ist nicht valide." -#: selfservice/views.py:42 -msgid "Passwords do not match" -msgstr "Die Passwörter stimmen nicht überein" +#: uffd/selfservice/views.py:42 +msgid "We sent you an email, please verify your mail address." +msgstr "Wir haben dir eine E-Mail gesendet, bitte prüfe deine E-Mail-Adresse." -#: selfservice/views.py:45 -msgid "Password changed." -msgstr "Passwort geändert." +#: uffd/selfservice/views.py:56 +msgid "Password changed" +msgstr "Passwort geändert" -#: selfservice/views.py:48 -msgid "Password could not be set." -msgstr "Das Passwort konnte nicht gesetzt werden." +#: uffd/selfservice/views.py:59 +msgid "Invalid password" +msgstr "Passwort ungültig" -#: selfservice/views.py:51 -msgid "We sent you an email, please verify your mail address." -msgstr "Wir haben dir eine E-Mail gesendet, bitte prüfe deine E-Mail-Adresse." +#: uffd/selfservice/views.py:77 +#, python-format +msgid "" +"We received too many password reset requests for this user! Please wait " +"at least %(delay)s." +msgstr "" +"Wir haben zu viele fehlgeschlagene Anmeldeversuche für diesen Account! " +"Bitte warte mindestens %(delay)s." + +#: uffd/selfservice/views.py:83 +msgid "" +"We sent a mail to this user's mail address if you entered the correct " +"mail and login name combination" +msgstr "" +"Falls E-Mail-Adresse und Benutzername richtig waren, wurde eine E-Mail an" +" die Adresse gesendet." + +#: uffd/selfservice/views.py:93 uffd/selfservice/views.py:121 +msgid "Token expired, please try again." +msgstr "Link abgelaufen, bitte versuche es erneut." + +#: uffd/selfservice/views.py:101 +msgid "You need to set a password, please try again." +msgstr "Password fehlt, bitte versuche es erneut." + +#: uffd/selfservice/views.py:104 +msgid "Passwords do not match, please try again." +msgstr "Die Passwörter stimmen nicht überein, bitte versuche es erneut" + +#: uffd/selfservice/views.py:108 +msgid "Password ist not valid, please try again." +msgstr "Ungültiges Passwort, bitte versuche es erneut" + +#: uffd/selfservice/views.py:111 +msgid "New password set" +msgstr "Passwort geändert" + +#: uffd/selfservice/views.py:129 +msgid "New mail set" +msgstr "E-Mail-Adresse geändert" + +#: uffd/selfservice/views.py:140 +msgid "Leaving roles is disabled" +msgstr "Verlassen von Rollen ist deaktiviert" + +#: uffd/selfservice/views.py:147 +#, python-format +msgid "You left role \"%(role_name)s\"" +msgstr "Rolle \"%(role_name)s\" verlassen" + +#: uffd/selfservice/views.py:211 +#, python-format +msgid "Mail to \"%(mail_address)s\" could not be sent!" +msgstr "E-Mail an \"%(mail_address)s\" konnte nicht gesendet werden!" -#: selfservice/templates/selfservice/forgot_password.html:11 +#: uffd/selfservice/templates/selfservice/forgot_password.html:11 msgid "Forgot password" msgstr "Passwort vergessen" -#: selfservice/templates/selfservice/forgot_password.html:14 -#: selfservice/templates/selfservice/self.html:25 -#: session/templates/session/login.html:14 user/templates/user/show.html:51 +#: uffd/selfservice/templates/selfservice/forgot_password.html:14 +#: uffd/selfservice/templates/selfservice/self.html:22 +#: uffd/session/templates/session/login.html:14 +#: uffd/signup/templates/signup/start.html:19 +#: uffd/user/templates/user/list.html:18 uffd/user/templates/user/show.html:48 msgid "Login Name" -msgstr "Anmeldename" +msgstr "Benutzername" -#: selfservice/templates/selfservice/forgot_password.html:18 +#: uffd/selfservice/templates/selfservice/forgot_password.html:18 msgid "Mail Address" -msgstr "Mail-Adresse" +msgstr "E-Mail-Adresse" -#: selfservice/templates/selfservice/forgot_password.html:22 +#: uffd/selfservice/templates/selfservice/forgot_password.html:22 msgid "Send password reset mail" msgstr "Passwort-Zurücksetzen-Mail versenden" -#: selfservice/templates/selfservice/self.html:7 +#: uffd/selfservice/templates/selfservice/self.html:7 msgid "" "Some permissions require you to setup two-factor authentication.\n" "\tThese permissions are not in effect until you do that." msgstr "" +"Einige deiner Berechtigungen erfordern das Einrichten von Zwei-Faktor-" +"Authentifizierung.\n" +"\tDiese Berechtigungen werden erst aktiv, wenn du dies getan hast." -#: selfservice/templates/selfservice/self.html:13 -msgid "Manage two-factor authentication" -msgstr "Zwei-Faktor-Authentifizierung (2FA) bearbeiten" +#: uffd/selfservice/templates/selfservice/self.html:14 +#: uffd/user/templates/user/show.html:18 +msgid "Profile" +msgstr "Profile" -#: selfservice/templates/selfservice/self.html:21 -msgid "Uid" +#: uffd/selfservice/templates/selfservice/self.html:15 +msgid "" +"Your profile information is used by all services that are integrated into" +" the Single-Sign-On. Your e-mail address is also used for password " +"recovery." msgstr "" - -#: selfservice/templates/selfservice/self.html:29 -#: user/templates/user/show.html:66 +"Deine Profilangaben werden von allen Diensten verwendet, die an das " +"Single-Sign-On angeschlossen sind. Die E-Mail-Adresse wird außerdem für " +"die „Passwort vergessen“ genutzt." + +#: uffd/selfservice/templates/selfservice/self.html:16 +msgid "Changes may take serveral minutes to be visible in all services." +msgstr "Änderungen sind erst nach einigen Minuten in allen Diensten sichtbar." + +#: uffd/selfservice/templates/selfservice/self.html:26 +#: uffd/user/templates/user/show.html:28 +msgid "User ID" +msgstr "Benutzer ID" + +#: uffd/selfservice/templates/selfservice/self.html:31 +#: uffd/signup/templates/signup/start.html:32 +#: uffd/user/templates/user/list.html:19 uffd/user/templates/user/show.html:63 msgid "Display Name" msgstr "Anzeigename" -#: selfservice/templates/selfservice/self.html:33 -#: user/templates/user/show.html:73 -msgid "Mail" +#: uffd/selfservice/templates/selfservice/self.html:35 +#: uffd/signup/templates/signup/start.html:39 +msgid "E-Mail Address" msgstr "E-Mail-Adresse" -#: selfservice/templates/selfservice/self.html:36 -msgid "We will send you a confirmation mail to set a new mail address." +#: uffd/selfservice/templates/selfservice/self.html:38 +msgid "We will send you a confirmation mail to this address if you change it" msgstr "" "Wir werden dir eine Bestätigungsmail zum Setzen der neuen E-Mail-Adresse " "senden." -#: selfservice/templates/selfservice/self.html:40 -#: session/templates/session/login.html:18 user/templates/user/show.html:80 +#: uffd/selfservice/templates/selfservice/self.html:41 +msgid "Update Profile" +msgstr "Änderungen speichern" + +#: uffd/selfservice/templates/selfservice/self.html:50 +#: uffd/session/templates/session/login.html:18 +#: uffd/signup/templates/signup/start.html:46 +#: uffd/user/templates/user/show.html:77 msgid "Password" msgstr "Passwort" -#: selfservice/templates/selfservice/self.html:43 -#: selfservice/templates/selfservice/set_password.html:17 -#: user/templates/user/show.html:87 +#: uffd/selfservice/templates/selfservice/self.html:51 msgid "" -"At least 8 and at most 256 characters, no other special requirements. But" -" please don't be stupid, do use a password manager." +"Your login password for the Single-Sign-On. Only enter it on the Single-" +"Sign-On login page! No other legit websites will ask you for this " +"password. We do not ever need your password to assist you." msgstr "" -"Mindestens 8 und maximal 256 Zeichen, keine weiteren Einschränkungen. " -"Bitte sei nicht dumm und verwende einen Passwort-Manager." +"Dein Passwort zur Anmeldung im Single-Sign-On. Gib dieses Passwort " +"ausschließlich auf der Anmeldeseite des Single-Sign-Ons ein! Keine andere" +" Webseite wird dich nach diesem Passwort fragen. Es wird auch niemals für" +" Support-Anfragen benötigt." -#: selfservice/templates/selfservice/self.html:47 -msgid "Password Repeat" +#: uffd/selfservice/templates/selfservice/self.html:56 +#: uffd/selfservice/templates/selfservice/set_password.html:14 +msgid "New Password" +msgstr "Neues Passwort" + +#: uffd/selfservice/templates/selfservice/self.html:58 +#: uffd/user/templates/user/show.html:84 +msgid "At least 8 and at most 256 characters, no other special requirements." +msgstr "Mindestens 8 und maximal 256 Zeichen, keine weiteren Einschränkungen." + +#: uffd/selfservice/templates/selfservice/self.html:62 +#: uffd/selfservice/templates/selfservice/set_password.html:21 +#: uffd/signup/templates/signup/start.html:53 +msgid "Repeat Password" msgstr "Passwort wiederholen" -#: selfservice/templates/selfservice/self.html:53 -msgid "You have this role" -msgstr "Du hast diese Rolle" +#: uffd/selfservice/templates/selfservice/self.html:64 +msgid "Change Password" +msgstr "Passwort ändern" + +#: uffd/selfservice/templates/selfservice/self.html:74 +msgid "" +"Setting up Two-Factor Authentication (2FA) adds an additional step to the" +" Single-Sign-On login and increases the security of your account " +"significantly." +msgstr "" +"Zwei-Faktor-Authentifizierung (2FA) fügt einen zusätzlichen Schritt zur " +"Anmeldung im Single-Sign-On hinzu und verbessert damit die Sicherheit " +"deines Accounts erheblich." + +#: uffd/selfservice/templates/selfservice/self.html:84 +msgid "Manage two-factor authentication" +msgstr "Zwei-Faktor-Authentifizierung (2FA) verwalten" + +#: uffd/selfservice/templates/selfservice/self.html:93 +msgid "" +"Aside from a set of base permissions, your roles determine the " +"permissions of your account." +msgstr "" +"Deine Berechtigungen werden, von einigen Basis-Berechtigungen abgesehen, " +"von deinen Rollen bestimmt" + +#: uffd/selfservice/templates/selfservice/self.html:95 +#, python-format +msgid "" +"See <a href=\"%(services_url)s\">Services</a> for an overview of your " +"current permissions." +msgstr "" +"Auf <a href=\"%(services_url)s\">Dienste</a> erhälst du einen Ãœberblick " +"über deine aktuellen Berechtigungen." + +#: uffd/selfservice/templates/selfservice/self.html:100 +msgid "Administrators and role moderators can invite you to new roles." +msgstr "Administratoren und Rollen-Moderatoren können dich zu Rollen einladen." + +#: uffd/selfservice/templates/selfservice/self.html:102 +msgid "Administrators can add new roles to your account." +msgstr "Administratoren können dich zu neuen Rollen hinzufügen." + +#: uffd/selfservice/templates/selfservice/self.html:117 +msgid "" +"Some permissions in this role require you to setup two-factor " +"authentication" +msgstr "" +"Einige Berechtigungen dieser Rolle erfordern das Einrichten von Zwei-" +"Faktor-Authentifikation" -#: selfservice/templates/selfservice/self.html:55 -msgid "You currently have these roles" -msgstr "Du hast aktuell folgende Rollen" +#: uffd/selfservice/templates/selfservice/self.html:124 +msgid "Leave" +msgstr "Verlassen" -#: selfservice/templates/selfservice/self.html:63 -msgid "You currently don't have any roles." -msgstr "Du hast aktuell keine Rollen." +#: uffd/selfservice/templates/selfservice/self.html:132 +msgid "You currently don't have any roles" +msgstr "Du hast derzeit keine Rollen" -#: selfservice/templates/selfservice/set_password.html:11 +#: uffd/selfservice/templates/selfservice/set_password.html:11 msgid "Reset password" msgstr "Passwort zurücksetzen" -#: selfservice/templates/selfservice/set_password.html:14 -msgid "New Password" -msgstr "Neues Passwort" - -#: selfservice/templates/selfservice/set_password.html:21 -msgid "Repeat Password" -msgstr "Passwort wiederholen" +#: uffd/selfservice/templates/selfservice/set_password.html:17 +#: uffd/signup/templates/signup/start.html:49 +msgid "" +"At least 8 and at most 256 characters, no other special requirements. But" +" please don't be stupid, do use a password manager." +msgstr "" +"Mindestens 8 und maximal 256 Zeichen, keine weiteren Einschränkungen. " +"Bitte sei nicht dumm und verwende einen Passwort-Manager." -#: selfservice/templates/selfservice/set_password.html:25 +#: uffd/selfservice/templates/selfservice/set_password.html:25 msgid "Set password" msgstr "Passwort setzen" -#: services/views.py:83 +#: uffd/services/views.py:83 msgid "Services" -msgstr "" +msgstr "Dienste" -#: services/templates/services/overview.html:8 +#: uffd/services/templates/services/overview.html:8 msgid "" "Some services may not be publicly listed! Log in to see all services you " "have access to." msgstr "" -"Einige Services sind eventuell nicht öffentlich aufgelistet! Melde dich " -"an um alle deine Service zu sehen." +"Einige Dienste sind eventuell nicht öffentlich aufgelistet! Melde dich an" +" um alle Dienste zu sehen, auf die du Zugriff hast." -#: services/templates/services/overview.html:44 +#: uffd/services/templates/services/overview.html:44 msgid "No access" msgstr "Kein Zugriff" -#: services/templates/services/overview.html:78 -#: user/templates/user/list.html:58 user/templates/user/list.html:81 +#: uffd/services/templates/services/overview.html:78 +#: uffd/user/templates/user/list.html:58 uffd/user/templates/user/list.html:79 msgid "Close" msgstr "Schließen" -#: session/views.py:87 +#: uffd/session/views.py:91 #, python-format msgid "" "We received too many invalid login attempts for this user! Please wait at" -" least %s." -msgstr "" -"Wir haben zu viele fehlgeschlagene Anmeldeversuche für diesen Account! " -"Bitte warte mindestens %s." - -#: session/views.py:89 -#, python-format -msgid "" -"We received too many requests from your ip address/network! Please wait " -"at least %s." +" least %(delay)s." msgstr "" -"Wir haben zu viele Anfragen von der IP Adresses / aus deinem Netzwerk " -"bekommen! Bitte warte mindestens %s." +"Wir haben zu viele fehlgeschlagene Anmeldeversuche für diesen Account " +"erhalten! Bitte warte mindestens %(delay)s." -#: session/views.py:95 +#: uffd/session/views.py:99 msgid "Login name or password is wrong" -msgstr "Der Anmeldename oder das Passwort ist falsch" +msgstr "Der Benutzername oder das Passwort ist falsch" -#: session/views.py:98 +#: uffd/session/views.py:102 msgid "You do not have access to this service" msgstr "Du hast keinen Zugriff auf diesen Service" -#: session/views.py:110 session/views.py:121 +#: uffd/session/views.py:114 uffd/session/views.py:125 msgid "You need to login first" msgstr "Du musst dich erst anmelden" -#: session/templates/session/login.html:22 +#: uffd/session/views.py:147 uffd/session/views.py:157 +msgid "Initiation code is no longer valid" +msgstr "Startcode ist nicht mehr gültig" + +#: uffd/session/views.py:161 +msgid "Invalid confirmation code" +msgstr "Ungültiger Bestätigungscode" + +#: uffd/session/views.py:173 uffd/session/views.py:184 +msgid "Invalid initiation code" +msgstr "Ungültiger Startcode" + +#: uffd/session/templates/session/deviceauth.html:17 +#: uffd/templates/base.html:92 +msgid "Authorize Device Login" +msgstr "Gerätelogin erlauben" + +#: uffd/session/templates/session/deviceauth.html:20 +msgid "Log into a service on another device without entering your password." +msgstr "" +"Melde dich an einem Dienst auf einem anderen Gerät an ohne dein Password " +"eingeben zu müssen." + +#: uffd/session/templates/session/deviceauth.html:23 +#: uffd/session/templates/session/devicelogin.html:18 +msgid "Initiation Code" +msgstr "Startcode" + +#: uffd/session/templates/session/deviceauth.html:32 +#: uffd/session/templates/session/devicelogin.html:23 +msgid "Confirmation Code" +msgstr "Bestätigungscode" + +#: uffd/session/templates/session/deviceauth.html:38 +msgid "" +"Start logging into a service on the other device and chose \"Device " +"Login\" on the login page. Enter the displayed initiation code in the box" +" above." +msgstr "" +"Beginne die Anmeldung an einem Dienst auf dem anderen Gerät und wähle " +"\"Gerätelogin\" auf der Anmeldeseite aus. Gib den angezeigten Startcode " +"oben ein." + +#: uffd/session/templates/session/deviceauth.html:41 +#: uffd/session/templates/session/devicelogin.html:30 +msgid "Continue" +msgstr "Weiter" + +#: uffd/session/templates/session/deviceauth.html:48 +#, python-format +msgid "Authorize the login for service <b>%(service_name)s</b>?" +msgstr "Anmeldung an Dienst <b>%(service_name)s</b> erlauben?" + +#: uffd/session/templates/session/deviceauth.html:51 +msgid "Authorize Login" +msgstr "Anmeldung erlauben" + +#: uffd/session/templates/session/deviceauth.html:58 +msgid "" +"Enter the confirmation code on the other device and complete the login. " +"Click <em>Finish</em> afterwards." +msgstr "" +"Gib den Bestätigungscode auf dem anderen Gerät ein und schließe die " +"Anmeldung ab. Clicke danach auf <em>Abschließen</em>." + +#: uffd/session/templates/session/deviceauth.html:61 +msgid "Finish" +msgstr "Beenden" + +#: uffd/session/templates/session/devicelogin.html:11 +#: uffd/session/templates/session/login.html:27 uffd/templates/base.html:93 +msgid "Device Login" +msgstr "Geratelogin" + +#: uffd/session/templates/session/devicelogin.html:14 +msgid "" +"Use a login session on another device (e.g. your laptop) to log into a " +"service without entering your password." +msgstr "" +"Nutze eine Login-Sitzung auf einem anderen Gerät (z.B. deinem Laptop) um " +"dich bei einem Dienst anzumelden." + +#: uffd/session/templates/session/devicelogin.html:27 +#, python-format +msgid "" +"Open <code><a href=\"%(deviceauth_url)s\">%(deviceauth_url)s</a></code> " +"on the other device and enter the initiation code there. Then enter the " +"confirmation code in the box above." +msgstr "" +"Öffne <code><a href=\"%(deviceauth_url)s\">%(deviceauth_url)s</a></code> " +"auf dem anderen Gerät und gib dort den obenstehenden Startcode ein. Geben" +" anschließend den Bestätigungscode hier ein." + +#: uffd/session/templates/session/login.html:11 +#: uffd/session/templates/session/login.html:22 msgid "Login" msgstr "Anmelden" -#: session/templates/session/login.html:26 +#: uffd/session/templates/session/login.html:25 +msgid "- or -" +msgstr "- oder -" + +#: uffd/session/templates/session/login.html:32 msgid "Register" msgstr "Registrieren" -#: session/templates/session/login.html:29 +#: uffd/session/templates/session/login.html:35 msgid "Forgot Password?" msgstr "Passwort vergessen?" -#: templates/base.html:75 -msgid "Logout" -msgstr "Abmelden" +#: uffd/signup/views.py:23 +msgid "Singup not enabled" +msgstr "Account-Registrierung ist deaktiviert" + +#: uffd/signup/views.py:77 uffd/signup/views.py:85 +msgid "Invalid signup link" +msgstr "Ungültiger Account-Registrierungs-Link" + +#: uffd/signup/views.py:90 +#, python-format +msgid "Too many failed attempts! Please wait %(delay)s." +msgstr "Zu viele fehlgeschlagene Versuche! Bitte warte mindestens %(delay)s." + +#: uffd/signup/views.py:96 +msgid "Wrong password" +msgstr "Falsches Passwort" + +#: uffd/signup/views.py:103 +msgid "Your account was successfully created" +msgstr "Account erfolgreich erstellt" -#: templates/base.html:108 +#: uffd/signup/templates/signup/confirm.html:11 +msgid "Complete Registration" +msgstr "Account-Registrierung abschließen" + +#: uffd/signup/templates/signup/confirm.html:17 +msgid "Please enter your password to complete the account registration" +msgstr "Bitte gib dein Passwort ein, um die Account-Registrierung abzuschließen" + +#: uffd/signup/templates/signup/confirm.html:21 +msgid "Complete Account Registration" +msgstr "Account-Registrierung abschließen" + +#: uffd/signup/templates/signup/start.html:23 +msgid "Check" +msgstr "Ãœberprüfen" + +#: uffd/signup/templates/signup/start.html:28 +msgid "" +"At least one and at most 32 lower-case characters, digits, dashes (\"-\")" +" or underscores (\"_\"). <b>Cannot be changed later!</b>" +msgstr "" +"1 bis 32 Kleinbuchstaben, Zahlen, Binde- (\"-\") und Unterstriche " +"(\"_\"). <b>Kann später nicht geändert werden!</b>" + +#: uffd/signup/templates/signup/start.html:35 +msgid "At least one and at most 128 characters, no other special requirements." +msgstr "Mindestens 1 und maximal 128 Zeichen, keine weiteren Einschränkungen." + +#: uffd/signup/templates/signup/start.html:42 +msgid "" +"We will send a confirmation mail to this address that you need to " +"complete the registration." +msgstr "" +"Wir werden eine Bestätigungsmail an diese Adresse senden. Du benötigst " +"sie, um die Account-Registrierung abzuschließen." + +#: uffd/signup/templates/signup/start.html:57 +msgid "Create Account" +msgstr "Account registrieren" + +#: uffd/signup/templates/signup/start.html:86 +msgid "The name is already taken" +msgstr "Dieser Name wird bereits verwendet" + +#: uffd/signup/templates/signup/start.html:89 +msgid "Too many requests! Please wait a bit before trying again!" +msgstr "Zu viele Anfragen! Bitte warte etwas, bevor du es erneut versuchst!" + +#: uffd/signup/templates/signup/start.html:92 +msgid "The name is invalid" +msgstr "Name ungültig" + +#: uffd/signup/templates/signup/submitted.html:11 +msgid "Confirm your E-Mail Address" +msgstr "E-Mail-Adresse bestätigen" + +#: uffd/signup/templates/signup/submitted.html:13 +#, python-format +msgid "" +"We sent a confirmation mail to <b>%(signup_mail)s</b>. You need to " +"confirm your mail address within 48 hours to complete the account " +"registration." +msgstr "" +"Eine Bestätigungsmail wurde an <b>%(signup_mail)s</b> gesendet. Du must " +"deine E-Mail-Adresse innerhalb von 48 Stunden bestätigen, um die Account-" +"Registrierung abzuschließen." + +#: uffd/signup/templates/signup/submitted.html:14 +msgid "" +"If you mistyped your mail address or don't receive the confirmation mail " +"for another reason, retry the registration procedure from the beginning." +msgstr "" +"Falls du dich bei deiner E-Mail-Adresse vertippt hast oder du aus anderen" +" Gründen keine Bestätigungsmail erhalten hast, kannst du den Prozess " +"einfach von Vorne beginnen." + +#: uffd/templates/base.html:84 +msgid "Change" +msgstr "Ändern" + +#: uffd/templates/base.html:133 msgid "Sourcecode" msgstr "Quellcode" -#: user/views_group.py:21 +#: uffd/user/views_group.py:21 msgid "Groups" msgstr "Gruppen" -#: user/views_user.py:29 +#: uffd/user/views_user.py:29 msgid "Users" -msgstr "Nutzer:innen" +msgstr "Benutzer" -#: user/views_user.py:49 +#: uffd/user/views_user.py:49 msgid "Login name does not meet requirements" -msgstr "Anmeldename entspricht nicht den Anforderungen" +msgstr "Benutzername entspricht nicht den Anforderungen" -#: user/views_user.py:54 +#: uffd/user/views_user.py:54 msgid "Mail is invalid" msgstr "E-Mail-Adresse nicht valide" -#: user/views_user.py:58 +#: uffd/user/views_user.py:58 msgid "Display name does not meet requirements" msgstr "Anzeigename entspricht nicht den Anforderungen" -#: user/views_user.py:75 +#: uffd/user/views_user.py:75 msgid "Service user created" msgstr "Service-Account erstellt" -#: user/views_user.py:78 +#: uffd/user/views_user.py:78 msgid "User created. We sent the user a password reset link by mail" msgstr "" -"Nutzer:in erstellt. Wir haben der/dem Nutzer:in eine E-Mail mit einem " -"Passwort Zurücksetzen Link gesendet" +"Benutzer erstellt. E-Mail mit einem Link zum Setzen des Passworts wurde " +"versendet." -#: user/views_user.py:80 +#: uffd/user/views_user.py:80 msgid "User updated" -msgstr "Nutzer:in aktualisiert" +msgstr "Benutzer aktualisiert" -#: user/views_user.py:91 +#: uffd/user/views_user.py:91 msgid "Deleted user" -msgstr "Nutzer:in gelöscht" +msgstr "Benutzer gelöscht" -#: user/templates/group/list.html:9 user/templates/group/show.html:7 -msgid "gid" -msgstr "" +#: uffd/user/templates/group/list.html:9 uffd/user/templates/group/show.html:7 +msgid "GID" +msgstr "GID" -#: user/templates/user/list.html:11 +#: uffd/user/templates/user/list.html:11 msgid "CSV import" -msgstr "CSV Import" +msgstr "CSV-Import" -#: user/templates/user/list.html:17 -msgid "uid" -msgstr "" - -#: user/templates/user/list.html:18 -msgid "login name" -msgstr "Anmeldename" +#: uffd/user/templates/user/list.html:17 +msgid "UID" +msgstr "UID" -#: user/templates/user/list.html:19 -msgid "display name" -msgstr "Anzeigename" - -#: user/templates/user/list.html:20 -msgid "roles" -msgstr "Rollen" +#: uffd/user/templates/user/list.html:34 uffd/user/templates/user/show.html:30 +msgid "service" +msgstr "service" -#: user/templates/user/list.html:57 +#: uffd/user/templates/user/list.html:57 msgid "Import a csv formated list of users" -msgstr "Importiere eine als CSV formatierte Liste von Nutzer:innen" +msgstr "Importiere eine als CSV formatierte Liste von Benutzern" -#: user/templates/user/list.html:77 -msgid "Ignore login name blacklist" +#: uffd/user/templates/user/list.html:64 +msgid "" +"The format should be \"loginname,mailaddres,roleid1;roleid2\". Neither " +"setting the display name nor setting passwords is supported (yet). " +"Example:" msgstr "" +"Das Format sollte \"loginname,mailaddres,roleid1;roleid2\" sein. Der " +"Anzeigename oder das Password können (derzeit) nicht gesetzt werden. " +"Beispiel:" -#: user/templates/user/list.html:82 +#: uffd/user/templates/user/list.html:75 uffd/user/templates/user/show.html:58 +msgid "Ignore login name blacklist" +msgstr "Benutzernamen-Blacklist ignorieren" + +#: uffd/user/templates/user/list.html:80 msgid "Import" msgstr "Importieren" -#: user/templates/user/show.html:10 +#: uffd/user/templates/user/show.html:10 msgid "Reset 2FA" msgstr "2FA zurücksetzen" -#: user/templates/user/show.html:18 -msgid "Profile" -msgstr "Profile" +#: uffd/user/templates/user/show.html:36 +msgid "will be choosen" +msgstr "wird automatisch bestimmt" + +#: uffd/user/templates/user/show.html:43 +msgid "Service User" +msgstr "Service-Account" -#: user/templates/user/show.html:54 +#: uffd/user/templates/user/show.html:51 msgid "" -"Only letters, numbers and underscore (\"_\") are allowed. At most 32, at " -"least 2 characters. There is a word blacklist. Must be unique." +"Only letters, numbers, dashes (\"-\") and underscores (\"_\") are " +"allowed. At most 32, at least 2 characters. There is a word blacklist. " +"Must be unique." msgstr "" -"Nur Buchstaben, Zahlen und Unterstriche (\"_\") sind erlaubt. Maximal 32," -" mindestens 2 Zeichen. Muss einmalig sein." +"Nur Buchstaben, Zahlen, Binde- (\"-\") und Unterstriche (\"_\") sind " +"erlaubt. Maximal 32, mindestens 2 Zeichen. Es gibt eine Blacklist. Muss " +"einmalig sein." -#: user/templates/user/show.html:69 +#: uffd/user/templates/user/show.html:66 msgid "" "If you leave this empty it will be set to the login name. At most 128, at" " least 2 characters. No character restrictions." msgstr "" -"Wenn das Feld leer bleibt, wird der Anmeldename verwendet. Maximal 128, " -"midestens 2 Zeichen. Keine Zeichenbeschränkung." +"Wenn das Feld leer bleibt, wird der Benutzername verwendet. Maximal 128, " +"mindestens 2 Zeichen. Keine Zeichenbeschränkung." + +#: uffd/user/templates/user/show.html:70 +msgid "Mail" +msgstr "E-Mail-Adresse" -#: user/templates/user/show.html:76 +#: uffd/user/templates/user/show.html:73 msgid "" "Do a sanity check here. A user can take over another account if both have" " the same mail address set." msgstr "" -"Prüfe die Einmaligkeit. Ein:e Nutzer:in kann einen anderen Account " -"übernehmen, wenn beide die selbe E-Mail-Adresse verwenden." +"Ãœberprüfe, ob die E-Mail-Adresse plausibel ist! Ein Benutzer kann einen " +"anderen Account übernehmen, wenn beide die selbe E-Mail-Adresse " +"verwenden." -#: user/templates/user/show.html:84 +#: uffd/user/templates/user/show.html:81 msgid "mail to set it will be sent" msgstr "Mail zum Setzen wird versendet" -#: user/templates/user/show.html:126 +#: uffd/user/templates/user/show.html:123 msgid "Resulting groups (only updated after save)" msgstr "Resultierende Gruppen (wird nur aktualisiert beim Speichern)" diff --git a/uffd/user/templates/group/list.html b/uffd/user/templates/group/list.html index 89abf1ad..e393485d 100644 --- a/uffd/user/templates/group/list.html +++ b/uffd/user/templates/group/list.html @@ -6,9 +6,9 @@ <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">{{_("gid")}}</th> - <th scope="col">{{_("name")}}</th> - <th scope="col">{{_("description")}}</th> + <th scope="col">{{_("GID")}}</th> + <th scope="col">{{_("Name")}}</th> + <th scope="col">{{_("Description")}}</th> </tr> </thead> <tbody> diff --git a/uffd/user/templates/group/show.html b/uffd/user/templates/group/show.html index 71ab805a..d43d7406 100644 --- a/uffd/user/templates/group/show.html +++ b/uffd/user/templates/group/show.html @@ -4,11 +4,11 @@ <form action="{{ url_for("group.show", gid=group.gid) }}" method="POST"> <div class="align-self-center"> <div class="form-group col"> - <label for="group-gid">{{_("gid")}}</label> + <label for="group-gid">{{_("GID")}}</label> <input type="number" class="form-control" id="group-gid" name="gid" value="{{ group.gid }}" readonly> </div> <div class="form-group col"> - <label for="group-loginname">{{_("name")}}</label> + <label for="group-loginname">{{_("Name")}}</label> <input type="text" class="form-control" id="group-loginname" name="loginname" value="{{ group.name }}" readonly> </div> <div class="col"> diff --git a/uffd/user/templates/user/list.html b/uffd/user/templates/user/list.html index 7f5edc7e..013f6fb3 100644 --- a/uffd/user/templates/user/list.html +++ b/uffd/user/templates/user/list.html @@ -14,10 +14,10 @@ <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">{{_("uid")}}</th> - <th scope="col">{{_("login name")}}</th> - <th scope="col">{{_("display name")}}</th> - <th scope="col">{{_("roles")}}</th> + <th scope="col">{{_("UID")}}</th> + <th scope="col">{{_("Login Name")}}</th> + <th scope="col">{{_("Display Name")}}</th> + <th scope="col">{{_("Roles")}}</th> </tr> </thead> <tbody> @@ -31,7 +31,7 @@ {{ user.loginname }} </a> {% if user.is_service_user %} - <span class="badge badge-secondary">service</span> + <span class="badge badge-secondary">{{_('service')}}</span> {% endif %} </td> <td> @@ -61,9 +61,7 @@ </div> <div class="modal-body"> <p> - The format should be "loginname,mailaddres,roleid1;roleid2". - Neither setting the display name nor setting passwords is supported (yet). - Example: + {{_('The format should be "loginname,mailaddres,roleid1;roleid2". Neither setting the display name nor setting passwords is supported (yet). Example:')}} </p> <pre> testuser1,foobar@example.com,5;2;6 diff --git a/uffd/user/templates/user/show.html b/uffd/user/templates/user/show.html index dff8eb97..9bf051d2 100644 --- a/uffd/user/templates/user/show.html +++ b/uffd/user/templates/user/show.html @@ -8,7 +8,7 @@ <a href="{{ url_for("user.index") }}" class="btn btn-secondary">{{_("Cancel")}}</a> {% if user.uid %} <a href="{{ url_for("mfa.admin_disable", uid=user.uid) }}" class="btn btn-secondary">{{_("Reset 2FA")}}</a> - <a href="{{ url_for("user.delete", uid=user.uid) }}" onClick="return confirm('Are you sure?');" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> + <a href="{{ url_for("user.delete", uid=user.uid) }}" onClick="return confirm({{_('Are you sure?')|tojson}});" class="btn btn-danger"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> {% else %} <a href="#" class="btn btn-danger disabled"><i class="fa fa-trash" aria-hidden="true"></i> {{_("Delete")}}</a> {% endif %} @@ -20,30 +20,27 @@ <li class="nav-item"> <a class="nav-link" id="roles-tab" data-toggle="tab" href="#roles" role="tab" aria-controls="roles" aria-selected="false">{{_("Roles")}}</a> </li> - <li class="nav-item"> - <a class="nav-link" id="ldif-tab" data-toggle="tab" href="#ldif" role="tab" aria-controls="ldif" aria-selected="false">LDIF</a> - </li> </ul> <div class="tab-content border mb-2 pt-2" id="tabcontent"> <div class="tab-pane fade show active" id="profile" role="tabpanel" aria-labelledby="roles-tab"> <div class="form-group col"> <label for="user-uid"> - uid + {{_('User ID')}} {% if user.is_service_user %} - <span class="badge badge-secondary">service</span> + <span class="badge badge-secondary">{{_('service')}}</span> {% endif %} </label> {% if user.uid %} <input type="number" class="form-control" id="user-uid" name="uid" value="{{ user.uid or '' }}" readonly> {% else %} - <input type="text" class="form-control" id="user-uid" name="uid" placeholder="will be choosen" readonly> + <input type="text" class="form-control" id="user-uid" name="uid" placeholder="{{_('will be choosen')}}" readonly> {% endif %} </div> {% if not user.uid %} <div class="form-group col"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="user-serviceaccount" name="serviceaccount" value="1" aria-label="enabled"> - <label class="form-check-label" for="user-serviceaccount">Service User</label> + <label class="form-check-label" for="user-serviceaccount">{{_('Service User')}}</label> </div> </div> {% endif %} @@ -58,7 +55,7 @@ <div class="form-group col"> <div class="form-check"> <input class="form-check-input" type="checkbox" id="ignore-loginname-blacklist" name="ignore-loginname-blacklist" value="1" aria-label="enabled"> - <label class="form-check-label" for="ignore-loginname-blacklist">Ignore login name blacklist</label> + <label class="form-check-label" for="ignore-loginname-blacklist">{{_('Ignore login name blacklist')}}</label> </div> </div> {% endif %} @@ -84,7 +81,7 @@ <input type="password" class="form-control" id="user-password" name="password" placeholder="{{_("mail to set it will be sent")}}" readonly> {% endif %} <small class="form-text text-muted"> - {{_("At least 8 and at most 256 characters, no other special requirements. But please don't be stupid, do use a password manager.")}} + {{_("At least 8 and at most 256 characters, no other special requirements.")}} </small> </div> </div> @@ -95,8 +92,8 @@ <thead> <tr> <th scope="col"></th> - <th scope="col">{{_("name")}}</th> - <th scope="col">{{_("description")}}</th> + <th scope="col">{{_("Name")}}</th> + <th scope="col">{{_("Description")}}</th> </tr> </thead> <tbody> @@ -127,8 +124,8 @@ <table class="table table-striped table-sm"> <thead> <tr> - <th scope="col">{{_("name")}}</th> - <th scope="col">{{_("description")}}</th> + <th scope="col">{{_("Name")}}</th> + <th scope="col">{{_("Description")}}</th> </tr> </thead> <tbody> @@ -148,11 +145,6 @@ </table> </div> </div> - <div class="tab-pane fade" id="ldif" role="tabpanel" aria-labelledby="ldif-tab"> - <div class="form-group col"> - <pre>{{ '<not available>'|e }}</pre> - </div> - </div> </div> </div> </form> -- GitLab