Add internationalization support with English/German language toggle across all components
Integrated i18n system throughout the application with `useLanguage` hook providing translation function and language state. Added bilingual support to all user-facing strings in App.jsx (password validation, error messages, game creation, sheet sections), AdminPanel (user management, form validation, status messages), AdminSettingsModal (SMTP configuration), ChipModal, DesignModal, GamePickerCard (lobby interface,
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { styles } from "../styles/styles";
|
||||
import { stylesTokens } from "../styles/theme";
|
||||
import { useLanguage } from "../i18n";
|
||||
|
||||
export default function NewGameModal({
|
||||
open,
|
||||
@@ -13,6 +14,7 @@ export default function NewGameModal({
|
||||
gameFinished = false,
|
||||
hasGame = false,
|
||||
}) {
|
||||
const { language, t } = useLanguage();
|
||||
// modes: running | choice | create | join
|
||||
const [mode, setMode] = useState("choice");
|
||||
const [joinCode, setJoinCode] = useState("");
|
||||
@@ -53,7 +55,7 @@ export default function NewGameModal({
|
||||
setCreated({ code: res.code });
|
||||
setMode("create");
|
||||
} catch (e) {
|
||||
setErr("❌ Fehler: " + (e?.message || "unknown"));
|
||||
setErr("❌ " + (language === "en" ? "Error: " : "Fehler: ") + (e?.message || "unknown"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -63,16 +65,16 @@ export default function NewGameModal({
|
||||
await onJoin(joinCode.trim().toUpperCase());
|
||||
onClose();
|
||||
} catch (e) {
|
||||
setErr("❌ Fehler: " + (e?.message || "unknown"));
|
||||
setErr("❌ " + (language === "en" ? "Error: " : "Fehler: ") + (e?.message || "unknown"));
|
||||
}
|
||||
};
|
||||
|
||||
const copyText = async (text, okMsg = "✅ Code kopiert") => {
|
||||
const copyText = async (text, okMsg = language === "en" ? "✅ Code copied" : "✅ Code kopiert") => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text || "");
|
||||
showToast(okMsg);
|
||||
} catch {
|
||||
showToast("❌ Copy nicht möglich");
|
||||
showToast(language === "en" ? "❌ Copy failed" : "❌ Copy nicht möglich");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,9 +87,9 @@ export default function NewGameModal({
|
||||
<div style={styles.modalCard} onMouseDown={(e) => e.stopPropagation()}>
|
||||
<div style={styles.modalHeader}>
|
||||
<div style={{ fontWeight: 1000, color: stylesTokens.textGold }}>
|
||||
Spiel
|
||||
{t("game")}
|
||||
</div>
|
||||
<button onClick={onClose} style={styles.modalCloseBtn} aria-label="Schließen">
|
||||
<button onClick={onClose} style={styles.modalCloseBtn} aria-label={t("close")}>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
@@ -116,7 +118,7 @@ export default function NewGameModal({
|
||||
{mode === "running" && (
|
||||
<>
|
||||
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
||||
Das Spiel läuft noch. Hier ist der <b>Join-Code</b>:
|
||||
{language === "en" ? <>The game is in progress. Here is the <b>join code</b>:</> : <>Das Spiel läuft noch. Hier ist der <b>Join-Code</b>:</>}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -131,7 +133,7 @@ export default function NewGameModal({
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, opacity: 0.8, color: stylesTokens.textDim }}>
|
||||
Spiel-Code
|
||||
{t("gameCode")}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -150,19 +152,19 @@ export default function NewGameModal({
|
||||
onClick={() => copyText(codeToShow)}
|
||||
style={styles.primaryBtn}
|
||||
disabled={!codeToShow}
|
||||
title={!codeToShow ? "Kein Code verfügbar" : "Code kopieren"}
|
||||
title={!codeToShow ? (language === "en" ? "No code available" : "Kein Code verfügbar") : (language === "en" ? "Copy code" : "Code kopieren")}
|
||||
>
|
||||
⧉ Code kopieren
|
||||
⧉ {t("copy")} {language === "en" ? "code" : "Code"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ fontSize: 12, opacity: 0.75, color: stylesTokens.textDim }}>
|
||||
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."}
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
||||
<button onClick={onClose} style={styles.primaryBtn}>
|
||||
Fertig
|
||||
{language === "en" ? "Done" : "Fertig"}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -173,15 +175,15 @@ export default function NewGameModal({
|
||||
{mode === "choice" && (
|
||||
<>
|
||||
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
||||
Willst du ein Spiel <b>erstellen</b> oder einem Spiel <b>beitreten</b>?
|
||||
{language === "en" ? <>Would you like to <b>create</b> a game or <b>join</b> one?</> : <>Willst du ein Spiel <b>erstellen</b> oder einem Spiel <b>beitreten</b>?</>}
|
||||
</div>
|
||||
|
||||
<button onClick={doCreate} style={styles.primaryBtn}>
|
||||
✦ Spiel erstellen
|
||||
✦ {t("createGame")}
|
||||
</button>
|
||||
|
||||
<button onClick={() => setMode("join")} style={styles.secondaryBtn}>
|
||||
⎆ Spiel beitreten
|
||||
⎆ {t("joinGame")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -189,7 +191,7 @@ export default function NewGameModal({
|
||||
{mode === "join" && (
|
||||
<>
|
||||
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
||||
Gib den <b>Code</b> ein:
|
||||
{language === "en" ? <>Enter the <b>code</b>:</> : <>Gib den <b>Code</b> ein:</>}
|
||||
</div>
|
||||
|
||||
<input
|
||||
@@ -202,10 +204,10 @@ export default function NewGameModal({
|
||||
|
||||
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
||||
<button onClick={() => setMode("choice")} style={styles.secondaryBtn}>
|
||||
Zurück
|
||||
{language === "en" ? "Back" : "Zurück"}
|
||||
</button>
|
||||
<button onClick={doJoin} style={styles.primaryBtn} disabled={!canJoin}>
|
||||
Beitreten
|
||||
{t("join")}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -214,7 +216,7 @@ export default function NewGameModal({
|
||||
{mode === "create" && created && (
|
||||
<>
|
||||
<div style={{ color: stylesTokens.textMain, opacity: 0.92 }}>
|
||||
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:"}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -229,7 +231,7 @@ export default function NewGameModal({
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: 12, opacity: 0.8, color: stylesTokens.textDim }}>
|
||||
Spiel-Code
|
||||
{t("gameCode")}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -245,13 +247,13 @@ export default function NewGameModal({
|
||||
</div>
|
||||
|
||||
<button onClick={() => copyText(created?.code || "")} style={styles.primaryBtn}>
|
||||
⧉ Code kopieren
|
||||
⧉ {t("copy")} {language === "en" ? "code" : "Code"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
|
||||
<button onClick={onClose} style={styles.primaryBtn}>
|
||||
Fertig
|
||||
{language === "en" ? "Done" : "Fertig"}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user