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, { 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 (
|
||||
<div style={{ marginTop: 14 }}>
|
||||
<div style={styles.card}>
|
||||
<div style={styles.sectionHeader}>Spiel</div>
|
||||
<div style={styles.sectionHeader}>{t("game")}</div>
|
||||
|
||||
<div style={styles.cardBody}>
|
||||
<select
|
||||
@@ -83,8 +85,8 @@ export default function GamePickerCard({
|
||||
))}
|
||||
</select>
|
||||
|
||||
<button onClick={onOpenHelp} style={styles.helpBtn} title="Hilfe">
|
||||
Hilfe
|
||||
<button onClick={onOpenHelp} style={styles.helpBtn} title={t("help")}>
|
||||
{t("help")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -122,15 +124,15 @@ export default function GamePickerCard({
|
||||
<div className="hp-lobby-header" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 12 }}>
|
||||
<div>
|
||||
<div style={{ color: stylesTokens.textGold, fontWeight: 1000, fontSize: 15 }}>
|
||||
Lobby
|
||||
{t("lobby")}
|
||||
</div>
|
||||
<div style={{ marginTop: 3, color: stylesTokens.textDim, fontSize: 12 }}>
|
||||
{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.")}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right", color: stylesTokens.textGold, fontWeight: 1000 }}>
|
||||
<div style={{ fontSize: 20, lineHeight: 1 }}>{members.length}</div>
|
||||
<div style={{ fontSize: 11, color: stylesTokens.textDim, fontWeight: 700 }}>Spieler</div>
|
||||
<div style={{ fontSize: 11, color: stylesTokens.textDim, fontWeight: 700 }}>{t("players")}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -138,7 +140,7 @@ export default function GamePickerCard({
|
||||
{members.length > 0 ? members.map((m, index) => {
|
||||
const isMe = String(me?.id) === String(m.id);
|
||||
const isMemberHost = String(hostUserId) === String(m.id);
|
||||
const name = ((m.display_name || "").trim() || (m.email || "").trim() || "Spieler");
|
||||
const name = ((m.display_name || "").trim() || (m.email || "").trim() || t("players"));
|
||||
return (
|
||||
<div key={m.id} className="hp-lobby-player" style={{ display: "flex", alignItems: "center", gap: 9, padding: "8px 10px", borderRadius: 11, background: "rgba(255,255,255,0.055)", border: "1px solid rgba(233,216,166,0.10)" }}>
|
||||
<span style={{ color: stylesTokens.textDim, fontSize: 12, minWidth: 16 }}>{index + 1}</span>
|
||||
@@ -150,7 +152,7 @@ export default function GamePickerCard({
|
||||
);
|
||||
}) : (
|
||||
<div style={{ padding: "10px 4px", color: stylesTokens.textDim, fontSize: 13 }}>
|
||||
Warte auf Spieler …
|
||||
{language === "en" ? "Waiting for players …" : "Warte auf Spieler …"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -162,15 +164,15 @@ export default function GamePickerCard({
|
||||
style={{ ...styles.primaryBtn, width: "100%", marginTop: 12 }}
|
||||
disabled={starting || members.length < 2}
|
||||
>
|
||||
{starting ? "Spiel wird gestartet …" : "▶ Spiel starten"}
|
||||
{starting ? (language === "en" ? "Starting game …" : "Spiel wird gestartet …") : `▶ ${t("startGame")}`}
|
||||
</button>
|
||||
<div style={{ marginTop: 7, textAlign: "center", color: stylesTokens.textDim, fontSize: 11 }}>
|
||||
{members.length < 2 ? "Mindestens 2 Spieler werden benötigt." : "Beim Start werden die Spieler-Chips erstellt."}
|
||||
{members.length < 2 ? t("atLeastTwoPlayers") : t("chipsOnStart")}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div style={{ marginTop: 11, padding: "9px 10px", borderRadius: 11, background: "rgba(233,216,166,0.07)", color: stylesTokens.textDim, fontSize: 12, textAlign: "center" }}>
|
||||
Warte, bis der Host das Spiel startet.
|
||||
{t("waitingForHost")}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -179,8 +181,8 @@ export default function GamePickerCard({
|
||||
) : (
|
||||
<div style={{ margin: "0 12px 12px", padding: "10px 12px", borderRadius: 13, border: `1px solid ${stylesTokens.panelBorder}`, background: finished ? "rgba(233,216,166,0.09)" : "rgba(124,255,182,0.07)", color: stylesTokens.textDim, fontSize: 12 }}>
|
||||
{finished
|
||||
? `🏆 Spiel beendet${winnerName ? ` · Sieger: ${winnerName}` : ""}`
|
||||
: `✓ Spiel läuft · ${chipCount} Spieler-Chips erstellt`}
|
||||
? `🏆 ${t("finished")}${winnerName ? ` · ${t("winner")}: ${winnerName}` : ""}`
|
||||
: `✓ ${t("started")} · ${chipCount} ${t("chipsCreated")}`}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -198,7 +200,7 @@ export default function GamePickerCard({
|
||||
gap: 10,
|
||||
}}
|
||||
>
|
||||
<div>Spieler</div>
|
||||
<div>{t("players")}</div>
|
||||
<div style={{ fontWeight: 900, color: stylesTokens.textGold }}>
|
||||
{members.length}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user