Framework

HTML plano

¿Sin framework? Verbaly trata el HTML plano como ciudadano de primera, algo que ninguna otra herramienta i18n con compilador hace. Marca elementos, llama bindDom, listo.

Uso

index.html
<h1 data-verbaly="home.title"></h1>
<p  data-verbaly="inbox" data-verbaly-args='{"count":3}'></p>
<input data-verbaly-attr='{"placeholder":"search.hint"}' />
main.ts
import { bindDom, createVerbaly } from 'verbaly';

const v = createVerbaly({ locale: 'es', messages });
const unbind = bindDom(v);   // render + observe

v.setLocale('en');            // every bound node re-renders

Atributos

AtributoPropósito
data-verbalyClave cuya traducción se vuelve el texto del elemento
data-verbaly-argsParams JSON compartidos por las traducciones de texto y atributos
data-verbaly-attrMapa JSON de atributo → clave, p. ej. placeholder, title, aria-label. Desde 0.17.0: los atributos URL (href, src…) se sanitizan contra esquemas inseguros; on*, style y srcdoc se bloquean, así que un catálogo no puede inyectar scripts
data-verbaly-richOpt-in: renderiza los tags del mensaje como elementos reales (ver Rich text)
data-verbaly-linksMapa JSON de nombre de link → href para links nombrados (ver Links nombrados)

Rich text

Por defecto las traducciones renderizan vía textContent, así que los tags quedan literales. Marca el elemento con data-verbaly-rich y los mensajes con tags se vuelven elementos reales:

locales/en.json
{ "gate.title": "The build <em>gate</em>" }
index.html
<h3 data-verbaly="gate.title" data-verbaly-rich></h3>
<!-- renders: The build <em>gate</em> -->

Una clave por frase, así que el traductor ve el mensaje completo y cada idioma mantiene su propio orden de palabras. El modelo de seguridad:

Los links quedan fuera de la whitelist a propósito: un <a> sin atributos no sirve, y los atributos nunca salen de los mensajes. Desde 0.11.0 nombras el link en el mensaje y provees el href desde el código:

locales/en.json
{ "cta": "Read the <docs>guide</docs> now" }
main.ts
bindDom(v, {
  richLinks: { docs: { href: '/docs', target: '_blank', rel: 'noopener' } },
});
// renders: Read the <a href="/docs" target="_blank" rel="noopener">guide</a> now
index.html: per-element override
<p data-verbaly="cta" data-verbaly-rich data-verbaly-links='{"docs":"/es/docs"}'></p>

Bootstrap de idioma

Dos helpers cubren el boilerplate que toda app repite, elegir el idioma inicial y recordar la elección del usuario:

main.ts
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>

Comportamiento

Copiado en el portapapeles