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 Abbrechen
</button> </button>
<button className="btn primary" onClick={handleSaveSettings}> <button className="btn primary" onClick={handleSaveSettings}>
Einstellungen speichern Mach rin
</button> </button>
</div> </div>
</div> </div>

View File

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

View File

@ -10,6 +10,7 @@ function VotingScreen() {
const [hasVoted, setHasVoted] = useState(false); const [hasVoted, setHasVoted] = useState(false);
const [selectedSong, setSelectedSong] = useState(null); const [selectedSong, setSelectedSong] = useState(null);
const [countdown, setCountdown] = useState(null); const [countdown, setCountdown] = useState(null);
const [processingByeAdvance, setProcessingByeAdvance] = useState(false);
// Hole aktuellen Kampf // Hole aktuellen Kampf
const battle = lobby?.currentBattle || null; const battle = lobby?.currentBattle || null;
@ -61,6 +62,24 @@ function VotingScreen() {
await submitVote(selectedSong); await submitVote(selectedSong);
// setHasVoted wird jetzt durch den useEffect behandelt, der die Stimmen prüft // 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 // Hole YouTube-Video-IDs aus Links
const getYouTubeId = (url) => { const getYouTubeId = (url) => {
@ -120,8 +139,12 @@ function VotingScreen() {
</div> </div>
<div className="voting-status"> <div className="voting-status">
<button className="btn primary pixelated full-width" onClick={() => submitVote(battle.song1.id)}> <button
Weiter zum nächsten Kampf 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 tl"></span>
<span className="pixel-corner tr"></span> <span className="pixel-corner tr"></span>
<span className="pixel-corner bl"></span> <span className="pixel-corner bl"></span>