feat(i18n): Deutsch/Englisch, Sprachknopf und GitHub-Link in der Kopfzeile
- i18n-Kern im gettext-Stil (t/msg, Wörterbuch en.ts); Startsprache aus localStorage, sonst Browsersprache; <html lang> 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 <noreply@anthropic.com>
This commit is contained in:
1 parent
9455013833
commit
e1fb632cdd
21 files changed
+1321
-66
No files matched your search
+51
-10
@@ -1,9 +1,11 @@
|
||||
import '@fontsource-variable/nunito-sans';
|
||||
import './styles.css';
|
||||
import { mountAccountMenu } from './account/account-menu';
|
||||
import { mountAccountMenu, type AccountMenu } from './account/account-menu';
|
||||
import { client } from './account/appwrite';
|
||||
import { getCurrentUser } from './account/auth';
|
||||
import { DONATE_URL } from './config';
|
||||
import { legacyRedirect, startRouter } from './pages/router';
|
||||
import { currentLang, onLangChange, setLang } from './i18n/i18n';
|
||||
import { legacyRedirect, rerender, startRouter } from './pages/router';
|
||||
import { mountCardsPage } from './pages/cards-page';
|
||||
import { mountCommunityPage } from './pages/community-page';
|
||||
import { mountHomePage } from './pages/home-page';
|
||||
@@ -12,12 +14,18 @@ import { mountEditor } from './editor/editor-page';
|
||||
import { bindEditorToAuth } from './editor/auth-reset';
|
||||
import { createStore, initialEditorState } from './editor/state';
|
||||
import { mountDonateBanner } from './shell/donate-banner';
|
||||
import { mountFooter } from './shell/footer';
|
||||
import { mountHeader } from './shell/header';
|
||||
import { initTheme } from './shell/theme';
|
||||
|
||||
// Hell/Dunkel: das Skript in index.html hat es vor dem ersten Paint gesetzt; hier wird nur die Browserleiste angeglichen.
|
||||
initTheme();
|
||||
|
||||
// Sprache: das Skript in index.html hat <html lang> schon vor dem ersten Paint gesetzt; hier wird sie übernommen (ohne sie zu speichern:
|
||||
// gespeichert wird nur eine Wahl per Sprachknopf) und dem Appwrite-Client mitgegeben, damit dessen Mails (Passwort) in ihr ankommen.
|
||||
setLang(currentLang(), { persist: false });
|
||||
client.setLocale(currentLang());
|
||||
|
||||
// Alte Editor-Links (`/?karte=<id>`) zeigen jetzt auf `/gestalten?karte=<id>`; der Verlauf bekommt keinen Eintrag dazu.
|
||||
const legacyTarget = legacyRedirect(new URL(location.href));
|
||||
if (legacyTarget) history.replaceState(null, '', legacyTarget);
|
||||
@@ -27,24 +35,57 @@ const editorStore = createStore(initialEditorState());
|
||||
// … und gehört zum angemeldeten Konto: beim Abmelden oder Kontowechsel wird er geleert bzw. entkoppelt.
|
||||
bindEditorToAuth(editorStore);
|
||||
|
||||
// Spenden-Banner (ohne DONATE_URL bleibt der Platz leer) und Kopfzeile mit Navigation, Suche und Knöpfen.
|
||||
// Der Rahmen der Seite: Spenden-Banner (ohne DONATE_URL bleibt der Platz leer), Kopfzeile mit Navigation, Suche und Knöpfen,
|
||||
// Konto-Menü im Seitenkopf und Fußzeile. Alle tragen Texte der gewählten Sprache und werden bei einem Sprachwechsel neu aufgebaut.
|
||||
const bannerSlot = document.getElementById('donate-banner');
|
||||
if (bannerSlot) mountDonateBanner(bannerSlot, DONATE_URL);
|
||||
const header = document.getElementById('app-header');
|
||||
if (header) mountHeader(header, { donateUrl: DONATE_URL });
|
||||
|
||||
// Konto-Menü im Seitenkopf. Der Anmeldestatus wird einmal beim Start geholt; schlägt das fehl
|
||||
// (außer 401, das getCurrentUser selbst als „abgemeldet“ meldet), gilt der Nutzer als abgemeldet.
|
||||
const accountSlot = document.getElementById('account-slot');
|
||||
const footer = document.getElementById('app-footer');
|
||||
|
||||
let unmountHeader: (() => void) | null = null;
|
||||
let accountMenu: AccountMenu | null = null;
|
||||
// Ob der Anmeldestatus schon geholt wurde: ein später neu gebautes Konto-Menü zeigt sich dann sofort (kein Aufblitzen von „Anmelden“).
|
||||
let authKnown = false;
|
||||
|
||||
function mountShell(): void {
|
||||
if (bannerSlot) mountDonateBanner(bannerSlot, DONATE_URL);
|
||||
if (header) {
|
||||
unmountHeader?.();
|
||||
unmountHeader = mountHeader(header, { donateUrl: DONATE_URL });
|
||||
}
|
||||
if (accountSlot) {
|
||||
accountMenu?.dispose();
|
||||
accountMenu = mountAccountMenu(accountSlot);
|
||||
if (authKnown) accountMenu.ready();
|
||||
}
|
||||
if (footer) mountFooter(footer);
|
||||
}
|
||||
mountShell();
|
||||
|
||||
// Der Anmeldestatus wird einmal beim Start geholt; schlägt das fehl (außer 401, das getCurrentUser selbst als „abgemeldet“ meldet),
|
||||
// gilt der Nutzer als abgemeldet.
|
||||
if (accountSlot) {
|
||||
const menu = mountAccountMenu(accountSlot);
|
||||
getCurrentUser()
|
||||
.catch((error: unknown) => {
|
||||
console.error('Anmeldestatus konnte nicht geprüft werden', error);
|
||||
})
|
||||
.finally(() => menu.ready());
|
||||
.finally(() => {
|
||||
authKnown = true;
|
||||
accountMenu?.ready();
|
||||
});
|
||||
}
|
||||
|
||||
// Sprachwechsel ohne Neuladen: Der Appwrite-Client bekommt die neue Sprache, der Rahmen wird neu gebaut und die aktuelle Seite baut
|
||||
// sich neu auf (der Router setzt auch den Titel neu). Der Editor-Store bleibt dasselbe Objekt, ein Entwurf geht also nicht verloren.
|
||||
onLangChange((lang) => {
|
||||
client.setLocale(lang);
|
||||
// Die Kopfzeile wird ersetzt, der Sprachknopf darunter ebenso: wer ihn per Tastatur bedient hat, behält den Fokus auf dem neuen.
|
||||
const hadFocus = document.activeElement?.matches('.lang-btn') ?? false;
|
||||
mountShell();
|
||||
if (hadFocus) header?.querySelector<HTMLElement>('.lang-btn')?.focus();
|
||||
rerender();
|
||||
});
|
||||
|
||||
// Der Router setzt `document.title` je Seite (titleFor) und ruft für jede Route die passende Mount-Funktion.
|
||||
startRouter((route, root) => {
|
||||
switch (route.id) {
|
||||
|
||||
Reference in new issue
Block a user