const LANGUAGES = Object.freeze({ fr: { code: 'fr', short: 'FR', label: 'French' }, en: { code: 'en', short: 'EN', label: 'English' }, es: { code: 'es', short: 'ES', label: 'Spanish' }, de: { code: 'de', short: 'DE', label: 'German' }, it: { code: 'it', short: 'IT', label: 'Italian' }, pt: { code: 'pt', short: 'PT', label: 'Portuguese' } }); const CODE_ALIASES = Object.freeze({ fr: 'fr', fra: 'fr', fre: 'fr', french: 'fr', francais: 'fr', france: 'fr', en: 'en', eng: 'en', english: 'en', anglais: 'en', uk: 'en', us: 'en', es: 'es', spa: 'es', spanish: 'es', espanol: 'es', spain: 'es', de: 'de', deu: 'de', ger: 'de', german: 'de', deutsch: 'de', it: 'it', ita: 'it', italian: 'it', italiano: 'it', pt: 'pt', por: 'pt', portuguese: 'pt', portugues: 'pt' }); function normalize(value) { return String(value || '').normalize('NFKD').replace(/[\u0300-\u036f]/g, '').toLowerCase(); } function detectLanguage(item) { const data = item.data && typeof item.data === 'object' ? item.data : {}; const values = [item.name, item.category_name, data.groupTitle, data.tvgId, data.tvgName].filter(Boolean); const scores = {}; const evidence = []; const prefix = String(item.name || '').trim().match(/^(?:\|\s*([a-z-]{2,8})\s*\||\[\s*([a-z-]{2,8})\s*\]|([a-z-]{2,8})\s*\|)/i); if (prefix) add(prefix[1] || prefix[2] || prefix[3], 70, 'name-prefix'); values.forEach((value, index) => { const tokens = normalize(value).split(/[^a-z]+/).filter(Boolean); for (const token of tokens) add(token, index === 0 ? 45 : 25, index === 0 ? 'name-token' : 'metadata'); }); function add(token, weight, type) { const code = CODE_ALIASES[normalize(token)]; if (!code) return; scores[code] = (scores[code] || 0) + weight; evidence.push({ code, weight, type, value: String(token) }); } const ranked = Object.entries(scores).sort((a, b) => b[1] - a[1]); if (!ranked.length) return { code: 'und', short: 'UND', label: 'Unknown', confidence: 0, scores, evidence }; const total = ranked.reduce((sum, [, score]) => sum + score, 0); const confidence = Math.round(ranked[0][1] / total * 100); if (confidence < 55 || (ranked[1] && ranked[0][1] - ranked[1][1] < 15)) { return { code: 'und', short: 'UND', label: 'Unknown', confidence: 0, scores, evidence }; } return { ...LANGUAGES[ranked[0][0]], confidence, scores, evidence }; } module.exports = { CODE_ALIASES, LANGUAGES, detectLanguage, normalize };