Add bilingual UI and improve event actions layout

Add persistent German/English switching, translated UI messages and English 12-hour times. Align desktop event actions while preserving mobile layouts and record bilingual project guidance.

Include pending diary deletion exclusions. Validate both languages with nine tests.
This commit is contained in:
kai
2026-09-14 15:31:56 +02:00
parent b2af6c6857
commit b2af866cf0
28 changed files with 1339 additions and 506 deletions
+41 -39
View File
@@ -1,11 +1,12 @@
<!DOCTYPE html>
<html lang="de">
<html lang="{{ language() }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ concert.artist }} bearbeiten · MetalCircle</title>
<title>{{ concert.artist }} {{ _('bearbeiten · MetalCircle') }}</title>
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/language.css">
</head>
<body>
<header>
@@ -13,127 +14,128 @@
<a href="/" class="logo"><img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle"></a>
{% include '_user_menu.html' %}
</div>
{% include '_language_switch.html' %}
</header>
<main>
<div class="page-title">
<h1>✏️ Veranstaltung bearbeiten</h1>
<p>Änderungen werden direkt bei der Veranstaltung gespeichert.</p>
<h1>{{ _('✏️ Veranstaltung bearbeiten') }}</h1>
<p>{{ _('Änderungen werden direkt bei der Veranstaltung gespeichert.') }}</p>
</div>
<section class="empty">
<form method="post" action="/concerts/{{ concert.id }}/edit" enctype="multipart/form-data">
{% if can_edit_details %}
<p>
<label>Kategorie<br>
<label>{{ _('Kategorie') }}<br>
<select name="event_type" id="event-type" required {% if not can_manage_access %}disabled{% endif %}>
<option value="concert" {% if concert.event_type == 'concert' %}selected{% endif %}>Konzert</option>
<option value="concert" {% if concert.event_type == 'concert' %}selected{% endif %}>{{ _('Konzert') }}</option>
<option value="festival" {% if concert.event_type == 'festival' %}selected{% endif %}>Festival</option>
<option value="other" {% if concert.event_type == 'other' %}selected{% endif %}>Sonstiges</option>
<option value="other" {% if concert.event_type == 'other' %}selected{% endif %}>{{ _('Sonstiges') }}</option>
</select>
</label>
</p>
<p id="parent-event-field" {% if concert.event_type != 'other' %}hidden{% endif %}>
<label>Gehört zu<br>
<label>{{ _('Gehört zu') }}<br>
<select name="parent_event_id" id="parent-event-id">
<option value="">Keine Verknüpfung eigenständige Karte</option>
<option value="">{{ _('Keine Verknüpfung eigenständige Karte') }}</option>
{% for event in linkable_events %}
<option value="{{ event.id }}" {% if concert.parent_event and concert.parent_event.id == event.id %}selected{% endif %}>{{ event.event_type_label }} · {{ event.title }} · {{ event.date }}</option>
<option value="{{ event.id }}" {% if concert.parent_event and concert.parent_event.id == event.id %}selected{% endif %}>{{ event.event_type_label | t }} · {{ event.title }} · {{ event.date }}</option>
{% endfor %}
</select>
</label>
</p>
{% if can_manage_access %}<fieldset id="event-visibility-field" {% if concert.event_type != 'other' %}hidden{% endif %}>
<legend>Sichtbarkeit</legend>
<label><input type="radio" name="visibility" value="public" {% if concert.visibility == 'public' %}checked{% endif %}> Öffentlich</label><br>
<label><input type="radio" name="visibility" value="friends" {% if concert.visibility == 'friends' %}checked{% endif %}> Nur Freunde</label><br>
<label><input type="radio" name="visibility" value="private" {% if concert.visibility == 'private' %}checked{% endif %}> Privat nur eingeladene Mitglieder</label>
<legend>{{ _('Sichtbarkeit') }}</legend>
<label><input type="radio" name="visibility" value="public" {% if concert.visibility == 'public' %}checked{% endif %}> {{ _('Öffentlich') }}</label><br>
<label><input type="radio" name="visibility" value="friends" {% if concert.visibility == 'friends' %}checked{% endif %}> {{ _('Nur Freunde') }}</label><br>
<label><input type="radio" name="visibility" value="private" {% if concert.visibility == 'private' %}checked{% endif %}> {{ _('Privat nur eingeladene Mitglieder') }}</label>
</fieldset>
<fieldset id="event-invitations-field" {% if concert.event_type != 'other' or concert.visibility != 'private' %}hidden{% endif %}>
<legend>Mitglieder einladen</legend>
<legend>{{ _('Mitglieder einladen') }}</legend>
{% for member in invitable_users %}
<label style="display:block;margin:7px 0"><input type="checkbox" name="invited_user_ids" value="{{ member.id }}" {% if member.id in invited_user_ids %}checked{% endif %}> {{ member.display_name }} <small>@{{ member.username }}</small></label>
{% else %}<p>Noch keine weiteren Mitglieder vorhanden.</p>{% endfor %}
{% else %}<p>{{ _('Noch keine weiteren Mitglieder vorhanden.') }}</p>{% endfor %}
</fieldset>
{% endif %}
{% endif %}
<p>
<label>Titel / Künstler / Band<br>
<label>{{ _('Titel / Künstler / Band') }}<br>
<input type="text" name="artist" value="{{ concert.artist }}" {% if not can_edit_title %}disabled{% endif %} required>
</label>
</p>
<p>
<label>Bands / Line-up <small>(eine Band pro Zeile)</small><br>
<label>{{ _('Bands / Line-up') }} <small>{{ _('(eine Band pro Zeile)') }}</small><br>
<textarea name="band_names" rows="3" maxlength="4000" {% if not can_edit_title %}disabled{% endif %}>{{ band_names }}</textarea>
</label>
<small>Diese Namen werden einzeln für die Folgen-Funktion verwendet.</small>
<small>{{ _('Diese Namen werden einzeln für die Folgen-Funktion verwendet.') }}</small>
</p>
{% if can_edit_details %}
<p>
<label>Veranstaltungsort<br>
<input type="text" name="venue_name" value="{{ concert.venue.name if concert.venue.id else '' }}" placeholder="Name des Veranstaltungsorts">
<label>{{ _('Veranstaltungsort') }}<br>
<input type="text" name="venue_name" value="{{ concert.venue.name if concert.venue.id else '' }}" placeholder="{{ _('Name des Veranstaltungsorts') }}">
</label>
</p>
<p>
<label>Straße<br>
<label>{{ _('Straße') }}<br>
<input type="text" name="street" value="{{ concert.venue.street or '' }}">
</label>
</p>
<p>
<label>PLZ und Ort<br>
<input type="text" name="postal_code" value="{{ concert.venue.postal_code or '' }}" placeholder="PLZ">
<input type="text" name="city" value="{{ concert.venue.city or '' }}" placeholder="Ort">
<label>{{ _('PLZ und Ort') }}<br>
<input type="text" name="postal_code" value="{{ concert.venue.postal_code or '' }}" placeholder="{{ _('PLZ') }}">
<input type="text" name="city" value="{{ concert.venue.city or '' }}" placeholder="{{ _('Ort') }}">
</label>
</p>
<p>
<label>Land<br>
<input type="text" name="country" value="{{ concert.venue.country or '' }}" placeholder="Land">
<label>{{ _('Land') }}<br>
<input type="text" name="country" value="{{ concert.venue.country or '' }}" placeholder="{{ _('Land') }}">
</label>
</p>
<input type="hidden" name="latitude" value="{{ concert.venue.latitude or '' }}">
<input type="hidden" name="longitude" value="{{ concert.venue.longitude or '' }}">
<p>
<label>Beginn<br>
<label>{{ _('Beginn') }}<br>
<input type="datetime-local" name="start_datetime" value="{{ concert.start_local }}" required>
</label>
</p>
<p id="festival-end-field" {% if concert.event_type != 'festival' %}hidden{% endif %}>
<label>Ende<br>
<label>{{ _('Ende') }}<br>
<input type="datetime-local" name="end_datetime" id="festival-end-datetime" value="{{ concert.end_local }}" {% if concert.event_type == 'festival' %}required{% endif %}>
</label>
</p>
<p>
<label>Beschreibung<br>
<label>{{ _('Beschreibung') }}<br>
<textarea name="description" rows="5">{{ concert.description or '' }}</textarea>
</label>
</p>
<p>
<label>Ticket-Link<br>
<label>{{ _('Ticket-Link') }}<br>
<input type="url" name="ticket_url" value="{{ concert.ticket_url or '' }}" placeholder="https://...">
</label>
</p>
<p>
<label>Ticketpreis<br>
<label>{{ _('Ticketpreis') }}<br>
<input type="number" name="ticket_price" value="{{ concert.ticket_price or '' }}" step="0.01" min="0">
</label>
</p>
<p>
<label>Flyer extern verlinken <small>(bevorzugt)</small><br>
<label>{{ _('Flyer extern verlinken') }} <small>{{ _('(bevorzugt)') }}</small><br>
<input type="url" name="flyer_url" value="{{ concert.flyer_url or '' }}" placeholder="https://veranstalter.example/flyer.jpg">
</label>
<small>Bitte möglichst die offizielle Quelle verwenden. Ein externer Link wird gegenüber einem Upload bevorzugt und unter dem Bild genannt. Feld leeren, um wieder den Upload zu verwenden.</small>
<small>{{ _('Bitte möglichst die offizielle Quelle verwenden. Ein externer Link wird gegenüber einem Upload bevorzugt und unter dem Bild genannt. Feld leeren, um wieder den Upload zu verwenden.') }}</small>
</p>
<p>
<label>Neuen Flyer hochladen<br>
<label>{{ _('Neuen Flyer hochladen') }}<br>
<input type="file" name="flyer" accept="image/jpeg,image/png,image/webp">
</label>
<small>Ohne neue Datei bleibt der bisherige Flyer erhalten.</small>
<small>{{ _('Ohne neue Datei bleibt der bisherige Flyer erhalten.') }}</small>
</p>
{% if concert.flyer_url or concert.flyer_path %}
<p><img src="{{ concert.flyer_url or concert.flyer_path }}" alt="Aktueller Flyer" referrerpolicy="no-referrer" style="max-width: 300px; max-height: 400px; border-radius: 10px;"></p>
<p><img src="{{ concert.flyer_url or concert.flyer_path }}" alt="{{ _('Aktueller Flyer') }}" referrerpolicy="no-referrer" style="max-width: 300px; max-height: 400px; border-radius: 10px;"></p>
{% endif %}
{% endif %}
<div>
<button type="submit" class="button">💾 Änderungen speichern</button>
<a href="/concerts/{{ concert.id }}" class="button button-secondary">Abbrechen</a>
<button type="submit" class="button">{{ _('💾 Änderungen speichern') }}</button>
<a href="/concerts/{{ concert.id }}" class="button button-secondary">{{ _('Abbrechen') }}</a>
</div>
</form>
</section>