Skip to content

Releases & bundles

A release is a snapshot of a project’s translations, compiled into one file per language the project had at the moment it was published, and uploaded to the CDN. Publishing a release is what makes translations available to your application.

A release is immutable in both directions. Adding a language to a project does not add it to releases that already exist — there is no bundle for it under their slugs — and removing one does not retract what was already published. Publish a new release to distribute a new language.

The API tells you which releases exist and where their files are. It does not serve the files.

Formats

Every language a release ships is compiled into three formats, all containing the same content:

FormatExtensionTypical use
JSON.jsonWeb and server applications, most i18n libraries
YAML.yamlRuby, configuration-driven stacks
ARB.arbFlutter / Dart intl

You do not choose a format when publishing — all three are always produced. Pick whichever the consumer wants at download time.

URL structure

https://cdn.localeo.app/p/{projectSlug}/r/{releaseSlug}/{language}.{ext}
  • projectSlug — a random identifier for the project, fixed when the project is created.
  • releaseSlug — a random identifier generated for a release when it is published. A draft has none.
  • language — the language code, e.g. en, nl, pt-BR.

Both slugs are 16 random characters. They are not the IDs you see in the API (Qk3vAe, 7bWq0R) and they cannot be derived from them.

You should not construct these URLs yourself. Read them from download_urls on GET /releases/{release_id} — with one exception, below. The languages listed there are the ones that release shipped, so a URL you read from it always resolves; one you assemble for a language the project gained later does not.

Always fetching the latest release

There is a stable, predictable path that always points at the newest published release of a project:

https://cdn.localeo.app/p/{projectSlug}/r/latest/{language}.{ext}

Note the literal latest where a release slug would be. This path is intentionally guessable, because its whole purpose is to be hard-coded.

This is the right choice for most applications. It means:

  • No API call at all on the hot path. No token, no rate limit, no metadata request — just a CDN fetch.
  • New translations appear automatically when a release is published, with no deploy.

latest tracks the newest release’s languages exactly. A language the newest release does not ship is removed from latest when that release is published, so a hard-coded latest/{language} path starts returning 404 rather than continuing to serve an older release’s copy of it.

The trade-off is that content changes underneath you. If you need a build to be reproducible — the same commit producing the same strings a year later — pin to a release slug instead.

Caching and invalidation

The two kinds of URL have deliberately opposite cache policies:

URLCache-ControlBehaviour
Pinned release (/r/{releaseSlug}/)public, max-age=31536000, immutableCached for a year. The content never changes, because a new publish means a new slug.
Latest (/r/latest/)public, max-age=60, must-revalidateCached for a minute, and purged from the CDN when a release is published.

So a pinned URL is as cheap as a static asset and safe to cache aggressively at every layer. A latest URL costs a revalidation at most once a minute, and a publish becomes visible within about that long.

The security model

The download URLs carry no authentication. They are not signed, they do not expire, and anyone holding one can fetch the file.

This is a deliberate design — a capability URL. Access control is that the path cannot be guessed: both slugs are 16 random characters, so the URL itself is the secret. It is what makes bundles cheap to serve from a CDN edge with no auth round-trip, which is the reason translations load fast.

What follows from that:

  • A download URL is a credential. Treat it as one. Putting it in a public repository or a client-side bundle publishes those translations.
  • Leaking one release’s URL does not expose others. Each release gets its own random slug, so one URL reveals nothing about a project’s other or future releases.
  • latest is the exception. Its path is predictable given the project slug, so anyone with any download URL for a project can reach that project’s latest bundle. That is by design, but worth knowing before you share a URL.
  • Revoking access means rotating a slug. Deleting the API token does not invalidate URLs it already revealed — the files are not behind the token. Rotating a release’s public slug revokes that release; rotating the project’s revokes every release at once. Both leave the old URLs returning 404 once the CDN cache clears.

Drafts

A release that has not been published has no slug and no files, and the public API does not acknowledge it: GET /releases omits it and GET /releases/{release_id} returns 404, identical to a release that does not exist.

If a release you expect is missing, it is almost always still a draft.