/** * DC-077: i18n Frontend — Language selector and translation system * * Provides window.DCI18n.t(key) for the dashboard frontend. * Loads translations from /api/v1/i18n/translations/:lang * Language preference stored in localStorage. * Handles RTL for Arabic. */ (function () { 'use strict'; const STORAGE_KEY = 'dashcaddy-language'; const DEFAULT_LANG = 'en'; const SUPPORTED_LANGS = ['en', 'es', 'fr', 'de', 'ar']; const LANG_NAMES = { en: 'English', es: 'Español', fr: 'Français', de: 'Deutsch', ar: 'العربية', }; let currentLang = localStorage.getItem(STORAGE_KEY) || DEFAULT_LANG; let translations = {}; let loaded = false; async function loadTranslations(lang) { if (lang === DEFAULT_LANG) { translations = {}; // English is the default — no translation needed loaded = true; return; } try { const res = await fetch(`/api/v1/i18n/translations/${lang}`); if (res.ok) { const data = await res.json(); translations = data.translations || {}; loaded = true; } } catch (e) { console.warn('[i18n] Failed to load translations for', lang, e); } } function t(key) { if (currentLang === DEFAULT_LANG) return key; return translations[key] || key; } function setLanguage(lang) { if (!SUPPORTED_LANGS.includes(lang)) return; currentLang = lang; localStorage.setItem(STORAGE_KEY, lang); // RTL handling const isRtl = lang === 'ar'; document.documentElement.dir = isRtl ? 'rtl' : 'ltr'; document.documentElement.lang = lang; loadTranslations(lang).then(() => { applyTranslations(); }); } function getLanguage() { return currentLang; } function applyTranslations() { // Apply translations to elements with data-i18n attributes document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); const translated = t(key); if (translated && translated !== key) { el.textContent = translated; } }); // Apply to placeholders document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { const key = el.getAttribute('data-i18n-placeholder'); const translated = t(key); if (translated && translated !== key) { el.placeholder = translated; } }); // Apply to titles document.querySelectorAll('[data-i18n-title]').forEach(el => { const key = el.getAttribute('data-i18n-title'); const translated = t(key); if (translated && translated !== key) { el.title = translated; } }); } function createLanguageSelector() { // Find the top bar area to insert the selector // Look for the auth-settings area or the header actions const targetContainer = document.querySelector('.header-actions') || document.querySelector('#auth-settings-btn')?.parentElement || document.querySelector('.top-bar-actions'); if (!targetContainer) { // If we can't find a target, try to add it near the settings button const settingsBtn = document.getElementById('auth-settings-btn'); if (settingsBtn && settingsBtn.parentElement) { return createDropdown(settingsBtn.parentElement); } return null; } return createDropdown(targetContainer); } function createDropdown(container) { // Check if selector already exists if (document.getElementById('dc-lang-selector')) return; const wrapper = document.createElement('div'); wrapper.id = 'dc-lang-selector'; wrapper.style.cssText = 'display: inline-flex; align-items: center; gap: 4px; margin: 0 8px; position: relative;'; const btn = document.createElement('button'); btn.id = 'dc-lang-btn'; btn.className = 'lang-selector-btn'; btn.style.cssText = 'background: var(--card-base, #2a2a3e); border: 1px solid var(--border, #3a3a4e); color: var(--text-primary, #e0e0e0); padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 0.8rem; display: flex; align-items: center; gap: 4px;'; btn.innerHTML = `🌐 ${currentLang.toUpperCase()}`; btn.title = 'Language / Idioma / Langue / Sprache / اللغة'; const dropdown = document.createElement('div'); dropdown.id = 'dc-lang-dropdown'; dropdown.style.cssText = 'display: none; position: absolute; top: 100%; right: 0; margin-top: 4px; background: var(--card-base, #2a2a3e); border: 1px solid var(--border, #3a3a4e); border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 9999; min-width: 140px; overflow: hidden;'; SUPPORTED_LANGS.forEach(lang => { const item = document.createElement('div'); item.className = 'lang-option'; item.style.cssText = 'padding: 8px 14px; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 0.85rem; color: var(--text-primary, #e0e0e0);'; item.onmouseenter = () => item.style.background = 'var(--card-hover, rgba(255,255,255,0.05))'; item.onmouseleave = () => item.style.background = 'transparent'; const flag = document.createElement('span'); flag.textContent = lang === currentLang ? '✓' : ''; flag.style.cssText = 'width: 16px; color: var(--ok-fg, #4ade80);'; const name = document.createElement('span'); name.textContent = LANG_NAMES[lang]; item.appendChild(flag); item.appendChild(name); item.onclick = () => { setLanguage(lang); dropdown.style.display = 'none'; // Update button text btn.querySelector('.lang-current').textContent = lang.toUpperCase(); // Update checkmarks dropdown.querySelectorAll('.lang-option').forEach((opt, i) => { opt.querySelector('span').textContent = SUPPORTED_LANGS[i] === lang ? '✓' : ''; }); // Show notification if (window.showNotification) { window.showNotification(`Language: ${LANG_NAMES[lang]}`, 'info'); } }; dropdown.appendChild(item); }); btn.onclick = (e) => { e.stopPropagation(); dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none'; }; // Close on outside click document.addEventListener('click', (e) => { if (!wrapper.contains(e.target)) { dropdown.style.display = 'none'; } }); wrapper.appendChild(btn); wrapper.appendChild(dropdown); container.insertBefore(wrapper, container.firstChild); return wrapper; } // Initialize on page load function init() { // Set initial RTL if needed if (currentLang === 'ar') { document.documentElement.dir = 'rtl'; document.documentElement.lang = 'ar'; } // Create the language selector after DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { createLanguageSelector(); if (currentLang !== DEFAULT_LANG) loadTranslations(currentLang).then(applyTranslations); }); } else { createLanguageSelector(); if (currentLang !== DEFAULT_LANG) loadTranslations(currentLang).then(applyTranslations); } } // Expose globally window.DCI18n = { t, setLanguage, getLanguage, applyTranslations, loadTranslations }; // Auto-init init(); })();