Início rápido
React + i18next
Instala o SDK, envolve a tua app em <SonentaProvider /> e chama useTranslation(). As chaves em falta seguem para o teu dashboard automaticamente, sem ligações extra.
1. Instalar
Uma única dependência. Sem ginástica de peer-dep, o SDK inclui tudo do lado React.
terminal 1npm i @sonenta/react-i18next 2. Envolve a tua app
SonentaProvider recebe um projectUuid e um defaultLocale; a sua chave de API vai na prop token. Não há deteção automática do locale, defaultLocale é obrigatório. Os namespaces são carregados a pedido a partir do CDN, e as chaves em falta são enviadas em lotes, a cada 5 segundos ou a cada 50 eventos, o que ocorrer primeiro.
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 as props do SonentaProvider
| Prop | Type | Default |
|---|---|---|
| projectUuid | string | obrigatória |
| defaultLocale | Locale | obrigatória |
| children | ReactNode | obrigatória |
| 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 o hook
useTranslation() devolve { t, i18n }. Forma familiar se já usaste react-i18next. i18n.ready indica quando os namespaces iniciais estão hidratados; i18n.changeLanguage() troca o locale em 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} O que recebes de borla
- Captura de chaves em falta. Qualquer chave que chamares e que não esteja no dicionário é enfileirada, com debounce (5s por defeito) e enviada por POST para a fila de chaves em falta do teu dashboard. Seguro em produção, o teu fallback continua a renderizar.
- Namespaces servidos por CDN. Os bundles de tradução são puxados de
cdn.sonenta.comcom caching HTTP e stale-while-revalidate. Sem necessidade de bundling em build-time. - Deteção automática do locale. Se não passares
defaultLocale, o SDK lênavigator.languagee recai no default do teu projeto. - Exports abertos. O que enviares para o Sonenta, podes exportar de volta para JSON i18next, XLIFF ou PO. Muda de ferramenta amanhã sem reescrever 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 (avançado)
Precisas de registar chaves em falta na tua própria stack de observability, escondê-las atrás da tua auth ou simulá-las em testes? Passa uma função transport. O SDK continua a fazer debounce e batching; tu decides o que acontece ao 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/>