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
+16 -11
View File
@@ -3,6 +3,7 @@ import { apiGet } from '../api.js';
import { SkeletonRows } from '../components/Skeleton.jsx';
import { IconTrophy, IconLevel } from '../icons.jsx';
import EmptyState from '../components/EmptyState.jsx';
import { useT, useFmt } from '../i18n.jsx';
/** Muss der Formel in src/bot/levels.js entsprechen */
function xpForLevel(level) {
@@ -16,6 +17,8 @@ const MEDAL_COLORS = ['#f5c518', '#c0c4cc', '#c07a3e'];
/** Nachrichten/Tag der letzten 30 Tage als Balken */
function ActivityChart({ stats }) {
const { t } = useT();
const { zahl } = useFmt();
const byDay = new Map(stats.days.map((d) => [d.day, d]));
const days = [];
for (let i = 29; i >= 0; i--) {
@@ -26,14 +29,14 @@ function ActivityChart({ stats }) {
const total = days.reduce((s, d) => s + d.messages, 0);
return (
<div className="roadmap-group" style={{ marginTop: '2.5rem' }}>
<h2 className="settings-title">// Server-Aktivität — {total.toLocaleString('de-DE')} Nachrichten in 30 Tagen · {stats.members} Member</h2>
<h2 className="settings-title">{t('level.activity', zahl(total), stats.members)}</h2>
<div className="act-chart">
{days.map((d) => (
<span
key={d.key}
className="act-bar"
style={{ height: `${Math.max(4, (d.messages / max) * 100)}%` }}
title={`${d.key}: ${d.messages} Nachrichten · +${d.joins}/${d.leaves} Member`}
title={t('level.barTitle', d.key, d.messages, d.joins, d.leaves)}
/>
))}
</div>
@@ -42,6 +45,8 @@ function ActivityChart({ stats }) {
}
export default function Level() {
const { t } = useT();
const { zahl } = useFmt();
const [rows, setRows] = useState(null);
const [stats, setStats] = useState(null);
const [error, setError] = useState(false);
@@ -58,23 +63,23 @@ export default function Level() {
<div className="page-bg-text">LEVEL</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// Bestenliste</div>
<h1 className="page-title">Level</h1>
<div className="page-tag">{t('level.tag')}</div>
<h1 className="page-title">{t('nav.level')}</h1>
<p className="page-subtitle">
XP gibt's fürs Mitreden im Discord — hier stehen die Aktivsten. <code>/rank</code> zeigt deinen Platz.
{t('level.subtitle').split('%s')[0]}<code>/rank</code>{t('level.subtitle').split('%s')[1]}
</p>
</div>
</section>
<section className="content">
{error && <p className="notice">Bestenliste konnte nicht geladen werden.</p>}
{error && <p className="notice">{t('level.error')}</p>}
{!error && !rows && <SkeletonRows n={10} />}
{rows?.length === 0 && (
<EmptyState
Icon={IconLevel}
title="Noch keine XP vergeben"
text="XP gibt es fürs Mitreden im Discord. Sobald geschrieben wird, füllt sich die Bestenliste von selbst."
actions={[{ to: '/devlogs', label: 'Devlogs lesen' }]}
title={t('level.emptyTitle')}
text={t('level.emptyText')}
actions={[{ to: '/devlogs', label: t('common.readDevlogs') }]}
/>
)}
@@ -99,8 +104,8 @@ export default function Level() {
<div className="lvl-progress">
<div className="progress"><div className="progress-bar" style={{ width: `${pct}%` }} /></div>
</div>
<span className="lvl-level">Lv. {r.level}</span>
<span className="lvl-xp">{r.xp.toLocaleString('de-DE')} XP</span>
<span className="lvl-level">{t('level.lv', r.level)}</span>
<span className="lvl-xp">{zahl(r.xp)} XP</span>
</div>
);
})}