Files
d4rkbot/frontend/src/BotApp.jsx
T
D4rkst3randClaude Fable 5 5360c8229f
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
Englisch als zweite Sprache fuer die oeffentlichen Seiten
Hub und Bot-Produktseite lassen sich jetzt auf Englisch umstellen. Die Wahl
haengt am Browser (Deutsch nur, wenn der Browser es will) und bleibt im
localStorage stehen.

- src/i18n.jsx: Context mit t(), tOr(), Datums- und Zahlenformat der Sprache.
  Keine Bibliothek, die Woerterbuecher werden mitgebaut — kein Nachlade-Blitzer.
- Platzhalter im %s-Stil, dieselbe Schreibweise wie in den Bot-Texten.
- Umschalter in der Navigation, zusaetzlich im Schubladen-Menue: .auth ist auf
  schmalen Schirmen ausgeblendet, sonst waere der Wechsel dort unerreichbar.
- Datums- und Zahlenformat folgen mit (31.07.2026 / 31/07/2026, 45.231 / 45,231).
- Die Funktionsliste kommt weiter deutsch vom Server; englische Modulnamen
  liegen unter feat.<id> im Woerterbuch. Fehlt einer, bleibt der Servertext
  stehen — ein neues Modul verschwindet nie von der Seite.

Das Config-Panel und die Discord-Texte bleiben deutsch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 22:41:32 +02:00

116 lines
5.3 KiB
React

// Rahmen der Bot-Produktseite — eigene Navigation, orange Leitfarbe.
// Das Dashboard ist dieselbe Config-Seite wie bisher, nur unter /dashboard.
import { useEffect, useState } from 'react';
import { Routes, Route, NavLink, Navigate, useLocation } from 'react-router-dom';
import { apiGet } from './api.js';
import BotLanding from './pages/BotLanding.jsx';
import BotFeatures from './pages/BotFeatures.jsx';
import BotCommands from './pages/BotCommands.jsx';
import Settings from './pages/Settings.jsx';
import Impressum from './pages/Impressum.jsx';
import Datenschutz from './pages/Datenschutz.jsx';
import { IconMenu, IconX } from './icons.jsx';
import { useT, LangSwitch } from './i18n.jsx';
export default function BotApp() {
const [me, setMe] = useState({ user: null, admin: false, scopes: [], loading: true });
const [scrolled, setScrolled] = useState(false);
const [drawer, setDrawer] = useState(false);
const [hub, setHub] = useState(null);
const location = useLocation();
const { t } = useT();
useEffect(() => { setDrawer(false); }, [location.pathname]);
useEffect(() => {
apiGet('/api/me')
.then((data) => setMe({ scopes: [], ...data, loading: false }))
.catch(() => setMe({ user: null, admin: false, scopes: [], loading: false }));
apiGet('/api/legal').then((d) => setHub(d.split ? d.hubUrl : null)).catch(() => {});
}, []);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 24);
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const canDashboard = me.admin || me.scopes?.length > 0;
return (
<div className="app site-bot">
<header className={`nav ${scrolled ? 'scrolled' : ''}`}>
<NavLink to="/" className="nav-logo">
D4RKBOT<span>.</span>
</NavLink>
<nav className="nav-links">
<NavLink to="/features">{t('nav.features')}</NavLink>
<NavLink to="/commands">{t('nav.commands')}</NavLink>
{canDashboard && <NavLink to="/dashboard">{t('nav.dashboard')}</NavLink>}
{hub && <a href={hub}>{t('nav.community')}</a>}
</nav>
<div className="auth">
<LangSwitch />
{me.loading ? null : me.user ? (
<span className="user-chip">
{me.user.avatar && <img className="avatar" src={me.user.avatar} alt="" />}
<span className="username">{me.user.username}</span>
<a className="logout" href="/auth/logout" title={t('nav.signOut')}>
<IconX size={13} />
</a>
</span>
) : (
<a className="btn-login" href="/auth/login">{t('nav.signIn')}</a>
)}
</div>
<button className="burger" onClick={() => setDrawer((d) => !d)} aria-label={t('nav.menu')}>
{drawer ? <IconX size={20} /> : <IconMenu size={20} />}
</button>
</header>
{drawer && (
<nav className="drawer" onClick={() => setDrawer(false)}>
<NavLink to="/features">{t('nav.features')}</NavLink>
<NavLink to="/commands">{t('nav.commands')}</NavLink>
{canDashboard && <NavLink to="/dashboard">{t('nav.dashboard')}</NavLink>}
{hub && <a href={hub}>{t('nav.community')}</a>}
{me.user
? <a href="/auth/logout">{t('nav.signOut')}</a>
: <a href="/auth/login">{t('nav.signIn')}</a>}
{/* Auf schmalen Schirmen ist .auth ausgeblendet — der
Sprachwechsel muss trotzdem erreichbar bleiben. */}
<div className="drawer-lang" onClick={(e) => e.stopPropagation()}>
<LangSwitch />
</div>
</nav>
)}
<main className="main">
<Routes>
<Route path="/" element={<BotLanding />} />
<Route path="/features" element={<BotFeatures />} />
<Route path="/commands" element={<BotCommands />} />
<Route path="/dashboard" element={<Settings me={me} />} />
<Route path="/impressum" element={<Impressum />} />
<Route path="/datenschutz" element={<Datenschutz />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</main>
<footer className="footer">
<span className="footer-logo">D4RKBOT<span>.</span></span>
<span className="footer-sep">//</span>
<a href="https://git.d4rkst3r.de/D4rkst3r/d4rkbot" target="_blank" rel="noreferrer">
{t('footer.source')}
</a>
<span className="footer-sep">//</span>
<NavLink to="/impressum">{t('footer.imprint')}</NavLink>
<NavLink to="/datenschutz">{t('footer.privacy')}</NavLink>
</footer>
</div>
);
}