import { EN } from './i18n-en.js'; let lang = 'pt'; export const isEn = () => lang === 'en'; export const currentLang = () => lang; export const LOC = () => (lang === 'en' ? 'en-US' : 'pt-BR'); export function resolveLang(pref) { if (pref === 'pt' || pref === 'en') return pref; return (navigator.language || 'pt').toLowerCase().startsWith('pt') ? 'pt' : 'en'; } const ATTRS = ['placeholder', 'title', 'aria-label', 'alt', 'data-empty']; const SKIP = '.ssid, .bssid, .mono, pre, code, input, textarea, script, style, [data-i18n-skip]'; const exact = new Map(Object.entries(EN).filter(([key]) => !key.includes('{n}'))); const patterns = Object.entries(EN) .filter(([key]) => key.includes('{n}')) .map(([key, value]) => { const parts = key.split('{n}').map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); return { re: new RegExp('^' + parts.join('(.+?)') + '$', 's'), to: value }; }) .sort((a, b) => b.re.source.length - a.re.source.length); const cache = new Map(); export function tr(text) { if (!isEn()) return text; const lead = text.match(/^\s*/)[0]; const trail = text.match(/\s*$/)[0]; const core = text.trim(); if (!core) return text; if (cache.has(core)) return lead + cache.get(core) + trail; let out = exact.get(core); if (out === undefined) { for (const pattern of patterns) { const match = core.match(pattern.re); if (match) { let index = 0; out = pattern.to.replace(/\{n\}/g, () => tr(match[++index] ?? '')); break; } } } if (out === undefined) out = core; if (cache.size > 5000) cache.clear(); cache.set(core, out); return lead + out + trail; } const original = new WeakMap(); let observer = null; function translateNode(node) { if (node.nodeType === 3) { if (node.parentElement?.closest(SKIP)) return; const source = original.get(node) ?? node.nodeValue; const translated = tr(source); if (translated !== node.nodeValue) { if (!original.has(node)) original.set(node, source); node.nodeValue = translated; } } else if (node.nodeType === 1) { if (node.matches?.(SKIP) && node.tagName !== 'INPUT') return; for (const attribute of ATTRS) { if (node.hasAttribute?.(attribute)) { const key = `i18n-${attribute}`; const source = node[key] ?? node.getAttribute(attribute); const translated = tr(source); if (translated !== node.getAttribute(attribute)) { node[key] = source; node.setAttribute(attribute, translated); } } } for (const child of node.childNodes) translateNode(child); } } function restoreAll(root) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { if (original.has(node)) { node.nodeValue = original.get(node); original.delete(node); } } root.querySelectorAll('*').forEach((element) => { for (const attribute of ATTRS) { const key = `i18n-${attribute}`; if (element[key] !== undefined) { element.setAttribute(attribute, element[key]); delete element[key]; } } }); } function start() { translateNode(document.body); document.title = tr(document.title); if (observer) return; observer = new MutationObserver((mutations) => { observer.disconnect(); for (const mutation of mutations) { if (mutation.type === 'childList') mutation.addedNodes.forEach(translateNode); else if (mutation.type === 'characterData') { original.delete(mutation.target); translateNode(mutation.target); } else if (mutation.type === 'attributes') { delete mutation.target[`i18n-${mutation.attributeName}`]; translateNode(mutation.target); } } observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ATTRS, }); }); observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ATTRS, }); } function stop() { observer?.disconnect(); observer = null; restoreAll(document.body); } export function setLang(pref) { const next = resolveLang(pref); const changed = next !== lang; lang = next; document.documentElement.lang = lang === 'en' ? 'en' : 'pt-BR'; if (lang === 'en') start(); else if (changed || observer) stop(); }