added translations

This commit is contained in:
Hendrik Behme committed 2026-10-09 16:37:53 +02:00
1 parent 36ed3880e8
commit 71635eb574
33 files changed
+1025 -241

No files matched your search

+56 -56
View File
@@ -1,6 +1,6 @@
{% extends 'base.html.twig' %}
{% block title %}Browse events — VINDN{% endblock %}
{% block title %}{{ 'browse.title'|trans }}{% endblock %}
{% block head_extra %}
{% include '_datepicker_head.html.twig' %}
@@ -77,19 +77,19 @@
{% block body %}
<section class="section" style="padding-bottom:0;">
<div class="wrap">
<p class="eyebrow" style="margin-bottom:10px;">All events</p>
<p class="eyebrow" style="margin-bottom:10px;">{{ 'browse.eyebrow'|trans }}</p>
<div class="toolbar">
<h1 style="font-size:clamp(28px,4vw,40px);">{{ q ? 'Searching: "' ~ q ~ '"' : "What's on" }}{{ activeCategory ? ' — ' ~ activeCategory : '' }}</h1>
<h1 style="font-size:clamp(28px,4vw,40px);">{{ q ? 'browse.heading_search'|trans({'%q%': q}) : 'browse.heading'|trans }}{{ activeCategory ? ' — ' ~ activeCategory : '' }}</h1>
<div style="display:flex; align-items:center; gap:12px;">
<p class="mono" style="font-size:13px; color:var(--muted); margin:0;">{{ total|de_number }} event{{ total == 1 ? '' : 's' }}</p>
<label for="sortSelect" style="display:none;">Sort by</label>
<p class="mono" style="font-size:13px; color:var(--muted); margin:0;">{{ 'browse.count'|trans({'%count%': total, '%n%': total|format_number}) }}</p>
<label for="sortSelect" style="display:none;">{{ 'browse.sort.label'|trans }}</label>
<select id="sortSelect" style="padding:7px 8px; border:1px solid var(--line-strong); border-radius:6px; font-family:var(--f-mono); font-size:12px; background:#fff;">
<option value="soonest" data-href="{{ path('event_list', filters|merge({sort: 'soonest'})) }}" {{ 'soonest' == sort ? 'selected' : '' }}>Sort: Soonest</option>
<option value="soonest" data-href="{{ path('event_list', filters|merge({sort: 'soonest'})) }}" {{ 'soonest' == sort ? 'selected' : '' }}>{{ 'browse.sort.soonest'|trans }}</option>
{% if hasLocation %}
<option value="nearest" data-href="{{ path('event_list', filters|merge({sort: 'nearest'})) }}" {{ 'nearest' == sort ? 'selected' : '' }}>Sort: Nearest</option>
<option value="nearest" data-href="{{ path('event_list', filters|merge({sort: 'nearest'})) }}" {{ 'nearest' == sort ? 'selected' : '' }}>{{ 'browse.sort.nearest'|trans }}</option>
{% endif %}
<option value="az" data-href="{{ path('event_list', filters|merge({sort: 'az'})) }}" {{ 'az' == sort ? 'selected' : '' }}>Sort: A–Z</option>
<option value="za" data-href="{{ path('event_list', filters|merge({sort: 'za'})) }}" {{ 'za' == sort ? 'selected' : '' }}>Sort: Z–A</option>
<option value="az" data-href="{{ path('event_list', filters|merge({sort: 'az'})) }}" {{ 'az' == sort ? 'selected' : '' }}>{{ 'browse.sort.az'|trans }}</option>
<option value="za" data-href="{{ path('event_list', filters|merge({sort: 'za'})) }}" {{ 'za' == sort ? 'selected' : '' }}>{{ 'browse.sort.za'|trans }}</option>
</select>
</div>
</div>
@@ -103,31 +103,31 @@
<input type="hidden" name="lon" value="{{ lon }}">
<input type="hidden" name="range" value="{{ rangeKm }}">
{% endif %}
<label for="q" style="display:none;">Search events</label>
<input id="q" name="q" type="text" value="{{ q }}" placeholder="Search by event, artist or venue…">
<button class="btn btn-primary" type="submit">Search</button>
<label for="q" style="display:none;">{{ 'search.label'|trans }}</label>
<input id="q" name="q" type="text" value="{{ q }}" placeholder="{{ 'browse.search.placeholder'|trans }}">
<button class="btn btn-primary" type="submit">{{ 'browse.search.submit'|trans }}</button>
</form>
{% set activeCount = (activeCategory ? 1 : 0) + (dateFrom or dateTo ? 1 : 0) + (hasLocation ? 1 : 0) %}
<div class="filter-bar">
<button type="button" class="btn btn-ghost btn-sm" id="openFilters" aria-haspopup="dialog">Filters{% if activeCount %}<span class="count">{{ activeCount }}</span>{% endif %}</button>
<button type="button" class="btn btn-ghost btn-sm" id="openFilters" aria-haspopup="dialog">{{ 'browse.filters.button'|trans }}{% if activeCount %}<span class="count">{{ activeCount }}</span>{% endif %}</button>
{% if activeCategory %}
<a class="filter-pill" href="{{ path('event_list', filters|merge({category: null})) }}" aria-label="Remove category filter {{ activeCategory }}">{{ activeCategory }}<span class="x" aria-hidden="true">×</span></a>
<a class="filter-pill" href="{{ path('event_list', filters|merge({category: null})) }}" aria-label="{{ 'browse.filters.remove_category'|trans({'%name%': activeCategory}) }}">{{ activeCategory }}<span class="x" aria-hidden="true">×</span></a>
{% endif %}
{% if dateFrom or dateTo %}
{% if dateFrom == today and dateTo == today %}{% set dateLabel = 'Today' %}
{% elseif dateFrom == today and dateTo == weekEnd %}{% set dateLabel = 'Next 7 days' %}
{% elseif dateFrom and dateTo and dateFrom == dateTo %}{% set dateLabel = dateFrom|date('d.m.Y') %}
{% elseif dateFrom and dateTo %}{% set dateLabel = dateFrom|date('d.m.Y') ~ ' – ' ~ dateTo|date('d.m.Y') %}
{% elseif dateFrom %}{% set dateLabel = 'From ' ~ dateFrom|date('d.m.Y') %}
{% else %}{% set dateLabel = 'Until ' ~ dateTo|date('d.m.Y') %}{% endif %}
<a class="filter-pill" data-forget="dates" href="{{ path('event_list', filters|merge({from: null, to: null})) }}" aria-label="Remove date filter {{ dateLabel }}">{{ dateLabel }}<span class="x" aria-hidden="true">×</span></a>
{% if dateFrom == today and dateTo == today %}{% set dateLabel = 'dates.today'|trans %}
{% elseif dateFrom == today and dateTo == weekEnd %}{% set dateLabel = 'dates.next7'|trans %}
{% elseif dateFrom and dateTo and dateFrom == dateTo %}{% set dateLabel = dateFrom|format_date %}
{% elseif dateFrom and dateTo %}{% set dateLabel = dateFrom|format_date ~ ' – ' ~ dateTo|format_date %}
{% elseif dateFrom %}{% set dateLabel = 'browse.filters.from'|trans({'%date%': dateFrom|format_date}) %}
{% else %}{% set dateLabel = 'browse.filters.until'|trans({'%date%': dateTo|format_date}) %}{% endif %}
<a class="filter-pill" data-forget="dates" href="{{ path('event_list', filters|merge({from: null, to: null})) }}" aria-label="{{ 'browse.filters.remove_dates'|trans({'%label%': dateLabel}) }}">{{ dateLabel }}<span class="x" aria-hidden="true">×</span></a>
{% endif %}
{% if hasLocation %}
<a class="filter-pill" id="locationPill" data-forget="location" data-lat="{{ lat }}" data-lon="{{ lon }}" href="{{ path('event_list', filters|merge({lat: null, lon: null, range: null})) }}" aria-label="Remove location filter"><span class="label">Within {{ rangeKm }} km of {{ lat|de_number(3) }}, {{ lon|de_number(3) }}</span><span class="x" aria-hidden="true">×</span></a>
<a class="filter-pill" id="locationPill" data-forget="location" data-lat="{{ lat }}" data-lon="{{ lon }}" href="{{ path('event_list', filters|merge({lat: null, lon: null, range: null})) }}" aria-label="{{ 'browse.filters.remove_location'|trans }}"><span class="label">{{ 'browse.filters.within_coords'|trans({'%km%': rangeKm, '%lat%': lat|format_number(3), '%lon%': lon|format_number(3)}) }}</span><span class="x" aria-hidden="true">×</span></a>
{% endif %}
{% if activeCount %}
<a class="filter-clear-all" data-forget="all" href="{{ path('event_list', {q: q ?: null, sort: filters.sort}) }}">Clear all</a>
<a class="filter-clear-all" data-forget="all" href="{{ path('event_list', {q: q ?: null, sort: filters.sort}) }}">{{ 'browse.filters.clear_all'|trans }}</a>
{% endif %}
</div>
</div>
@@ -138,15 +138,15 @@
{% if q %}<input type="hidden" name="q" value="{{ q }}">{% endif %}
{% if filters.sort %}<input type="hidden" name="sort" value="{{ filters.sort }}">{% endif %}
<div class="fd-head">
<h2 id="filterDialogTitle">Filters</h2>
<button type="button" class="fd-close" id="closeFilters" aria-label="Close">×</button>
<h2 id="filterDialogTitle">{{ 'browse.filters.dialog'|trans }}</h2>
<button type="button" class="fd-close" id="closeFilters" aria-label="{{ 'browse.filters.close'|trans }}">×</button>
</div>
<div class="fd-body">
<div class="fd-section">
<span class="filter-label">Category</span>
<span class="filter-label">{{ 'browse.filters.category'|trans }}</span>
<div class="pick-row">
<label class="pick"><input type="radio" name="category" value="" {{ activeCategory is null ? 'checked' : '' }}><span>All</span></label>
<label class="pick"><input type="radio" name="category" value="" {{ activeCategory is null ? 'checked' : '' }}><span>{{ 'map.all'|trans }}</span></label>
{% for category in categories %}
<label class="pick"><input type="radio" name="category" value="{{ category }}" {{ activeCategory == category ? 'checked' : '' }}><span>{{ category }}</span></label>
{% endfor %}
@@ -154,46 +154,46 @@
</div>
<div class="fd-section">
<span class="filter-label">When</span>
<span class="filter-label">{{ 'browse.filters.when'|trans }}</span>
<div class="when-filter">
<div class="chip-row-tight" id="datePresets">
<button type="button" class="chip-sm" data-from="" data-to="">Any date</button>
<button type="button" class="chip-sm" data-from="{{ today }}" data-to="{{ today }}">Today</button>
<button type="button" class="chip-sm" data-from="{{ today }}" data-to="{{ weekEnd }}">Next 7 days</button>
<button type="button" class="chip-sm" data-from="" data-to="">{{ 'dates.any'|trans }}</button>
<button type="button" class="chip-sm" data-from="{{ today }}" data-to="{{ today }}">{{ 'dates.today'|trans }}</button>
<button type="button" class="chip-sm" data-from="{{ today }}" data-to="{{ weekEnd }}">{{ 'dates.next7'|trans }}</button>
</div>
<div class="date-range" role="group" aria-label="Date range">
<label for="dateFrom">From</label>
<input type="date" id="dateFrom" name="from" aria-label="From date" min="{{ today }}" value="{{ dateFrom }}">
<label for="dateTo">to</label>
<input type="date" id="dateTo" name="to" aria-label="To date" min="{{ today }}" value="{{ dateTo }}">
<div class="date-range" role="group" aria-label="{{ 'dates.group'|trans }}">
<label for="dateFrom">{{ 'dates.from'|trans }}</label>
<input type="date" id="dateFrom" name="from" aria-label="{{ 'dates.from_aria'|trans }}" min="{{ today }}" value="{{ dateFrom }}">
<label for="dateTo">{{ 'dates.to'|trans }}</label>
<input type="date" id="dateTo" name="to" aria-label="{{ 'dates.to_aria'|trans }}" min="{{ today }}" value="{{ dateTo }}">
</div>
</div>
</div>
<div class="fd-section">
<span class="filter-label">Near a location</span>
<span class="filter-label">{{ 'browse.filters.near'|trans }}</span>
<input type="hidden" name="lat" id="fLat" value="{{ hasLocation ? lat : '' }}">
<input type="hidden" name="lon" id="fLon" value="{{ hasLocation ? lon : '' }}">
<div class="location-filter">
<label for="locationSearchInput" style="display:none;">Search a place</label>
<input id="locationSearchInput" type="text" placeholder="Search a place…" autocomplete="off">
<button class="btn btn-ghost btn-sm" type="button" id="locationGo">Go</button>
<button type="button" class="btn btn-ghost btn-sm" id="useMyLocationBtn">Use my location</button>
<label for="rangeSelect" style="display:none;">Radius</label>
<label for="locationSearchInput" style="display:none;">{{ 'location.search_label'|trans }}</label>
<input id="locationSearchInput" type="text" placeholder="{{ 'location.search_placeholder'|trans }}" autocomplete="off">
<button class="btn btn-ghost btn-sm" type="button" id="locationGo">{{ 'location.go'|trans }}</button>
<button type="button" class="btn btn-ghost btn-sm" id="useMyLocationBtn">{{ 'location.use_mine'|trans }}</button>
<label for="rangeSelect" style="display:none;">{{ 'location.radius'|trans }}</label>
<select id="rangeSelect" name="range">
{% for km in rangeOptions %}
<option value="{{ km }}" {{ km == rangeKm ? 'selected' : '' }}>{{ km }} km</option>
{% endfor %}
</select>
<button type="button" class="fd-link" id="clearLocation" {{ hasLocation ? '' : 'hidden' }}>clear</button>
<button type="button" class="fd-link" id="clearLocation" {{ hasLocation ? '' : 'hidden' }}>{{ 'location.clear'|trans }}</button>
</div>
<p class="locate-status" id="locationStatus">{% if hasLocation %}Within {{ rangeKm }} km of {{ lat|de_number(3) }}, {{ lon|de_number(3) }}{% endif %}</p>
<p class="locate-status" id="locationStatus">{% if hasLocation %}{{ 'browse.filters.within_coords'|trans({'%km%': rangeKm, '%lat%': lat|format_number(3), '%lon%': lon|format_number(3)}) }}{% endif %}</p>
</div>
</div>
<div class="fd-foot">
<button type="button" class="fd-link" id="resetFilters">Reset all</button>
<button type="submit" class="btn btn-primary btn-sm">Show events</button>
<button type="button" class="fd-link" id="resetFilters">{{ 'browse.filters.reset'|trans }}</button>
<button type="submit" class="btn btn-primary btn-sm">{{ 'browse.filters.apply'|trans }}</button>
</div>
</form>
</dialog>
@@ -205,8 +205,8 @@
{% include 'events/_card.html.twig' with { event: event, dist: hasLocation ? distances[event.id]|default(null) : null } %}
{% else %}
<div class="no-results" style="grid-column:1/-1; padding:60px 20px; text-align:center; color:var(--muted); border:1px dashed var(--line-strong); border-radius:var(--r-card);">
<p class="eyebrow" style="margin-bottom:8px;">No events</p>
<p>Nothing matches those filters yet — try clearing one, or check back once the importer has run.</p>
<p class="eyebrow" style="margin-bottom:8px;">{{ 'browse.empty.title'|trans }}</p>
<p>{{ 'browse.empty.text'|trans }}</p>
</div>
{% endfor %}
</div>
@@ -302,7 +302,7 @@
placeInput.value = saved.name;
// the summary pill can only show coordinates server-side
var pillLabel = document.querySelector('#locationPill .label');
if (pillLabel) pillLabel.textContent = 'Within ' + rangeSelect.value + ' km of ' + saved.name.split(',').slice(0, 2).join(',');
if (pillLabel) pillLabel.textContent = window.VindnI18n.t('browse.within', { km: rangeSelect.value, place: saved.name.split(',').slice(0, 2).join(',') });
}
function setStatus(text, isError) {
@@ -315,7 +315,7 @@
placeInput.value = name || '';
resolvedName = name || '';
clearLocationBtn.hidden = false;
setStatus('Found: ' + (name || lat.toFixed(3) + ', ' + lon.toFixed(3)), false);
setStatus(window.VindnI18n.t('location.found', { name: name || lat.toFixed(3) + ', ' + lon.toFixed(3) }), false);
}
function clearLocation() {
latEl.value = '';
@@ -327,10 +327,10 @@
}
function lookup(query, done) {
window.VindnLocation.searchPlace(query, {
onStart: function () { setStatus('Looking up "' + query + '"…', false); },
onNotFound: function () { setStatus('Could not find "' + query + '"', true); },
onStart: function () { setStatus(window.VindnI18n.t('location.looking_up', { query: query }), false); },
onNotFound: function () { setStatus(window.VindnI18n.t('location.not_found', { query: query }), true); },
onFound: function (lat, lon, name) { setLocation(lat, lon, name); if (done) done(); },
onError: function () { setStatus('Location search failed', true); }
onError: function () { setStatus(window.VindnI18n.t('location.search_failed'), true); }
});
}
@@ -348,10 +348,10 @@
});
document.getElementById('useMyLocationBtn').addEventListener('click', function () {
window.VindnLocation.useMyLocation({
onUnsupported: function () { setStatus('Location not supported by your browser', true); },
onStart: function () { setStatus('Finding you…', false); },
onUnsupported: function () { setStatus(window.VindnI18n.t('location.unsupported'), true); },
onStart: function () { setStatus(window.VindnI18n.t('location.finding'), false); },
onFound: setLocation,
onError: function () { setStatus("Couldn't get your location", true); }
onError: function () { setStatus(window.VindnI18n.t('location.failed'), true); }
});
});
clearLocationBtn.addEventListener('click', clearLocation);