Inicio rápido
React + i18next
Instala el SDK, envuelve tu app en <SonentaProvider /> y llama a useTranslation(). Las claves faltantes llegan a tu dashboard automáticamente, sin cableado extra.
1. Instalar
Una única dependencia. Sin gimnasia de peer-dep, el SDK incluye todo lo del lado de React.
terminal 1npm i @sonenta/react-i18next 2. Envuelve tu app
SonentaProvider recibe un projectUuid y un defaultLocale; tu clave de API va en la prop token. No hay detección automática de locale, defaultLocale es obligatorio. Los namespaces se cargan bajo demanda desde el CDN, y las claves faltantes se envían por lotes, cada 5 segundos o cada 50 eventos, lo que ocurra primero.
main.tsx 1// src/main.tsx2import { SonentaProvider } from "@sonenta/react-i18next";3import { createRoot } from "react-dom/client";4import { App } from "./App"; 6createRoot(document.getElementById("root")!).render(7 <SonentaProvider8 projectUuid="proj_xxx"9 token={import.meta.env.VITE_SONENTA_TOKEN}10 defaultLocale="en"11 namespaces={["common"]}12 >13 <App />14 </SonentaProvider>15); Todas las props de SonentaProvider
| Prop | Type | Default |
|---|---|---|
| projectUuid | string | obligatoria |
| defaultLocale | Locale | obligatoria |
| children | ReactNode | obligatoria |
| token | string | req. unless transport |
| namespaces | Namespace[] | ["common"] |
| defaultNS | Namespace | - |
| keySeparator | string | false | "." (auto-detected) |
| nsSeparator | string | false | ":" |
| apiBase | string | https://api.sonenta.dev |
| cdnBase | string | https://cdn.sonenta.com |
| fetchImpl | typeof fetch | global fetch |
| env | "prod" | "dev" | "prod" |
| version | string | "main" |
| versionSlug (deprecated) | string | - |
| missingHandler | "send" | "log" | "off" | "send" |
| transport | (batch: MissingKeyEvent[]) => … | built-in POST |
| flushIntervalMs | number | 5000 |
| flushBatchSize | number | 50 |
| missingEventsBufferSize | number | 200 |
| initialBundles | Record<Locale, Record<...>> | - |
| languageCatalog | LanguageMeta[] | - |
| disableLanguageCatalog | boolean | false |
| disableLanguageManifest | boolean | false |
| fallbackLng | Locale | Locale[] | - |
| surface | Surface | - |
| surfaceBreakpoints | SurfaceBreakpoints | boolean | - |
| a11ySurfaces | A11ySurface[] | [] |
| plainLanguage | boolean | false |
| plugins | SonentaPlugin[] | - |
| interpolation | { format?: (…) => string } | - |
-
tokenis required unless you supply your owntransport. Passing neither throws at mount. It authenticates only the missing-key POST, the key-style probe, and the runtime fetch inenv: "dev". - Only
formatis configurable underinterpolation.escapeValueis alwaysfalse, because React escapes already. - Regional variants already fall back to their base (
fr-CAtofr) with no configuration.fallbackLngis appended after that chain, it does not replace it.
3. Usa el hook
useTranslation() devuelve { t, i18n }. Forma familiar si has usado react-i18next. i18n.ready te indica cuándo los namespaces iniciales se han hidratado; i18n.changeLanguage() cambia el locale en runtime.
Checkout.tsx 1// src/Checkout.tsx2import { useTranslation } from "@sonenta/react-i18next"; 4export function Checkout() {5 const { t, i18n } = useTranslation("common"); 7 if (!i18n.ready) return null; // first paint after hydration 9 return (10 <button onClick={() => i18n.changeLanguage("fr")}>11 {t("checkout.review.confirm")}12 </button>13 );14} Lo que obtienes gratis
- Captura de claves faltantes. Cualquier clave que llames y que no esté en el diccionario se encola, se debouncea (5s por defecto) y se envía por POST a la cola de faltantes de tu dashboard. Seguro en producción, tu fallback sigue renderizando.
- Namespaces servidos desde CDN. Los bundles de traducción se extraen de
cdn.sonenta.comcon caché HTTP y stale-while-revalidate. No hace falta bundling en build-time. - Auto-detección de locale. Si no pasas
defaultLocale, el SDK leenavigator.languagey recae en el default de tu proyecto. - Exports abiertos. Todo lo que envíes a Sonenta, lo puedes exportar de vuelta a JSON i18next, XLIFF o PO. Cambia de herramienta mañana sin reescribir tu código.
Three failures that stay silent
Each of these leaves your page looking perfectly fine. None of them raises an error you will notice.
- A broken token barely warns, and never throws. Passing no
tokenand notransportthrows at mount. A token that is present but shaped like an unset environment variable ("undefined","null") produces aconsole.warn. Both guards exist only from@sonenta/react-i18next2.6.1 (@sonenta/i18n-core1.1.3); below that, nothing is reported at all. And an empty string, the?? ""you write to satisfy TypeScript, is covered by no warning at any version. In every case the server returns 401, the SDK degrades gracefully, the CDN keeps serving, and your app stays unauthenticated. Do not grep your logs forApiKey undefined: with?? ""that is a guaranteed false negative. Read theAuthorizationheader of the network request in your deployed build instead. - A missing namespace looks like an empty one. Namespace bundles are fetched from the CDN only when
envis"prod"; in"dev"they come from the authenticated runtime API instead. And a 404 resolves to an empty bundle, with no error and no retry, so a namespace you have not published yet is indistinguishable from one that is genuinely empty. - Undeclared a11y surfaces fall back quietly. The accessibility accessors only read surfaces you asked for at load time, via
a11ySurfaces. Without it the overlay is never downloaded andt.aria(key)simply returns the visible text, with no error and no warning. The symptom is not a crash, it is anaria-labelthat duplicates the label next to it.
Transport personalizado (avanzado)
¿Necesitas loguear claves faltantes en tu propio stack de observabilidad, protegerlas detrás de tu auth o stubearlas en tests? Pasa una función transport. El SDK sigue haciendo debounce y batching; tú decides qué pasa con el batch.
main.tsx 1// custom transport, useful for tests, edge cases, or auditing2<SonentaProvider3 projectUuid="proj_xxx"4 token={import.meta.env.VITE_SONENTA_TOKEN}5 flushIntervalMs={2000}6 transport={(batch) => fetch("/internal/i18n-misses", {7 method: "POST",8 body: JSON.stringify(batch),9 })}10/>