1/* ============================================================ 2 PT | EN na plataforma (29/09/2026) 3 Regra: tudo no simby existe em PT e EN. As páginas escrevem-se em PT; este 4 ficheiro traduz para EN quando é essa a lÃngua escolhida: 5 - o texto das páginas (nós de texto e placeholder/title/aria-label) é trocado 6 pelo dicionário SIMBY_EN (js/i18n-en.js), frase a frase, também no que é 7 acrescentado depois (MutationObserver); 8 - textos com variáveis usam t('... {nome} ...', { nome }) no código. 9 A lÃngua fica em localStorage (simby_idioma) e, com sessão, nos metadados 10 da conta (idioma), que decidem a lÃngua dos emails e dos avisos. 11 Carregar depois de js/supabase.js e de js/i18n-en.js. 12 ============================================================ */ 13;(function () { 14 const EN = window.SIMBY_EN = window.SIMBY_EN || {} 15 const lingua = () => (window.simbyIdioma ? simbyIdioma() : 'pt') 16 window.simbyEmIngles = () => lingua() === 'en' 17 18 window.t = function (pt, vars) { 19 let s = (lingua() === 'en' && EN[pt]) || pt 20 if (vars) for (const k of Object.keys(vars)) s = s.split('{' + k + '}').join(vars[k]) 21 return s 22 } 23 24 const ATRIBUTOS = ['placeholder', 'title', 'aria-label', 'alt'] 25 const IGNORAR = new Set(['SCRIPT', 'STYLE', 'TEXTAREA', 'CODE', 'PRE']) 26 27 function traduzirTexto(no) { 28 const v = no.nodeValue 29 const k = v.trim() 30 if (!k) return 31 const en = EN[k] || EN[k.replace(/\s+/g, ' ')] 32 if (en && en !== k) no.nodeValue = v.replace(k, en) 33 } 34 function traduzirElemento(el) { 35 if (el.closest && el.closest('[data-sem-traducao]')) return 36 for (const a of ATRIBUTOS) { 37 const v = el.getAttribute && el.getAttribute(a) 38 if (v && EN[v.trim()]) el.setAttribute(a, EN[v.trim()]) 39 } 40 if ((el.tagName === 'INPUT') && (el.type === 'button' || el.type === 'submit') && EN[el.value]) el.value = EN[el.value] 41 } 42 window.simbyTraduzir = function (raiz) { 43 if (lingua() !== 'en' || !raiz) return 44 if (raiz.nodeType === 3) { traduzirTexto(raiz); return } 45 if (raiz.nodeType !== 1 || IGNORAR.has(raiz.tagName)) return 46 traduzirElemento(raiz) 47 const w = document.createTreeWalker(raiz, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, { 48 acceptNode: n => (n.nodeType === 1 && IGNORAR.has(n.tagName)) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT, 49 }) 50 let n 51 while ((n = w.nextNode())) { 52 if (n.nodeType === 3) { 53 if (!(n.parentElement && n.parentElement.closest('[data-sem-traducao]'))) traduzirTexto(n) 54 } else traduzirElemento(n) 55 } 56 } 57 58 // Muda a lÃngua: guarda-a, grava-a na conta (emails e avisos) e recarrega. 59 window.simbyMudarIdioma = async function (l) { 60 if (l !== 'pt' && l !== 'en') return 61 try { localStorage.setItem('simby_idioma', l); localStorage.setItem('simby_lang', l) } catch (_) {} 62 try { 63 if (window.db) { 64 const { data } = await db.auth.getSession() 65 if (data && data.session) await db.auth.updateUser({ data: { idioma: l } }) 66 } 67 } catch (_) { /* sem sessão ou sem rede: fica só no navegador */ } 68 window.location.reload() 69 } 70 71 // Seletor PT | EN em pastilha, igual ao do simby.pt. 72 window.montarSeletorIdioma = function (alvo) { 73 if (!alvo || alvo.querySelector('.seletor-idioma')) return 74 const l = lingua() 75 const caixa = document.createElement('div') 76 caixa.className = 'seletor-idioma' 77 caixa.setAttribute('role', 'group') 78 caixa.setAttribute('aria-label', l === 'en' ? 'Language' : 'Idioma') 79 caixa.setAttribute('data-sem-traducao', '') 80 for (const c of ['pt', 'en']) { 81 const b = document.createElement('button') 82 b.type = 'button' 83 b.textContent = c.toUpperCase() 84 b.className = c === l ? 'ativo' : '' 85 b.setAttribute('aria-pressed', String(c === l)) 86 if (c !== l) b.addEventListener('click', () => simbyMudarIdioma(c)) 87 caixa.appendChild(b) 88 } 89 alvo.appendChild(caixa) 90 } 91 92 /* ---- Mensagens de erro do servidor (js/i18n/en-servidor.js) ---- */ 93 const escRe = x => x.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 94 const PADROES = (window.SIMBY_ERROS_PADROES || []).map(([pt, en]) => { 95 // Cada % da função é um valor variável: vira grupo de captura e volta a 96 // entrar, pela mesma ordem, no lugar do % correspondente em inglês. 97 const re = new RegExp('^' + pt.split('%').map(escRe).join('(.+?)') + '$') 98 return [re, en] 99 }) 100 window.traduzirErroServidor = function (msg) { 101 if (lingua() !== 'en' || typeof msg !== 'string') return msg
102 const k = msg.trim() 103 if (EN[k]) return EN[k] 104 for (const [re, en] of PADROES) { 105 const m = k.match(re) 106 if (m) { let i = 1; return en.replace(/%(\.2f)?/g, () => m[i++] ?? '') } 107 } 108 return msg 109 } 110 // As chamadas db.rpc() passam a devolver error.message já na lÃngua escolhida. 111 function traduzirErrosDoCliente() { 112 if (!window.db || db.__errosTraduzidos || lingua() !== 'en') return 113 db.__errosTraduzidos = true 114 const rpc = db.rpc.bind(db) 115 db.rpc = function (...args) { 116 const pedido = rpc(...args) 117 const then = pedido.then.bind(pedido) 118 pedido.then = (ok, falha) => then(r => { 119 if (r && r.error && r.error.message) r.error.message = traduzirErroServidor(r.error.message) 120 return r 121 }).then(ok, falha) 122 return pedido 123 } 124 } 125 traduzirErrosDoCliente() 126 127 function arrancar() { 128 document.documentElement.lang = lingua() === 'en' ? 'en' : 'pt' 129 if (lingua() !== 'en') return 130 // TÃtulos com espaços que não quebram à volta da barra ("simby | â¦"): assim o 131 // Safari não esconde o "simby |" do separador. O dicionário usa espaços normais. 132 const titulo = document.title.replace(/\u00a0/g, ' ') 133 if (EN[titulo]) document.title = EN[titulo].replace(/ \| /g, '\u00a0|\u00a0') 134 simbyTraduzir(document.body) 135 new MutationObserver(lista => { 136 for (const m of lista) { 137 if (m.type === 'characterData') traduzirTexto(m.target) 138 else if (m.type === 'attributes') traduzirElemento(m.target) 139 else m.addedNodes.forEach(simbyTraduzir) 140 } 141 }).observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ATRIBUTOS }) 142 } 143 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', arrancar) 144 else arrancar() 145})()
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.