Framework
HTML puro
Sem framework? O Verbaly trata HTML puro como cidadão de primeira, algo que nenhuma outra ferramenta i18n com compilador faz. Marque elementos, chame bindDom, pronto.
Uso
<h1 data-verbaly="home.title"></h1>
<p data-verbaly="inbox" data-verbaly-args='{"count":3}'></p>
<input data-verbaly-attr='{"placeholder":"search.hint"}' />import { bindDom, createVerbaly } from 'verbaly';
const v = createVerbaly({ locale: 'es', messages });
const unbind = bindDom(v); // render + observe
v.setLocale('en'); // every bound node re-rendersAtributos
| Atributo | Propósito |
|---|---|
| data-verbaly | Chave cuja tradução vira o texto do elemento |
| data-verbaly-args | Params JSON compartilhados pelas traduções de texto e atributos |
| data-verbaly-attr | Mapa JSON de atributo → chave, ex. placeholder, title, aria-label. Desde 0.17.0: atributos URL (href, src…) são sanitizados contra esquemas inseguros; on*, style e srcdoc são bloqueados, então um catálogo não pode injetar scripts |
| data-verbaly-rich | Opt-in: renderiza os tags da mensagem como elementos reais (ver Rich text) |
| data-verbaly-links | Mapa JSON de nome do link → href para links nomeados (ver Links nomeados) |
Rich text
Por padrão as traduções renderizam via textContent, então os tags ficam literais. Marque o elemento com data-verbaly-rich e as mensagens com tags viram elementos reais:
{ "gate.title": "The build <em>gate</em>" }<h3 data-verbaly="gate.title" data-verbaly-rich></h3>
<!-- renders: The build <em>gate</em> -->Uma chave por frase, então o tradutor vê a mensagem inteira e cada idioma mantém sua própria ordem de palavras. O modelo de segurança:
- Só renderiza uma whitelist de tags de frase:
em,strong,code,b,i,u,s,small,mark,sub,sup,span,kbd,abbr,br,wbr. - Os elementos são construídos programaticamente, nunca
innerHTML, e os atributos jamais vêm da mensagem. - Os tags fora da whitelist são desembrulhados para texto inerte: um
<script>num catálogo renderiza como texto puro, sem elemento. - Sobrescreva a whitelist com
bindDom(v, { richTags: ['q'] }).
Links nomeados
Links ficam fora da whitelist de propósito: um <a> sem atributos é inútil, e atributos nunca vêm das mensagens. Desde 0.11.0 você nomeia o link na mensagem e fornece o href pelo código:
{ "cta": "Read the <docs>guide</docs> now" }bindDom(v, {
richLinks: { docs: { href: '/docs', target: '_blank', rel: 'noopener' } },
});
// renders: Read the <a href="/docs" target="_blank" rel="noopener">guide</a> now<p data-verbaly="cta" data-verbaly-rich data-verbaly-links='{"docs":"/es/docs"}'></p>- Um link é um href
stringou{ href, target, rel }; o mapa por elementodata-verbaly-linksmescla por cima dorichLinksglobal. - Os hrefs vêm do seu código, nunca do catálogo, e esquemas inseguros (
javascript:,data:) são bloqueados porsafeHref: o<a>renderiza semhref. - Nomes fora do mapa se desembrulham em texto inerte, como qualquer tag desconhecida, então as traduções nunca quebram a página.
Bootstrap de idioma
Dois helpers cobrem o boilerplate que todo app repete, escolher o idioma inicial e lembrar a escolha do usuário:
import { bindDom, createVerbaly, persistLocale, resolveLocale } from 'verbaly';
const v = createVerbaly({
locale: resolveLocale({ supported: ['en', 'es', 'pt'] }), // storage → navigator → fallback
messages: { en }, // bundle only the source
loaders: { es: () => import('./locales/es.json'), pt: () => import('./locales/pt.json') },
});
bindDom(v);
// on user switch:
await v.loadLocale('es'); // flash-free (setLocale alone also auto-loads)
v.setLocale('es');
persistLocale('es'); // localStorage + <html lang>resolveLocale: escolha salva →navigator.languages(com narrowing BCP-47,es-PE→es) →fallback(por padrão, o primeiro idioma suportado). SSR-safe.- Ambos aceitam um
storageKeycustom (defaultverbaly-locale);falsedesativa o storage. - Catálogos lazy (
0.8.0): só a fonte vai no bundle, e osloadersbuscam o resto sob demanda, deduplicados e com narrowing BCP-47.
Comportamento
- Re-renderiza cada elemento vinculado em
setLocale/addMessages. - Um
MutationObserverdetecta nós adicionados depois e edições de atributos, então o conteúdo dinâmico simplesmente funciona. bindDomretorna umunbind()que desconecta tudo.- Opções:
bindDom(v, { root, attribute, richTags, richLinks })para limitar o escaneamento, renomear o atributo, sobrescrever a whitelist rich ou fornecer links nomeados.