Public Access
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:
1 parent
7ff53c0f6c
commit
c8f1d66cd2
8 files changed
+342
-63
No files matched your search
+85
-57
@@ -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">
|
||||
© 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;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import i18n from 'i18next';
|
||||
import {initReactI18next} from 'react-i18next';
|
||||
import HttpBackend from 'i18next-http-backend';
|
||||
|
||||
|
||||
i18n.use(HttpBackend)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
lng: 'de',
|
||||
fallbackLng: 'en',
|
||||
backend: {
|
||||
loadPath: '/i18n/{{lng}}.json',
|
||||
},
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App.jsx';
|
||||
import './i18n';
|
||||
import './style.css';
|
||||
import '@mdi/font/css/materialdesignicons.css';
|
||||
|
||||
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
Reference in new issue
Block a user