@lightsparkdev/core 1.0.5 → 1.0.6

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.
@@ -1,32 +1,14 @@
1
1
  // Copyright ©, 2023-present, Lightspark Group, Inc. - All Rights Reserved
2
2
 
3
3
  import LightsparkException from "../LightsparkException.js";
4
+ import { getCurrentLocale } from "./locale.js";
5
+ import { localeToCurrencyCode } from "./localeToCurrencyCodes.js";
6
+ import { isNumber, round } from "./numbers.js";
4
7
 
5
- /** Represents the value and unit for an amount of currency. **/
6
- type CurrencyAmount = {
7
- /** The original numeric value for this CurrencyAmount. **/
8
- originalValue: number;
9
-
10
- /** The original unit of currency for this CurrencyAmount. **/
11
- originalUnit: CurrencyUnit;
12
-
13
- /** The unit of user's preferred currency. **/
14
- preferredCurrencyUnit: CurrencyUnit;
15
-
16
- /**
17
- * The rounded numeric value for this CurrencyAmount in the very base level of user's preferred
18
- * currency. For example, for USD, the value will be in cents.
19
- **/
20
- preferredCurrencyValueRounded: number;
21
-
22
- /**
23
- * The approximate float value for this CurrencyAmount in the very base level of user's preferred
24
- * currency. For example, for USD, the value will be in cents.
25
- **/
26
- preferredCurrencyValueApprox: number;
27
- };
8
+ export const defaultCurrencyCode = "USD";
28
9
 
29
- enum CurrencyUnit {
10
+ /** This enum identifies the unit of currency associated with a CurrencyAmount. **/
11
+ export enum CurrencyUnit {
30
12
  /**
31
13
  * This is an enum value that represents values that could be added in the future.
32
14
  * Clients should support unknown values as more of them could be added without notice.
@@ -48,6 +30,26 @@ enum CurrencyUnit {
48
30
  MILLIBITCOIN = "MILLIBITCOIN",
49
31
  }
50
32
 
33
+ /** This object represents the value and unit for an amount of currency. **/
34
+ export type CurrencyAmountType = {
35
+ /** The original numeric value for this CurrencyAmount. **/
36
+ originalValue: number;
37
+ /** The original unit of currency for this CurrencyAmount. **/
38
+ originalUnit: CurrencyUnit;
39
+ /** The unit of user's preferred currency. **/
40
+ preferredCurrencyUnit: CurrencyUnit;
41
+ /**
42
+ * The rounded numeric value for this CurrencyAmount in the very base level of user's preferred
43
+ * currency. For example, for USD, the value will be in cents.
44
+ **/
45
+ preferredCurrencyValueRounded: number;
46
+ /**
47
+ * The approximate float value for this CurrencyAmount in the very base level of user's preferred
48
+ * currency. For example, for USD, the value will be in cents.
49
+ **/
50
+ preferredCurrencyValueApprox: number;
51
+ };
52
+
51
53
  const CONVERSION_MAP = {
52
54
  [CurrencyUnit.BITCOIN]: {
53
55
  [CurrencyUnit.BITCOIN]: (v: number) => v,
@@ -56,73 +58,414 @@ const CONVERSION_MAP = {
56
58
  [CurrencyUnit.MILLISATOSHI]: (v: number) => v * 100_000_000_000,
57
59
  [CurrencyUnit.NANOBITCOIN]: (v: number) => v * 1_000_000_000,
58
60
  [CurrencyUnit.SATOSHI]: (v: number) => v * 100_000_000,
61
+ [CurrencyUnit.USD]: (v: number, centsPerBtc = 1) =>
62
+ /* Round without decimals since we're returning cents: */
63
+ round(v * centsPerBtc, 2),
59
64
  },
60
65
  [CurrencyUnit.MICROBITCOIN]: {
61
- [CurrencyUnit.BITCOIN]: (v: number) => Math.round(v / 1_000_000),
66
+ [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000,
62
67
  [CurrencyUnit.MICROBITCOIN]: (v: number) => v,
63
- [CurrencyUnit.MILLIBITCOIN]: (v: number) => Math.round(v / 1000),
68
+ [CurrencyUnit.MILLIBITCOIN]: (v: number) => v / 1000,
64
69
  [CurrencyUnit.MILLISATOSHI]: (v: number) => v * 100_000,
65
70
  [CurrencyUnit.NANOBITCOIN]: (v: number) => v * 1000,
66
71
  [CurrencyUnit.SATOSHI]: (v: number) => v * 100,
72
+ [CurrencyUnit.USD]: (v: number, centsPerBtc = 1) =>
73
+ /* Round without decimals since we're returning cents: */
74
+ round((v / 1_000_000) * centsPerBtc),
67
75
  },
68
76
  [CurrencyUnit.MILLIBITCOIN]: {
69
- [CurrencyUnit.BITCOIN]: (v: number) => Math.round(v / 1_000),
77
+ [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000,
70
78
  [CurrencyUnit.MICROBITCOIN]: (v: number) => v * 1000,
71
79
  [CurrencyUnit.MILLIBITCOIN]: (v: number) => v,
72
80
  [CurrencyUnit.MILLISATOSHI]: (v: number) => v * 100_000_000,
73
81
  [CurrencyUnit.NANOBITCOIN]: (v: number) => v * 1_000_000,
74
82
  [CurrencyUnit.SATOSHI]: (v: number) => v * 100_000,
83
+ [CurrencyUnit.USD]: (v: number, centsPerBtc = 1) =>
84
+ /* Round without decimals since we're returning cents: */
85
+ round((v / 1_000) * centsPerBtc),
75
86
  },
76
87
  [CurrencyUnit.MILLISATOSHI]: {
77
- [CurrencyUnit.BITCOIN]: (v: number) => Math.round(v / 100_000_000_000),
78
- [CurrencyUnit.MICROBITCOIN]: (v: number) => Math.round(v / 100_000),
79
- [CurrencyUnit.MILLIBITCOIN]: (v: number) => Math.round(v / 100_000_000),
88
+ [CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000_000,
89
+ [CurrencyUnit.MICROBITCOIN]: (v: number) => v / 100_000,
90
+ [CurrencyUnit.MILLIBITCOIN]: (v: number) => v / 100_000_000,
80
91
  [CurrencyUnit.MILLISATOSHI]: (v: number) => v,
81
- [CurrencyUnit.NANOBITCOIN]: (v: number) => Math.round(v / 100),
82
- [CurrencyUnit.SATOSHI]: (v: number) => Math.round(v / 1000),
92
+ [CurrencyUnit.NANOBITCOIN]: (v: number) => v / 100,
93
+ [CurrencyUnit.SATOSHI]: (v: number) => v / 1000,
94
+ [CurrencyUnit.USD]: (v: number, centsPerBtc = 1) =>
95
+ /* Round without decimals since we're returning cents: */
96
+ round((v / 100_000_000_000) * centsPerBtc),
83
97
  },
84
98
  [CurrencyUnit.NANOBITCOIN]: {
85
- [CurrencyUnit.BITCOIN]: (v: number) => Math.round(v / 1_000_000_000),
86
- [CurrencyUnit.MICROBITCOIN]: (v: number) => Math.round(v / 1000),
87
- [CurrencyUnit.MILLIBITCOIN]: (v: number) => Math.round(v / 1_000_000),
99
+ [CurrencyUnit.BITCOIN]: (v: number) => v / 1_000_000_000,
100
+ [CurrencyUnit.MICROBITCOIN]: (v: number) => v / 1000,
101
+ [CurrencyUnit.MILLIBITCOIN]: (v: number) => v / 1_000_000,
88
102
  [CurrencyUnit.MILLISATOSHI]: (v: number) => v * 100,
89
103
  [CurrencyUnit.NANOBITCOIN]: (v: number) => v,
90
- [CurrencyUnit.SATOSHI]: (v: number) => Math.round(v / 10),
104
+ [CurrencyUnit.SATOSHI]: (v: number) => v / 10,
105
+ [CurrencyUnit.USD]: (v: number, centsPerBtc = 1) =>
106
+ /* Round without decimals since we're returning cents: */
107
+ round((v / 1_000_000_000) * centsPerBtc),
91
108
  },
92
109
  [CurrencyUnit.SATOSHI]: {
93
- [CurrencyUnit.BITCOIN]: (v: number) => Math.round(v / 100_000_000),
94
- [CurrencyUnit.MICROBITCOIN]: (v: number) => Math.round(v / 100),
95
- [CurrencyUnit.MILLIBITCOIN]: (v: number) => Math.round(v / 100_000),
110
+ [CurrencyUnit.BITCOIN]: (v: number) => v / 100_000_000,
111
+ [CurrencyUnit.MICROBITCOIN]: (v: number) => v / 100,
112
+ [CurrencyUnit.MILLIBITCOIN]: (v: number) => v / 100_000,
96
113
  [CurrencyUnit.MILLISATOSHI]: (v: number) => v * 1000,
97
114
  [CurrencyUnit.NANOBITCOIN]: (v: number) => v * 10,
98
115
  [CurrencyUnit.SATOSHI]: (v: number) => v,
116
+ [CurrencyUnit.USD]: (v: number, centsPerBtc = 1) =>
117
+ /* Round without decimals since we're returning cents: */
118
+ round((v / 100_000_000) * centsPerBtc),
119
+ },
120
+ [CurrencyUnit.USD]: {
121
+ [CurrencyUnit.BITCOIN]: (v: number, centsPerBtc = 1) => v / centsPerBtc,
122
+ [CurrencyUnit.MICROBITCOIN]: (v: number, centsPerBtc = 1) =>
123
+ (v / centsPerBtc) * 1_000_000,
124
+ [CurrencyUnit.MILLIBITCOIN]: (v: number, centsPerBtc = 1) =>
125
+ (v / centsPerBtc) * 1_000,
126
+ [CurrencyUnit.MILLISATOSHI]: (v: number, centsPerBtc = 1) =>
127
+ (v / centsPerBtc) * 100_000_000_000,
128
+ [CurrencyUnit.NANOBITCOIN]: (v: number, centsPerBtc = 1) =>
129
+ (v / centsPerBtc) * 1_000_000_000,
130
+ [CurrencyUnit.SATOSHI]: (v: number, centsPerBtc = 1) =>
131
+ (v / centsPerBtc) * 100_000_000,
132
+ [CurrencyUnit.USD]: (v: number) => v,
99
133
  },
100
134
  };
101
135
 
102
- export const convertCurrencyAmount = (
103
- from: CurrencyAmount,
136
+ export function convertCurrencyAmountValue(
137
+ fromUnit: CurrencyUnit,
104
138
  toUnit: CurrencyUnit,
105
- ): CurrencyAmount => {
139
+ amount: number,
140
+ centsPerBtc = 1,
141
+ ): number {
106
142
  if (
107
- from.originalUnit === CurrencyUnit.FUTURE_VALUE ||
108
- from.originalUnit === CurrencyUnit.USD ||
109
- toUnit === CurrencyUnit.FUTURE_VALUE ||
110
- toUnit === CurrencyUnit.USD
143
+ fromUnit === CurrencyUnit.FUTURE_VALUE ||
144
+ toUnit === CurrencyUnit.FUTURE_VALUE
111
145
  ) {
112
146
  throw new LightsparkException("CurrencyError", `Unsupported CurrencyUnit.`);
113
147
  }
114
148
 
115
- const conversionFn = CONVERSION_MAP[from.originalUnit][toUnit];
149
+ if (fromUnit === toUnit) {
150
+ return amount;
151
+ }
152
+
153
+ const conversionFn = CONVERSION_MAP[fromUnit][toUnit];
116
154
  if (!conversionFn) {
117
155
  throw new LightsparkException(
118
156
  "CurrencyError",
119
- `Cannot convert from ${from.originalUnit} to ${toUnit}`,
157
+ `Cannot convert from ${fromUnit} to ${toUnit}`,
120
158
  );
121
159
  }
160
+
161
+ return conversionFn(amount, centsPerBtc);
162
+ }
163
+
164
+ export const convertCurrencyAmount = (
165
+ from: CurrencyAmountType,
166
+ toUnit: CurrencyUnit,
167
+ ): CurrencyAmountType => {
168
+ const value = convertCurrencyAmountValue(
169
+ from.originalUnit,
170
+ toUnit,
171
+ from.originalValue,
172
+ );
122
173
  return {
123
174
  ...from,
124
175
  preferredCurrencyUnit: toUnit,
125
- preferredCurrencyValueApprox: conversionFn(from.originalValue),
126
- preferredCurrencyValueRounded: conversionFn(from.originalValue),
176
+ preferredCurrencyValueApprox: value,
177
+ preferredCurrencyValueRounded: value,
178
+ };
179
+ };
180
+
181
+ export type CurrencyMap = {
182
+ sats: number;
183
+ msats: number;
184
+ btc: number;
185
+ [CurrencyUnit.BITCOIN]: number;
186
+ [CurrencyUnit.SATOSHI]: number;
187
+ [CurrencyUnit.MILLISATOSHI]: number;
188
+ [CurrencyUnit.MICROBITCOIN]: number;
189
+ [CurrencyUnit.MILLIBITCOIN]: number;
190
+ [CurrencyUnit.NANOBITCOIN]: number;
191
+ [CurrencyUnit.USD]: number;
192
+ [CurrencyUnit.FUTURE_VALUE]: number;
193
+ formatted: {
194
+ sats: string;
195
+ msats: string;
196
+ btc: string;
197
+ [CurrencyUnit.BITCOIN]: string;
198
+ [CurrencyUnit.SATOSHI]: string;
199
+ [CurrencyUnit.MILLISATOSHI]: string;
200
+ [CurrencyUnit.MILLIBITCOIN]: string;
201
+ [CurrencyUnit.MICROBITCOIN]: string;
202
+ [CurrencyUnit.NANOBITCOIN]: string;
203
+ [CurrencyUnit.USD]: string;
204
+ [CurrencyUnit.FUTURE_VALUE]: string;
205
+ };
206
+ isZero: boolean;
207
+ isLessThan: (other: CurrencyMap | CurrencyAmountObj | number) => boolean;
208
+ isGreaterThan: (other: CurrencyMap | CurrencyAmountObj | number) => boolean;
209
+ isEqualTo: (other: CurrencyMap | CurrencyAmountObj | number) => boolean;
210
+ type: "CurrencyMap";
211
+ };
212
+
213
+ export type CurrencyAmountObj = {
214
+ /* Technically the generated graphql schema has value as `any` but it's always a number.
215
+ We are intentionally widening the type here to allow for more forgiving input: */
216
+ value?: number | string | null;
217
+ /* assume satoshi if not provided */
218
+ unit?: CurrencyUnit;
219
+ __typename?: "CurrencyAmount";
220
+ };
221
+
222
+ export type CurrencyAmountArg =
223
+ | CurrencyAmountObj
224
+ | CurrencyAmountType
225
+ | undefined
226
+ | null;
227
+
228
+ export function isCurrencyAmountObj(arg: unknown): arg is CurrencyAmountObj {
229
+ return (
230
+ typeof arg === "object" && arg !== null && "value" in arg && "unit" in arg
231
+ );
232
+ }
233
+
234
+ export function isCurrencyAmount(arg: unknown): arg is CurrencyAmountType {
235
+ return (
236
+ typeof arg === "object" &&
237
+ arg !== null &&
238
+ "originalValue" in arg &&
239
+ "originalUnit" in arg &&
240
+ "preferredCurrencyUnit" in arg &&
241
+ "preferredCurrencyValueRounded" in arg &&
242
+ "preferredCurrencyValueApprox" in arg
243
+ );
244
+ }
245
+
246
+ function asNumber(value: string | number | null | undefined) {
247
+ if (typeof value === "string") {
248
+ return Number(value);
249
+ }
250
+ return value || 0;
251
+ }
252
+
253
+ function getCurrencyAmount(currencyAmountArg: CurrencyAmountArg) {
254
+ let value = 0;
255
+ let unit = undefined;
256
+
257
+ if (isCurrencyAmountObj(currencyAmountArg)) {
258
+ value = asNumber(currencyAmountArg.value);
259
+ unit = currencyAmountArg.unit;
260
+ } else if (isCurrencyAmount(currencyAmountArg)) {
261
+ value = currencyAmountArg.originalValue;
262
+ unit = currencyAmountArg.originalUnit;
263
+ }
264
+
265
+ return {
266
+ value: asNumber(value),
267
+ unit: unit || CurrencyUnit.SATOSHI,
268
+ };
269
+ }
270
+
271
+ export function mapCurrencyAmount(
272
+ currencyAmountArg: CurrencyAmountArg,
273
+ centsPerBtc = 1,
274
+ ): CurrencyMap {
275
+ const { value, unit } = getCurrencyAmount(currencyAmountArg);
276
+
277
+ const convert = convertCurrencyAmountValue;
278
+ const sats = convert(unit, CurrencyUnit.SATOSHI, value, centsPerBtc);
279
+ const btc = convert(unit, CurrencyUnit.BITCOIN, value, centsPerBtc);
280
+ const msats = convert(unit, CurrencyUnit.MILLISATOSHI, value, centsPerBtc);
281
+ const usd = convert(unit, CurrencyUnit.USD, value, centsPerBtc);
282
+ const mibtc = convert(unit, CurrencyUnit.MICROBITCOIN, value, centsPerBtc);
283
+ const mlbtc = convert(unit, CurrencyUnit.MILLIBITCOIN, value, centsPerBtc);
284
+ const nbtc = convert(unit, CurrencyUnit.NANOBITCOIN, value, centsPerBtc);
285
+
286
+ const mapWithCurrencyUnits = {
287
+ [CurrencyUnit.BITCOIN]: btc,
288
+ [CurrencyUnit.SATOSHI]: sats,
289
+ [CurrencyUnit.MILLISATOSHI]: msats,
290
+ [CurrencyUnit.USD]: usd,
291
+ [CurrencyUnit.MICROBITCOIN]: mibtc,
292
+ [CurrencyUnit.MILLIBITCOIN]: mlbtc,
293
+ [CurrencyUnit.NANOBITCOIN]: nbtc,
294
+ [CurrencyUnit.FUTURE_VALUE]: NaN,
295
+ formatted: {
296
+ [CurrencyUnit.BITCOIN]: formatCurrencyStr({
297
+ value: btc,
298
+ unit: CurrencyUnit.BITCOIN,
299
+ }),
300
+ [CurrencyUnit.SATOSHI]: formatCurrencyStr({
301
+ value: sats,
302
+ unit: CurrencyUnit.SATOSHI,
303
+ }),
304
+ [CurrencyUnit.MILLISATOSHI]: formatCurrencyStr({
305
+ value: msats,
306
+ unit: CurrencyUnit.MILLISATOSHI,
307
+ }),
308
+ [CurrencyUnit.MICROBITCOIN]: formatCurrencyStr({
309
+ value: mibtc,
310
+ unit: CurrencyUnit.MICROBITCOIN,
311
+ }),
312
+ [CurrencyUnit.MILLIBITCOIN]: formatCurrencyStr({
313
+ value: mlbtc,
314
+ unit: CurrencyUnit.MILLIBITCOIN,
315
+ }),
316
+ [CurrencyUnit.NANOBITCOIN]: formatCurrencyStr({
317
+ value: nbtc,
318
+ unit: CurrencyUnit.NANOBITCOIN,
319
+ }),
320
+ [CurrencyUnit.USD]: formatCurrencyStr({
321
+ value: usd,
322
+ unit: CurrencyUnit.USD,
323
+ }),
324
+ [CurrencyUnit.FUTURE_VALUE]: "-",
325
+ },
326
+ };
327
+
328
+ return {
329
+ ...mapWithCurrencyUnits,
330
+ btc,
331
+ sats,
332
+ msats,
333
+ isZero: msats === 0,
334
+ isLessThan: (other: CurrencyMap | CurrencyAmountObj | number) => {
335
+ if (isNumber(other)) {
336
+ return msats < other;
337
+ }
338
+ if (isCurrencyAmountObj(other)) {
339
+ other = mapCurrencyAmount(other);
340
+ }
341
+ return msats < other.msats;
342
+ },
343
+ isGreaterThan: (other: CurrencyMap | CurrencyAmountObj | number) => {
344
+ if (isNumber(other)) {
345
+ return msats > other;
346
+ }
347
+ if (isCurrencyAmountObj(other)) {
348
+ other = mapCurrencyAmount(other);
349
+ }
350
+ return msats > other.msats;
351
+ },
352
+ isEqualTo: (other: CurrencyMap | CurrencyAmountObj | number) => {
353
+ if (isNumber(other)) {
354
+ return msats === other;
355
+ }
356
+ if (isCurrencyAmountObj(other)) {
357
+ other = mapCurrencyAmount(other);
358
+ }
359
+ return msats === other.msats;
360
+ },
361
+ formatted: {
362
+ ...mapWithCurrencyUnits.formatted,
363
+ btc: mapWithCurrencyUnits.formatted[CurrencyUnit.BITCOIN],
364
+ sats: mapWithCurrencyUnits.formatted[CurrencyUnit.SATOSHI],
365
+ msats: mapWithCurrencyUnits.formatted[CurrencyUnit.MILLISATOSHI],
366
+ },
367
+ type: "CurrencyMap" as const,
127
368
  };
369
+ }
370
+
371
+ export const isCurrencyMap = (
372
+ currencyMap: unknown,
373
+ ): currencyMap is CurrencyMap =>
374
+ typeof currencyMap === "object" &&
375
+ currencyMap !== null &&
376
+ "type" in currencyMap &&
377
+ typeof currencyMap.type === "string" &&
378
+ currencyMap.type === "CurrencyMap";
379
+
380
+ export const abbrCurrencyUnit = (unit: CurrencyUnit) => {
381
+ switch (unit) {
382
+ case CurrencyUnit.BITCOIN:
383
+ return "BTC";
384
+ case CurrencyUnit.SATOSHI:
385
+ return "SAT";
386
+ case CurrencyUnit.MILLISATOSHI:
387
+ return "MSAT";
388
+ case CurrencyUnit.USD:
389
+ return "USD";
390
+ }
391
+ return "Unsupported CurrencyUnit";
128
392
  };
393
+
394
+ export function formatCurrencyStr(
395
+ amount: CurrencyAmountArg,
396
+ maxFractionDigits?: number,
397
+ compact?: boolean,
398
+ showBtcSymbol = false,
399
+ options: Intl.NumberFormatOptions = {},
400
+ ) {
401
+ const currencyAmount = getCurrencyAmount(amount);
402
+ let { value: num } = currencyAmount;
403
+ const { unit } = currencyAmount;
404
+
405
+ /* Currencies should always be represented in the smallest unit, e.g. cents for USD: */
406
+ if (unit === CurrencyUnit.USD) {
407
+ num = num / 100;
408
+ }
409
+
410
+ function getDefaultMaxFractionDigits(defaultDigits: number) {
411
+ return typeof maxFractionDigits === "undefined"
412
+ ? compact
413
+ ? 1
414
+ : defaultDigits
415
+ : maxFractionDigits;
416
+ }
417
+
418
+ // Symbol handled by toLocaleString for USD. These rely on the LightsparkIcons font
419
+ const symbol = !showBtcSymbol
420
+ ? ""
421
+ : unit === CurrencyUnit.BITCOIN
422
+ ? ""
423
+ : unit === CurrencyUnit.SATOSHI
424
+ ? ""
425
+ : "";
426
+
427
+ const currentLocale = getCurrentLocale();
428
+
429
+ switch (unit) {
430
+ case CurrencyUnit.BITCOIN:
431
+ return `${symbol}${num.toLocaleString(currentLocale, {
432
+ notation: compact ? ("compact" as const) : undefined,
433
+ maximumFractionDigits: getDefaultMaxFractionDigits(4),
434
+ ...options,
435
+ })}`;
436
+ case CurrencyUnit.MILLISATOSHI:
437
+ case CurrencyUnit.SATOSHI:
438
+ case CurrencyUnit.MICROBITCOIN:
439
+ case CurrencyUnit.MILLIBITCOIN:
440
+ case CurrencyUnit.NANOBITCOIN:
441
+ default:
442
+ return `${symbol}${num.toLocaleString(currentLocale, {
443
+ notation: compact ? ("compact" as const) : undefined,
444
+ maximumFractionDigits: getDefaultMaxFractionDigits(0),
445
+ ...options,
446
+ })}`;
447
+ case CurrencyUnit.USD:
448
+ return num.toLocaleString(currentLocale, {
449
+ style: "currency",
450
+ currency: defaultCurrencyCode,
451
+ notation: compact ? ("compact" as const) : undefined,
452
+ maximumFractionDigits: getDefaultMaxFractionDigits(2),
453
+ ...options,
454
+ });
455
+ }
456
+ }
457
+
458
+ export function localeToCurrencySymbol(locale: string) {
459
+ const currencyCode = localeToCurrencyCode(locale);
460
+ const formatted = new Intl.NumberFormat(locale, {
461
+ style: "currency",
462
+ currency: currencyCode,
463
+ useGrouping: false, // to avoid thousands separators
464
+ minimumFractionDigits: 0,
465
+ maximumFractionDigits: 0,
466
+ }).format(0);
467
+
468
+ // Remove numeric and non-breaking space characters to extract the currency symbol
469
+ const symbol = formatted.replace(/[0-9\s\u00a0]/g, "");
470
+ return symbol;
471
+ }
@@ -6,6 +6,9 @@ export * from "./currency.js";
6
6
  export * from "./environment.js";
7
7
  export * from "./errors.js";
8
8
  export * from "./hex.js";
9
+ export * from "./locale.js";
10
+ export * from "./localeToCurrencyCodes.js";
11
+ export * from "./numbers.js";
9
12
  export * from "./pollUntil.js";
10
13
  export * from "./sleep.js";
11
14
  export * from "./types.js";
@@ -0,0 +1,3 @@
1
+ export function getCurrentLocale() {
2
+ return Intl.NumberFormat().resolvedOptions().locale;
3
+ }