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:
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="{{ language() }}">
|
||||
|
||||
<head>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
>
|
||||
|
||||
<title>Veranstaltung hinzufügen · MetalCircle</title>
|
||||
<title>{{ _('Veranstaltung hinzufügen · MetalCircle') }}</title>
|
||||
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
|
||||
|
||||
<link
|
||||
@@ -71,6 +71,7 @@
|
||||
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="/static/css/language.css">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -88,6 +89,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
{% include '_language_switch.html' %}
|
||||
</header>
|
||||
|
||||
|
||||
@@ -95,10 +97,10 @@
|
||||
|
||||
<div class="page-title">
|
||||
|
||||
<h1>🎤 Veranstaltung hinzufügen</h1>
|
||||
<h1>{{ _('🎤 Veranstaltung hinzufügen') }}</h1>
|
||||
|
||||
<p>
|
||||
Trag ein, worauf wir uns freuen.
|
||||
{{ _('Trag ein, worauf wir uns freuen.') }}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -113,38 +115,38 @@
|
||||
>
|
||||
|
||||
<p>
|
||||
<label for="event-type">Kategorie</label><br>
|
||||
<label for="event-type">{{ _('Kategorie') }}</label><br>
|
||||
<select name="event_type" id="event-type" required>
|
||||
<option value="concert">Konzert</option>
|
||||
<option value="concert">{{ _('Konzert') }}</option>
|
||||
<option value="festival">Festival</option>
|
||||
<option value="other">Sonstiges</option>
|
||||
<option value="other">{{ _('Sonstiges') }}</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p id="parent-event-field" hidden>
|
||||
<label for="parent-event-id">Gehört zu</label><br>
|
||||
<label for="parent-event-id">{{ _('Gehört zu') }}</label><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 }}">{{ event.event_type_label }} · {{ event.title }} · {{ event.date }}</option>
|
||||
<option value="{{ event.id }}">{{ event.event_type_label | t }} · {{ event.title }} · {{ event.date }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small>Zum Beispiel ein gemeinsames Vortrinken vor einem Konzert.</small>
|
||||
<small>{{ _('Zum Beispiel ein gemeinsames Vortrinken vor einem Konzert.') }}</small>
|
||||
</p>
|
||||
|
||||
<fieldset id="event-visibility-field" hidden>
|
||||
<legend>Sichtbarkeit</legend>
|
||||
<label><input type="radio" name="visibility" value="public" checked> Öffentlich – alle können die Veranstaltung sehen und teilnehmen</label><br>
|
||||
<label><input type="radio" name="visibility" value="friends"> Nur Freunde – nur bestätigte Freunde können sie sehen und teilnehmen</label><br>
|
||||
<label><input type="radio" name="visibility" value="private"> Privat – nur gezielt eingeladene Mitglieder</label>
|
||||
<legend>{{ _('Sichtbarkeit') }}</legend>
|
||||
<label><input type="radio" name="visibility" value="public" checked> {{ _('Öffentlich – alle können die Veranstaltung sehen und teilnehmen') }}</label><br>
|
||||
<label><input type="radio" name="visibility" value="friends"> {{ _('Nur Freunde – nur bestätigte Freunde können sie sehen und teilnehmen') }}</label><br>
|
||||
<label><input type="radio" name="visibility" value="private"> {{ _('Privat – nur gezielt eingeladene Mitglieder') }}</label>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="event-invitations-field" hidden>
|
||||
<legend>Mitglieder einladen</legend>
|
||||
<p class="flyer-help">Eingeladene erhalten eine Benachrichtigung und sehen die Veranstaltung in ihrer Übersicht.</p>
|
||||
<legend>{{ _('Mitglieder einladen') }}</legend>
|
||||
<p class="flyer-help">{{ _('Eingeladene erhalten eine Benachrichtigung und sehen die Veranstaltung in ihrer Übersicht.') }}</p>
|
||||
{% for member in invitable_users %}
|
||||
<label style="display:block;margin:7px 0"><input type="checkbox" name="invited_user_ids" value="{{ member.id }}"> {{ 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>
|
||||
|
||||
<!-- ================================================= -->
|
||||
@@ -154,7 +156,7 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Titel / Künstler / Band<br>
|
||||
{{ _('Titel / Künstler / Band') }}<br>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
@@ -163,7 +165,7 @@
|
||||
required
|
||||
minlength="2"
|
||||
maxlength="255"
|
||||
placeholder="z. B. Iron Maiden"
|
||||
placeholder="{{ _('z. B. Iron Maiden') }}"
|
||||
>
|
||||
|
||||
</label>
|
||||
@@ -171,10 +173,10 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="band-names">Bands / Line-up <small>(eine Band pro Zeile)</small><br>
|
||||
<textarea id="band-names" name="band_names" rows="3" maxlength="4000" placeholder="dArtagnan Supportband"></textarea>
|
||||
<label for="band-names">{{ _('Bands / Line-up') }} <small>{{ _('(eine Band pro Zeile)') }}</small><br>
|
||||
<textarea id="band-names" name="band_names" rows="3" maxlength="4000" placeholder="{{ _('dArtagnan\nSupportband') }}"></textarea>
|
||||
</label>
|
||||
<small>Der Veranstaltungstitel bleibt frei. Diese Namen werden einzeln für „Band folgen“ verwendet.</small>
|
||||
<small>{{ _('Der Veranstaltungstitel bleibt frei. Diese Namen werden einzeln für „Band folgen“ verwendet.') }}</small>
|
||||
</p>
|
||||
|
||||
<!-- ================================================= -->
|
||||
@@ -184,7 +186,7 @@
|
||||
<div class="venue-search">
|
||||
|
||||
<label for="venue-search">
|
||||
Veranstaltungsort
|
||||
{{ _('Veranstaltungsort') }}
|
||||
</label>
|
||||
|
||||
<div class="venue-search-control">
|
||||
@@ -193,7 +195,7 @@
|
||||
id="venue-search"
|
||||
name="venue_name"
|
||||
autocomplete="off"
|
||||
placeholder="z. B. Turbinenhalle"
|
||||
placeholder="{{ _('z. B. Turbinenhalle') }}"
|
||||
required
|
||||
>
|
||||
|
||||
@@ -210,12 +212,12 @@
|
||||
></div>
|
||||
|
||||
<div class="venue-manual-details">
|
||||
<small>Optional für eine bessere Zuordnung:</small>
|
||||
<small>{{ _('Optional für eine bessere Zuordnung:') }}</small>
|
||||
<div class="venue-address-grid">
|
||||
<input type="text" name="street" id="venue-street" placeholder="Straße">
|
||||
<input type="text" name="postal_code" id="venue-postal-code" placeholder="PLZ">
|
||||
<input type="text" name="city" id="venue-city" placeholder="Ort / Stadt">
|
||||
<input type="text" name="country" id="venue-country" placeholder="Land">
|
||||
<input type="text" name="street" id="venue-street" placeholder="{{ _('Straße') }}">
|
||||
<input type="text" name="postal_code" id="venue-postal-code" placeholder="{{ _('PLZ') }}">
|
||||
<input type="text" name="city" id="venue-city" placeholder="{{ _('Ort / Stadt') }}">
|
||||
<input type="text" name="country" id="venue-country" placeholder="{{ _('Land') }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -248,7 +250,7 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Datum und Uhrzeit<br>
|
||||
{{ _('Datum und Uhrzeit') }}<br>
|
||||
|
||||
<input
|
||||
type="datetime-local"
|
||||
@@ -266,7 +268,7 @@
|
||||
|
||||
<p id="festival-end-field" hidden>
|
||||
<label>
|
||||
Enddatum und Uhrzeit<br>
|
||||
{{ _('Enddatum und Uhrzeit') }}<br>
|
||||
<input type="datetime-local" name="end_datetime" id="festival-end-datetime">
|
||||
</label>
|
||||
</p>
|
||||
@@ -279,12 +281,12 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Beschreibung<br>
|
||||
{{ _('Beschreibung') }}<br>
|
||||
|
||||
<textarea
|
||||
name="description"
|
||||
rows="5"
|
||||
placeholder="Optional: Infos zur Veranstaltung, Tour, Supportbands usw."
|
||||
placeholder="{{ _('Optional: Infos zur Veranstaltung, Tour, Supportbands usw.') }}"
|
||||
></textarea>
|
||||
|
||||
</label>
|
||||
@@ -299,7 +301,7 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Ticket-Link<br>
|
||||
{{ _('Ticket-Link') }}<br>
|
||||
|
||||
<input
|
||||
type="url"
|
||||
@@ -319,14 +321,14 @@
|
||||
<p>
|
||||
|
||||
<label>
|
||||
Ticketpreis<br>
|
||||
{{ _('Ticketpreis') }}<br>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
name="ticket_price"
|
||||
step="0.01"
|
||||
min="0"
|
||||
placeholder="z. B. 59.90"
|
||||
placeholder="{{ _('z. B. 59.90') }}"
|
||||
>
|
||||
|
||||
</label>
|
||||
@@ -340,13 +342,13 @@
|
||||
|
||||
<div class="flyer-upload">
|
||||
|
||||
<label for="flyer-url">🔗 Flyer extern verlinken <small>(bevorzugt)</small></label>
|
||||
<label for="flyer-url">{{ _('🔗 Flyer extern verlinken') }} <small>{{ _('(bevorzugt)') }}</small></label>
|
||||
<input type="url" id="flyer-url" name="flyer_url" placeholder="https://veranstalter.example/flyer.jpg">
|
||||
<div class="flyer-help">Bitte möglichst den offiziellen Flyer des Veranstalters oder der Band verlinken. Das Bild wird nicht auf MetalCircle kopiert; der Link wird als Quelle ausgewiesen.</div>
|
||||
<div class="flyer-help">{{ _('Bitte möglichst den offiziellen Flyer des Veranstalters oder der Band verlinken. Das Bild wird nicht auf MetalCircle kopiert; der Link wird als Quelle ausgewiesen.') }}</div>
|
||||
|
||||
<label for="flyer">
|
||||
|
||||
🎨 Veranstaltungs-Flyer
|
||||
{{ _('🎨 Veranstaltungs-Flyer') }}
|
||||
|
||||
</label>
|
||||
|
||||
@@ -361,7 +363,7 @@
|
||||
|
||||
<div class="flyer-help">
|
||||
|
||||
Alternative, falls kein offizieller externer Link verfügbar ist: JPG, PNG oder WEBP · maximal 10 MB. Ist ein Link angegeben, wird er bevorzugt dargestellt.
|
||||
{{ _('Alternative, falls kein offizieller externer Link verfügbar ist: JPG, PNG oder WEBP · maximal 10 MB. Ist ein Link angegeben, wird er bevorzugt dargestellt.') }}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -374,7 +376,7 @@
|
||||
<img
|
||||
id="flyer-preview"
|
||||
class="flyer-preview"
|
||||
alt="Flyer Vorschau"
|
||||
alt="{{ _('Flyer Vorschau') }}"
|
||||
>
|
||||
|
||||
<div
|
||||
@@ -397,7 +399,7 @@
|
||||
type="submit"
|
||||
class="button"
|
||||
>
|
||||
💾 Veranstaltung speichern
|
||||
{{ _('💾 Veranstaltung speichern') }}
|
||||
</button>
|
||||
|
||||
|
||||
@@ -405,7 +407,7 @@
|
||||
href="/"
|
||||
class="button button-secondary"
|
||||
>
|
||||
Abbrechen
|
||||
{{ _('Abbrechen') }}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
@@ -663,8 +665,8 @@ function appendManualVenueResult(query, isOnlyResult) {
|
||||
result.type = "button";
|
||||
result.className = "venue-result venue-result-manual";
|
||||
result.innerHTML = `
|
||||
<strong>✏️ „${escapeHtml(query)}“ verwenden</strong>
|
||||
<span>${isOnlyResult ? "Keine passenden Treffer – Eingabe trotzdem speichern" : "Als eigenen Veranstaltungsort anlegen"}</span>
|
||||
<strong>${escapeHtml({{ _('✏️ „{name}“ verwenden') | tojson }}.replace('{name}', query))}</strong>
|
||||
<span>${isOnlyResult ? {{ _('Keine passenden Treffer – Eingabe trotzdem speichern') | tojson }} : {{ _('Als eigenen Veranstaltungsort anlegen') | tojson }}}</span>
|
||||
`;
|
||||
result.addEventListener("click", () => {
|
||||
selectVenue({
|
||||
@@ -839,7 +841,7 @@ flyerInput.addEventListener(
|
||||
) {
|
||||
|
||||
alert(
|
||||
"Bitte JPG, PNG oder WEBP auswählen."
|
||||
{{ _('Bitte JPG, PNG oder WEBP auswählen.') | tojson }}
|
||||
);
|
||||
|
||||
|
||||
@@ -857,7 +859,7 @@ flyerInput.addEventListener(
|
||||
) {
|
||||
|
||||
alert(
|
||||
"Der Flyer darf maximal 10 MB groß sein."
|
||||
{{ _('Der Flyer darf maximal 10 MB groß sein.') | tojson }}
|
||||
);
|
||||
|
||||
|
||||
@@ -916,7 +918,7 @@ function showDuplicateMatches(matches) {
|
||||
return;
|
||||
}
|
||||
const heading = document.createElement("strong");
|
||||
heading.textContent = "⚠️ Möglicherweise existiert diese Veranstaltung bereits:";
|
||||
heading.textContent = {{ _('⚠️ Möglicherweise existiert diese Veranstaltung bereits:') | tojson }};
|
||||
const list = document.createElement("ul");
|
||||
matches.forEach(match => {
|
||||
const item = document.createElement("li");
|
||||
@@ -990,7 +992,7 @@ concertForm.addEventListener("submit", async event => {
|
||||
const matches = await checkForDuplicates(true);
|
||||
if (matches.length && duplicateConfirmed.value !== "true") {
|
||||
const proceed = window.confirm(
|
||||
"Am selben Tag gibt es bereits eine Veranstaltung mit einem sehr ähnlichen Bandnamen. Trotzdem speichern?"
|
||||
{{ _('Am selben Tag gibt es bereits eine Veranstaltung mit einem sehr ähnlichen Bandnamen. Trotzdem speichern?') | tojson }}
|
||||
);
|
||||
if (!proceed) return;
|
||||
duplicateConfirmed.value = "true";
|
||||
|
||||
Reference in New Issue
Block a user