Skip to content

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.

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.

// your source key: a literal label with dots in it
{ "App Version 6.3.8": "App Version 6.3.8" }
// nested bundle (default): split on "." → broken tree
{ "App Version 6": { "3": { "8": "App Version 6.3.8" } } }
// flat bundle: the key stays literal, lookups just work
{ "App Version 6.3.8": "App Version 6.3.8" }

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.

SettingValuesDefault
bundle_key_stylenested | flatnested
bundle_key_separatorstring"."

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.

Fenêtre de terminal
# the version object reports the active key style
GET /v1/projects/<project_uuid>/versions/main
{ "slug": "main", "key_style": "flat", "key_separator": "." }

bundle_key_style: flat. 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.

bundle_key_style: 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.

@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).

// src/main.tsx: match the bundle in @sonenta/react-i18next >= 0.11.0
import { SonentaProvider } from "@sonenta/react-i18next";
<SonentaProvider
projectUuid="<project_uuid>"
token={import.meta.env.VITE_SONENTA_TOKEN}
keySeparator={false} // literal lookup, for dotted / natural-text keys
nsSeparator=":" // default; set false to disable ns parsing too
>
<App />
</SonentaProvider>
// then t() treats the whole string as one key, no splitting
t("App Version 6.3.8"); // ✓ exact match
  • Keys contain dots? Set bundle_key_style: flat on the project and keySeparator={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.
  • Guide: Migrate from i18next: Bring an existing catalogue into Sonenta in one command.
  • Reference: CLI: Import, push, pull and publish from your terminal.
  • Guide: Update source values: Change a key's source text after it already exists.