Locale bundle format
The object you pass into the core createIntl (and all seeded helpers) is called a bundle. A bundle describes translations, number formats, and date/time formats for a specific locale.
Top-level shape
Each bundle is keyed by locale code ("en", "hu", "fr", …).
Each locale contains three optional sections:
messages- your translated text, with placeholders or custom formatter functions.numberFormats- named number format options, passed toIntl.NumberFormat.dateFormats- named date/time format options, passed toIntl.DateTimeFormat.currencyFormats- named currency format options, passed toIntl.NumberFormat.
Messages
Plain strings
const bundle = {
en: {
messages: {
hello: "Hello world",
},
},
} as const;Placeholders
Messages can include placeholders in curly braces. At runtime you must provide these placeholders.
const bundle = {
en: {
messages: {
greet: "Hello {name}, you have {count} new messages.",
},
},
} as const;
// usage
t("greet", { name: "John", count: 3 });
// -> "Hello John, you have 3 new messages."- Placeholders are matched by name ({name}).
- Multiple placeholders can be used. (type checker can only infer up to 10 in a single string)
- Ordering can vary between locales.
Function messages
For more control, messages can also be functions. This gives you typed parameters which you will need to pass down to the t function instead of the placeholders.
PARAMETERS
The function should always take in a single object parameter which is properly typed for inference.
const bundle = {
en: {
messages: {
items: (p: { count: number }) =>
p.count === 1 ? "1 item" : `${p.count} items`,
},
},
} as const;
// usage
t("items", { count: 5 }); // -> "5 items"This pattern is useful for pluralization or context-based formatting.
Nesting
For better organization you can nest objects and use the keys via the dot notation.
TYPES
Type inference can only go 10 objects deep.
const bundle = {
en: {
messages: {
hello: "Hello world",
erros: {
forbidden: "You can't do this. Error code: {code}",
},
},
},
} as const;
// usage
t("errors.forbidden", { code: "0x12323" });Number formats
Number formats are named presets for Intl.NumberFormat.
const bundle = {
en: {
numberFormats: {
default: { style: "decimal" },
currency: { style: "currency", currency: "USD" },
percent: { style: "percent", maximumFractionDigits: 2 },
},
},
} as const;
// usage
n("currency", 1234.5); // -> "$1,234.50"
n("percent", 0.125); // -> "12.5%"
n(1000); // -> 1,000 (uses default if not provided)- Keys are arbitrary names (
"currency","percent","compact", etc). - The key named
defaultwill be used if not specified in thenfunction call - Values are standard
Intl.NumberFormatOptions.
Currency formats
Currency formats are named presets for Intl.NumberFormat. They always have style: currency applied to them. The currency you pass into the function will be added to the Intl options object as well.
const bundle = {
en: {
currencyFormats: {
default: { currencyDisplay: "code" },
symbol: { currencyDisplay: "symbol" },
},
},
} as const;
// usage
c("symbol", 1234.5, "USD"); // -> "$1,234.50"
c(1000, "USD"); // -> USD 1,000.00 (uses default if not provided)- Keys are arbitrary names (
"currency","percent","compact", etc). - The key named
defaultwill be used if not specified in thenfunction call - Values are standard
Intl.NumberFormatOptions.
Date/time formats
Date/time formats are named presets for Intl.DateTimeFormat.
const bundle = {
en: {
dateFormats: {
default: { dateStyle: "short", timeStyle: "short" },
short: { dateStyle: "short" },
long: { dateStyle: "full", timeStyle: "short" },
},
},
} as const;
// usage
d("short", new Date()); // -> "11/18/25"
d("long", new Date()); // -> "Tuesday, November 18, 2025 at 10:32 AM"
d(new Date())); // -> uses default if not provided- Keys are arbitrary names (
"short","long","timeOnly", etc). - The key named
defaultwill be used if not specified in thedfunction call - Values are standard
Intl.DateTimeFormatOptions.
Complete example
const bundles = {
en: {
messages: {
hello: "Hello {name}!",
inbox: (p: { count: number }) =>
p.count === 1 ? "1 message" : `${p.count} messages`,
},
numberFormats: {
default: { style: "decimal" },
percent: { style: "percent", maximumFractionDigits: 2 },
},
currencyFormats: {
default: { currencyDisplay: "code" },
symbol: { currencyDisplay: "symbol" },
},
dateFormats: {
short: { dateStyle: "short" },
},
},
hu: {
messages: {
hello: "Szia {name}!",
inbox: (p: { count: number }) =>
p.count === 1 ? "1 üzenet" : `${p.count} üzenet`,
},
numberFormats: {
default: { style: "decimal" },
percent: { style: "percent", maximumFractionDigits: 2 },
},
currencyFormats: {
default: { currencyDisplay: "code" },
symbol: { currencyDisplay: "symbol" },
},
dateFormats: {
short: { dateStyle: "short" },
},
},
} as const;