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
@@ -89,6 +89,13 @@ final class SiteSettings
|
||||
'consentAccentInk' => '',
|
||||
'privacyPath' => '',
|
||||
'imprintPath' => '',
|
||||
// Die weiteren Sprachen neben der Hauptsprache (ADR 111), gepflegt über Languages::save.
|
||||
// Leer heißt: einsprachig - und dann ist nirgends etwas von Übersetzungen zu sehen.
|
||||
'languages' => [],
|
||||
// Namen von Kategorien und Schlagwörtern je weiterer Sprache: {en: {"category:projekte": "Projects"}}.
|
||||
'termNames' => [],
|
||||
// Überschrift und Text des Einwilligungshinweises je weiterer Sprache: {en: {title, text}}.
|
||||
'consentTexts' => [],
|
||||
];
|
||||
|
||||
/** Die Wochentage, wie schema.org sie nennt, mit deutscher Beschriftung für das Formular. */
|
||||
@@ -191,6 +198,10 @@ final class SiteSettings
|
||||
$values = self::DEFAULTS;
|
||||
// Assigned separately, so saving company data cannot clear the error page.
|
||||
$values['notFoundPageId'] = $this->load()['notFoundPageId'];
|
||||
// Ebenso die Sprachen: sie haben einen eigenen Bildschirm mit eigener Prüfung.
|
||||
$values['languages'] = $this->load()['languages'];
|
||||
$values['termNames'] = $this->load()['termNames'];
|
||||
$values['consentTexts'] = $this->load()['consentTexts'];
|
||||
foreach (['legalName' => 200, 'description' => 300, 'phone' => 40, 'email' => 120, 'street' => 120, 'postalCode' => 16, 'city' => 120] as $key => $max) {
|
||||
$values[$key] = mb_substr(trim((string) ($input[$key] ?? '')), 0, $max);
|
||||
}
|
||||
@@ -403,6 +414,71 @@ final class SiteSettings
|
||||
$this->loaded = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nur die Sprachen ändern, alles andere unberührt lassen. Geprüft wird in Languages::save.
|
||||
*
|
||||
* @param list<string> $codes
|
||||
*/
|
||||
public function saveLanguages(array $codes): void
|
||||
{
|
||||
$this->db->transactional(function () use ($codes): void {
|
||||
$raw = $this->db->fetchOne('SELECT settings FROM website WHERE id = ?', ['default']);
|
||||
$stored = is_string($raw) ? json_decode($raw, true, 512, JSON_THROW_ON_ERROR) : [];
|
||||
$stored = is_array($stored) ? $stored : [];
|
||||
$stored['languages'] = array_values($codes);
|
||||
$this->db->executeStatement('UPDATE website SET settings = ? WHERE id = ?', [
|
||||
json_encode($stored, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE), 'default',
|
||||
]);
|
||||
});
|
||||
$this->loaded = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Die Texte der Einstellungen in weiteren Sprachen (ADR 111): Namen von Kategorien und
|
||||
* Schlagwörtern sowie Überschrift und Text des Einwilligungshinweises. Alles andere bleibt.
|
||||
*
|
||||
* @param array<string, array<string, string>> $names Sprache => "art:adresse" => Name
|
||||
* @param array<string, array<string, string>> $consent Sprache => {title, text}
|
||||
*/
|
||||
public function saveLanguageTexts(array $names, array $consent = []): void
|
||||
{
|
||||
$consentClean = [];
|
||||
foreach ($consent as $locale => $texts) {
|
||||
if (!is_string($locale) || !isset(Languages::CATALOG[$locale]) || !is_array($texts)) {
|
||||
continue;
|
||||
}
|
||||
foreach (['title' => 80, 'text' => 600] as $key => $max) {
|
||||
$text = mb_substr(trim((string) ($texts[$key] ?? '')), 0, $max);
|
||||
if ('' !== $text) {
|
||||
$consentClean[$locale][$key] = $text;
|
||||
}
|
||||
}
|
||||
}
|
||||
$clean = [];
|
||||
foreach ($names as $locale => $entries) {
|
||||
if (!is_string($locale) || !isset(Languages::CATALOG[$locale]) || !is_array($entries)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($entries as $key => $name) {
|
||||
$name = mb_substr(trim((string) $name), 0, 120);
|
||||
if ('' !== $name && 1 === preg_match('/^(category|tag):[a-z0-9-]{1,80}$/D', (string) $key)) {
|
||||
$clean[$locale][(string) $key] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->db->transactional(function () use ($clean, $consentClean): void {
|
||||
$raw = $this->db->fetchOne('SELECT settings FROM website WHERE id = ?', ['default']);
|
||||
$stored = is_string($raw) ? json_decode($raw, true, 512, JSON_THROW_ON_ERROR) : [];
|
||||
$stored = is_array($stored) ? $stored : [];
|
||||
$stored['termNames'] = $clean;
|
||||
$stored['consentTexts'] = $consentClean;
|
||||
$this->db->executeStatement('UPDATE website SET settings = ? WHERE id = ?', [
|
||||
json_encode($stored, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE), 'default',
|
||||
]);
|
||||
});
|
||||
$this->loaded = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which of the two mandatory pages are missing, by their settings key.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user