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:
| Feature | Example 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.10 | en, 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/Y | en |
| Date order: D.M.Y | de, fr, es, ru |
| Date order: Y/M/D | ja, zh, ko, sv |
| 24-hour time | de, 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.