From d9b233a438d85a7b2bf74d643b43698de53afc26 Mon Sep 17 00:00:00 2001 From: dangreen Date: Tue, 8 Sep 2026 19:00:52 +0400 Subject: [PATCH] fix(intl): require explicit options next to a fallback in the Intl formatters The overloads of `number`, `datetime`, `relativetime`, `duration`, `list` and `range` accepted a lone fallback such as `number(42)`, but the runtime reads a lone argument as options, so the fallback was silently dropped, and `duration({ hours: 1 })` even threw once the object reached `Intl.DurationFormat`. A fallback cannot be told apart from options by shape, `duration` takes plain objects for both, so the contract is now spelled out in the types: a fallback always comes with options, `{}` or `false`. The custom `format` overloads of `number` and `datetime` require the function, so they no longer swallow such calls, and `relativetime` requires its options, since formatting without a `unit` throws. --- packages/intl/src/format/datetime.ts | 10 +++++----- packages/intl/src/format/duration.ts | 4 ++-- packages/intl/src/format/list.ts | 4 ++-- packages/intl/src/format/number.ts | 10 +++++----- packages/intl/src/format/range.ts | 10 +++++----- packages/intl/src/format/relativetime.ts | 4 ++-- website/src/content/docs/intl/api.mdx | 2 ++ 7 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/intl/src/format/datetime.ts b/packages/intl/src/format/datetime.ts index cfb2d236..bd8d9c1c 100644 --- a/packages/intl/src/format/datetime.ts +++ b/packages/intl/src/format/datetime.ts @@ -43,12 +43,12 @@ export function datetime( /** * Creates a locale-aware date/time formatter with a fallback value. * @param fallback - Value used when the input is `undefined` or `null`. - * @param options - `Intl.DateTimeFormat` options. + * @param options - `Intl.DateTimeFormat` options, or `{}` for the defaults. * @returns Formatter that returns a formatted string or `undefined`. */ export function datetime( fallback: Date | number | string, - options?: Intl.DateTimeFormatOptions + options: Intl.DateTimeFormatOptions ): Format /** @@ -59,9 +59,9 @@ export function datetime( * @returns Formatter that returns a formatted string or `undefined`. */ export function datetime( - optionsOrFallback?: Intl.DateTimeFormatOptions | false | I, - maybeOptions?: Intl.DateTimeFormatOptions | false, - format?: IntlFormatFn + optionsOrFallback: Intl.DateTimeFormatOptions | false | I | undefined, + maybeOptions: Intl.DateTimeFormatOptions | false | undefined, + format: IntlFormatFn ): Format /* @__NO_SIDE_EFFECTS__ */ diff --git a/packages/intl/src/format/duration.ts b/packages/intl/src/format/duration.ts index 450ebb42..2e6ad7bf 100644 --- a/packages/intl/src/format/duration.ts +++ b/packages/intl/src/format/duration.ts @@ -38,12 +38,12 @@ export function duration( /** * Creates a locale-aware duration formatter with a fallback value. * @param fallback - Value used when the input is `undefined` or `null`. - * @param options - `Intl.DurationFormat` options. + * @param options - `Intl.DurationFormat` options, or `{}` for the defaults. * @returns Formatter that returns a formatted string or `undefined`. */ export function duration( fallback: Duration, - options?: Intl.DurationFormatOptions + options: Intl.DurationFormatOptions ): Format /* @__NO_SIDE_EFFECTS__ */ diff --git a/packages/intl/src/format/list.ts b/packages/intl/src/format/list.ts index d9f0d16e..c9bb2afa 100644 --- a/packages/intl/src/format/list.ts +++ b/packages/intl/src/format/list.ts @@ -36,12 +36,12 @@ export function list( /** * Creates a locale-aware list formatter with a fallback value. * @param fallback - Value used when the input is `undefined` or `null`. - * @param options - `Intl.ListFormat` options. + * @param options - `Intl.ListFormat` options, or `{}` for the defaults. * @returns Formatter that returns a formatted string or `undefined`. */ export function list( fallback: Iterable, - options?: Intl.ListFormatOptions + options: Intl.ListFormatOptions ): Format | undefined, string | undefined> /* @__NO_SIDE_EFFECTS__ */ diff --git a/packages/intl/src/format/number.ts b/packages/intl/src/format/number.ts index e1a3b736..838460ed 100644 --- a/packages/intl/src/format/number.ts +++ b/packages/intl/src/format/number.ts @@ -37,12 +37,12 @@ export function number( /** * Creates a locale-aware number formatter with a fallback value. * @param fallback - Value used when the input is `undefined` or `null`. - * @param options - `Intl.NumberFormat` options. + * @param options - `Intl.NumberFormat` options, or `{}` for the defaults. * @returns Formatter that returns a formatted string or `undefined`. */ export function number( fallback: number | bigint | string, - options?: Intl.NumberFormatOptions + options: Intl.NumberFormatOptions ): Format /** @@ -53,9 +53,9 @@ export function number( * @returns Formatter that returns a formatted string or `undefined`. */ export function number( - optionsOrFallback?: Intl.NumberFormatOptions | false | number | bigint | string, - maybeOptions?: Intl.NumberFormatOptions | false, - format?: IntlFormatFn + optionsOrFallback: Intl.NumberFormatOptions | false | number | bigint | string | undefined, + maybeOptions: Intl.NumberFormatOptions | false | undefined, + format: IntlFormatFn ): Format /* @__NO_SIDE_EFFECTS__ */ diff --git a/packages/intl/src/format/range.ts b/packages/intl/src/format/range.ts index 3abc1a3d..f2955185 100644 --- a/packages/intl/src/format/range.ts +++ b/packages/intl/src/format/range.ts @@ -7,9 +7,9 @@ import type { type RangeInput = readonly [from: I, to: I] type RangeFrom, I, O, R> = ( - optionsOrFallback?: O | false | I, - maybeOptions?: O | false, - format?: IntlFormatFn + optionsOrFallback: O | false | I | undefined, + maybeOptions: O | false | undefined, + format: IntlFormatFn ) => Format function rangeFormat( @@ -58,13 +58,13 @@ export function range, I, O, R>( * Creates a locale-aware range formatter with a fallback range. * @param format - Formatter factory that supports a custom range formatting function. * @param fallback - Range used when the input is `undefined` or `null`. - * @param options - Formatter options. + * @param options - Formatter options, or `{}` for the defaults. * @returns Formatter that returns a formatted range string or `undefined`. */ export function range, I, O, R>( format: RangeFrom, fallback: RangeInput, - options?: O + options: O ): Format | undefined, string | undefined> /* @__NO_SIDE_EFFECTS__ */ diff --git a/packages/intl/src/format/relativetime.ts b/packages/intl/src/format/relativetime.ts index a05f554a..bf4d8c57 100644 --- a/packages/intl/src/format/relativetime.ts +++ b/packages/intl/src/format/relativetime.ts @@ -28,7 +28,7 @@ export function relativetime( * @returns Formatter that returns a formatted string or `undefined`. */ export function relativetime( - options?: RelativeTimeOptions + options: RelativeTimeOptions ): Format /** @@ -50,7 +50,7 @@ export function relativetime( */ export function relativetime( fallback: number, - options?: RelativeTimeOptions + options: RelativeTimeOptions ): Format /* @__NO_SIDE_EFFECTS__ */ diff --git a/website/src/content/docs/intl/api.mdx b/website/src/content/docs/intl/api.mdx index 464c2689..0e818b8e 100644 --- a/website/src/content/docs/intl/api.mdx +++ b/website/src/content/docs/intl/api.mdx @@ -152,6 +152,8 @@ This is useful for dates, numbers, and other UI values that come from app state. Intl formats can be used inside `params(...)` for translated templates, or wrapped with `format(...)` when the value comes from runtime state, API data, or a database. +A fallback is always passed together with options, for example `number(0, {})` or `datetime(fallback, false)`: a lone argument is always read as options. + ### `number(fallback?, options?)` Formats numbers with `Intl.NumberFormat`.