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:
2025-09-10 19:40:57 +02:00
parent 11d0647ab9
commit 340e22a815
7 changed files with 541 additions and 149 deletions

View File

@@ -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');
}
}

View File

@@ -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();
}
}
}