Shipeasy
Translations

Profiles

Versioned, named manifests of label keys for one locale — the unit you publish, diff, and roll back.

ReferenceOn this page · 8 min readUpdated · May 3, 2026Works with · Next, Vite, Remix, Astro, Expo

A profile is a named, versioned manifest of label keys for a specific locale combination. It is the unit you publish, version, diff, and roll back. Everything else in Shipeasy Translations — drafts, AI translations, the loader script, validation in CI — pivots around the profile.

If gates and configs are "one row per setting", profiles are "one snapshot per locale per publish". Every publish is immutable; revert is just "republish version N".

Naming

Profiles use locale:

ProfileMeaning
enEnglish, production
frFrench, production
de:stagingGerman, staging
ja:devJapanese, dev
pt-BR:prodBrazilian Portuguese, production

The locale is BCP-47 (en, fr, pt-BR, zh-Hant). The optional :suffix (:dev, :staging, :prod) is just part of the profile name — a convention teams use to keep an in-review copy separate from the live one.

Profiles are not per-environment values

Translations have no built-in environment axis. Unlike a config or killswitch — which stores a distinct dev / staging / prod value behind one name — a profile is a single named snapshot. "Staging" vs "prod" copy means two separate profiles (fr:staging and fr:prod) that you pin and promote by name (see below), not one profile with per-env values.

Why region tags matter

Use a region tag (pt-BR, es-MX, zh-Hant) whenever register, vocabulary, or script differs from the base language. The AI translator switches its system prompt based on the region tag — es-MX produces different copy than es-ES, and zh-Hant writes traditional characters where zh-Hans writes simplified.

Anatomy of a profile

Every profile is a row in the project's database plus a published bundle in KV. The dashboard's Profiles list surfaces it with rough shape { id, name, locale, role, current_version, key_count, missing_count, stale_count, published_at } — see packages/core/src/db/schema.ts (labelProfiles) for the canonical fields.

The runtime fetches the strings via the i18n loader script (see The loader script below), which talks to https://api.shipeasy.ai/sdk/i18n/strings?profile=<name> with the client key. The published bundle is served from KV through the CDN with long TTL and explicitly purged on every publish.

Lifecycle

Create

# name is positional; --locales is comma-separated (defaults to "en")
shipeasy i18n profiles create fr --locales fr

On creation the profile is empty (key_count: 0, no version). It is registered in your project but nothing is written to KV until the first publish.

Populate

Populate the source profile via codemod (shipeasy i18n scan --rewrite then i18n push). For target locales, drive the AI translation from the dashboard Drafts panel or the i18n_translate_draft MCP tool — the CLI subcommand for AI drafts isn't shipped today. See Drafts for the full review flow.

# Source profile — pushed from a code scan
shipeasy i18n push --profile en --source codemod

Publish

Each publish creates a new immutable version. The current version is what the loader serves from KV. Old versions stay in the dashboard for rollback.

shipeasy i18n publish --profile fr

Publishing is two writes and one purge: write the chunked bundle to KV, write a new row to i18n_profile_versions, purge the CDN URL for the locale's index.json. End-to-end latency is typically under three seconds globally.

Diff

The version-to-version diff lives in the dashboard's Versions tab — added, removed, and changed keys with their old and new values. CLI diff / rollback subcommands aren't shipped today; both flows are dashboard-driven.

Roll back

From Profiles → fr → Versions, click any prior version → Restore. The selected version is republished as the new current version (so v17 → restore v15 → produces v18 with v15's contents). This keeps history append-only and roll-forward-only.

Rollback typically goes live in under five seconds — same path as a normal publish.

Source vs target

Shipeasy Translations does not hard-code English as "the" source. The first profile you create on a project is implicitly the source: it is where the codemod pushes discovered keys, and it is the basis the AI translates from.

Other profiles are targets: they hold translations of the source's keys. You can switch the source profile in Project → Shipeasy Translations → Settings, but in practice every team picks one and sticks with it.

Profile inheritance and fallback

When a key is missing from a target profile the loader falls back to the source profile :

loader requests fr label "checkout.cta"
  ↓ fr has no value
  ↓ falls back to source profile (en)
  ↓ returns "Pay"

This means an in-progress French translation never breaks the page — visitors see English for the missing strings until you publish translations. Fallbacks are also visible in the dashboard so you can target what to translate next; the Coverage column shows 1272 / 1284 (99.1%) per target profile.

The fallback only crosses locale. fr does not fall back to fr — staging is meant to be the place where bad translations are caught.

Per-locale profiles

Why not just one profile per locale? Because:

  • Translators can work in fr while fr keeps serving last week's strings.
  • New keys land in en:dev first; you do not need to translate them until they are promoted to staging or prod.
  • An emergency copy fix in en does not require redoing translation review for development branches.
  • Preview deployments (Vercel, Cloudflare Pages branch previews) can pin themselves to :dev or :staging so QA sees the unreviewed copy without it leaking to users.

Promotion (fr:stagingfr:prod) is a dashboard operation today — open Translations → Profiles → fr:staging → Promote and pick the destination. A CLI i18n promote subcommand is on the roadmap.

The recommended flow: write and translate in staging, validate (shipeasy i18n validate ./src --profile fr:staging), then promote to prod from the dashboard. Promotion creates a draft on the destination so you still get one final review before it ships.

Pinning a build to a version

Drop the loader into your page once; it reads data-key (your client SDK key) and data-profile (the locale:env profile, e.g. fr:prod) and installs window.i18n with t(key, vars):

<script
  src="https://api.shipeasy.ai/sdk/i18n/loader.js"
  data-key="sdk_client_..."
  data-profile="fr:prod"
></script>

The loader always serves the current published version. Per-page version pinning (snapshot deploys, locked landing pages) isn't a shipped feature today — the workaround for "freeze the copy for this signed contract" is to publish into a separate, no-longer-edited profile (e.g. fr:contract-2026-q2) and point that page's script tag at it.

API

The same operations are available over HTTPS for programmatic use; the CLI is a thin wrapper over these.

Field
Type
Description
POST /translations/profilesrequired
endpoint
Create a profile. Body: { name, locale, role? }.
GET /translations/profilesrequired
endpoint
List profiles for the project. Returns id, name, current version, key/missing/stale counts.
GET /translations/profiles/:namerequired
endpoint
Profile detail with current version and loader URL.
POST /translations/profiles/:name/publishrequired
endpoint
Publish current draft set. Returns new version id and CDN purge status.
POST /translations/profiles/:name/rollbackrequired
endpoint
Republish a prior version. Body: { version }.
GET /translations/profiles/:name/versions
endpoint ?
List versions kept under the current plan.
GET /translations/profiles/:name/diff
endpoint ?
Diff between two versions. Query: ?from=v15&to=v17.

All endpoints take a personal access token or a CI key. The runtime SDK key cannot create or publish profiles — it is read-only against the loader URL.

Limits

PlanProfiles per projectKeys per profileVersions kept
Free51,00010
Pro5050,000100
EnterpriseUnlimitedUnlimitedUnlimited

Hitting a limit does not break published profiles — it blocks new publishes until you upgrade or prune. Old versions past the retention window are deleted from KV; the metadata row stays so you can see what was deleted.

Deleting a profile is not reversible

Deleting a profile removes its KV bundle, all versions, and all drafts. The CDN purge is instant but in-flight loader requests may still receive a stale 404 for up to a minute. Always rename or stop traffic before deleting.

NEXT

Now fill them with keys.

Profiles are empty until you push keys. The codemod finds them automatically — and the schema decides how the AI translates each one.

Create source
$shipeasy i18n profiles create en --locales en
Create target
$shipeasy i18n profiles create fr --locales fr
Versioned · CDN-pinned · rollback in seconds
Was this page helpful?✎ Edit on GitHub

On this page