Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Configuration and time

Configuration and time

Failures for client apps

On this page

A client app can show a failure to its user in the user's language. Word the failure in its domain's language files under its code (its code: when it has one), as you would a message. Its client: { } fields are the placeholders, and kind: and message: are not. The wording is never written on the failure type itself, so the same rule holds for all text, Jadpo's and packages' failures included. Names are unique across the project, so a failure and a message never share a key.

Every failure that can reach a response, which is any failure some route's fails lists anywhere in the project, must have wording in every supported locale. Any other failure may be worded, and needn't be. A client field no wording writes isn't an error: the client gets it in details anyway. The not-found failure the compiler provides for an entity (The not-found failure) is worded in the entity's domain, as one the project declares is. A project that declares no locales { } words no failures.

A client field that is a date, a time or a number is sent as a value (an Instant stays an instant on the wire, Output and status). The client formats it for its own language before filling the placeholder. So, unlike a message's, a failure's date or time field can be a placeholder. The field's type decides how the client formats it:

Client field type Format
CalendarDate date
Instant date_time, shown in the reader's own zone by the client
ZonedTime date_time, in the zone it carries
Duration duration
Int number
Decimal decimal
Text, PresentationText, Uuid, Email, Url, IpAddress none: written as it is
A type the client cannot format (an object, a list, an enum, T?) translation.field_type, as for a message's placeholder

A type built on one of these is formatted as the one it is built on.

example
use booking/types { SeatCount, SlotTime }/// The slot was taken while the booking was made.type SlotTaken = Failure {    kind: FailureKind.conflict    client: {        next_free: SlotTime        seats_left: SeatCount    }}
example
translations {    slot_taken: "Ce créneau vient d'être pris. Le prochain est {next_free}, avec {seats_left} places."}

The file for client apps

jadpo check writes, beside each API's OpenAPI document in build/openapi/, one file per supported locale named <api>.failures.<locale>.json (main.failures.fr_fr.json), for client apps to show. It maps each failure code to an object of the same shape, with its keys sorted as in every generated JSON file, so a client has one way to read it:

  • text is the wording in {field} syntax, a literal brace doubled as in a language file.
  • formats lists only the placeholders the wording writes that the client must format, each as one of number, decimal, date, date_time or duration.

A locale in progress is sent what the server would write for it, its regional sibling's wording and then the default's, so a client has text for every code. The example above writes this for fr_fr (only slot_taken's entry is shown; the file also holds Jadpo's own failures, below):

json
{  "slot_taken": {    "formats": {      "next_free": "date_time",      "seats_left": "number"    },    "text": "Ce créneau vient d'être pris. Le prochain est {next_free}, avec {seats_left} places."  }}

The server's message stays fixed English, for developers and logs (message:).

Jadpo's own failures (invalid_input, rate_limited and the rest) are worded with Jadpo's own messages (Jadpo's and packages' text), and join every API's file. An API gets the ones its routes can meet: the request failures for any API with routes, and the failures of signing in for one with a route that needs a caller. A Jadpo failure's text is your app's root wording for the locale, else what Jadpo ships in it, else the in-progress fallback. Your app's own failure under the same code is sent in place of Jadpo's. So an API with routes has a file for every supported locale, whether or not the app declares failures of its own.

example
translations {    jadpo: {        deadline_exceeded: "Cela a pris trop de temps, et rien n'a été enregistré. Réessayez."        idempotency_key_reused: "Cette requête a déjà été envoyée avec d'autres détails. Recommencez."        internal_fault: "Un problème est survenu de notre côté. Réessayez bientôt."        invalid_input: "Certaines informations envoyées ne sont pas valides. Vérifiez-les et réessayez."        invalid_request: "La requête n'a pas pu être lue. Réessayez."        method_not_allowed: "Cette adresse ne permet pas cette action."        rate_limited: "Trop de requêtes. Patientez un instant, puis réessayez."        request_too_large: "Ce qui a été envoyé est trop volumineux. Envoyez moins et réessayez."        route_not_found: "Nous n'avons pas trouvé ce que vous cherchiez."        unavailable: "Un service dont dépend l'application est indisponible pour le moment. Réessayez bientôt."    }}

Jadpo's and packages' text

Text Jadpo writes itself, its built-in emails and its failures' wording, is Jadpo's own messages. (Jadpo generates endpoints only, never pages.) Jadpo ships their wording in en_gb and en_us, and later releases add locales.

An app supporting any other locale words them itself. Until it does, jadpo check fails with one error for each such locale, translation.jadpo_missing, pointing at the locale in supported: (or at the missing file's name), whose reason lists every missing message. An in_progress: locale falls back instead. The default locale is held to the same rule: if Jadpo doesn't ship it, the app words the messages itself. translation.field_unused doesn't apply to Jadpo's messages.

Only the messages of features your app has switched on count. Rewording a message of a feature that is off, such as magic-link wording without magic_link:, is an error saying the feature is off (translation.feature_off). jadpo translations builtin lists only the switched-on features' messages, with their fields.

Every Jadpo message may use {app_name}, the application's display_name:. An app that needs the name translated rewords the messages. The magic-link email's messages are listed in Built-in emails.

Each of Jadpo's messages has a key, a kind (a message, a failure or an enum), the feature that switches it on, the fields it may write besides {app_name}, a description for translators and the wording Jadpo ships. Code can't call them: translations.jadpo isn't a scope code can call. An email template's {{ locale.direction }} writes the wording of Jadpo's TextDirection enum.

A feature is switched on by what the project has. A message's name is its key in the jadpo: { } group, and a failure's is its code:

Feature On when Its messages
Requests an API serves routes invalid_request, invalid_input, route_not_found, method_not_allowed, request_too_large, idempotency_key_reused, rate_limited, unavailable, internal_fault, deadline_exceeded
Signing in a route needs a caller authentication_required, invalid_credentials, ambiguous_credentials, not_permitted, authentication_unavailable
Signing users in an API accepts a browser session or an API token user_disabled
Signing services in an API accepts a service key service_disabled
Browser sessions an API accepts browser sessions and a route that needs a caller writes csrf_rejected
Locales the app declares locales { } text_direction, the wording of TextDirection
Magic links the app signs people in with magic links the messages in Built-in emails
Magic-link routes the app writes its own magic-link routes (generate_routes: false, Magic links) sign_in_return_invalid
Passkeys the app signs users in with passkeys passkey_challenge_expired, passkey_rejected, passkey_already_registered, passkey_limit_reached, passkey_not_found
External identity tokens an API accepts external identity tokens (Ways to sign in) authentication_misconfigured

The failures of signing in that only some methods can give (user_disabled, service_disabled, csrf_rejected) are on only where an API accepts those methods, by the same rule as the OpenAPI document's responses. An API's file for clients carries the Jadpo failures of its own features, and the application needs those of all its APIs together.

The wording for an idempotency key whose first request is still running (conflict, 409) is your app's: conflict is a code an app declares too, so Jadpo doesn't word it.

The twelve magic-link messages are on once magic_link: is declared (Built-in emails). The messages of privacy requests (Data requests) join the catalogue with that feature.

TextDirection's wording, ltr and rtl, is the same HTML values in every language, so Jadpo ships it in every locale and no app locale has to word it. An email template's {{ locale.direction }} writes it.

A library package's text

A library package, one that adds code to your app rather than calling an outside service (Kinds of package), carries translations as a domain does: a translations/ folder with its message types, its failures' wording and one <locale>.jadpo per language it supports. Your app calls a package's message as Postcodes.translations.<message>(...). A provider package shows users no text, because its failures are always mapped to the app's own, so it carries none.

The root wording file

The root translations/<locale>.jadpo holds your app's wording for text it didn't write, grouped by whose text it is: jadpo: { } for Jadpo's own, and the package's name for a package's. This is the one place a language file has groups, and each group is checked against its owner's message and failure types. A member outside any group is translation.wording_shape, and a group that names no owner is translation.group_unknown.

The root folder holds language files and nothing else (layout.wrong_folder for any other declaration), and a root file with no locales { } in the project is translation.locales_required.

A wording of a Jadpo message is checked as any wording is: it may write only {app_name} and the message's own fields. text_direction, Jadpo's wording of its TextDirection enum, has one wording for left_to_right and one for right_to_left.

Here the booking app above rewords what Jadpo ships in English and gives text_direction its wording:

example
translations {    jadpo: {        rate_limited: "Steady on: {app_name} is busy. Try again in a minute."        text_direction: {            left_to_right: "ltr"            right_to_left: "rtl"        }    }}

Markdown for agents: /docs/failures-for-client-apps.md.Built from Jadpo main at a707efb.