~homeworkprod/byceps

e6f5debad1efcca29947351164b9171c6fd60611 — Jochen Kupperschmidt 8 months ago 1a41d41
Wrap flash message texts in `gettext` calls for translation
38 files changed, 894 insertions(+), 303 deletions(-)

M byceps/blueprints/admin/authentication/login/views.py
M byceps/blueprints/admin/board/views.py
M byceps/blueprints/admin/brand/views.py
M byceps/blueprints/admin/news/views.py
M byceps/blueprints/admin/orga/views.py
M byceps/blueprints/admin/orga_team/views.py
M byceps/blueprints/admin/party/views.py
M byceps/blueprints/admin/shop/article/views.py
M byceps/blueprints/admin/shop/order/views.py
M byceps/blueprints/admin/shop/shop/views.py
M byceps/blueprints/admin/shop/storefront/views.py
M byceps/blueprints/admin/site/views.py
M byceps/blueprints/admin/snippet/views.py
M byceps/blueprints/admin/ticketing/checkin/views.py
M byceps/blueprints/admin/ticketing/views.py
M byceps/blueprints/admin/tourney/category/views.py
M byceps/blueprints/admin/tourney/tourney/views.py
M byceps/blueprints/admin/user/views.py
M byceps/blueprints/admin/user_badge/views.py
M byceps/blueprints/admin/webhook/views.py
M byceps/blueprints/common/authentication/password/views.py
M byceps/blueprints/site/authentication/login/views.py
M byceps/blueprints/site/board/views_category.py
M byceps/blueprints/site/board/views_posting.py
M byceps/blueprints/site/board/views_topic.py
M byceps/blueprints/site/consent/views.py
M byceps/blueprints/site/newsletter/views.py
M byceps/blueprints/site/seating/views.py
M byceps/blueprints/site/shop/order/views.py
M byceps/blueprints/site/shop/orders/views.py
M byceps/blueprints/site/ticketing/views.py
M byceps/blueprints/site/user/avatar/views.py
M byceps/blueprints/site/user/creation/views.py
M byceps/blueprints/site/user/email_address/views.py
M byceps/blueprints/site/user/settings/views.py
M byceps/blueprints/site/user_group/views.py
M byceps/blueprints/site/user_message/views.py
M byceps/util/views.py
M byceps/blueprints/admin/authentication/login/views.py => byceps/blueprints/admin/authentication/login/views.py +12 -3
@@ 7,6 7,7 @@ byceps.blueprints.admin.authentication.login.views
"""

from flask import abort, g, redirect, request
from flask_babel import gettext

from .....services.authentication.exceptions import AuthenticationFailed
from .....services.authentication import service as authentication_service


@@ 33,7 34,10 @@ def login_form():
    """Show login form."""
    if g.user.is_active:
        flash_notice(
            f'Du bist bereits als Benutzer "{g.user.screen_name}" angemeldet.'
            gettext(
                'Du bist bereits als Benutzer "%(screen_name)s" angemeldet.',
                screen_name=g.user.screen_name,
            )
        )
        return redirect('/')



@@ 68,7 72,12 @@ def login():

    auth_token = session_service.log_in_user(user.id, request.remote_addr)
    user_session.start(user.id, auth_token, permanent=permanent)
    flash_success(f'Erfolgreich eingeloggt als {user.screen_name}.')
    flash_success(
        gettext(
            'Erfolgreich eingeloggt als %(screen_name)s.',
            screen_name=user.screen_name,
        )
    )


def _require_admin_access_permission(user_id: UserID) -> None:


@@ 84,4 93,4 @@ def _require_admin_access_permission(user_id: UserID) -> None:
def logout():
    """Log out user by deleting the corresponding cookie."""
    user_session.end()
    flash_success('Erfolgreich ausgeloggt.')
    flash_success(gettext('Erfolgreich ausgeloggt.'))

M byceps/blueprints/admin/board/views.py => byceps/blueprints/admin/board/views.py +57 -14
@@ 9,6 9,7 @@ byceps.blueprints.admin.board.views
from dataclasses import dataclass

from flask import abort, request
from flask_babel import gettext

from ....services.board import board_service
from ....services.board import (


@@ 123,7 124,12 @@ def board_create(brand_id):

    board = board_service.create_board(brand.id, board_id)

    flash_success(f'Das Forum mit der ID "{board.id}" wurde angelegt.')
    flash_success(
        gettext(
            'Das Forum mit der ID "%(board_id)s" wurde angelegt.',
            board_id=board.id,
        )
    )
    return redirect_to('.board_view', board_id=board.id)




@@ 167,7 173,12 @@ def category_create(board_id):
        board.id, slug, title, description
    )

    flash_success(f'Die Kategorie "{category.title}" wurde angelegt.')
    flash_success(
        gettext(
            'Die Kategorie "%(category_title)s" wurde angelegt.',
            category_title=category.title,
        )
    )
    return redirect_to('.board_view', board_id=board.id)




@@ 210,7 221,12 @@ def category_update(category_id):
        category.id, slug, title, description
    )

    flash_success(f'Die Kategorie "{category.title}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Die Kategorie "%(category_title)s" wurde aktualisiert.',
            category_title=category.title,
        )
    )
    return redirect_to('.board_view', board_id=category.board_id)




@@ 225,7 241,12 @@ def category_hide(category_id):

    board_category_command_service.hide_category(category.id)

    flash_success(f'Die Kategorie "{category.title}" wurde versteckt.')
    flash_success(
        gettext(
            'Die Kategorie "%(category_title)s" wurde versteckt.',
            category_title=category.title,
        )
    )


@blueprint.route(


@@ 239,7 260,12 @@ def category_unhide(category_id):

    board_category_command_service.unhide_category(category.id)

    flash_success(f'Die Kategorie "{category.title}" wurde sichtbar gemacht.')
    flash_success(
        gettext(
            'Die Kategorie "%(category_title)s" wurde sichtbar gemacht.',
            category_title=category.title,
        )
    )


@blueprint.route('/categories/<uuid:category_id>/up', methods=['POST'])


@@ 253,12 279,17 @@ def category_move_up(category_id):
        board_category_command_service.move_category_up(category.id)
    except ValueError:
        flash_error(
            f'Die Kategorie "{category.title}" befindet sich bereits ganz oben.'
            gettext(
                'Die Kategorie "%(category_title)s" befindet sich bereits ganz oben.',
                category_title=category.title,
            )
        )
    else:
        flash_success(
            f'Die Kategorie "{category.title}" wurde '
            'eine Position nach oben verschoben.'
            gettext(
                'Die Kategorie "%(category_title)s" wurde eine Position nach oben verschoben.',
                category_title=category.title,
            )
        )




@@ 273,13 304,17 @@ def category_move_down(category_id):
        board_category_command_service.move_category_down(category.id)
    except ValueError:
        flash_error(
            f'Die Kategorie "{category.title}" befindet sich bereits '
            'ganz unten.'
            gettext(
                'Die Kategorie "%(category_title)s" befindet sich bereits ganz unten.',
                category_title=category.title,
            )
        )
    else:
        flash_success(
            f'Die Kategorie "{category.title}" wurde '
            'eine Position nach unten verschoben.'
            gettext(
                'Die Kategorie "%(category_title)s" wurde eine Position nach unten verschoben.',
                category_title=category.title,
            )
        )




@@ 294,10 329,18 @@ def category_delete(category_id):
        board_category_command_service.delete_category(category.id)
    except Exception:
        flash_error(
            f'Die Kategorie "{category.title}" konnte nicht gelöscht werden.'
            gettext(
                'Die Kategorie "%(category_title)s" konnte nicht gelöscht werden.',
                category_title=category.title,
            )
        )
    else:
        flash_success(f'Die Kategorie "{category.title}" wurde gelöscht.')
        flash_success(
            gettext(
                'Die Kategorie "%(category_title)s" wurde gelöscht.',
                category_title=category.title,
            )
        )


# -------------------------------------------------------------------- #

M byceps/blueprints/admin/brand/views.py => byceps/blueprints/admin/brand/views.py +8 -3
@@ 7,6 7,7 @@ byceps.blueprints.admin.brand.views
"""

from flask import abort, request
from flask_babel import gettext

from ....services.brand import (
    service as brand_service,


@@ 99,7 100,9 @@ def create():
        contact_address=f'info@{brand.id}.example',
    )

    flash_success(f'Die Marke "{brand.title}" wurde angelegt.')
    flash_success(
        gettext('Die Marke "%(title)s" wurde angelegt.', title=brand.title)
    )
    return redirect_to('.index')




@@ 135,7 138,9 @@ def update(brand_id):
        brand.id, title, image_filename=image_filename
    )

    flash_success(f'Die Marke "{brand.title}" wurde aktualisiert.')
    flash_success(
        gettext('Die Marke "%(title)s" wurde aktualisiert.', title=brand.title)
    )
    return redirect_to('.view', brand_id=brand_id)




@@ 189,7 194,7 @@ def email_config_update(brand_id):
        config.brand_id, sender_address, sender_name, contact_address
    )

    flash_success(f'Die E-Mail-Konfiguration wurde aktualisiert.')
    flash_success(gettext('Die E-Mail-Konfiguration wurde aktualisiert.'))
    return redirect_to('.view', brand_id=brand.id)



M byceps/blueprints/admin/news/views.py => byceps/blueprints/admin/news/views.py +33 -7
@@ 9,6 9,7 @@ byceps.blueprints.admin.news.views
from datetime import date, datetime

from flask import abort, g, request
from flask_babel import gettext

from ....services.brand import service as brand_service
from ....services.image import service as image_service


@@ 101,7 102,12 @@ def channel_create(brand_id):
        brand.id, channel_id, url_prefix
    )

    flash_success(f'Der News-Kanal mit der ID "{channel.id}" wurde angelegt.')
    flash_success(
        gettext(
            'Der News-Kanal mit der ID "%(channel_id)s" wurde angelegt.',
            channel_id=channel.id,
        )
    )
    return redirect_to('.channel_view', channel_id=channel.id)




@@ 191,7 197,12 @@ def image_create(item_id):
    except FileExistsError:
        abort(409, 'File already exists, not overwriting.')

    flash_success(f'Das Newsbild #{image.number} wurde hinzugefügt.')
    flash_success(
        gettext(
            'Das Newsbild #%(image_number)s wurde hinzugefügt.',
            image_number=image.number,
        )
    )

    return redirect_to('.item_view', item_id=image.item_id)



@@ 231,7 242,12 @@ def image_update(image_id):
        image.id, alt_text=alt_text, caption=caption, attribution=attribution,
    )

    flash_success(f'Das Newsbild #{image.number} wurde aktualisiert.')
    flash_success(
        gettext(
            'Das Newsbild #%(image_number)s wurde aktualisiert.',
            image_number=image.number,
        )
    )

    return redirect_to('.item_view', item_id=image.item_id)



@@ 386,7 402,9 @@ def item_create(channel_id):
        channel.id, slug, creator.id, title, body, image_url_path=image_url_path
    )

    flash_success(f'Die News "{item.title}" wurde angelegt.')
    flash_success(
        gettext('Die News "%(title)s" wurde angelegt.', title=item.title)
    )

    return redirect_to('.item_view', item_id=item.id)



@@ 432,7 450,9 @@ def item_update(item_id):
        item.id, slug, creator.id, title, body, image_url_path=image_url_path
    )

    flash_success(f'Die News "{item.title}" wurde aktualisiert.')
    flash_success(
        gettext('Die News "%(title)s" wurde aktualisiert.', title=item.title)
    )

    return redirect_to('.item_view', item_id=item.id)



@@ 472,7 492,11 @@ def item_publish_later(item_id):

    news_signals.item_published.send(None, event=event)

    flash_success(f'Die News "{item.title}" wird später veröffentlicht.')
    flash_success(
        gettext(
            'Die News "%(title)s" wird später veröffentlicht.', title=item.title
        )
    )

    return redirect_to('.item_view', item_id=item.id)



@@ 488,7 512,9 @@ def item_publish_now(item_id):

    news_signals.item_published.send(None, event=event)

    flash_success(f'Die News "{item.title}" wurde veröffentlicht.')
    flash_success(
        gettext('Die News "%(title)s" wurde veröffentlicht.', title=item.title)
    )


# -------------------------------------------------------------------- #

M byceps/blueprints/admin/orga/views.py => byceps/blueprints/admin/orga/views.py +13 -4
@@ 9,6 9,7 @@ byceps.blueprints.admin.orga.views
from typing import Optional

from flask import abort, g, request
from flask_babel import gettext

from ....services.brand import service as brand_service
from ....services.orga import birthday_service as orga_birthday_service


@@ 99,8 100,12 @@ def create_orgaflag(brand_id):
    orga_flag = orga_service.add_orga_flag(brand.id, user.id, initiator.id)

    flash_success(
        f'{orga_flag.user.screen_name} wurde das Orga-Flag '
        f'für die Marke {orga_flag.brand.title} gegeben.'
        gettext(
            '%(screen_name)s wurde das Orga-Flag '
            'für die Marke %(brand_title)s gegeben.',
            screen_name=orga_flag.user.screen_name,
            brand_title=orga_flag.brand.title,
        )
    )
    return redirect_to('.persons_for_brand', brand_id=orga_flag.brand.id)



@@ 122,8 127,12 @@ def remove_orgaflag(brand_id, user_id):
    orga_service.remove_orga_flag(orga_flag, initiator.id)

    flash_success(
        f'{user.screen_name} wurde das Orga-Flag '
        f'für die Marke {brand.title} entzogen.'
        gettext(
            '%(screen_name)s wurde das Orga-Flag '
            'für die Marke %(brand_title)s entzogen.',
            screen_name=user.screen_name,
            brand_title=brand.title,
        )
    )



M byceps/blueprints/admin/orga_team/views.py => byceps/blueprints/admin/orga_team/views.py +42 -15
@@ 7,6 7,7 @@ byceps.blueprints.admin.orga_team.views
"""

from flask import abort, request
from flask_babel import gettext

from ....services.orga_team import service as orga_team_service
from ....services.party import service as party_service


@@ 94,8 95,12 @@ def team_create(party_id):
    team = orga_team_service.create_team(party.id, title)

    flash_success(
        f'Das Team "{team.title}" wurde '
        f'für die Party "{party.title}" erstellt.'
        gettext(
            'Das Team "%(team_title)s" wurde '
            'für die Party "%(party_title)s" erstellt.',
            team_title=team.title,
            party_title=party.title,
        )
    )
    return redirect_to('.teams_for_party', party_id=party.id)



@@ 109,7 114,10 @@ def team_delete(team_id):

    if orga_team_service.has_team_memberships(team.id):
        flash_error(
            f'Orga team "{team.title}" cannot be deleted because it has members.'
            gettext(
                'Orga team "%(team_title)s" cannot be deleted because it has members.',
                team_title=team.title,
            )
        )
        return



@@ 117,7 125,7 @@ def team_delete(team_id):

    orga_team_service.delete_team(team.id)

    flash_success(f'Das Team "{title}" wurde gelöscht.')
    flash_success(gettext('Das Team "%(title)s" wurde gelöscht.', title=title))


@blueprint.route('/teams/<target_party_id>/copy')


@@ 130,8 138,9 @@ def teams_copy_form(target_party_id, erroneous_form=None):
    team_count = orga_team_service.count_teams_for_party(target_party.id)
    if team_count:
        flash_error(
            'Diese Party hat bereits Orga-Teams. '
            'Es können keine weiteren hinzu kopiert werden.'
            gettext(
                'Diese Party hat bereits Orga-Teams. Es können keine weiteren hinzu kopiert werden.'
            )
        )
        return redirect_to('.teams_for_party', party_id=target_party.id)



@@ 148,8 157,9 @@ def teams_copy_form(target_party_id, erroneous_form=None):

    if not parties:
        flash_error(
            'Es sind keine anderen Partys vorhanden, von denen Orga-Teams '
            'kopiert werden können.'
            gettext(
                'Es sind keine anderen Partys vorhanden, von denen Orga-Teams kopiert werden können.'
            )
        )
        return redirect_to('.teams_for_party', party_id=target_party.id)



@@ 173,8 183,9 @@ def teams_copy(target_party_id):
    target_team_count = orga_team_service.count_teams_for_party(target_party.id)
    if target_team_count:
        flash_error(
            'Diese Party hat bereits Orga-Teams. '
            'Es können keine weiteren hinzu kopiert werden.'
            gettext(
                'Diese Party hat bereits Orga-Teams. Es können keine weiteren hinzu kopiert werden.'
            )
        )
        return redirect_to('.teams_for_party', party_id=target_party.id)



@@ 192,8 203,13 @@ def teams_copy(target_party_id):
    )

    flash_success(
        f'{copied_teams_count:d} Team(s) wurde von Party '
        f'"{source_party.title}" zu Party "{target_party.title}" kopiert.'
        gettext(
            '%(copied_teams_count)s Team(s) wurde(n) von Party '
            '"%(source_party_title)s" zu Party "%(target_party_title)s" kopiert.',
            copied_teams_count=copied_teams_count,
            source_party_title=source_party.title,
            target_party_title=target_party.title,
        )
    )

    return redirect_to('.teams_for_party', party_id=target_party.id)


@@ 260,7 276,11 @@ def membership_create(team_id):
    membership = orga_team_service.create_membership(team.id, user.id, duties)

    flash_success(
        f'{user.screen_name} wurde in das Team "{team.title}" aufgenommen.'
        gettext(
            '%(screen_name)s wurde in das Team "%(team_title)s" aufgenommen.',
            screen_name=user.screen_name,
            team_title=team.title,
        )
    )
    return redirect_to('.teams_for_party', party_id=team.party_id)



@@ 318,7 338,10 @@ def membership_update(membership_id):
    orga_team_service.update_membership(membership.id, team.id, duties)

    flash_success(
        f'Die Teammitgliedschaft von {user.screen_name} wurde aktualisiert.'
        gettext(
            'Die Teammitgliedschaft von %(screen_name)s wurde aktualisiert.',
            screen_name=user.screen_name,
        )
    )
    return redirect_to('.teams_for_party', party_id=team.party_id)



@@ 336,7 359,11 @@ def membership_remove(membership_id):
    orga_team_service.delete_membership(membership.id)

    flash_success(
        f'{user.screen_name} wurde aus dem Team "{team.title}" entfernt.'
        gettext(
            '%(screen_name)s wurde aus dem Team "%(team_title)s" entfernt.',
            screen_name=user.screen_name,
            team_title=team.title,
        )
    )



M byceps/blueprints/admin/party/views.py => byceps/blueprints/admin/party/views.py +8 -2
@@ 11,6 11,7 @@ from datetime import date
from typing import Dict, List

from flask import abort, request
from flask_babel import gettext

from ....services.brand import service as brand_service
from ....services.party import (


@@ 164,7 165,10 @@ def create(brand_id):
        max_ticket_quantity=max_ticket_quantity,
    )

    flash_success(f'Die Party "{party.title}" wurde angelegt.')
    flash_success(
        gettext('Die Party "%(title)s" wurde angelegt.', title=party.title)
    )

    return redirect_to('.index_for_brand', brand_id=brand.id)




@@ 225,7 229,9 @@ def update(party_id):
    except party_service.UnknownPartyId:
        abort(404, f'Unknown party ID "{party_id}".')

    flash_success(f'Der Party "{party.title}" wurde aktualisiert.')
    flash_success(
        gettext('Die Party "%(title)s" wurde aktualisiert.', title=party.title)
    )

    return redirect_to('.view', party_id=party.id)


M byceps/blueprints/admin/shop/article/views.py => byceps/blueprints/admin/shop/article/views.py +41 -13
@@ 10,6 10,7 @@ from datetime import datetime
from decimal import Decimal

from flask import abort, request
from flask_babel import gettext

from .....services.brand import service as brand_service
from .....services.shop.article import (


@@ 193,7 194,9 @@ def create(shop_id):
    )
    if not article_number_sequences:
        flash_error(
            f'Für diesen Shop sind keine Artikelnummer-Sequenzen definiert.'
            gettext(
                'Für diesen Shop sind keine Artikelnummer-Sequenzen definiert.'
            )
        )
        return create_form(shop_id, form)



@@ 203,7 206,9 @@ def create(shop_id):

    article_number_sequence_id = form.article_number_sequence_id.data
    if not article_number_sequence_id:
        flash_error(f'Es wurde keine gültige Artikelnummer-Sequenz angegeben.')
        flash_error(
            gettext('Es wurde keine gültige Artikelnummer-Sequenz angegeben.')
        )
        return create_form(shop_id, form)

    article_number_sequence = (


@@ 214,7 219,9 @@ def create(shop_id):
    if (article_number_sequence is None) or (
        article_number_sequence.shop_id != shop.id
    ):
        flash_error(f'Es wurde keine gültige Artikelnummer-Sequenz angegeben.')
        flash_error(
            gettext('Es wurde keine gültige Artikelnummer-Sequenz angegeben.')
        )
        return create_form(shop_id, form)

    try:


@@ 241,7 248,12 @@ def create(shop_id):
        max_quantity_per_order,
    )

    flash_success(f'Der Artikel "{article.item_number}" wurde angelegt.')
    flash_success(
        gettext(
            'Der Artikel "%(item_number)s" wurde angelegt.',
            item_number=article.item_number,
        )
    )
    return redirect_to('.view', article_id=article.id)




@@ 316,7 328,12 @@ def update(article_id):
        shipping_required,
    )

    flash_success(f'Der Artikel "{article.description}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Der Artikel "%(description)s" wurde aktualisiert.',
            description=article.description,
        )
    )
    return redirect_to('.view', article_id=article.id)




@@ 375,9 392,12 @@ def attachment_create(article_id):
    )

    flash_success(
        f'Der Artikel "{article_to_attach.item_number}" '
        f'wurde {quantity:d} mal an den Artikel "{article.item_number}" '
        'angehängt.'
        gettext(
            'Der Artikel "%(article_to_attach_item_number)s" wurde %(quantity)s mal an den Artikel "%(article_item_number)s" angehängt.',
            article_to_attach_item_number=article_to_attach.item_number,
            quantity=quantity,
            article_item_number=article.item_number,
        )
    )
    return redirect_to('.view', article_id=article.id)



@@ 398,8 418,11 @@ def attachment_remove(article_id):
    article_service.unattach_article(attached_article.id)

    flash_success(
        f'Artikel "{article.item_number}" ist nun nicht mehr '
        f'an Artikel "{attached_to_article.item_number}" angehängt.'
        gettext(
            'Artikel "%(article_item_number)s" ist nun nicht mehr an Artikel "%(attached_to_article_item_number)s" angehängt.',
            article_item_number=article.item_number,
            attached_to_article_item_number=attached_to_article.item_number,
        )
    )




@@ 444,13 467,18 @@ def create_number_sequence(shop_id):
    )
    if sequence_id is None:
        flash_error(
            'Die Artikelnummer-Sequenz konnte nicht angelegt werden. '
            f'Ist das Präfix "{prefix}" bereits definiert?'
            gettext(
                'Die Artikelnummer-Sequenz konnte nicht angelegt werden. Ist das Präfix "%(prefix)s" bereits definiert?',
                prefix=prefix,
            )
        )
        return create_number_sequence_form(shop.id, form)

    flash_success(
        f'Die Artikelnummer-Sequenz mit dem Präfix "{prefix}" wurde angelegt.'
        gettext(
            'Die Artikelnummer-Sequenz mit dem Präfix "%(prefix)s" wurde angelegt.',
            prefix=prefix,
        )
    )
    return redirect_to('.index_for_shop', shop_id=shop.id)


M byceps/blueprints/admin/shop/order/views.py => byceps/blueprints/admin/shop/order/views.py +47 -21
@@ 7,6 7,7 @@ byceps.blueprints.admin.shop.order.views
"""

from flask import abort, g, request, Response
from flask_babel import gettext

from .....services.brand import service as brand_service
from .....services.shop.order import (


@@ 162,8 163,10 @@ def set_invoiced_flag(order_id):
    order_service.set_invoiced_flag(order.id, initiator_id)

    flash_success(
        f'Bestellung {order.order_number} wurde '
        'als in Rechnung gestellt markiert.'
        gettext(
            'Bestellung %(order_number)s wurde als in Rechnung gestellt markiert.',
            order_number=order.order_number,
        )
    )




@@ 178,8 181,10 @@ def unset_invoiced_flag(order_id):
    order_service.unset_invoiced_flag(order.id, initiator_id)

    flash_success(
        f'Bestellung {order.order_number} wurde '
        'als nicht in Rechnung gestellt markiert.'
        gettext(
            'Bestellung %(order_number)s wurde als nicht in Rechnung gestellt markiert.',
            order_number=order.order_number,
        )
    )




@@ 194,7 199,10 @@ def set_shipped_flag(order_id):
    order_service.set_shipped_flag(order.id, initiator_id)

    flash_success(
        f'Bestellung {order.order_number} wurde als verschickt markiert.'
        gettext(
            'Bestellung %(order_number)s wurde als verschickt markiert.',
            order_number=order.order_number,
        )
    )




@@ 209,7 217,10 @@ def unset_shipped_flag(order_id):
    order_service.unset_shipped_flag(order.id, initiator_id)

    flash_success(
        f'Bestellung {order.order_number} wurde als nicht verschickt markiert.'
        gettext(
            'Bestellung %(order_number)s wurde als nicht verschickt markiert.',
            order_number=order.order_number,
        )
    )




@@ 226,8 237,9 @@ def cancel_form(order_id, erroneous_form=None):

    if order.is_canceled:
        flash_error(
            'Die Bestellung ist bereits storniert worden; '
            'der Bezahlstatus kann nicht mehr geändert werden.'
            gettext(
                'Die Bestellung ist bereits storniert worden; der Bezahlstatus kann nicht mehr geändert werden.'
            )
        )
        return redirect_to('.view', order_id=order.id)



@@ 264,22 276,25 @@ def cancel(order_id):
        event = order_service.cancel_order(order.id, g.user.id, reason)
    except order_service.OrderAlreadyCanceled:
        flash_error(
            'Die Bestellung ist bereits storniert worden; '
            'der Bezahlstatus kann nicht mehr geändert werden.'
            gettext(
                'Die Bestellung ist bereits storniert worden; der Bezahlstatus kann nicht mehr geändert werden.'
            )
        )
        return redirect_to('.view', order_id=order.id)

    flash_success(
        'Die Bestellung wurde als storniert markiert und die betroffenen '
        'Artikel in den entsprechenden Stückzahlen wieder zur Bestellung '
        'freigegeben.'
        gettext(
            'Die Bestellung wurde als storniert markiert und die betroffenen Artikel in den entsprechenden Stückzahlen wieder zur Bestellung freigegeben.'
        )
    )

    if send_email:
        order_email_service.send_email_for_canceled_order_to_orderer(order.id)
    else:
        flash_notice(
            'Es wurde keine E-Mail an den/die Auftraggeber/in versendet.'
            gettext(
                'Es wurde keine E-Mail an den/die Auftraggeber/in versendet.'
            )
        )

    shop_signals.order_canceled.send(None, event=event)


@@ 299,7 314,9 @@ def mark_as_paid_form(order_id, erroneous_form=None):
    order = _get_order_or_404(order_id)

    if order.is_paid:
        flash_error('Die Bestellung ist bereits als bezahlt markiert worden.')
        flash_error(
            gettext('Die Bestellung ist bereits als bezahlt markiert worden.')
        )
        return redirect_to('.view', order_id=order.id)

    shop = shop_service.get_shop(order.shop_id)


@@ 334,10 351,12 @@ def mark_as_paid(order_id):
            order.id, payment_method, updated_by_id
        )
    except order_service.OrderAlreadyMarkedAsPaid:
        flash_error('Die Bestellung ist bereits als bezahlt markiert worden.')
        flash_error(
            gettext('Die Bestellung ist bereits als bezahlt markiert worden.')
        )
        return redirect_to('.view', order_id=order.id)

    flash_success('Die Bestellung wurde als bezahlt markiert.')
    flash_success(gettext('Die Bestellung wurde als bezahlt markiert.'))

    order_email_service.send_email_for_paid_order_to_orderer(order.id)



@@ 371,7 390,9 @@ def resend_email_for_incoming_order_to_orderer(order_id):
        },
    )

    flash_success('Die E-Mail-Eingangsbestätigung wurde erneut versendet.')
    flash_success(
        gettext('Die E-Mail-Eingangsbestätigung wurde erneut versendet.')
    )


# -------------------------------------------------------------------- #


@@ 413,13 434,18 @@ def create_number_sequence(shop_id):
    )
    if sequence_id is None:
        flash_error(
            'Die Bestellnummer-Sequenz konnte nicht angelegt werden. '
            f'Ist das Präfix "{prefix}" bereits definiert?'
            gettext(
                'Die Bestellnummer-Sequenz konnte nicht angelegt werden. Ist das Präfix "%(prefix)s" bereits definiert?',
                prefix=prefix,
            )
        )
        return create_number_sequence_form(shop.id, form)

    flash_success(
        f'Die Bestellnummer-Sequenz mit dem Präfix "{prefix}" wurde angelegt.'
        gettext(
            'Die Bestellnummer-Sequenz mit dem Präfix "%(prefix)s" wurde angelegt.',
            prefix=prefix,
        )
    )
    return redirect_to('.index_for_shop', shop_id=shop.id)


M byceps/blueprints/admin/shop/shop/views.py => byceps/blueprints/admin/shop/shop/views.py +2 -1
@@ 7,6 7,7 @@ byceps.blueprints.admin.shop.shop.views
"""

from flask import abort, request, url_for
from flask_babel import gettext

from .....services.brand import service as brand_service
from .....services.shop.order import service as order_service


@@ 86,7 87,7 @@ def create(brand_id):

    shop = shop_service.create_shop(shop_id, brand.id, title)

    flash_success(f'Der Shop wurde angelegt.')
    flash_success(gettext('Der Shop wurde angelegt.'))
    return url_for('.view', shop_id=shop.id)



M byceps/blueprints/admin/shop/storefront/views.py => byceps/blueprints/admin/shop/storefront/views.py +22 -5
@@ 7,6 7,7 @@ byceps.blueprints.admin.shop.storefront.views
"""

from flask import abort, request
from flask_babel import gettext

from .....services.brand import service as brand_service
from .....services.shop.catalog import service as catalog_service


@@ 123,7 124,9 @@ def create(shop_id):
    )
    if not order_number_sequences:
        flash_error(
            f'Für diesen Shop sind keine Bestellnummer-Sequenzen definiert.'
            gettext(
                'Für diesen Shop sind keine Bestellnummer-Sequenzen definiert.'
            )
        )
        return create_form(shop_id, form)



@@ 137,7 140,9 @@ def create(shop_id):
    order_number_sequence_id = form.order_number_sequence_id.data

    if not order_number_sequence_id:
        flash_error('Es wurde keine gültige Bestellnummer-Sequenz angegeben.')
        flash_error(
            gettext('Es wurde keine gültige Bestellnummer-Sequenz angegeben.')
        )
        return create_form(shop_id, form)

    order_number_sequence = order_sequence_service.find_order_number_sequence(


@@ 146,7 151,9 @@ def create(shop_id):
    if (order_number_sequence is None) or (
        order_number_sequence.shop_id != shop.id
    ):
        flash_error('Es wurde keine gültige Bestellnummer-Sequenz angegeben.')
        flash_error(
            gettext('Es wurde keine gültige Bestellnummer-Sequenz angegeben.')
        )
        return create_form(shop_id, form)

    try:


@@ 164,7 171,12 @@ def create(shop_id):
        catalog_id=catalog_id,
    )

    flash_success(f'Storefront "{storefront.id}" wurde angelegt.')
    flash_success(
        gettext(
            'Storefront "%(storefront_id)s" wurde angelegt.',
            storefront_id=storefront.id,
        )
    )
    return redirect_to('.view', storefront_id=storefront.id)




@@ 227,7 239,12 @@ def update(storefront_id):
        storefront.id, catalog_id, order_number_sequence_id, closed
    )

    flash_success(f'Storefront "{storefront.id}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Storefront "%(storefront_id)s" wurde aktualisiert.',
            storefront_id=storefront.id,
        )
    )
    return redirect_to('.view', storefront_id=storefront.id)



M byceps/blueprints/admin/site/views.py => byceps/blueprints/admin/site/views.py +20 -4
@@ 10,6 10,7 @@ import dataclasses
from typing import Iterable, Iterator

from flask import abort, request
from flask_babel import gettext

from ....services.board import board_service
from ....services.brand import service as brand_service


@@ 203,7 204,12 @@ def create(brand_id):
    if party_id:
        party = party_service.find_party(party_id)
        if not party:
            flash_error(f'Die Party-ID "{party_id}" ist unbekannt.')
            flash_error(
                gettext(
                    'Die Party-ID "%(party_id)s" ist unbekannt.',
                    party_id=party_id,
                )
            )
            return create_form(brand_id, form)
    else:
        party_id = None


@@ 222,7 228,10 @@ def create(brand_id):
        storefront_id=storefront_id,
    )

    flash_success(f'Die Site "{site.title}" wurde angelegt.')
    flash_success(
        gettext('Die Site "%(title)s" wurde angelegt.', title=site.title)
    )

    return redirect_to('.view', site_id=site.id)




@@ 271,7 280,12 @@ def update(site_id):
    if party_id:
        party = party_service.find_party(party_id)
        if not party:
            flash_error(f'Die Party-ID "{party_id}" ist unbekannt.')
            flash_error(
                gettext(
                    'Die Party-ID "%(party_id)s" ist unbekannt.',
                    party_id=party_id,
                )
            )
            return update_form(site.id, form)
    else:
        party_id = None


@@ 294,7 308,9 @@ def update(site_id):
    except site_service.UnknownSiteId:
        abort(404, f'Unknown site ID "{site_id}".')

    flash_success(f'Die Site "{site.title}" wurde aktualisiert.')
    flash_success(
        gettext('Die Site "%(title)s" wurde aktualisiert.', title=site.title)
    )

    return redirect_to('.view', site_id=site.id)


M byceps/blueprints/admin/snippet/views.py => byceps/blueprints/admin/snippet/views.py +42 -9
@@ 7,6 7,7 @@ byceps.blueprints.admin.snippet.views
"""

from flask import abort, g, request, url_for
from flask_babel import gettext

from ....services.brand import service as brand_service
from ....services.site import service as site_service


@@ 192,7 193,11 @@ def create_document(scope_type, scope_name):
        image_url_path=image_url_path,
    )

    flash_success(f'Das Dokument "{version.snippet.name}" wurde angelegt.')
    flash_success(
        gettext(
            'Das Dokument "%(name)s" wurde angelegt.', name=version.snippet.name
        )
    )

    snippet_signals.snippet_created.send(None, event=event)



@@ 246,7 251,12 @@ def update_document(snippet_id):
        image_url_path=image_url_path,
    )

    flash_success(f'Das Dokument "{version.snippet.name}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Das Dokument "%(name)s" wurde aktualisiert.',
            name=version.snippet.name,
        )
    )

    snippet_signals.snippet_updated.send(None, event=event)



@@ 331,7 341,11 @@ def create_fragment(scope_type, scope_name):
        scope, name, creator.id, body
    )

    flash_success(f'Das Fragment "{version.snippet.name}" wurde angelegt.')
    flash_success(
        gettext(
            'Das Fragment "%(name)s" wurde angelegt.', name=version.snippet.name
        )
    )

    snippet_signals.snippet_created.send(None, event=event)



@@ 377,7 391,12 @@ def update_fragment(snippet_id):
        snippet.id, creator.id, body
    )

    flash_success(f'Das Fragment "{version.snippet.name}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Das Fragment "%(name)s" wurde aktualisiert.',
            name=version.snippet.name,
        )
    )

    snippet_signals.snippet_updated.send(None, event=event)



@@ 432,12 451,16 @@ def delete_snippet(snippet_id):

    if not success:
        flash_error(
            f'Das Snippet "{snippet_name}" konnte nicht gelöscht werden. '
            'Ist es noch gemountet?'
            gettext(
                'Das Snippet "%(snippet_name)s" konnte nicht gelöscht werden. Ist es noch gemountet?',
                snippet_name=snippet_name,
            )
        )
        return url_for('.view_current_version', snippet_id=snippet.id)

    flash_success(f'Das Snippet "{snippet_name}" wurde gelöscht.')
    flash_success(
        gettext('Das Snippet "%(name)s" wurde gelöscht.', name=snippet_name)
    )
    snippet_signals.snippet_deleted.send(None, event=event)
    return url_for(
        '.index_for_scope', scope_type=scope.type_, scope_name=scope.name


@@ 515,7 538,12 @@ def create_mountpoint(snippet_id):
        site_id, endpoint_suffix, url_path, snippet.id
    )

    flash_success(f'Der Mountpoint für "{mountpoint.url_path}" wurde angelegt.')
    flash_success(
        gettext(
            'Der Mountpoint für "%(url_path)s" wurde angelegt.',
            url_path=mountpoint.url_path,
        )
    )
    return redirect_to('.index_mountpoints', site_id=site_id)




@@ 533,7 561,12 @@ def delete_mountpoint(mountpoint_id):

    mountpoint_service.delete_mountpoint(mountpoint.id)

    flash_success(f'Der Mountpoint für "{url_path}" wurde entfernt.')
    flash_success(
        gettext(
            'Der Mountpoint für "%(url_path)s" wurde entfernt.',
            url_path=url_path,
        )
    )


# -------------------------------------------------------------------- #

M byceps/blueprints/admin/ticketing/checkin/views.py => byceps/blueprints/admin/ticketing/checkin/views.py +19 -9
@@ 9,6 9,7 @@ byceps.blueprints.admin.ticketing.checkin.views
from datetime import date

from flask import abort, g, request, url_for
from flask_babel import gettext

from .....services.party import service as party_service
from .....services.ticketing import (


@@ 121,14 122,16 @@ def check_in_user(party_id, ticket_id):
        )
    except ticket_exceptions.UserAccountDeleted:
        flash_error(
            'Das dem Ticket zugewiesene Benutzerkonto ist gelöscht worden. '
            'Der Check-In ist nicht erlaubt.'
            gettext(
                'Das dem Ticket zugewiesene Benutzerkonto ist gelöscht worden. Der Check-In ist nicht erlaubt.'
            )
        )
        return
    except ticket_exceptions.UserAccountSuspended:
        flash_error(
            'Das dem Ticket zugewiesene Benutzerkonto ist gesperrt. '
            'Der Check-In ist nicht erlaubt.'
            gettext(
                'Das dem Ticket zugewiesene Benutzerkonto ist gesperrt. Der Check-In ist nicht erlaubt.'
            )
        )
        return



@@ 137,16 140,23 @@ def check_in_user(party_id, ticket_id):
    ticket_url = url_for('ticketing_admin.view_ticket', ticket_id=ticket.id)

    flash_success(
        f'Benutzer <em>{ticket.used_by.screen_name}</em> wurde '
        f'mit Ticket <a href="{ticket_url}">{ticket.code}</a> eingecheckt.',
        gettext(
            'Benutzer <em>%(screen_name)s</em> wurde mit Ticket <a href="%(ticket_url)s">%(ticket_code)s</a> eingecheckt.',
            screen_name=ticket.used_by.screen_name,
            ticket_url=ticket_url,
            ticket_code=ticket.code,
        ),
        text_is_safe=True,
    )

    occupies_seat = ticket.occupied_seat_id is not None
    if not occupies_seat:
        flash_notice(
            f'Ticket <a href="{ticket_url}">{ticket.code}</a> '
            'belegt noch keinen Sitzplatz.',
            gettext(
                'Ticket <a href="%(ticket_url)s">%(ticket_code)s</a> belegt noch keinen Sitzplatz.',
                ticket_url=ticket_url,
                ticket_code=ticket.code,
            ),
            icon='warning',
            text_is_safe=True,
        )


@@ 165,7 175,7 @@ def revert_user_check_in(ticket_id):

    ticket_user_checkin_service.revert_user_check_in(ticket.id, initiator_id)

    flash_success('Der Check-In wurde rückgängig gemacht.')
    flash_success(gettext('Der Check-In wurde rückgängig gemacht.'))


def _get_party_or_404(party_id):

M byceps/blueprints/admin/ticketing/views.py => byceps/blueprints/admin/ticketing/views.py +19 -7
@@ 7,6 7,7 @@ byceps.blueprints.admin.ticketing.views
"""

from flask import abort, g, request
from flask_babel import gettext

from ....services.party import service as party_service
from ....services.shop.order import service as order_service


@@ 98,8 99,9 @@ def update_code_form(ticket_id, erroneous_form=None):

    if ticket.user_checked_in:
        flash_error(
            'Der Code kann nicht geändert werden, da bereits jemand '
            'mit diesem Ticket eingecheckt worden ist.'
            gettext(
                'Der Code kann nicht geändert werden, da bereits jemand mit diesem Ticket eingecheckt worden ist.'
            )
        )
        return redirect_to('.view_ticket', ticket_id=ticket.id)



@@ 122,8 124,9 @@ def update_code(ticket_id):

    if ticket.user_checked_in:
        flash_error(
            'Der Code kann nicht geändert werden, da bereits jemand '
            'mit diesem Ticket eingecheckt worden ist.'
            gettext(
                'Der Code kann nicht geändert werden, da bereits jemand mit diesem Ticket eingecheckt worden ist.'
            )
        )
        return redirect_to('.view_ticket', ticket_id=ticket.id)



@@ 136,7 139,12 @@ def update_code(ticket_id):

    ticket_service.update_ticket_code(ticket.id, code, manager.id)

    flash_success(f'Der Code von Ticket {ticket.code} wurde geändert.')
    flash_success(
        gettext(
            'Der Code von Ticket %(ticket_code)s wurde geändert.',
            ticket_code=ticket.code,
        )
    )

    return redirect_to('.view_ticket', ticket_id=ticket.id)



@@ 178,8 186,12 @@ def appoint_user(ticket_id):
    ticket_user_management_service.appoint_user(ticket.id, user.id, manager.id)

    flash_success(
        f'{user.screen_name} wurde als Nutzer/in '
        f'von Ticket {ticket.code} eingetragen.'
        gettext(
            '%(screen_name)s wurde als Nutzer/in '
            'von Ticket %(ticket_code)s eingetragen.',
            screen_name=user.screen_name,
            ticket_code=ticket.code,
        )
    )

    return redirect_to('.view_ticket', ticket_id=ticket.id)

M byceps/blueprints/admin/tourney/category/views.py => byceps/blueprints/admin/tourney/category/views.py +27 -9
@@ 7,6 7,7 @@ byceps.blueprints.admin.tourney.category.views
"""

from flask import abort, request
from flask_babel import gettext

from .....services.party import service as party_service
from .....services.tourney import category_service


@@ 71,7 72,12 @@ def create(party_id):

    category = category_service.create_category(party.id, title)

    flash_success(f'Die Kategorie "{category.title}" wurde angelegt.')
    flash_success(
        gettext(
            'Die Kategorie "%(category_title)s" wurde angelegt.',
            category_title=category.title,
        )
    )
    return redirect_to('.index', party_id=category.party_id)




@@ 109,7 115,12 @@ def update(category_id):

    category_service.update_category(category.id, form.title.data.strip())

    flash_success(f'Die Kategorie "{category.title}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Die Kategorie "%(category_title)s" wurde aktualisiert.',
            category_title=category.title,
        )
    )
    return redirect_to('.index', party_id=category.party_id)




@@ 124,12 135,16 @@ def move_up(category_id):
        category_service.move_category_up(category.id)
    except ValueError:
        flash_error(
            f'Die Kategorie "{category.title}" befindet sich bereits ganz oben.'
            gettext(
                'Die Kategorie "%(category_title)s" befindet sich bereits ganz oben.',
                category_title=category.title,
            )
        )
    else:
        flash_success(
            f'Die Kategorie "{category.title}" '
            'wurde eine Position nach oben verschoben.'
            gettext(
                'Die Kategorie "%(category_title)s" wurde eine Position nach oben verschoben.'
            )
        )




@@ 144,13 159,16 @@ def move_down(category_id):
        category_service.move_category_down(category.id)
    except ValueError:
        flash_error(
            f'Die Kategorie "{category.title}" befindet sich bereits '
            'ganz unten.'
            gettext(
                'Die Kategorie "%(category_title)s" befindet sich bereits ganz unten.',
                category_title=category.title,
            )
        )
    else:
        flash_success(
            f'Die Kategorie "{category.title}" '
            'wurde eine Position nach unten verschoben.'
            gettext(
                'Die Kategorie "%(category_title)s" wurde eine Position nach unten verschoben.'
            )
        )



M byceps/blueprints/admin/tourney/tourney/views.py => byceps/blueprints/admin/tourney/tourney/views.py +9 -2
@@ 10,6 10,7 @@ import dataclasses
from datetime import datetime

from flask import abort, request
from flask_babel import gettext

from .....services.party import service as party_service
from .....services.tourney import tourney_service


@@ 93,7 94,9 @@ def create(party_id):
        starts_at,
    )

    flash_success(f'Das Turnier "{tourney.title}" wurde angelegt.')
    flash_success(
        gettext('Das Turnier "%(title)s" wurde angelegt.', title=tourney.title)
    )

    return redirect_to('.index', party_id=tourney.party_id)



@@ 161,7 164,11 @@ def update(tourney_id):
        starts_at,
    )

    flash_success(f'Das Turnier "{tourney.title}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Das Turnier "%(title)s" wurde aktualisiert.', title=tourney.title
        )
    )

    return redirect_to('.index', party_id=tourney.party_id)


M byceps/blueprints/admin/user/views.py => byceps/blueprints/admin/user/views.py +87 -26
@@ 10,6 10,7 @@ from datetime import datetime
from typing import Optional

from flask import abort, g, request
from flask_babel import gettext

from ....services.authentication.password import service as password_service
from ....services.authentication.session import service as session_service


@@ 178,13 179,17 @@ def create_account():

    if user_service.is_screen_name_already_assigned(screen_name):
        flash_error(
            'Dieser Benutzername ist bereits einem Benutzerkonto zugeordnet.'
            gettext(
                'Dieser Benutzername ist bereits einem Benutzerkonto zugeordnet.'
            )
        )
        return create_account_form(form)

    if user_service.is_email_address_already_assigned(email_address):
        flash_error(
            'Diese E-Mail-Adresse ist bereits einem Benutzerkonto zugeordnet.'
            gettext(
                'Diese E-Mail-Adresse ist bereits einem Benutzerkonto zugeordnet.'
            )
        )
        return create_account_form(form)



@@ 201,19 206,26 @@ def create_account():
        )
    except user_creation_service.UserCreationFailed:
        flash_error(
            f'Das Benutzerkonto für "{screen_name}" '
            'konnte nicht angelegt werden.'
            gettext(
                'Das Benutzerkonto für "%(screen_name)s" konnte nicht angelegt werden.',
                screen_name=screen_name,
            )
        )
        return create_account_form(form)

    flash_success(f'Das Benutzerkonto "{user.screen_name}" wurde angelegt.')
    flash_success(
        gettext(
            'Das Benutzerkonto "%(screen_name)s" wurde angelegt.',
            screen_name=user.screen_name,
        )
    )

    if site:
        user_creation_service.request_email_address_confirmation(
            user, email_address, site_id
        )
        flash_success(
            f'Eine E-Mail wurde an die hinterlegte Adresse versendet.',
            gettext('Eine E-Mail wurde an die hinterlegte Adresse versendet.'),
            icon='email',
        )



@@ 253,8 265,10 @@ def set_password(user_id):
    password_service.update_password_hash(user.id, new_password, initiator_id)

    flash_success(
        f"Für Benutzerkonto '{user.screen_name}' wurde "
        "ein neues Passwort gesetzt."
        gettext(
            "Für Benutzerkonto '%(screen_name)s' wurde ein neues Passwort gesetzt.",
            screen_name=user.screen_name,
        )
    )

    return redirect_to('.view', user_id=user.id)


@@ 272,7 286,10 @@ def initialize_account(user_id):
    user_command_service.initialize_account(user.id, initiator_id=initiator_id)

    flash_success(
        f"Das Benutzerkonto '{user.screen_name}' wurde initialisiert."
        gettext(
            "Das Benutzerkonto '%(screen_name)s' wurde initialisiert.",
            screen_name=user.screen_name,
        )
    )




@@ 285,7 302,10 @@ def suspend_account_form(user_id, erroneous_form=None):

    if user.suspended:
        flash_error(
            f"Das Benutzerkonto '{user.screen_name}' ist bereits gesperrt."
            gettext(
                "Das Benutzerkonto '%(screen_name)s' ist bereits gesperrt.",
                screen_name=user.screen_name,
            )
        )
        return redirect_to('.view', user_id=user.id)



@@ 305,7 325,10 @@ def suspend_account(user_id):

    if user.suspended:
        flash_error(
            f"Das Benutzerkonto '{user.screen_name}' ist bereits gesperrt."
            gettext(
                "Das Benutzerkonto '%(screen_name)s' ist bereits gesperrt.",
                screen_name=user.screen_name,
            )
        )
        return redirect_to('.view', user_id=user.id)



@@ 320,7 343,12 @@ def suspend_account(user_id):

    user_signals.account_suspended.send(None, event=event)

    flash_success(f"Das Benutzerkonto '{user.screen_name}' wurde gesperrt.")
    flash_success(
        gettext(
            "Das Benutzerkonto '%(screen_name)s' wurde gesperrt.",
            screen_name=user.screen_name,
        )
    )
    return redirect_to('.view', user_id=user.id)




@@ 333,7 361,10 @@ def unsuspend_account_form(user_id, erroneous_form=None):

    if not user.suspended:
        flash_error(
            f"Das Benutzerkonto '{user.screen_name}' ist bereits entsperrt."
            gettext(
                "Das Benutzerkonto '%(screen_name)s' ist bereits entsperrt.",
                screen_name=user.screen_name,
            )
        )
        return redirect_to('.view', user_id=user.id)



@@ 353,7 384,10 @@ def unsuspend_account(user_id):

    if not user.suspended:
        flash_error(
            f"Das Benutzerkonto '{user.screen_name}' ist bereits entsperrt."
            gettext(
                "Das Benutzerkonto '%(screen_name)s' ist bereits entsperrt.",
                screen_name=user.screen_name,
            )
        )
        return redirect_to('.view', user_id=user.id)



@@ 370,7 404,12 @@ def unsuspend_account(user_id):

    user_signals.account_unsuspended.send(None, event=event)

    flash_success(f"Das Benutzerkonto '{user.screen_name}' wurde entsperrt.")
    flash_success(
        gettext(
            "Das Benutzerkonto '%(screen_name)s' wurde entsperrt.",
            screen_name=user.screen_name,
        )
    )
    return redirect_to('.view', user_id=user.id)




@@ 383,8 422,10 @@ def delete_account_form(user_id, erroneous_form=None):

    if user.deleted:
        flash_error(
            f"Das Benutzerkonto '{user.screen_name}' "
            "ist bereits gelöscht worden."
            gettext(
                f"Das Benutzerkonto '%(user.screen_name)s' ist bereits gelöscht worden.",
                screen_name=user.screen_name,
            )
        )
        return redirect_to('.view', user_id=user.id)



@@ 404,8 445,10 @@ def delete_account(user_id):

    if user.deleted:
        flash_error(
            f"Das Benutzerkonto '{user.screen_name}' "
            "ist bereits gelöscht worden."
            gettext(
                "Das Benutzerkonto '%(user.screen_name)s' ist bereits gelöscht worden.",
                screen_name=user.screen_name,
            )
        )
        return redirect_to('.view', user_id=user.id)



@@ 420,7 463,12 @@ def delete_account(user_id):

    user_signals.account_deleted.send(None, event=event)

    flash_success(f"Das Benutzerkonto '{user.screen_name}' wurde gelöscht.")
    flash_success(
        gettext(
            "Das Benutzerkonto '%(screen_name)s' wurde gelöscht.",
            screen_name=user.screen_name,
        )
    )
    return redirect_to('.view', user_id=user.id)




@@ 461,8 509,10 @@ def change_email_address(user_id):
    user_signals.email_address_changed.send(None, event=event)

    flash_success(
        f"Die E-Mail-Adresse des Benutzerkontos '{user.screen_name}' wurde "
        f"geändert."
        gettext(
            "Die E-Mail-Adresse des Benutzerkontos '%(screen_name)s' wurde geändert.",
            screen_name=user.screen_name,
        )
    )
    return redirect_to('.view', user_id=user.id)



@@ 504,8 554,11 @@ def change_screen_name(user_id):
    user_signals.screen_name_changed.send(None, event=event)

    flash_success(
        f"Das Benutzerkonto '{old_screen_name}' wurde "
        f"umbenannt in '{new_screen_name}'."
        gettext(
            "Das Benutzerkonto '%(old_screen_name)s' wurde umbenannt in '%(new_screen_name)s'.",
            old_screen_name=old_screen_name,
            new_screen_name=new_screen_name,
        )
    )
    return redirect_to('.view', user_id=user.id)



@@ 561,7 614,11 @@ def role_assign(user_id, role_id):
    )

    flash_success(
        f'{user.screen_name} wurde die Rolle "{role.title}" zugewiesen.'
        gettext(
            '%(screen_name)s wurde die Rolle "%(role_title)s" zugewiesen.',
            screen_name=user.screen_name,
            role_title=role.title,
        )
    )




@@ 579,7 636,11 @@ def role_deassign(user_id, role_id):
    )

    flash_success(
        f'{user.screen_name} wurde die Rolle "{role.title}" genommen.'
        gettext(
            '%(screen_name)s wurde die Rolle "%(role_title)s" genommen.',
            screen_name=user.screen_name,
            role_title=role.title,
        )
    )



M byceps/blueprints/admin/user_badge/views.py => byceps/blueprints/admin/user_badge/views.py +18 -3
@@ 7,6 7,7 @@ byceps.blueprints.admin.user_badge.views
"""

from flask import abort, g, request
from flask_babel import gettext

from ....services.brand import service as brand_service
from ....services.user import service as user_service


@@ 137,7 138,12 @@ def create():
        featured=featured,
    )

    flash_success(f'Das Abzeichen "{badge.label}" wurde angelegt.')
    flash_success(
        gettext(
            'Das Abzeichen "%(badge_label)s" wurde angelegt.',
            badge_label=badge.label,
        )
    )
    return redirect_to('.index')




@@ 179,7 185,12 @@ def update(badge_id):
        badge.id, slug, label, description, image_filename, brand_id, featured
    )

    flash_success(f'Das Abzeichen "{badge.label}" wurde aktualisiert.')
    flash_success(
        gettext(
            'Das Abzeichen "%(badge_label)s" wurde aktualisiert.',
            badge_label=badge.label,
        )
    )
    return redirect_to('.view', badge_id=badge_id)




@@ 237,7 248,11 @@ def award(user_id):
    )

    flash_success(
        f'Das Abzeichen "{badge.label}" wurde an {user.screen_name} verliehen.'
        gettext(
            'Das Abzeichen "%(badge_label)s" wurde an %(screen_name)s verliehen.',
            badge_label=badge.label,
            screen_name=user.screen_name,
        )
    )

    user_badge_signals.user_badge_awarded.send(None, event=event)

M byceps/blueprints/admin/webhook/views.py => byceps/blueprints/admin/webhook/views.py +6 -3
@@ 7,6 7,7 @@ byceps.blueprints.admin.webhook.views
"""

from flask import abort
from flask_babel import gettext

from ....announce.helpers import call_webhook
from ....services.webhooks import service as webhook_service


@@ 51,11 52,13 @@ def test(webhook_id):
    text = 'Test, test … is this thing on?!'
    try:
        call_webhook(webhook, text)
        flash_success('Der Webhook-Aufruf war erfolgreich.', icon='success')
        flash_success(
            gettext('Der Webhook-Aufruf war erfolgreich.'), icon='success'
        )
    except Exception as e:
        flash_error(
            'Der Webhook-Aufruf ist fehlgeschlagen:'
            f'<br><pre style="white-space: pre-wrap;">{e}</pre>',
            gettext('Der Webhook-Aufruf ist fehlgeschlagen:')
            + f'<br><pre style="white-space: pre-wrap;">{e}</pre>',
            icon='warning',
            text_is_safe=True,
        )

M byceps/blueprints/common/authentication/password/views.py => byceps/blueprints/common/authentication/password/views.py +35 -13
@@ 9,6 9,7 @@ byceps.blueprints.common.authentication.password.views
from typing import Optional

from flask import abort, g, request
from flask_babel import gettext

from .....config import get_app_mode
from .....services.authentication.password import (


@@ 63,7 64,9 @@ def update():

    password_service.update_password_hash(user.id, password, user.id)

    flash_success('Dein Passwort wurde geändert. Bitte melde dich erneut an.')
    flash_success(
        gettext('Dein Passwort wurde geändert. Bitte melde dich erneut an.')
    )

    if get_app_mode().is_admin():
        return redirect_to('authentication.login_admin.login_form')


@@ 97,24 100,39 @@ def request_reset():
    )

    if (user is None) or user.deleted:
        flash_error(f'Der Benutzername "{screen_name}" ist unbekannt.')
        flash_error(
            gettext(
                'Der Benutzername "%(screen_name)s" ist unbekannt.',
                screen_name=screen_name,
            )
        )
        return request_reset_form(form)

    if user.email_address is None:
        flash_error(
            f'Für das Benutzerkonto "{screen_name}" ist keine E-Mail-Adresse hinterlegt.'
            gettext(
                'Für das Benutzerkonto "%(screen_name)s" ist keine E-Mail-Adresse hinterlegt.',
                screen_name=screen_name,
            )
        )
        return request_reset_form(form)

    if not user.email_address_verified:
        flash_error(
            f'Die E-Mail-Adresse für das Benutzerkonto "{screen_name}" '
            'wurde noch nicht bestätigt.'
            gettext(
                'Die E-Mail-Adresse für das Benutzerkonto "%(screen_name)s" wurde noch nicht bestätigt.',
                screen_name=screen_name,
            )
        )
        return redirect_to('user_email_address.request_confirmation_email')

    if user.suspended:
        flash_error(f'Das Benutzerkonto "{screen_name}" ist gesperrt.')
        flash_error(
            gettext(
                'Das Benutzerkonto "%(screen_name)s" ist gesperrt.',
                screen_name=screen_name,
            )
        )
        return request_reset_form(form)

    sender = _get_sender()


@@ 124,9 142,11 @@ def request_reset():
    )

    flash_success(
        'Ein Link zum Setzen eines neuen Passworts '
        f'für den Benutzernamen "{user.screen_name}" '
        'wurde an die hinterlegte E-Mail-Adresse versendet.'
        gettext(
            'Ein Link zum Setzen eines neuen Passworts für den Benutzernamen '
            '"%(screen_name)s" wurde an die hinterlegte E-Mail-Adresse versendet.',
            screen_name=user.screen_name,
        )
    )
    return request_reset_form()



@@ 167,7 187,7 @@ def reset(token):

    password_reset_service.reset_password(verification_token, password)

    flash_success('Das Passwort wurde geändert.')
    flash_success(gettext('Das Passwort wurde geändert.'))
    return redirect_to('authentication.login.login_form')




@@ 178,14 198,16 @@ def _verify_reset_token(token: str) -> VerificationToken:

    if not _is_verification_token_valid(verification_token):
        flash_error(
            'Es wurde kein gültiges Token angegeben. '
            'Ein Token ist nur 24 Stunden lang gültig.'
            gettext(
                'Es wurde kein gültiges Token angegeben. Ein Token ist nur %(hours)s Stunden lang gültig.',
                hours=24,
            )
        )
        abort(404)

    user = user_service.find_active_user(verification_token.user_id)
    if user is None:
        flash_error('Es wurde kein gültiges Token angegeben.')
        flash_error(gettext('Es wurde kein gültiges Token angegeben.'))
        abort(404)

    return verification_token

M byceps/blueprints/site/authentication/login/views.py => byceps/blueprints/site/authentication/login/views.py +12 -3
@@ 7,6 7,7 @@ byceps.blueprints.site.authentication.login.views
"""

from flask import abort, g, request, url_for
from flask_babel import gettext

from .....services.authentication.exceptions import AuthenticationFailed
from .....services.authentication import service as authentication_service


@@ 43,7 44,10 @@ def login_form():
    """Show login form."""
    if g.user.is_active:
        flash_notice(
            f'Du bist bereits als Benutzer "{g.user.screen_name}" angemeldet.'
            gettext(
                'Du bist bereits als Benutzer "%(screen_name)s" angemeldet.',
                screen_name=g.user.screen_name,
            )
        )
        return redirect_to('dashboard.index')



@@ 101,7 105,12 @@ def login():

    auth_token = session_service.log_in_user(user.id, request.remote_addr)
    user_session.start(user.id, auth_token, permanent=permanent)
    flash_success(f'Erfolgreich eingeloggt als {user.screen_name}.')
    flash_success(
        gettext(
            'Erfolgreich eingeloggt als %(screen_name)s.',
            screen_name=user.screen_name,
        )
    )

    return [('Location', url_for('dashboard.index'))]



@@ 121,7 130,7 @@ def _is_consent_required(user_id: UserID) -> bool:
def logout():
    """Log out user by deleting the corresponding cookie."""
    user_session.end()
    flash_success('Erfolgreich ausgeloggt.')
    flash_success(gettext('Erfolgreich ausgeloggt.'))


# helpers

M byceps/blueprints/site/board/views_category.py => byceps/blueprints/site/board/views_category.py +2 -1
@@ 7,6 7,7 @@ byceps.blueprints.site.board.views_category
"""

from flask import abort, g, url_for
from flask_babel import gettext

from ....services.board import (
    category_query_service as board_category_query_service,


@@ 95,7 96,7 @@ def mark_all_topics_in_category_as_viewed(category_id):
    )

    flash_success(
        'Alle Themen in dieser Kategorie wurden als gelesen markiert.'
        gettext('Alle Themen in dieser Kategorie wurden als gelesen markiert.')
    )

    return url_for('.category_view', slug=category.slug)

M byceps/blueprints/site/board/views_posting.py => byceps/blueprints/site/board/views_posting.py +26 -19
@@ 9,6 9,7 @@ byceps.blueprints.site.board.views_posting
import dataclasses

from flask import g, redirect, request
from flask_babel import gettext

from ....services.board import (
    last_view_service as board_last_view_service,


@@ 69,7 70,7 @@ def quote_posting_as_bbcode():

    posting = board_posting_query_service.find_posting_by_id(posting_id)
    if posting is None:
        flash_error('Der zu zitierende Beitrag wurde nicht gefunden.')
        flash_error(gettext('Der zu zitierende Beitrag wurde nicht gefunden.'))
        return

    creator = user_service.get_user(posting.creator_id)


@@ 92,18 93,20 @@ def posting_create(topic_id):

    if topic.locked:
        flash_error(
            'Das Thema ist geschlossen. '
            'Es können keine Beiträge mehr hinzugefügt werden.',
            gettext(
                'Das Thema ist geschlossen. Es können keine Beiträge mehr hinzugefügt werden.'
            ),
            icon='lock',
        )
        return redirect(h.build_url_for_topic(topic.id))

    if (
        topic.posting_limited_to_moderators
        and not g.user.has_permission(BoardPermission.announce)
    if topic.posting_limited_to_moderators and not g.user.has_permission(
        BoardPermission.announce
    ):
        flash_error(
            'In diesem Thema dürfen nur Moderatoren Beiträge hinzufügen.',
            gettext(
                'In diesem Thema dürfen nur Moderatoren Beiträge hinzufügen.'
            ),
            icon='announce',
        )
        return redirect(h.build_url_for_topic(topic.id))


@@ 117,7 120,7 @@ def posting_create(topic_id):
            topic.category.id, g.user.id
        )

    flash_success('Deine Antwort wurde hinzugefügt.')
    flash_success(gettext('Deine Antwort wurde hinzugefügt.'))

    event = dataclasses.replace(
        event, url=h.build_external_url_for_posting(posting.id)


@@ 144,17 147,18 @@ def posting_update_form(posting_id, erroneous_form=None):

    if posting.topic.locked and not user_may_update:
        flash_error(
            'Der Beitrag darf nicht bearbeitet werden weil das Thema, '
            'zu dem dieser Beitrag gehört, gesperrt ist.'
            gettext(
                'Der Beitrag darf nicht bearbeitet werden weil das Thema, zu dem dieser Beitrag gehört, gesperrt ist.'
            )
        )
        return redirect(url)

    if posting.topic.hidden or posting.hidden:
        flash_error('Der Beitrag darf nicht bearbeitet werden.')
        flash_error(gettext('Der Beitrag darf nicht bearbeitet werden.'))
        return redirect(url)

    if not user_may_update:
        flash_error('Du darfst diesen Beitrag nicht bearbeiten.')
        flash_error(gettext('Du darfst diesen Beitrag nicht bearbeiten.'))
        return redirect(url)

    form = erroneous_form if erroneous_form else PostingUpdateForm(obj=posting)


@@ 179,17 183,18 @@ def posting_update(posting_id):

    if posting.topic.locked and not user_may_update:
        flash_error(
            'Der Beitrag darf nicht bearbeitet werden weil das Thema, '
            'zu dem dieser Beitrag gehört, gesperrt ist.'
            gettext(
                'Der Beitrag darf nicht bearbeitet werden weil das Thema, zu dem dieser Beitrag gehört, gesperrt ist.'
            )
        )
        return redirect(url)

    if posting.topic.hidden or posting.hidden:
        flash_error('Der Beitrag darf nicht bearbeitet werden.')
        flash_error(gettext('Der Beitrag darf nicht bearbeitet werden.'))
        return redirect(url)

    if not user_may_update:
        flash_error('Du darfst diesen Beitrag nicht bearbeiten.')
        flash_error(gettext('Du darfst diesen Beitrag nicht bearbeiten.'))
        return redirect(url)

    form = PostingUpdateForm(request.form)


@@ 200,7 205,7 @@ def posting_update(posting_id):
        posting.id, g.user.id, form.body.data
    )

    flash_success('Der Beitrag wurde aktualisiert.')
    flash_success(gettext('Der Beitrag wurde aktualisiert.'))

    event = dataclasses.replace(
        event, url=h.build_external_url_for_posting(posting.id)


@@ 236,7 241,7 @@ def posting_hide(posting_id):

    page = service.calculate_posting_page_number(posting, g.user)

    flash_success('Der Beitrag wurde versteckt.', icon='hidden')
    flash_success(gettext('Der Beitrag wurde versteckt.'), icon='hidden')

    event = dataclasses.replace(
        event, url=h.build_external_url_for_posting(posting.id)


@@ 260,7 265,9 @@ def posting_unhide(posting_id):

    page = service.calculate_posting_page_number(posting, g.user)

    flash_success('Der Beitrag wurde wieder sichtbar gemacht.', icon='view')
    flash_success(
        gettext('Der Beitrag wurde wieder sichtbar gemacht.'), icon='view'
    )

    event = dataclasses.replace(
        event, url=h.build_external_url_for_posting(posting.id)

M byceps/blueprints/site/board/views_topic.py => byceps/blueprints/site/board/views_topic.py +57 -19
@@ 9,6 9,7 @@ byceps.blueprints.site.board.views_topic
import dataclasses

from flask import abort, g, redirect, request
from flask_babel import gettext

from ....services.board import (
    category_query_service as board_category_query_service,


@@ 160,7 161,9 @@ def topic_create(category_id):
    )
    topic_url = h.build_external_url_for_topic(topic.id)

    flash_success(f'Das Thema "{topic.title}" wurde hinzugefügt.')
    flash_success(
        gettext('Das Thema "%(title)s" wurde hinzugefügt.', title=topic.title)
    )

    event = dataclasses.replace(event, url=topic_url)
    board_signals.topic_created.send(None, event=event)


@@ 180,16 183,18 @@ def topic_update_form(topic_id, erroneous_form=None):

    if topic.locked and not user_may_update:
        flash_error(
            'Das Thema darf nicht bearbeitet werden weil es gesperrt ist.'
            gettext(
                'Das Thema darf nicht bearbeitet werden weil es gesperrt ist.'
            )
        )
        return redirect(url)

    if topic.hidden:
        flash_error('Das Thema darf nicht bearbeitet werden.')
        flash_error(gettext('Das Thema darf nicht bearbeitet werden.'))
        return redirect(url)

    if not user_may_update:
        flash_error('Du darfst dieses Thema nicht bearbeiten.')
        flash_error(gettext('Du darfst dieses Thema nicht bearbeiten.'))
        return redirect(url)

    form = (


@@ 216,16 221,18 @@ def topic_update(topic_id):

    if topic.locked and not user_may_update:
        flash_error(
            'Das Thema darf nicht bearbeitet werden weil es gesperrt ist.'
            gettext(
                'Das Thema darf nicht bearbeitet werden weil es gesperrt ist.'
            )
        )
        return redirect(url)

    if topic.hidden:
        flash_error('Das Thema darf nicht bearbeitet werden.')
        flash_error(gettext('Das Thema darf nicht bearbeitet werden.'))
        return redirect(url)

    if not user_may_update:
        flash_error('Du darfst dieses Thema nicht bearbeiten.')
        flash_error(gettext('Du darfst dieses Thema nicht bearbeiten.'))
        return redirect(url)

    form = TopicUpdateForm(request.form)


@@ 236,7 243,9 @@ def topic_update(topic_id):
        topic.id, g.user.id, form.title.data, form.body.data
    )

    flash_success(f'Das Thema "{topic.title}" wurde aktualisiert.')
    flash_success(
        gettext('Das Thema "%(title)s" wurde aktualisiert.', title=topic.title)
    )
    return redirect(url)




@@ 270,7 279,10 @@ def topic_hide(topic_id):

    event = board_topic_command_service.hide_topic(topic.id, moderator_id)

    flash_success(f'Das Thema "{topic.title}" wurde versteckt.', icon='hidden')
    flash_success(
        gettext('Das Thema "%(title)s" wurde versteckt.', title=topic.title),
        icon='hidden',
    )

    event = dataclasses.replace(
        event, url=h.build_external_url_for_topic(topic.id)


@@ 291,7 303,11 @@ def topic_unhide(topic_id):
    event = board_topic_command_service.unhide_topic(topic.id, moderator_id)

    flash_success(
        f'Das Thema "{topic.title}" wurde wieder sichtbar gemacht.', icon='view'
        gettext(
            'Das Thema "%(title)s" wurde wieder sichtbar gemacht.',
            title=topic.title,
        ),
        icon='view',
    )

    event = dataclasses.replace(


@@ 312,7 328,10 @@ def topic_lock(topic_id):

    event = board_topic_command_service.lock_topic(topic.id, moderator_id)

    flash_success(f'Das Thema "{topic.title}" wurde geschlossen.', icon='lock')
    flash_success(
        gettext('Das Thema "%(title)s" wurde geschlossen.', title=topic.title),
        icon='lock',
    )

    event = dataclasses.replace(
        event, url=h.build_external_url_for_topic(topic.id)


@@ 333,7 352,10 @@ def topic_unlock(topic_id):
    event = board_topic_command_service.unlock_topic(topic.id, moderator_id)

    flash_success(
        f'Das Thema "{topic.title}" wurde wieder geöffnet.', icon='unlock'
        gettext(
            'Das Thema "%(title)s" wurde wieder geöffnet.', title=topic.title
        ),
        icon='unlock',
    )

    event = dataclasses.replace(


@@ 354,7 376,10 @@ def topic_pin(topic_id):

    event = board_topic_command_service.pin_topic(topic.id, moderator_id)

    flash_success(f'Das Thema "{topic.title}" wurde angepinnt.', icon='pin')
    flash_success(
        gettext('Das Thema "%(title)s" wurde angepinnt.', title=topic.title),
        icon='pin',
    )

    event = dataclasses.replace(
        event, url=h.build_external_url_for_topic(topic.id)


@@ 374,7 399,9 @@ def topic_unpin(topic_id):

    event = board_topic_command_service.unpin_topic(topic.id, moderator_id)

    flash_success(f'Das Thema "{topic.title}" wurde wieder gelöst.')
    flash_success(
        gettext('Das Thema "%(title)s" wurde wieder gelöst.', title=topic.title)
    )

    event = dataclasses.replace(
        event, url=h.build_external_url_for_topic(topic.id)


@@ 404,9 431,14 @@ def topic_move(topic_id):
    )

    flash_success(
        f'Das Thema "{topic.title}" wurde '
        f'aus der Kategorie "{old_category.title}" '
        f'in die Kategorie "{new_category.title}" verschoben.',
        gettext(
            'Das Thema "%(topic_title)s" wurde '
            'aus der Kategorie "%(old_category_title)s" '
            'in die Kategorie "%(new_category_title)s" verschoben.',
            topic_title=topic.title,
            old_category_title=old_category.title,
            new_category_title=new_category.title,
        ),
        icon='move',
    )



@@ 430,7 462,10 @@ def topic_limit_to_announcements(topic_id):
    board_topic_command_service.limit_topic_to_announcements(topic.id)

    flash_success(
        f'Das Thema "{topic.title}" wurde auf Ankündigungen beschränkt.',
        gettext(
            'Das Thema "%(title)s" wurde auf Ankündigungen beschränkt.',
            title=topic.title,
        ),
        icon='announce',
    )



@@ 449,7 484,10 @@ def topic_remove_limit_to_announcements(topic_id):
    board_topic_command_service.remove_limit_of_topic_to_announcements(topic.id)

    flash_success(
        f'Das Thema "{topic.title}" wurde für normale Beiträge geöffnet.'
        gettext(
            'Das Thema "%(title)s" wurde für normale Beiträge geöffnet.',
            title=topic.title,
        )
    )

    return h.build_url_for_topic_in_category_view(topic)

M byceps/blueprints/site/consent/views.py => byceps/blueprints/site/consent/views.py +4 -3
@@ 10,6 10,7 @@ from datetime import datetime
from uuid import UUID

from flask import abort, g, request
from flask_babel import gettext

from ....services.consent import consent_service, subject_service
from ....services.verification_token import (


@@ 75,7 76,7 @@ def consent(token):
    try:
        subject_service.get_subjects(subject_ids_from_form)
    except subject_service.UnknownSubjectId:
        flash_error('Unbekanntes Zustimmungsthema')
        flash_error(gettext('Unbekanntes Zustimmungsthema'))
        return consent_form(token, erroneous_form=form)

    expressed_at = datetime.utcnow()


@@ 84,7 85,7 @@ def consent(token):
    )

    flash_success(
        'Vielen Dank für deine Zustimmung. Bitte melde dich erneut an.'
        gettext('Vielen Dank für deine Zustimmung. Bitte melde dich erneut an.')
    )
    return redirect_to('authentication.login.login_form')



@@ 107,7 108,7 @@ def _get_verification_token_or_404(token_value):
    )

    if verification_token is None:
        flash_error('Unbekannter Bestätigungscode.')
        flash_error(gettext('Unbekannter Bestätigungscode'))
        abort(404)

    return verification_token

M byceps/blueprints/site/newsletter/views.py => byceps/blueprints/site/newsletter/views.py +13 -2
@@ 9,6 9,7 @@ byceps.blueprints.site.newsletter.views
from datetime import datetime

from flask import abort, g
from flask_babel import gettext

from ....services.newsletter import command_service, service
from ....util.framework.blueprint import create_blueprint


@@ 28,7 29,12 @@ def subscribe(list_id):

    command_service.subscribe(g.user.id, list_.id, expressed_at)

    flash_success(f'Du hast dich zum Newsletter "{list_.title}" angemeldet.')
    flash_success(
        gettext(
            'Du hast dich zum Newsletter "%(title)s" angemeldet.',
            title=list_.title,
        )
    )


@blueprint.route('/lists/<list_id>/subscription', methods=['DELETE'])


@@ 40,7 46,12 @@ def unsubscribe(list_id):

    command_service.unsubscribe(g.user.id, list_.id, expressed_at)

    flash_success(f'Du hast dich vom Newsletter "{list_.title}" abgemeldet.')
    flash_success(
        gettext(
            'Du hast dich vom Newsletter "%(title)s" abgemeldet.',
            title=list_.title,
        )
    )


def _get_list_or_404(list_id):

M byceps/blueprints/site/seating/views.py => byceps/blueprints/site/seating/views.py +62 -19
@@ 7,6 7,7 @@ byceps.blueprints.site.seating.views
"""

from flask import abort, g, request
from flask_babel import gettext

from ....services.party import service as party_service
from ....services.seating import area_service as seating_area_service


@@ 87,7 88,9 @@ def manage_seats_in_area(slug):
    """Manage seats for assigned tickets in area."""
    if not _is_seat_management_enabled():
        flash_error(
            'Sitzplatzreservierungen können derzeit nicht verändert werden.'
            gettext(
                'Sitzplatzreservierungen können derzeit nicht verändert werden.'
            )
        )
        return redirect_to('.view_area', slug=slug)



@@ 149,10 152,20 @@ def _get_selected_ticket():
    if selected_ticket_id_arg:
        selected_ticket = ticket_service.find_ticket(selected_ticket_id_arg)
        if selected_ticket is None:
            flash_error(f'Ticket ID "{selected_ticket_id_arg}" not found.')
            flash_error(
                gettext(
                    'Ticket ID "%(selected_ticket_id_arg)s" not found.',
                    selected_ticket_id_arg=selected_ticket_id_arg,
                )
            )

    if (selected_ticket is not None) and selected_ticket.revoked:
        flash_error(f'Ticket "{selected_ticket.code}" wurde widerrufen.')
        flash_error(
            gettext(
                'Ticket "%(selected_ticket_code)s" wurde widerrufen.',
                selected_ticket_code=selected_ticket.code,
            )
        )
        selected_ticket = None

    return selected_ticket


@@ 167,7 180,9 @@ def occupy_seat(ticket_id, seat_id):
    """Use ticket to occupy seat."""
    if not _is_seat_management_enabled():
        flash_error(
            'Sitzplatzreservierungen können derzeit nicht verändert werden.'
            gettext(
                'Sitzplatzreservierungen können derzeit nicht verändert werden.'
            )
        )
        return



@@ 179,15 194,19 @@ def occupy_seat(ticket_id, seat_id):
        manager
    ):
        flash_error(
            'Du bist nicht berechtigt, den Sitzplatz '
            f'für Ticket {ticket.code} zu verwalten.'
            gettext(
                'Du bist nicht berechtigt, den Sitzplatz für Ticket %(ticket_code)s zu verwalten.',
                ticket_code=ticket.code,
            )
        )
        return

    seat = _get_seat_or_404(seat_id)

    if seat.is_occupied:
        flash_error(f'{seat.label} ist bereits belegt.')
        flash_error(
            gettext('%(seat_label)s ist bereits belegt.', seat_label=seat.label)
        )
        return

    try:


@@ 196,20 215,31 @@ def occupy_seat(ticket_id, seat_id):
        )
    except ticket_exceptions.SeatChangeDeniedForBundledTicket:
        flash_error(
            f'Ticket {ticket.code} gehört zu einem Paket '
            'und kann nicht einzeln verwaltet werden.'
            gettext(
                'Ticket %(ticket_code)s gehört zu einem Paket und kann nicht einzeln verwaltet werden.',
                ticket_code=ticket.code,
            )
        )
        return
    except ticket_exceptions.TicketCategoryMismatch:
        flash_error(
            f'Ticket {ticket.code} und {seat.label} haben '
            'unterschiedliche Kategorien.'
            gettext(
                'Ticket %(ticket_code)s und %(seat_label)s haben unterschiedliche Kategorien.',
                ticket_code=ticket.code,
                seat_label=seat.label,
            )
        )
        return
    except ValueError:
        abort(404)

    flash_success(f'{seat.label} wurde mit Ticket {ticket.code} reserviert.')
    flash_success(
        gettext(
            '%(seat_label)s wurde mit Ticket %(ticket_code)s reserviert.',
            seat_label=seat.label,
            ticket_code=ticket.code,
        )
    )


@blueprint.route('/ticket/<uuid:ticket_id>/seat', methods=['DELETE'])


@@ 219,14 249,21 @@ def release_seat(ticket_id):
    """Release the seat."""
    if not _is_seat_management_enabled():
        flash_error(
            'Sitzplatzreservierungen können derzeit nicht verändert werden.'
            gettext(
                'Sitzplatzreservierungen können derzeit nicht verändert werden.'
            )
        )
        return

    ticket = _get_ticket_or_404(ticket_id)

    if not ticket.occupied_seat:
        flash_error(f'Ticket {ticket.code} belegt keinen Sitzplatz.')
        flash_error(
            gettext(
                'Ticket %(ticket_code)s belegt keinen Sitzplatz.',
                ticket_code=ticket.code,
            )
        )
        return

    manager = g.user


@@ 235,8 272,10 @@ def release_seat(ticket_id):
        manager
    ):
        flash_error(
            'Du bist nicht berechtigt, den Sitzplatz '
            f'für Ticket {ticket.code} zu verwalten.'
            gettext(
                'Du bist nicht berechtigt, den Sitzplatz für Ticket %(ticket_code)s zu verwalten.',
                ticket_code=ticket.code,
            )
        )
        return



@@ 246,12 285,16 @@ def release_seat(ticket_id):
        ticket_seat_management_service.release_seat(ticket.id, manager.id)
    except ticket_exceptions.SeatChangeDeniedForBundledTicket:
        flash_error(
            f'Ticket {ticket.code} gehört zu einem Paket '
            'und kann nicht einzeln verwaltet werden.'
            gettext(
                'Ticket %(ticket_code)s gehört zu einem Paket und kann nicht einzeln verwaltet werden.',
                ticket_code=ticket.code,
            )
        )
        return

    flash_success(f'{seat.label} wurde freigegeben.')
    flash_success(
        gettext('%(seat_label)s wurde freigegeben.', seat_label=seat.label)
    )


def _is_seat_management_enabled():

M byceps/blueprints/site/shop/order/views.py => byceps/blueprints/site/shop/order/views.py +22 -18
@@ 9,6 9,7 @@ byceps.blueprints.site.shop.order.views
from decimal import Decimal

from flask import abort, g, request
from flask_babel import gettext

from .....services.country import service as country_service
from .....services.shop.article import service as article_service


@@ 48,7 49,7 @@ def order_form(erroneous_form=None):
    shop = shop_service.get_shop(storefront.shop_id)

    if storefront.closed:
        flash_notice('Der Shop ist derzeit geschlossen.')
        flash_notice(gettext('Der Shop ist derzeit geschlossen.'))
        return {'article_compilation': None}

    article_compilation = article_service.get_article_compilation_for_orderable_articles(


@@ 56,7 57,7 @@ def order_form(erroneous_form=None):
    )

    if article_compilation.is_empty():
        flash_error('Es sind keine Artikel verfügbar.')
        flash_error(gettext('Es sind keine Artikel verfügbar.'))
        return {'article_compilation': None}

    is_logged_in = g.user.is_active


@@ 97,7 98,7 @@ def order():
    shop = shop_service.get_shop(storefront.shop_id)

    if storefront.closed:
        flash_notice('Der Shop ist derzeit geschlossen.')
        flash_notice(gettext('Der Shop ist derzeit geschlossen.'))
        return order_form()

    article_compilation = article_service.get_article_compilation_for_orderable_articles(


@@ 105,7 106,7 @@ def order():
    )

    if article_compilation.is_empty():
        flash_error('Es sind keine Artikel verfügbar.')
        flash_error(gettext('Es sind keine Artikel verfügbar.'))
        return order_form()

    ArticlesOrderForm = assemble_articles_order_form(article_compilation)


@@ 117,7 118,7 @@ def order():
    cart = form.get_cart(article_compilation)

    if cart.is_empty():
        flash_error('Es wurden keine Artikel ausgewählt.')
        flash_error(gettext('Es wurden keine Artikel ausgewählt.'))
        return order_form(form)

    orderer = form.get_orderer(g.user.id)


@@ 125,7 126,7 @@ def order():
    try:
        order = _place_order(storefront.id, orderer, cart)
    except order_service.OrderFailed:
        flash_error('Die Bestellung ist fehlgeschlagen.')
        flash_error(gettext('Die Bestellung ist fehlgeschlagen.'))
        return order_form(form)

    _flash_order_success(order)


@@ 148,7 149,7 @@ def order_single_form(article_id, erroneous_form=None):
    form = erroneous_form if erroneous_form else OrderForm(obj=user.detail)

    if storefront.closed:
        flash_notice('Der Shop ist derzeit geschlossen.')
        flash_notice(gettext('Der Shop ist derzeit geschlossen.'))
        return {
            'form': form,
            'article': None,


@@ 161,14 162,14 @@ def order_single_form(article_id, erroneous_form=None):
    country_names = country_service.get_country_names()

    if article.not_directly_orderable:
        flash_error('Der Artikel kann nicht direkt bestellt werden.')
        flash_error(gettext('Der Artikel kann nicht direkt bestellt werden.'))
        return {
            'form': form,
            'article': None,
        }

    if order_service.has_user_placed_orders(user.id, shop.id):
        flash_error('Du kannst keine weitere Bestellung aufgeben.')
        flash_error(gettext('Du kannst keine weitere Bestellung aufgeben.'))
        return {
            'form': form,
            'article': None,


@@ 177,7 178,7 @@ def order_single_form(article_id, erroneous_form=None):
    if article.quantity < 1 or not article_service.is_article_available_now(
        article
    ):
        flash_error('Der Artikel ist nicht verfügbar.')
        flash_error(gettext('Der Artikel ist nicht verfügbar.'))
        return {
            'form': form,
            'article': None,


@@ 202,11 203,11 @@ def order_single(article_id):
    shop = shop_service.get_shop(storefront.shop_id)

    if storefront.closed:
        flash_notice('Der Shop ist derzeit geschlossen.')
        flash_notice(gettext('Der Shop ist derzeit geschlossen.'))
        return order_single_form(article.id)

    if article.not_directly_orderable:
        flash_error('Der Artikel kann nicht direkt bestellt werden.')
        flash_error(gettext('Der Artikel kann nicht direkt bestellt werden.'))
        return order_single_form(article.id)

    article_compilation = article_service.get_article_compilation_for_single_article(


@@ 216,13 217,13 @@ def order_single(article_id):
    user = g.user

    if order_service.has_user_placed_orders(user.id, shop.id):
        flash_error('Du kannst keine weitere Bestellung aufgeben.')
        flash_error(gettext('Du kannst keine weitere Bestellung aufgeben.'))
        return order_single_form(article.id)

    if article.quantity < 1 or not article_service.is_article_available_now(
        article
    ):
        flash_error('Der Artikel ist nicht verfügbar.')
        flash_error(gettext('Der Artikel ist nicht verfügbar.'))
        return order_single_form(article.id)

    form = OrderForm(request.form)


@@ 236,7 237,7 @@ def order_single(article_id):
    try:
        order = _place_order(storefront.id, orderer, cart)
    except order_service.OrderFailed:
        flash_error('Die Bestellung ist fehlgeschlagen.')
        flash_error(gettext('Die Bestellung ist fehlgeschlagen.'))
        return order_form(form)

    _flash_order_success(order)


@@ 285,8 286,11 @@ def _place_order(storefront_id, orderer, cart):

def _flash_order_success(order):
    flash_success(
        'Deine Bestellung mit der '
        f'Bestellnummer <strong>{order.order_number}</strong> '
        'wurde entgegengenommen. Vielen Dank!',
        gettext(
            'Deine Bestellung mit der '
            'Bestellnummer <strong>%(order_number)s</strong> '
            'wurde entgegengenommen. Vielen Dank!',
            order_number=order.order_number,
        ),
        text_is_safe=True,
    )

M byceps/blueprints/site/shop/orders/views.py => byceps/blueprints/site/shop/orders/views.py +13 -9
@@ 7,6 7,7 @@ byceps.blueprints.site.shop.orders.views
"""

from flask import abort, g, request
from flask_babel import gettext

from .....services.shop.order.email import service as order_email_service
from .....services.shop.order import service as order_service


@@ 107,13 108,14 @@ def cancel_form(order_id, erroneous_form=None):
    order = _get_order_by_current_user_or_404(order_id)

    if order.is_canceled:
        flash_error('Die Bestellung ist bereits storniert worden.')
        flash_error(gettext('Die Bestellung ist bereits storniert worden.'))
        return redirect_to('.view', order_id=order.id)

    if order.is_paid:
        flash_error(
            'Die Bestellung ist bereits bezahlt worden. '
            'Du kannst sie nicht mehr selbst stornieren.'
            gettext(
                'Die Bestellung ist bereits bezahlt worden. Du kannst sie nicht mehr selbst stornieren.'
            )
        )
        return redirect_to('.view', order_id=order.id)



@@ 134,13 136,14 @@ def cancel(order_id):
    order = _get_order_by_current_user_or_404(order_id)

    if order.is_canceled:
        flash_error('Die Bestellung ist bereits storniert worden.')
        flash_error(gettext('Die Bestellung ist bereits storniert worden.'))
        return redirect_to('.view', order_id=order.id)

    if order.is_paid:
        flash_error(
            'Die Bestellung ist bereits bezahlt worden. '
            'Du kannst sie nicht mehr selbst stornieren.'
            gettext(
                'Die Bestellung ist bereits bezahlt worden. Du kannst sie nicht mehr selbst stornieren.'
            )
        )
        return redirect_to('.view', order_id=order.id)



@@ 154,12 157,13 @@ def cancel(order_id):
        event = order_service.cancel_order(order.id, g.user.id, reason)
    except order_service.OrderAlreadyCanceled:
        flash_error(
            'Die Bestellung ist bereits storniert worden; '
            'der Bezahlstatus kann nicht mehr geändert werden.'
            gettext(
                'Die Bestellung ist bereits storniert worden; der Bezahlstatus kann nicht mehr geändert werden.'
            )
        )
        return redirect_to('.view', order_id=order.id)

    flash_success('Die Bestellung wurde storniert.')
    flash_success(gettext('Die Bestellung wurde storniert.'))

    order_email_service.send_email_for_canceled_order_to_orderer(order.id)


M byceps/blueprints/site/ticketing/views.py => byceps/blueprints/site/ticketing/views.py +35 -11
@@ 7,6 7,7 @@ byceps.blueprints.site.ticketing.views
"""

from flask import abort, g, request
from flask_babel import gettext

from ....services.party import service as party_service
from ....services.ticketing import (


@@ 150,8 151,12 @@ def appoint_user(ticket_id):
    ticket_user_management_service.appoint_user(ticket.id, user.id, manager.id)

    flash_success(
        f'{user.screen_name} wurde als Nutzer/in '
        f'von Ticket {ticket.code} eingetragen.'
        gettext(
            '%(screen_name)s wurde als Nutzer/in '
            'von Ticket %(ticket_code)s eingetragen.',
            screen_name=user.screen_name,
            ticket_code=ticket.code,
        )
    )

    notification_service.notify_appointed_user(ticket, user, manager)


@@ 179,7 184,10 @@ def withdraw_user(ticket_id):
    )

    flash_success(
        f'Du wurdest als Nutzer/in von Ticket {ticket.code} eingetragen.'
        gettext(
            'Du wurdest als Nutzer/in von Ticket %(ticket_code)s eingetragen.',
            ticket_code=ticket.code,
        )
    )




@@ 236,8 244,12 @@ def appoint_user_manager(ticket_id):
    )

    flash_success(
        f'{user.screen_name} wurde als Nutzer-Verwalter/in '
        f'von Ticket {ticket.code} eingetragen.'
        gettext(
            '%(screen_name)s wurde als Nutzer-Verwalter/in '
            'von Ticket %(ticket_code)s eingetragen.',
            screen_name=user.screen_name,
            ticket_code=ticket.code,
        )
    )

    notification_service.notify_appointed_user_manager(ticket, user, manager)


@@ 265,7 277,10 @@ def withdraw_user_manager(ticket_id):
    ticket_user_management_service.withdraw_user_manager(ticket.id, manager.id)

    flash_success(
        f'Der Nutzer-Verwalter von Ticket {ticket.code} wurde entfernt.'
        gettext(
            'Der Nutzer-Verwalter von Ticket %(ticket_code)s wurde entfernt.',
            ticket_code=ticket.code,
        )
    )

    notification_service.notify_withdrawn_user_manager(ticket, user, manager)


@@ 320,8 335,12 @@ def appoint_seat_manager(ticket_id):
    )

    flash_success(
        f'{user.screen_name} wurde als Sitzplatz-Verwalter/in '
        f'von Ticket {ticket.code} eingetragen.'
        gettext(
            '%(screen_name)s wurde als Sitzplatz-Verwalter/in '
            'von Ticket %(ticket_code)s eingetragen.',
            screen_name=user.screen_name,
            ticket_code=ticket.code,
        )
    )

    notification_service.notify_appointed_seat_manager(ticket, user, manager)


@@ 347,7 366,10 @@ def withdraw_seat_manager(ticket_id):
    ticket_seat_management_service.withdraw_seat_manager(ticket.id, manager.id)

    flash_success(
        f'Der Sitzplatz-Verwalter von Ticket {ticket.code} wurde entfernt.'
        gettext(
            'Der Sitzplatz-Verwalter von Ticket %(ticket_code)s wurde entfernt.',
            ticket_code=ticket.code,
        )
    )

    notification_service.notify_withdrawn_seat_manager(ticket, user, manager)


@@ 358,7 380,7 @@ def withdraw_seat_manager(ticket_id):

def _abort_if_ticket_management_disabled():
    if not _is_ticket_management_enabled():
        flash_error('Tickets können derzeit nicht verändert werden.')
        flash_error(gettext('Tickets können derzeit nicht verändert werden.'))
        abort(403)




@@ 385,7 407,9 @@ def _get_ticket_or_404(ticket_id):
def _abort_if_ticket_user_checked_in(ticket):
    if ticket.user_checked_in:
        flash_error(
            'Es ist bereits jemand mit diesem Ticket eingecheckt worden.'
            gettext(
                'Es ist bereits jemand mit diesem Ticket eingecheckt worden.'
            )
        )
        abort(403)


M byceps/blueprints/site/user/avatar/views.py => byceps/blueprints/site/user/avatar/views.py +6 -3
@@ 7,6 7,7 @@ byceps.blueprints.site.user.avatar.views
"""

from flask import abort, g, request
from flask_babel import gettext

from .....services.image import service as image_service
from .....services.user import service as user_service


@@ 66,7 67,7 @@ def update():

    _update(user.id, image)

    flash_success('Dein Avatarbild wurde aktualisiert.', icon='upload')
    flash_success(gettext('Dein Avatarbild wurde aktualisiert.'), icon='upload')
    user_avatar_signals.avatar_updated.send(None, user_id=user.id)

    return redirect_to('user_settings.view')


@@ 100,10 101,12 @@ def delete():
        # No avatar selected.
        # But that's ok, deletions should be idempotent.
        flash_notice(
            'Es ist kein Avatarbild gesetzt, das entfernt werden könnte.'
            gettext(
                'Es ist kein Avatarbild gesetzt, das entfernt werden könnte.'
            )
        )
    else:
        flash_success('Dein Avatarbild wurde entfernt.')
        flash_success(gettext('Dein Avatarbild wurde entfernt.'))


def _get_current_user_or_404():

M byceps/blueprints/site/user/creation/views.py => byceps/blueprints/site/user/creation/views.py +14 -5
@@ 10,6 10,7 @@ from datetime import datetime
from typing import Optional, Set

from flask import abort, g, request
from flask_babel import gettext

from .....services.brand import settings_service as brand_settings_service
from .....services.consent import subject_service as consent_subject_service


@@ 114,14 115,20 @@ def create():
        )
    except user_creation_service.UserCreationFailed:
        flash_error(
            f'Das Benutzerkonto für "{screen_name}" konnte nicht angelegt werden.'
            gettext(
                'Das Benutzerkonto für "%(screen_name)s" konnte nicht angelegt werden.',
                screen_name=screen_name,
            )
        )
        return create_form(form)

    flash_success(
        f'Das Benutzerkonto für "{user.screen_name}" wurde angelegt. '
        'Bevor du dich damit anmelden kannst, muss zunächst der Link in '
        'der an die angegebene Adresse verschickten E-Mail besucht werden.'
        gettext(
            'Das Benutzerkonto für "%(screen_name)s" wurde angelegt. '
            'Bevor du dich damit anmelden kannst, muss zunächst der Link in der '
            'an die angegebene Adresse verschickten E-Mail besucht werden.',
            screen_name=user.screen_name,
        )
    )

    user_signals.account_created.send(None, event=event)


@@ 139,7 146,9 @@ def create():
def _abort_if_user_account_creation_disabled():
    site = site_service.get_site(g.site_id)
    if not site.user_account_creation_enabled:
        flash_error('Das Erstellen von Benutzerkonten ist deaktiviert.')
        flash_error(
            gettext('Das Erstellen von Benutzerkonten ist deaktiviert.')
        )
        abort(403)



M byceps/blueprints/site/user/email_address/views.py => byceps/blueprints/site/user/email_address/views.py +33 -11
@@ 7,6 7,7 @@ byceps.blueprints.site.user.email_address.views
"""

from flask import abort, g, request
from flask_babel import gettext

from .....services.user import email_address_verification_service
from .....services.user import (


@@ 53,24 54,39 @@ def request_confirmation_email():
    )

    if (user is None) or user.deleted:
        flash_error(f'Der Benutzername "{screen_name}" ist unbekannt.')
        flash_error(
            gettext(
                'Der Benutzername "%(screen_name)s" ist unbekannt.',
                screen_name=screen_name,
            )
        )
        return request_confirmation_email_form(form)

    if user.email_address is None:
        flash_error(
            f'Für das Benutzerkonto "{screen_name}" ist keine E-Mail-Adresse hinterlegt.'
            gettext(
                'Für das Benutzerkonto "%(screen_name)s" ist keine E-Mail-Adresse hinterlegt.',
                screen_name=screen_name,
            )
        )
        return request_confirmation_email_form(form)

    if user.email_address_verified:
        flash_notice(
            f'Die E-Mail-Adresse für den Benutzernamen "{user.screen_name}" '
            'wurde bereits bestätigt.'
            gettext(
                'Die E-Mail-Adresse für den Benutzernamen "%(screen_name)s" wurde bereits bestätigt.',
                screen_name=user.screen_name,
            )
        )
        return request_confirmation_email_form()

    if user.suspended:
        flash_error(f'Das Benutzerkonto "{screen_name}" ist gesperrt.')
        flash_error(
            gettext(
                'Das Benutzerkonto "%(screen_name)s" ist gesperrt.',
                screen_name=screen_name,
            )
        )
        return request_confirmation_email_form()

    email_address_verification_service.send_email_address_confirmation_email(


@@ 78,9 94,12 @@ def request_confirmation_email():
    )

    flash_success(
        'Der Link zur Bestätigung der für den '
        f'Benutzernamen "{user.screen_name}" '
        'hinterlegten E-Mail-Adresse wurde erneut versendet.'
        gettext(
            'Der Link zur Bestätigung der für den Benutzernamen '
            '"%(screen_name)s" hinterlegten E-Mail-Adresse '
            'wurde erneut versendet.',
            screen_name=user.screen_name,
        )
    )

    return redirect_to('.request_confirmation_email_form')


@@ 101,18 120,21 @@ def confirm(token):

    user = user_service.get_db_user(verification_token.user_id)
    if (user is None) or user.suspended or user.deleted:
        flash_error('Es wurde kein gültiges Token angegeben.')
        flash_error(gettext('Es wurde kein gültiges Token angegeben.'))
        abort(404)

    event = email_address_verification_service.confirm_email_address(
        verification_token
    )
    flash_success('Die E-Mail-Adresse wurde bestätigt.')
    flash_success(gettext('Die E-Mail-Adresse wurde bestätigt.'))

    if not user.initialized:
        user_command_service.initialize_account(user.id)
        flash_success(
            f'Das Benutzerkonto "{user.screen_name}" wurde aktiviert.'
            gettext(
                'Das Benutzerkonto "%(screen_name)s" wurde aktiviert.',
                screen_name=user.screen_name,
            )
        )

    user_signals.email_address_confirmed.send(None, event=event)

M byceps/blueprints/site/user/settings/views.py => byceps/blueprints/site/user/settings/views.py +8 -2
@@ 9,6 9,7 @@ byceps.blueprints.site.user.settings.views
from typing import Optional

from flask import abort, g, request
from flask_babel import gettext

from .....config import get_app_mode
from .....services.brand import settings_service as brand_settings_service


@@ 90,7 91,12 @@ def change_screen_name():

    user_signals.screen_name_changed.send(None, event=event)

    flash_success(f'Dein Benutzername wurde zu "{new_screen_name}" geändert.')
    flash_success(
        gettext(
            'Dein Benutzername wurde zu "%(new_screen_name)s" geändert.',
            new_screen_name=new_screen_name,
        )
    )

    return redirect_to('.view')



@@ 144,7 150,7 @@ def details_update():
        current_user.id,  # initiator_id
    )

    flash_success('Deine Daten wurden gespeichert.')
    flash_success(gettext('Deine Daten wurden gespeichert.'))

    user_signals.details_updated.send(None, event=event)


M byceps/blueprints/site/user_group/views.py => byceps/blueprints/site/user_group/views.py +13 -3
@@ 7,6 7,7 @@ byceps.blueprints.site.user_group.views
"""

from flask import g, request
from flask_babel import gettext

from ....services.user_group import service as user_group_service
from ....util.framework.blueprint import create_blueprint


@@ 37,7 38,9 @@ def create_form(erroneous_form=None):
    """Show a form to create a group."""
    if not g.user.is_active:
        flash_error(
            'Du musst angemeldet sein, um eine Benutzergruppe erstellen zu können.'
            gettext(
                'Du musst angemeldet sein, um eine Benutzergruppe erstellen zu können.'
            )
        )
        return redirect_to('.index')



@@ 53,7 56,9 @@ def create():
    """Create a group."""
    if not g.user.is_active:
        flash_error(
            'Du musst angemeldet sein, um eine Benutzergruppe erstellen zu können.'
            gettext(
                'Du musst angemeldet sein, um eine Benutzergruppe erstellen zu können.'
            )
        )
        return redirect_to('.index')



@@ 65,5 70,10 @@ def create():

    group = user_group_service.create_group(creator.id, title, description)

    flash_success(f'Die Gruppe "{group.title}" wurde erstellt.')
    flash_success(
        gettext(
            'Die Gruppe "%(group_title)s" wurde erstellt.',
            group_title=group.title,
        )
    )
    return redirect_to('.index')

M byceps/blueprints/site/user_message/views.py => byceps/blueprints/site/user_message/views.py +5 -1
@@ 9,6 9,7 @@ Send messages from one user to another.
"""

from flask import abort, g, request, url_for
from flask_babel import gettext

from ....services.user import service as user_service
from ....services.user_message import service as user_message_service


@@ 59,7 60,10 @@ def create(recipient_id):
    )

    flash_success(
        f'Deine Nachricht an {recipient.screen_name} wurde versendet.'
        gettext(
            'Deine Nachricht an %(screen_name)s wurde versendet.',
            screen_name=recipient.screen_name,
        )
    )

    return redirect_to('user_profile.view', user_id=recipient.id)

M byceps/util/views.py => byceps/util/views.py +2 -1
@@ 19,6 19,7 @@ from flask import (
    stream_with_context,
    url_for,
)
from flask_babel import gettext

from .framework.flash import flash_notice



@@ 29,7 30,7 @@ def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not g.user.is_active:
            flash_notice('Bitte melde dich an.')
            flash_notice(gettext('Bitte melde dich an.'))
            return redirect_to('authentication.login.login_form')
        return func(*args, **kwargs)