Bundle format
Everything a visitor's browser needs is static JSON: UTF-8, minified, served with Access-Control-Allow-Origin: *. This page summarises BUNDLE_FORMAT.md in the repository, which is the normative text.
URL layout#
<base>/v1/<siteKey>/manifest.json mutable, short cache
<base>/v1/<siteKey>/<version>/<lang>.<hash8>.json immutable, cached for a year
<base> is a delivery origin: your own site, then the primary CDN, then the secondary CDN. <version> increases with every publish. <hash8> is the first 8 hex characters of the file's SHA-256.
| File | Cache-Control |
|---|---|
manifest.json | public, max-age=60, stale-while-revalidate=86400, stale-if-error=604800 |
| language files | public, max-age=31536000, immutable |
manifest.json#
{
"schema": 1,
"siteKey": "rl_site_live_abcdefghijklmnopqrstuv",
"version": 42,
"generatedAt": "2026-10-28T12:00:00Z",
"sourceLanguage": "en",
"languages": [
{ "code": "fr", "name": "French", "nativeName": "Français", "dir": "ltr",
"file": "42/fr.1a2b3c4d.json", "sha256": "<64 hex>", "bytes": 18234, "strings": 512, "urlPrefix": "/fr" }
],
"cdns": ["https://cdn-a.example/", "https://cdn-b.example/"],
"kid": "<signing key id>",
"signature": "<base64url Ed25519>"
}
languages[].codeis a BCP 47 tag (fr,pt-BR,zh-Hans).dirisltrorrtl.urlPrefixappears only where the platform serves language URLs.- Signatures are mandatory.
signatureis a base64url Ed25519 signature over the RFC 8785 (JCS) canonical form of the manifest without thesignaturemember.kidnames the signing key. The loader and plugins hold the public key(s) and refuse a manifest whose signature is missing or invalid, whosekidis unknown, or whoseversionis lower than the highest already accepted (rollback protection). - Manifests carry no expiry, so an outage cannot stop translations.
- Language files are protected by the
sha256in the signed manifest.
Language file#
{ "schema": 1, "lang": "fr", "version": 42,
"strings": { "0e9408353c729c32": "Ajouter au panier" },
"attrs": { "<key>": "Rechercher" } }
strings are text nodes and text blocks; attrs are attribute values (alt, title, placeholder, aria-label, button value, selected meta content). The publisher puts attribute strings in attrs, never in strings. A missing key means "show the source text"; it is never an error.
What the loader checks in a manifest#
A manifest that fails any rule is refused as a whole (the page keeps the cached accepted version, else the source language):
schemais 1,siteKeyequals the configured site key,versionis an integer from 0 to 2^53-1.kidmatches^[A-Za-z0-9_-]{1,32}$andsignatureis 86 base64url characters (a 64-byte Ed25519 signature).cdnsis present, at most 5 entries, each anhttps://DNS host with a trailing slash and no port, credentials, query or IP address.- At most 200 languages; each has a valid
code,dir(ltrorrtl),nameandnativeName(1 to 100 characters, no control characters),sha256(64 lowercase hex),fileof the form<version digits>/<code>.<sha256 first 8>.json,bytesfrom 0 to 5 MiB, andurlPrefixnull or a single path segment such as/fr. - Only integers appear in the signed data; anything else is refused.
How the signature and version are judged across key changes is on Signing and key epochs.
Versioning#
schema changes only additively within v1. A breaking change creates /v2/ paths.
Next: how keys are computed, in Strings and keys, and how the loader consumes all this, in The loader.