Translate pages, areas, menus, forms and posts field by field instead of rebuilding them
A site can have further languages (Settings → Languages). A page is built once; each language adds only its texts, keyed by the stable block IDs, plus its own address under /en/… (ADR 111, document schema v27, migration 20261009000100 for page_translation). - Editor: DE | EN switch, a list of all texts with original, state (missing, translated, original changed) and filter; structure stays with the main language. Undo, drafts, publishing and revisions cover all languages at once. - Website: /en and translated slugs, lang, canonical, hreflang with x-default, og:locale, sitemap; links to translated pages follow the language; a language switch block. - Header, footer and linked sections translate the same way; menu entries carry labels per language; fixed template texts come from a small dictionary. - Forms: labels, help, options, button and success message per language; messages and the step script speak the language, the submission and the email stay in the main language. - Posts, archives, category names, consent text and the 404 page follow the language. - Single-language sites see none of it and never touch the new table. The forms administration becomes an overview with one row per form and its own edit page; fields are one line each with details collapsed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c7705b7083
commit
e15b2517b1
51 files changed
+3045
-219
No files matched your search
@@ -58,7 +58,7 @@
|
||||
{%- if klick == 'vergroessern' -%}
|
||||
{# Ohne Skript öffnet dieser Verweis schlicht das große Bild. Mit Skript fängt der
|
||||
Betrachter den Klick ab - die Seite bleibt also in jedem Fall benutzbar. #}
|
||||
<a class="image-link image-zoom" href="{{ grossesBild }}" data-zoom data-caption="{{ props.caption|default('') }}" aria-label="{{ props.alt|default('Bild') }} vergrößern">
|
||||
<a class="image-link image-zoom" href="{{ grossesBild }}" data-zoom data-caption="{{ props.caption|default('') }}" aria-label="{{ props.alt|default('Bild'|kant_t) }} {{ 'vergrößern'|kant_t }}">
|
||||
{%- elseif imageLinked -%}
|
||||
<a class="image-link" href="{{ props.href }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab|default(false)} only %}>
|
||||
{%- endif -%}<img src="{{ props.src }}" alt="{{ props.alt }}" width="{{ props.width }}" height="{{ props.height }}" loading="{{ eager or inHeader|default(false) ? 'eager' : 'lazy' }}"{{ eager ? ' fetchpriority="high"' : '' }} decoding="async"{% if props.sources|default([]) %} srcset="{% for source in props.sources %}{{ source.src }} {{ source.width }}w{% if not loop.last %}, {% endif %}{% endfor %}" sizes="{{ imageSizes }}"{% endif %}>{% if klick == 'vergroessern' or imageLinked %}</a>{% endif %}{% if props.caption|default('') %}<figcaption>{{ props.caption }}</figcaption>{% endif %}</figure>
|
||||
@@ -112,7 +112,7 @@
|
||||
{% set menuLayout = props.layout|default('horizontal') %}
|
||||
{% set collapse = menuLayout == 'horizontal' ? props.collapse|default('tablet') : 'never' %}
|
||||
<nav id="{{ block.id }}" class="block-menu menu-{{ menuLayout }} menu-collapse-{{ collapse }}{{ designClasses }}"
|
||||
aria-label="{{ {header: 'Hauptnavigation', secondary: 'Zweite Navigation', footer: 'Fußzeile', legal: 'Rechtliches'}[props.slot]|default('Navigation') }}">
|
||||
aria-label="{{ {header: 'Hauptnavigation', secondary: 'Zweite Navigation', footer: 'Fußzeile', legal: 'Rechtliches'}[props.slot]|default('Navigation')|kant_t }}">
|
||||
{% if menuLayout == 'burger' or collapse != 'never' %}
|
||||
{# Buttons, die auf dem Telefon neben dem Menü-Knopf stehen bleiben (ADR 110). Sichtbar nur,
|
||||
solange das Menü zusammengeklappt ist; im aufgeklappten Menü fehlen sie dann. #}
|
||||
@@ -121,7 +121,7 @@
|
||||
<details class="menu-burger">
|
||||
{# Nur Symbol behält den Namen für Vorlesesoftware; sichtbar ist dann nur der Strich. #}
|
||||
{% set burger = props.burgerLabel|default('both') %}
|
||||
<summary>{% if burger != 'text' %}<span class="burger-lines" aria-hidden="true"></span>{% endif %}<span class="burger-text{{ burger == 'icon' ? ' visually-hidden' : '' }}">Menü</span></summary>
|
||||
<summary>{% if burger != 'text' %}<span class="burger-lines" aria-hidden="true"></span>{% endif %}<span class="burger-text{{ burger == 'icon' ? ' visually-hidden' : '' }}">{{ 'Menü'|kant_t }}</span></summary>
|
||||
<div class="menu-burger-body">{% include 'nav.html.twig' with {links: links} only %}</div>
|
||||
</details>
|
||||
{% endif %}
|
||||
@@ -147,9 +147,9 @@
|
||||
{% endif %}
|
||||
{% elseif type == 'breadcrumb' %}
|
||||
{% if breadcrumbs|default([])|length > 1 %}
|
||||
<nav id="{{ block.id }}" class="block-breadcrumb{{ designClasses }}" aria-label="Brotkrumen">
|
||||
<nav id="{{ block.id }}" class="block-breadcrumb{{ designClasses }}" aria-label="{{ 'Brotkrumen'|kant_t }}">
|
||||
<ol>
|
||||
<li><a href="/">Start</a></li>
|
||||
<li><a href="/">{{ 'Start'|kant_t }}</a></li>
|
||||
{% for crumb in breadcrumbs %}
|
||||
<li>{% if loop.last %}<span aria-current="page">{{ crumb.name }}</span>{% else %}<a href="{{ crumb.href }}">{{ crumb.name }}</a>{% endif %}</li>
|
||||
{% endfor %}
|
||||
@@ -208,7 +208,7 @@
|
||||
{% if profiles[key]|default('') and (chosen is empty or key in chosen) %}{% set shown = shown|merge([{key: key, name: name, url: profiles[key]}]) %}{% endif %}
|
||||
{% endfor %}
|
||||
{% if shown is not empty %}
|
||||
<ul id="{{ block.id }}" class="block-social social-{{ props.layout|default('horizontal') }} social-shape-{{ props.shape|default('plain') }} social-size-{{ props.size|default(24) }}{{ props.labels|default(false) ? ' social-labels' : '' }}{{ designClasses }}" aria-label="Soziale Profile">
|
||||
<ul id="{{ block.id }}" class="block-social social-{{ props.layout|default('horizontal') }} social-shape-{{ props.shape|default('plain') }} social-size-{{ props.size|default(24) }}{{ props.labels|default(false) ? ' social-labels' : '' }}{{ designClasses }}" aria-label="{{ 'Soziale Profile'|kant_t }}">
|
||||
{% for profile in shown %}
|
||||
<li><a href="{{ profile.url }}"{% include 'blocks/link-attributes.html.twig' with {newTab: props.newTab is defined ? props.newTab : true} only %}>{% include 'blocks/social-icon.html.twig' with {network: profile.key} only %}<span{{ props.labels|default(false) ? '' : ' class="visually-hidden"' }}>{{ profile.name }}</span></a></li>
|
||||
{% endfor %}
|
||||
@@ -233,7 +233,7 @@
|
||||
browser that ignores snapping. No autoplay - a moving carousel is something people ask to
|
||||
stop, not something a page should start on its own. #}
|
||||
<div id="{{ block.id }}" class="block-slider{{ designClasses }} slide-desktop-{{ props.desktopVisible }} slide-tablet-{{ props.tabletVisible }} slide-mobile-{{ props.mobileVisible }}{{ props.snap ? ' slider-snap' : '' }}{{ props.dots ?? false ? ' slider-dots' : '' }}"{% if props.autoplay|default(0) >= 3 %} data-autoplay="{{ props.autoplay }}"{% endif %}
|
||||
tabindex="0" role="group" aria-label="Zum Blättern seitwärts scrollen">
|
||||
tabindex="0" role="group" aria-label="{{ 'Zum Blättern seitwärts scrollen'|kant_t }}">
|
||||
{% for child in block.children|default([]) %}
|
||||
<div class="slider-slide visibility-{{ child.layout.visibility|default('all') }}">{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}</div>
|
||||
{% endfor %}
|
||||
@@ -255,7 +255,7 @@
|
||||
<div id="{{ block.id }}" class="block-embed embed-blocked{{ designClasses }}">
|
||||
<p class="embed-title">{{ props.title }}</p>
|
||||
<p class="embed-hint">{{ props.hint ?: 'Dieser Inhalt wird von einem anderen Anbieter geladen. Dabei erfährt er deine IP-Adresse und welche Seite du ansiehst.' }}</p>
|
||||
<p class="embed-provider">Anbieter: <strong>{{ props.provider ?: props.url|split('/')[2] }}</strong></p>
|
||||
<p class="embed-provider">{{ 'Anbieter:'|kant_t }} <strong>{{ props.provider ?: props.url|split('/')[2] }}</strong></p>
|
||||
{% if editing|default(false) %}
|
||||
<p class="embed-hint">Wird erst auf der veröffentlichten Seite geladen, und dort erst nach Einwilligung.</p>
|
||||
{% else %}
|
||||
@@ -265,7 +265,7 @@
|
||||
{# The groups already granted stay granted; this button adds this one. #}
|
||||
{% for granted in consentGate.state(app.request).groups %}{% if granted.granted %}<input type="hidden" name="groups[]" value="{{ granted.key }}">{% endif %}{% endfor %}
|
||||
<input type="hidden" name="groups[]" value="{{ props.group }}">
|
||||
<button class="consent-button" type="submit">Inhalt laden und einwilligen</button>
|
||||
<button class="consent-button" type="submit">{{ 'Inhalt laden und einwilligen'|kant_t }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -294,12 +294,24 @@
|
||||
erbt statt als heller Streifen unter einem dunklen Fuß zu kleben. #}
|
||||
{% set rechtliches = consentGate.legalLinks() %}
|
||||
{% set widerruflich = not editing|default(false) and consentGate.askedGroups is not empty and not consentGate.needsBanner(app.request) %}
|
||||
<nav id="{{ block.id }}" class="block-legal{{ designClasses }}" aria-label="{{ props.label|default('Rechtliches') }}">
|
||||
{% for path, label in rechtliches %}{% if not loop.first %}<span aria-hidden="true"> · </span>{% endif %}<a href="/{{ path }}">{{ label }}</a>{% endfor %}
|
||||
{% if widerruflich %}{% if rechtliches is not empty %}<span aria-hidden="true"> · </span>{% endif %}<a href="/einwilligung">Einwilligung ändern</a>{% endif %}
|
||||
<nav id="{{ block.id }}" class="block-legal{{ designClasses }}" aria-label="{{ props.label|default('Rechtliches'|kant_t) }}">
|
||||
{% for path, label in rechtliches %}{% if not loop.first %}<span aria-hidden="true"> · </span>{% endif %}<a href="/{{ path }}">{{ label|kant_t }}</a>{% endfor %}
|
||||
{% if widerruflich %}{% if rechtliches is not empty %}<span aria-hidden="true"> · </span>{% endif %}<a href="/einwilligung">{{ 'Einwilligung ändern'|kant_t }}</a>{% endif %}
|
||||
</nav>
|
||||
{% elseif type == 'language' %}
|
||||
{# Die Sprachwahl (ADR 111): dieselbe Seite in jeder Sprache, in der sie veröffentlicht ist.
|
||||
Gibt es die Seite nur in einer Sprache, steht hier nichts - eine Wahl mit einer Möglichkeit
|
||||
ist keine. hreflang hält die Links aus der Umstellung auf übersetzte Adressen heraus. #}
|
||||
{% set sprachen = app.request.attributes.get('kant_alternates')|default([]) %}
|
||||
{% if editing|default(false) and sprachen|length < 2 %}{% set sprachen = [{locale: 'de', native: 'Deutsch', href: '#', current: true}, {locale: 'en', native: 'English', href: '#', current: false}] %}{% endif %}
|
||||
{% if sprachen|length > 1 %}
|
||||
<nav id="{{ block.id }}" class="block-language{{ designClasses }}" aria-label="{{ 'Sprache'|kant_t }}">
|
||||
<ul>{% for sprache in sprachen %}<li><a href="{{ sprache.href }}" hreflang="{{ sprache.locale }}" lang="{{ sprache.locale }}"{% if sprache.current %} aria-current="page"{% endif %}>{% if props.display|default('code') == 'name' %}{{ sprache.native }}{% else %}{{ sprache.locale|upper }}<span class="visually-hidden"> {{ sprache.native }}</span>{% endif %}</a></li>{% endfor %}</ul>
|
||||
</nav>
|
||||
{% elseif editing|default(false) %}<p id="{{ block.id }}" class="block-unavailable{{ designClasses }}" role="note">Sprachwahl</p>
|
||||
{% endif %}
|
||||
{% elseif type == 'gallery' %}
|
||||
<div id="{{ block.id }}" class="block-gallery{{ designClasses }}" role="group" aria-label="Bildergalerie"{% if props.lightbox|default(false) %} data-zoom-group{% endif %}>
|
||||
<div id="{{ block.id }}" class="block-gallery{{ designClasses }}" role="group" aria-label="{{ 'Bildergalerie'|kant_t }}"{% if props.lightbox|default(false) %} data-zoom-group{% endif %}>
|
||||
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, galleryZoom: props.lightbox|default(false), editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null), inHeader: inHeader|default(false)} only %}{% endfor %}
|
||||
</div>
|
||||
{% elseif kant_plugin_block(type) %}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
Schritt nach dem anderen. Bedingungen stehen als Datenattribute am Feld; der Server prüft sie
|
||||
beim Absenden selbst und verlangt nichts, was gar nicht galt. #}
|
||||
{% if form is null %}
|
||||
<p class="form-missing">Dieses Formular ist nicht mehr vorhanden.</p>
|
||||
<p class="form-missing">{{ 'Dieses Formular ist nicht mehr vorhanden.'|kant_t }}</p>
|
||||
{% elseif sent %}
|
||||
<p class="form-success" role="status">{{ form.settings.success }}</p>
|
||||
<p class="form-success" role="status">{{ form.settings.success|kant_t }}</p>
|
||||
{% else %}
|
||||
{% set hasSteps = form.fields|filter(f => f.type == 'step')|length > 0 %}
|
||||
<form class="block-form{{ hasSteps ? ' form-stepped' : '' }}" data-form="{{ form.slug }}" method="post" action="{{ path('form_submit', {slug: form.slug}) }}" novalidate{% if hasSteps %} data-steps{% endif %}{% if form.settings.summary|default(false) %} data-summary{% endif %}>
|
||||
<form class="block-form{{ hasSteps ? ' form-stepped' : '' }}" data-form="{{ form.slug }}" method="post" action="{{ path('form_submit', {slug: form.slug}) }}" novalidate{% if hasSteps %} data-steps{% endif %}{% if form.settings.summary|default(false) %} data-summary{% endif %}{% if form.locale|default(null) %} data-texts="{{ {summary: 'Zusammenfassung'|kant_t, summaryIntro: 'Bitte prüfen Sie Ihre Angaben. Über „Ändern“ kommen Sie zum jeweiligen Schritt zurück.'|kant_t, steps: 'Schritte des Formulars'|kant_t, step: 'Schritt'|kant_t, back: 'Zurück'|kant_t, next: 'Weiter'|kant_t, change: 'Ändern'|kant_t, yes: 'Ja'|kant_t, chooseOne: 'Bitte wähle mindestens eine Option.'|kant_t}|json_encode|e('html_attr') }}"{% endif %}>
|
||||
<input type="hidden" name="page" value="{{ page.path }}">
|
||||
{% if form.locale|default(null) %}<input type="hidden" name="language" value="{{ form.locale }}">{% endif %}
|
||||
<input type="hidden" name="block" value="{{ blockId }}">
|
||||
<input type="hidden" name="issued" value="{{ issued }}">
|
||||
<input type="hidden" name="issuedSignature" value="{{ issuedSignature }}">
|
||||
@@ -17,7 +18,7 @@
|
||||
<div class="form-trap" aria-hidden="true"><label for="website-{{ blockId }}">Website</label><input id="website-{{ blockId }}" name="website" type="text" tabindex="-1" autocomplete="off"></div>
|
||||
|
||||
{% if errors is not empty %}
|
||||
<p class="form-error-summary" role="alert">Bitte prüfe {{ errors|length }} {{ errors|length == 1 ? 'Eingabe' : 'Eingaben' }}. Die betroffenen Felder sind unten gekennzeichnet.</p>
|
||||
<p class="form-error-summary" role="alert">{{ (errors|length == 1 ? 'Bitte prüfe 1 Eingabe. Die betroffenen Felder sind unten gekennzeichnet.' : 'Bitte prüfe %d Eingaben. Die betroffenen Felder sind unten gekennzeichnet.')|kant_t|format(errors|length) }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% for field in form.fields %}
|
||||
@@ -26,7 +27,7 @@
|
||||
{% set value = values[field.name]|default('') %}
|
||||
{% set described = (field.help ? id ~ '-help ' : '') ~ (error ? id ~ '-error' : '') %}
|
||||
{% set condition %}{% if field.showIf|default(null) %} data-show-if="{{ field.showIf.field }}" data-show-values="{{ field.showIf.values|join('|') }}"{% endif %}{% endset %}
|
||||
{% set requiredMark %}{% if field.required %} <span class="required-mark" aria-hidden="true">*</span><span class="visually-hidden"> (Pflichtfeld)</span>{% endif %}{% endset %}
|
||||
{% set requiredMark %}{% if field.required %} <span class="required-mark" aria-hidden="true">*</span><span class="visually-hidden"> ({{ 'Pflichtfeld'|kant_t }})</span>{% endif %}{% endset %}
|
||||
{% if field.type == 'step' %}
|
||||
{% if not loop.first and form.fields[0:loop.index0]|filter(f => f.type == 'step')|length > 0 %}</fieldset>{% endif %}
|
||||
<fieldset class="form-step" data-step{{ condition }}>
|
||||
@@ -58,7 +59,7 @@
|
||||
{% set bild = optionImages[field.images[option]|default('')]|default(null) %}
|
||||
<div class="{{ bildauswahl ? 'form-choice-card' : 'check-row' }}">
|
||||
<input id="{{ id }}-{{ loop.index }}" name="{{ field.name }}{{ field.type == 'checkboxes' ? '[]' : '' }}" type="{{ field.type == 'checkboxes' ? 'checkbox' : 'radio' }}" value="{{ option }}"{{ option in picked ? ' checked' : '' }}{{ field.type == 'radio' and field.required ? ' required' : '' }}{{ error ? ' aria-invalid="true"' : '' }}>
|
||||
<label for="{{ id }}-{{ loop.index }}">{% if bildauswahl %}<span class="form-choice-picture">{% if bild %}<img src="{{ bild.src }}" alt="" width="{{ bild.width }}" height="{{ bild.height }}" loading="lazy" decoding="async">{% endif %}</span><span class="form-choice-text">{{ option }}</span>{% else %}{{ option }}{% endif %}</label>
|
||||
<label for="{{ id }}-{{ loop.index }}">{% if bildauswahl %}<span class="form-choice-picture">{% if bild %}<img src="{{ bild.src }}" alt="" width="{{ bild.width }}" height="{{ bild.height }}" loading="lazy" decoding="async">{% endif %}</span><span class="form-choice-text">{{ field.optionLabels[option]|default(option) }}</span>{% else %}{{ field.optionLabels[option]|default(option) }}{% endif %}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if bildauswahl %}</div>{% endif %}
|
||||
@@ -67,15 +68,15 @@
|
||||
</fieldset>
|
||||
{% else %}
|
||||
<div class="form-field{{ error ? ' has-error' : '' }}" data-field data-label="{{ field.label }}"{{ condition }}>
|
||||
<label for="{{ id }}">{{ field.label }}{{ requiredMark }}{% if field.unit|default('') %}<span class="visually-hidden"> in {{ field.unit }}</span>{% endif %}</label>
|
||||
<label for="{{ id }}">{{ field.label }}{{ requiredMark }}{% if field.unit|default('') %}<span class="visually-hidden"> {{ 'in'|kant_t }} {{ field.unit }}</span>{% endif %}</label>
|
||||
{% if field.type == 'textarea' %}
|
||||
<textarea id="{{ id }}" name="{{ field.name }}" rows="6" maxlength="5000"{{ field.required ? ' required' : '' }}
|
||||
{% if described|trim %}aria-describedby="{{ described|trim }}"{% endif %}{{ error ? ' aria-invalid="true"' : '' }}>{{ value }}</textarea>
|
||||
{% elseif field.type == 'select' %}
|
||||
<select id="{{ id }}" name="{{ field.name }}"{{ field.required ? ' required' : '' }}
|
||||
{% if described|trim %}aria-describedby="{{ described|trim }}"{% endif %}{{ error ? ' aria-invalid="true"' : '' }}>
|
||||
<option value="">— bitte wählen —</option>
|
||||
{% for option in field.options %}<option value="{{ option }}"{{ value == option ? ' selected' : '' }}>{{ option }}</option>{% endfor %}
|
||||
<option value="">— {{ 'bitte wählen'|kant_t }} —</option>
|
||||
{% for option in field.options %}<option value="{{ option }}"{{ value == option ? ' selected' : '' }}>{{ field.optionLabels[option]|default(option) }}</option>{% endfor %}
|
||||
</select>
|
||||
{% elseif field.type == 'number' %}
|
||||
<div class="form-number{{ field.unit|default('') ? ' has-unit' : '' }}">
|
||||
@@ -101,10 +102,10 @@
|
||||
{% if turnstileKey|default('') %}
|
||||
{# Cloudflare Turnstile. Braucht JavaScript; ohne Skript bleibt das Formular unabsendbar, und
|
||||
genau deshalb ist die Prüfung eine Entscheidung und keine Voreinstellung. #}
|
||||
<div class="cf-turnstile" data-sitekey="{{ turnstileKey }}" data-language="de"></div>
|
||||
<noscript><p class="field-error">Zum Absenden dieses Formulars wird JavaScript benötigt. Schreib uns sonst gern direkt eine E-Mail.</p></noscript>
|
||||
<div class="cf-turnstile" data-sitekey="{{ turnstileKey }}" data-language="{{ form.locale|default('de') }}"></div>
|
||||
<noscript><p class="field-error">{{ 'Zum Absenden dieses Formulars wird JavaScript benötigt. Schreib uns sonst gern direkt eine E-Mail.'|kant_t }}</p></noscript>
|
||||
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
|
||||
{% endif %}
|
||||
<button class="button cms-button" type="submit">{{ form.settings.buttonLabel|default('Absenden') }}</button>
|
||||
<button class="button cms-button" type="submit">{{ form.settings.buttonLabel|default('Absenden')|kant_t }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
@@ -14,7 +14,7 @@
|
||||
srcset="{% for source in post.image.variants %}{{ source.src }} {{ source.width }}w{% if not loop.last %}, {% endif %}{% endfor %}" sizes="(max-width: 650px) 100vw, 380px">
|
||||
{# Der Hinweis liegt über dem Bild und erscheint beim Zeigen. Für Vorlesesoftware ist
|
||||
er nichts Neues - der Titel darunter ist derselbe Verweis -, deshalb ausgeblendet. #}
|
||||
<span class="post-view" aria-hidden="true">Ansehen</span>
|
||||
<span class="post-view" aria-hidden="true">{{ 'Ansehen'|kant_t }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="post-body">
|
||||
@@ -22,14 +22,14 @@
|
||||
{% if (showMeta ?? true) and (post.publishedAt or post.author) %}
|
||||
<p class="post-meta">
|
||||
{% if post.publishedAt %}<time datetime="{{ post.publishedAt|date('Y-m-d') }}">{{ post.publishedAt|date('d.m.Y') }}</time>{% endif %}
|
||||
{% if post.author %}<span>von {{ post.author }}</span>{% endif %}
|
||||
{% if post.author %}<span>{{ 'von'|kant_t }} {{ post.author }}</span>{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if (showExcerpt ?? true) and post.excerpt %}<p class="post-excerpt">{{ post.excerpt }}</p>{% endif %}
|
||||
{# Ein Element, das aussieht wie ein Knopf, muss eines sein. Vorher stand hier eine
|
||||
reine Beschriftung: sichtbar, aber nicht anklickbar. Der Titel steckt für
|
||||
Vorlesesoftware mit drin, damit "Weiterlesen" nicht zwölfmal gleich heißt. #}
|
||||
{% if art == 'wechsel' %}<a class="post-more" href="{{ post.href }}">Weiterlesen<span class="visually-hidden">: {{ post.title }}</span></a>{% endif %}
|
||||
{% if art == 'wechsel' %}<a class="post-more" href="{{ post.href }}">{{ 'Weiterlesen'|kant_t }}<span class="visually-hidden">: {{ post.title }}</span></a>{% endif %}
|
||||
{% if (showTerms ?? true) and post.terms.category is not empty %}
|
||||
<p class="post-terms">{% for term in post.terms.category %}<a href="{{ term.href }}">{{ term.name }}</a>{% endfor %}</p>
|
||||
{% endif %}
|
||||
@@ -40,9 +40,9 @@
|
||||
{% endif %}
|
||||
{% if pages|default(1) > 1 %}
|
||||
{# Dieselbe Blätterleiste wie im Archiv: eine Liste, die mehr hat als sie zeigt, sagt es. #}
|
||||
<nav class="post-pager" aria-label="Weitere Beiträge">
|
||||
{% if page > 1 %}<a rel="prev" href="?seite={{ page - 1 }}">← Neuere</a>{% endif %}
|
||||
<span>Seite {{ page }} von {{ pages }}{% if total %} · {{ total }} Beiträge{% endif %}</span>
|
||||
{% if page < pages %}<a rel="next" href="?seite={{ page + 1 }}">Ältere →</a>{% endif %}
|
||||
<nav class="post-pager" aria-label="{{ 'Weitere Beiträge'|kant_t }}">
|
||||
{% if page > 1 %}<a rel="prev" href="?seite={{ page - 1 }}">{{ '← Neuere'|kant_t }}</a>{% endif %}
|
||||
<span>{{ 'Seite %d von %d'|kant_t|format(page, pages) }}{% if total %} · {{ total }} {{ 'Beiträge'|kant_t }}{% endif %}</span>
|
||||
{% if page < pages %}<a rel="next" href="?seite={{ page + 1 }}">{{ 'Ältere →'|kant_t }}</a>{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
@@ -1,16 +1,16 @@
|
||||
{# Weiter zum nächsten Beitrag. Die Beiträge kommen vom Renderer, nie aus gespeichertem Inhalt.
|
||||
Fehlt ein Nachbar, bleibt seine Seite leer statt im Kreis zu führen. #}
|
||||
{% if previous or next %}
|
||||
<nav class="post-nav" aria-label="Weitere Beiträge">
|
||||
<nav class="post-nav" aria-label="{{ 'Weitere Beiträge'|kant_t }}">
|
||||
{% if previous %}
|
||||
<a class="post-nav-link post-nav-previous" href="{{ previous.href }}">
|
||||
<span class="post-nav-label"><span class="nav-arrow" aria-hidden="true">{% include 'blocks/icon.html.twig' with {name: 'arrow-left'} only %}</span> {{ previousLabel|default('Vorheriger Beitrag') }}</span>
|
||||
<span class="post-nav-label"><span class="nav-arrow" aria-hidden="true">{% include 'blocks/icon.html.twig' with {name: 'arrow-left'} only %}</span> {{ previousLabel|default('Vorheriger Beitrag'|kant_t) }}</span>
|
||||
<span class="post-nav-title">{{ previous.title }}</span>
|
||||
</a>
|
||||
{% else %}<span></span>{% endif %}
|
||||
{% if next %}
|
||||
<a class="post-nav-link post-nav-next" href="{{ next.href }}">
|
||||
<span class="post-nav-label">{{ nextLabel|default('Nächster Beitrag') }} <span class="nav-arrow" aria-hidden="true">{% include 'blocks/icon.html.twig' with {name: 'arrow-right'} only %}</span></span>
|
||||
<span class="post-nav-label">{{ nextLabel|default('Nächster Beitrag'|kant_t) }} <span class="nav-arrow" aria-hidden="true">{% include 'blocks/icon.html.twig' with {name: 'arrow-right'} only %}</span></span>
|
||||
<span class="post-nav-title">{{ next.title }}</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
Reference in new issue
Block a user