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
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Content\Languages;
|
||||
use App\Content\PostRepository;
|
||||
use App\Content\SiteSettings;
|
||||
use Doctrine\DBAL\Connection;
|
||||
@@ -22,6 +23,7 @@ final class SeoController extends AbstractController
|
||||
private PostRepository $posts,
|
||||
private string $siteOrigin,
|
||||
private string $adminPath,
|
||||
private Languages $languages,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -44,6 +46,24 @@ final class SeoController extends AbstractController
|
||||
'lastmod' => substr(max((string) ($row['published_at'] ?? ''), (string) ($row['created_at'] ?? '')), 0, 10),
|
||||
];
|
||||
}
|
||||
// Jede weitere Sprache mit ihren veröffentlichten Seiten (ADR 111), unter denselben
|
||||
// Bedingungen wie die Hauptsprache. Die Zuordnung der Sprachen zueinander steht als
|
||||
// hreflang in den Seiten selbst.
|
||||
$extra = $this->languages->extra();
|
||||
if ([] !== $extra) {
|
||||
foreach ($this->db->fetchAllAssociative(
|
||||
"SELECT t.locale, t.path AS translated, p.path, p.published_at, r.created_at FROM page_translation t JOIN page p ON p.id = t.page_id JOIN revision r ON r.id = p.published_revision_id AND r.page_id = p.id WHERE t.website_id = ? AND p.robots = 'index' AND p.deleted_at IS NULL AND p.id <> ? ORDER BY t.locale, t.path",
|
||||
['default', $this->settings->load()['notFoundPageId']],
|
||||
) as $row) {
|
||||
if (!in_array($row['locale'], $extra, true)) {
|
||||
continue;
|
||||
}
|
||||
$entries[] = [
|
||||
'loc' => $origin.'/'.$row['locale'].($row['path'] === $home ? '' : '/'.$row['translated']),
|
||||
'lastmod' => substr(max((string) ($row['published_at'] ?? ''), (string) ($row['created_at'] ?? '')), 0, 10),
|
||||
];
|
||||
}
|
||||
}
|
||||
// Archives exist only while they list something.
|
||||
if ([] !== $this->posts->published(1, null, 1)['posts']) {
|
||||
$entries[] = ['loc' => $origin.'/beitraege', 'lastmod' => null];
|
||||
|
||||
Reference in new issue
Block a user