Add real country flags to language selector
- Replace emoji flags with SVG-based country flags - German flag: Black-Red-Gold (official colors) - USA flag: Red-White-Blue with stars - Dynamic flag switching on language change - Applied to both dashboard and leaderboard - Database achievements now support English translations - Extended achievements table with name_en and description_en columns - Updated API routes to return English translations - Simplified frontend translation system to use database translations
This commit is contained in:
@@ -7,6 +7,7 @@ const supabase = window.supabase.createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
|
||||
|
||||
// Global variables
|
||||
let currentUser = null;
|
||||
let currentLanguage = 'en'; // Default to English
|
||||
|
||||
// Check authentication and load dashboard
|
||||
async function initDashboard() {
|
||||
@@ -88,8 +89,123 @@ supabase.auth.onAuthStateChange((event, session) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Language Management
|
||||
function translateElement(element, language) {
|
||||
if (element.dataset[language]) {
|
||||
element.textContent = element.dataset[language];
|
||||
}
|
||||
}
|
||||
|
||||
// Change language function
|
||||
function changeLanguage() {
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
currentLanguage = languageSelect.value;
|
||||
|
||||
// Save language preference
|
||||
localStorage.setItem('ninjacross_language', currentLanguage);
|
||||
|
||||
// Update flag in select
|
||||
updateLanguageFlag();
|
||||
|
||||
// Translate all elements with data attributes
|
||||
const elementsToTranslate = document.querySelectorAll('[data-de][data-en]');
|
||||
elementsToTranslate.forEach(element => {
|
||||
translateElement(element, currentLanguage);
|
||||
});
|
||||
|
||||
// Update dynamic content
|
||||
updateDynamicContent();
|
||||
|
||||
console.log(`🌐 Language changed to: ${currentLanguage}`);
|
||||
}
|
||||
|
||||
// Update flag in language selector
|
||||
function updateLanguageFlag() {
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
if (currentLanguage === 'de') {
|
||||
// German flag (black-red-gold)
|
||||
languageSelect.style.backgroundImage = `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="15" viewBox="0 0 20 15"><rect width="20" height="5" fill="%23000000"/><rect y="5" width="20" height="5" fill="%23DD0000"/><rect y="10" width="20" height="5" fill="%23FFCE00"/></svg>')`;
|
||||
} else {
|
||||
// USA flag
|
||||
languageSelect.style.backgroundImage = `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="15" viewBox="0 0 20 15"><rect width="20" height="15" fill="%23B22234"/><rect width="20" height="1.15" fill="%23FFFFFF"/><rect y="2.3" width="20" height="1.15" fill="%23FFFFFF"/><rect y="4.6" width="20" height="1.15" fill="%23FFFFFF"/><rect y="6.9" width="20" height="1.15" fill="%23FFFFFF"/><rect y="9.2" width="20" height="1.15" fill="%23FFFFFF"/><rect y="11.5" width="20" height="1.15" fill="%23FFFFFF"/><rect y="13.8" width="20" height="1.15" fill="%23FFFFFF"/><rect width="7.7" height="8.05" fill="%230033A0"/></svg>')`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update dynamic content that's not in HTML
|
||||
function updateDynamicContent() {
|
||||
// Update achievement notifications
|
||||
updateAchievementNotifications();
|
||||
|
||||
// Update time display formats
|
||||
updateTimeDisplayFormats();
|
||||
|
||||
// Update achievement progress text
|
||||
updateAchievementProgressText();
|
||||
|
||||
// Reload achievements if they're loaded
|
||||
if (window.allAchievements && window.allAchievements.length > 0) {
|
||||
displayAchievements();
|
||||
}
|
||||
|
||||
// Reload times if they're loaded
|
||||
if (document.getElementById('timesDisplay').style.display !== 'none') {
|
||||
// Times are displayed, reload them
|
||||
checkLinkStatusAndLoadTimes();
|
||||
}
|
||||
}
|
||||
|
||||
// Load saved language preference
|
||||
function loadLanguagePreference() {
|
||||
const savedLanguage = localStorage.getItem('ninjacross_language');
|
||||
if (savedLanguage && (savedLanguage === 'de' || savedLanguage === 'en')) {
|
||||
currentLanguage = savedLanguage;
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
languageSelect.value = currentLanguage;
|
||||
// Update flag when loading
|
||||
updateLanguageFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update achievement notifications
|
||||
function updateAchievementNotifications() {
|
||||
// This will be called when achievements are displayed
|
||||
}
|
||||
|
||||
// Update time display formats
|
||||
function updateTimeDisplayFormats() {
|
||||
// This will be called when times are displayed
|
||||
}
|
||||
|
||||
// Update achievement progress text
|
||||
function updateAchievementProgressText() {
|
||||
// This will be called when achievements are displayed
|
||||
}
|
||||
|
||||
// Achievement translations are now handled by the database
|
||||
|
||||
// Translate achievement data using database translations
|
||||
function translateAchievement(achievement) {
|
||||
if (currentLanguage === 'en' && achievement.name_en) {
|
||||
return {
|
||||
...achievement,
|
||||
name: achievement.name_en,
|
||||
description: achievement.description_en || achievement.description
|
||||
};
|
||||
}
|
||||
return achievement;
|
||||
}
|
||||
|
||||
|
||||
// Initialize dashboard when page loads
|
||||
initDashboard();
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
loadLanguagePreference();
|
||||
changeLanguage(); // Apply saved language
|
||||
initDashboard();
|
||||
});
|
||||
|
||||
// Modal functions
|
||||
function openModal(modalId) {
|
||||
@@ -185,7 +301,10 @@ async function startQRScanner() {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error accessing camera:', error);
|
||||
showMessage('rfidMessage', 'Kamera-Zugriff fehlgeschlagen. Bitte verwende die manuelle Eingabe.', 'error');
|
||||
const cameraErrorMsg = currentLanguage === 'de' ?
|
||||
'Kamera-Zugriff fehlgeschlagen. Bitte verwende die manuelle Eingabe.' :
|
||||
'Camera access failed. Please use manual input.';
|
||||
showMessage('rfidMessage', cameraErrorMsg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,21 +384,30 @@ async function handleQRCodeDetected(qrData) {
|
||||
const rawUid = qrData.trim();
|
||||
|
||||
if (!rawUid) {
|
||||
showMessage('rfidMessage', 'QR-Code enthält keine gültige RFID UID', 'error');
|
||||
const qrErrorMsg = currentLanguage === 'de' ?
|
||||
'QR-Code enthält keine gültige RFID UID' :
|
||||
'QR code contains no valid RFID UID';
|
||||
showMessage('rfidMessage', qrErrorMsg, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the UID to match database format (XX:XX:XX:XX)
|
||||
const formattedUid = formatRfidUid(rawUid);
|
||||
|
||||
showMessage('rfidMessage', `QR-Code erkannt: ${rawUid} → ${formattedUid}`, 'info');
|
||||
const qrDetectedMsg = currentLanguage === 'de' ?
|
||||
`QR-Code erkannt: ${rawUid} → ${formattedUid}` :
|
||||
`QR code detected: ${rawUid} → ${formattedUid}`;
|
||||
showMessage('rfidMessage', qrDetectedMsg, 'info');
|
||||
|
||||
// Link the user using the formatted RFID UID
|
||||
await linkUserByRfidUid(formattedUid);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error formatting RFID UID:', error);
|
||||
showMessage('rfidMessage', `Fehler beim Formatieren der RFID UID: ${error.message}`, 'error');
|
||||
const formatErrorMsg = currentLanguage === 'de' ?
|
||||
`Fehler beim Formatieren der RFID UID: ${error.message}` :
|
||||
`Error formatting RFID UID: ${error.message}`;
|
||||
showMessage('rfidMessage', formatErrorMsg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +416,10 @@ async function linkManualRfid() {
|
||||
const rawUid = document.getElementById('manualRfidInput').value.trim();
|
||||
|
||||
if (!rawUid) {
|
||||
showMessage('rfidMessage', 'Bitte gib eine RFID UID ein', 'error');
|
||||
const inputErrorMsg = currentLanguage === 'de' ?
|
||||
'Bitte gib eine RFID UID ein' :
|
||||
'Please enter a RFID UID';
|
||||
showMessage('rfidMessage', inputErrorMsg, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -296,20 +427,29 @@ async function linkManualRfid() {
|
||||
// Format the UID to match database format
|
||||
const formattedUid = formatRfidUid(rawUid);
|
||||
|
||||
showMessage('rfidMessage', `Formatiert: ${rawUid} → ${formattedUid}`, 'info');
|
||||
const formattedMsg = currentLanguage === 'de' ?
|
||||
`Formatiert: ${rawUid} → ${formattedUid}` :
|
||||
`Formatted: ${rawUid} → ${formattedUid}`;
|
||||
showMessage('rfidMessage', formattedMsg, 'info');
|
||||
|
||||
await linkUserByRfidUid(formattedUid);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error formatting manual RFID UID:', error);
|
||||
showMessage('rfidMessage', `Fehler beim Formatieren: ${error.message}`, 'error');
|
||||
const formatErrorMsg = currentLanguage === 'de' ?
|
||||
`Fehler beim Formatieren: ${error.message}` :
|
||||
`Error formatting: ${error.message}`;
|
||||
showMessage('rfidMessage', formatErrorMsg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Link user by RFID UID (core function)
|
||||
async function linkUserByRfidUid(rfidUid) {
|
||||
if (!currentUser) {
|
||||
showMessage('rfidMessage', 'Benutzer nicht authentifiziert', 'error');
|
||||
const authErrorMsg = currentLanguage === 'de' ?
|
||||
'Benutzer nicht authentifiziert' :
|
||||
'User not authenticated';
|
||||
showMessage('rfidMessage', authErrorMsg, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -329,18 +469,27 @@ async function linkUserByRfidUid(rfidUid) {
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
showMessage('rfidMessage', `✅ RFID erfolgreich verknüpft!\nSpieler: ${result.data.firstname} ${result.data.lastname}`, 'success');
|
||||
const successMsg = currentLanguage === 'de' ?
|
||||
`✅ RFID erfolgreich verknüpft!\nSpieler: ${result.data.firstname} ${result.data.lastname}` :
|
||||
`✅ RFID successfully linked!\nPlayer: ${result.data.firstname} ${result.data.lastname}`;
|
||||
showMessage('rfidMessage', successMsg, 'success');
|
||||
setTimeout(() => {
|
||||
closeModal('rfidModal');
|
||||
// Reload times section after successful linking
|
||||
checkLinkStatusAndLoadTimes();
|
||||
}, 2000);
|
||||
} else {
|
||||
showMessage('rfidMessage', result.message || 'Fehler beim Verknüpfen', 'error');
|
||||
const errorMsg = currentLanguage === 'de' ?
|
||||
result.message || 'Fehler beim Verknüpfen' :
|
||||
result.message || 'Error linking';
|
||||
showMessage('rfidMessage', errorMsg, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error linking RFID:', error);
|
||||
showMessage('rfidMessage', 'Fehler beim Verknüpfen der RFID', 'error');
|
||||
const linkErrorMsg = currentLanguage === 'de' ?
|
||||
'Fehler beim Verknüpfen der RFID' :
|
||||
'Error linking RFID';
|
||||
showMessage('rfidMessage', linkErrorMsg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +498,50 @@ function showTimesNotLinked() {
|
||||
document.getElementById('timesLoading').style.display = 'none';
|
||||
document.getElementById('timesNotLinked').style.display = 'block';
|
||||
document.getElementById('timesDisplay').style.display = 'none';
|
||||
|
||||
// Update the text content for the not linked state
|
||||
const notLinkedTitle = document.querySelector('#timesNotLinked h3');
|
||||
const notLinkedDescription = document.querySelector('#timesNotLinked p');
|
||||
const notLinkedButton = document.querySelector('#timesNotLinked button');
|
||||
const notLinkedSteps = document.querySelectorAll('#timesNotLinked li');
|
||||
|
||||
if (notLinkedTitle) {
|
||||
notLinkedTitle.textContent = currentLanguage === 'de' ?
|
||||
'RFID noch nicht verknüpft' :
|
||||
'RFID not linked yet';
|
||||
}
|
||||
|
||||
if (notLinkedDescription) {
|
||||
notLinkedDescription.textContent = currentLanguage === 'de' ?
|
||||
'Um deine persönlichen Zeiten zu sehen, musst du zuerst deine RFID-Karte mit deinem Account verknüpfen.' :
|
||||
'To see your personal times, you must first link your RFID card with your account.';
|
||||
}
|
||||
|
||||
if (notLinkedButton) {
|
||||
notLinkedButton.textContent = currentLanguage === 'de' ?
|
||||
'🏷️ RFID jetzt verknüpfen' :
|
||||
'🏷️ Link RFID now';
|
||||
}
|
||||
|
||||
if (notLinkedSteps.length >= 3) {
|
||||
notLinkedSteps[0].textContent = currentLanguage === 'de' ?
|
||||
'Klicke auf "RFID jetzt verknüpfen"' :
|
||||
'Click on "Link RFID now"';
|
||||
notLinkedSteps[1].textContent = currentLanguage === 'de' ?
|
||||
'Scanne den QR-Code auf deiner RFID-Karte' :
|
||||
'Scan the QR code on your RFID card';
|
||||
notLinkedSteps[2].textContent = currentLanguage === 'de' ?
|
||||
'Fertig! Deine Zeiten werden automatisch hier angezeigt' :
|
||||
'Done! Your times will be displayed here automatically';
|
||||
}
|
||||
|
||||
// Update the "So funktioniert's" title
|
||||
const howItWorksTitle = document.querySelector('#timesNotLinked h4');
|
||||
if (howItWorksTitle) {
|
||||
howItWorksTitle.textContent = currentLanguage === 'de' ?
|
||||
'So funktioniert\'s:' :
|
||||
'How it works:';
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
@@ -356,6 +549,14 @@ function showTimesLoading() {
|
||||
document.getElementById('timesLoading').style.display = 'block';
|
||||
document.getElementById('timesNotLinked').style.display = 'none';
|
||||
document.getElementById('timesDisplay').style.display = 'none';
|
||||
|
||||
// Update the loading text
|
||||
const loadingText = document.querySelector('#timesLoading p');
|
||||
if (loadingText) {
|
||||
loadingText.textContent = currentLanguage === 'de' ?
|
||||
'Lade deine Zeiten...' :
|
||||
'Loading your times...';
|
||||
}
|
||||
}
|
||||
|
||||
// Load user times for the section
|
||||
@@ -422,10 +623,15 @@ function displayUserTimes(times) {
|
||||
const timesGrid = document.getElementById('userTimesGrid');
|
||||
|
||||
if (times.length === 0) {
|
||||
const noTimesTitle = currentLanguage === 'de' ? 'Noch keine Zeiten aufgezeichnet' : 'No times recorded yet';
|
||||
const noTimesDescription = currentLanguage === 'de' ?
|
||||
'Deine ersten Läufe werden hier angezeigt, sobald du sie abgeschlossen hast!' :
|
||||
'Your first runs will be displayed here as soon as you complete them!';
|
||||
|
||||
timesGrid.innerHTML = `
|
||||
<div style="grid-column: 1 / -1; text-align: center; padding: 3rem; color: #8892b0;">
|
||||
<h3>Noch keine Zeiten aufgezeichnet</h3>
|
||||
<p>Deine ersten Läufe werden hier angezeigt, sobald du sie abgeschlossen hast!</p>
|
||||
<h3>${noTimesTitle}</h3>
|
||||
<p>${noTimesDescription}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -456,7 +662,7 @@ function displayUserTimes(times) {
|
||||
let rankClass = '';
|
||||
|
||||
if (runIndex === 0) {
|
||||
rankBadge = '🥇 Beste';
|
||||
rankBadge = currentLanguage === 'de' ? '🥇 Beste' : '🥇 Best';
|
||||
rankClass = 'best';
|
||||
} else if (runIndex === 1) {
|
||||
rankBadge = '🥈 2.';
|
||||
@@ -475,8 +681,8 @@ function displayUserTimes(times) {
|
||||
<div class="run-time">${formatTime(run.recorded_time)}</div>
|
||||
</div>
|
||||
<div class="run-details">
|
||||
<div>${new Date(run.created_at).toLocaleDateString('de-DE')}</div>
|
||||
<div>${new Date(run.created_at).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}</div>
|
||||
<div>${new Date(run.created_at).toLocaleDateString(currentLanguage === 'de' ? 'de-DE' : 'en-US')}</div>
|
||||
<div>${new Date(run.created_at).toLocaleTimeString(currentLanguage === 'de' ? 'de-DE' : 'en-US', { hour: '2-digit', minute: '2-digit' })}</div>
|
||||
<span class="run-rank-badge ${rankClass}">${rankBadge}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -493,13 +699,13 @@ function displayUserTimes(times) {
|
||||
<div class="card-main-content">
|
||||
<div class="time-value-large">${formatTime(bestTime.recorded_time)}</div>
|
||||
<div class="time-date-info">
|
||||
<span>${new Date(bestTime.created_at).toLocaleDateString('de-DE')}</span>
|
||||
<span class="time-rank">${locationTimes.length} Läufe</span>
|
||||
<span>${new Date(bestTime.created_at).toLocaleDateString(currentLanguage === 'de' ? 'de-DE' : 'en-US')}</span>
|
||||
<span class="time-rank">${locationTimes.length} ${currentLanguage === 'de' ? 'Läufe' : 'Runs'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="expanded-content">
|
||||
<div class="all-runs-title">Alle Läufe an diesem Standort:</div>
|
||||
<div class="all-runs-title">${currentLanguage === 'de' ? 'Alle Läufe an diesem Standort:' : 'All runs at this location:'}</div>
|
||||
${allRunsHtml}
|
||||
</div>
|
||||
</div>
|
||||
@@ -609,6 +815,14 @@ async function loadPlayerAchievements() {
|
||||
document.getElementById('achievementCategories').style.display = 'none';
|
||||
document.getElementById('achievementsNotAvailable').style.display = 'none';
|
||||
|
||||
// Update loading text
|
||||
const loadingText = document.querySelector('#achievementsLoading p');
|
||||
if (loadingText) {
|
||||
loadingText.textContent = currentLanguage === 'de' ?
|
||||
'Lade deine Achievements...' :
|
||||
'Loading your achievements...';
|
||||
}
|
||||
|
||||
// Load player achievements (includes all achievements with player status)
|
||||
const response = await fetch(`/api/achievements/player/${currentPlayerId}?t=${Date.now()}`);
|
||||
if (!response.ok) {
|
||||
@@ -668,11 +882,16 @@ function displayAchievements() {
|
||||
const achievementsGrid = document.getElementById('achievementsGrid');
|
||||
|
||||
if (!window.allAchievements || window.allAchievements.length === 0) {
|
||||
const noAchievementsTitle = currentLanguage === 'de' ? 'Noch keine Achievements' : 'No Achievements Yet';
|
||||
const noAchievementsDescription = currentLanguage === 'de' ?
|
||||
'Starte deine ersten Läufe, um Achievements zu sammeln!' :
|
||||
'Start your first runs to collect achievements!';
|
||||
|
||||
achievementsGrid.innerHTML = `
|
||||
<div class="no-achievements">
|
||||
<div class="no-achievements-icon">🏆</div>
|
||||
<h3>Noch keine Achievements</h3>
|
||||
<p>Starte deine ersten Läufe, um Achievements zu sammeln!</p>
|
||||
<h3>${noAchievementsTitle}</h3>
|
||||
<p>${noAchievementsDescription}</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
@@ -692,6 +911,9 @@ function displayAchievements() {
|
||||
const progress = achievement.progress || 0;
|
||||
const earnedAt = achievement.earned_at;
|
||||
|
||||
// Translate achievement
|
||||
const translatedAchievement = translateAchievement(achievement);
|
||||
|
||||
// Debug logging
|
||||
if (achievement.name === 'Tageskönig') {
|
||||
console.log('Tageskönig Debug:', { isCompleted, progress, earnedAt });
|
||||
@@ -699,9 +921,11 @@ function displayAchievements() {
|
||||
|
||||
let progressText = '';
|
||||
if (isCompleted) {
|
||||
const achievedText = currentLanguage === 'de' ? 'Erreicht am' : 'Achieved on';
|
||||
const completedText = currentLanguage === 'de' ? 'Abgeschlossen' : 'Completed';
|
||||
progressText = earnedAt ?
|
||||
`Erreicht am ${new Date(earnedAt).toLocaleDateString('de-DE')}` :
|
||||
'Abgeschlossen';
|
||||
`${achievedText} ${new Date(earnedAt).toLocaleDateString(currentLanguage === 'de' ? 'de-DE' : 'en-US')}` :
|
||||
completedText;
|
||||
} else if (progress > 0) {
|
||||
// Show progress for incomplete achievements
|
||||
const conditionValue = getAchievementConditionValue(achievement.name);
|
||||
@@ -710,15 +934,17 @@ function displayAchievements() {
|
||||
}
|
||||
}
|
||||
|
||||
const pointsText = currentLanguage === 'de' ? 'Punkte' : 'Points';
|
||||
|
||||
return `
|
||||
<div class="achievement-card ${isCompleted ? 'completed' : 'incomplete'}"
|
||||
onclick="showAchievementDetails('${achievement.id}')">
|
||||
<div class="achievement-icon">${achievement.icon}</div>
|
||||
<div class="achievement-content">
|
||||
<h4 class="achievement-name">${achievement.name}</h4>
|
||||
<p class="achievement-description">${achievement.description}</p>
|
||||
<h4 class="achievement-name">${translatedAchievement.name}</h4>
|
||||
<p class="achievement-description">${translatedAchievement.description}</p>
|
||||
<div class="achievement-meta">
|
||||
<span class="achievement-points">+${achievement.points} Punkte</span>
|
||||
<span class="achievement-points">+${achievement.points} ${pointsText}</span>
|
||||
${progressText ? `<span class="achievement-progress">${progressText}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
@@ -780,6 +1006,51 @@ function showAchievementsNotAvailable() {
|
||||
document.getElementById('achievementStats').style.display = 'none';
|
||||
document.getElementById('achievementCategories').style.display = 'none';
|
||||
document.getElementById('achievementsNotAvailable').style.display = 'block';
|
||||
|
||||
// Update the text content for the not available state
|
||||
const notAvailableTitle = document.querySelector('#achievementsNotAvailable h3');
|
||||
const notAvailableDescription = document.querySelector('#achievementsNotAvailable p');
|
||||
const notAvailableButton = document.querySelector('#achievementsNotAvailable button');
|
||||
|
||||
if (notAvailableTitle) {
|
||||
notAvailableTitle.textContent = currentLanguage === 'de' ?
|
||||
'Achievements noch nicht verfügbar' :
|
||||
'Achievements not available yet';
|
||||
}
|
||||
|
||||
if (notAvailableDescription) {
|
||||
notAvailableDescription.textContent = currentLanguage === 'de' ?
|
||||
'Um Achievements zu sammeln, musst du zuerst deine RFID-Karte mit deinem Account verknüpfen und einige Läufe absolvieren.' :
|
||||
'To collect achievements, you must first link your RFID card with your account and complete some runs.';
|
||||
}
|
||||
|
||||
if (notAvailableButton) {
|
||||
notAvailableButton.textContent = currentLanguage === 'de' ?
|
||||
'🏷️ RFID jetzt verknüpfen' :
|
||||
'🏷️ Link RFID now';
|
||||
}
|
||||
|
||||
// Update the "So funktioniert's" title
|
||||
const howItWorksTitle = document.querySelector('#achievementsNotAvailable h4');
|
||||
if (howItWorksTitle) {
|
||||
howItWorksTitle.textContent = currentLanguage === 'de' ?
|
||||
'So funktioniert\'s:' :
|
||||
'How it works:';
|
||||
}
|
||||
|
||||
// Update the steps
|
||||
const notAvailableSteps = document.querySelectorAll('#achievementsNotAvailable li');
|
||||
if (notAvailableSteps.length >= 3) {
|
||||
notAvailableSteps[0].textContent = currentLanguage === 'de' ?
|
||||
'Klicke auf "RFID jetzt verknüpfen"' :
|
||||
'Click on "Link RFID now"';
|
||||
notAvailableSteps[1].textContent = currentLanguage === 'de' ?
|
||||
'Scanne den QR-Code auf deiner RFID-Karte' :
|
||||
'Scan the QR code on your RFID card';
|
||||
notAvailableSteps[2].textContent = currentLanguage === 'de' ?
|
||||
'Fertig! Deine Zeiten werden automatisch hier angezeigt' :
|
||||
'Done! Your times will be displayed here automatically';
|
||||
}
|
||||
}
|
||||
|
||||
// Check achievements for current player
|
||||
@@ -810,12 +1081,18 @@ function showAchievementNotification(newAchievements) {
|
||||
// Create notification element
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'achievement-notification';
|
||||
|
||||
const titleText = currentLanguage === 'de' ? 'Neue Achievements erreicht!' : 'New Achievements Unlocked!';
|
||||
const descriptionText = currentLanguage === 'de' ?
|
||||
`Du hast ${newAchievements.length} neue Achievement${newAchievements.length > 1 ? 's' : ''} erhalten!` :
|
||||
`You have received ${newAchievements.length} new achievement${newAchievements.length > 1 ? 's' : ''}!`;
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<div class="notification-icon">🏆</div>
|
||||
<div class="notification-text">
|
||||
<h4>Neue Achievements erreicht!</h4>
|
||||
<p>Du hast ${newAchievements.length} neue Achievement${newAchievements.length > 1 ? 's' : ''} erhalten!</p>
|
||||
<h4>${titleText}</h4>
|
||||
<p>${descriptionText}</p>
|
||||
</div>
|
||||
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">×</button>
|
||||
</div>
|
||||
@@ -874,27 +1151,27 @@ async function checkBestTimeNotifications() {
|
||||
// Check if current player has best times
|
||||
if (currentPlayerId) {
|
||||
if (daily && daily.player_id === currentPlayerId) {
|
||||
showWebNotification(
|
||||
'🏆 Tageskönig!',
|
||||
`Glückwunsch! Du hast die beste Zeit des Tages mit ${daily.best_time} erreicht!`,
|
||||
'👑'
|
||||
);
|
||||
const title = currentLanguage === 'de' ? '🏆 Tageskönig!' : '🏆 Daily King!';
|
||||
const message = currentLanguage === 'de' ?
|
||||
`Glückwunsch! Du hast die beste Zeit des Tages mit ${daily.best_time} erreicht!` :
|
||||
`Congratulations! You achieved the best time of the day with ${daily.best_time}!`;
|
||||
showWebNotification(title, message, '👑');
|
||||
}
|
||||
|
||||
if (weekly && weekly.player_id === currentPlayerId) {
|
||||
showWebNotification(
|
||||
'🏆 Wochenchampion!',
|
||||
`Fantastisch! Du bist der Wochenchampion mit ${weekly.best_time}!`,
|
||||
'🏆'
|
||||
);
|
||||
const title = currentLanguage === 'de' ? '🏆 Wochenchampion!' : '🏆 Weekly Champion!';
|
||||
const message = currentLanguage === 'de' ?
|
||||
`Fantastisch! Du bist der Wochenchampion mit ${weekly.best_time}!` :
|
||||
`Fantastic! You are the weekly champion with ${weekly.best_time}!`;
|
||||
showWebNotification(title, message, '🏆');
|
||||
}
|
||||
|
||||
if (monthly && monthly.player_id === currentPlayerId) {
|
||||
showWebNotification(
|
||||
'🏆 Monatsmeister!',
|
||||
`Unglaublich! Du bist der Monatsmeister mit ${monthly.best_time}!`,
|
||||
'🥇'
|
||||
);
|
||||
const title = currentLanguage === 'de' ? '🏆 Monatsmeister!' : '🏆 Monthly Master!';
|
||||
const message = currentLanguage === 'de' ?
|
||||
`Unglaublich! Du bist der Monatsmeister mit ${monthly.best_time}!` :
|
||||
`Incredible! You are the monthly master with ${monthly.best_time}!`;
|
||||
showWebNotification(title, message, '🥇');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -921,9 +1198,10 @@ async function checkAchievementNotifications() {
|
||||
|
||||
if (newAchievements.length > 0) {
|
||||
newAchievements.forEach(achievement => {
|
||||
const translatedAchievement = translateAchievement(achievement);
|
||||
showWebNotification(
|
||||
`🏆 ${achievement.name}`,
|
||||
achievement.description,
|
||||
`🏆 ${translatedAchievement.name}`,
|
||||
translatedAchievement.description,
|
||||
achievement.icon || '🏆'
|
||||
);
|
||||
});
|
||||
@@ -956,11 +1234,11 @@ async function loadSettings() {
|
||||
console.error('No user ID available');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Load current player settings using user ID
|
||||
const response = await fetch(`/api/v1/public/user-player/${currentUser.id}`);
|
||||
const result = await response.json();
|
||||
|
||||
|
||||
if (result.success && result.data) {
|
||||
const showInLeaderboard = result.data.show_in_leaderboard || false;
|
||||
document.getElementById('showInLeaderboard').checked = showInLeaderboard;
|
||||
@@ -1006,14 +1284,23 @@ async function saveSettings() {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showNotification('Einstellungen erfolgreich gespeichert!', 'success');
|
||||
const successMsg = currentLanguage === 'de' ?
|
||||
'Einstellungen erfolgreich gespeichert!' :
|
||||
'Settings saved successfully!';
|
||||
showNotification(successMsg, 'success');
|
||||
closeModal('settingsModal');
|
||||
} else {
|
||||
showNotification('Fehler beim Speichern der Einstellungen: ' + result.message, 'error');
|
||||
const errorMsg = currentLanguage === 'de' ?
|
||||
'Fehler beim Speichern der Einstellungen: ' + result.message :
|
||||
'Error saving settings: ' + result.message;
|
||||
showNotification(errorMsg, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving settings:', error);
|
||||
showNotification('Fehler beim Speichern der Einstellungen', 'error');
|
||||
const errorMsg = currentLanguage === 'de' ?
|
||||
'Fehler beim Speichern der Einstellungen' :
|
||||
'Error saving settings';
|
||||
showNotification(errorMsg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -643,6 +643,9 @@ function changeLanguage() {
|
||||
// Save language preference
|
||||
localStorage.setItem('ninjacross_language', currentLanguage);
|
||||
|
||||
// Update flag in select
|
||||
updateLanguageFlag();
|
||||
|
||||
// Translate all elements with data attributes
|
||||
const elementsToTranslate = document.querySelectorAll('[data-de][data-en]');
|
||||
elementsToTranslate.forEach(element => {
|
||||
@@ -655,6 +658,20 @@ function changeLanguage() {
|
||||
console.log(`🌐 Language changed to: ${currentLanguage}`);
|
||||
}
|
||||
|
||||
// Update flag in language selector
|
||||
function updateLanguageFlag() {
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
if (currentLanguage === 'de') {
|
||||
// German flag (black-red-gold)
|
||||
languageSelect.style.backgroundImage = `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="15" viewBox="0 0 20 15"><rect width="20" height="5" fill="%23000000"/><rect y="5" width="20" height="5" fill="%23DD0000"/><rect y="10" width="20" height="5" fill="%23FFCE00"/></svg>')`;
|
||||
} else {
|
||||
// USA flag
|
||||
languageSelect.style.backgroundImage = `url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="20" height="15" viewBox="0 0 20 15"><rect width="20" height="15" fill="%23B22234"/><rect width="20" height="1.15" fill="%23FFFFFF"/><rect y="2.3" width="20" height="1.15" fill="%23FFFFFF"/><rect y="4.6" width="20" height="1.15" fill="%23FFFFFF"/><rect y="6.9" width="20" height="1.15" fill="%23FFFFFF"/><rect y="9.2" width="20" height="1.15" fill="%23FFFFFF"/><rect y="11.5" width="20" height="1.15" fill="%23FFFFFF"/><rect y="13.8" width="20" height="1.15" fill="%23FFFFFF"/><rect width="7.7" height="8.05" fill="%230033A0"/></svg>')`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update dynamic content that's not in HTML
|
||||
function updateDynamicContent() {
|
||||
// Update location select placeholder
|
||||
@@ -710,6 +727,8 @@ function loadLanguagePreference() {
|
||||
const languageSelect = document.getElementById('languageSelect');
|
||||
if (languageSelect) {
|
||||
languageSelect.value = currentLanguage;
|
||||
// Update flag when loading
|
||||
updateLanguageFlag();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user