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 -13
View File
@@ -4,10 +4,13 @@ import { IconChevronLeft, IconChevronRight, IconGallery } from '../icons.jsx';
import EmptyState from '../components/EmptyState.jsx';
import Lightbox from '../components/Lightbox.jsx';
import { SkeletonGrid } from '../components/Skeleton.jsx';
import { useT, useFmt } from '../i18n.jsx';
const dateFmt = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
const DATUM = { day: '2-digit', month: '2-digit', year: 'numeric' };
export default function Galerie() {
const { t } = useT();
const { datum } = useFmt();
const [data, setData] = useState(null);
const [page, setPage] = useState(1);
const [error, setError] = useState(false);
@@ -30,21 +33,21 @@ export default function Galerie() {
<div className="page-bg-text">GALERIE</div>
<div className="page-header-grid" aria-hidden="true" />
<div className="page-header-content">
<div className="page-tag">// Community</div>
<h1 className="page-title">Galerie</h1>
<p className="page-subtitle">Screenshots aus der Community direkt aus dem Discord.</p>
<div className="page-tag">{t('gallery.tag')}</div>
<h1 className="page-title">{t('nav.gallery')}</h1>
<p className="page-subtitle">{t('gallery.subtitle')}</p>
</div>
</section>
<section className="content">
{error && <p className="notice">Galerie konnte nicht geladen werden.</p>}
{error && <p className="notice">{t('gallery.error')}</p>}
{!error && !data && <SkeletonGrid n={9} />}
{data?.total === 0 && (
<EmptyState
Icon={IconGallery}
title="Noch keine Screenshots"
text="Alles, was im Screenshot-Kanal landet, erscheint automatisch hier — mitsamt Name und Datum."
actions={[{ to: '/devlogs', label: 'Devlogs lesen' }]}
title={t('gallery.emptyTitle')}
text={t('gallery.emptyText')}
actions={[{ to: '/devlogs', label: t('common.readDevlogs') }]}
/>
)}
@@ -54,10 +57,10 @@ export default function Galerie() {
className="shot" href={s.src} key={s.key}
onClick={(e) => { e.preventDefault(); setLightbox(i); }}
>
<img src={s.src} alt={`Screenshot von ${s.author}`} loading="lazy" />
<img src={s.src} alt={t('gallery.shotBy', s.author)} loading="lazy" />
<span className="shot-meta">
<span className="shot-author">{s.author}</span>
<span className="shot-date">{dateFmt.format(new Date(s.date))}</span>
<span className="shot-date">{datum(s.date, DATUM)}</span>
</span>
</a>
))}
@@ -67,7 +70,7 @@ export default function Galerie() {
<Lightbox
images={shots.map((s) => ({
src: s.src,
caption: `${s.author} · ${dateFmt.format(new Date(s.date))}`,
caption: `${s.author} · ${datum(s.date, DATUM)}`,
}))}
start={lightbox}
onClose={() => setLightbox(null)}
@@ -77,13 +80,13 @@ export default function Galerie() {
{pages > 1 && (
<div className="pager">
<button className="btn" disabled={page <= 1} onClick={() => setPage(page - 1)}>
<IconChevronLeft size={14} /> Neuere
<IconChevronLeft size={14} /> {t('common.newer')}
</button>
<span className="pager-info">
{String(page).padStart(2, '0')} / {String(pages).padStart(2, '0')}
</span>
<button className="btn" disabled={page >= pages} onClick={() => setPage(page + 1)}>
Ältere <IconChevronRight size={14} />
{t('common.older')} <IconChevronRight size={14} />
</button>
</div>
)}