skip to content

How would you decide whether validation error bodies carry server-rendered messages, stable machine codes with parameters, or both?

level: principalimportance: should knowfreq 44%

answer

  1. who owns the wording
  2. who ships to change it
  3. prose cannot be branched on
  4. parameters let clients render
  5. a code is a frozen interface

basics

~20 s

Decide by who owns the wording and who ships to change it. Codes with parameters suit clients writing their own copy or branching on failures; rendered text suits consumers without translation. Most publish both, code as contract.

solid answer

~40 s

Each declared rule yields a message key and parameters such as `min` or the length actually seen, so the converter can publish rendered sentences, stable codes with parameters, or both. The axes are ownership and change cost: rendered text means a server release to reword and a server bundle per language, while codes mean every client writes and translates its own copy. Anything a client must **branch** on needs a code, because prose is reworded by design. The position that survives review is usually both — codes and parameters always, a default-language sentence documented as a fallback — with the code vocabulary keyed on the rule rather than the field, since a published code is an interface you cannot rename.

go deeper

for a junior

Know that a violation carries a message key and parameters rather than a fixed sentence, and that the sentence a caller sees is produced later from those parts.

for a middle

Explain the mechanics of each option and what it costs: who rewords, who translates, and why programmatic reactions need a code rather than text.

for a senior

Argue the hybrid and defend it: code plus parameters always, sentence as a documented fallback, dashboards grouped by code so wording changes do not fragment the data.

for a principal

Own the vocabulary and the sequence. Freeze codes before publishing prose, decide which consumers justify a server-side translation pipeline, and measure that need rather than assuming it.

## The three shapes on the table A declared rule typically produces a message key plus parameters — `min`, `max`, the length actually seen, the pattern that was expected. A resolved locale for the request is available from the framework. From those ingredients the converter can publish: 1. **Rendered text only** — one sentence per violation, already in the caller's language. 2. **Code and parameters only** — a stable identifier such as `size.too_short` with `min=8`, and the client writes the wording. 3. **Both** — code, parameters, and a message rendered from the server's bundles. The decision is not aesthetic. It fixes **who owns the wording**, **who must ship to change it**, and **what becomes a contract you cannot rename**. ## What each option costs | | Rendered text only | Code and parameters only | Both | |---|---|---|---| | Wording owned by | Server translators | Each client | Server, with client override | | Changing a sentence | Server release | Client release | Either | | New language | Server bundle only | Every client separately | Server bundle only | | Client work to display | None | Must map every code | None, mapping optional | | What is frozen | Little — text is free to change | Every code, forever | Codes, and the text is advisory | | Branching on the failure | Impossible without string matching | Straightforward | Straightforward | The third row is the one that decides most real cases. If the API has a single first-party client shipped by the same team, client-side wording is cheap and gives the best copy, because the client knows the form's own vocabulary. As soon as there are integrators you cannot deploy, a code-only body means every one of them invents wording — badly, in one language, and out of date. ## The axes to reason on - **Who the clients are.** One internal UI, several internal ones, or an open API with partners you will never coordinate with. - **Where translation already happens.** If the organisation runs a translation pipeline for the product, it probably lives on the client side; duplicating it server-side to render validation sentences is real cost for a small share of the copy. - **Whether callers need to branch.** Anything a client must react to programmatically — offer a suggestion, prefill a field, retry — needs a code. Prose is a poor branching key: it is translated, reworded and rephrased by design. - **Stability.** A published code is an interface. Choose a vocabulary keyed on the **rule and the shape of the failure**, not on the field or the screen, so renaming a field does not rename a code. - **Parameters.** Any client rendering its own wording needs the numbers: the bound, the actual value's length, the permitted set. Publishing text but withholding the parameters quietly forces everyone onto your sentence. - **Fallbacks.** A missing key in a locale bundle must degrade to a default language, never to a blank field or to the raw key. ## The position that survives review For most services: **publish the code and parameters always, and the rendered sentence when the server can render it honestly** — documenting that the sentence is a fallback and the code is the contract. The client that wants its own copy ignores the sentence; the integration script and the partner without translators display it; the dashboard groups by code rather than by sentence, which is the only grouping that stays stable across wording changes. Two caveats belong in the answer. First, server-rendered text needs real bundles with plural and grammatical-agreement handling; a naive template concatenating a number into a sentence produces text that is wrong in many languages, and shipping that is worse than shipping the code alone. Second, sentences written for a caller must not restate the rejected input — an echo of user-supplied text in a response is both a leak and a rendering hazard. ## How to sequence it in a real organisation 1. Freeze the **code vocabulary** first and document it; it is the part you cannot take back. 2. Emit codes, parameters and a default-language sentence, marked as non-authoritative. 3. Let the first-party client override wording for the screens whose copy matters. 4. Add server bundles for languages only when there is a consumer that cannot translate for itself, and measure that before paying for a translation pipeline. The failure mode to avoid is the reverse order: shipping sentences first, letting clients pattern-match on them, and discovering that the prose has silently become the contract.

  • What makes a good code vocabulary?
    Key it on the rule and the shape of the failure, not on the field or the screen, so renaming a field does not rename a code. Keep it small, namespaced and documented, and treat additions as additive: clients should be able to fall back on an unknown code without breaking.
  • Why publish parameters even when you already render the sentence?
    Because withholding them forces every consumer onto your wording. The bound, the observed length or the permitted set is what a client needs to phrase its own message, prefill a field or offer a correction, and it costs nothing to include alongside the text.
  • What is the risk of shipping rendered sentences first?
    Clients start pattern-matching on the prose, and the wording silently becomes the contract. Rewording then breaks consumers that never appeared in any integration document, which is why the code vocabulary should be frozen and published before any sentence is.
  • When is server-rendered text a mistake even for multilingual consumers?
    When there is no real bundle behind it. Concatenating a number into a template produces text that breaks plural and agreement rules in many languages, so a naive rendering ships confidently wrong sentences where a code and parameters would have let each client phrase it correctly.

saying these in an interview costs you the question

  • Treats the sentence as the contract and lets clients match on prose
  • Publishes codes without the parameters clients need to phrase messages
  • Names codes after fields or screens, so renames break the contract
  • Assumes template concatenation is adequate translation for every language
  • Renders the rejected input into the message sent back to the caller
  • Lets a missing bundle entry surface as a blank message or a raw key