FormattingLast updated on
Last updated on
Locale-aware presentation of artifact values
@paradoc/format presents structured Paradoc values as localized text. Stored values remain unchanged. The same formatter can be passed to text, PDF, DOCX, and React renderers.
Installation
npm install @paradoc/formatThe SDK also exports the formatter API:
import { createFormatter } from '@paradoc/sdk'Create a formatter
Use a precise locale. The initial documented locales are en-US, en-GB, de-DE, fr-FR, and ar-SA.
import { createFormatter } from '@paradoc/format'
const formatter = createFormatter({ locale: 'de-DE' })
formatter.formatNumber(1234567.89) // '1.234.567,89'
formatter.formatMoney({ amount: 1500.5, currency: 'EUR' }) // '1.500,50 €'
formatter.formatPercentage(8.25) // '8,25 %'
formatter.formatDate('2026-09-07') // '7. Sept. 2026'Money always carries its currency. Percentages use percentage points, so 8.25 formats as 8.25%. Address country is independent of document locale.
Selection values
Booleans, single choices, multiple choices, and ratings go through the same formatter. Each call takes the options the field declares.
import { createFormatter } from '@paradoc/format'
const formatter = createFormatter({ locale: 'en-US' })
const options = [
{ value: 'plumbing', label: 'Plumbing' },
{ value: 'roofing', label: 'Roofing' },
]
formatter.formatBoolean(true) // 'Yes'
formatter.formatBoolean(true, { trueLabel: 'Agreed' }) // 'Agreed'
formatter.formatEnum('plumbing', { options }) // 'Plumbing'
formatter.formatMultiselect(['plumbing', 'roofing'], { options }) // 'Plumbing and Roofing'
formatter.formatRating(4, { max: 5 }) // '4 of 5'
formatter.formatRating(4, { display: 'value' }) // '4'| Kind | Options |
|---|---|
boolean | trueLabel, falseLabel replace the locale's words |
enum | options (the declared { value, label } list), unknownOption |
multiselect | the enum options, plus listType ('conjunction', 'disjunction', 'unit'; default 'conjunction') and listStyle ('long', 'short', 'narrow'; default 'long') |
rating | max (the top of the scale), display ('scale' or 'value'; default 'scale') |
UnknownOptionPolicy decides what happens to a value no option declares: 'error' (the default) refuses it, and 'value' prints the raw value.
A multiselect joins its labels with the locale's own list conjunction, so ['plumbing', 'roofing'] prints as "Plumbing and Roofing" in en-US and "Plumbing und Roofing" in de-DE. Ratings print with their scale in the locale's words: "4 of 5", "4 von 5".
The standalone functions formatBoolean, formatEnum, formatMultiselect, and formatRating use the default en-US formatter. Each has a safeFormat* pair, and formatValue('rating', 4, { max: 5 }) dispatches by kind.
Two unsupported outcomes have exported codes, so you can key a fallback off them:
MISSING_RATING_SCALE: the rating declares nomax, so it has no scale to print.UNSUPPORTED_LIST_JOIN: the runtime has no list conjunction for the locale.
import { MISSING_RATING_SCALE } from '@paradoc/format'
const result = formatter.safeFormatRating(4)
if (!result.success && result.issues[0]?.code === MISSING_RATING_SCALE) {
// print the plain number instead
}Safe results
Strict methods return text or throw FormatError. Safe methods distinguish missing, incomplete, invalid, unsupported, and unexpected failures:
const result = formatter.safeFormatMoney({ amount: 10 })
// { success: false, status: 'incomplete', issues: [...] }A bad option is invalid with the code invalid_options, like a bad value: a malformed locale, an unknown timezone or calendar, or an Intl option out of range. unsupported means the input is right and the runtime or its resources fall short: a locale the runtime has no data for (unsupported_locale), a missing package message (missing_message), or a country with no address layout.
Compose policy and overrides
Formatters are immutable. compose() and withOverrides() return independent instances.
const amountOnly = formatter.compose({
money: { currencyDisplay: 'none' },
})
amountOnly.formatMoney({ amount: 12000, currency: 'USD' }) // '12,000.00'currencyDisplay: 'none' drops the symbol and keeps the currency's fraction digits, unless you set digits yourself. For a PDF template that pre-prints the symbol, declare it on the layer instead; see Artifact formatting.
An override receives a delegate for the previous implementation:
const compact = formatter.withOverrides({
organization(value, options, context) {
context.delegate(value, options)
return String(value.name)
},
})In-progress documents
By default a value that has not been supplied renders blank. To mark it in a preview, pass a progressive policy to render(). missing sets the text for an absent value (default: an em dash) and incomplete the text for a composite value that is only partly supplied (default: an ellipsis). For a layer {{fields.name}} / {{fields.total}} with only name filled:
const preview = await draft.render({ progressive: {} })
// 'Ada / —'
const marked = await draft.render({ progressive: { missing: '___' } })
// 'Ada / ___'React compositions use the partial option instead; see @paradoc/react.
Use one formatter for an artifact
import { createFormatter, p, createLayerRenderer } from '@paradoc/sdk'
const formatter = createFormatter({ locale: 'fr-FR' })
const output = await p.form(formDefinition).fill(data).render({
renderer: createLayerRenderer({ formatter }),
layer: 'markdown',
})Renderer configuration is explicit. Artifact metadata does not select a locale or formatter profile.