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
@@ -1,6 +1,6 @@
|
||||
<!doctype html>
|
||||
{# Ohne Klasse spielt die Website Bewegung immer ab; mit ihr gilt die Browservorgabe. #}
|
||||
<html lang="{{ page.locale }}"{{ siteSettings.load().motion == 'respect' ? ' class="motion-respect"' : '' }}>
|
||||
<html lang="{{ page.locale }}"{{ page.locale in ['ar'] ? ' dir="rtl"' : '' }}{{ siteSettings.load().motion == 'respect' ? ' class="motion-respect"' : '' }}>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -9,6 +9,11 @@
|
||||
<title>{{ seoTitle }}</title>
|
||||
<meta name="description" content="{{ page.document.description }}">
|
||||
{% if canonical|default(null) %}<link rel="canonical" href="{{ canonical }}">{% endif %}
|
||||
{# Dieselbe Seite in den anderen Sprachen (ADR 111). Nur, wenn es sie gibt; die Hauptsprache
|
||||
ist zugleich die Vorgabe für alle, deren Sprache nicht dabei ist. #}
|
||||
{% for alternate in alternates|default([]) %}<link rel="alternate" hreflang="{{ alternate.locale }}" href="{{ alternate.url }}">
|
||||
{% endfor %}{% if alternates|default([]) %}<link rel="alternate" hreflang="x-default" href="{{ (alternates|first).url }}">
|
||||
{% endif %}
|
||||
{% if pagination|default(null) %}{% if pagination.page > 1 %}<link rel="prev" href="{{ pagination.base }}{{ pagination.page > 2 ? '?seite=' ~ (pagination.page - 1) : '' }}">{% endif %}{% if pagination.page < pagination.pages %}<link rel="next" href="{{ pagination.base }}?seite={{ pagination.page + 1 }}">{% endif %}{% endif %}
|
||||
{% if page.robots|default('index') != 'index' %}<meta name="robots" content="noindex, follow">{% endif %}
|
||||
<meta property="og:title" content="{{ seoTitle }}">
|
||||
@@ -17,7 +22,9 @@
|
||||
{% if canonical|default(null) %}<meta property="og:url" content="{{ canonical }}">{% endif %}
|
||||
{# In der Vorschau des Editors gibt es den Websitenamen nicht - dort fehlt das Feld einfach. #}
|
||||
{% if page.website_name|default('') %}<meta property="og:site_name" content="{{ page.website_name }}">{% endif %}
|
||||
<meta property="og:locale" content="{{ page.locale == 'de' ? 'de_DE' : page.locale }}">
|
||||
<meta property="og:locale" content="{{ kant_og_locale(page.locale) }}">
|
||||
{% for alternate in alternates|default([]) %}{% if not alternate.current %}<meta property="og:locale:alternate" content="{{ kant_og_locale(alternate.locale) }}">
|
||||
{% endif %}{% endfor %}
|
||||
{% if socialImage|default(null) %}
|
||||
<meta property="og:image" content="{{ socialImage.url }}">
|
||||
{% if socialImage.alt %}<meta property="og:image:alt" content="{{ socialImage.alt }}">{% endif %}
|
||||
@@ -82,7 +89,7 @@
|
||||
{% set consentOpen = not editing|default(false) and consentGate.needsBanner(app.request) %}
|
||||
<body class="kant{{ consentOpen ? ' consent-open' : '' }}">
|
||||
<span id="seitenanfang" tabindex="-1"></span>
|
||||
<a class="skip-link" href="#main">Zum Inhalt springen</a>
|
||||
<a class="skip-link" href="#main">{{ 'Zum Inhalt springen'|kant_t }}</a>
|
||||
{# Header and footer are block documents like any page, so they are built from elements
|
||||
instead of being written into this template. #}
|
||||
{% if areas.header|default(null) %}
|
||||
@@ -94,7 +101,7 @@
|
||||
{% if postMeta is defined and postMeta is not null and siteSettings.load().postMetaHeader %}
|
||||
<p class="post-header shell">
|
||||
{% if page.published_at %}<time datetime="{{ page.published_at|date('Y-m-d') }}">{{ page.published_at|date('d.m.Y') }}</time>{% endif %}
|
||||
{% if page.author %}<span>von {{ page.author }}</span>{% endif %}
|
||||
{% if page.author %}<span>{{ 'von'|kant_t }} {{ page.author }}</span>{% endif %}
|
||||
{% for term in postMeta.category %}<a href="{{ term.href }}">{{ term.name }}</a>{% endfor %}
|
||||
{% for term in postMeta.tag %}<a class="post-tag" href="{{ term.href }}">#{{ term.name }}</a>{% endfor %}
|
||||
</p>
|
||||
@@ -116,7 +123,7 @@
|
||||
{# Ein Anker, kein Skript. Er springt an den Anfang - das kann ein Browser allein. Der nav-Rahmen
|
||||
gibt ihm eine Landmarke: ein Verweis außerhalb jeder Landmarke ist für Vorlesesoftware
|
||||
Inhalt ohne Ort, und axe meldet ihn als Verstoß gegen „region“. #}
|
||||
<nav aria-label="{{ oben.scrollTopLabel|default('Nach oben') }}"><a class="scroll-top{{ oben.scrollTopPosition|default('rechts') == 'links' ? ' scroll-top-links' : '' }}" href="#seitenanfang" aria-label="{{ oben.scrollTopLabel|default('Nach oben') }}"><span aria-hidden="true">↑</span></a></nav>
|
||||
<nav aria-label="{{ oben.scrollTopLabel|default('Nach oben')|kant_t }}"><a class="scroll-top{{ oben.scrollTopPosition|default('rechts') == 'links' ? ' scroll-top-links' : '' }}" href="#seitenanfang" aria-label="{{ oben.scrollTopLabel|default('Nach oben')|kant_t }}"><span aria-hidden="true">↑</span></a></nav>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user