Keys are never split, each one is stored and looked up literally. Choose this when your keys contain dots or are natural text: versions, prices, filenames, scripture or legal references. App Version 6.3.8 stays exactly that.
Guide · Key nesting
Flat vs nested keys
By default Sonenta splits your keys on . into a nested JSON tree in the CDN bundle, the classic i18next shape. That is perfect for organised keys like checkout.review.confirm, but it silently mangles keys whose text contains a dot. This guide covers the project setting and the matching SDK option so your lookups always resolve.
The dotted-key trap
A key is just a string. When that string contains a literal dot, a version like App Version 6.3.8, a price, a filename, a scripture reference like Jean 3.16, splitting on . turns one key into an accidental nested object. The translation is still stored, but t("App Version 6.3.8") no longer finds it.
bundle.json 1// your source key, a literal label with dots in it2{ "App Version 6.3.8": "App Version 6.3.8" } 4// nested bundle (default), split on "." → broken tree5{ "App Version 6": { "3": { "8": "App Version 6.3.8" } } } 7// flat bundle, the key stays literal, lookups just work8{ "App Version 6.3.8": "App Version 6.3.8" } The project setting
Two project-level settings control how the CDN bundle is shaped. The defaults reproduce the current i18next behaviour, so existing projects are unaffected until you opt in.
| Setting | Values | Default |
|---|---|---|
| bundle_key_style | nested | flat | nested |
| bundle_key_separator | string | "." |
Set them on the project, in your dashboard's project settings, or via the projects API. The chosen style is baked into every release, and every published version reports it back, so any client can self-configure.
versions/main 1# the version object reports the active key style2GET /v1/projects/<project_uuid>/versions/main 4{ "slug": "main", "key_style": "flat", "key_separator": "." } Choosing flat or nested
Keys are split on the separator into a JSON tree, the classic i18next layout. Choose this for deliberately namespaced keys like checkout.review.confirm. This is the default.
Match it in the SDK
@sonenta/react-i18next (>= 0.11.0) takes a keySeparator option: false for literal / flat lookups, a string for nested, default ".". There is also nsSeparator (default ":"). The SDK is literal-first, it tries an exact bundle[key] match before any splitting, so dotted keys resolve even in nested mode. On start() it also auto-detects key_style / key_separator from the published version (best-effort).
main.tsx 1// src/main.tsx, match the bundle in @sonenta/react-i18next >= 0.11.02import { SonentaProvider } from "@sonenta/react-i18next"; 4<SonentaProvider5 projectUuid="<project_uuid>"6 token={import.meta.env.VITE_SONENTA_TOKEN}7 keySeparator={false} // literal lookup, for dotted / natural-text keys8 nsSeparator=":" // default; set false to disable ns parsing too9>10 <App />11</SonentaProvider> 13// then t() treats the whole string as one key, no splitting14t("App Version 6.3.8"); // ✓ exact match Auto-detection reads the version metadata and needs a key with project:read. If that read is denied (403) the SDK falls back gracefully to its defaults, so when in doubt, set keySeparator explicitly to match your bundle rather than relying on detection.
Recommendation
- Keys contain dots? Set
bundle_key_style: flaton the project andkeySeparator={false}in the SDK. Both ends literal, no surprises. - Cleanly namespaced keys (
checkout.review.confirm)? Keep the nested default; nothing to change. - Migrating an existing app? The defaults preserve your current behaviour. Flip to flat only when you hit a dotted key, then re-publish and update the SDK option together.