SDK

Formatting

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/format

The 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'
KindOptions
booleantrueLabel, falseLabel replace the locale's words
enumoptions (the declared { value, label } list), unknownOption
multiselectthe enum options, plus listType ('conjunction', 'disjunction', 'unit'; default 'conjunction') and listStyle ('long', 'short', 'narrow'; default 'long')
ratingmax (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 no max, 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.

On this page