(() => { const LANG_KEY = 'rosomat.language'; const CACHE_KEY = 'rosomat.translation.cache.ar_en.v1'; const TRANSLATE_URL = 'https://translate.googleapis.com/translate_a/single'; const SEP = '[[[__ROSOMAT_SEP__]]]'; const ARABIC_RE = /[\u0600-\u06FF]/; const EXCLUDED_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'CODE', 'PRE', 'SVG', 'TEXTAREA']); const state = { current: 'ar', cache: loadCache(), textTargets: [], attrTargets: [], titleSource: '', applying: false, observer: null, refreshTimer: null, pendingRequest: null }; function loadCache() { try { const raw = localStorage.getItem(CACHE_KEY); if (!raw) return {}; const parsed = JSON.parse(raw); return (parsed && typeof parsed === 'object') ? parsed : {}; } catch (err) { return {}; } } function saveCache() { try { localStorage.setItem(CACHE_KEY, JSON.stringify(state.cache)); } catch (err) { // Ignore storage failures (private mode / quota). } } function containsArabic(value) { return ARABIC_RE.test(value || ''); } function normalizeWhitespace(value) { return (value || '').replace(/\s+/g, ' ').trim(); } function createDesktopSwitch() { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'lang-switch'; btn.setAttribute('data-lang-toggle', 'desktop'); btn.setAttribute('aria-label', 'Switch language'); btn.innerHTML = [ 'AR', 'EN', '' ].join(''); return btn; } function createMobileSwitch() { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'lang-mobile-btn'; btn.setAttribute('data-lang-toggle', 'mobile'); btn.setAttribute('aria-label', 'Switch language'); btn.innerHTML = 'English'; return btn; } function injectSwitches() { const actions = document.querySelector('.navbar__actions'); if (actions && !actions.querySelector('[data-lang-toggle="desktop"]')) { actions.insertBefore(createDesktopSwitch(), actions.firstChild || null); } const navLinks = document.getElementById('navLinks'); if (navLinks && !navLinks.querySelector('[data-lang-toggle="mobile"]')) { navLinks.appendChild(createMobileSwitch()); } } function syncSwitchUI(lang) { const isEN = lang === 'en'; document.querySelectorAll('.lang-switch').forEach((el) => { el.classList.toggle('is-en', isEN); el.setAttribute('aria-pressed', isEN ? 'true' : 'false'); }); document.querySelectorAll('.lang-mobile-btn').forEach((el) => { el.setAttribute('aria-pressed', isEN ? 'true' : 'false'); const label = el.querySelector('span'); if (label) label.textContent = isEN ? 'العربية' : 'English'; }); } function setDocumentDirection(lang) { const html = document.documentElement; const isEN = lang === 'en'; html.setAttribute('lang', isEN ? 'en' : 'ar'); html.setAttribute('dir', isEN ? 'ltr' : 'rtl'); document.body.classList.toggle('lang-en', isEN); } function trackTextNodes() { const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); while (walker.nextNode()) { const node = walker.currentNode; const parent = node.parentElement; if (!parent) continue; if (EXCLUDED_TAGS.has(parent.tagName)) continue; if (parent.closest('[data-no-translate]')) continue; if (!node.__rosomatI18n) { const source = node.nodeValue || ''; const key = normalizeWhitespace(source); if (!key || !containsArabic(key)) continue; const prefix = (source.match(/^\s*/) || [''])[0]; const suffix = (source.match(/\s*$/) || [''])[0]; node.__rosomatI18n = { source, key, prefix, suffix }; state.textTargets.push(node); } } } function trackAttributeTargets() { const attrs = ['placeholder', 'title', 'aria-label']; const elements = document.body.querySelectorAll('*'); elements.forEach((el) => { if (el.closest('[data-no-translate]')) return; if (!el.__rosomatI18nAttrs) el.__rosomatI18nAttrs = {}; attrs.forEach((attr) => { const current = el.getAttribute(attr); if (!current) return; const key = normalizeWhitespace(current); if (!key) return; if (!el.__rosomatI18nAttrs[attr]) { if (!containsArabic(key)) return; el.__rosomatI18nAttrs[attr] = { source: current, key }; state.attrTargets.push({ el, attr }); } }); }); } function trackTitle() { if (state.titleSource) return; const title = document.title || ''; if (!containsArabic(title)) return; state.titleSource = title; } function compactTargetLists() { state.textTargets = state.textTargets.filter((node) => node && node.isConnected && node.__rosomatI18n); state.attrTargets = state.attrTargets.filter((target) => { if (!target || !target.el || !target.el.isConnected) return false; return Boolean(target.el.__rosomatI18nAttrs && target.el.__rosomatI18nAttrs[target.attr]); }); } function collectMissingKeys() { const missing = new Set(); state.textTargets.forEach((node) => { const meta = node.__rosomatI18n; if (!meta || !meta.key) return; if (!state.cache[meta.key]) missing.add(meta.key); }); state.attrTargets.forEach(({ el, attr }) => { const meta = el.__rosomatI18nAttrs && el.__rosomatI18nAttrs[attr]; if (!meta || !meta.key) return; if (!state.cache[meta.key]) missing.add(meta.key); }); if (state.titleSource) { const key = normalizeWhitespace(state.titleSource); if (key && !state.cache[key]) missing.add(key); } return Array.from(missing); } async function translateQuery(rawText) { const url = `${TRANSLATE_URL}?client=gtx&sl=ar&tl=en&dt=t&q=${encodeURIComponent(rawText)}`; const response = await fetch(url, { method: 'GET' }); if (!response.ok) { throw new Error(`Translate request failed: ${response.status}`); } const payload = await response.json(); if (!Array.isArray(payload) || !Array.isArray(payload[0])) { return rawText; } return payload[0].map((part) => Array.isArray(part) ? (part[0] || '') : '').join(''); } function buildChunks(list) { const chunks = []; let current = []; let size = 0; list.forEach((item) => { const projected = size + item.length + SEP.length; if (current.length > 0 && (projected > 3600 || current.length >= 40)) { chunks.push(current); current = []; size = 0; } current.push(item); size += item.length + SEP.length; }); if (current.length > 0) chunks.push(current); return chunks; } async function translateMissingKeys(missing) { if (!missing.length) return; const chunks = buildChunks(missing); for (const chunk of chunks) { if (state.pendingRequest && state.pendingRequest.lang === 'ar') { break; } try { const packed = chunk.join(SEP); const translatedPacked = await translateQuery(packed); const pieces = translatedPacked.split(SEP); if (pieces.length === chunk.length) { chunk.forEach((source, idx) => { state.cache[source] = (pieces[idx] || source).trim() || source; }); } else { // Fallback for unexpected separator handling. for (const source of chunk) { try { const translated = await translateQuery(source); state.cache[source] = translated || source; } catch (err) { state.cache[source] = source; } } } } catch (err) { chunk.forEach((source) => { if (!state.cache[source]) state.cache[source] = source; }); } } saveCache(); } function applyEnglish() { state.textTargets.forEach((node) => { const meta = node.__rosomatI18n; if (!meta) return; const translated = state.cache[meta.key] || meta.key; node.nodeValue = `${meta.prefix}${translated}${meta.suffix}`; }); state.attrTargets.forEach(({ el, attr }) => { const meta = el.__rosomatI18nAttrs && el.__rosomatI18nAttrs[attr]; if (!meta) return; const translated = state.cache[meta.key] || meta.key; el.setAttribute(attr, translated); }); if (state.titleSource) { const key = normalizeWhitespace(state.titleSource); document.title = state.cache[key] || state.titleSource; } } function applyArabic() { state.textTargets.forEach((node) => { const meta = node.__rosomatI18n; if (!meta) return; node.nodeValue = meta.source; }); state.attrTargets.forEach(({ el, attr }) => { const meta = el.__rosomatI18nAttrs && el.__rosomatI18nAttrs[attr]; if (!meta) return; el.setAttribute(attr, meta.source); }); if (state.titleSource) { document.title = state.titleSource; } } async function applyLanguage(lang, persist = true) { const targetLang = lang === 'en' ? 'en' : 'ar'; if (state.applying) { state.pendingRequest = { lang: targetLang, persist }; return; } clearTimeout(state.refreshTimer); state.applying = true; try { state.current = targetLang; setDocumentDirection(state.current); trackTextNodes(); trackAttributeTargets(); trackTitle(); compactTargetLists(); if (state.current === 'en') { const missing = collectMissingKeys(); await translateMissingKeys(missing); if (state.pendingRequest && state.pendingRequest.lang === 'ar') { return; } applyEnglish(); } else { applyArabic(); } syncSwitchUI(state.current); if (persist) localStorage.setItem(LANG_KEY, state.current); } finally { state.applying = false; const pending = state.pendingRequest; state.pendingRequest = null; if (pending && pending.lang !== state.current) { applyLanguage(pending.lang, pending.persist); } } } function scheduleRefreshForEnglish() { if (state.current !== 'en' || state.applying) return; clearTimeout(state.refreshTimer); state.refreshTimer = setTimeout(() => { applyLanguage('en', false); }, 220); } function setupObserver() { if (state.observer) return; state.observer = new MutationObserver((mutations) => { if (state.current !== 'en' || state.applying) return; const hasAddedNodes = mutations.some((m) => m.addedNodes && m.addedNodes.length > 0); if (!hasAddedNodes) return; scheduleRefreshForEnglish(); }); state.observer.observe(document.body, { childList: true, subtree: true }); } function bindSwitches() { document.querySelectorAll('[data-lang-toggle]').forEach((btn) => { btn.addEventListener('click', () => { const next = state.current === 'ar' ? 'en' : 'ar'; applyLanguage(next, true); }); }); } document.addEventListener('DOMContentLoaded', () => { injectSwitches(); bindSwitches(); setupObserver(); const preferred = localStorage.getItem(LANG_KEY); applyLanguage(preferred === 'en' ? 'en' : 'ar', false); }); })();