Formats
Messagevisor locale formats are named preset collections for Intl-style formatting. They give Product Managers, translators, and engineers a shared vocabulary for number, currency, date, time, relative time, and date-time-range output.
Messagevisor intentionally supports a documented subset of the JavaScript Intl APIs:
formats.numberusesIntl.NumberFormatformats.dateusesIntl.DateTimeFormatformats.timeusesIntl.DateTimeFormatformats.dateTimeRangeusesIntl.DateTimeFormat.formatRangeformats.relativeusesIntl.RelativeTimeFormat
The rendered examples on this page were verified with the JavaScript runtime using en-US. Exact punctuation, spaces, calendar names, compact labels, unit labels, and time zone labels can vary slightly across runtimes and ICU data versions, but the configuration patterns are the same.
Cross-SDK consistency#
Messagevisor treats JavaScript Intl as the reference for the portable formatter contract. Java and Swift SDKs map the same format presets to ICU4J and Foundation APIs rather than rewriting strings by hand.
Formatter behavior falls into three profiles:
| Profile | Meaning |
|---|---|
| Portable | Expected to match across JavaScript, Java, and Swift for supported options. |
| Runtime-native | Valid platform output may differ because CLDR, ICU, or Foundation chooses different localized text. Shared tests use expectedByRuntime for these cases. |
| Unsupported or approximated | The SDK emits unsupported_formatter and continues with the closest platform behavior. |
SDKs must not add locale-specific or time-zone-specific string hacks to force a fixture to pass. If a platform formats Bengali digits, Dutch unit names, or a time-zone label differently, Messagevisor records that as an explicit runtime expectation unless the difference can be solved by passing better standard options to the platform formatter.
Where formats live#
Formats are authored in locale files:
description: English baseformats: number: money: style: currency currency: USD currencyDisplay: symbol date: long: year: numeric month: long day: numeric time: short: hour: numeric minute: 2-digitTargets can override locale formats per locale. See Targets.
Locales can inherit formats from other locales with inheritFormatsFrom. See Inheritance for the full workflow and prune --formats cleanup.
Quick selection guide#
| Need | Start with |
|---|---|
| Plain counts or scores | formats.number with default decimal style |
| Money where the currency is fixed by locale | style: currency and currency: USD |
| Money where the app decides the currency | style: currency without currency, then pass currency at runtime |
| Percentages | style: percent and pass fractional values such as 0.125 |
| Measurements | style: unit, unit, and unitDisplay |
| Short product UI dates | dateStyle: short or explicit year / month / day fields |
| Marketing or notification dates | dateStyle: long / full, or weekday + long month |
| Event times across time zones | time fields with timeZone and timeZoneName |
| "yesterday" / "in 3 days" | formats.relative with numeric: auto |
| Start/end event windows | formats.dateTimeRange |
Allowed combinations#
These rules mirror the supported Intl subset and help avoid presets that JavaScript runtimes reject:
- Currency fields (
currency,currencyDisplay,currencySign) requirestyle: currency. - Unit fields (
unit,unitDisplay) requirestyle: unit. compactDisplayonly applies whennotation: compact.dateStyleandtimeStylecannot be mixed with granular fields such asyear,month,day,hour,minute, ortimeZoneNamein the same date/time preset.dateStylebelongs to date and date-time-range presets.timeStylebelongs to time and date-time-range presets.currencyandtimeZonecan be supplied per call in the SDK. Per-call values win over preset values, instance defaults, and SDK fallbacks.
Number formats#
Number presets are passed to Intl.NumberFormat.
formats: number: decimal: maximumFractionDigits: 2 fixed2: minimumFractionDigits: 2 maximumFractionDigits: 2 compactShort: notation: compact compactDisplay: short signAlways: signDisplay: always maximumFractionDigits: 0Number options#
| Field | Supported values |
|---|---|
style | decimal, currency, percent, unit |
useGrouping | true, false, min2, auto, always |
minimumIntegerDigits | number |
minimumFractionDigits | number |
maximumFractionDigits | number |
minimumSignificantDigits | number |
maximumSignificantDigits | number |
notation | standard, scientific, engineering, compact |
compactDisplay | short, long |
signDisplay | auto, never, always, exceptZero, negative |
roundingPriority | auto, morePrecision, lessPrecision |
roundingIncrement | 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 |
roundingMode | ceil, floor, expand, trunc, halfCeil, halfFloor, halfExpand, halfTrunc, halfEven |
trailingZeroDisplay | auto, stripIfInteger |
numberingSystem | Unicode numbering system such as latn or arab |
Number outputs#
Input values used below include 1234.5, -1234.5, 0, 0.125, 1234567, and 1.234.
| Goal | Preset options | Example output |
|---|---|---|
| Decimal with max 2 fraction digits | { maximumFractionDigits: 2 } | 1,234.57 |
| Fixed 2 fraction digits | { style: "decimal", minimumFractionDigits: 2, maximumFractionDigits: 2 } | 1,234.50 |
| 3 significant digits | { minimumSignificantDigits: 3, maximumSignificantDigits: 3 } | 12,300 |
| Grouping always | { useGrouping: "always" } | 1,234.5 |
| Grouping only when at least 2 groups | { useGrouping: "min2" } | 1234.5 |
| Compact short | { notation: "compact", compactDisplay: "short" } | 1.2M |
| Compact long | { notation: "compact", compactDisplay: "long" } | 1.2 million |
| Scientific notation | { notation: "scientific" } | 1.235E6 |
| Engineering notation | { notation: "engineering" } | 1.235E6 |
| Always show sign | { signDisplay: "always", maximumFractionDigits: 0 } | +1,235 |
| Show sign for negative only | { signDisplay: "negative", maximumFractionDigits: 0 } | -1,235 |
| Round to nickel | { minimumFractionDigits: 2, maximumFractionDigits: 2, roundingIncrement: 5, roundingMode: "halfExpand" } | 1.25 |
| Round upward | { maximumFractionDigits: 0, roundingMode: "ceil" } | 2 |
| Round downward | { maximumFractionDigits: 0, roundingMode: "floor" } | 1 |
| Prefer more precision | { minimumFractionDigits: 2, maximumFractionDigits: 2, minimumSignificantDigits: 3, maximumSignificantDigits: 4, roundingPriority: "morePrecision" } | 1.234 |
| Prefer less precision | { minimumFractionDigits: 2, maximumFractionDigits: 2, minimumSignificantDigits: 3, maximumSignificantDigits: 4, roundingPriority: "lessPrecision" } | 1.23 |
Strip .00 when integer | { minimumFractionDigits: 2, maximumFractionDigits: 2, trailingZeroDisplay: "stripIfInteger" } | 12 |
| Arabic numbering system | { numberingSystem: "arab" } | ٢٬٠٢٥ |
Currency, percent, and unit formats#
Currency, percent, and unit presets are still formats.number presets, but they use a non-default style.
formats: number: money: style: currency currency: USD currencyDisplay: symbol moneyAccounting: style: currency currency: USD currencySign: accounting percent: style: percent maximumFractionDigits: 1 distanceKm: style: unit unit: kilometer unitDisplay: shortCurrency and unit options#
| Field | Supported values |
|---|---|
currency | ISO 4217 code such as USD, EUR, or JPY |
currencyDisplay | code, symbol, narrowSymbol, name |
currencySign | standard, accounting |
unit | ECMA-402 unit identifier such as kilometer, meter, kilogram, or percent |
unitDisplay | short, narrow, long |
Runtime currency resolution is:
- per-call
options.currency - preset
currency - SDK instance
currency - SDK fallback
USD
Currency, percent, and unit outputs#
| Goal | Preset options | Example output |
|---|---|---|
| Currency symbol | { style: "currency", currency: "USD", currencyDisplay: "symbol" } | $1,234.50 |
| Currency code | { style: "currency", currency: "USD", currencyDisplay: "code" } | USD 1,234.50 |
| Narrow currency symbol | { style: "currency", currency: "USD", currencyDisplay: "narrowSymbol" } | $1,234.50 |
| Currency name | { style: "currency", currency: "USD", currencyDisplay: "name" } | 1,234.50 US dollars |
| Accounting negative | { style: "currency", currency: "USD", currencySign: "accounting" } | ($1,234.50) |
| Zero-decimal currency | { style: "currency", currency: "JPY" } | ¥1,235 |
| Percent from fraction | { style: "percent", maximumFractionDigits: 1 } | 12.5% |
| Unit short | { style: "unit", unit: "kilometer", unitDisplay: "short" } | 42 km |
| Unit narrow | { style: "unit", unit: "kilometer", unitDisplay: "narrow" } | 42km |
| Unit long | { style: "unit", unit: "kilometer", unitDisplay: "long" } | 42 kilometers |
Intl.NumberFormat expects fractional values for percent style. For example, 0.125 renders as 12.5%, not 0.125%.
Date and time formats#
Date, time, and date-time-range presets are passed to Intl.DateTimeFormat.
Use style presets when you want locale-appropriate output:
formats: date: fullStyle: dateStyle: full shortStyle: dateStyle: short time: shortStyle: timeStyle: short timeZone: UTCUse field presets when you need exact components:
formats: date: weekdayLong: weekday: long year: numeric month: long day: numeric timeZone: UTC buddhistCalendar: year: numeric month: long day: numeric calendar: buddhist timeZone: UTC time: fractional: hour: numeric minute: 2-digit second: 2-digit fractionalSecondDigits: 3 timeZone: UTCDate and time options#
| Field | Supported values |
|---|---|
dateStyle | full, long, medium, short |
timeStyle | full, long, medium, short |
formatMatcher | basic, best fit |
timeZone | IANA time zone such as UTC, Europe/Amsterdam, or America/New_York |
calendar | Unicode calendar such as gregory, buddhist, or japanese |
numberingSystem | Unicode numbering system such as latn or arab |
weekday | long, short, narrow |
era | long, short, narrow |
year | numeric, 2-digit |
month | numeric, 2-digit, long, short, narrow |
day | numeric, 2-digit |
dayPeriod | long, short, narrow |
hour | numeric, 2-digit |
minute | numeric, 2-digit |
second | numeric, 2-digit |
fractionalSecondDigits | 1, 2, 3 |
timeZoneName | long, short, shortOffset, longOffset, shortGeneric, longGeneric |
hour12 | true, false |
hourCycle | h11, h12, h23, h24 |
Runtime time zone resolution is:
- per-call
options.timeZone - preset
timeZone - SDK instance
timeZone - SDK fallback
UTC
Date outputs#
The date examples use 2026-05-12T08:30:45.678Z.
| Goal | Preset options | Example output |
|---|---|---|
| Full date style | { dateStyle: "full", timeZone: "UTC" } | Tuesday, May 12, 2026 |
| Long date style | { dateStyle: "long", timeZone: "UTC" } | May 12, 2026 |
| Medium date style | { dateStyle: "medium", timeZone: "UTC" } | May 12, 2026 |
| Short date style | { dateStyle: "short", timeZone: "UTC" } | 5/12/26 |
| Weekday + long month | { weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: "UTC" } | Tuesday, May 12, 2026 |
| Two-digit fields | { year: "2-digit", month: "2-digit", day: "2-digit", timeZone: "UTC" } | 05/12/26 |
| Era | { era: "short", year: "numeric", month: "short", day: "numeric", timeZone: "UTC" } | May 12, 2026 AD |
| Buddhist calendar | { year: "numeric", month: "long", day: "numeric", calendar: "buddhist", timeZone: "UTC" } | May 12, 2569 BE |
| Arabic numbering system | { year: "numeric", month: "2-digit", day: "2-digit", numberingSystem: "arab", timeZone: "UTC" } | ٠٥/١٢/٢٠٢٦ |
Time outputs#
The time examples use 2026-05-12T08:30:45.678Z unless the row mentions another value.
| Goal | Preset options | Example output |
|---|---|---|
| Full time style | { timeStyle: "full", timeZone: "UTC" } | 8:30:45 AM Coordinated Universal Time |
| Short time style | { timeStyle: "short", timeZone: "UTC" } | 8:30 AM |
| Hour + minute | { hour: "numeric", minute: "2-digit", timeZone: "UTC" } | 8:30 AM |
| With seconds | { hour: "numeric", minute: "2-digit", second: "2-digit", timeZone: "UTC" } | 8:30:45 AM |
| Fractional seconds | { hour: "numeric", minute: "2-digit", second: "2-digit", fractionalSecondDigits: 3, timeZone: "UTC" } | 8:30:45.678 AM |
| Long day period | { hour: "numeric", dayPeriod: "long", hour12: true, timeZone: "UTC" } | 12 noon |
| 24-hour cycle | { hour: "2-digit", minute: "2-digit", hourCycle: "h23", timeZone: "UTC" } | 08:30 |
| Short time zone name | { hour: "numeric", minute: "2-digit", timeZone: "UTC", timeZoneName: "short" } | 8:30 AM UTC |
| Long time zone name | { hour: "numeric", minute: "2-digit", timeZone: "UTC", timeZoneName: "long" } | 8:30 AM Coordinated Universal Time |
| Short offset time zone | { hour: "numeric", minute: "2-digit", timeZone: "UTC", timeZoneName: "shortOffset" } | 8:30 AM GMT |
| Long offset time zone | { hour: "numeric", minute: "2-digit", timeZone: "UTC", timeZoneName: "longOffset" } | 8:30 AM GMT |
| Short generic time zone | { hour: "numeric", minute: "2-digit", timeZone: "America/New_York", timeZoneName: "shortGeneric" } | 4:30 AM ET |
| Long generic time zone | { hour: "numeric", minute: "2-digit", timeZone: "America/New_York", timeZoneName: "longGeneric" } | 4:30 AM Eastern Time |
Date-time range formats#
Date-time range presets use the same date and time options, then render with formatRange.
formats: dateTimeRange: short: year: 2-digit month: numeric day: numeric hour: numeric minute: 2-digit timeZone: UTC event: year: numeric month: short day: numeric hour: numeric minute: 2-digit timeZone: UTC fullStyle: dateStyle: full timeStyle: short timeZone: UTCThe range examples use 2026-05-12T08:00:00Z to 2026-05-12T09:30:00Z.
| Goal | Preset options | Example output |
|---|---|---|
| Short fields, same day | { year: "2-digit", month: "numeric", day: "numeric", hour: "numeric", minute: "2-digit", timeZone: "UTC" } | 5/12/26, 8:00 – 9:30 AM |
| Event fields | { year: "numeric", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZone: "UTC" } | May 12, 2026, 8:00 – 9:30 AM |
| Full date + short time | { dateStyle: "full", timeStyle: "short", timeZone: "UTC" } | Tuesday, May 12, 2026, 8:00 – 9:30 AM |
Relative time formats#
Relative time presets are passed to Intl.RelativeTimeFormat.
formats: relative: auto: numeric: auto style: long short: numeric: auto style: short narrow: numeric: always style: narrow| Field | Supported values |
|---|---|
numeric | always, auto |
style | long, short, narrow |
Relative formatter calls also include a unit at runtime, such as second, minute, hour, day, week, month, quarter, or year.
| Goal | Preset options | Example output |
|---|---|---|
Auto long, -1 day | { numeric: "auto", style: "long" } | yesterday |
Always long, -1 day | { numeric: "always", style: "long" } | 1 day ago |
Auto short, 0 day | { numeric: "auto", style: "short" } | today |
Narrow always, 3 day | { numeric: "always", style: "narrow" } | in 3d |
Using named formats in messages#
When using the ICU module, messages can reference named presets by name:
translations: checkout.total: en-US: "Total: {amount, number, money}" checkout.due: en-US: "Due: {date, date, long}" checkout.time: en-US: "Time: {time, time, short}"The part after the format type, such as money, long, or short, must match a key in the corresponding locale format family.
Applications can also call SDK format helpers directly:
m.formatNumber(12, "money", { currency: "EUR" });m.formatDate(startsAt, "weekday", { timeZone: "Europe/Amsterdam" });m.formatTime(startsAt, "short", { timeZone: "America/New_York" });m.formatDateTimeRange(startsAt, endsAt, "event", { timeZone: "UTC" });Format resolution order#
At runtime, format presets for a given locale and target follow this priority chain:
- SDK constructor
defaultFormatsfor the active locale, as runtime defaults - inherited locale formats via
inheritFormatsFrom - locale's own
formats - target-level format overrides for that locale
- per-call overrides passed at evaluation time
This means target-level overrides can give you a different currency or date style for a specific deployment surface without changing the base locale definition.
Constructor defaultFormats are useful as fallback presets before a datafile is loaded or when a datafile omits a preset. Authored datafile formats win for same-named presets, and per-call overrides remain strongest.
Format inheritance example#
formats: number: money: style: currency currency: USD date: long: year: numeric month: long day: numericinheritFormatsFrom: enformats: number: money: currency: GBP # only overrides the currency; other money fields are inheritedThe resolved en-GB formats will have money.currency: GBP but will still carry money.style: currency from en.
Advanced playground#
projects/project-1 includes a format gallery under locales/en.yml and locales/en-US.yml. It is useful when you want to adjust a preset and verify the rendered output through the CLI before copying the pattern into docs or a real project.
Nested or highly specialized format presets are best introduced only when a product surface needs them. Most teams can start with a small shared set for money, percentages, dates, times, relative time, and event ranges.
What formats are not#
Formats are not:
- ICU plural or select rule definitions
- module definitions
- arbitrary custom formatter code
Plural and select behavior in ICU messages comes from locale-specific plural rules built into the ICU library, not from adding extra format families.

