Englisch als zweite Sprache fuer die oeffentlichen Seiten
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

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>
This commit is contained in:
2026-07-31 22:41:32 +02:00
co-authored by Claude Fable 5
parent 570e45fe60
commit 5360c8229f
19 changed files with 1084 additions and 329 deletions
+34 -42
View File
@@ -3,24 +3,26 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { apiGet } from '../api.js';
import { useT } from '../i18n.jsx';
import {
IconDevlog, IconLevel, IconRoles, IconServer, IconSupport, IconGiveaway,
IconBirthday, IconShield, IconMagic, IconKey, IconArrowRight, IconBot,
} from '../icons.jsx';
const HIGHLIGHTS = [
[IconDevlog, 'Devlogs & Feeds', 'Entwicklungs-Berichte landen als gebrandetes Embed im Discord und werden gleichzeitig durchsuchbar archiviert. Commits und Releases aus Gitea genauso.'],
[IconServer, 'Server-Monitor', 'Über 300 Spiele per gamedig, dazu FiveM und einfache Erreichbarkeits-Checks. Ein Live-Embed pro Server, Alarm bei Ausfall, Spielerzahl in der Bot-Präsenz.'],
[IconLevel, 'Level & Aktivität', 'XP fürs Mitreden mit Rollen-Belohnungen, öffentliche Bestenliste und Aktivitäts-Statistik des Servers.'],
[IconRoles, 'Rollen-Menüs', 'Selbstbedienung per Knopfdruck — im Discord und im Browser. Farben pro Knopf, wahlweise exklusiv.'],
[IconSupport, 'Support & Kontakt', 'Tickets als private Threads mit Gesprächsprotokoll, dazu Modmail: eine Direktnachricht an den Bot landet beim Team.'],
[IconShield, 'Moderation', 'Verwarnungen mit Verlauf, Auszeiten, Aufräumen — alles im Mod-Log, dazu Protokoll für Beitritte, Namens- und Rollenwechsel.'],
[IconGiveaway, 'Verlosungen', 'Teilnahme per Knopf, automatische Ziehung, Neuauslosung wenn sich niemand meldet.'],
[IconBirthday, 'Geburtstage', 'Wer mag, trägt seinen Tag ein — der Bot gratuliert morgens und vergibt eine Rolle für den Tag.'],
[IconMagic, 'Willkommens-Karten', 'Neue Mitglieder bekommen ein gerendertes Bild mit Avatar und Mitglieds-Nummer.'],
[IconDevlog, 'bot.hl.devlogs'],
[IconServer, 'bot.hl.monitor'],
[IconLevel, 'bot.hl.level'],
[IconRoles, 'bot.hl.roles'],
[IconSupport, 'bot.hl.support'],
[IconShield, 'bot.hl.mod'],
[IconGiveaway, 'bot.hl.giveaway'],
[IconBirthday, 'bot.hl.birthday'],
[IconMagic, 'bot.hl.welcome'],
];
export default function BotLanding() {
const { t } = useT();
const [status, setStatus] = useState(null);
const [hub, setHub] = useState(null);
const [featureCount, setFeatureCount] = useState(null);
@@ -40,21 +42,17 @@ export default function BotLanding() {
<div className="page-bg-text">BOT</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// Selbst gebaut, keine Paywall</div>
<div className="page-tag">{t('bot.tag')}</div>
<h1 className="page-title">
Ein Discord-Bot,<br />der alles macht
{t('bot.title1')}<br />{t('bot.title2')}
</h1>
<p className="page-subtitle">
Devlogs, Level, Moderation, Server-Monitoring, Verlosungen, Tickets
was sonst drei Bots mit Abo-Modell erledigen, läuft hier in einem,
mit eigenem Webinterface.
</p>
<p className="page-subtitle">{t('bot.subtitle')}</p>
<div className="home-cta">
<Link className="btn btn-save with-icon" to="/features">
Alle Funktionen <IconArrowRight size={15} />
{t('bot.allFeatures')} <IconArrowRight size={15} />
</Link>
<Link className="btn btn-ghost with-icon" to="/commands">
<IconBot /> Befehle ansehen
<IconBot /> {t('bot.viewCommands')}
</Link>
</div>
</div>
@@ -65,62 +63,56 @@ export default function BotLanding() {
{featureCount && (
<div className="srv-sum-item">
<span className="srv-sum-n">{featureCount}</span>
<span className="srv-sum-label">Funktionen, einzeln schaltbar</span>
<span className="srv-sum-label">{t('bot.statFeatures')}</span>
</div>
)}
<div className="srv-sum-item">
<span className="srv-sum-n">16</span>
<span className="srv-sum-label">Slash-Befehle</span>
<span className="srv-sum-label">{t('bot.statCommands')}</span>
</div>
<div className="srv-sum-item">
<span className="srv-sum-n">300<span className="srv-sum-of">+</span></span>
<span className="srv-sum-label">Spiele im Monitor</span>
<span className="srv-sum-label">{t('bot.statGames')}</span>
</div>
<div className="srv-sum-item">
<span className="srv-sum-n">0<span className="srv-sum-of"> </span></span>
<span className="srv-sum-label">Kosten, keine Premium-Stufen</span>
<span className="srv-sum-label">{t('bot.statCost')}</span>
</div>
</div>
<h2 className="settings-title" style={{ marginTop: '2.5rem' }}>// Was er kann</h2>
<h2 className="settings-title" style={{ marginTop: '2.5rem' }}>{t('bot.whatItDoes')}</h2>
<div className="home-tiles">
{HIGHLIGHTS.map(([Icon, title, text]) => (
<div className="card home-tile" key={title}>
{HIGHLIGHTS.map(([Icon, key]) => (
<div className="card home-tile" key={key}>
<span className="home-tile-icon"><Icon size={24} /></span>
<span className="home-tile-title">{title}</span>
<span className="home-tile-desc">{text}</span>
<span className="home-tile-title">{t(key)}</span>
<span className="home-tile-desc">{t(`${key}.text`)}</span>
</div>
))}
</div>
<div className="roadmap-group" style={{ marginTop: '2.5rem' }}>
<h2 className="settings-title">// Für Entwickler</h2>
<h2 className="settings-title">{t('bot.forDevs')}</h2>
<div className="home-tiles">
<div className="card home-tile">
<span className="home-tile-icon"><IconKey size={24} /></span>
<span className="home-tile-title">API &amp; Single Sign-On</span>
<span className="home-tile-desc">
Eigene Skripte posten über die API v1 mit Bearer-Schlüsseln und Bereichs-Rechten.
Andere Dienste nutzen den Discord-Login des Bots mit und bekommen die Rollen
gleich mitgeliefert.
</span>
<span className="home-tile-title">{t('bot.dev.api')}</span>
<span className="home-tile-desc">{t('bot.dev.api.text')}</span>
</div>
<div className="card home-tile">
<span className="home-tile-icon"><IconServer size={24} /></span>
<span className="home-tile-title">Läuft überall</span>
<span className="home-tile-desc">
Node und SQLite in einem Container, ein Volume für die Daten.
Nächtliche Sicherungen inklusive von der Datenbank und allen Repositories.
</span>
<span className="home-tile-title">{t('bot.dev.host')}</span>
<span className="home-tile-desc">{t('bot.dev.host.text')}</span>
</div>
</div>
</div>
{hub && (
<p className="notice" style={{ marginTop: '2rem' }}>
Der Bot läuft für die D4RKST3R-Community
{status?.members ? ` mit ${status.members} Mitgliedern` : ''}
{' '}<a href={hub} style={{ textDecoration: 'underline' }}>zum Community-Hub</a>.
{status?.members
? t('bot.runsForWith', status.members)
: t('bot.runsFor')}
{' '}<a href={hub} style={{ textDecoration: 'underline' }}>{t('bot.toHub')}</a>.
</p>
)}
</section>