Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Locale-Aware Formatting

Perry provides format wrapper functions that automatically format values according to the current locale. Import them from perry/i18n:

// All format wrappers come from perry/i18n.
// (The same `Currency`, `Percent`, … you'd pass into a Text(...) param object.)
const price = Currency(23.10)
const discount = Percent(0.15)
const population = FormatNumber(1234567.89)
const due = ShortDate(Date.now())
const event = LongDate(Date.now())
const at = FormatTime(Date.now())
const code = Raw(12345)

// Compose them with t() the same way you'd compose with Text(...):
console.log(t("Total: {price}", { price }))
console.log(t("Discount: {rate}", { rate: discount }))
console.log(t("Population: {n}", { n: population }))
console.log(t("Due: {d}", { d: due }))
console.log(t("Event: {d}", { d: event }))
console.log(t("At: {t}", { t: at }))
console.log(t("Code: {amount}", { amount: code }))

Format Wrappers

Currency

Formats a number as currency with the locale’s symbol, decimal separator, and symbol placement:

// Currency: locale-appropriate symbol, separator, and placement.
//   en: "$23.10"   de: "23,10 €"   ja: "¥23.10"
const cur = Currency(23.10)
console.log(t("Total: {price}", { price: cur }))
en: "Total: $23.10"
de: "Total: 23,10 €"
fr: "Total: 23,10 €"
ja: "Total: ¥23.10"

Percent

Formats a decimal as a percentage (value is multiplied by 100):

// Percent: input is a decimal (0.15 → 15 %). en omits the space; de/fr add it.
const rate = Percent(0.15)
console.log(t("Discount: {rate}", { rate }))
en: "Discount: 15%"
de: "Discount: 15 %"
fr: "Discount: 15 %"

FormatNumber

Formats a number with locale-appropriate grouping and decimal separators:

// FormatNumber: locale-appropriate grouping + decimal separators.
//   en: "1,234,567.89"   de: "1.234.567,89"   fr: "1 234 567,89"
const n = FormatNumber(1234567.89)
console.log(t("Population: {n}", { n }))
en: "Population: 1,234,567.89"
de: "Population: 1.234.567,89"
fr: "Population: 1 234 567,89"

ShortDate / LongDate / FormatDate

Formats a timestamp (milliseconds since epoch) as a date:

// ShortDate / LongDate take a millisecond timestamp.
const now = Date.now()
const short = ShortDate(now)   // en: "3/22/2026"   de: "22.03.2026"
const long = LongDate(now)     // en: "March 22, 2026"   de: "22. März 2026"
console.log(t("Due: {d}", { d: short }))
console.log(t("Event: {d}", { d: long }))
ShortDate
  en: "Due: 3/22/2026"
  de: "Due: 22.03.2026"
  ja: "Due: 2026/03/22"

LongDate
  en: "Event: March 22, 2026"
  de: "Event: 22. März 2026"
  fr: "Event: 22 mars 2026"

FormatTime

Formats a timestamp as time (12h vs 24h based on locale):

// FormatTime: 12h vs 24h based on the active locale.
const ts = Date.now()
const formatted = FormatTime(ts)
console.log(t("At: {t}", { t: formatted }))
en: "At: 3:45 PM"
de: "At: 15:45"
fr: "At: 15:45"

Raw

Pass-through — prevents any automatic formatting. Use when a parameter name might trigger auto-formatting but you want the raw value:

// Raw is a pass-through — prevents auto-formatting when the param name
// might otherwise trigger it.
const orderCode = Raw(12345)
console.log(t("Code: {amount}", { amount: orderCode }))

All locales: "Code: 12345" (no currency formatting despite the name).

Locale-Specific Formatting Rules

Perry includes hand-rolled formatting rules for 25+ locales:

FeatureExample Locales
Decimal: . / Thousands: ,en, ja, zh, ko
Decimal: , / Thousands: .de, nl, tr, es, it, pt
Decimal: , / Thousands: (narrow space)fr
Decimal: , / Thousands: (non-breaking space)ru, uk, pl, sv, da, no, fi
Currency before number: $23.10en, ja, zh, ko
Currency after number: 23,10 €de, fr, es, it, ru
Percent with space: 42 %de, fr, es, ru
Percent without space: 42%en, ja, zh
Date order: M/D/Yen
Date order: D.M.Yde, fr, es, ru
Date order: Y/M/Dja, zh, ko, sv
24-hour timede, fr, es, ja, zh, ru (most)
12-hour time (AM/PM)en

Currency Configuration

Configure default currency codes per locale in perry.toml:

[i18n]
locales = ["en", "de", "fr"]
default_locale = "en"

[i18n.currencies]
en = "USD"
de = "EUR"
fr = "EUR"

When Currency(value) is called, the locale’s configured currency code determines the symbol and formatting rules.