Avvio rapido
React + i18next
Installa l'SDK, avvolgi la tua app in <SonentaProvider /> e chiama useTranslation(). Le chiavi mancanti arrivano alla tua dashboard automaticamente, nessun cablaggio extra.
1. Installa
Una sola dipendenza. Niente ginnastica peer-dep, l'SDK include tutto il lato React.
terminal 1npm i @sonenta/react-i18next 2. Avvolgi la tua app
SonentaProvider prende un projectUuid e una defaultLocale; la tua chiave API va nella prop token. Non c’è rilevamento automatico del locale, defaultLocale è obbligatorio. I namespace vengono caricati su richiesta dal CDN, e le chiavi mancanti vengono inviate a lotti, ogni 5 secondi o ogni 50 eventi, a seconda di quale arrivi prima.
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); Tutte le props di SonentaProvider
| Prop | Type | Default |
|---|---|---|
| projectUuid | string | obbligatoria |
| defaultLocale | Locale | obbligatoria |
| children | ReactNode | obbligatoria |
| 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 l'hook
useTranslation() restituisce { t, i18n }. Forma familiare se hai usato react-i18next. i18n.ready ti dice quando i namespace iniziali sono idratati; i18n.changeLanguage() cambia locale a 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} Cosa ottieni gratis
- Cattura delle chiavi mancanti. Ogni chiave che chiami e che non è nel dizionario viene messa in coda, debounciata (5s di default) e inviata via POST alla coda delle mancanti della tua dashboard. Sicuro in produzione, il tuo fallback continua a renderizzare.
- Namespace serviti da CDN. I bundle di traduzione sono recuperati da
cdn.sonenta.comcon caching HTTP e stale-while-revalidate. Nessun bundling a build-time richiesto. - Auto-rilevamento del locale. Se non passi
defaultLocale, l'SDK leggenavigator.languagee ricade sul default del tuo progetto. - Export aperti. Qualunque cosa pushi su Sonenta, la puoi esportare in JSON i18next, XLIFF o PO. Cambia tool domani senza riscrivere il tuo codice.
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 personalizzato (avanzato)
Serve loggare le chiavi mancanti nel tuo stack di observability, proteggerle dietro la tua auth o stubbarle nei test? Passa una funzione transport. L'SDK fa comunque debounce e batching; tu decidi cosa fare del 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/>