intl-formatter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/cjs/core/formatter.js +471 -0
- package/dist/cjs/core/types.js +2 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/testing.js +61 -0
- package/dist/esm/core/formatter.d.ts +3 -0
- package/dist/esm/core/formatter.d.ts.map +1 -0
- package/dist/esm/core/formatter.js +468 -0
- package/dist/esm/core/types.d.ts +164 -0
- package/dist/esm/core/types.d.ts.map +1 -0
- package/dist/esm/core/types.js +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/testing.d.ts +4 -0
- package/dist/esm/testing.d.ts.map +1 -0
- package/dist/esm/testing.js +58 -0
- package/package.json +115 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
const DEFAULT_RULES = {
|
|
2
|
+
compactThreshold: 10000,
|
|
3
|
+
minimumFractionDigits: 0,
|
|
4
|
+
maximumFractionDigits: 2,
|
|
5
|
+
currencyDisplay: "narrowSymbol",
|
|
6
|
+
numberFormat: {},
|
|
7
|
+
currencyFormat: {},
|
|
8
|
+
relativeTimeFormat: { style: "long", numeric: "auto" },
|
|
9
|
+
dateFormat: { year: "numeric", month: "short", day: "2-digit" },
|
|
10
|
+
dateTimeFormat: {
|
|
11
|
+
year: "numeric",
|
|
12
|
+
month: "short",
|
|
13
|
+
day: "2-digit",
|
|
14
|
+
hour: "2-digit",
|
|
15
|
+
minute: "2-digit",
|
|
16
|
+
hour12: true,
|
|
17
|
+
},
|
|
18
|
+
fallback: "—",
|
|
19
|
+
numberFallback: "",
|
|
20
|
+
currencyFallback: "",
|
|
21
|
+
percentageFallback: "",
|
|
22
|
+
durationFallback: "",
|
|
23
|
+
dateFallback: "",
|
|
24
|
+
dateTimeFallback: "",
|
|
25
|
+
relativeTimeFallback: "",
|
|
26
|
+
};
|
|
27
|
+
const DEFAULT_DURATION_LABELS = {
|
|
28
|
+
h: "h",
|
|
29
|
+
m: "m",
|
|
30
|
+
s: "s",
|
|
31
|
+
hour: { one: "hour", other: "hours" },
|
|
32
|
+
minute: { one: "minute", other: "minutes" },
|
|
33
|
+
second: { one: "second", other: "seconds" }
|
|
34
|
+
};
|
|
35
|
+
// ─── Module-Level High Performance Global Caches ──────────────────────────────
|
|
36
|
+
const MAX_CACHE_SIZE = 1000;
|
|
37
|
+
const numberFormatters = new Map();
|
|
38
|
+
const dateTimeFormatters = new Map();
|
|
39
|
+
const relativeTimeFormatters = new Map();
|
|
40
|
+
const pluralRulesFormatters = new Map();
|
|
41
|
+
function serializeOptions(locale, opts) {
|
|
42
|
+
const keys = Object.keys(opts);
|
|
43
|
+
if (keys.length === 0)
|
|
44
|
+
return locale;
|
|
45
|
+
keys.sort();
|
|
46
|
+
let parts = [locale];
|
|
47
|
+
for (let i = 0; i < keys.length; i++) {
|
|
48
|
+
const k = keys[i];
|
|
49
|
+
const v = opts[k];
|
|
50
|
+
if (v === undefined)
|
|
51
|
+
continue;
|
|
52
|
+
// escape backslashes, pipes, commas, and colons to prevent cache key collisions
|
|
53
|
+
const safeK = k.replace(/[\\|:,]/g, '\\$&');
|
|
54
|
+
const safeV = String(v).replace(/[\\|:,]/g, '\\$&');
|
|
55
|
+
parts.push(`${safeK}:${safeV}`);
|
|
56
|
+
}
|
|
57
|
+
return parts.join("|");
|
|
58
|
+
}
|
|
59
|
+
function getNumberFormatter(locale, options) {
|
|
60
|
+
const key = serializeOptions(locale, options);
|
|
61
|
+
if (numberFormatters.has(key)) {
|
|
62
|
+
const entry = numberFormatters.get(key);
|
|
63
|
+
numberFormatters.delete(key);
|
|
64
|
+
numberFormatters.set(key, entry);
|
|
65
|
+
return entry;
|
|
66
|
+
}
|
|
67
|
+
const formatter = new Intl.NumberFormat(locale, options);
|
|
68
|
+
if (numberFormatters.size >= MAX_CACHE_SIZE) {
|
|
69
|
+
const oldestKey = numberFormatters.keys().next().value;
|
|
70
|
+
if (oldestKey !== undefined)
|
|
71
|
+
numberFormatters.delete(oldestKey);
|
|
72
|
+
}
|
|
73
|
+
numberFormatters.set(key, formatter);
|
|
74
|
+
return formatter;
|
|
75
|
+
}
|
|
76
|
+
function getDateTimeFormatter(locale, options) {
|
|
77
|
+
const key = serializeOptions(locale, options);
|
|
78
|
+
if (dateTimeFormatters.has(key)) {
|
|
79
|
+
const entry = dateTimeFormatters.get(key);
|
|
80
|
+
dateTimeFormatters.delete(key);
|
|
81
|
+
dateTimeFormatters.set(key, entry);
|
|
82
|
+
return entry;
|
|
83
|
+
}
|
|
84
|
+
const formatter = new Intl.DateTimeFormat(locale, options);
|
|
85
|
+
if (dateTimeFormatters.size >= MAX_CACHE_SIZE) {
|
|
86
|
+
const oldestKey = dateTimeFormatters.keys().next().value;
|
|
87
|
+
if (oldestKey !== undefined)
|
|
88
|
+
dateTimeFormatters.delete(oldestKey);
|
|
89
|
+
}
|
|
90
|
+
dateTimeFormatters.set(key, formatter);
|
|
91
|
+
return formatter;
|
|
92
|
+
}
|
|
93
|
+
function getRelativeTimeFormatter(locale, options) {
|
|
94
|
+
const key = serializeOptions(locale, options);
|
|
95
|
+
if (relativeTimeFormatters.has(key)) {
|
|
96
|
+
const entry = relativeTimeFormatters.get(key);
|
|
97
|
+
relativeTimeFormatters.delete(key);
|
|
98
|
+
relativeTimeFormatters.set(key, entry);
|
|
99
|
+
return entry;
|
|
100
|
+
}
|
|
101
|
+
const formatter = new Intl.RelativeTimeFormat(locale, options);
|
|
102
|
+
if (relativeTimeFormatters.size >= MAX_CACHE_SIZE) {
|
|
103
|
+
const oldestKey = relativeTimeFormatters.keys().next().value;
|
|
104
|
+
if (oldestKey !== undefined)
|
|
105
|
+
relativeTimeFormatters.delete(oldestKey);
|
|
106
|
+
}
|
|
107
|
+
relativeTimeFormatters.set(key, formatter);
|
|
108
|
+
return formatter;
|
|
109
|
+
}
|
|
110
|
+
function getPluralRulesFormatter(locale, options) {
|
|
111
|
+
const key = serializeOptions(locale, options);
|
|
112
|
+
if (pluralRulesFormatters.has(key)) {
|
|
113
|
+
const entry = pluralRulesFormatters.get(key);
|
|
114
|
+
pluralRulesFormatters.delete(key);
|
|
115
|
+
pluralRulesFormatters.set(key, entry);
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
|
118
|
+
const rulesInstance = new Intl.PluralRules(locale, options);
|
|
119
|
+
if (pluralRulesFormatters.size >= MAX_CACHE_SIZE) {
|
|
120
|
+
const oldestKey = pluralRulesFormatters.keys().next().value;
|
|
121
|
+
if (oldestKey !== undefined)
|
|
122
|
+
pluralRulesFormatters.delete(oldestKey);
|
|
123
|
+
}
|
|
124
|
+
pluralRulesFormatters.set(key, rulesInstance);
|
|
125
|
+
return rulesInstance;
|
|
126
|
+
}
|
|
127
|
+
export function createFormatter(config = {}) {
|
|
128
|
+
const locale = config.locale ?? "en-US";
|
|
129
|
+
let defaultCurrency = config.currency ?? "USD";
|
|
130
|
+
if (typeof defaultCurrency === "string") {
|
|
131
|
+
defaultCurrency = defaultCurrency.toUpperCase();
|
|
132
|
+
if (!/^[A-Z]{3}$/.test(defaultCurrency)) {
|
|
133
|
+
console.warn(`[intl-formatter] Invalid currency code "${defaultCurrency}". Falling back to "USD".`);
|
|
134
|
+
defaultCurrency = "USD";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const fallback = config.fallback ?? "—";
|
|
138
|
+
const rules = {
|
|
139
|
+
...DEFAULT_RULES,
|
|
140
|
+
...config.rules,
|
|
141
|
+
numberFormat: { ...DEFAULT_RULES.numberFormat, ...config.rules?.numberFormat },
|
|
142
|
+
currencyFormat: { ...DEFAULT_RULES.currencyFormat, ...config.rules?.currencyFormat },
|
|
143
|
+
relativeTimeFormat: { ...DEFAULT_RULES.relativeTimeFormat, ...config.rules?.relativeTimeFormat },
|
|
144
|
+
dateFormat: { ...DEFAULT_RULES.dateFormat, ...config.rules?.dateFormat },
|
|
145
|
+
dateTimeFormat: { ...DEFAULT_RULES.dateTimeFormat, ...config.rules?.dateTimeFormat },
|
|
146
|
+
};
|
|
147
|
+
// Specialized Fallbacks Resolution
|
|
148
|
+
const defaultNumberFallback = rules.numberFallback || rules.fallback || fallback;
|
|
149
|
+
const defaultCurrencyFallback = rules.currencyFallback || rules.fallback || fallback;
|
|
150
|
+
const defaultPercentageFallback = rules.percentageFallback || rules.fallback || fallback;
|
|
151
|
+
const defaultDurationFallback = rules.durationFallback || rules.fallback || fallback;
|
|
152
|
+
const defaultDateFallback = rules.dateFallback || rules.fallback || fallback;
|
|
153
|
+
const defaultDateTimeFallback = rules.dateTimeFallback || rules.fallback || fallback;
|
|
154
|
+
const defaultRelativeTimeFallback = rules.relativeTimeFallback || rules.fallback || fallback;
|
|
155
|
+
function toNumber(value) {
|
|
156
|
+
if (value == null || value === "")
|
|
157
|
+
return null;
|
|
158
|
+
try {
|
|
159
|
+
if (typeof value === "boolean")
|
|
160
|
+
return null;
|
|
161
|
+
if (Array.isArray(value))
|
|
162
|
+
return null;
|
|
163
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
164
|
+
return Number.isNaN(n) ? null : n;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function toValidDate(value) {
|
|
171
|
+
if (value == null || value === "")
|
|
172
|
+
return null;
|
|
173
|
+
try {
|
|
174
|
+
if (value instanceof Date) {
|
|
175
|
+
return Number.isNaN(value.getTime()) ? null : value;
|
|
176
|
+
}
|
|
177
|
+
if (typeof value === "number") {
|
|
178
|
+
const d = new Date(value);
|
|
179
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
180
|
+
}
|
|
181
|
+
if (typeof value === "string") {
|
|
182
|
+
const isDigitsOnly = /^-?\d+$/.test(value);
|
|
183
|
+
const isCalendarDate = /^(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])(?:[01]\d|2[0-3])?$/.test(value);
|
|
184
|
+
if (isDigitsOnly && value.length === 10 && !isCalendarDate) {
|
|
185
|
+
// Second UNIX timestamp: convert to milliseconds
|
|
186
|
+
const parsedValue = Number(value) * 1000;
|
|
187
|
+
const d = new Date(parsedValue);
|
|
188
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
189
|
+
}
|
|
190
|
+
if (isDigitsOnly && value.length >= 12) {
|
|
191
|
+
// Millisecond UNIX timestamp
|
|
192
|
+
const parsedValue = Number(value);
|
|
193
|
+
const d = new Date(parsedValue);
|
|
194
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
195
|
+
}
|
|
196
|
+
const d = new Date(value);
|
|
197
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function isLarge(n) {
|
|
206
|
+
return Math.abs(n) >= rules.compactThreshold;
|
|
207
|
+
}
|
|
208
|
+
function normalizeICU(str, localeStr) {
|
|
209
|
+
// Normalize both narrow no-break space and standard non-breaking space to regular space
|
|
210
|
+
let normalized = str.replace(/[\u202f\u00a0]/g, " ").trim();
|
|
211
|
+
// Only apply English-style compact suffix normalizer if locale is English to avoid breaking other languages
|
|
212
|
+
if (localeStr.startsWith("en")) {
|
|
213
|
+
normalized = normalized.replace(/(\d)\s*([kKmMbBtT])\b/g, (match, p1, p2) => p1 + p2.toUpperCase());
|
|
214
|
+
}
|
|
215
|
+
return normalized;
|
|
216
|
+
}
|
|
217
|
+
function safeFormat(formatterFn, fallbackValue) {
|
|
218
|
+
try {
|
|
219
|
+
return formatterFn();
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
console.error(`[intl-formatter] Formatting failed. Returning fallback. Error:`, err);
|
|
223
|
+
return fallbackValue;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function resolvePluralLabel(v, form, localeStr) {
|
|
227
|
+
if (typeof form === "string") {
|
|
228
|
+
// Singular/plural backwards fallback
|
|
229
|
+
return v === 1 ? (form === "hours" ? "hour" : form === "minutes" ? "minute" : form === "seconds" ? "second" : form) : form;
|
|
230
|
+
}
|
|
231
|
+
// Backward compatibility for legacy option keys { singular, plural }
|
|
232
|
+
const legacyForm = form;
|
|
233
|
+
if (legacyForm.singular !== undefined || legacyForm.plural !== undefined) {
|
|
234
|
+
return v === 1 ? (legacyForm.singular ?? legacyForm.other) : (legacyForm.plural ?? legacyForm.other);
|
|
235
|
+
}
|
|
236
|
+
const rulesInstance = getPluralRulesFormatter(localeStr, { type: "cardinal" });
|
|
237
|
+
const category = rulesInstance.select(v);
|
|
238
|
+
return form[category] ?? form.other;
|
|
239
|
+
}
|
|
240
|
+
const formatters = {
|
|
241
|
+
number(value, options = {}) {
|
|
242
|
+
const n = toNumber(value);
|
|
243
|
+
const callFallback = options.fallback ?? defaultNumberFallback;
|
|
244
|
+
if (n == null)
|
|
245
|
+
return callFallback;
|
|
246
|
+
return safeFormat(() => {
|
|
247
|
+
const { fallback: _, ...rest } = options;
|
|
248
|
+
const resolvedOptions = {
|
|
249
|
+
style: "decimal",
|
|
250
|
+
notation: isLarge(n) ? "compact" : "standard",
|
|
251
|
+
minimumFractionDigits: rules.minimumFractionDigits,
|
|
252
|
+
maximumFractionDigits: rules.maximumFractionDigits,
|
|
253
|
+
...rules.numberFormat,
|
|
254
|
+
...rest,
|
|
255
|
+
};
|
|
256
|
+
return normalizeICU(getNumberFormatter(locale, resolvedOptions).format(n), locale);
|
|
257
|
+
}, callFallback);
|
|
258
|
+
},
|
|
259
|
+
currency(value, options = {}) {
|
|
260
|
+
const n = toNumber(value);
|
|
261
|
+
const callFallback = options.fallback ?? defaultCurrencyFallback;
|
|
262
|
+
if (n == null)
|
|
263
|
+
return callFallback;
|
|
264
|
+
return safeFormat(() => {
|
|
265
|
+
const { currency: overrideCurrency, currencyDisplay, fallback: _, ...rest } = options;
|
|
266
|
+
let finalCurrency = overrideCurrency ?? defaultCurrency;
|
|
267
|
+
if (typeof finalCurrency === "string") {
|
|
268
|
+
finalCurrency = finalCurrency.toUpperCase();
|
|
269
|
+
if (!/^[A-Z]{3}$/.test(finalCurrency)) {
|
|
270
|
+
console.warn(`[intl-formatter] Invalid override currency code "${finalCurrency}". Falling back to default: "${defaultCurrency}".`);
|
|
271
|
+
finalCurrency = defaultCurrency;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const resolvedOptions = {
|
|
275
|
+
style: "currency",
|
|
276
|
+
currency: finalCurrency,
|
|
277
|
+
notation: isLarge(n) ? "compact" : "standard",
|
|
278
|
+
minimumFractionDigits: rules.minimumFractionDigits,
|
|
279
|
+
maximumFractionDigits: rules.maximumFractionDigits,
|
|
280
|
+
currencyDisplay: currencyDisplay ?? rules.currencyDisplay,
|
|
281
|
+
...rules.currencyFormat,
|
|
282
|
+
...rest,
|
|
283
|
+
};
|
|
284
|
+
return normalizeICU(getNumberFormatter(locale, resolvedOptions).format(n), locale);
|
|
285
|
+
}, callFallback);
|
|
286
|
+
},
|
|
287
|
+
percentage(value, options = {}) {
|
|
288
|
+
const n = toNumber(value);
|
|
289
|
+
const callFallback = options.fallback ?? defaultPercentageFallback;
|
|
290
|
+
if (n == null)
|
|
291
|
+
return callFallback;
|
|
292
|
+
return safeFormat(() => {
|
|
293
|
+
const { inputMode, fallback: _, ...intlOptions } = options;
|
|
294
|
+
const normalized = inputMode === "fraction" ? n : n / 100;
|
|
295
|
+
const checkMaxFraction = intlOptions.maximumFractionDigits ?? rules.maximumFractionDigits;
|
|
296
|
+
const wouldBeZero = Math.abs(normalized) > 0 && parseFloat(Math.abs(normalized).toFixed(checkMaxFraction + 2)) === 0;
|
|
297
|
+
const sigDigits = Math.max(checkMaxFraction, 4);
|
|
298
|
+
const resolvedOptions = {
|
|
299
|
+
style: "percent",
|
|
300
|
+
...(wouldBeZero
|
|
301
|
+
? { maximumSignificantDigits: sigDigits }
|
|
302
|
+
: {
|
|
303
|
+
minimumFractionDigits: rules.minimumFractionDigits,
|
|
304
|
+
maximumFractionDigits: rules.maximumFractionDigits,
|
|
305
|
+
}),
|
|
306
|
+
...intlOptions,
|
|
307
|
+
};
|
|
308
|
+
return normalizeICU(getNumberFormatter(locale, resolvedOptions).format(normalized), locale);
|
|
309
|
+
}, callFallback);
|
|
310
|
+
},
|
|
311
|
+
duration(value, options = {}) {
|
|
312
|
+
const n = toNumber(value);
|
|
313
|
+
const callFallback = options.fallback ?? defaultDurationFallback;
|
|
314
|
+
if (n == null)
|
|
315
|
+
return callFallback;
|
|
316
|
+
return safeFormat(() => {
|
|
317
|
+
const absN = Math.abs(n);
|
|
318
|
+
const fracDigits = options.fractionalDigits ?? 0;
|
|
319
|
+
const roundedAbsN = fracDigits > 0
|
|
320
|
+
? Math.round(absN * Math.pow(10, fracDigits)) / Math.pow(10, fracDigits)
|
|
321
|
+
: Math.round(absN);
|
|
322
|
+
const totalSec = Math.floor(roundedAbsN);
|
|
323
|
+
const h = Math.floor(totalSec / 3600);
|
|
324
|
+
const m = Math.floor((totalSec % 3600) / 60);
|
|
325
|
+
let s;
|
|
326
|
+
let sVal;
|
|
327
|
+
if (fracDigits > 0) {
|
|
328
|
+
sVal = roundedAbsN % 60;
|
|
329
|
+
s = sVal.toFixed(fracDigits);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
sVal = totalSec % 60;
|
|
333
|
+
s = String(sVal);
|
|
334
|
+
}
|
|
335
|
+
const sign = n < 0 && absN >= 0.0001 ? "-" : "";
|
|
336
|
+
const userHour = options.labels?.hour;
|
|
337
|
+
const resolvedHour = typeof userHour === "string"
|
|
338
|
+
? userHour
|
|
339
|
+
: { ...DEFAULT_DURATION_LABELS.hour, ...userHour };
|
|
340
|
+
const userMinute = options.labels?.minute;
|
|
341
|
+
const resolvedMinute = typeof userMinute === "string"
|
|
342
|
+
? userMinute
|
|
343
|
+
: { ...DEFAULT_DURATION_LABELS.minute, ...userMinute };
|
|
344
|
+
const userSecond = options.labels?.second;
|
|
345
|
+
const resolvedSecond = typeof userSecond === "string"
|
|
346
|
+
? userSecond
|
|
347
|
+
: { ...DEFAULT_DURATION_LABELS.second, ...userSecond };
|
|
348
|
+
const labels = {
|
|
349
|
+
h: options.labels?.h ?? DEFAULT_DURATION_LABELS.h,
|
|
350
|
+
m: options.labels?.m ?? DEFAULT_DURATION_LABELS.m,
|
|
351
|
+
s: options.labels?.s ?? DEFAULT_DURATION_LABELS.s,
|
|
352
|
+
hour: resolvedHour,
|
|
353
|
+
minute: resolvedMinute,
|
|
354
|
+
second: resolvedSecond,
|
|
355
|
+
};
|
|
356
|
+
if (options.format === "verbose") {
|
|
357
|
+
const parts = [];
|
|
358
|
+
if (h)
|
|
359
|
+
parts.push(`${h} ${resolvePluralLabel(h, labels.hour, locale)}`);
|
|
360
|
+
if (m)
|
|
361
|
+
parts.push(`${m} ${resolvePluralLabel(m, labels.minute, locale)}`);
|
|
362
|
+
if (sVal > 0 || fracDigits > 0 || !parts.length) {
|
|
363
|
+
parts.push(`${s} ${resolvePluralLabel(sVal, labels.second, locale)}`);
|
|
364
|
+
}
|
|
365
|
+
return sign + parts.join(", ");
|
|
366
|
+
}
|
|
367
|
+
if (h > 0)
|
|
368
|
+
return `${sign}${h}${labels.h} ${m}${labels.m}`;
|
|
369
|
+
if (m > 0)
|
|
370
|
+
return `${sign}${m}${labels.m} ${s}${labels.s}`;
|
|
371
|
+
return `${sign}${s}${labels.s}`;
|
|
372
|
+
}, callFallback);
|
|
373
|
+
},
|
|
374
|
+
date(value, options = {}) {
|
|
375
|
+
const date = toValidDate(value);
|
|
376
|
+
const callFallback = options.fallback ?? defaultDateFallback;
|
|
377
|
+
if (!date)
|
|
378
|
+
return callFallback;
|
|
379
|
+
return safeFormat(() => {
|
|
380
|
+
const { fallback: _, ...rest } = options;
|
|
381
|
+
const resolvedOptions = rest.dateStyle
|
|
382
|
+
? rest
|
|
383
|
+
: { ...rules.dateFormat, ...rest };
|
|
384
|
+
return normalizeICU(getDateTimeFormatter(locale, resolvedOptions).format(date), locale);
|
|
385
|
+
}, callFallback);
|
|
386
|
+
},
|
|
387
|
+
dateTime(value, options = {}) {
|
|
388
|
+
const date = toValidDate(value);
|
|
389
|
+
const callFallback = options.fallback ?? defaultDateTimeFallback;
|
|
390
|
+
if (!date)
|
|
391
|
+
return callFallback;
|
|
392
|
+
return safeFormat(() => {
|
|
393
|
+
const { fallback: _, ...rest } = options;
|
|
394
|
+
const resolvedOptions = rest.dateStyle || rest.timeStyle
|
|
395
|
+
? rest
|
|
396
|
+
: { ...rules.dateTimeFormat, ...rest };
|
|
397
|
+
return normalizeICU(getDateTimeFormatter(locale, resolvedOptions).format(date), locale);
|
|
398
|
+
}, callFallback);
|
|
399
|
+
},
|
|
400
|
+
relativeTime(value, optionsOrNow) {
|
|
401
|
+
const date = toValidDate(value);
|
|
402
|
+
let opts = {};
|
|
403
|
+
let now = Date.now();
|
|
404
|
+
if (typeof optionsOrNow === "number") {
|
|
405
|
+
now = optionsOrNow;
|
|
406
|
+
}
|
|
407
|
+
else if (optionsOrNow && typeof optionsOrNow === "object") {
|
|
408
|
+
const { now: overrideNow, ...rest } = optionsOrNow;
|
|
409
|
+
opts = rest;
|
|
410
|
+
if (overrideNow !== undefined) {
|
|
411
|
+
now = overrideNow;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const callFallback = opts.fallback ?? defaultRelativeTimeFallback;
|
|
415
|
+
if (!date)
|
|
416
|
+
return callFallback;
|
|
417
|
+
return safeFormat(() => {
|
|
418
|
+
const diffSec = Math.round((date.getTime() - now) / 1000);
|
|
419
|
+
const abs = Math.abs(diffSec);
|
|
420
|
+
const resolvedOptions = {
|
|
421
|
+
style: opts.style ?? rules.relativeTimeFormat?.style ?? "long",
|
|
422
|
+
numeric: opts.numeric ?? rules.relativeTimeFormat?.numeric ?? "auto",
|
|
423
|
+
};
|
|
424
|
+
const rtfInstance = getRelativeTimeFormatter(locale, resolvedOptions);
|
|
425
|
+
let formatted;
|
|
426
|
+
if (abs < 60) {
|
|
427
|
+
formatted = rtfInstance.format(diffSec, "second");
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
const min = Math.round(diffSec / 60);
|
|
431
|
+
if (Math.abs(min) < 60) {
|
|
432
|
+
formatted = rtfInstance.format(min, "minute");
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const hr = Math.round(min / 60);
|
|
436
|
+
if (Math.abs(hr) < 24) {
|
|
437
|
+
formatted = rtfInstance.format(hr, "hour");
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const day = Math.round(hr / 24);
|
|
441
|
+
if (Math.abs(day) < 7) {
|
|
442
|
+
formatted = rtfInstance.format(day, "day");
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
const week = Math.round(day / 7);
|
|
446
|
+
if (Math.abs(week) < 4) {
|
|
447
|
+
formatted = rtfInstance.format(week, "week");
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
const month = Math.round(day / 30);
|
|
451
|
+
if (Math.abs(month) < 12) {
|
|
452
|
+
formatted = rtfInstance.format(month, "month");
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
const year = Math.round(day / 365);
|
|
456
|
+
formatted = rtfInstance.format(year, "year");
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return normalizeICU(formatted, locale);
|
|
464
|
+
}, callFallback);
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
return Object.freeze(formatters);
|
|
468
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export type NumericInput = number | string | null | undefined;
|
|
2
|
+
export type DateInput = string | number | Date | null | undefined;
|
|
3
|
+
export type NumberOptions = Pick<Intl.NumberFormatOptions, "notation" | "minimumFractionDigits" | "maximumFractionDigits" | "minimumSignificantDigits" | "maximumSignificantDigits" | "minimumIntegerDigits" | "useGrouping" | "signDisplay"> & {
|
|
4
|
+
/** Override global fallback string for this call */
|
|
5
|
+
fallback?: string;
|
|
6
|
+
};
|
|
7
|
+
export type CurrencyOptions = NumberOptions & {
|
|
8
|
+
currency?: string;
|
|
9
|
+
currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
|
|
10
|
+
currencySign?: "standard" | "accounting";
|
|
11
|
+
};
|
|
12
|
+
export type PercentageOptions = Pick<Intl.NumberFormatOptions, "minimumFractionDigits" | "maximumFractionDigits" | "minimumSignificantDigits" | "maximumSignificantDigits" | "signDisplay"> & {
|
|
13
|
+
/** "percent" (default): 50 → "50%". "fraction": 0.5 → "50%" */
|
|
14
|
+
inputMode?: "percent" | "fraction";
|
|
15
|
+
/** Override global fallback string for this call */
|
|
16
|
+
fallback?: string;
|
|
17
|
+
};
|
|
18
|
+
export type DateOptions = Intl.DateTimeFormatOptions & {
|
|
19
|
+
/** Override global fallback string for this call */
|
|
20
|
+
fallback?: string;
|
|
21
|
+
};
|
|
22
|
+
export type DateTimeOptions = Intl.DateTimeFormatOptions & {
|
|
23
|
+
/** Override global fallback string for this call */
|
|
24
|
+
fallback?: string;
|
|
25
|
+
};
|
|
26
|
+
export type PluralForm = string | {
|
|
27
|
+
zero?: string;
|
|
28
|
+
one?: string;
|
|
29
|
+
two?: string;
|
|
30
|
+
few?: string;
|
|
31
|
+
many?: string;
|
|
32
|
+
other: string;
|
|
33
|
+
};
|
|
34
|
+
export type DurationLabels = {
|
|
35
|
+
h?: string;
|
|
36
|
+
m?: string;
|
|
37
|
+
s?: string;
|
|
38
|
+
hour?: PluralForm;
|
|
39
|
+
minute?: PluralForm;
|
|
40
|
+
second?: PluralForm;
|
|
41
|
+
};
|
|
42
|
+
export type DurationOptions = {
|
|
43
|
+
/**
|
|
44
|
+
* "compact" (default): produces compact output like "2m 30s" or "1h 0m".
|
|
45
|
+
* "verbose": produces spelled-out output like "1 hour, 1 minute, 1 second".
|
|
46
|
+
*/
|
|
47
|
+
format?: "compact" | "verbose";
|
|
48
|
+
/** Override global fallback string for this call */
|
|
49
|
+
fallback?: string;
|
|
50
|
+
/** Custom localization labels for durations */
|
|
51
|
+
labels?: DurationLabels;
|
|
52
|
+
/** Number of decimal places for seconds. Default: 0 */
|
|
53
|
+
fractionalDigits?: number;
|
|
54
|
+
};
|
|
55
|
+
export type RelativeTimeOptions = {
|
|
56
|
+
/** The format style. Default: "long" */
|
|
57
|
+
style?: "long" | "short" | "narrow";
|
|
58
|
+
/** The numeric values display format. Default: "auto" */
|
|
59
|
+
numeric?: "always" | "auto";
|
|
60
|
+
/** Override global fallback string for this call */
|
|
61
|
+
fallback?: string;
|
|
62
|
+
};
|
|
63
|
+
export type FormatterRules = {
|
|
64
|
+
/** Numbers >= this use compact notation (1.2K, 3.4M). Default: 10000 */
|
|
65
|
+
compactThreshold?: number;
|
|
66
|
+
/** Minimum fraction digits for number, currency, percentage. Default: 0 */
|
|
67
|
+
minimumFractionDigits?: number;
|
|
68
|
+
/** Maximum fraction digits for number, currency, percentage. Default: 2 */
|
|
69
|
+
maximumFractionDigits?: number;
|
|
70
|
+
/** Default currency display style. Default: "narrowSymbol" */
|
|
71
|
+
currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
|
|
72
|
+
/** Default date format options */
|
|
73
|
+
dateFormat?: Intl.DateTimeFormatOptions;
|
|
74
|
+
/** Default dateTime format options */
|
|
75
|
+
dateTimeFormat?: Intl.DateTimeFormatOptions;
|
|
76
|
+
/** Default number format options — merged with per-call overrides */
|
|
77
|
+
numberFormat?: NumberOptions;
|
|
78
|
+
/** Default currency format options — merged with per-call overrides */
|
|
79
|
+
currencyFormat?: CurrencyOptions;
|
|
80
|
+
/** Default relativeTime format options */
|
|
81
|
+
relativeTimeFormat?: RelativeTimeOptions;
|
|
82
|
+
/** Specialized Default Fallbacks */
|
|
83
|
+
fallback?: string;
|
|
84
|
+
numberFallback?: string;
|
|
85
|
+
currencyFallback?: string;
|
|
86
|
+
percentageFallback?: string;
|
|
87
|
+
durationFallback?: string;
|
|
88
|
+
dateFallback?: string;
|
|
89
|
+
dateTimeFallback?: string;
|
|
90
|
+
relativeTimeFallback?: string;
|
|
91
|
+
};
|
|
92
|
+
export type FormatterConfig = {
|
|
93
|
+
locale?: string;
|
|
94
|
+
currency?: string;
|
|
95
|
+
fallback?: string;
|
|
96
|
+
rules?: FormatterRules;
|
|
97
|
+
};
|
|
98
|
+
export interface Formatter {
|
|
99
|
+
/**
|
|
100
|
+
* Format a plain number. Uses compact notation above compactThreshold.
|
|
101
|
+
* @example
|
|
102
|
+
* fmt.number(1234567) // "1.2M"
|
|
103
|
+
* fmt.number(1234, { notation: "standard" }) // "1,234"
|
|
104
|
+
* fmt.number(1.5678, { maximumFractionDigits: 1 }) // "1.6"
|
|
105
|
+
*/
|
|
106
|
+
number(value: NumericInput, options?: NumberOptions): string;
|
|
107
|
+
/**
|
|
108
|
+
* Format a currency value. Uses compact notation above compactThreshold.
|
|
109
|
+
* @example
|
|
110
|
+
* fmt.currency(49900) // "$49.9K"
|
|
111
|
+
* fmt.currency(1234, { currency: "EUR" }) // "€1,234"
|
|
112
|
+
* fmt.currency(1234, { currencyDisplay: "code" }) // "USD 1,234"
|
|
113
|
+
* fmt.currency(1234, { minimumFractionDigits: 2 }) // "$1,234.00"
|
|
114
|
+
*/
|
|
115
|
+
currency(value: NumericInput, options?: CurrencyOptions): string;
|
|
116
|
+
/**
|
|
117
|
+
* Format a percentage. Input treated as raw percentage by default (50 → "50%").
|
|
118
|
+
* For values that round to zero, uses significantDigits to preserve precision.
|
|
119
|
+
* @example
|
|
120
|
+
* fmt.percentage(12.5) // "12.5%"
|
|
121
|
+
* fmt.percentage(0.001) // "0.001%"
|
|
122
|
+
* fmt.percentage(0.5, { inputMode: "fraction" }) // "50%"
|
|
123
|
+
* fmt.percentage(12.5, { maximumFractionDigits: 0 }) // "13%"
|
|
124
|
+
*/
|
|
125
|
+
percentage(value: NumericInput, options?: PercentageOptions): string;
|
|
126
|
+
/**
|
|
127
|
+
* Format a duration in seconds. Pure math — no Intl, no ICU risk.
|
|
128
|
+
* Output is always in English by default, but customizable via labels.
|
|
129
|
+
* Negative values are supported and produce a leading "-" sign.
|
|
130
|
+
* @example
|
|
131
|
+
* fmt.duration(150) // "2m 30s"
|
|
132
|
+
* fmt.duration(3661, { format: "verbose" }) // "1 hour, 1 minute, 1 second"
|
|
133
|
+
* fmt.duration(-90) // "-1m 30s"
|
|
134
|
+
*/
|
|
135
|
+
duration(value: NumericInput, options?: DurationOptions): string;
|
|
136
|
+
/**
|
|
137
|
+
* Format a Date or ISO string as a human-readable date.
|
|
138
|
+
* @example
|
|
139
|
+
* fmt.date("2024-01-15") // "Jan 15, 2024"
|
|
140
|
+
* fmt.date("2024-01-15", { dateStyle: "full" }) // "Monday, January 15, 2024"
|
|
141
|
+
*/
|
|
142
|
+
date(value: DateInput, options?: DateOptions): string;
|
|
143
|
+
/**
|
|
144
|
+
* Format a Date or ISO string as a human-readable date and time.
|
|
145
|
+
* @example
|
|
146
|
+
* fmt.dateTime("2024-01-15T14:30:00") // "Jan 15, 2024, 2:30 PM"
|
|
147
|
+
*/
|
|
148
|
+
dateTime(value: DateInput, options?: DateTimeOptions): string;
|
|
149
|
+
/**
|
|
150
|
+
* Format a Date or ISO string as relative time.
|
|
151
|
+
* `now` defaults to `Date.now()` at call time. In Server Components, always pass
|
|
152
|
+
* a fixed timestamp to prevent SSR/client hydration clock drift.
|
|
153
|
+
* Supports an options object for custom styling or a backward-compatible timestamp.
|
|
154
|
+
* @example
|
|
155
|
+
* const now = Date.now(); // capture once per request
|
|
156
|
+
* fmt.relativeTime("2024-01-15T10:00:00Z", { now }) // "2 hours ago"
|
|
157
|
+
* fmt.relativeTime("2024-01-15T10:00:00Z", { now, style: "short" }) // "2 hr. ago"
|
|
158
|
+
* fmt.relativeTime(new Date()) // "just now"
|
|
159
|
+
*/
|
|
160
|
+
relativeTime(value: DateInput, optionsOrNow?: (RelativeTimeOptions & {
|
|
161
|
+
now?: number;
|
|
162
|
+
}) | number): string;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createFormatter } from "./core/formatter.js";
|
|
2
|
+
export type { Formatter, FormatterConfig } from "./core/types.js";
|
|
3
|
+
export type { NumericInput, DateInput, NumberOptions, CurrencyOptions, PercentageOptions, DurationOptions, DateOptions, DateTimeOptions, FormatterRules, } from "./core/types.js";
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClE,YAAY,EACV,YAAY,EACZ,SAAS,EACT,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,eAAe,EACf,cAAc,GACf,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFormatter } from "./core/formatter.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EASV,MAAM,iBAAiB,CAAC;AAezB,eAAO,MAAM,aAAa,EAAE,SA0C1B,CAAC;AAEH,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
|