const phpInitialLang = ''; let currentEntries = {}; const SUGGESTION_LIMIT_DISPLAY = 20; const searchCache = new Map(); const MAX_HISTORY_ITEMS = 50; const symbolMapping = { "[N1]": "/اسم/", "[N2]": "/صفة/", "[N3]": "/ظرف/", "[N4]": "/ضمير/", "[N5]": "/اسم الإشارة/", "[N6]": "/اسم الموصول/", "[N7]": "/كلمة مركبة/", "[N8]": "/اسم فعل/", "[N9]": "/علم/", "[V1]": "/فعل مجرد/", "[V2]": "/فعل مزيد/", "[V3]": "/فعل جامد/", "[P1]": "/حرف/", "[P2]": "/عبارة/", "[P3]": "/اسم استفهام/", "[P4]": "/عدد/", "[P5]": "/اسم شرط/", "[P6]": "/مصدر/", "[S7]": "/تركيب وصفي/", "[D2]": "/مصطلح/" }; const THEME_KEY = 'zotoThemePreference'; const LANGUAGE_KEY = 'zotoLanguagePreference'; let currentLang = phpInitialLang; let activeAudioElement = null; function getUIText(key) { const element = document.querySelector(`[data-key="${key}"]`) || document.getElementById(key); if (element) { return element.dataset[currentLang] || element.dataset['fr'] || element.textContent; } switch (key) { case 'copied': return currentLang === 'ar' ? "تم النسخ!" : (currentLang === 'mg' ? "Efa voadika!" : "Copié !"); case 'copy_error': return currentLang === 'ar' ? "خطأ في النسخ" : (currentLang === 'mg' ? "Misy tsy fetezana ny fandikana" : "Erreur de copie"); case 'copy_failed': return currentLang === 'ar' ? "فشل النسخ" : (currentLang === 'mg' ? "Tsy nahomby ny fandikana" : "Échec copie"); case 'confirm_clear_history': return currentLang === 'ar' ? "هل تريد بالتأكيد مسح كل السجل؟" : (currentLang === 'mg' ? "Tena hofafanaina daholo ve ny raki-pikarohana?" : "Voulez-vous vraiment effacer tout l'historique ?"); case 'history_cleared': return currentLang === 'ar' ? "تم مسح السجل." : (currentLang === 'mg' ? "Efa voafafa ny raki-pikarohana." : "Historique effacé."); case 'history_save_error': return currentLang === 'ar' ? "خطأ في حفظ السجل" : (currentLang === 'mg' ? "Misy tsy fetezana ny fampidirana ny raki-pikarohana" : "Erreur sauvegarde historique"); case 'history_empty': return currentLang === 'ar' ? "السجل فارغ" : (currentLang === 'mg' ? "Foana ny raki-pikarohana" : "L'historique est vide"); case 'tts_unavailable': return currentLang === 'ar' ? "النطق الصوتي غير متوفر" : (currentLang === 'mg' ? "Tsy misy feon-drindrambaiko ny navigateranao" : "Synthèse vocale indisponible"); case 'tts_error': return currentLang === 'ar' ? "خطأ في القراءة" : (currentLang === 'mg' ? "Misy tsy fetezana ny famakiana" : "Erreur de lecture"); case 'suggestion_error': return currentLang === 'ar' ? 'خطأ في الاقتراحات' : (currentLang === 'mg' ? 'Misy tsy fetezana ny sosokevitra' : 'Erreur suggestions'); case 'loading': return currentLang === 'ar' ? 'جار التحميل...' : (currentLang === 'mg' ? 'Eo am-pitelesarizena azy...' : 'Chargement...'); case 'no_results_for': return currentLang === 'ar' ? 'لم يتم العثور على نتائج لـ' : (currentLang === 'mg' ? 'Tsy misy vokatra hita ho an\'ny' : 'Aucun résultat trouvé pour'); case 'results_found': return (count) => currentLang === 'ar' ? `${count} ${count === 1 ? 'نتيجة' : (count === 2 ? 'نتيجتان' : 'نتائج')} لـ ` : (currentLang === 'mg' ? `${count} ny vokatra hita ho an'ny ` : `${count} résultat${count > 1 ? 's' : ''} pour `); case 'dictionary_not_loaded': return currentLang === 'ar' ? 'لم يتم تحميل القاموس.' : (currentLang === 'mg' ? 'Tsy tafiditra ny rakibolana.' : 'Dictionnaire non chargé.'); case 'definition_fetch_error': return currentLang === 'ar' ? 'خطأ في استرداد التعريف (التعريفات)' : (currentLang === 'mg' ? 'Misy tsy fetezana ny fakana famaritana' : 'Erreur récupération définition(s)'); case 'searching': return currentLang === 'ar' ? 'جار البحث...' : (currentLang === 'mg' ? 'Eo am-pitadiavana...' : 'Recherche...'); case 'search_error': return currentLang === 'ar' ? 'خطأ أثناء البحث' : (currentLang === 'mg' ? 'Misy tsy fetezana ny fikarohana' : 'Erreur lors de la recherche'); case 'listen': return currentLang === 'ar' ? 'استماع' : (currentLang === 'mg' ? 'Henoina' : 'Écouter'); case 'remove_item': return currentLang === 'ar' ? 'حذف هذا العنصر' : (currentLang === 'mg' ? 'Fafaina ity singa ity' : 'Supprimer cet élément'); case 'local_storage_warning': return currentLang === 'ar' ? "تحذير: الحفظ المحلي قد يفشل." : (currentLang === 'mg' ? "Fampitandremana: Mety tsy hahomby ny fitehirizana anaty." : "Attention: Sauvegarde locale pourrait échouer."); case 'local_storage_error': return currentLang === 'ar' ? "خطأ: الحفظ المحلي غير متوفر." : (currentLang === 'mg' ? "Tsy fetezana: Tsy misy ny fitehirizana anaty." : "Erreur: Sauvegarde locale indisponible."); case 'critical_error_startup': return currentLang === 'ar' ? 'خطأ في بدء تشغيل التطبيق' : (currentLang === 'mg' ? 'Misy tsy fetezana ny fandefasana ny rindrankajy' : 'Erreur Démarrage Application'); case 'preparing_app': return currentLang === 'ar' ? 'جار تحضير التطبيق...' : (currentLang === 'mg' ? 'Eo am-panomanana azy...' : 'Préparation application...'); case 'homeButton': return currentLang === 'ar' ? 'الرئيسية' : (currentLang === 'mg' ? 'Pejy fandraisana' : 'Accueil'); default: return key; } } function debounce(func, delay) { let t; return function (...a) { clearTimeout(t); t = setTimeout(() => func.apply(this, a), delay); }; } function removeAccents(str) { if(!str) return ''; try { return str.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); } catch (e) { return str.replace(/[àáâäãåā]/g, "a").replace(/[èéêëēėę]/g, "e").replace(/[îïíīįì]/g, "i").replace(/[ôöòóøōõ]/g, "o").replace(/[ûüùúū]/g, "u").replace(/[çćč]/g, "c"); } } function replaceSymbols(text) { if(!text) return ''; let r = text; for (const s in symbolMapping) { r = r.replace(s, symbolMapping[s]); } return r; } function removeParenthesesWords(text) { if(!text) return ''; return text.replace(/(?{t.classList.remove('show');}, duration); } function highlightSimple(text, searchTerm) { if (!text || !searchTerm) return document.createTextNode(text || ''); const fragment = document.createDocumentFragment(); const normalizedSearchTerm = removeAccents(searchTerm.toLowerCase()); const escapedTerm = normalizedSearchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const searchRegex = new RegExp(`(${escapedTerm})`, 'gi'); let lastIndex = 0; let match; const normalizedText = removeAccents(text.toLowerCase()); while ((match = searchRegex.exec(normalizedText)) !== null) { const startIndex = match.index; const matchedLength = match[1].length; const originalMatchedText = text.substring(startIndex, startIndex + matchedLength); if (startIndex > lastIndex) { fragment.appendChild(document.createTextNode(text.substring(lastIndex, startIndex))); } const span = document.createElement('span'); span.className = 'highlight'; span.textContent = originalMatchedText; fragment.appendChild(span); lastIndex = startIndex + matchedLength; searchRegex.lastIndex = lastIndex; } if (lastIndex < text.length) { fragment.appendChild(document.createTextNode(text.substring(lastIndex))); } return fragment; } function updateURL(searchTerm) { try { const base = window.location.pathname; const params = new URLSearchParams(window.location.search); params.set('lang', currentLang); if (searchTerm) { params.set('query', searchTerm); } else { params.delete('query'); } const newQueryString = params.toString(); const newUrl = base + (newQueryString ? '?' + newQueryString : ''); if (window.location.href !== window.location.origin + newUrl && history.pushState) { history.pushState({ path: newUrl, searchTerm: searchTerm, lang: currentLang }, '', newUrl); } } catch (e) { console.warn("Échec mise à jour URL:", e)} } const searchHistory = { items: [], init() { try { const s = localStorage.getItem('zotoSearchHistory'); if (s) { try { this.items = JSON.parse(s); } catch (e) { console.error('Erreur chargement historique:', e); this.items = []; localStorage.removeItem('zotoSearchHistory');} } } catch (e) { console.error("LocalStorage indisponible pour historique:", e); } const hb = document.getElementById('historyButton'); const ch = document.getElementById('closeHistory'); const cl = document.getElementById('clearHistory'); const hp = document.getElementById('historyPopup'); if(hb) hb.addEventListener('click', () => this.showHistory()); if(ch) ch.addEventListener('click', () => this.hideHistory()); if(cl) cl.addEventListener('click', () => this.clearHistory()); if(hp) hp.addEventListener('click', (e) => { if(e.target === hp) this.hideHistory(); }); this.updateHistoryTexts(); }, updateHistoryTexts() { updateElementTexts({'historyTitle': {fr:"Historique de recherche",ar:"سجل البحث",mg:"Raki-pikarohana"},'clearHistory': {fr:"Effacer tout",ar:"مسح الكل",mg:"Fafaina daholo"},'#historyContent .history-empty':{fr:"Votre historique est vide",ar:"السجل فارغ",mg:"Foana ny raki-pikarohana"},'closeHistory':{frTitle:"Fermer",arTitle:"إغلاق",mgTitle:"Akatona"},'historyButton':{frTitle:"Historique",arTitle:"سجل البحث",mgTitle:"Raki-pikarohana"}}); document.querySelectorAll('.history-item-remove').forEach(b=>b.title=getUIText('remove_item')); }, addItem(word, flag) { if (!word || word.length < 1 || !flag) return; const lw = word.toLowerCase(); this.items = this.items.filter(i => i.word.toLowerCase() !== lw); this.items.unshift({ word: word, flag: flag, timestamp: Date.now() }); if (this.items.length > MAX_HISTORY_ITEMS) this.items = this.items.slice(0, MAX_HISTORY_ITEMS); this.saveToLocalStorage(); }, removeItem(word) { this.items = this.items.filter(i => i.word !== word); this.saveToLocalStorage(); this.renderHistory(); }, clearHistory() { if (confirm(getUIText('confirm_clear_history'))) { this.items = []; this.saveToLocalStorage(); this.renderHistory(); showToast(getUIText('history_cleared')); this.hideHistory(); } }, saveToLocalStorage() { try { localStorage.setItem('zotoSearchHistory', JSON.stringify(this.items)); } catch (e) { console.error('Erreur sauvegarde historique:', e); showToast(getUIText('history_save_error')); } }, formatTimestamp(t) { try { const d=new Date(t);const n=new Date();const diff=Math.floor((n-d)/864e5);const l=currentLang==='mg'?'fr-FR':(currentLang==='ar'?'ar-SA':'fr-FR');const tf={hour:'2-digit',minute:'2-digit',hour12:!1};const df={day:'2-digit',month:'2-digit',year:'numeric'};const ts=d.toLocaleTimeString(l,tf);if(diff===0){const td=currentLang==='ar'?'اليوم':(currentLang==='mg'?'Anio':'Auj.');return `${td} ${ts}`;}if(diff===1){const yd=currentLang==='ar'?'أمس':(currentLang==='mg'?'Omaly':'Hier');return `${yd} ${ts}`;}return d.toLocaleDateString(l,df);} catch (e) { return "Date invalide"; } }, showHistory() { this.renderHistory(); const p = document.getElementById('historyPopup'); if(p) p.classList.add('show'); }, hideHistory() { const p = document.getElementById('historyPopup'); if(p) p.classList.remove('show'); }, renderHistory() { const hc=document.getElementById('historyContent'); if(!hc)return; hc.innerHTML=''; const rt = getUIText('remove_item'); const cl=document.getElementById('clearHistory'); if(this.items.length===0){const e=document.createElement('div'); e.className='history-empty'; e.textContent=getUIText('history_empty'); hc.appendChild(e); if(cl)cl.disabled=!0; return;} if(cl)cl.disabled=!1; this.items.forEach(i=>{const d=document.createElement('div');d.className='history-item';d.innerHTML=`${i.flag}${i.word}${this.formatTimestamp(i.timestamp)}`;d.addEventListener('click',e=>{if(!e.target.classList.contains('history-item-remove')){fetchEntriesForTerm(i.word);this.hideHistory();}});const r=d.querySelector('.history-item-remove');if(r)r.onclick=e=>{e.stopPropagation();this.removeItem(i.word);};hc.appendChild(d);}); this.updateHistoryTexts(); } }; function speakText(text, buttonElement) { if(typeof SpeechSynthesisUtterance === 'undefined') return showToast(getUIText('tts_unavailable')); speechSynthesis.cancel(); const replacements = { 'ـعَـ\\،': 'فِعْلُ مَاضٍ ثلاثِي، ومُضارعه يأتي على وزن يَفْعَلُ.', 'ـعُـ\\،': 'فِعْلُ مَاضٍ ثلاثِي، ومُضارعه يأتي على وزن يَفْعُلُ.', 'ـعِـ\\،': 'فِعْلُ مَاضٍ ثلاثِي، ومُضارعه يأتي على وزن يَفْعِلُ.', 'س\\.': 'أما مصدره فَسَمَاعِيّ وهو.', 'ج\\.': 'وجمعه.', 'ج': 'جمع', 'مؤ\\.': 'ومؤنثه.', 'مؤ': 'مؤنث', 'مع\\.': 'بمعنى.', '~ة': 'وتُؤَنَّثُ بإضافة تاء التأنيث.', '~ون': 'وَتُجْمَع جَمْعًا مذكَّرًا سالما.', '~ات': 'وَتُجْمَع جَمْعًا مؤنَّثا سالما.', 'م{3}': 'اسم مؤنث مجازي مجرّد من علامة التأنيث، ولا يجوز أن يُذَكَّر.', 'ممذ': 'اسم مؤنث مجازي مجرد من علامة التأنيث، ويجوز أن يُذَكَّر.', 'مذمؤ': 'اسم مذكَّر، ويجوز أن يُؤَنَّث.', 'مذمذ': 'اسم مذكَّر رغم وجود علامة التأنيث فيه.' }; let pText = text; for (const [s, r] of Object.entries(replacements)) { pText = pText.replace(new RegExp(s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'), 'g'), r); } const utt = new SpeechSynthesisUtterance(pText); utt.lang = /[ء-ي]/.test(text) ? 'ar-SA' : (currentLang === 'mg' ? 'fr-FR' : 'fr-FR'); utt.rate = 0.8; utt.pitch = 1; utt.volume = 1; if (activeAudioElement && activeAudioElement !== buttonElement) activeAudioElement.classList.remove('playing'); if (buttonElement) { utt.onstart = () => { buttonElement.classList.add('playing'); activeAudioElement = buttonElement; }; utt.onend = () => { buttonElement.classList.remove('playing'); activeAudioElement = null; }; utt.onerror = e => { console.error('TTS Error:', e.error); buttonElement.classList.remove('playing'); activeAudioElement = null; showToast(getUIText('tts_error')); }; } else { utt.onerror = e => { console.error('TTS Error:', e.error); showToast(getUIText('tts_error')); }; } speechSynthesis.speak(utt); } const settingsManager = { currentTheme: 'light', settingsButton: null, settingsPopup: null, closeSettingsButton: null, themeOptions: [], languageOptions: [], init() { try { this.settingsButton = document.getElementById('settingsButton'); this.settingsPopup = document.getElementById('settingsPopup'); this.closeSettingsButton = this.settingsPopup?.querySelector('.close-settings'); this.themeOptions = this.settingsPopup?.querySelectorAll('.theme-option[data-theme]') || []; this.languageOptions = this.settingsPopup?.querySelectorAll('.language-option[data-lang]') || []; if (!this.settingsButton || !this.settingsPopup || !this.closeSettingsButton) return; this.settingsButton.addEventListener('click', e => { e.stopPropagation(); this.showSettings(); }); this.closeSettingsButton.addEventListener('click', () => this.hideSettings()); document.addEventListener('click', e => { if (this.settingsPopup.classList.contains('show') && !this.settingsPopup.contains(e.target) && e.target !== this.settingsButton && !this.settingsButton.contains(e.target)) this.hideSettings(); }); this.themeOptions.forEach(o => { o.addEventListener('click', () => { const t = o.dataset.theme; if (t) this.applyTheme(t); }); }); this.languageOptions.forEach(o => { o.addEventListener('click', () => { const l = o.dataset.lang; if (l) { applyLanguage(l); this.hideSettings(); } }); }); this.loadInitialTheme(); this.updateSettingsPopupUI(); } catch (error) { console.error("Error init Settings Manager:", error); } }, showSettings() { this.updateSettingsPopupUI(); this.settingsPopup.classList.add('show'); }, hideSettings() { this.settingsPopup.classList.remove('show'); }, updateSettingsPopupUI() { this.themeOptions.forEach(o => o.classList.toggle('active', o.dataset.theme === this.currentTheme)); this.languageOptions.forEach(o => o.classList.toggle('active', o.dataset.lang === currentLang)); updateElementTexts({ '.settings-popup-header h4': {fr:"Paramètres",ar:"الإعدادات",mg:"Parametatra"}, '.close-settings': {frTitle:"Fermer",arTitle:"إغلاق",mgTitle:"Akatona"}, '.settings-section-title[data-fr="Thème d\'affichage"]': {fr:"Thème d'affichage",ar:"سمة العرض",mg:"Lokon'ny efijery"}, '.settings-section-title[data-fr="Langue de l\'interface"]': {fr:"Langue de l'interface",ar:"لغة الواجهة",mg:"Fiteny ampiasaina"}, '#lightThemeOption .theme-option-text': {fr:"Mode clair",ar:"الوضع الفاتح",mg:"Efijery mazava"}, '#darkThemeOption .theme-option-text': {fr:"Mode sombre",ar:"الوضع الداكن",mg:"Efijery maizina"}, '#softThemeOption .theme-option-text': {fr:"Mode doux",ar:"الوضع اللطيف",mg:"Efijery malefaka"} }); updateElementTexts({ '#settingsButton': {frTitle:"Paramètres",arTitle:"الإعدادات",mgTitle:"Parametatra"} }); }, applyTheme(theme) { if (!['light', 'dark', 'soft'].includes(theme)) theme = 'light'; const b = document.body; b.classList.remove('dark-mode', 'soft-mode'); if (theme === 'dark') b.classList.add('dark-mode'); else if (theme === 'soft') b.classList.add('soft-mode'); this.currentTheme = theme; try { localStorage.setItem(THEME_KEY, theme); } catch (e) {} this.updateSettingsPopupUI(); this.hideSettings(); }, loadInitialTheme() { let p = 'light'; try { const s = localStorage.getItem(THEME_KEY); if (s && ['light', 'dark', 'soft'].includes(s)) p = s; else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) p = 'dark'; } catch (e) {} const b = document.body; b.classList.remove('dark-mode', 'soft-mode'); if (p === 'dark') b.classList.add('dark-mode'); else if (p === 'soft') b.classList.add('soft-mode'); this.currentTheme = p; } }; function applyLanguage(langCode) { if (!['fr', 'ar', 'mg'].includes(langCode)) langCode = 'ar'; currentLang = langCode; const h = document.documentElement; const b = document.body; h.lang = langCode; b.classList.remove('rtl', 'fr', 'mg'); b.removeAttribute('dir'); if (langCode === 'ar') { b.dir = 'rtl'; b.classList.add('rtl'); } else { b.dir = 'ltr'; if (langCode === 'fr') b.classList.add('fr'); else if (langCode === 'mg') b.classList.add('mg'); } try { localStorage.setItem(LANGUAGE_KEY, langCode); } catch (e) {} updateUITexts(); settingsManager.updateSettingsPopupUI(); if (Object.keys(currentEntries).length > 0) { const searchTerm = document.getElementById('search')?.value || ''; const firstKey = Object.keys(currentEntries)[0]; const firstEntry = currentEntries[firstKey]; const flag = firstEntry ? getFlag(firstEntry.teny) : '❓'; displayMultipleEntries(currentEntries, searchTerm, flag); } } function loadInitialLanguage() { let langToApply = phpInitialLang; try { const savedLang = localStorage.getItem(LANGUAGE_KEY); if (savedLang && ['fr', 'ar', 'mg'].includes(savedLang)) { langToApply = savedLang; } } catch (e) { console.warn("LocalStorage non accessible pour la langue."); } applyLanguage(langToApply); } function displayMultipleEntries(entriesData, searchTermUsedForSearch, suggestionFlag) { const dictionaryDiv = document.getElementById('dictionary'); if (!dictionaryDiv) return; dictionaryDiv.textContent = ''; currentEntries = entriesData || {}; const entriesArray = Object.entries(currentEntries); const hasResults = entriesArray.length > 0; const flagToAdd = suggestionFlag || (searchTermUsedForSearch ? getFlag(searchTermUsedForSearch) : '❓'); if (searchTermUsedForSearch) { if (hasResults) { searchHistory.addItem(searchTermUsedForSearch, flagToAdd); updateURL(searchTermUsedForSearch); const rc = document.createElement('div'); rc.className = 'results-count'; const count = entriesArray.length; const rtf = getUIText('results_found'); rc.textContent = rtf(count) + `"${searchTermUsedForSearch}"`; dictionaryDiv.appendChild(rc); } else { dictionaryDiv.innerHTML = `
${getUIText('no_results_for')} "${searchTermUsedForSearch}".
`; updateURL(searchTermUsedForSearch); searchHistory.addItem(searchTermUsedForSearch, '❓'); } } else { updateURL(null); } if (!hasResults) return; entriesArray.forEach(([key, entry], index) => { if (entry) { const wc = document.createElement('div'); wc.className = 'word-card'; wc.style.setProperty('--animation-order', index); wc.innerHTML = `
${entry.teny || ''}
${removeParenthesesWords(replaceSymbols(entry.note1 || ''))}
`; const ab = wc.querySelector('.audio-btn'); if (ab && entry.teny) { ab.onclick = e => { e.preventDefault(); e.stopPropagation(); speakText(entry.teny, ab); }; } else if (ab) { ab.disabled = true; ab.style.opacity = '0.3'; } const dEl = wc.querySelector('.dikany'); if (dEl && entry.dikany) { let dHtml = entry.dikany || ''; dHtml = dHtml.replace(/(?:<\s*([^>]+?)\s*>\s*)+/g, match => { let contentArray = []; match.replace(/<\s*([^>]+?)\s*>/g, (innerMatch, content) => contentArray.push(content.trim())); const safeContent = contentArray.map(line => line.replace(//g, '>')); return `${safeContent.join('
')}
`; }); dHtml = dHtml.replace(/(? `${number} `); dHtml = dHtml.replace(/\(([^)]*?⚜️\s*fr\.)([^)]*)\)/g, (match, beforeMarker, afterMarker) => { const coloredPart = (afterMarker && afterMarker.trim()) ? `${afterMarker}` : (afterMarker || ''); return `(${beforeMarker}${coloredPart})`; }); dEl.innerHTML = dHtml; dEl.querySelectorAll('.bordered-phrase[data-lines="3"]').forEach(phraseSpan => { const childNodes = Array.from(phraseSpan.childNodes); let brCount = 0; const thirdLineNodes = []; for (const node of childNodes) { if (node.nodeName === 'BR') { brCount++; } else if (brCount === 2) { thirdLineNodes.push(node); } } if (thirdLineNodes.length > 0) { const wrapperSpan = document.createElement('span'); wrapperSpan.className = 'example-french-line'; phraseSpan.insertBefore(wrapperSpan, thirdLineNodes[0]); thirdLineNodes.forEach(node => wrapperSpan.appendChild(node)); } }); const searchTermForHighlight = document.getElementById('search')?.value || searchTermUsedForSearch || ''; if (searchTermForHighlight) { const processNode = (node, term) => { const parent = node.parentNode; if (!parent) return; const highlightedContent = highlightSimple(node.textContent, term); if (highlightedContent.childNodes.length > 1 || (highlightedContent.firstChild?.nodeType === Node.ELEMENT_NODE)) { parent.replaceChild(highlightedContent, node); } }; const walkAndHighlight = (element, term) => { const childNodes = Array.from(element.childNodes); childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE) { const parent = node.parentNode; if (parent && !parent.classList.contains('dikany-french-term') && !parent.classList.contains('example-french-line') && !parent.classList.contains('styled-number') && !parent.classList.contains('highlight') && parent.nodeName !== 'SCRIPT' && parent.nodeName !== 'STYLE') { processNode(node, term); } } else if (node.nodeType === Node.ELEMENT_NODE) { if (!node.classList.contains('dikany-french-term') && !node.classList.contains('example-french-line') && !node.classList.contains('styled-number') && !node.classList.contains('highlight') && node.nodeName !== 'SCRIPT' && node.nodeName !== 'STYLE') { walkAndHighlight(node, term); } } }); }; walkAndHighlight(dEl, searchTermForHighlight); dEl.querySelectorAll('.dikany-french-term, .example-french-line').forEach(styledSpan => { walkAndHighlight(styledSpan, searchTermForHighlight); }); } } dictionaryDiv.appendChild(wc); } }); } async function fetchSuggestions(query) { const sd = document.getElementById('suggestions'); if (!sd) return; sd.textContent = ''; sd.style.display = 'none'; if (query.length < 2) return; const ck = `suggest_${query.toLowerCase()}`; if (searchCache.has(ck)) { renderSuggestions(searchCache.get(ck)); return; } try { sd.innerHTML = `
⏳ ${getUIText('searching')}
`; sd.style.display = 'block'; const r = await fetch(`/suggest.php?query=${encodeURIComponent(query)}`, {method:'GET',credentials:'include'}); if (!r.ok) throw new Error(`Erreur HTTP: ${r.status}`); const s = await r.json(); if (s.error) throw new Error(s.error); searchCache.set(ck, s); renderSuggestions(s); } catch (e) { console.error('Erreur fetch suggestions:', e); sd.innerHTML = `
⚠️ ${getUIText('suggestion_error')}: ${e.message}
`; sd.style.display = 'block'; } } function renderSuggestions(suggestions) { const sd = document.getElementById('suggestions'); if (!sd) return; sd.textContent = ''; if (suggestions && suggestions.length > 0) { suggestions.slice(0, SUGGESTION_LIMIT_DISPLAY).forEach(s => { const d = document.createElement('div'); d.innerHTML = `${s.flag} `; d.appendChild(document.createTextNode(s.word)); d.onclick = () => { const i = document.getElementById('search'); if(i) i.value = s.word; sd.style.display = 'none'; const dd = document.getElementById('dictionary'); if(dd) dd.innerHTML = `
⏳
${getUIText('loading')}
`; fetchEntriesByKeys(s.keys, s.word, s.flag); }; sd.appendChild(d); }); sd.style.display = 'block'; } else { sd.style.display = 'none'; } } async function fetchEntriesByKeys(keysArray, searchTerm, flag) { const dd = document.getElementById('dictionary'); if (!keysArray || keysArray.length === 0) { console.warn("Fetch avec clés vides:", searchTerm); displayMultipleEntries({}, searchTerm, '❓'); return; } const ks = keysArray.join(','); const ck = `entry_${ks}`; if (searchCache.has(ck)) { displayMultipleEntries(searchCache.get(ck), searchTerm, flag); return; } if(dd) dd.innerHTML = `
⏳${getUIText('loading')}
`; try { const r = await fetch(`/get_entry.php?keys=${encodeURIComponent(ks)}`, {method:'GET',credentials:'include'}); if (!r.ok) throw new Error(`Erreur HTTP: ${r.status}`); const d = await r.json(); if (d.error) throw new Error(d.error); searchCache.set(ck, d); displayMultipleEntries(d, searchTerm, flag); } catch (e) { console.error('Erreur fetch entry:', e); if(dd) dd.innerHTML = `
⚠️
${getUIText('definition_fetch_error')}
${e.message}
`; updateURL(searchTerm); } } async function fetchEntriesForTerm(term) { const si = document.getElementById('search'); const dd = document.getElementById('dictionary'); const sd = document.getElementById('suggestions'); term = term ? term.trim() : ''; if (!term) { if(dd) dd.textContent = ''; if(si) si.value = ''; if(sd) sd.style.display = 'none'; currentEntries={}; updateURL(null); return; } if(si) si.value = term; if(dd) dd.innerHTML = `
⏳${getUIText('searching')}
`; if(sd) sd.style.display = 'none'; try { const r = await fetch(`/suggest.php?query=${encodeURIComponent(term)}`, {method:'GET',credentials:'include'}); if (!r.ok) throw new Error(`Erreur Suggest API: ${r.status}`); const s = await r.json(); if (s.error) throw new Error(s.error); let ts = s.find(i => i.word.toLowerCase() === term.toLowerCase()); if (ts && ts.keys && ts.keys.length > 0) { await fetchEntriesByKeys(ts.keys, term, ts.flag); } else { console.warn("Aucune suggestion exacte pour:", term); const exactMatch = s.find(i => removeAccents(i.word.toLowerCase()) === removeAccents(term.toLowerCase())); if(exactMatch && exactMatch.keys && exactMatch.keys.length > 0) { await fetchEntriesByKeys(exactMatch.keys, term, exactMatch.flag); } else { displayMultipleEntries({}, term, '❓'); } } } catch (e) { console.error('Erreur fetch entry for term:', term, e); if(dd) dd.innerHTML = `
⚠️
${getUIText('search_error')}
${e.message}
`; updateURL(term); searchHistory.addItem(term, '❓'); } } const searchWords = debounce(function () { const i=document.getElementById('search'); if(!i) return; const v=i.value.trim(); if(v.length>=2) fetchSuggestions(v); else { const s=document.getElementById('suggestions'); if(s)s.style.display='none'; if(v.length===0){const d=document.getElementById('dictionary'); if(d)d.textContent='';currentEntries={};updateURL(null);}} }, 300); function setupConnectivityListeners() { const o=document.getElementById('offlineNotification'); const m=document.getElementById('offlineMessage'); if(!o||!m)return; const u=()=>{m.textContent=getUIText('offlineMessage')||'Connexion perdue';}; window.addEventListener('online',()=>{document.body.classList.remove('offline');o.classList.remove('show');}); window.addEventListener('offline',()=>{document.body.classList.add('offline');u();o.classList.add('show');}); if(!navigator.onLine){document.body.classList.add('offline');u();o.classList.add('show');}} function updateUITexts() { document.querySelectorAll('[data-fr], [data-ar], [data-mg]').forEach(el => { const text = el.dataset[currentLang] || el.dataset['fr']; if (text !== undefined) el.textContent = text; }); document.querySelectorAll('[data-fr-title], [data-ar-title], [data-mg-title]').forEach(el => { const title = el.dataset[currentLang + 'Title'] || el.dataset['frTitle']; if (title !== undefined) el.title = title; }); document.querySelectorAll('[data-fr-placeholder], [data-ar-placeholder], [data-mg-placeholder]').forEach(el => { const placeholder = el.dataset[currentLang + 'Placeholder'] || el.dataset['frPlaceholder']; if (placeholder !== undefined) el.placeholder = placeholder; }); const si=document.getElementById('search');if(si){const p=si.dataset[currentLang]||si.dataset['fr'];if(p)si.placeholder=p;} const tt=document.querySelector('title');if(tt)tt.textContent=currentLang==='ar'?'معجم زوتو-ربحي':(currentLang==='mg'?'Rakibolana Zoto-Ribhi':'Dictionnaire Zoto-Ribhi'); const hb=document.getElementById('homeButton');const hbt=document.getElementById('homeButtonText');if(hb&&hbt){hb.title=getUIText('homeButton');hbt.innerHTML=hb.title;} searchHistory.updateHistoryTexts(); settingsManager.updateSettingsPopupUI(); if (Object.keys(currentEntries).length > 0) { const st = document.getElementById('search')?.value || ''; const fk = Object.keys(currentEntries)[0]; const ff = currentEntries[fk] ? getFlag(currentEntries[fk].teny) : '❓'; displayMultipleEntries(currentEntries, st, ff); } } function updateElementTexts(selectorsAndTexts) { for (const s in selectorsAndTexts) { const e = document.querySelectorAll(s); e.forEach(el => { const t = selectorsAndTexts[s]; const txt = t[currentLang] || t['fr'] || t['ar']; const ttl = t[currentLang + 'Title'] || t['frTitle'] || t['arTitle']; if (txt !== undefined) el.textContent = txt; if (ttl !== undefined) el.title = ttl; }); } } document.addEventListener('DOMContentLoaded', function() { try { settingsManager.loadInitialTheme(); loadInitialLanguage(); settingsManager.init(); searchHistory.init(); setupConnectivityListeners(); const searchInput = document.getElementById('search'); if(searchInput) { searchInput.addEventListener('input', searchWords); searchInput.addEventListener('keydown', function(event) { if (event.key === 'Enter') { event.preventDefault(); fetchEntriesForTerm(searchInput.value); searchInput.blur(); } }); searchInput.focus(); } const urlParams = new URLSearchParams(window.location.search); const initialQuery = urlParams.get('query'); if (initialQuery) { fetchEntriesForTerm(initialQuery); } else { const fullQueryParam = urlParams.get('q'); if (fullQueryParam) { const matchTranslate = fullQueryParam.match(/que signifie ['"]?([^'"]+)['"]? en (malgache|français|arabe)/i) || fullQueryParam.match(/traduction ['"]?([^'"]+)['"]? (?:en|vers|->) (malgache|français|arabe)/i) || fullQueryParam.match(/ما معنى ['"]?([^'"]+)['"]? (?:في|بال)? (الملغاشية|الفرنسية|العربية)/i); if (matchTranslate && matchTranslate[1]) { const termToSearch = matchTranslate[1].trim(); if (termToSearch) { console.log("Tentative d'extraction de terme depuis la requête Google:", termToSearch); if(searchInput) searchInput.value = termToSearch; fetchEntriesForTerm(termToSearch); } } else { const pathSegments = window.location.pathname.split('/'); let lastSegment = decodeURIComponent(pathSegments.pop() || pathSegments.pop() || ''); const ignored = ['rakibolana.php', 'rakibolana', 'index.php', 'index.html', '']; if (lastSegment && !ignored.includes(lastSegment.toLowerCase())) { fetchEntriesForTerm(lastSegment); } } } else { const pathSegments = window.location.pathname.split('/'); let lastSegment = decodeURIComponent(pathSegments.pop() || pathSegments.pop() || ''); const ignored = ['rakibolana.php', 'rakibolana', 'index.php', 'index.html', '']; if (lastSegment && !ignored.includes(lastSegment.toLowerCase())) { fetchEntriesForTerm(lastSegment); } } } document.addEventListener('click', function(event) { const s = document.getElementById('suggestions'); const i = document.getElementById('search'); const h = document.getElementById('historyButton'); if(s && i && s.style.display === 'block' && !s.contains(event.target) && event.target !== i && !(h && h.contains(event.target)) ) { s.style.display = 'none'; } }); } catch(e) { console.error("Erreur initialisation:", e); const b = document.querySelector('body'); if(b) { b.innerHTML = `
⚠️
${getUIText('critical_error_startup')}
${e.message}
`; } } }); window.addEventListener('popstate', function (event) { const state = event.state; let searchTerm = null; let lang = phpInitialLang; if (state) { searchTerm = state.searchTerm; lang = state.lang || lang; } else { const urlParams = new URLSearchParams(window.location.search); searchTerm = urlParams.get('query'); lang = urlParams.get('lang') || lang; } if (currentLang !== lang) { applyLanguage(lang); } const currentInputVal = document.getElementById('search')?.value; if (currentInputVal !== searchTerm) { fetchEntriesForTerm(searchTerm); } else if (!currentInputVal && !searchTerm && Object.keys(currentEntries).length > 0){ const dd = document.getElementById('dictionary'); if(dd) dd.textContent = ''; currentEntries={}; } });