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.reviewed(optional integer) is how many of the language'sstringsa person approved; it is signed with the rest of the manifest, absent means none reported, and readers that do not use it ignore it. The language file may list the approved keys in an optionalreviewedarray.- 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.