From e1fb632cdd585ec25fd1c642b6484e2fa4733324 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 18:33:15 +0000 Subject: [PATCH] feat(i18n): Deutsch/Englisch, Sprachknopf und GitHub-Link in der Kopfzeile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - i18n-Kern im gettext-Stil (t/msg, Wörterbuch en.ts); Startsprache aus localStorage, sonst Browsersprache; setzt auch das Inline-Skript in index.html. Ein Test prüft t()/msg() und en.ts in beide Richtungen. - Kopfzeile: GitHub-Link und Sprachknopf (Zielsprache, Beschriftung und lang in der Zielsprache), Fußzeile aus mountFooter, Banner und Seitentitel übersetzt, statisches index.html englisch. - Sprachwechsel ohne Neuladen (main.ts): Kopfzeile, Banner, Konto-Menü und Fußzeile neu aufgebaut, rerender() des Routers baut die Seite neu auf, der Editor-Store bleibt; der Appwrite-Client bekommt die Sprache. - Kopfzeile bleibt in einer Zeile: einzeilig ab 1000 px (vorher 900), GitHub-Text ab 1230 px, auf schmalen Telefonen steht „Anmelden“ neben der Suche. Co-Authored-By: Claude Opus 5.5 --- web/index.html | 26 ++- web/src/branding.test.ts | 74 ++++++-- web/src/config.ts | 3 + web/src/editor/dom.ts | 4 + web/src/i18n/coverage.test.ts | 123 +++++++++++++ web/src/i18n/en.ts | 33 ++++ web/src/i18n/i18n.test.ts | 258 ++++++++++++++++++++++++++++ web/src/i18n/i18n.ts | 83 +++++++++ web/src/main.test.ts | 181 +++++++++++++++++++ web/src/main.ts | 61 +++++-- web/src/pages/router.test.ts | 108 +++++++++++- web/src/pages/router.ts | 26 ++- web/src/shell/donate-banner.test.ts | 11 ++ web/src/shell/donate-banner.ts | 7 +- web/src/shell/footer.test.ts | 46 +++++ web/src/shell/footer.ts | 16 ++ web/src/shell/header.test.ts | 149 +++++++++++++++- web/src/shell/header.ts | 66 +++++-- web/src/shell/shell.css | 105 ++++++++++- web/src/test-setup.ts | 5 + web/vite.config.ts | 2 + 21 files changed, 1321 insertions(+), 66 deletions(-) create mode 100644 web/src/i18n/coverage.test.ts create mode 100644 web/src/i18n/en.ts create mode 100644 web/src/i18n/i18n.test.ts create mode 100644 web/src/i18n/i18n.ts create mode 100644 web/src/main.test.ts create mode 100644 web/src/shell/footer.test.ts create mode 100644 web/src/shell/footer.ts create mode 100644 web/src/test-setup.ts diff --git a/web/index.html b/web/index.html index d40a323..2758153 100644 --- a/web/index.html +++ b/web/index.html @@ -1,17 +1,19 @@ - + - AirCard-IOS – Kartendesigns für Apple Wallet + AirCard-IOS – Card designs for Apple Wallet + @@ -33,9 +47,7 @@
-
-

Inoffizielles Hobbyprojekt. Keine Verbindung zu Apple, Visa, Mastercard, UnionPay, American Express, JCB oder Discover. Alle Marken gehören ihren Inhabern.

-
+
diff --git a/web/src/branding.test.ts b/web/src/branding.test.ts index b9ca7a2..0974726 100644 --- a/web/src/branding.test.ts +++ b/web/src/branding.test.ts @@ -3,6 +3,7 @@ import { resolve } from 'node:path'; import { runInNewContext } from 'node:vm'; import { describe, expect, it } from 'vitest'; import indexHtml from '../index.html?raw'; +import { initialLang } from './i18n/i18n'; import { initialTheme } from './shell/theme'; const publicFile = (name: string) => readFileSync(resolve(import.meta.dirname, '../public', name)); @@ -16,7 +17,11 @@ function pngInfo(buf: Buffer) { describe('Name und Kopfbereich', () => { it('Titel und Meta-Angaben nennen AirCard-IOS', () => { - expect(indexHtml).toContain('AirCard-IOS – Kartendesigns für Apple Wallet'); + // Statisch englisch, damit Link-Vorschauen englisch sind; der Router setzt danach den Titel in der gewählten Sprache. + expect(indexHtml).toContain('AirCard-IOS – Card designs for Apple Wallet'); + expect(indexHtml).toContain( + '', + ); expect(indexHtml).toContain(''); expect(indexHtml).toContain(''); }); @@ -33,10 +38,9 @@ describe('Name und Kopfbereich', () => { ); }); - it('Fußzeilen-Hinweis bleibt unverändert', () => { - expect(indexHtml).toContain( - 'Inoffizielles Hobbyprojekt. Keine Verbindung zu Apple, Visa, Mastercard, UnionPay, American Express, JCB oder Discover. Alle Marken gehören ihren Inhabern.', - ); + it('Fußzeile ist leer: ihren Text setzt mountFooter in der gewählten Sprache', () => { + expect(indexHtml).toContain('
'); + expect(indexHtml).not.toContain('Inoffizielles Hobbyprojekt'); }); }); @@ -50,20 +54,33 @@ describe('Icon-Dateien', () => { }); }); -describe('Inline-Skript für Hell/Dunkel in index.html', () => { +describe('Inline-Skript für Hell/Dunkel und Sprache in index.html', () => { const script = /