- Das Spiel wurde noch nicht gestartet oder es sind keine Spieler-Chips vorhanden.
+ {language === "en" ? "The game has not started yet or no player chips exist." : "Das Spiel wurde noch nicht gestartet oder es sind keine Spieler-Chips vorhanden."}
)}
- Tipp: Wenn du wieder auf den Notiz-Button klickst, geht’s von s zurück auf —.
+ {language === "en" ? <>Tip: Tap the note button again to go from s back to —.> : <>Tipp: Wenn du wieder auf den Notiz-Button klickst, geht’s von s zurück auf —.>}
diff --git a/frontend/src/components/DesignModal.jsx b/frontend/src/components/DesignModal.jsx
index 84a2ca9..fd0dce1 100644
--- a/frontend/src/components/DesignModal.jsx
+++ b/frontend/src/components/DesignModal.jsx
@@ -2,8 +2,10 @@ import React from "react";
import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
import { THEMES } from "../styles/themes";
+import { useLanguage } from "../i18n";
export default function DesignModal({ open, onClose, themeKey, onSelect }) {
+ const { language, t } = useLanguage();
if (!open) return null;
const themeEntries = Object.entries(THEMES);
@@ -12,14 +14,14 @@ export default function DesignModal({ open, onClose, themeKey, onSelect }) {
e.stopPropagation()}>
-
Design ändern
-
+
{t("changeDesign")}
+
✕
- Wähle dein Theme:
+ {language === "en" ? "Choose your theme:" : "Wähle dein Theme:"}
diff --git a/frontend/src/components/GamePickerCard.jsx b/frontend/src/components/GamePickerCard.jsx
index 7cb06f4..a78662b 100644
--- a/frontend/src/components/GamePickerCard.jsx
+++ b/frontend/src/components/GamePickerCard.jsx
@@ -1,6 +1,7 @@
import React, { useState } from "react";
import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
+import { useLanguage } from "../i18n";
export default function GamePickerCard({
games,
@@ -17,6 +18,7 @@ export default function GamePickerCard({
chipCount = 0,
onStartGame,
}) {
+ const { language, t } = useLanguage();
const cur = games.find((x) => x.id === gameId);
const [starting, setStarting] = useState(false);
const [startError, setStartError] = useState("");
@@ -52,14 +54,14 @@ export default function GamePickerCard({
const handleStartGame = async () => {
if (!onStartGame || starting || members.length < 2) return;
- if (!window.confirm("Spiel jetzt starten? Danach können keine weiteren Spieler beitreten.")) return;
+ if (!window.confirm(language === "en" ? "Start the game now? No more players can join afterwards." : "Spiel jetzt starten? Danach können keine weiteren Spieler beitreten.")) return;
setStarting(true);
setStartError("");
try {
await onStartGame();
} catch (e) {
- setStartError(e?.message || "Das Spiel konnte nicht gestartet werden.");
+ setStartError(e?.message || (language === "en" ? "The game could not be started." : "Das Spiel konnte nicht gestartet werden."));
} finally {
setStarting(false);
}
@@ -68,7 +70,7 @@ export default function GamePickerCard({
return (
-
Spiel
+
{t("game")}
@@ -122,15 +124,15 @@ export default function GamePickerCard({
- Lobby
+ {t("lobby")}
- {isHost ? "Du bist der Host dieses Spiels." : "Du bist der Lobby beigetreten."}
+ {isHost ? (language === "en" ? "You are the host of this game." : "Du bist der Host dieses Spiels.") : (language === "en" ? "You joined the lobby." : "Du bist der Lobby beigetreten.")}
- Ein neues Spiel startet zunächst als Lobby. Teile den angezeigten Spiel-Code mit den
- anderen Spielern. Sie können dem Spiel beitreten, solange es noch nicht gestartet wurde.
+ {en ? <>A new game starts as a lobby. Share the displayed game code with the other players. They can join until the host starts the game.> : <>Ein neues Spiel startet zunächst als Lobby. Teile den angezeigten Spiel-Code mit den anderen Spielern. Sie können dem Spiel beitreten, solange es noch nicht gestartet wurde.>}
👑
- Der Host sieht alle beigetretenen Spieler und startet das Spiel.
+ {en ? <>The host sees all joined players and starts the game.> : <>Der Host sieht alle beigetretenen Spieler und startet das Spiel.>}
▶
- Der Start ist ab zwei Spielern möglich. Danach können keine weiteren Spieler
- beitreten.
+ {en ? <>The game can start with two players. No additional players can join afterwards.> : <>Der Start ist ab zwei Spielern möglich. Danach können keine weiteren Spieler beitreten.>}
SNE
- Beim Start werden die Spieler-Chips automatisch erstellt. Beispiel: Sascha Nesterovic
- wird zu SNE.
+ {en ? <>Player chips are created automatically when the game starts. Example: Sascha Nesterovic becomes SNE.> : <>Beim Start werden die Spieler-Chips automatisch erstellt. Beispiel: Sascha Nesterovic wird zu SNE.>}
-
2) Spiel auswählen
+
2) {en ? "Selecting a game" : "Spiel auswählen"}
- Mit dem Dropdown im Bereich Spiel wechselst du zwischen deinen Spielen. Über
- New Game kannst du ein neues Spiel erstellen oder einem bestehenden Spiel beitreten.
+ {en ? <>Use the Game dropdown to switch between your games. With New Game you can create or join a game.> : <>Mit dem Dropdown im Bereich Spiel wechselst du zwischen deinen Spielen. Über New Game kannst du ein neues Spiel erstellen oder einem bestehenden Spiel beitreten.>}
- New Game = neues Spiel erstellen oder per Code beitreten
+ New Game = {en ? "create a game or join with a code" : "neues Spiel erstellen oder per Code beitreten"}
-
3) Namen antippen – Status
+
3) {en ? "Tap a name – status" : "Namen antippen – Status"}
- Tippe während des laufenden Spiels auf den Namen eines Eintrags. Die Statusfolge ist:
+ {en ? "During the game, tap an entry name. The status cycle is:" : "Tippe während des laufenden Spiels auf den Namen eines Eintrags. Die Statusfolge ist:"}
- Der Button rechts in jeder Zeile rotiert durch deine persönlichen Notizen:
+ {en ? "The button on the right of each row cycles through your personal notes:" : "Der Button rechts in jeder Zeile rotiert durch deine persönlichen Notizen:"}
i
-
i = Ich habe diese Karte
+
i = {en ? "I have this card" : "Ich habe diese Karte"}
m
-
m = Karte aus dem mittleren Deck
+
m = {en ? "card from the middle deck" : "Karte aus dem mittleren Deck"}
s
-
s = Ein anderer Spieler hat die Karte; danach wählst du den passenden Chip
+
s = {en ? "another player has the card; then choose the matching chip" : "Ein anderer Spieler hat die Karte; danach wählst du den passenden Chip"}
SNE
-
s.SNE = Sascha Nesterovic besitzt die Karte
+
s.SNE = {en ? "Sascha Nesterovic has the card" : "Sascha Nesterovic besitzt die Karte"}
- Nur der Host kann den Sieger festlegen. Sobald ein Sieger gespeichert wurde, gilt das Spiel
- als beendet:
+ {en ? "Only the host can select the winner. Once a winner is saved, the game is finished:" : "Nur der Host kann den Sieger festlegen. Sobald ein Sieger gespeichert wurde, gilt das Spiel als beendet:"}
🔒
-
Alle Notizzettel werden schreibgeschützt.
+
{en ? <>All note sheets become read-only.> : <>Alle Notizzettel werden schreibgeschützt.>}
🏆
-
Der Sieger wird für alle Spieler angezeigt.
+
{en ? "The winner is shown to all players." : "Der Sieger wird für alle Spieler angezeigt."}
-
6) User-Menü
+
6) {en ? "User menu" : "User-Menü"}
- Oben rechts findest du abhängig von deiner Rolle das User- oder Admin-Menü.
+ {en ? <>At the top right, you will find the User or Admin menu depending on your role.> : <>Oben rechts findest du abhängig von deiner Rolle das User- oder Admin-Menü.>}
📊
-
Statistik = persönliche Spiele- und Siegstatistik
+
{t("statistics")} = {en ? "personal game and win statistics" : "persönliche Spiele- und Siegstatistik"}
🎨
-
Design ändern = Theme auswählen
+
{t("changeDesign")} = {en ? "choose a theme" : "Theme auswählen"}
Admin Dashboard = User anlegen, bearbeiten, Rollen ändern, Passwörter setzen und Konten deaktivieren
+
{t("adminDashboard")} = {en ? "create and edit users, change roles, set passwords and disable accounts" : "User anlegen, bearbeiten, Rollen ändern, Passwörter setzen und Konten deaktivieren"}
✉
-
Admin Settings = SMTP konfigurieren und Einladungen ohne vorgegebenes Passwort versenden
+
{t("adminSettings")} = {en ? "configure SMTP and send invitations without a preset password" : "SMTP konfigurieren und Einladungen ohne vorgegebenes Passwort versenden"}
- Deine Notizen bleiben privat. Jeder Spieler sieht nur seinen eigenen Notizzettel.
+ {en ? "Your notes remain private. Each player sees only their own note sheet." : "Deine Notizen bleiben privat. Jeder Spieler sieht nur seinen eigenen Notizzettel."}
diff --git a/frontend/src/components/InvitePage.jsx b/frontend/src/components/InvitePage.jsx
index 518b0ff..ada7a8a 100644
--- a/frontend/src/components/InvitePage.jsx
+++ b/frontend/src/components/InvitePage.jsx
@@ -2,8 +2,10 @@ import React, { useEffect, useState } from "react";
import { api } from "../api/client";
import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
+import { useLanguage } from "../i18n";
export default function InvitePage({ token }) {
+ const { language, t } = useLanguage();
const [info, setInfo] = useState(null);
const [password, setPassword] = useState("");
const [confirm, setConfirm] = useState("");
@@ -13,8 +15,8 @@ export default function InvitePage({ token }) {
useEffect(() => { api(`/auth/invite/${encodeURIComponent(token)}`).then(setInfo).catch((e) => setMessage("❌ " + (e?.message || "Diese Einladung ist nicht mehr gültig."))); }, [token]);
const submit = async (event) => {
event.preventDefault(); setMessage("");
- if (password.length < 8) return setMessage("❌ Das Passwort muss mindestens 8 Zeichen haben.");
- if (password !== confirm) return setMessage("❌ Die Passwörter stimmen nicht überein.");
+ if (password.length < 8) return setMessage(`❌ ${language === "en" ? "Password must be at least 8 characters." : "Das Passwort muss mindestens 8 Zeichen haben."}`);
+ if (password !== confirm) return setMessage(`❌ ${language === "en" ? "Passwords do not match." : "Die Passwörter stimmen nicht überein."}`);
setSaving(true);
try { await api(`/auth/invite/${encodeURIComponent(token)}`, { method: "POST", body: JSON.stringify({ password }) }); setMessage("✅ Passwort gesetzt. Du wirst weitergeleitet …"); setTimeout(() => { window.location.href = "/"; }, 700); }
catch (e) { setMessage("❌ " + (e?.message || "Einladung konnte nicht aktiviert werden.")); setSaving(false); }
@@ -25,14 +27,14 @@ export default function InvitePage({ token }) {
diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx
index 070eb33..9897a16 100644
--- a/frontend/src/components/LoginPage.jsx
+++ b/frontend/src/components/LoginPage.jsx
@@ -1,5 +1,6 @@
import React from "react";
import { styles } from "../styles/styles";
+import { useLanguage } from "../i18n";
export default function LoginPage({
loginEmail,
@@ -22,6 +23,7 @@ export default function LoginPage({
setupSaving,
doSetup,
}) {
+ const { language, t } = useLanguage();
const isSetup = setupRequired === true;
return (
@@ -37,10 +39,10 @@ export default function LoginPage({
{setupRequired === null
- ? "Initialisiere Anwendung …"
+ ? (language === "en" ? "Initializing application …" : "Initialisiere Anwendung …")
: isSetup
- ? "Richte den ersten Administrator ein"
- : "Melde dich an, um dein Cluedo-Magie-Sheet zu öffnen"}
+ ? (language === "en" ? "Set up the first administrator" : "Richte den ersten Administrator ein")
+ : (language === "en" ? "Log in to open your Cluedo sheet" : "Melde dich an, um dein Cluedo-Magie-Sheet zu öffnen")}
- Your notes remain private – every player only sees their own sheet.
+ {language === "en" ? "Your notes remain private – every player only sees their own sheet." : "Deine Notizen bleiben privat – jeder Spieler sieht nur seinen eigenen Notizzettel."}
- Das Spiel läuft noch. Hier ist der Join-Code:
+ {language === "en" ? <>The game is in progress. Here is the join code:> : <>Das Spiel läuft noch. Hier ist der Join-Code:>}
- Sobald ein Sieger gesetzt wurde, kannst du hier ein neues Spiel erstellen oder beitreten.
+ {language === "en" ? "Once a winner is selected, you can create or join a new game here." : "Sobald ein Sieger gesetzt wurde, kannst du hier ein neues Spiel erstellen oder beitreten."}
- Willst du ein Spiel erstellen oder einem Spiel beitreten?
+ {language === "en" ? <>Would you like to create a game or join one?> : <>Willst du ein Spiel erstellen oder einem Spiel beitreten?>}
>
@@ -214,7 +216,7 @@ export default function NewGameModal({
{mode === "create" && created && (
<>
- Dein Spiel wurde erstellt. Dieser Code bleibt auch bei „Alte Spiele“ sichtbar:
+ {language === "en" ? "Your game was created. This code will remain visible in your old games:" : "Dein Spiel wurde erstellt. Dieser Code bleibt auch bei „Alte Spiele“ sichtbar:"}
);
diff --git a/frontend/src/components/WinnerCard.jsx b/frontend/src/components/WinnerCard.jsx
index b76f588..6cd7229 100644
--- a/frontend/src/components/WinnerCard.jsx
+++ b/frontend/src/components/WinnerCard.jsx
@@ -1,6 +1,7 @@
import React from "react";
import { styles } from "../styles/styles";
import { stylesTokens } from "../styles/theme";
+import { useLanguage } from "../i18n";
export default function WinnerCard({
isHost,
@@ -9,12 +10,13 @@ export default function WinnerCard({
setWinnerUserId,
onSave,
}) {
+ const { language, t } = useLanguage();
if (!isHost) return null;
return (
-
Sieger
+
{t("winner")}
- Speichern
+ {t("save")}
- Nur der Host (Spiel-Ersteller) kann den Sieger setzen.
+ {language === "en" ? "Only the host (game creator) can select the winner." : "Nur der Host (Spiel-Ersteller) kann den Sieger setzen."}
diff --git a/frontend/src/components/WinnerCelebration.jsx b/frontend/src/components/WinnerCelebration.jsx
index ec2244c..f02c368 100644
--- a/frontend/src/components/WinnerCelebration.jsx
+++ b/frontend/src/components/WinnerCelebration.jsx
@@ -2,8 +2,10 @@ import React, { useEffect } from "react";
import { createPortal } from "react-dom";
import confetti from "canvas-confetti";
import { stylesTokens } from "../styles/theme";
+import { useLanguage } from "../i18n";
export default function WinnerCelebration({ open, winnerName, onClose }) {
+ const { language } = useLanguage();
useEffect(() => {
if (!open) return;
@@ -146,15 +148,15 @@ export default function WinnerCelebration({ open, winnerName, onClose }) {
lineHeight: 1.25,
}}
>
- Spieler{" "}
+ {language === "en" ? "Player " : "Spieler "}
{winnerName || "Unbekannt"}
{" "}
- hat die richtige Lösung!
+ {language === "en" ? "has the correct solution!" : "hat die richtige Lösung!"}