- 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>
104 lines
4.6 KiB
TypeScript
104 lines
4.6 KiB
TypeScript
import '@fontsource-variable/nunito-sans';
|
|
import './styles.css';
|
|
import { mountAccountMenu, type AccountMenu } from './account/account-menu';
|
|
import { client } from './account/appwrite';
|
|
import { getCurrentUser } from './account/auth';
|
|
import { DONATE_URL } from './config';
|
|
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';
|
|
import { mountResetPage } from './pages/reset-page';
|
|
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);
|
|
|
|
// Der Editor-Zustand lebt außerhalb der Seiten und überlebt Seitenwechsel.
|
|
const editorStore = createStore(initialEditorState());
|
|
// … und gehört zum angemeldeten Konto: beim Abmelden oder Kontowechsel wird er geleert bzw. entkoppelt.
|
|
bindEditorToAuth(editorStore);
|
|
|
|
// 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');
|
|
const header = document.getElementById('app-header');
|
|
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) {
|
|
getCurrentUser()
|
|
.catch((error: unknown) => {
|
|
console.error('Anmeldestatus konnte nicht geprüft werden', error);
|
|
})
|
|
.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) {
|
|
case 'gestalten':
|
|
return mountEditor(root, editorStore);
|
|
case 'cards':
|
|
return mountCardsPage(root, editorStore);
|
|
case 'community':
|
|
return mountCommunityPage(root, route.params.id ?? '');
|
|
case 'reset':
|
|
return mountResetPage(root);
|
|
case 'home':
|
|
return mountHomePage(root);
|
|
}
|
|
});
|