Finish an archive update in one go and translate Kantarci Reisen into Turkish
The request that swaps the files now ends with a plain page instead of running on through the framework with old code in memory and new code on disk - that is what answered with a 500 after a successful update. The page calls the second step itself with a one-time key: migrations, clean-up, maintenance off, back to the system screen (ADR 112). Turkish is complete in the fixed template texts and form messages. Two language switches on one page get distinct landmark names. Kantarci Reisen gets a Turkish version of every page, the header, footer, menu and enquiry form, with a DE · TR switch; the reset script applies it. The DM Sans 600 files are needed after all and are committed with fonts.css. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8aee38bde8
commit
47292c7266
14 files changed
+617
-11
No files matched your search
@@ -439,6 +439,20 @@ final class FormRepository
|
||||
'Bitte gib mindestens %s an.' => 'Please enter at least %s.',
|
||||
'Bitte gib einen Wert zwischen %s und %s an.' => 'Please enter a value between %s and %s.',
|
||||
],
|
||||
'tr' => [
|
||||
'Bitte wähle nur aus den angebotenen Optionen.' => 'Lütfen yalnızca sunulan seçeneklerden seçin.',
|
||||
'Bitte wähle mindestens eine Option.' => 'Lütfen en az bir seçenek seçin.',
|
||||
'Bitte kürze die Eingabe auf höchstens %d Zeichen.' => 'Lütfen girişinizi en fazla %d karaktere kısaltın.',
|
||||
'Ohne diese Einwilligung können wir die Anfrage nicht bearbeiten.' => 'Bu onay olmadan talebinizi işleme alamayız.',
|
||||
'Bitte wähle eine der angebotenen Optionen.' => 'Lütfen sunulan seçeneklerden birini seçin.',
|
||||
'Dieses Feld wird benötigt.' => 'Bu alan zorunludur.',
|
||||
'Bitte gib eine gültige E-Mail-Adresse an.' => 'Lütfen geçerli bir e-posta adresi girin.',
|
||||
'Bitte gib eine gültige Telefonnummer an.' => 'Lütfen geçerli bir telefon numarası girin.',
|
||||
'Bitte gib eine Zahl an.' => 'Lütfen bir sayı girin.',
|
||||
'Bitte gib höchstens %s an.' => 'Lütfen en fazla %s girin.',
|
||||
'Bitte gib mindestens %s an.' => 'Lütfen en az %s girin.',
|
||||
'Bitte gib einen Wert zwischen %s und %s an.' => 'Lütfen %s ile %s arasında bir değer girin.',
|
||||
],
|
||||
];
|
||||
|
||||
private static function message(?string $locale, string $text, mixed ...$args): string
|
||||
|
||||
@@ -174,13 +174,14 @@ final class SystemController extends AbstractController
|
||||
|
||||
return $this->redirectToRoute('admin_system');
|
||||
}
|
||||
// Alles, was nach dem Dateitausch noch gebraucht wird, steht vorher fest: Danach darf diese
|
||||
// Anfrage keinen Code mehr nachladen (siehe sendAndStop).
|
||||
$abschluss = '/'.trim($this->adminPath, '/').'/system/abschliessen?token=';
|
||||
$ergebnis = $this->archive->run($request->request->getString('target') ?: null);
|
||||
$request->getSession()->set('update_steps', $ergebnis['steps']);
|
||||
if ($ergebnis['migrate'] ?? false) {
|
||||
$this->addFlash('success', 'Die Dateien sind ersetzt. Jetzt noch die Migrationen - der Knopf steht oben.');
|
||||
|
||||
return $this->redirectToRoute('admin_system');
|
||||
$this->sendAndStop((string) $ergebnis['fassung'], $abschluss.$ergebnis['token'], $ergebnis['steps']);
|
||||
}
|
||||
$request->getSession()->set('update_steps', $ergebnis['steps']);
|
||||
$this->addFlash($ergebnis['ok'] ? 'success' : 'error', $ergebnis['ok']
|
||||
? 'Es war nichts einzuspielen.'
|
||||
: 'Das Update ist nicht durchgelaufen. Die Schritte stehen unten.');
|
||||
@@ -188,6 +189,73 @@ final class SystemController extends AbstractController
|
||||
return $this->redirectToRoute('admin_system');
|
||||
}
|
||||
|
||||
/**
|
||||
* Der zweite Schritt des Archivupdates, aufgerufen von der Zwischenseite selbst (ADR 112).
|
||||
*
|
||||
* Läuft in einer eigenen Anfrage und damit mit dem Code der neuen Fassung: Migrationen,
|
||||
* Aufräumen, Wartungszustand aus. Der Einmalschlüssel aus var/update/offen.json ersetzt das
|
||||
* CSRF-Token, das die Zwischenseite nicht mehr erzeugen kann; ohne Administratorrolle geht es
|
||||
* ohnehin nicht. Ein zweiter Aufruf (Neuladen) findet nichts mehr offen und meldet das.
|
||||
*/
|
||||
#[Route('/%kant.admin_path%/system/abschliessen', name: 'admin_system_finish', methods: ['GET'])]
|
||||
public function finishArchive(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||
$offen = $this->archive->pending();
|
||||
if (null === $offen) {
|
||||
$this->addFlash('success', 'Das Update ist bereits abgeschlossen.');
|
||||
|
||||
return $this->redirectToRoute('admin_system');
|
||||
}
|
||||
if (!is_string($offen['token'] ?? null) || !hash_equals($offen['token'], $request->query->getString('token'))) {
|
||||
return $this->back('Dieser Link gehört zu keinem offenen Update. Schließe es oben mit „Migrationen ausführen“ ab.');
|
||||
}
|
||||
@set_time_limit(0);
|
||||
$ergebnis = $this->archive->finish($this->installation);
|
||||
$request->getSession()->set('update_steps', $ergebnis['steps']);
|
||||
$this->addFlash($ergebnis['ok'] ? 'success' : 'error', $ergebnis['ok']
|
||||
? sprintf('Update auf %s abgeschlossen. Die Website ist wieder erreichbar.', $offen['fassung'])
|
||||
: 'Die Migrationen sind fehlgeschlagen. Die Website bleibt im Wartungszustand.');
|
||||
|
||||
return $this->redirectToRoute('admin_system');
|
||||
}
|
||||
|
||||
/**
|
||||
* Beendet die Anfrage, die gerade die Dateien getauscht hat, mit einer schlichten Seite.
|
||||
*
|
||||
* Dieser Prozess hat den alten Code im Speicher, auf der Platte liegt der neue. Alles, was
|
||||
* jetzt noch nachgeladen würde - Antwort-Listener, Sitzung, Vorlagen -, käme aus der neuen
|
||||
* Fassung und passte nicht zu dem, was schon geladen ist. Genau das endete vorher mit einem
|
||||
* 500er, obwohl das Update gelungen war. Deshalb hier nur noch PHP selbst: Kopfzeilen, Text,
|
||||
* Ende. Die Seite ruft den Abschluss sofort selbst auf, ein Link steht für den Fall daneben,
|
||||
* dass der Browser das Weiterleiten unterdrückt.
|
||||
*
|
||||
* @param list<array{step: string, ok: bool, detail: string}> $schritte
|
||||
*/
|
||||
private function sendAndStop(string $fassung, string $weiter, array $schritte): never
|
||||
{
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
$e = static fn (string $text): string => htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$liste = '';
|
||||
foreach ($schritte as $schritt) {
|
||||
$liste .= '<li>'.($schritt['ok'] ? '✓ ' : '! ').$e($schritt['step']).'</li>';
|
||||
}
|
||||
http_response_code(200);
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
header('Cache-Control: no-store');
|
||||
header('X-Robots-Tag: noindex');
|
||||
header("Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'; base-uri 'none'");
|
||||
echo '<!doctype html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">'
|
||||
.'<meta http-equiv="refresh" content="1;url='.$e($weiter).'"><title>Update wird abgeschlossen</title>'
|
||||
.'<style>body{font:16px/1.5 system-ui,sans-serif;background:#f3f5f2;color:#203b34;margin:0;display:grid;place-items:center;min-height:100vh}main{background:#fff;border:1px solid #dfe5e1;border-radius:12px;padding:28px 32px;max-width:460px}h1{font-size:20px;margin:0 0 8px}ul{padding-left:18px;color:#46514c;font-size:14px}a{color:#1f5f45}</style></head>'
|
||||
.'<body><main><h1>Update auf '.$e($fassung).' wird abgeschlossen …</h1>'
|
||||
.'<p role="status">Die neuen Dateien sind da. Jetzt laufen die Migrationen, danach geht es von selbst zur Systemseite.</p>'
|
||||
.'<ul>'.$liste.'</ul><p><a href="'.$e($weiter).'">Weiter, falls nichts passiert</a></p></main></body></html>';
|
||||
exit;
|
||||
}
|
||||
|
||||
#[Route('/%kant.admin_path%/system/update', name: 'admin_system_update', methods: ['POST'])]
|
||||
public function update(Request $request): Response
|
||||
{
|
||||
|
||||
@@ -256,23 +256,28 @@ final class ArchiveUpdate
|
||||
// Speicher - Klassen, die PHP schon geladen hat, laedt es nicht neu. Die Migrationen
|
||||
// gehoeren deshalb in eine eigene Anfrage, die mit der neuen Fassung startet. Der
|
||||
// Wartungszustand bleibt so lange an.
|
||||
// Ein Einmalschlüssel für den Abschluss: Die Seite, die diese Anfrage noch ausgibt, ruft
|
||||
// damit den zweiten Schritt selbst auf - ohne Knopf, und ohne dass eine fremde Seite ihn
|
||||
// auslösen könnte (dazu braucht es zusätzlich eine Anmeldung als Administrator).
|
||||
$token = bin2hex(random_bytes(16));
|
||||
@file_put_contents($this->root.'/'.self::MARKE, json_encode([
|
||||
'fassung' => $ziel,
|
||||
'seit' => gmdate('c'),
|
||||
'alt' => basename($arbeit),
|
||||
'token' => $token,
|
||||
], JSON_UNESCAPED_SLASHES));
|
||||
$melden($schritte, 'Dateien sind ersetzt', true, sprintf(
|
||||
'Fassung %s liegt bereit. Jetzt fehlen noch die Migrationen - sie laufen im nächsten Schritt mit dem neuen Code.',
|
||||
$ziel,
|
||||
));
|
||||
|
||||
return ['ok' => true, 'steps' => $schritte, 'migrate' => true];
|
||||
return ['ok' => true, 'steps' => $schritte, 'migrate' => true, 'token' => $token, 'fassung' => $ziel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Steht ein zweiter Schritt aus? Gibt Fassung und Zeitpunkt zurück, sonst null.
|
||||
*
|
||||
* @return array{fassung: string, seit: string, alt: string}|null
|
||||
* @return array{fassung: string, seit: string, alt: string, token?: string}|null
|
||||
*/
|
||||
public function pending(): ?array
|
||||
{
|
||||
|
||||
@@ -49,6 +49,7 @@ final class LanguageExtension extends AbstractExtension
|
||||
'Navigation' => 'Navigation',
|
||||
'Nach oben' => 'Back to top',
|
||||
'Sprache' => 'Language',
|
||||
'Sprache wählen' => 'Choose language',
|
||||
// Formulare
|
||||
'Dieses Formular ist nicht mehr vorhanden.' => 'This form is no longer available.',
|
||||
'Bitte prüfe 1 Eingabe. Die betroffenen Felder sind unten gekennzeichnet.' => 'Please check 1 entry. The field concerned is marked below.',
|
||||
@@ -105,6 +106,7 @@ final class LanguageExtension extends AbstractExtension
|
||||
'Alle akzeptieren' => 'Tout accepter',
|
||||
'Nur notwendige' => 'Uniquement nécessaires',
|
||||
'Sprache' => 'Langue',
|
||||
'Sprache wählen' => 'Choisir la langue',
|
||||
],
|
||||
'tr' => [
|
||||
'Zum Inhalt springen' => 'İçeriğe geç',
|
||||
@@ -118,6 +120,67 @@ final class LanguageExtension extends AbstractExtension
|
||||
'Alle akzeptieren' => 'Tümünü kabul et',
|
||||
'Nur notwendige' => 'Yalnızca gerekli',
|
||||
'Sprache' => 'Dil',
|
||||
'Sprache wählen' => 'Dil seçin',
|
||||
// Vollständig, damit eine türkische Seite nirgends auf Englisch ausweicht (09.10.2026).
|
||||
'Brotkrumen' => 'Sayfa yolu',
|
||||
'Soziale Profile' => 'Sosyal medya',
|
||||
'Zum Blättern seitwärts scrollen' => 'Gezinmek için yana kaydırın',
|
||||
'Anbieter:' => 'Sağlayıcı:',
|
||||
'Inhalt laden und einwilligen' => 'İçeriği yükle ve onayla',
|
||||
'Einwilligung ändern' => 'Onayı değiştir',
|
||||
'Bildergalerie' => 'Resim galerisi',
|
||||
'Bild' => 'Resim',
|
||||
'vergrößern' => 'büyüt',
|
||||
'Ansehen' => 'İncele',
|
||||
'Weitere Beiträge' => 'Diğer yazılar',
|
||||
'Vorheriger Beitrag' => 'Önceki yazı',
|
||||
'Nächster Beitrag' => 'Sonraki yazı',
|
||||
'Einzeln wählen' => 'Tek tek seç',
|
||||
'Auswahl speichern' => 'Seçimi kaydet',
|
||||
'Zweite Navigation' => 'İkinci menü',
|
||||
'Fußzeile' => 'Alt bilgi',
|
||||
'Rechtliches' => 'Yasal bilgiler',
|
||||
'Navigation' => 'Menü',
|
||||
'Dieses Formular ist nicht mehr vorhanden.' => 'Bu form artık mevcut değil.',
|
||||
'Bitte prüfe 1 Eingabe. Die betroffenen Felder sind unten gekennzeichnet.' => 'Lütfen 1 girişi kontrol edin. İlgili alan aşağıda işaretlenmiştir.',
|
||||
'Bitte prüfe %d Eingaben. Die betroffenen Felder sind unten gekennzeichnet.' => 'Lütfen %d girişi kontrol edin. İlgili alanlar aşağıda işaretlenmiştir.',
|
||||
'Pflichtfeld' => 'zorunlu alan',
|
||||
'in' => '',
|
||||
'bitte wählen' => 'lütfen seçin',
|
||||
'Zum Absenden dieses Formulars wird JavaScript benötigt. Schreib uns sonst gern direkt eine E-Mail.' => 'Bu formu göndermek için JavaScript gereklidir. Dilerseniz bize doğrudan e-posta gönderebilirsiniz.',
|
||||
'Zusammenfassung' => 'Özet',
|
||||
'Bitte prüfen Sie Ihre Angaben. Über „Ändern“ kommen Sie zum jeweiligen Schritt zurück.' => 'Lütfen bilgilerinizi kontrol edin. „Değiştir“ ile ilgili adıma geri dönebilirsiniz.',
|
||||
'Schritte des Formulars' => 'Form adımları',
|
||||
'Schritt' => 'Adım',
|
||||
'Zurück' => 'Geri',
|
||||
'Weiter' => 'İleri',
|
||||
'Ändern' => 'Değiştir',
|
||||
'Ja' => 'Evet',
|
||||
'Bitte wähle mindestens eine Option.' => 'Lütfen en az bir seçenek seçin.',
|
||||
'Absenden' => 'Gönder',
|
||||
'Vielen Dank. Deine Nachricht ist angekommen.' => 'Teşekkürler. Mesajınız bize ulaştı.',
|
||||
'Beiträge' => 'Yazılar',
|
||||
'Beitrag' => 'yazı',
|
||||
'Kategorie' => 'Kategori',
|
||||
'Schlagwort' => 'Etiket',
|
||||
'Alle Beiträge, die neuesten zuerst.' => 'Tüm yazılar, en yenisi önce.',
|
||||
'Beiträge in der Kategorie „%s“.' => '„%s“ kategorisindeki yazılar.',
|
||||
'Beiträge zum Schlagwort „%s“.' => '„%s“ etiketli yazılar.',
|
||||
'von' => 'yazan:',
|
||||
'← Neuere' => '← Daha yeni',
|
||||
'Ältere →' => 'Daha eski →',
|
||||
'Seite %d von %d' => 'Sayfa %d / %d',
|
||||
'Externe Inhalte und Einwilligung' => 'Harici içerikler ve onay',
|
||||
'Diese Website lädt bestimmte Inhalte erst, wenn du zustimmst. Ohne Zustimmung wird die Seite vollständig angezeigt, nur eingebettete Inhalte anderer Anbieter bleiben leer.' => 'Bu web sitesi bazı içerikleri ancak onayınızdan sonra yükler. Onay vermeseniz de sayfa eksiksiz görüntülenir; yalnızca diğer sağlayıcıların yerleştirilmiş içerikleri boş kalır.',
|
||||
'Statistik' => 'İstatistik',
|
||||
'Externe Inhalte' => 'Harici içerikler',
|
||||
'Messung, wie die Website benutzt wird.' => 'Web sitesinin nasıl kullanıldığının ölçülmesi.',
|
||||
'Inhalte von anderen Anbietern, etwa Karten, Videos, Bewertungen oder ein Chat. Sie sehen die Adresse dieser Seite und deine IP-Adresse, sobald sie geladen werden.' => 'Harita, video, değerlendirme veya sohbet gibi diğer sağlayıcıların içerikleri. Yüklendiklerinde bu sayfanın adresini ve IP adresinizi görürler.',
|
||||
'Seite nicht gefunden' => 'Sayfa bulunamadı',
|
||||
'Diese Seite ist nicht mehr verfügbar.' => 'Bu sayfa artık mevcut değil.',
|
||||
'Die gesuchte Seite ist nicht verfügbar. Über die Startseite findest du wieder zum richtigen Inhalt.' => 'Aradığınız sayfa mevcut değil. Ana sayfadan doğru içeriğe ulaşabilirsiniz.',
|
||||
'Die Adresse ist möglicherweise veraltet oder wurde falsch eingegeben. Über die Startseite findest du wieder zum richtigen Inhalt.' => 'Adres eskimiş veya yanlış yazılmış olabilir. Ana sayfadan doğru içeriğe ulaşabilirsiniz.',
|
||||
'Zur Startseite' => 'Ana sayfaya',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user