Files
Sdk_TV_app/static/js/i18n.js
T
2025-11-14 17:03:30 +01:00

284 lines
8.1 KiB
JavaScript

/**
* 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();
// Signal that translations are ready
window.dispatchEvent(new Event('i18nReady'));
}
} catch (error) {
console.error('Failed to initialize i18n:', error);
// Even on error, signal ready so page doesn't get stuck
window.dispatchEvent(new Event('i18nReady'));
}
}
/**
* 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;
// Export i18n object with useful functions
window.i18n = {
t: t,
updatePageTranslations: translatePage,
changeLanguage: changeLanguage
};