Enhance print layouts with branded headers and fix navigation consistency
- Replace plain print headers with full branded headers including logo - Add dynamic tournament-type styling (🎯 4-target, ⚡ 20-target, 💪 40-target) - Remove border lines and optimize spacing for clean print appearance - Fix emoji positioning in league championship headers - Standardize navigation with proper active button indicators - Add missing translation keys for calculator instructions - Update print media queries for professional document output Print improvements: - Logo and branding now appear on printed results - Consistent 20px spacing between header and table - Clean white background with subtle borders - Optimized typography for print readability Navigation fixes: - Added active button highlighting across all PC pages - Consistent navigation order: Dashboard → Tournament → Player Analysis → Archive → Draft → Calculator - Fixed draft page active indicator 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com> This commit message covers all the major improvements we made: - Print layout enhancements with branded headers - Navigation standardization and active indicators - Translation fixes - Visual styling improvements - Professional document output optimization
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Internationalization (i18n) support for the application
|
||||
*/
|
||||
|
||||
// Global translations object
|
||||
let currentTranslations = {};
|
||||
let currentLanguage = 'sl';
|
||||
|
||||
/**
|
||||
* Initialize i18n system
|
||||
*/
|
||||
async function initI18n() {
|
||||
try {
|
||||
const response = await fetch('/api/language');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
currentTranslations = data.translations;
|
||||
currentLanguage = data.current_language;
|
||||
|
||||
// Update language selector if it exists
|
||||
updateLanguageSelector();
|
||||
|
||||
// Translate the page
|
||||
translatePage();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize i18n:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation for a key with dot notation
|
||||
* @param {string} key - Translation key (e.g., 'general.save')
|
||||
* @param {object} params - Optional parameters for string interpolation
|
||||
* @returns {string} Translated text or key if not found
|
||||
*/
|
||||
function t(key, params = {}) {
|
||||
const keys = key.split('.');
|
||||
let value = currentTranslations;
|
||||
|
||||
for (const k of keys) {
|
||||
if (value && typeof value === 'object' && k in value) {
|
||||
value = value[k];
|
||||
} else {
|
||||
console.warn(`Translation key not found: ${key}`);
|
||||
return key; // Return key if translation not found
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
console.warn(`Translation value is not a string: ${key}`);
|
||||
return key;
|
||||
}
|
||||
|
||||
// Simple string interpolation
|
||||
let result = value;
|
||||
for (const [param, replacement] of Object.entries(params)) {
|
||||
result = result.replace(new RegExp(`\\{${param}\\}`, 'g'), replacement);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change application language
|
||||
* @param {string} language - Language code (e.g., 'sl', 'en')
|
||||
*/
|
||||
async function changeLanguage(language) {
|
||||
try {
|
||||
const response = await fetch(`/api/language/${language}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
currentTranslations = data.translations;
|
||||
currentLanguage = language;
|
||||
|
||||
// Update UI
|
||||
updateLanguageSelector();
|
||||
translatePage();
|
||||
} else {
|
||||
throw new Error('Failed to change language');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error changing language:', error);
|
||||
showNotification(t('messages.error'), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update language selector UI
|
||||
*/
|
||||
function updateLanguageSelector() {
|
||||
const selector = document.getElementById('languageSelector');
|
||||
if (selector) {
|
||||
selector.value = currentLanguage;
|
||||
}
|
||||
|
||||
// Update flag if exists
|
||||
const flag = document.getElementById('languageFlag');
|
||||
if (flag) {
|
||||
flag.textContent = currentLanguage === 'sl' ? '🇸🇮' : '🇬🇧';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate all elements with data-i18n attribute
|
||||
*/
|
||||
function translatePage() {
|
||||
// Translate elements with data-i18n attribute
|
||||
const elements = document.querySelectorAll('[data-i18n]');
|
||||
elements.forEach(element => {
|
||||
const key = element.getAttribute('data-i18n');
|
||||
if (key) {
|
||||
element.textContent = t(key);
|
||||
}
|
||||
});
|
||||
|
||||
// Translate placeholders
|
||||
const placeholderElements = document.querySelectorAll('[data-i18n-placeholder]');
|
||||
placeholderElements.forEach(element => {
|
||||
const key = element.getAttribute('data-i18n-placeholder');
|
||||
if (key) {
|
||||
element.placeholder = t(key);
|
||||
}
|
||||
});
|
||||
|
||||
// Translate titles/tooltips
|
||||
const titleElements = document.querySelectorAll('[data-i18n-title]');
|
||||
titleElements.forEach(element => {
|
||||
const key = element.getAttribute('data-i18n-title');
|
||||
if (key) {
|
||||
element.title = t(key);
|
||||
}
|
||||
});
|
||||
|
||||
// Translate aria-labels
|
||||
const ariaElements = document.querySelectorAll('[data-i18n-aria]');
|
||||
ariaElements.forEach(element => {
|
||||
const key = element.getAttribute('data-i18n-aria');
|
||||
if (key) {
|
||||
element.setAttribute('aria-label', t(key));
|
||||
}
|
||||
});
|
||||
|
||||
// Translate tooltips
|
||||
const tooltipElements = document.querySelectorAll('[data-i18n-tooltip]');
|
||||
tooltipElements.forEach(element => {
|
||||
const key = element.getAttribute('data-i18n-tooltip');
|
||||
if (key) {
|
||||
element.setAttribute('data-tooltip', t(key));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification message
|
||||
*/
|
||||
function showNotification(message, type = 'info') {
|
||||
// Create notification element if it doesn't exist
|
||||
let notification = document.getElementById('i18nNotification');
|
||||
if (!notification) {
|
||||
notification = document.createElement('div');
|
||||
notification.id = 'i18nNotification';
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
z-index: 10000;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateX(100%);
|
||||
`;
|
||||
document.body.appendChild(notification);
|
||||
}
|
||||
|
||||
// Set color based on type
|
||||
const colors = {
|
||||
success: '#28a745',
|
||||
error: '#dc3545',
|
||||
warning: '#ffc107',
|
||||
info: '#007bff'
|
||||
};
|
||||
|
||||
notification.style.backgroundColor = colors[type] || colors.info;
|
||||
notification.textContent = message;
|
||||
notification.style.transform = 'translateX(0)';
|
||||
|
||||
// Hide after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.transform = 'translateX(100%)';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create language selector dropdown
|
||||
*/
|
||||
function createLanguageSelector(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
const selector = document.createElement('select');
|
||||
selector.id = 'languageSelector';
|
||||
selector.style.cssText = `
|
||||
width: 100%;
|
||||
padding: 14px 18px;
|
||||
border: 2px solid #dee2e6;
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
color: #495057;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: 12px;
|
||||
`;
|
||||
|
||||
// Add options
|
||||
const languages = [
|
||||
{ code: 'sl', name: '🇸🇮 Slovenščina', flag: '🇸🇮' },
|
||||
{ code: 'en', name: '🇬🇧 English', flag: '🇬🇧' }
|
||||
];
|
||||
|
||||
languages.forEach(lang => {
|
||||
const option = document.createElement('option');
|
||||
option.value = lang.code;
|
||||
option.textContent = lang.name;
|
||||
selector.appendChild(option);
|
||||
});
|
||||
|
||||
selector.value = currentLanguage;
|
||||
|
||||
// Add event listeners
|
||||
selector.addEventListener('change', (e) => {
|
||||
changeLanguage(e.target.value);
|
||||
});
|
||||
|
||||
// Add hover effects to match nav-link styling
|
||||
selector.addEventListener('mouseenter', () => {
|
||||
selector.style.background = '#f8f9fa';
|
||||
selector.style.borderColor = '#007bff';
|
||||
selector.style.color = '#007bff';
|
||||
selector.style.transform = 'translateY(-2px)';
|
||||
selector.style.boxShadow = '0 5px 12px rgba(0, 123, 255, 0.15)';
|
||||
});
|
||||
|
||||
selector.addEventListener('mouseleave', () => {
|
||||
selector.style.background = 'white';
|
||||
selector.style.borderColor = '#dee2e6';
|
||||
selector.style.color = '#495057';
|
||||
selector.style.transform = 'translateY(0)';
|
||||
selector.style.boxShadow = '0 3px 6px rgba(0, 0, 0, 0.1)';
|
||||
});
|
||||
|
||||
container.appendChild(selector);
|
||||
}
|
||||
|
||||
// Initialize when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initI18n);
|
||||
|
||||
// Export functions for global use
|
||||
window.t = t;
|
||||
window.changeLanguage = changeLanguage;
|
||||
window.createLanguageSelector = createLanguageSelector;
|
||||
Reference in New Issue
Block a user