next-formatter 2.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/README.md ADDED
@@ -0,0 +1,91 @@
1
+ ### next-formatter
2
+
3
+ Universal number, currency, date, and relative time formatter for Next.js App Router — works in both **Server Components** and **Client Components** with zero hydration mismatches.
4
+
5
+ [→ View Full Documentation (Interactive)](https://gauravgorade.github.io/next-formatter/)
6
+
7
+ ### Installation
8
+
9
+ ```bash
10
+ npm install next-formatter
11
+ ```
12
+
13
+ ### Recommended Setup
14
+
15
+ Define your configuration once in a shared library file to ensure consistency across the server and client.
16
+
17
+ **lib/formatter.ts**
18
+
19
+ ```typescript
20
+ import { getFormatter as _getFormatter } from "next-formatter/server";
21
+ import { useFormatter } from "next-formatter/client";
22
+ import type { NextFormatterConfig } from "next-formatter";
23
+
24
+ const config: NextFormatterConfig = {
25
+ locale: "en-US",
26
+ currency: "USD",
27
+ rules: {
28
+ compactThreshold: 10000,
29
+ },
30
+ };
31
+
32
+ export const formatterConfig = config;
33
+ export const getFormatter = () => _getFormatter(config);
34
+ export { useFormatter };
35
+ ```
36
+
37
+ ### 1. Initialize Provider
38
+
39
+ Add the `NextFormatterProvider` to your root layout.
40
+
41
+ **app/layout.tsx**
42
+
43
+ ```tsx
44
+ import { NextFormatterProvider } from "next-formatter";
45
+ import { formatterConfig } from "@/lib/formatter";
46
+
47
+ export default async function RootLayout({ children }) {
48
+ return (
49
+ <html lang="en">
50
+ <body>
51
+ <NextFormatterProvider config={formatterConfig}>{children}</NextFormatterProvider>
52
+ </body>
53
+ </html>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ### 2. Usage in Server Components
59
+
60
+ ```tsx
61
+ import { getFormatter } from "@/lib/formatter";
62
+
63
+ export default async function Page() {
64
+ const fmt = await getFormatter();
65
+ return <h1>{fmt.currency(49.99)}</h1>;
66
+ }
67
+ ```
68
+
69
+ ### 3. Usage in Client Components
70
+
71
+ ```tsx
72
+ "use client";
73
+ import { useFormatter } from "@/lib/formatter";
74
+
75
+ export function Price({ value }) {
76
+ const { currency } = useFormatter();
77
+ return <span>{currency(value)}</span>;
78
+ }
79
+ ```
80
+
81
+ ### Why next-formatter?
82
+
83
+ - **Safe ICU Hydration**: Zero spacing or fraction-digit mismatches between server and client.
84
+ - **Dynamic Resolvers**: Detect locale from cookies, headers, or your database.
85
+ - **Micro-Bundle**: Optimized for the App Router with negligible client-side impact.
86
+
87
+ [→ Full Documentation](https://gauravgorade.github.io/next-formatter/)
88
+
89
+ ### License
90
+
91
+ MIT © [gauravgorade](https://github.com/gauravgorade)
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+ var core = require('./core');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ var FormatterContext = react.createContext(null);
9
+ function FormattersProvider({ config, children }) {
10
+ const formatters = react.useMemo(
11
+ () => core.createFormatters(config),
12
+ // eslint-disable-next-line react-hooks/exhaustive-deps
13
+ [config.locale, config.currency, config.fallback, JSON.stringify(config.rules)]
14
+ );
15
+ return /* @__PURE__ */ jsxRuntime.jsx(FormatterContext.Provider, { value: formatters, children });
16
+ }
17
+ function useFormatter() {
18
+ const ctx = react.useContext(FormatterContext);
19
+ if (!ctx) {
20
+ throw new Error(
21
+ "[next-formatter] useFormatter() must be used inside <NextFormatterProvider />.\nMake sure you have <NextFormatterProvider> in your root layout."
22
+ );
23
+ }
24
+ return ctx;
25
+ }
26
+
27
+ exports.FormattersProvider = FormattersProvider;
28
+ exports.useFormatter = useFormatter;
@@ -0,0 +1,198 @@
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;
@@ -0,0 +1,27 @@
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;
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ var client = require('./client');
4
+ var create = require('./create');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+
7
+ // src/index.tsx
8
+ async function NextFormatterProvider({ config = {}, children }) {
9
+ const locale = await create.resolveLocale(config.getLocale, config.locale);
10
+ const currency = await create.resolveCurrency(config.getCurrency, config.currency);
11
+ return /* @__PURE__ */ jsxRuntime.jsx(client.FormattersProvider, { config: { locale, currency, fallback: config.fallback, rules: config.rules }, children });
12
+ }
13
+
14
+ exports.NextFormatterProvider = NextFormatterProvider;
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ var core = require('./core');
4
+ var create = require('./create');
5
+
6
+ // src/server.ts
7
+ async function getFormatter(config = {}) {
8
+ const locale = await create.resolveLocale(config.getLocale, config.locale);
9
+ const currency = await create.resolveCurrency(config.getCurrency, config.currency);
10
+ return core.createFormatters({
11
+ locale,
12
+ currency,
13
+ fallback: config.fallback,
14
+ rules: config.rules
15
+ });
16
+ }
17
+
18
+ exports.getFormatter = getFormatter;
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { createContext, useMemo, useContext } from 'react';
3
+ import { createFormatters } from './core';
4
+ import { jsx } from 'react/jsx-runtime';
5
+
6
+ var FormatterContext = createContext(null);
7
+ function FormattersProvider({ config, children }) {
8
+ const formatters = useMemo(
9
+ () => createFormatters(config),
10
+ // eslint-disable-next-line react-hooks/exhaustive-deps
11
+ [config.locale, config.currency, config.fallback, JSON.stringify(config.rules)]
12
+ );
13
+ return /* @__PURE__ */ jsx(FormatterContext.Provider, { value: formatters, children });
14
+ }
15
+ function useFormatter() {
16
+ const ctx = useContext(FormatterContext);
17
+ if (!ctx) {
18
+ throw new Error(
19
+ "[next-formatter] useFormatter() must be used inside <NextFormatterProvider />.\nMake sure you have <NextFormatterProvider> in your root layout."
20
+ );
21
+ }
22
+ return ctx;
23
+ }
24
+
25
+ export { FormattersProvider, useFormatter };
@@ -0,0 +1,196 @@
1
+ // src/core.ts
2
+ var DEFAULT_RULES = {
3
+ compactThreshold: 1e4,
4
+ minimumFractionDigits: 0,
5
+ maximumFractionDigits: 2,
6
+ currencyDisplay: "narrowSymbol",
7
+ numberFormat: {},
8
+ dateFormat: { year: "numeric", month: "short", day: "2-digit" },
9
+ dateTimeFormat: {
10
+ year: "numeric",
11
+ month: "short",
12
+ day: "2-digit",
13
+ hour: "2-digit",
14
+ minute: "2-digit",
15
+ hour12: true
16
+ }
17
+ };
18
+ function createFormatters(config = {}) {
19
+ const locale = config.locale ?? "en-US";
20
+ const defaultCurrency = config.currency ?? "USD";
21
+ const fallback = config.fallback ?? "\u2014";
22
+ const rules = {
23
+ ...DEFAULT_RULES,
24
+ ...config.rules
25
+ };
26
+ function toNumber(value) {
27
+ if (value == null || value === "") return null;
28
+ const n = typeof value === "number" ? value : Number(value);
29
+ return Number.isNaN(n) ? null : n;
30
+ }
31
+ function toValidDate(value) {
32
+ if (value == null || value === "") return null;
33
+ const date = value instanceof Date ? value : new Date(value);
34
+ return Number.isNaN(date.getTime()) ? null : date;
35
+ }
36
+ function isLarge(n) {
37
+ return Math.abs(n) >= rules.compactThreshold;
38
+ }
39
+ function normalizeICU(str) {
40
+ return str.replace(/\u202f/g, " ").replace(/\u00a0/g, " ").trim();
41
+ }
42
+ function alignFractionDigits(opts) {
43
+ const { minimumFractionDigits, maximumFractionDigits, maximumSignificantDigits, minimumSignificantDigits } = opts;
44
+ if (minimumFractionDigits !== void 0 && maximumFractionDigits !== void 0 && minimumFractionDigits !== maximumFractionDigits && maximumSignificantDigits === void 0 && minimumSignificantDigits === void 0) {
45
+ return { ...opts, minimumFractionDigits: maximumFractionDigits };
46
+ }
47
+ return opts;
48
+ }
49
+ const numberCache = /* @__PURE__ */ new Map();
50
+ function getNumberFormatter(options) {
51
+ const key = JSON.stringify({ locale, ...options });
52
+ if (!numberCache.has(key)) {
53
+ numberCache.set(key, new Intl.NumberFormat(locale, options));
54
+ }
55
+ return numberCache.get(key);
56
+ }
57
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "long" });
58
+ const formatters = {
59
+ /**
60
+ * Format a plain number.
61
+ * @example
62
+ * fmt.number(1234567) // "1.2M"
63
+ * fmt.number(1234, { notation: "standard" }) // "1,234"
64
+ * fmt.number(1.5678, { maximumFractionDigits: 1 }) // "1.6"
65
+ */
66
+ number(value, options = {}) {
67
+ const n = toNumber(value);
68
+ if (n == null) return fallback;
69
+ return normalizeICU(
70
+ getNumberFormatter(
71
+ alignFractionDigits({
72
+ style: "decimal",
73
+ notation: isLarge(n) ? "compact" : "standard",
74
+ minimumFractionDigits: rules.minimumFractionDigits,
75
+ maximumFractionDigits: rules.maximumFractionDigits,
76
+ ...rules.numberFormat,
77
+ ...options
78
+ })
79
+ ).format(n)
80
+ );
81
+ },
82
+ /**
83
+ * Format a currency value.
84
+ * @example
85
+ * fmt.currency(49900) // "$49.9K"
86
+ * fmt.currency(1234, { currency: "EUR" }) // "€1,234"
87
+ * fmt.currency(1234, { currencyDisplay: "code" }) // "USD 1,234"
88
+ * fmt.currency(1234, { minimumFractionDigits: 2 }) // "$1,234.00"
89
+ */
90
+ currency(value, options = {}) {
91
+ const n = toNumber(value);
92
+ if (n == null) return fallback;
93
+ const { currency: overrideCurrency, currencyDisplay, ...rest } = options;
94
+ return normalizeICU(
95
+ getNumberFormatter(
96
+ alignFractionDigits({
97
+ style: "currency",
98
+ currency: overrideCurrency ?? defaultCurrency,
99
+ notation: isLarge(n) ? "compact" : "standard",
100
+ minimumFractionDigits: rules.minimumFractionDigits,
101
+ maximumFractionDigits: rules.maximumFractionDigits,
102
+ currencyDisplay: currencyDisplay ?? rules.currencyDisplay,
103
+ ...rules.numberFormat,
104
+ ...rest
105
+ })
106
+ ).format(n)
107
+ );
108
+ },
109
+ /**
110
+ * Format a percentage. Input is treated as a raw percentage (50 → "50%").
111
+ * Uses minimumFractionDigits and maximumFractionDigits from rules.
112
+ * For values that would round to zero, maximumSignificantDigits is set to
113
+ * max(maximumFractionDigits, 4) to preserve meaningful precision.
114
+ * @example
115
+ * fmt.percentage(12.5) // "12.50%" (with default rules min=0, max=2 → aligned to 2)
116
+ * fmt.percentage(0.001) // "0.001%" (wouldBeZero → uses significantDigits)
117
+ * fmt.percentage(12.5, { maximumFractionDigits: 0 }) // "13%"
118
+ */
119
+ percentage(value, options = {}) {
120
+ const n = toNumber(value);
121
+ if (n == null) return fallback;
122
+ const normalized = n / 100;
123
+ const wouldBeZero = parseFloat(normalized.toFixed(3)) === 0;
124
+ const sigDigits = Math.max(rules.maximumFractionDigits, 4);
125
+ return getNumberFormatter({
126
+ style: "percent",
127
+ ...wouldBeZero ? { maximumSignificantDigits: sigDigits } : alignFractionDigits({
128
+ minimumFractionDigits: rules.minimumFractionDigits,
129
+ maximumFractionDigits: rules.maximumFractionDigits
130
+ }),
131
+ ...options
132
+ }).format(normalized);
133
+ },
134
+ /**
135
+ * Format a duration in seconds. Pure math — no Intl, no ICU risk.
136
+ * @example
137
+ * fmt.duration(150) // "2m 30s"
138
+ * fmt.duration(45) // "45s"
139
+ */
140
+ duration(value) {
141
+ const n = toNumber(value);
142
+ if (n == null) return fallback;
143
+ const mins = Math.floor(n / 60);
144
+ const secs = n % 60;
145
+ return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
146
+ },
147
+ /**
148
+ * Format a date.
149
+ * @example
150
+ * fmt.date("2024-01-15") // "Jan 15, 2024"
151
+ * fmt.date("2024-01-15", { dateStyle: "full" }) // "Monday, January 15, 2024"
152
+ */
153
+ date(value, options = {}) {
154
+ const date = toValidDate(value);
155
+ if (!date) return fallback;
156
+ const resolvedOptions = options.dateStyle ? options : { ...rules.dateFormat, ...options };
157
+ return normalizeICU(new Intl.DateTimeFormat(locale, resolvedOptions).format(date));
158
+ },
159
+ dateTime(value, options = {}) {
160
+ const date = toValidDate(value);
161
+ if (!date) return fallback;
162
+ const resolvedOptions = options.dateStyle || options.timeStyle ? options : { ...rules.dateTimeFormat, ...options };
163
+ return normalizeICU(new Intl.DateTimeFormat(locale, resolvedOptions).format(date));
164
+ },
165
+ /**
166
+ * Format a date as relative time.
167
+ * Always pass an explicit `now` timestamp — this ensures consistent
168
+ * output whether called from a server or client component.
169
+ * @example
170
+ * const now = Date.now();
171
+ * fmt.relativeTime("2024-01-15", now) // "3 months ago"
172
+ */
173
+ relativeTime(value, now = Date.now()) {
174
+ const date = toValidDate(value);
175
+ if (!date) return fallback;
176
+ const diffSec = Math.round((date.getTime() - now) / 1e3);
177
+ const abs = Math.abs(diffSec);
178
+ if (abs < 60) return rtf.format(diffSec, "second");
179
+ const min = Math.round(diffSec / 60);
180
+ if (Math.abs(min) < 60) return rtf.format(min, "minute");
181
+ const hr = Math.round(min / 60);
182
+ if (Math.abs(hr) < 24) return rtf.format(hr, "hour");
183
+ const day = Math.round(hr / 24);
184
+ if (Math.abs(day) < 7) return rtf.format(day, "day");
185
+ const week = Math.round(day / 7);
186
+ if (Math.abs(week) < 4) return rtf.format(week, "week");
187
+ const month = Math.round(day / 30);
188
+ if (Math.abs(month) < 12) return rtf.format(month, "month");
189
+ const year = Math.round(day / 365);
190
+ return rtf.format(year, "year");
191
+ }
192
+ };
193
+ return formatters;
194
+ }
195
+
196
+ export { createFormatters };
@@ -0,0 +1,24 @@
1
+ // src/create.ts
2
+ async function resolveLocale(resolver, defaultLocale) {
3
+ const resolved = await resolver?.();
4
+ if (resolved) return resolved;
5
+ if (defaultLocale) return defaultLocale;
6
+ try {
7
+ const { headers } = await import('next/headers');
8
+ const h = await headers();
9
+ const acceptLanguage = h.get("accept-language");
10
+ if (acceptLanguage) {
11
+ const locale = acceptLanguage.split(",")[0]?.trim().split(";")[0]?.trim();
12
+ if (locale) return locale;
13
+ }
14
+ } catch {
15
+ }
16
+ return "en-US";
17
+ }
18
+ async function resolveCurrency(resolver, defaultCurrency) {
19
+ const resolved = await resolver?.();
20
+ if (resolved) return resolved;
21
+ return defaultCurrency ?? "USD";
22
+ }
23
+
24
+ export { resolveCurrency, resolveLocale };
@@ -0,0 +1,12 @@
1
+ import { FormattersProvider } from './client';
2
+ import { resolveLocale, resolveCurrency } from './create';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ // src/index.tsx
6
+ async function NextFormatterProvider({ config = {}, children }) {
7
+ const locale = await resolveLocale(config.getLocale, config.locale);
8
+ const currency = await resolveCurrency(config.getCurrency, config.currency);
9
+ return /* @__PURE__ */ jsx(FormattersProvider, { config: { locale, currency, fallback: config.fallback, rules: config.rules }, children });
10
+ }
11
+
12
+ export { NextFormatterProvider };
@@ -0,0 +1,16 @@
1
+ import { createFormatters } from './core';
2
+ import { resolveLocale, resolveCurrency } from './create';
3
+
4
+ // src/server.ts
5
+ async function getFormatter(config = {}) {
6
+ const locale = await resolveLocale(config.getLocale, config.locale);
7
+ const currency = await resolveCurrency(config.getCurrency, config.currency);
8
+ return createFormatters({
9
+ locale,
10
+ currency,
11
+ fallback: config.fallback,
12
+ rules: config.rules
13
+ });
14
+ }
15
+
16
+ export { getFormatter };
@@ -0,0 +1,50 @@
1
+ import { type PropsWithChildren } from "react";
2
+ import { type Formatter, type FormatterConfig } from "./core";
3
+ interface FormattersProviderProps {
4
+ config: FormatterConfig;
5
+ }
6
+ /**
7
+ * Low-level client provider. Accepts a pre-resolved config object with plain
8
+ * string `locale` and `currency` values — no async resolvers.
9
+ *
10
+ * Prefer `<NextFormatterProvider>` in your root layout when you need dynamic
11
+ * config resolution (e.g. locale/currency from session, cookies, or headers).
12
+ * `NextFormatterProvider` resolves async config server-side and passes the
13
+ * result to this provider internally.
14
+ *
15
+ * Use `<FormattersProvider>` directly only when you already have resolved
16
+ * values, e.g. in Storybook, tests, or non-Next.js React apps.
17
+ *
18
+ * @example
19
+ * // layout.tsx
20
+ * import { FormattersProvider } from "next-formatter/client";
21
+ *
22
+ * <FormattersProvider config={{ locale: "en-US", currency: "USD" }}>
23
+ * {children}
24
+ * </FormattersProvider>
25
+ */
26
+ export declare function FormattersProvider({ config, children }: PropsWithChildren<FormattersProviderProps>): import("react/jsx-runtime").JSX.Element;
27
+ /**
28
+ * Returns all formatters inside any Client Component.
29
+ * Must be used inside `<NextFormatterProvider>` or `<FormattersProvider>`.
30
+ *
31
+ * @example
32
+ * "use client";
33
+ * import { useFormatter } from "next-formatter/client";
34
+ *
35
+ * const fmt = useFormatter();
36
+ *
37
+ * fmt.number(9876543); // "9,876,543"
38
+ * fmt.currency(1234); // "$1,234.00"
39
+ * fmt.percentage(0.1275); // "12.75%"
40
+ * fmt.date(new Date()); // "Jan 1, 2025"
41
+ * fmt.dateTime(new Date()); // "Jan 1, 2025, 12:00 AM"
42
+ * fmt.relativeTime("2024-01-15"); // "2 years ago"
43
+ * fmt.duration(150); // "2m 30s"
44
+ *
45
+ * // or destructure
46
+ * const { currency, date, percentage } = useFormatter();
47
+ */
48
+ export declare function useFormatter(): Formatter;
49
+ export {};
50
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsC,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AACnF,OAAO,EAAoB,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAIhF,UAAU,uBAAuB;IAC/B,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,iBAAiB,CAAC,uBAAuB,CAAC,2CAQlG;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAWxC"}
@@ -0,0 +1,99 @@
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
+ export type CurrencyOptions = NumberOptions & {
5
+ currency?: string;
6
+ currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
7
+ currencySign?: "standard" | "accounting";
8
+ };
9
+ export type PercentageOptions = Pick<Intl.NumberFormatOptions, "minimumFractionDigits" | "maximumFractionDigits" | "minimumSignificantDigits" | "maximumSignificantDigits" | "signDisplay">;
10
+ export type DateOptions = Intl.DateTimeFormatOptions;
11
+ export type DateTimeOptions = Intl.DateTimeFormatOptions;
12
+ export type FormatterRules = {
13
+ /** Numbers >= this use compact notation (1.2K, 3.4M). Default: 10000 */
14
+ compactThreshold?: number;
15
+ /**
16
+ * Minimum fraction digits for number, currency and percentage formatters.
17
+ * Default: 0.
18
+ * alignFractionDigits sets this to maximumFractionDigits automatically
19
+ * to prevent ICU hydration mismatches.
20
+ */
21
+ minimumFractionDigits?: number;
22
+ /**
23
+ * Maximum fraction digits for number, currency and percentage formatters.
24
+ * Default: 2.
25
+ * For wouldBeZero percentage values, maximumSignificantDigits is set to
26
+ * max(maximumFractionDigits, 4) to preserve meaningful precision.
27
+ */
28
+ maximumFractionDigits?: number;
29
+ /** Default currency display style. Default: "narrowSymbol" */
30
+ currencyDisplay?: "symbol" | "narrowSymbol" | "code" | "name";
31
+ /** Default date format options */
32
+ dateFormat?: Intl.DateTimeFormatOptions;
33
+ /** Default dateTime format options */
34
+ dateTimeFormat?: Intl.DateTimeFormatOptions;
35
+ /** Default number format options — merged with per-call overrides */
36
+ numberFormat?: NumberOptions;
37
+ };
38
+ export type FormatterConfig = {
39
+ locale?: string;
40
+ currency?: string;
41
+ fallback?: string;
42
+ rules?: FormatterRules;
43
+ };
44
+ export declare function createFormatters(config?: FormatterConfig): {
45
+ /**
46
+ * Format a plain number.
47
+ * @example
48
+ * fmt.number(1234567) // "1.2M"
49
+ * fmt.number(1234, { notation: "standard" }) // "1,234"
50
+ * fmt.number(1.5678, { maximumFractionDigits: 1 }) // "1.6"
51
+ */
52
+ number(value: NumericInput, options?: NumberOptions): string;
53
+ /**
54
+ * Format a currency value.
55
+ * @example
56
+ * fmt.currency(49900) // "$49.9K"
57
+ * fmt.currency(1234, { currency: "EUR" }) // "€1,234"
58
+ * fmt.currency(1234, { currencyDisplay: "code" }) // "USD 1,234"
59
+ * fmt.currency(1234, { minimumFractionDigits: 2 }) // "$1,234.00"
60
+ */
61
+ currency(value: NumericInput, options?: CurrencyOptions): string;
62
+ /**
63
+ * Format a percentage. Input is treated as a raw percentage (50 → "50%").
64
+ * Uses minimumFractionDigits and maximumFractionDigits from rules.
65
+ * For values that would round to zero, maximumSignificantDigits is set to
66
+ * max(maximumFractionDigits, 4) to preserve meaningful precision.
67
+ * @example
68
+ * fmt.percentage(12.5) // "12.50%" (with default rules min=0, max=2 → aligned to 2)
69
+ * fmt.percentage(0.001) // "0.001%" (wouldBeZero → uses significantDigits)
70
+ * fmt.percentage(12.5, { maximumFractionDigits: 0 }) // "13%"
71
+ */
72
+ percentage(value: NumericInput, options?: PercentageOptions): string;
73
+ /**
74
+ * Format a duration in seconds. Pure math — no Intl, no ICU risk.
75
+ * @example
76
+ * fmt.duration(150) // "2m 30s"
77
+ * fmt.duration(45) // "45s"
78
+ */
79
+ duration(value: NumericInput): string;
80
+ /**
81
+ * Format a date.
82
+ * @example
83
+ * fmt.date("2024-01-15") // "Jan 15, 2024"
84
+ * fmt.date("2024-01-15", { dateStyle: "full" }) // "Monday, January 15, 2024"
85
+ */
86
+ date(value: DateInput, options?: DateOptions): string;
87
+ dateTime(value: DateInput, options?: DateTimeOptions): string;
88
+ /**
89
+ * Format a date as relative time.
90
+ * Always pass an explicit `now` timestamp — this ensures consistent
91
+ * output whether called from a server or client component.
92
+ * @example
93
+ * const now = Date.now();
94
+ * fmt.relativeTime("2024-01-15", now) // "3 months ago"
95
+ */
96
+ relativeTime(value: DateInput, now?: number): string;
97
+ };
98
+ export type Formatter = ReturnType<typeof createFormatters>;
99
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;AAC9D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;AAIlE,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,IAAI,CAAC,mBAAmB,EACtB,UAAU,GACV,uBAAuB,GACvB,uBAAuB,GACvB,0BAA0B,GAC1B,0BAA0B,GAC1B,sBAAsB,GACtB,aAAa,GACb,aAAa,CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,aAAa,GAAG;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;IAC9D,YAAY,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,IAAI,CAAC,mBAAmB,EACxB,uBAAuB,GAAG,uBAAuB,GAAG,0BAA0B,GAAG,0BAA0B,GAAG,aAAa,CAC5H,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,qBAAqB,CAAC;AACrD,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,qBAAqB,CAAC;AAIzD,MAAM,MAAM,cAAc,GAAG;IAC3B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;IAC9D,kCAAkC;IAClC,UAAU,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAC;IACxC,sCAAsC;IACtC,cAAc,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAC;IAC5C,qEAAqE;IACrE,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB,CAAC;AAuBF,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,eAAoB;IA4EzD;;;;;;OAMG;kBACW,YAAY,YAAW,aAAa,GAAQ,MAAM;IAkBhE;;;;;;;OAOG;oBACa,YAAY,YAAW,eAAe,GAAQ,MAAM;IAsBpE;;;;;;;;;OASG;sBACe,YAAY,YAAW,iBAAiB,GAAQ,MAAM;IAuBxE;;;;;OAKG;oBACa,YAAY,GAAG,MAAM;IAQrC;;;;;OAKG;gBACS,SAAS,YAAW,WAAW,GAAQ,MAAM;oBASzC,SAAS,YAAW,eAAe,GAAQ,MAAM;IAQjE;;;;;;;OAOG;wBACiB,SAAS,QAAO,MAAM,GAAgB,MAAM;EAsBnE;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { type FormatterConfig, type Formatter, type FormatterRules } from "./core";
2
+ export type { FormatterConfig, Formatter };
3
+ export type NextFormatterConfig = {
4
+ /**
5
+ * Default currency.
6
+ * @default "USD"
7
+ */
8
+ currency?: string;
9
+ /**
10
+ * Default locale. Falls back to accept-language header if not set.
11
+ */
12
+ locale?: string;
13
+ /**
14
+ * Shown for null, undefined, or invalid values.
15
+ * @default "—"
16
+ */
17
+ fallback?: string;
18
+ /**
19
+ * Global formatting rules — applied to every format call.
20
+ * Can be overridden per format call.
21
+ */
22
+ rules?: FormatterRules;
23
+ /**
24
+ * Resolve locale dynamically per request.
25
+ * Return undefined to fall back to header detection.
26
+ * @example
27
+ * getLocale: async () => {
28
+ * const session = await auth();
29
+ * return session?.user?.locale;
30
+ * }
31
+ */
32
+ getLocale?: () => string | undefined | Promise<string | undefined>;
33
+ /**
34
+ * Resolve currency dynamically per request.
35
+ * Return undefined to fall back to config default.
36
+ * @example
37
+ * getCurrency: async () => {
38
+ * const session = await auth();
39
+ * return session?.user?.currency;
40
+ * }
41
+ */
42
+ getCurrency?: () => string | undefined | Promise<string | undefined>;
43
+ };
44
+ export declare function resolveLocale(resolver: (() => string | undefined | Promise<string | undefined>) | undefined, defaultLocale: string | undefined): Promise<string>;
45
+ export declare function resolveCurrency(resolver: (() => string | undefined | Promise<string | undefined>) | undefined, defaultCurrency: string | undefined): Promise<string>;
46
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/create.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,QAAQ,CAAC;AAEnF,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IAEvB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAEnE;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACtE,CAAC;AAIF,wBAAsB,aAAa,CACjC,QAAQ,EAAE,CAAC,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,EAC9E,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,CAAC,MAAM,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,EAC9E,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,OAAO,CAAC,MAAM,CAAC,CAIjB"}
@@ -0,0 +1,60 @@
1
+ import { type PropsWithChildren } from "react";
2
+ import { type NextFormatterConfig } from "./create";
3
+ /**
4
+ * Root provider for Next.js App Router. Place in `app/layout.tsx`.
5
+ *
6
+ * Async Server Component — resolves locale and currency server-side per request,
7
+ * then passes the resolved config to the client-side context. Zero client bundle cost.
8
+ *
9
+ * Resolution order for locale:
10
+ * 1. `getLocale()` resolver (session, db, cookie)
11
+ * 2. `locale` static value
12
+ * 3. `accept-language` request header
13
+ * 4. `"en-US"` default
14
+ *
15
+ * Resolution order for currency:
16
+ * 1. `getCurrency()` resolver (session, db, cookie)
17
+ * 2. `currency` static value
18
+ * 3. `"USD"` default
19
+ *
20
+ * @example
21
+ * // Zero config — locale auto-detected from accept-language header
22
+ * <NextFormatterProvider>{children}</NextFormatterProvider>
23
+ *
24
+ * @example
25
+ * // Static locale and currency
26
+ * <NextFormatterProvider config={{ locale: "de-DE", currency: "EUR" }}>
27
+ * {children}
28
+ * </NextFormatterProvider>
29
+ *
30
+ * @example
31
+ * // Dynamic locale and currency from session
32
+ * <NextFormatterProvider
33
+ * config={{
34
+ * getLocale: async () => (await auth())?.user?.locale,
35
+ * getCurrency: async () => (await auth())?.user?.currency,
36
+ * fallback: "N/A",
37
+ * rules: {
38
+ * compactThreshold: 50_000,
39
+ * currencyDisplay: "symbol",
40
+ * dateFormat: { year: "numeric", month: "long", day: "2-digit" },
41
+ * },
42
+ * }}
43
+ * >
44
+ * {children}
45
+ * </NextFormatterProvider>
46
+ *
47
+ * @example
48
+ * // Recommended — define config once in lib/formatter.ts, import everywhere
49
+ * import { formatterConfig } from "@/lib/formatter";
50
+ *
51
+ * <NextFormatterProvider config={formatterConfig}>
52
+ * {children}
53
+ * </NextFormatterProvider>
54
+ */
55
+ export declare function NextFormatterProvider({ config, children }: PropsWithChildren<{
56
+ config?: NextFormatterConfig;
57
+ }>): Promise<import("react/jsx-runtime").JSX.Element>;
58
+ export type { FormatterConfig, Formatter, FormatterRules, NumericInput, DateInput } from "./core";
59
+ export type { NextFormatterConfig } from "./create";
60
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAkC,KAAK,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAsB,qBAAqB,CAAC,EAAE,MAAW,EAAE,QAAQ,EAAE,EAAE,iBAAiB,CAAC;IAAE,MAAM,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAAC,oDAKzH;AAED,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAClG,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { type Formatter } from "./core";
2
+ import { type NextFormatterConfig } from "./create";
3
+ /**
4
+ * Use in Server Components or Server Actions.
5
+ * Locale is auto-detected from request headers — pass config only to override.
6
+ *
7
+ * @example
8
+ * // Zero config
9
+ * const fmt = await getFormatter();
10
+ *
11
+ * @example
12
+ * // With overrides
13
+ * const fmt = await getFormatter({ currency: "EUR" });
14
+ *
15
+ * @example
16
+ * // Dynamic currency from session
17
+ * const fmt = await getFormatter({
18
+ * getCurrency: async () => (await auth())?.user?.currency,
19
+ * });
20
+ */
21
+ export declare function getFormatter(config?: NextFormatterConfig): Promise<Formatter>;
22
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAkC,KAAK,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAEpF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,YAAY,CAAC,MAAM,GAAE,mBAAwB,GAAG,OAAO,CAAC,SAAS,CAAC,CAUvF"}
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "next-formatter",
3
+ "version": "2.0.0",
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
+ "author": "gauravgorade",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "next.js",
9
+ "nextjs",
10
+ "next-intl",
11
+ "react-intl",
12
+ "formatter",
13
+ "intl",
14
+ "internationalization",
15
+ "i18n",
16
+ "currency",
17
+ "currency-formatter",
18
+ "number-format",
19
+ "number-formatter",
20
+ "date-format",
21
+ "date-formatter",
22
+ "relative-time",
23
+ "percentage",
24
+ "duration",
25
+ "server-components",
26
+ "client-components",
27
+ "app-router",
28
+ "react",
29
+ "typescript",
30
+ "hydration-safe",
31
+ "icu",
32
+ "intl-numberformat",
33
+ "intl-datetimeformat",
34
+ "universal-formatter",
35
+ "next-formatter",
36
+ "compact-notation",
37
+ "locale",
38
+ "react-server-components",
39
+ "rsc"
40
+ ],
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/gauravgorade/next-formatter.git"
44
+ },
45
+ "homepage": "https://github.com/gauravgorade/next-formatter#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/gauravgorade/next-formatter/issues"
48
+ },
49
+ "sideEffects": [
50
+ "./dist/esm/client.mjs",
51
+ "./dist/cjs/client.js"
52
+ ],
53
+ "main": "./dist/cjs/index.js",
54
+ "module": "./dist/esm/index.mjs",
55
+ "types": "./dist/types/index.d.ts",
56
+ "exports": {
57
+ ".": {
58
+ "types": "./dist/types/index.d.ts",
59
+ "import": "./dist/esm/index.mjs",
60
+ "require": "./dist/cjs/index.js"
61
+ },
62
+ "./server": {
63
+ "types": "./dist/types/server.d.ts",
64
+ "import": "./dist/esm/server.mjs",
65
+ "require": "./dist/cjs/server.js"
66
+ },
67
+ "./client": {
68
+ "types": "./dist/types/client.d.ts",
69
+ "import": "./dist/esm/client.mjs",
70
+ "require": "./dist/cjs/client.js"
71
+ }
72
+ },
73
+ "files": [
74
+ "dist"
75
+ ],
76
+ "scripts": {
77
+ "build": "node --eval \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsup && node scripts/add-use-client.mjs && tsc",
78
+ "dev": "tsup --watch",
79
+ "test": "vitest run",
80
+ "test:watch": "vitest",
81
+ "prepublishOnly": "npm test && npm run build"
82
+ },
83
+ "devDependencies": {
84
+ "@testing-library/react": "^16.0.0",
85
+ "@types/react": "^19.0.0",
86
+ "jsdom": "^25.0.0",
87
+ "next": "^15.0.0",
88
+ "react": "^19.0.0",
89
+ "tsup": "^8.0.0",
90
+ "typescript": "^5.0.0",
91
+ "vitest": "^3.0.0"
92
+ },
93
+ "peerDependencies": {
94
+ "next": ">=14.0.0",
95
+ "react": ">=18.3.0",
96
+ "@types/react": ">=18.3.0"
97
+ },
98
+ "peerDependenciesMeta": {
99
+ "next": {
100
+ "optional": true
101
+ },
102
+ "@types/react": {
103
+ "optional": true
104
+ }
105
+ }
106
+ }