Skip to main content
astilba splits ownership of a project cleanly in two, and that split is what lets developers and translators work the same project without stepping on each other:
  • Code owns the source language. It’s authored in the repo and pushed up.
  • The dashboard owns every other language. Translators edit there; their work is never overwritten by a push.
Everything else follows from that invariant. The loop is push → edit → pull.

The loop

1

Set up (once)

An owner or admin sets up the project and mints an API token from the dashboard, which authenticates with your session — no token needed yet: create the project with the New project form, then mint a token on the Tokens page (owner/admin only). Export both for the CLI:
export ASTILBA_URL=https://astilba.example.com
export ASTILBA_TOKEN=
Prefer to create the project from the CLI? astilba create --project web --name "Web App" does the same — but run it after exporting the token, since it authenticates just like push/pull and fails fast without a server and token.
2

Push your source strings

From the repo, upload the code-owned bundles:
npx astilba push --project web ./locales
The server applies the ownership policy: the source language overwrites (code owns it), and every other language fills only its gaps — a push never clobbers a translation someone wrote in the dashboard. Push is additive: removing a key locally doesn’t delete it on the server. Run with --dry-run first to preview the counts.
3

Translate in the dashboard

Translators open the project, pick a language and namespace, and edit values inline. Each save overwrites just that one value. (Today only simple string values are editable — plurals and contexts are read-only.)A target language shows the keys it already has. Those arrive from a push that includes that language, or from a running app registering them via i18next saveMissing — a source-only push does not create empty rows in the target languages, and there’s no “untranslated source keys” view in the dashboard yet.
4

Pull translations back into code

Bring the translators’ work into the repo:
npx astilba pull --project web ./locales
pull writes one ./locales/<lang>/<ns>.json per language and namespace — the server renders each bundle, so pull never re-implements i18next. Commit the result.
5

Guard fidelity in CI

Keep the round-trip honest — fail the build if any locale dropped, added, or altered an interpolation placeholder:
- run: npx astilba check

Why push and edit don’t fight

The two writers touch disjoint data:
WriterWritesRule
astilba pushthe source languageoverwrites (code is the source of truth)
astilba pushtarget languagesfill-only — only keys that don’t exist yet
dashboard edittarget languagesoverwrites one value at a time
So a push and a dashboard edit never race: push only ever overwrites the source language, and a translator’s edit to a target language can’t be undone by the next push (targets are fill-only). A key you delete locally stays on the server until it’s removed there — pushes never delete.

Authentication

Everything after setup runs on an org API token (ASTILBA_TOKEN, sent as x-api-key). The token is org-scoped: it reaches every project in the org by slug, and another org’s slug returns the same 404 as a project that doesn’t exist. Mint tokens per CI system or per developer so you can revoke them individually.

Self-hosting

Don’t have a server yet? Stand one up first.