Referencia
API del runtime
El core de verbaly pesa ~3 KB gzip con cero dependencias, y todo se construye sobre Intl nativo.
createVerbaly(options)
| Opción | Tipo | Descripción |
|---|---|---|
| locale | string | Idioma inicial; por defecto navigator.language en el navegador |
| fallback | string | string[] | Cadena de fallback tras el narrowing BCP-47 (es-MX → es) |
| messages | object | Idioma → árbol; las claves anidadas se aplanan con puntos (home.title). Un string vacío cuenta como sin traducir y sigue el fallback, la misma regla que check |
| loaders | object | Catálogos lazy: locale → () => import('./es.json'), cargados a demanda. El del locale con el que arrancas se pide de entrada, y la UI se actualiza cuando llega |
| formatters | object | Formatters custom {v:name} |
| partial | string[] | Idiomas cuyos mensajes llegaron con la página: no se pide nada para ellos, y un mensaje que la página no mostró carga el resto una vez en lugar de caer al fallback sin decir nada |
| onMissing | function | (key, locale): devuelve un string para sustituir; silencia el warn-once por defecto |
| onResolve | function | (info) en cada t(): key, locale, value y status (hit / fallback / miss). Alimenta los devtools |
Instancia
| Miembro | Descripción |
|---|---|
| t(key, params?) | Traduce. Las claves y los params se comprueban de tipo contra tus mensajes. Los datos malos nunca tumban un render, y tampoco pasan desapercibidos: un param que falta, un bloque de plural sin caso general, un hueco sin argumento, un valor que no es texto, un código de idioma que el navegador no sabe leer. Cada uno avisa una vez en la consola, nombrando el mensaje, la ruta o el código del que vino |
| t`...` | Tagged template: interpola texto fuente; el compilador lo mejora |
| t.id(key)`...` | Claves legibles opt-in: formatea el texto inline; el compilador lo reescribe a una llamada con clave |
| setLocale(locale) | Cambia el idioma y notifica a los suscriptores, auto-carga un catálogo lazy pendiente |
| loadLocale(locale) | Carga un catálogo lazy (narrowing BCP-47, deduplicado): haz await antes de setLocale para cambiar sin flash |
| addMessages(locale, tree, options?) | Mezcla mensajes en caliente: la primitiva de la carga diferida. Pasa { partial: true } cuando los mensajes son los de una sola página, para que el idioma siga incompleto y el siguiente mensaje que falte vaya a por el resto |
| subscribe(fn) | Listener de cambios; devuelve unsubscribe |
| has(key) | La clave existe en la cadena actual |
| inspect(key) | Locale de origen (from) + texto fuente de una key (devtools/herramientas) |
| locale | Idioma actual (readonly) |
| locales | Idiomas cargados + cargables (readonly): alimenta un switcher desde una sola fuente de verdad |
| version | Contador de cambios que impulsa los adapters de frameworks |
Helpers de idioma
localeFromPath y resolveLocale responden a dos preguntas distintas, y elegir la que no es el bug clásico. localeFromPath(options) pregunta en qué idioma está esta página. Lee el prefijo /{locale}/ y devuelve undefined cuando la url no lleva ninguno, así que en un sitio con un árbol por idioma escribes localeFromPath(...) ?? sourceLocale y nada puede contradecir a la url. resolveLocale(options) pregunta qué idioma quiere este visitante: primero el prefijo de la url, luego la elección guardada, luego navigator.languages con estrechamiento BCP-47, y luego tu fallback (path: false apaga la url). Úsalo para decidir a dónde mandar a alguien, nunca para decidir qué dice ya una página. persistLocale recuerda un cambio. Todos son seguros en SSR. Los dos primeros aceptan un base para un sitio servido desde una subcarpeta, y los de almacenamiento aceptan un storageKey propio (false apaga el almacenamiento). Mira HTML plano → Bootstrap de idioma para el patrón completo.
negotiateLocale(header, supported, fallback?) es la contraparte del lado servidor: dale un header Accept-Language y tus locales y devuelve la mejor coincidencia. Respeta los q-values y no distingue mayúsculas. Cada idioma encuentra el locale soportado más cercano: es-PE coincide con es, zh-HK con zh-Hant, y pt o pt-PT con pt-BR, pero nunca con uno de otra escritura (zh-HK nunca recibe zh-Hans). Funciona con cualquier servidor; las integraciones de SvelteKit, Nuxt y Next.js la usan por debajo.
resolveRequestLocale(options) toma toda la decisión por petición en una sola llamada. Dale el path y tu routing y decide la dirección: una página con prefijo es ese idioma para todo el mundo, y una sin prefijo es tu idioma fuente, así que una elección guardada no puede traducirla sin querer. Deja routing fuera y el orden es primero la cookie guardada, luego la cabecera Accept-Language, luego tu fallback. LOCALE_STORAGE_KEY exporta el nombre compartido (verbaly-locale) que usan tanto el almacenamiento del navegador como la cookie de SSR.
inlineMessages(root?) lee los mensajes que verbaly render escribió en la página cuando render.inlineCatalog está encendido, así que una página pre-traducida no necesita catálogo por red. Rara vez la llamas: el módulo generado ya lo hace, y pasa el resultado como partial para que un mensaje que la página no mostró se cargue igualmente cuando haga falta en vez de caer al fallback en silencio.
stripLocalePath(options) es la inversa de localePath: da la ruta que hay detrás de una dirección con idioma, así que un router puede casar un solo árbol en vez de uno por idioma. alternateLinks(options) devuelve la dirección de esta página en todos los idiomas más un x-default, lista para las etiquetas link rel="alternate" de una cabecera renderizada en servidor. Es el mismo conjunto que verbaly render escribe en un sitio estático, y está vacío cuando tus direcciones no llevan idioma, porque una sola dirección no puede decir honestamente que es todos los idiomas a la vez.
switchLocale(instance, locale, options?) es el cambio de idioma del lado cliente que comparten las integraciones SSR: carga el catálogo primero, luego cambia el locale, escribe la cookie verbaly-locale y actualiza los atributos lang y dir. Es seguro llamarlo en el servidor (ahí no hace nada); @verbaly/sveltekit lo re-exporta.
localeName(locale, displayIn?) y localeDirection(locale) alimentan un selector de idioma sin tablas hardcodeadas: nombres de idioma reales vía Intl.DisplayNames (por defecto el nombre en su propio idioma) y la dirección de escritura de cualquier locale. Los idiomas de derecha a izquierda no piden trabajo extra: switchLocale, persistLocale, las integraciones SSR y verbaly render ya mantienen el dir correcto por su cuenta.
Seguridad a nivel de tipos
v.t('home.title'); ✓
v.t('home.title', { x: 1 }); ✗ no params declared
v.t('nope'); ✗ unknown keyTypeScript parsea tus mensajes: FlatKeys aplana árboles anidados, ParamNames lee las apariciones de {param}, y TArgs exige los params exactamente cuando el mensaje los declara.
Con el compilador, los tipos generados rellenan Register, así que Translate es el t de tu proyecto y Locale la unión de tus idiomas: cada t que entrega un adaptador (useT(), getT()) y cada instancia del módulo generado comprueban claves y params contra tu catálogo. Una instancia que construyes desde un diccionario conserva los tipos de ese diccionario. Los tres son solo tipos, así que no cuestan nada en tiempo de ejecución.
Exports de bajo nivel
¿Construyes herramientas encima de Verbaly? El paquete exporta las mismas piezas que usa el compilador. parse(message) te da el AST del mensaje, y parseTags(text) con RICH_TAGS es el tokenizador de texto rico y su whitelist de etiquetas, que es lo que necesita un renderizador propio. flatten(tree) convierte un árbol anidado en claves con puntos. safeHref(href) y safeAttribute(name, value) son los guardas de url y de atributos, y normalizeLink(link) es el único normalizador de enlaces que comparten todos los adaptadores, junto con el tipo RichLink.