Translate UI text to German for Lobby, Results, and Voting screens to enhance localization
All checks were successful
Publish Docker image / Push Docker image to Docker Hub (push) Successful in 1m44s

This commit is contained in:
Mathias Wagner 2025-04-24 21:19:15 +02:00
parent e24ecb418c
commit 4f4626260f
3 changed files with 70 additions and 31 deletions

View File

@ -143,7 +143,7 @@ const LobbyScreen = () => {
Abbrechen
</button>
<button className="btn primary" onClick={handleSaveSettings}>
Einstellungen speichern
Mach rin
</button>
</div>
</div>

View File

@ -9,42 +9,49 @@ const ResultsScreen = () => {
const [showBattleHistory, setShowBattleHistory] = useState(false);
const [confetti, setConfetti] = useState(true);
// Winner information
// Gewinner-Informationen
const winner = lobby?.finalWinner;
const winnerVideoId = getYouTubeId(winner?.youtubeLink);
// Confetti effect for winner celebration
// Konfetti-Effekt für die Siegerfeier
useEffect(() => {
if (confetti) {
// Create confetti animation
// Konfetti-Animation erstellen
const createConfetti = () => {
const confettiContainer = document.createElement('div');
confettiContainer.className = 'confetti';
// Random position and color
// Zufällige Position, Größe und Farbe
const left = Math.random() * 100;
const colors = ['#f94144', '#f3722c', '#f8961e', '#f9c74f', '#90be6d', '#43aa8b', '#577590'];
const color = colors[Math.floor(Math.random() * colors.length)];
const size = Math.random() * 0.7 + 0.3; // Zwischen 0.3 und 1rem
const rotation = Math.random() * 360; // Zufällige Rotation
const duration = Math.random() * 3 + 2; // Zwischen 2 und 5 Sekunden
confettiContainer.style.left = `${left}%`;
confettiContainer.style.backgroundColor = color;
confettiContainer.style.width = `${size}rem`;
confettiContainer.style.height = `${size * 0.6}rem`;
confettiContainer.style.transform = `rotate(${rotation}deg)`;
confettiContainer.style.animation = `confetti-fall ${duration}s linear forwards`;
document.querySelector('.results-screen').appendChild(confettiContainer);
// Remove after animation
// Nach Animation entfernen
setTimeout(() => {
confettiContainer.remove();
}, 5000);
}, duration * 1000);
};
// Create multiple confetti pieces
// Mehrere Konfetti-Stücke erstellen
const confettiInterval = setInterval(() => {
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 5; i++) {
createConfetti();
}
}, 300);
}, 200);
// Stop confetti after some time
// Konfetti nach einiger Zeit stoppen
setTimeout(() => {
clearInterval(confettiInterval);
setConfetti(false);
@ -56,7 +63,7 @@ const ResultsScreen = () => {
}
}, [confetti]);
// Get YouTube video ID from link
// YouTube-Video-ID aus Link extrahieren
function getYouTubeId(url) {
if (!url) return null;
@ -69,7 +76,7 @@ const ResultsScreen = () => {
if (!winner) {
return (
<div className="results-screen">
<h2>Calculating results...</h2>
<h2>Ergebnisse werden berechnet...</h2>
</div>
);
}
@ -77,14 +84,14 @@ const ResultsScreen = () => {
return (
<div className="results-screen">
<header>
<h1><FontAwesomeIcon icon={faTrophy} /> Winner!</h1>
<h1><FontAwesomeIcon icon={faTrophy} /> Gewinner!</h1>
</header>
<div className="winner-card">
<div className="winner-info">
<h2>{winner.title}</h2>
<h3>by {winner.artist}</h3>
<p className="submitter">Submitted by: {winner.submittedByName}</p>
<h3>von {winner.artist}</h3>
<p className="submitter">Eingereicht von: {winner.submittedByName}</p>
</div>
{winnerVideoId ? (
@ -104,17 +111,17 @@ const ResultsScreen = () => {
onClick={() => setShowBattleHistory(!showBattleHistory)}
>
<FontAwesomeIcon icon={faChartLine} />
{showBattleHistory ? 'Hide Battle History' : 'Show Battle History'}
{showBattleHistory ? 'Kampfverlauf ausblenden' : 'Kampfverlauf anzeigen'}
</button>
<button className="btn primary" onClick={leaveLobby}>
<FontAwesomeIcon icon={faHome} /> Return to Home
<FontAwesomeIcon icon={faHome} /> Zurück zum Start
</button>
</div>
{showBattleHistory && (
<div className="battle-history">
<h3>Battle History</h3>
<h3>Kampfverlauf</h3>
<div className="battles-list">
{lobby?.battles?.map((battle, index) => {
@ -122,13 +129,13 @@ const ResultsScreen = () => {
const song1VideoId = getYouTubeId(battle.song1?.youtubeLink);
const song2VideoId = battle.song2 ? getYouTubeId(battle.song2.youtubeLink) : null;
// Handle bye rounds
// Freilos-Runden behandeln
const isByeRound = battle.bye === true || !battle.song2;
return (
<div key={index} className="battle-item">
<div className="battle-header">
<h4>Round {battle.round + 1}, Battle {index + 1} {isByeRound ? "(Automatic Advance)" : ""}</h4>
<h4>Runde {battle.round + 1}, Kampf {index + 1} {isByeRound ? "(Automatisches Weiterkommen)" : ""}</h4>
</div>
<div className="battle-songs">
@ -136,19 +143,28 @@ const ResultsScreen = () => {
<div className="song-info">
<h5>{battle.song1.title}</h5>
<p>{battle.song1.artist}</p>
<span className="votes">{battle.song1Votes} votes</span>
<span className="votes">{battle.song1Votes} Stimmen</span>
</div>
</div>
<div className="versus">VS</div>
<div className="versus">{isByeRound ? 'FREILOS' : 'VS'}</div>
<div className={`battle-song ${!isWinner ? 'winner' : ''}`}>
<div className="song-info">
<h5>{battle.song2.title}</h5>
<p>{battle.song2.artist}</p>
<span className="votes">{battle.song2Votes} votes</span>
{battle.song2 ? (
<div className={`battle-song ${!isWinner ? 'winner' : ''}`}>
<div className="song-info">
<h5>{battle.song2.title}</h5>
<p>{battle.song2.artist}</p>
<span className="votes">{battle.song2Votes} Stimmen</span>
</div>
</div>
</div>
) : (
<div className="battle-song bye">
<div className="song-info">
<h5>Automatisches Freilos</h5>
<p>Kein Gegner</p>
</div>
</div>
)}
</div>
</div>
);

View File

@ -10,6 +10,7 @@ function VotingScreen() {
const [hasVoted, setHasVoted] = useState(false);
const [selectedSong, setSelectedSong] = useState(null);
const [countdown, setCountdown] = useState(null);
const [processingByeAdvance, setProcessingByeAdvance] = useState(false);
// Hole aktuellen Kampf
const battle = lobby?.currentBattle || null;
@ -62,6 +63,24 @@ function VotingScreen() {
// setHasVoted wird jetzt durch den useEffect behandelt, der die Stimmen prüft
};
// Handle bye round advancement - für automatisches Weiterkommen
const handleByeAdvance = async () => {
if (processingByeAdvance) return;
setProcessingByeAdvance(true);
try {
// Alle Spieler im Bye-Modus "stimmen" automatisch für das einzige Lied
if (battle && battle.song1 && !battle.song2 && battle.song1.id) {
await submitVote(battle.song1.id);
}
} catch (error) {
console.error("Fehler beim Fortfahren:", error);
} finally {
// Verzögerung, um mehrere Klicks zu verhindern
setTimeout(() => setProcessingByeAdvance(false), 1000);
}
};
// Hole YouTube-Video-IDs aus Links
const getYouTubeId = (url) => {
if (!url) return null;
@ -120,8 +139,12 @@ function VotingScreen() {
</div>
<div className="voting-status">
<button className="btn primary pixelated full-width" onClick={() => submitVote(battle.song1.id)}>
Weiter zum nächsten Kampf
<button
className={`btn primary pixelated full-width ${processingByeAdvance ? 'disabled' : ''}`}
onClick={handleByeAdvance}
disabled={processingByeAdvance}
>
{processingByeAdvance ? 'Wird geladen...' : 'Mach weiter'}
<span className="pixel-corner tl"></span>
<span className="pixel-corner tr"></span>
<span className="pixel-corner bl"></span>