Ir para o conteúdo
Sonenta

Guide · Lifecycle

How your content reaches production

Sonenta sits between a key in your code and the value your app fetches at runtime. This page walks the whole path: how content is created, how keys are named, the statuses a value moves through, and how to confirm a language is live. Read it once and skip the trial and error.

Creating content

You can do everything from the CLI or over MCP. The dashboard is one option, not the only one, and the old "creation is dashboard-only" idea is out of date.

With an API key, over MCP (no human in the loop)

An mcp:* API key lets your agent create namespaces (create_namespace) and keys (create_key, or create_keys_bulk for up to 500 at a time, idempotent), and bulk-write values from your source files with sonenta push. The CLI mirrors this with the same key: sonenta namespaces create / list, and sonenta push auto-creates any missing namespace as it uploads (--no-create-namespaces to opt out; CLI 0.46.0). No sign-in needed.

What needs an account session

Creating a project (sonenta projects create) or an API key (sonenta api-keys create) requires an account session. Run sonenta login once: it opens a browser device flow that a human org-admin approves a single time, then the token is stored and auto-refreshes, so your agent can chain those commands afterwards with no further human step. projects create takes --slug, --description, --source-language (default en) and --org; api-keys create needs --name and takes --scopes (default mcp:*), --env, --projects and more. The API-key secret is shown only once. Available since CLI 0.32.0.

terminal
1# one-time: a human org-admin approves this once2sonenta login 4# then your agent can run, unattended:5sonenta projects create "My app" --source-language en6sonenta api-keys create --name "ci" --scopes mcp:*7#   the API-key secret is printed once, then never again

Why creation isn't an MCP tool

Creating projects and API keys are org-admin, account-level operations. They live on the CLI (account session) and are not in the MCP surface by design: they need an account token, not an API key, so an agent holding only an API key can't mint new projects or keys for itself. That boundary is intentional. In short: keys and namespaces are agent self-service over MCP; projects and API keys come from the CLI after a one-time human sonenta login, then run autonomously.

How to name keys

A key is two separate fields: a namespace (a slug, e.g. common) and a name in dot-notation (e.g. hero.title). At runtime the SDK addresses it as namespace:name. The dots group keys inside a namespace; they are not the namespace.

key
1// namespace and name are SEPARATE fields2namespace "common" + name "title"        -> common:title3namespace "common" + name "hero.title"   -> common:hero.title4// the trap: the slug repeated inside the name5namespace "common" + name "common.title" -> common:common.title6//                                          ^ doubled namespace

On the file side (sonenta push / pull), the tree is locales/<lang>/<namespace>.json with flat dot-notation keys. The filename already carries the namespace, so don't re-prefix every key with the same slug or you double it.

Caught automatically

This isn't only a style tip. validate_translations reports a doubled namespace as a structural_issues[] entry ({namespace, key, issue, detail} with issue: 'self_namespaced_key', plus counts.structural_issues). The detail reads: "key '{key}' is prefixed by its own namespace '{ns}': this creates a redundant {ns}: {…} layer in the bundle, which makes the SDK silently fall back to the source locale. Remove the '{ns}.' prefix (store it as '{stripped}')." The write path rejects it outright too: create_keys_bulk rejects the item (self_namespaced_key), the i18next import rejects the unit (redundant_namespace_layer), and sonenta ci fails whenever structural_issues isn't empty.

The statuses a value moves through

A value moves through these statuses. Publishing starts at translated: anything below it stays out of the CDN.

A pure draft can't be approved in one step (approve_translation returns not_approvable); it has to reach translated first. The default export scope (XLIFF, CSV) is likewise translated,reviewed,approved and leaves draft out unless you ask for it.

What reaches the CDN

Publishing (publish_cdn) builds one bundle per language and namespace. Only publishable statuses go in, and only non-empty values.

Included

translated, reviewed, approved (everything from the translated threshold up).

Excluded

draft, missing, rejected. An empty value is skipped too, even when its status would otherwise qualify.

The empty-bundle guardrail

This is why a namespace that is all draft can't quietly ship nothing. If none of a bundle's keys are publishable, publish_cdn skips that bundle (skipped: true, nothing is overwritten) and returns a warnings[] entry ({language_code, namespace, key_count, included_count, excluded_count, skipped, reason}). The reason reads: "namespace '{ns}' / '{lang}': 0 of {N} keys are publishable: all are below 'translated' status (e.g. draft). The bundle is EMPTY: nothing will be served. Publish was SKIPPED for this bundle; set allow_empty=true to force an empty bundle." Pass allow_empty: true to publish the empty bundle anyway (still flagged). The upshot: you can't accidentally blank out a live namespace by publishing a pile of draft keys.

Confirming a language is live

Before you publish: validate

Run validate_translations before publishing to catch quality problems while they're still cheap to fix. With no payload it audits the stored values (pass a payload to inspect a submission instead) and returns quality_issues[] (plus counts.quality_issues); each entry is {key, type, detail}, where type is one of same_as_source, wrong_language, or looks_like_raw_key. The detail reads, for example: "value is identical to the source, likely not translated"; "value is written in {Script} script but '{lang}' expects {Expected}"; "value reads like '{lang}', not the target '{target}' (stopword heuristic)"; or "value '{v}' looks like a key path/identifier, not text". It's advisory: it flags, it never rewrites your values.

Once it's published, there are two questions, with two checks:

Is it published?

Use distribution_info (read-only over MCP, zero credits): it returns the CDN base and bundle-URL template, the production version, which language and namespace pairs are published, and any gaps. That is exactly what the client SDKs can fetch right now.

Does it resolve?

Run sonenta pull --language <code>: it downloads the resolved translations for that language, concrete proof the values fetch.

Note that sonenta status diffs your local copy against the remote project, not the CDN, so it is not a publication check. Subscribed SDKs also receive a translations_published event and refresh in place. Two preflight helpers are worth knowing: sonenta doctor checks that the MCP is wired, reachable and mcp:*-scoped, and sonenta ci evaluates the i18n impact of a pull request.

Next