next-formatter 2.0.2 → 2.0.3
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/dist/cjs/client.js +195 -2
- package/dist/cjs/server.js +235 -10
- package/dist/esm/client.mjs +194 -1
- package/dist/esm/server.mjs +229 -4
- package/package.json +2 -15
- package/dist/cjs/core.js +0 -198
- package/dist/cjs/create.js +0 -27
- package/dist/esm/core.mjs +0 -196
- package/dist/esm/create.mjs +0 -24
- /package/dist/{cjs → esm}/index.js +0 -0
package/dist/esm/server.mjs
CHANGED
|
@@ -1,9 +1,234 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createFormatters } from './core';
|
|
3
|
-
import { resolveLocale, resolveCurrency } from './create';
|
|
1
|
+
import { createContext, useMemo } from 'react';
|
|
4
2
|
import { jsx } from 'react/jsx-runtime';
|
|
5
3
|
|
|
6
|
-
// src/
|
|
4
|
+
// src/client.tsx
|
|
5
|
+
|
|
6
|
+
// src/core.ts
|
|
7
|
+
var DEFAULT_RULES = {
|
|
8
|
+
compactThreshold: 1e4,
|
|
9
|
+
minimumFractionDigits: 0,
|
|
10
|
+
maximumFractionDigits: 2,
|
|
11
|
+
currencyDisplay: "narrowSymbol",
|
|
12
|
+
numberFormat: {},
|
|
13
|
+
dateFormat: { year: "numeric", month: "short", day: "2-digit" },
|
|
14
|
+
dateTimeFormat: {
|
|
15
|
+
year: "numeric",
|
|
16
|
+
month: "short",
|
|
17
|
+
day: "2-digit",
|
|
18
|
+
hour: "2-digit",
|
|
19
|
+
minute: "2-digit",
|
|
20
|
+
hour12: true
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function createFormatters(config = {}) {
|
|
24
|
+
const locale = config.locale ?? "en-US";
|
|
25
|
+
const defaultCurrency = config.currency ?? "USD";
|
|
26
|
+
const fallback = config.fallback ?? "\u2014";
|
|
27
|
+
const rules = {
|
|
28
|
+
...DEFAULT_RULES,
|
|
29
|
+
...config.rules
|
|
30
|
+
};
|
|
31
|
+
function toNumber(value) {
|
|
32
|
+
if (value == null || value === "") return null;
|
|
33
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
34
|
+
return Number.isNaN(n) ? null : n;
|
|
35
|
+
}
|
|
36
|
+
function toValidDate(value) {
|
|
37
|
+
if (value == null || value === "") return null;
|
|
38
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
39
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
40
|
+
}
|
|
41
|
+
function isLarge(n) {
|
|
42
|
+
return Math.abs(n) >= rules.compactThreshold;
|
|
43
|
+
}
|
|
44
|
+
function normalizeICU(str) {
|
|
45
|
+
return str.replace(/\u202f/g, " ").replace(/\u00a0/g, " ").trim();
|
|
46
|
+
}
|
|
47
|
+
function alignFractionDigits(opts) {
|
|
48
|
+
const { minimumFractionDigits, maximumFractionDigits, maximumSignificantDigits, minimumSignificantDigits } = opts;
|
|
49
|
+
if (minimumFractionDigits !== void 0 && maximumFractionDigits !== void 0 && minimumFractionDigits !== maximumFractionDigits && maximumSignificantDigits === void 0 && minimumSignificantDigits === void 0) {
|
|
50
|
+
return { ...opts, minimumFractionDigits: maximumFractionDigits };
|
|
51
|
+
}
|
|
52
|
+
return opts;
|
|
53
|
+
}
|
|
54
|
+
const numberCache = /* @__PURE__ */ new Map();
|
|
55
|
+
function getNumberFormatter(options) {
|
|
56
|
+
const key = JSON.stringify({ locale, ...options });
|
|
57
|
+
if (!numberCache.has(key)) {
|
|
58
|
+
numberCache.set(key, new Intl.NumberFormat(locale, options));
|
|
59
|
+
}
|
|
60
|
+
return numberCache.get(key);
|
|
61
|
+
}
|
|
62
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "long" });
|
|
63
|
+
const formatters = {
|
|
64
|
+
/**
|
|
65
|
+
* Format a plain number.
|
|
66
|
+
* @example
|
|
67
|
+
* fmt.number(1234567) // "1.2M"
|
|
68
|
+
* fmt.number(1234, { notation: "standard" }) // "1,234"
|
|
69
|
+
* fmt.number(1.5678, { maximumFractionDigits: 1 }) // "1.6"
|
|
70
|
+
*/
|
|
71
|
+
number(value, options = {}) {
|
|
72
|
+
const n = toNumber(value);
|
|
73
|
+
if (n == null) return fallback;
|
|
74
|
+
return normalizeICU(
|
|
75
|
+
getNumberFormatter(
|
|
76
|
+
alignFractionDigits({
|
|
77
|
+
style: "decimal",
|
|
78
|
+
notation: isLarge(n) ? "compact" : "standard",
|
|
79
|
+
minimumFractionDigits: rules.minimumFractionDigits,
|
|
80
|
+
maximumFractionDigits: rules.maximumFractionDigits,
|
|
81
|
+
...rules.numberFormat,
|
|
82
|
+
...options
|
|
83
|
+
})
|
|
84
|
+
).format(n)
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Format a currency value.
|
|
89
|
+
* @example
|
|
90
|
+
* fmt.currency(49900) // "$49.9K"
|
|
91
|
+
* fmt.currency(1234, { currency: "EUR" }) // "€1,234"
|
|
92
|
+
* fmt.currency(1234, { currencyDisplay: "code" }) // "USD 1,234"
|
|
93
|
+
* fmt.currency(1234, { minimumFractionDigits: 2 }) // "$1,234.00"
|
|
94
|
+
*/
|
|
95
|
+
currency(value, options = {}) {
|
|
96
|
+
const n = toNumber(value);
|
|
97
|
+
if (n == null) return fallback;
|
|
98
|
+
const { currency: overrideCurrency, currencyDisplay, ...rest } = options;
|
|
99
|
+
return normalizeICU(
|
|
100
|
+
getNumberFormatter(
|
|
101
|
+
alignFractionDigits({
|
|
102
|
+
style: "currency",
|
|
103
|
+
currency: overrideCurrency ?? defaultCurrency,
|
|
104
|
+
notation: isLarge(n) ? "compact" : "standard",
|
|
105
|
+
minimumFractionDigits: rules.minimumFractionDigits,
|
|
106
|
+
maximumFractionDigits: rules.maximumFractionDigits,
|
|
107
|
+
currencyDisplay: currencyDisplay ?? rules.currencyDisplay,
|
|
108
|
+
...rules.numberFormat,
|
|
109
|
+
...rest
|
|
110
|
+
})
|
|
111
|
+
).format(n)
|
|
112
|
+
);
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* Format a percentage. Input is treated as a raw percentage (50 → "50%").
|
|
116
|
+
* Uses minimumFractionDigits and maximumFractionDigits from rules.
|
|
117
|
+
* For values that would round to zero, maximumSignificantDigits is set to
|
|
118
|
+
* max(maximumFractionDigits, 4) to preserve meaningful precision.
|
|
119
|
+
* @example
|
|
120
|
+
* fmt.percentage(12.5) // "12.50%" (with default rules min=0, max=2 → aligned to 2)
|
|
121
|
+
* fmt.percentage(0.001) // "0.001%" (wouldBeZero → uses significantDigits)
|
|
122
|
+
* fmt.percentage(12.5, { maximumFractionDigits: 0 }) // "13%"
|
|
123
|
+
*/
|
|
124
|
+
percentage(value, options = {}) {
|
|
125
|
+
const n = toNumber(value);
|
|
126
|
+
if (n == null) return fallback;
|
|
127
|
+
const normalized = n / 100;
|
|
128
|
+
const wouldBeZero = parseFloat(normalized.toFixed(3)) === 0;
|
|
129
|
+
const sigDigits = Math.max(rules.maximumFractionDigits, 4);
|
|
130
|
+
return getNumberFormatter({
|
|
131
|
+
style: "percent",
|
|
132
|
+
...wouldBeZero ? { maximumSignificantDigits: sigDigits } : alignFractionDigits({
|
|
133
|
+
minimumFractionDigits: rules.minimumFractionDigits,
|
|
134
|
+
maximumFractionDigits: rules.maximumFractionDigits
|
|
135
|
+
}),
|
|
136
|
+
...options
|
|
137
|
+
}).format(normalized);
|
|
138
|
+
},
|
|
139
|
+
/**
|
|
140
|
+
* Format a duration in seconds. Pure math — no Intl, no ICU risk.
|
|
141
|
+
* @example
|
|
142
|
+
* fmt.duration(150) // "2m 30s"
|
|
143
|
+
* fmt.duration(45) // "45s"
|
|
144
|
+
*/
|
|
145
|
+
duration(value) {
|
|
146
|
+
const n = toNumber(value);
|
|
147
|
+
if (n == null) return fallback;
|
|
148
|
+
const mins = Math.floor(n / 60);
|
|
149
|
+
const secs = n % 60;
|
|
150
|
+
return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
151
|
+
},
|
|
152
|
+
/**
|
|
153
|
+
* Format a date.
|
|
154
|
+
* @example
|
|
155
|
+
* fmt.date("2024-01-15") // "Jan 15, 2024"
|
|
156
|
+
* fmt.date("2024-01-15", { dateStyle: "full" }) // "Monday, January 15, 2024"
|
|
157
|
+
*/
|
|
158
|
+
date(value, options = {}) {
|
|
159
|
+
const date = toValidDate(value);
|
|
160
|
+
if (!date) return fallback;
|
|
161
|
+
const resolvedOptions = options.dateStyle ? options : { ...rules.dateFormat, ...options };
|
|
162
|
+
return normalizeICU(new Intl.DateTimeFormat(locale, resolvedOptions).format(date));
|
|
163
|
+
},
|
|
164
|
+
dateTime(value, options = {}) {
|
|
165
|
+
const date = toValidDate(value);
|
|
166
|
+
if (!date) return fallback;
|
|
167
|
+
const resolvedOptions = options.dateStyle || options.timeStyle ? options : { ...rules.dateTimeFormat, ...options };
|
|
168
|
+
return normalizeICU(new Intl.DateTimeFormat(locale, resolvedOptions).format(date));
|
|
169
|
+
},
|
|
170
|
+
/**
|
|
171
|
+
* Format a date as relative time.
|
|
172
|
+
* Always pass an explicit `now` timestamp — this ensures consistent
|
|
173
|
+
* output whether called from a server or client component.
|
|
174
|
+
* @example
|
|
175
|
+
* const now = Date.now();
|
|
176
|
+
* fmt.relativeTime("2024-01-15", now) // "3 months ago"
|
|
177
|
+
*/
|
|
178
|
+
relativeTime(value, now = Date.now()) {
|
|
179
|
+
const date = toValidDate(value);
|
|
180
|
+
if (!date) return fallback;
|
|
181
|
+
const diffSec = Math.round((date.getTime() - now) / 1e3);
|
|
182
|
+
const abs = Math.abs(diffSec);
|
|
183
|
+
if (abs < 60) return rtf.format(diffSec, "second");
|
|
184
|
+
const min = Math.round(diffSec / 60);
|
|
185
|
+
if (Math.abs(min) < 60) return rtf.format(min, "minute");
|
|
186
|
+
const hr = Math.round(min / 60);
|
|
187
|
+
if (Math.abs(hr) < 24) return rtf.format(hr, "hour");
|
|
188
|
+
const day = Math.round(hr / 24);
|
|
189
|
+
if (Math.abs(day) < 7) return rtf.format(day, "day");
|
|
190
|
+
const week = Math.round(day / 7);
|
|
191
|
+
if (Math.abs(week) < 4) return rtf.format(week, "week");
|
|
192
|
+
const month = Math.round(day / 30);
|
|
193
|
+
if (Math.abs(month) < 12) return rtf.format(month, "month");
|
|
194
|
+
const year = Math.round(day / 365);
|
|
195
|
+
return rtf.format(year, "year");
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
return formatters;
|
|
199
|
+
}
|
|
200
|
+
var FormatterContext = createContext(null);
|
|
201
|
+
function FormattersProvider({ config, children }) {
|
|
202
|
+
const formatters = useMemo(
|
|
203
|
+
() => createFormatters(config),
|
|
204
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
205
|
+
[config.locale, config.currency, config.fallback, JSON.stringify(config.rules)]
|
|
206
|
+
);
|
|
207
|
+
return /* @__PURE__ */ jsx(FormatterContext.Provider, { value: formatters, children });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/create.ts
|
|
211
|
+
async function resolveLocale(resolver, defaultLocale) {
|
|
212
|
+
const resolved = await resolver?.();
|
|
213
|
+
if (resolved) return resolved;
|
|
214
|
+
if (defaultLocale) return defaultLocale;
|
|
215
|
+
try {
|
|
216
|
+
const { headers } = await import('next/headers');
|
|
217
|
+
const h = await headers();
|
|
218
|
+
const acceptLanguage = h.get("accept-language");
|
|
219
|
+
if (acceptLanguage) {
|
|
220
|
+
const locale = acceptLanguage.split(",")[0]?.trim().split(";")[0]?.trim();
|
|
221
|
+
if (locale) return locale;
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
}
|
|
225
|
+
return "en-US";
|
|
226
|
+
}
|
|
227
|
+
async function resolveCurrency(resolver, defaultCurrency) {
|
|
228
|
+
const resolved = await resolver?.();
|
|
229
|
+
if (resolved) return resolved;
|
|
230
|
+
return defaultCurrency ?? "USD";
|
|
231
|
+
}
|
|
7
232
|
async function NextFormatterProvider({ config = {}, children }) {
|
|
8
233
|
const locale = await resolveLocale(config.getLocale, config.locale);
|
|
9
234
|
const currency = await resolveCurrency(config.getCurrency, config.currency);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-formatter",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "A universal formatter for numbers, currency, dates, and relative time — built for Next.js App Router, supporting server components, client components, and Node.js backends.",
|
|
5
5
|
"author": "gauravgorade",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,10 +46,7 @@
|
|
|
46
46
|
"bugs": {
|
|
47
47
|
"url": "https://github.com/gauravgorade/next-formatter/issues"
|
|
48
48
|
},
|
|
49
|
-
"sideEffects":
|
|
50
|
-
"./dist/esm/client.mjs",
|
|
51
|
-
"./dist/cjs/client.js"
|
|
52
|
-
],
|
|
49
|
+
"sideEffects": false,
|
|
53
50
|
"main": "./dist/cjs/index.js",
|
|
54
51
|
"module": "./dist/esm/index.mjs",
|
|
55
52
|
"types": "./dist/types/index.d.ts",
|
|
@@ -68,16 +65,6 @@
|
|
|
68
65
|
"types": "./dist/types/client.d.ts",
|
|
69
66
|
"import": "./dist/esm/client.mjs",
|
|
70
67
|
"require": "./dist/cjs/client.js"
|
|
71
|
-
},
|
|
72
|
-
"./core": {
|
|
73
|
-
"types": "./dist/types/core.d.ts",
|
|
74
|
-
"import": "./dist/esm/core.mjs",
|
|
75
|
-
"require": "./dist/cjs/core.js"
|
|
76
|
-
},
|
|
77
|
-
"./create": {
|
|
78
|
-
"types": "./dist/types/create.d.ts",
|
|
79
|
-
"import": "./dist/esm/create.mjs",
|
|
80
|
-
"require": "./dist/cjs/create.js"
|
|
81
68
|
}
|
|
82
69
|
},
|
|
83
70
|
"files": [
|
package/dist/cjs/core.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/core.ts
|
|
4
|
-
var DEFAULT_RULES = {
|
|
5
|
-
compactThreshold: 1e4,
|
|
6
|
-
minimumFractionDigits: 0,
|
|
7
|
-
maximumFractionDigits: 2,
|
|
8
|
-
currencyDisplay: "narrowSymbol",
|
|
9
|
-
numberFormat: {},
|
|
10
|
-
dateFormat: { year: "numeric", month: "short", day: "2-digit" },
|
|
11
|
-
dateTimeFormat: {
|
|
12
|
-
year: "numeric",
|
|
13
|
-
month: "short",
|
|
14
|
-
day: "2-digit",
|
|
15
|
-
hour: "2-digit",
|
|
16
|
-
minute: "2-digit",
|
|
17
|
-
hour12: true
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
function createFormatters(config = {}) {
|
|
21
|
-
const locale = config.locale ?? "en-US";
|
|
22
|
-
const defaultCurrency = config.currency ?? "USD";
|
|
23
|
-
const fallback = config.fallback ?? "\u2014";
|
|
24
|
-
const rules = {
|
|
25
|
-
...DEFAULT_RULES,
|
|
26
|
-
...config.rules
|
|
27
|
-
};
|
|
28
|
-
function toNumber(value) {
|
|
29
|
-
if (value == null || value === "") return null;
|
|
30
|
-
const n = typeof value === "number" ? value : Number(value);
|
|
31
|
-
return Number.isNaN(n) ? null : n;
|
|
32
|
-
}
|
|
33
|
-
function toValidDate(value) {
|
|
34
|
-
if (value == null || value === "") return null;
|
|
35
|
-
const date = value instanceof Date ? value : new Date(value);
|
|
36
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
37
|
-
}
|
|
38
|
-
function isLarge(n) {
|
|
39
|
-
return Math.abs(n) >= rules.compactThreshold;
|
|
40
|
-
}
|
|
41
|
-
function normalizeICU(str) {
|
|
42
|
-
return str.replace(/\u202f/g, " ").replace(/\u00a0/g, " ").trim();
|
|
43
|
-
}
|
|
44
|
-
function alignFractionDigits(opts) {
|
|
45
|
-
const { minimumFractionDigits, maximumFractionDigits, maximumSignificantDigits, minimumSignificantDigits } = opts;
|
|
46
|
-
if (minimumFractionDigits !== void 0 && maximumFractionDigits !== void 0 && minimumFractionDigits !== maximumFractionDigits && maximumSignificantDigits === void 0 && minimumSignificantDigits === void 0) {
|
|
47
|
-
return { ...opts, minimumFractionDigits: maximumFractionDigits };
|
|
48
|
-
}
|
|
49
|
-
return opts;
|
|
50
|
-
}
|
|
51
|
-
const numberCache = /* @__PURE__ */ new Map();
|
|
52
|
-
function getNumberFormatter(options) {
|
|
53
|
-
const key = JSON.stringify({ locale, ...options });
|
|
54
|
-
if (!numberCache.has(key)) {
|
|
55
|
-
numberCache.set(key, new Intl.NumberFormat(locale, options));
|
|
56
|
-
}
|
|
57
|
-
return numberCache.get(key);
|
|
58
|
-
}
|
|
59
|
-
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "long" });
|
|
60
|
-
const formatters = {
|
|
61
|
-
/**
|
|
62
|
-
* Format a plain number.
|
|
63
|
-
* @example
|
|
64
|
-
* fmt.number(1234567) // "1.2M"
|
|
65
|
-
* fmt.number(1234, { notation: "standard" }) // "1,234"
|
|
66
|
-
* fmt.number(1.5678, { maximumFractionDigits: 1 }) // "1.6"
|
|
67
|
-
*/
|
|
68
|
-
number(value, options = {}) {
|
|
69
|
-
const n = toNumber(value);
|
|
70
|
-
if (n == null) return fallback;
|
|
71
|
-
return normalizeICU(
|
|
72
|
-
getNumberFormatter(
|
|
73
|
-
alignFractionDigits({
|
|
74
|
-
style: "decimal",
|
|
75
|
-
notation: isLarge(n) ? "compact" : "standard",
|
|
76
|
-
minimumFractionDigits: rules.minimumFractionDigits,
|
|
77
|
-
maximumFractionDigits: rules.maximumFractionDigits,
|
|
78
|
-
...rules.numberFormat,
|
|
79
|
-
...options
|
|
80
|
-
})
|
|
81
|
-
).format(n)
|
|
82
|
-
);
|
|
83
|
-
},
|
|
84
|
-
/**
|
|
85
|
-
* Format a currency value.
|
|
86
|
-
* @example
|
|
87
|
-
* fmt.currency(49900) // "$49.9K"
|
|
88
|
-
* fmt.currency(1234, { currency: "EUR" }) // "€1,234"
|
|
89
|
-
* fmt.currency(1234, { currencyDisplay: "code" }) // "USD 1,234"
|
|
90
|
-
* fmt.currency(1234, { minimumFractionDigits: 2 }) // "$1,234.00"
|
|
91
|
-
*/
|
|
92
|
-
currency(value, options = {}) {
|
|
93
|
-
const n = toNumber(value);
|
|
94
|
-
if (n == null) return fallback;
|
|
95
|
-
const { currency: overrideCurrency, currencyDisplay, ...rest } = options;
|
|
96
|
-
return normalizeICU(
|
|
97
|
-
getNumberFormatter(
|
|
98
|
-
alignFractionDigits({
|
|
99
|
-
style: "currency",
|
|
100
|
-
currency: overrideCurrency ?? defaultCurrency,
|
|
101
|
-
notation: isLarge(n) ? "compact" : "standard",
|
|
102
|
-
minimumFractionDigits: rules.minimumFractionDigits,
|
|
103
|
-
maximumFractionDigits: rules.maximumFractionDigits,
|
|
104
|
-
currencyDisplay: currencyDisplay ?? rules.currencyDisplay,
|
|
105
|
-
...rules.numberFormat,
|
|
106
|
-
...rest
|
|
107
|
-
})
|
|
108
|
-
).format(n)
|
|
109
|
-
);
|
|
110
|
-
},
|
|
111
|
-
/**
|
|
112
|
-
* Format a percentage. Input is treated as a raw percentage (50 → "50%").
|
|
113
|
-
* Uses minimumFractionDigits and maximumFractionDigits from rules.
|
|
114
|
-
* For values that would round to zero, maximumSignificantDigits is set to
|
|
115
|
-
* max(maximumFractionDigits, 4) to preserve meaningful precision.
|
|
116
|
-
* @example
|
|
117
|
-
* fmt.percentage(12.5) // "12.50%" (with default rules min=0, max=2 → aligned to 2)
|
|
118
|
-
* fmt.percentage(0.001) // "0.001%" (wouldBeZero → uses significantDigits)
|
|
119
|
-
* fmt.percentage(12.5, { maximumFractionDigits: 0 }) // "13%"
|
|
120
|
-
*/
|
|
121
|
-
percentage(value, options = {}) {
|
|
122
|
-
const n = toNumber(value);
|
|
123
|
-
if (n == null) return fallback;
|
|
124
|
-
const normalized = n / 100;
|
|
125
|
-
const wouldBeZero = parseFloat(normalized.toFixed(3)) === 0;
|
|
126
|
-
const sigDigits = Math.max(rules.maximumFractionDigits, 4);
|
|
127
|
-
return getNumberFormatter({
|
|
128
|
-
style: "percent",
|
|
129
|
-
...wouldBeZero ? { maximumSignificantDigits: sigDigits } : alignFractionDigits({
|
|
130
|
-
minimumFractionDigits: rules.minimumFractionDigits,
|
|
131
|
-
maximumFractionDigits: rules.maximumFractionDigits
|
|
132
|
-
}),
|
|
133
|
-
...options
|
|
134
|
-
}).format(normalized);
|
|
135
|
-
},
|
|
136
|
-
/**
|
|
137
|
-
* Format a duration in seconds. Pure math — no Intl, no ICU risk.
|
|
138
|
-
* @example
|
|
139
|
-
* fmt.duration(150) // "2m 30s"
|
|
140
|
-
* fmt.duration(45) // "45s"
|
|
141
|
-
*/
|
|
142
|
-
duration(value) {
|
|
143
|
-
const n = toNumber(value);
|
|
144
|
-
if (n == null) return fallback;
|
|
145
|
-
const mins = Math.floor(n / 60);
|
|
146
|
-
const secs = n % 60;
|
|
147
|
-
return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
148
|
-
},
|
|
149
|
-
/**
|
|
150
|
-
* Format a date.
|
|
151
|
-
* @example
|
|
152
|
-
* fmt.date("2024-01-15") // "Jan 15, 2024"
|
|
153
|
-
* fmt.date("2024-01-15", { dateStyle: "full" }) // "Monday, January 15, 2024"
|
|
154
|
-
*/
|
|
155
|
-
date(value, options = {}) {
|
|
156
|
-
const date = toValidDate(value);
|
|
157
|
-
if (!date) return fallback;
|
|
158
|
-
const resolvedOptions = options.dateStyle ? options : { ...rules.dateFormat, ...options };
|
|
159
|
-
return normalizeICU(new Intl.DateTimeFormat(locale, resolvedOptions).format(date));
|
|
160
|
-
},
|
|
161
|
-
dateTime(value, options = {}) {
|
|
162
|
-
const date = toValidDate(value);
|
|
163
|
-
if (!date) return fallback;
|
|
164
|
-
const resolvedOptions = options.dateStyle || options.timeStyle ? options : { ...rules.dateTimeFormat, ...options };
|
|
165
|
-
return normalizeICU(new Intl.DateTimeFormat(locale, resolvedOptions).format(date));
|
|
166
|
-
},
|
|
167
|
-
/**
|
|
168
|
-
* Format a date as relative time.
|
|
169
|
-
* Always pass an explicit `now` timestamp — this ensures consistent
|
|
170
|
-
* output whether called from a server or client component.
|
|
171
|
-
* @example
|
|
172
|
-
* const now = Date.now();
|
|
173
|
-
* fmt.relativeTime("2024-01-15", now) // "3 months ago"
|
|
174
|
-
*/
|
|
175
|
-
relativeTime(value, now = Date.now()) {
|
|
176
|
-
const date = toValidDate(value);
|
|
177
|
-
if (!date) return fallback;
|
|
178
|
-
const diffSec = Math.round((date.getTime() - now) / 1e3);
|
|
179
|
-
const abs = Math.abs(diffSec);
|
|
180
|
-
if (abs < 60) return rtf.format(diffSec, "second");
|
|
181
|
-
const min = Math.round(diffSec / 60);
|
|
182
|
-
if (Math.abs(min) < 60) return rtf.format(min, "minute");
|
|
183
|
-
const hr = Math.round(min / 60);
|
|
184
|
-
if (Math.abs(hr) < 24) return rtf.format(hr, "hour");
|
|
185
|
-
const day = Math.round(hr / 24);
|
|
186
|
-
if (Math.abs(day) < 7) return rtf.format(day, "day");
|
|
187
|
-
const week = Math.round(day / 7);
|
|
188
|
-
if (Math.abs(week) < 4) return rtf.format(week, "week");
|
|
189
|
-
const month = Math.round(day / 30);
|
|
190
|
-
if (Math.abs(month) < 12) return rtf.format(month, "month");
|
|
191
|
-
const year = Math.round(day / 365);
|
|
192
|
-
return rtf.format(year, "year");
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
return formatters;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
exports.createFormatters = createFormatters;
|
package/dist/cjs/create.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// src/create.ts
|
|
4
|
-
async function resolveLocale(resolver, defaultLocale) {
|
|
5
|
-
const resolved = await resolver?.();
|
|
6
|
-
if (resolved) return resolved;
|
|
7
|
-
if (defaultLocale) return defaultLocale;
|
|
8
|
-
try {
|
|
9
|
-
const { headers } = await import('next/headers');
|
|
10
|
-
const h = await headers();
|
|
11
|
-
const acceptLanguage = h.get("accept-language");
|
|
12
|
-
if (acceptLanguage) {
|
|
13
|
-
const locale = acceptLanguage.split(",")[0]?.trim().split(";")[0]?.trim();
|
|
14
|
-
if (locale) return locale;
|
|
15
|
-
}
|
|
16
|
-
} catch {
|
|
17
|
-
}
|
|
18
|
-
return "en-US";
|
|
19
|
-
}
|
|
20
|
-
async function resolveCurrency(resolver, defaultCurrency) {
|
|
21
|
-
const resolved = await resolver?.();
|
|
22
|
-
if (resolved) return resolved;
|
|
23
|
-
return defaultCurrency ?? "USD";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.resolveCurrency = resolveCurrency;
|
|
27
|
-
exports.resolveLocale = resolveLocale;
|