1(function () { 2 'use strict'; 3 4 var DEFAULT_LANG = 'es'; 5 var SUPPORTED_LANGS = ['es', 'en']; 6 var translations = {}; 7 var currentLang = DEFAULT_LANG; 8 9 function getStoredLang() { 10 try { 11 return localStorage.getItem('festivalle-lang'); 12 } catch (e) { 13 return null; 14 } 15 } 16 17 function setStoredLang(lang) { 18 try { 19 localStorage.setItem('festivalle-lang', lang); 20 } catch (e) {} 21 } 22 23 function detectLang() { 24 var stored = getStoredLang(); 25 if (stored && SUPPORTED_LANGS.indexOf(stored) !== -1) return stored; 26 27 var urlParams = new URLSearchParams(window.location.search); 28 var urlLang = urlParams.get('lang'); 29 if (urlLang && SUPPORTED_LANGS.indexOf(urlLang) !== -1) return urlLang; 30 31 var browserLang = (navigator.language || navigator.userLanguage || '').substring(0, 2); 32 if (SUPPORTED_LANGS.indexOf(browserLang) !== -1) return browserLang; 33 34 return DEFAULT_LANG; 35 } 36 37 function getNestedValue(obj, path) { 38 var keys = path.split('.'); 39 var val = obj; 40 for (var i = 0; i < keys.length; i++) { 41 if (val == null) return undefined; 42 val = val[keys[i]]; 43 } 44 return val; 45 } 46 47 function t(key) { 48 var val = getNestedValue(translations[currentLang], key); 49 if (val !== undefined) return val; 50 val = getNestedValue(translations[DEFAULT_LANG], key); 51 return val !== undefined ? val : key; 52 } 53 54 function applyTranslations() { 55 document.documentElement.lang = currentLang; 56 57 // data-i18n: sets textContent 58 var elements = document.querySelectorAll('[data-i18n]'); 59 for (var i = 0; i < elements.length; i++) { 60 var el = elements[i]; 61 var key = el.getAttribute('data-i18n'); 62 var val = t(key); 63 if (typeof val === 'string') { 64 el.textContent = val; 65 } 66 } 67 68 // data-i18n-html: sets innerHTML (for content with HTML) 69 var htmlElements = document.querySelectorAll('[data-i18n-html]'); 70 for (var i = 0; i < htmlElements.length; i++) { 71 var el = htmlElements[i]; 72 var key = el.getAttribute('data-i18n-html'); 73 var val = t(key); 74 if (typeof val === 'string') { 75 el.innerHTML = val; 76 } 77 } 78 79 // data-i18n-placeholder 80 var placeholders = document.querySelectorAll('[data-i18n-placeholder]'); 81 for (var i = 0; i < placeholders.length; i++) { 82 var el = placeholders[i]; 83 var key = el.getAttribute('data-i18n-placeholder'); 84 var val = t(key); 85 if (typeof val === 'string') { 86 el.placeholder = val; 87 } 88 } 89 90 // Document title + meta description. 91 // Páginas con data-i18n-meta="<scope>" en <html> usan <scope>.metaTitle/.metaDescription; 92 // el resto usa las claves globales meta.title / meta.description. 93 var metaScope = document.documentElement.getAttribute('data-i18n-meta'); 94 var titleKey = metaScope ? metaScope + '.metaTitle' : 'meta.title'; 95 var descKey = metaScope ? metaScope + '.metaDescription' : 'meta.description'; 96 var title = t(titleKey); 97 if (title) document.title = title; 98 99 // Update meta description 100 var metaDesc = document.querySelector('meta[name="description"]'); 101 if (metaDesc) { 102 var desc = t(descKey); 103 if (desc) metaDesc.setAttribute('content', desc); 104 } 105 106 // Update language switcher active state 107 var switchers = document.querySelectorAll('[data-lang-switch]'); 108 for (var i = 0; i < switchers.length; i++) { 109 var btn = switchers[i]; 110 if (btn.getAttribute('data-lang-switch') === currentLang) { 111 btn.classList.add('active'); 112 } else { 113 btn.classList.remove('active'); 114 } 115 } 116 117 // Translate press cards (history page) 118 var isEn = currentLang === 'en'; 119 var pressCards = document.querySelectorAll('.press-card'); 120 for (var i = 0; i < pressCards.length; i++) { 121 var card = pressCards[i]; 122 var titleEl = card.querySelector('.press-card-title'); 123 var descEl = card.querySelector('.press-card-desc'); 124 var linkEl = card.querySelector('.press-card-link'); 125 if (titleEl) { 126 var enTitle = card.getAttribute('data-title-en'); 127 var esTitle = card.getAttribute('data-title-es') || titleEl.getAttribute('data-original'); 128 if (!titleEl.getAttribute('data-original')) titleEl.setAttribute('data-original', titleEl.textContent); 129 if (isEn && enTitle) titleEl.textContent = enTitle; 130 else if (!isEn && titleEl.getAttribute('data-original')) titleEl.textContent = titleEl.getAttribute('data-original'); 131 } 132 if (descEl) { 133 var enDesc = card.getAttribute('data-desc-en'); 134 var esDesc = descEl.getAttribute('data-original'); 135 if (!descEl.getAttribute('data-original')) descEl.setAttribute('data-original', descEl.textContent); 136 if (isEn && enDesc) descEl.textContent = enDesc; 137 else if (!isEn && descEl.getAttribute('data-original')) descEl.textContent = descEl.getAttribute('data-original'); 138 } 139 if (linkEl) { 140 if (!linkEl.getAttribute('data-original')) linkEl.setAttribute('data-original', linkEl.textContent); 141 if (isEn) { 142 var origLink = linkEl.getAttribute('data-original') || linkEl.textContent; 143 linkEl.textContent = origLink.indexOf('deo') >= 0 ? 'Watch video â' : 'Read article â'; 144 } else { 145 linkEl.textContent = linkEl.getAttribute('data-original'); 146 } 147 } 148 } 149 // Translate press awards 150 var awards = document.querySelectorAll('.press-award'); 151 for (var i = 0; i < awards.length; i++) { 152 var award = awards[i]; 153 var aTitle = award.querySelector('.press-award-title'); 154 var aSub = award.querySelector('.press-award-subtitle'); 155 if (aTitle) { 156 if (!aTitle.getAttribute('data-original')) aTitle.setAttribute('data-original', aTitle.textContent); 157 if (isEn) { 158 var txt = aTitle.getAttribute('data-original'); 159 if (txt === 'Mejor Festival del Año') aTitle.textContent = 'Best Festival of the Year'; 160 else if (txt === 'Vuelta tras la pandemia') aTitle.textContent = 'Back After the Pandemic'; 161 else aTitle.textContent = txt; 162 } else { 163 aTitle.textContent = aTitle.getAttribute('data-original'); 164 } 165 } 166 if (aSub) { 167 if (!aSub.getAttribute('data-original')) aSub.setAttribute('data-original', aSub.textContent); 168 if (isEn) { 169 var stxt = aSub.getAttribute('data-original'); 170 if (stxt.indexOf('2º año') >= 0) aSub.textContent = 'Ruta 66 Magazine â 2nd consecutive year'; 171 else if (stxt.indexOf('Revista Ruta') >= 0) aSub.textContent = 'Ruta 66 Magazine'; 172 else if (stxt.indexOf('festival regresa') >= 0) aSub.textContent = 'XIV Edition â The festival returns'; 173 else aSub.textContent = stxt; 174 } else { 175 aSub.textContent = aSub.getAttribute('data-original'); 176 } 177 } 178 } 179 180 // Re-apply circular text rotation (scripts.js splits text into spans) 181 var rotateTexts = document.querySelectorAll('.rotate-text p, .rotate-text2 p, .rotate-text3 p'); 182 for (var i = 0; i < rotateTexts.length; i++) { 183 var el = rotateTexts[i]; 184 var plainText = el.textContent; 185 if (plainText && plainText.length > 0) { 186 el.innerHTML = plainText.split('').map(function (char, idx) { 187 return '<span style="transform:rotate(' + (idx * 9.6) + 'deg)">' + char + '</span>'; 188 }).join(''); 189 } 190 } 191 192 // Avisar a otros módulos (banner de cookies) de que se aplicaron traducciones. 193 try { 194 document.dispatchEvent(new CustomEvent('fv:i18n-applied', { detail: { lang: currentLang } })); 195 } catch (e) {} 196 } 197 198 function loadTranslation(lang, callback) { 199 if (translations[lang]) { 200 callback(); 201 return; 202 } 203 var xhr = new XMLHttpRequest(); 204 xhr.open('GET', 'assets/i18n/' + lang + '.json', true); 205 xhr.onreadystatechange = function () { 206 if (xhr.readyState === 4) { 207 if (xhr.status === 200) { 208 try { 209 translations[lang] = JSON.parse(xhr.responseText); 210 } catch (e) { 211 console.error('i18n: parse error for ' + lang, e); 212 } 213 } 214 callback(); 215 } 216 }; 217 xhr.send(); 218 } 219 220 function switchLanguage(lang) { 221 if (SUPPORTED_LANGS.indexOf(lang) === -1) return; 222 currentLang = lang; 223 setStoredLang(lang); 224 loadTranslation(lang, function () { 225 applyTranslations(); 226 }); 227 } 228
229 function init() { 230 currentLang = detectLang(); 231 232 // Load default + current language 233 loadTranslation(DEFAULT_LANG, function () { 234 if (currentLang !== DEFAULT_LANG) { 235 loadTranslation(currentLang, function () { 236 applyTranslations(); 237 }); 238 } else { 239 applyTranslations(); 240 } 241 }); 242 243 // Bind language switcher clicks 244 document.addEventListener('click', function (e) { 245 var btn = e.target.closest('[data-lang-switch]'); 246 if (btn) { 247 e.preventDefault(); 248 var lang = btn.getAttribute('data-lang-switch'); 249 switchLanguage(lang); 250 } 251 }); 252 } 253 254 // Expose globally 255 window.i18n = { 256 t: t, 257 switchLanguage: switchLanguage, 258 getCurrentLang: function () { return currentLang; } 259 }; 260 261 // Init on DOM ready 262 if (document.readyState === 'loading') { 263 document.addEventListener('DOMContentLoaded', init); 264 } else { 265 init(); 266 } 267})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.