Add i18n, routing and dark-mode support

Introduce internationalization and basic routing: add i18n initialization (src/i18n.js), translation files (public/i18n/en.json, de.json), and wire i18n into the app (import in main.jsx). Update App.jsx to use react-router (BrowserRouter, Routes) and react-i18next (useTranslation), provide simple Landing/Login/Imprint pages, a language toggle and a dark-mode toggle. Add dependencies in package.json (i18next, i18next-http-backend, react-i18next, react-router-dom) and small Tailwind style tweak (custom-variant for dark) in src/style.css.
This commit is contained in:
lobo committed 2026-03-28 09:29:39 +01:00
1 parent 7ff53c0f6c
commit c8f1d66cd2
8 files changed
+342 -63

No files matched your search

+85 -57
View File
@@ -1,67 +1,95 @@
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { useState, useEffect } from 'react';
// Placeholder components to keep the routing structure simple
function LandingPage() {
const { t } = useTranslation();
return (
<div className="grow flex items-center justify-center p-4">
<h1 className="text-3xl md:text-5xl font-bold text-slate-900 dark:text-white">
{t('welcome')}
</h1>
</div>
);
}
function LoginPage() {
const { t } = useTranslation();
return (
<div className="grow flex items-center justify-center p-4">
<h2 className="text-2xl text-slate-800 dark:text-slate-200">{t('login')}</h2>
</div>
);
}
function ImprintPage() {
const { t } = useTranslation();
return (
<div className="grow p-8">
<h2 className="text-2xl text-slate-800 dark:text-slate-200">{t('imprint')}</h2>
</div>
);
}
function App() {
// Array to generate the placeholder game cards
const mockGames = [1, 2, 3];
const { t, i18n } = useTranslation();
const [isDarkMode, setIsDarkMode] = useState(true);
return (
<div className="min-h-screen bg-slate-50 flex flex-col">
<header className="border-b shadow-sm bg-white p-4 flex justify-between items-center">
<div className="font-bold text-blue-600 flex items-center">
<i className="mdi mdi-controller mr-2 text-xl"></i>
To Be Played
</div>
<div>
<button className="mr-4">Dashboard</button>
<button className="bg-blue-600 text-white px-4 py-2 rounded">Login</button>
</div>
</header>
// Apply dark mode class to the root HTML element for Tailwind
useEffect(() => {
if (isDarkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [isDarkMode]);
<main className="grow container mx-auto py-20 px-4">
<section className="text-center mb-16">
<h1 className="text-4xl md:text-5xl font-extrabold text-slate-900 mb-4">
Dein Game-Backlog unter Kontrolle.
</h1>
<p className="text-lg text-slate-600 max-w-2xl mx-auto">
Willkommen bei deiner neuen Applikation. Hier werden bald deine Spiele aus dem
<code className="bg-slate-200 px-1 rounded text-blue-600 ml-1">game-service</code> gelistet.
</p>
</section>
const toggleLanguage = () => {
if (i18n.language === 'de') {
i18n.changeLanguage('en').then(r => console.log("Changed language to en"));
} else {
i18n.changeLanguage('de').then(r => console.log("Changed language to en"));
}
};
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{mockGames.map((n) => {
return (
<div key={n} className="border border-slate-200 rounded-xl bg-white overflow-hidden">
<div className="h-48 bg-slate-100">
<img
src={`https://picsum.photos/500/300?random=${n}`}
alt={`Beispiel Spiel ${n}`}
className="w-full h-full object-cover"
/>
return (
<BrowserRouter>
<div className="min-h-screen flex flex-col bg-slate-50 dark:bg-slate-950 transition-colors duration-300">
<header className="p-4 flex justify-between items-center border-b border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900">
<Link to="/" className="font-bold text-blue-600 dark:text-blue-400 text-xl flex items-center">
<i className="mdi mdi-controller mr-2"></i> To Be Played
</Link>
<div className="flex gap-4 items-center">
<button onClick={toggleLanguage} className="text-sm font-medium dark:text-slate-300">
{i18n.language.toUpperCase()}
</button>
<button onClick={() => { setIsDarkMode(!isDarkMode); }} className="text-xl dark:text-slate-300">
<i className={isDarkMode ? "mdi mdi-weather-sunny" : "mdi mdi-weather-night"}></i>
</button>
<Link to="/login" className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors">
{t('login')}
</Link>
</div>
<div className="p-4">
<h3 className="font-bold text-lg">Beispiel Spiel {n}</h3>
<p className="text-sm text-slate-500 mb-2">Status: In Warteschlange</p>
<p className="text-slate-500 text-sm">
Dies ist ein Platzhalter für ein Spiel in deiner Liste. Später kommen hier echte Daten per API.
</p>
</div>
<div className="border-t p-4">
<button className="w-full bg-blue-100 text-blue-700 py-2 rounded font-medium hover:bg-blue-200">
Details ansehen
</button>
</div>
</div>
);
})}
</div>
</main>
</header>
<footer className="bg-white border-t py-4 text-center text-slate-400 text-sm">
&copy; 2026 To Be Played — Entwickelt mit React & Tailwind v4
</footer>
</div>
);
<main className="flex-grow flex flex-col">
<Routes>
<Route path="/" element={<LandingPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/impressum" element={<ImprintPage />} />
</Routes>
</main>
<footer className="p-4 text-center border-t border-slate-200 dark:border-slate-800 text-slate-500 text-sm bg-white dark:bg-slate-900">
<Link to="/impressum" className="hover:text-blue-500 transition-colors">
{t('imprint')}
</Link>
</footer>
</div>
</BrowserRouter>
);
}
export default App;