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>
This commit is contained in:
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user