@thoughtspot/ts-chart-sdk 0.0.2-alpha.24 → 0.0.2-alpha.25

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.
Files changed (54) hide show
  1. package/dist/ts-chart-sdk.d.ts +65 -5
  2. package/lib/index.d.ts +2 -0
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +2 -0
  5. package/lib/index.js.map +1 -1
  6. package/lib/main/custom-chart-context.d.ts.map +1 -1
  7. package/lib/main/custom-chart-context.js +3 -1
  8. package/lib/main/custom-chart-context.js.map +1 -1
  9. package/lib/types/answer-column.types.d.ts +3 -3
  10. package/lib/types/answer-column.types.d.ts.map +1 -1
  11. package/lib/types/answer-column.types.js +3 -3
  12. package/lib/types/answer-column.types.js.map +1 -1
  13. package/lib/types/number-formatting.types.d.ts +2 -2
  14. package/lib/types/number-formatting.types.d.ts.map +1 -1
  15. package/lib/types/visual-prop.types.d.ts +40 -0
  16. package/lib/types/visual-prop.types.d.ts.map +1 -1
  17. package/lib/types/visual-prop.types.js +32 -1
  18. package/lib/types/visual-prop.types.js.map +1 -1
  19. package/lib/utils/globalize-Initializer/globalize-utils.d.ts +16 -0
  20. package/lib/utils/globalize-Initializer/globalize-utils.d.ts.map +1 -0
  21. package/lib/utils/globalize-Initializer/globalize-utils.js +101 -0
  22. package/lib/utils/globalize-Initializer/globalize-utils.js.map +1 -0
  23. package/lib/utils/globalize-Initializer/globalize-utils.spec.d.ts +2 -0
  24. package/lib/utils/globalize-Initializer/globalize-utils.spec.d.ts.map +1 -0
  25. package/lib/utils/globalize-Initializer/globalize-utils.spec.js +149 -0
  26. package/lib/utils/globalize-Initializer/globalize-utils.spec.js.map +1 -0
  27. package/lib/utils/number-formatting/number-formatting-utils.d.ts +33 -0
  28. package/lib/utils/number-formatting/number-formatting-utils.d.ts.map +1 -0
  29. package/lib/utils/number-formatting/number-formatting-utils.js +180 -0
  30. package/lib/utils/number-formatting/number-formatting-utils.js.map +1 -0
  31. package/lib/utils/number-formatting/number-formatting-utils.spec.d.ts +2 -0
  32. package/lib/utils/number-formatting/number-formatting-utils.spec.d.ts.map +1 -0
  33. package/lib/utils/number-formatting/number-formatting-utils.spec.js +235 -0
  34. package/lib/utils/number-formatting/number-formatting-utils.spec.js.map +1 -0
  35. package/lib/utils/number-formatting/number-formatting.d.ts +5 -0
  36. package/lib/utils/number-formatting/number-formatting.d.ts.map +1 -0
  37. package/lib/utils/number-formatting/number-formatting.js +131 -0
  38. package/lib/utils/number-formatting/number-formatting.js.map +1 -0
  39. package/lib/utils/number-formatting/number-formatting.spec.d.ts +2 -0
  40. package/lib/utils/number-formatting/number-formatting.spec.d.ts.map +1 -0
  41. package/lib/utils/number-formatting/number-formatting.spec.js +159 -0
  42. package/lib/utils/number-formatting/number-formatting.spec.js.map +1 -0
  43. package/package.json +4 -1
  44. package/src/index.ts +2 -0
  45. package/src/main/custom-chart-context.ts +4 -1
  46. package/src/types/answer-column.types.ts +3 -3
  47. package/src/types/number-formatting.types.ts +2 -2
  48. package/src/types/visual-prop.types.ts +86 -0
  49. package/src/utils/globalize-Initializer/globalize-utils.spec.ts +192 -0
  50. package/src/utils/globalize-Initializer/globalize-utils.ts +216 -0
  51. package/src/utils/number-formatting/number-formatting-utils.spec.ts +321 -0
  52. package/src/utils/number-formatting/number-formatting-utils.ts +288 -0
  53. package/src/utils/number-formatting/number-formatting.spec.ts +243 -0
  54. package/src/utils/number-formatting/number-formatting.ts +268 -0
@@ -58,7 +58,7 @@ export type NumberFormatConfig = {
58
58
  /** default to true */
59
59
  toSeparateThousands?: Maybe<Scalars['Boolean']>;
60
60
  /** default is Auto */
61
- unit?: Maybe<Unit>;
61
+ unit?: Maybe<Unit> | number;
62
62
  };
63
63
 
64
64
  export type PercentageFormatConfig = {
@@ -71,7 +71,7 @@ export type PercentageFormatConfig = {
71
71
 
72
72
  export type FormatConfig = {
73
73
  __typename?: 'FormatConfig';
74
- category?: Maybe<CategoryType>;
74
+ category?: Maybe<CategoryType> | number;
75
75
  currencyFormatConfig?: Maybe<CurrencyFormatConfig>;
76
76
  customFormatConfig?: Maybe<CustomFormatConfig>;
77
77
  isCategoryEditable?: Maybe<Scalars['Boolean']>;
@@ -16,6 +16,38 @@ export type TSTooltipConfig = {
16
16
  columnIds: Array<string>;
17
17
  };
18
18
 
19
+ export enum VisualPropComponentTranslationKeys {
20
+ SHOW_ALL_LABELS = 'SHOW_ALL_LABELS',
21
+ TOO_MANY_LABELS = 'TOO_MANY_LABELS',
22
+ MAP_TILE_LABEL = 'MAP_TILE_LABEL',
23
+ ENABLE_MARKERS = 'ENABLE_MARKERS',
24
+ SHOW_REGRESSION_LINE = 'SHOW_REGRESSION_LINE',
25
+ X_AXIS_GRID_LINE = 'X_AXIS_GRID_LINE',
26
+ Y_AXIS_GRID_LINE = 'Y_AXIS_GRID_LINE',
27
+ MAX_DATA_POINTS = 'MAX_DATA_POINTS',
28
+ HIGH_CARDINALITY_BATCH_SIZE_DISABLED = 'highCardinalityBatchSizeDisabled',
29
+ HIGH_CARDINALITY_BATCH_SIZE_LIMIT = 'highCardinalityBatchSizeLimit',
30
+ CHART_CUSTOMIZE = 'CHART_CUSTOMIZE',
31
+ SELECT_AN_AREA = 'chartConfigurator.SELECT_AN_AREA',
32
+ RESET_ZOOM = 'chartConfigurator.RESET_ZOOM',
33
+ EDIT_TOOLTIP = 'EDIT_TOOLTIP',
34
+ DONT_SHOW = 'DONT_SHOW',
35
+ SHOW_GAP = 'SHOW_GAP',
36
+ SHOW_AS_ZERO = 'SHOW_AS_ZERO',
37
+ HANDLE_MISSING_VALUES = 'HANDLE_MISSING_VALUES',
38
+ SHOW_NULL_AS_ZERO = 'SHOW_NULL_AS_ZERO',
39
+ EXCLUDE_NULL_VALUES = 'EXCLUDE_NULL_VALUES',
40
+ COLUMN_CUSTOMIZE = 'COLUMN_CUSTOMIZE',
41
+ SHOW_TOTAL_LABELS = 'SHOW_TOTAL_LABELS',
42
+ SHOW_DETAILED_LABELS = 'SHOW_DETAILED_LABELS',
43
+ SHOW_DATA_LABELS = 'SHOW_DATA_LABELS',
44
+ SHOW_AXIS_AS_PERCENT = 'SHOW_AXIS_AS_PERCENT',
45
+ RIGHT_LEGEND = 'RIGHT_LEGEND',
46
+ LEFT_LEGEND = 'LEFT_LEGEND',
47
+ TOP_LEGEND = 'TOP_LEGEND',
48
+ BOTTOM_LEGEND = 'BOTTOM_LEGEND',
49
+ }
50
+
19
51
  /**
20
52
  * Configuration for input validation rules
21
53
  */
@@ -127,6 +159,12 @@ export interface TextInputFormDetail {
127
159
  * @version SDK: 0.0.2-alpha.13 | ThoughtSpot:
128
160
  */
129
161
  disabled?: boolean;
162
+ /**
163
+ * Translation key for the label
164
+ *
165
+ * @version SDK: 0.2 | ThoughtSpot:
166
+ */
167
+ labelTranslation?: VisualPropComponentTranslationKeys;
130
168
  }
131
169
 
132
170
  /**
@@ -166,6 +204,12 @@ export interface NumberInputFormDetail {
166
204
  * @version SDK: 0.0.2-alpha.13 | ThoughtSpot:
167
205
  */
168
206
  disabled?: boolean;
207
+ /**
208
+ * Translation key for the label
209
+ *
210
+ * @version SDK: 0.2 | ThoughtSpot:
211
+ */
212
+ labelTranslation?: VisualPropComponentTranslationKeys;
169
213
  }
170
214
 
171
215
  /**
@@ -200,6 +244,12 @@ export interface ColorPickerFormDetail {
200
244
  * @version SDK: 0.0.1-alpha.7 | ThoughtSpot:
201
245
  */
202
246
  defaultValue?: string;
247
+ /**
248
+ * Translation key for the label
249
+ *
250
+ * @version SDK: 0.2 | ThoughtSpot:
251
+ */
252
+ labelTranslation?: VisualPropComponentTranslationKeys;
203
253
  }
204
254
 
205
255
  /**
@@ -233,6 +283,12 @@ export interface ToggleFormDetail {
233
283
  * @version SDK: 0.0.2-alpha.13 | ThoughtSpot:
234
284
  */
235
285
  disabled?: boolean;
286
+ /**
287
+ * Translation key for the label
288
+ *
289
+ * @version SDK: 0.2 | ThoughtSpot:
290
+ */
291
+ labelTranslation?: VisualPropComponentTranslationKeys;
236
292
  }
237
293
 
238
294
  /**
@@ -266,6 +322,12 @@ export interface CheckboxFormDetail {
266
322
  * @version SDK: 0.0.2-alpha.13 | ThoughtSpot:
267
323
  */
268
324
  disabled?: boolean;
325
+ /**
326
+ * Translation key for the label
327
+ *
328
+ * @version SDK: 0.2 | ThoughtSpot:
329
+ */
330
+ labelTranslation?: VisualPropComponentTranslationKeys;
269
331
  }
270
332
 
271
333
  /**
@@ -305,6 +367,12 @@ export interface RadioButtonFormDetail {
305
367
  * @version SDK: 0.0.2-3 | ThoughtSpot:
306
368
  */
307
369
  disabled?: boolean;
370
+ /**
371
+ * Translation key for the label
372
+ *
373
+ * @version SDK: 0.2 | ThoughtSpot:
374
+ */
375
+ labelTranslation?: VisualPropComponentTranslationKeys;
308
376
  }
309
377
 
310
378
  /**
@@ -344,6 +412,12 @@ export interface DropDownFormDetail {
344
412
  * @version SDK: 0.0.2-alpha.13 | ThoughtSpot:
345
413
  */
346
414
  disabled?: boolean;
415
+ /**
416
+ * Translation key for the label
417
+ *
418
+ * @version SDK: 0.2 | ThoughtSpot:
419
+ */
420
+ labelTranslation?: VisualPropComponentTranslationKeys;
347
421
  }
348
422
 
349
423
  /**
@@ -397,6 +471,12 @@ export interface Section {
397
471
  * @version SDK: 0.0.2-alpha.19 | ThoughtSpot:
398
472
  */
399
473
  isAccordianExpanded?: boolean;
474
+ /**
475
+ * Translation key for the label
476
+ *
477
+ * @version SDK: 0.2 | ThoughtSpot:
478
+ */
479
+ labelTranslation?: VisualPropComponentTranslationKeys;
400
480
  }
401
481
 
402
482
  /**
@@ -431,6 +511,12 @@ export interface NativeEditToolTip {
431
511
  * @version SDK: 0.0.2-alpha.13 | ThoughtSpot:
432
512
  */
433
513
  disabled?: boolean;
514
+ /**
515
+ * Translation key for the label
516
+ *
517
+ * @version SDK: 0.2 | ThoughtSpot:
518
+ */
519
+ labelTranslation?: VisualPropComponentTranslationKeys;
434
520
  }
435
521
 
436
522
  /**
@@ -0,0 +1,192 @@
1
+ import Globalize from 'globalize';
2
+ import {
3
+ formatNumberSafely,
4
+ getCountryCode,
5
+ getCurrentCurrencyFormat,
6
+ getDefaultCurrencyCode,
7
+ getGlobalizeLocale,
8
+ globalizeCurrencyFormatter,
9
+ globalizeNumberFormatter,
10
+ initGlobalize,
11
+ loadCurrencyData,
12
+ loadGlobalizeData,
13
+ sanitizeFormat,
14
+ setCurrentCurrencyFormat,
15
+ setGlobalizeLocale,
16
+ validateNumberFormat,
17
+ } from './globalize-utils';
18
+
19
+ describe('Initialize Globalize', () => {
20
+ beforeEach(() => {
21
+ jest.clearAllMocks();
22
+ initGlobalize('en-US');
23
+ });
24
+
25
+ describe('getCountryCode', () => {
26
+ it('should extract country code from locale with underscore', () => {
27
+ expect(getCountryCode('en_US')).toBe('US');
28
+ });
29
+
30
+ it('should extract country code from locale with hyphen', () => {
31
+ expect(getCountryCode('en-US')).toBe('US');
32
+ });
33
+
34
+ it('should return the input locale if no delimiter is found', () => {
35
+ expect(getCountryCode('en')).toBe('en');
36
+ });
37
+ });
38
+
39
+ describe('getDefaultCurrencyCode', () => {
40
+ it('should return the current currency format if set', () => {
41
+ setCurrentCurrencyFormat('EUR');
42
+ expect(getDefaultCurrencyCode()).toBe('EUR');
43
+ });
44
+
45
+ it('should return GBP if no currency data is found', () => {
46
+ setCurrentCurrencyFormat(null);
47
+ loadCurrencyData({});
48
+ expect(getDefaultCurrencyCode()).toBe('GBP');
49
+ });
50
+
51
+ it('should retrieve the default currency code from supplemental data', () => {
52
+ loadCurrencyData({
53
+ supplemental: {
54
+ currencyData: {
55
+ region: {
56
+ US: [{ USD: {} }],
57
+ },
58
+ },
59
+ },
60
+ });
61
+ setGlobalizeLocale('en-US');
62
+ expect(getDefaultCurrencyCode()).toBe('USD');
63
+ });
64
+ });
65
+
66
+ describe('setGlobalizeLocale', () => {
67
+ it('should set the Globalize locale', () => {
68
+ setGlobalizeLocale('fr-FR');
69
+ expect(getGlobalizeLocale()).toBe('fr-FR');
70
+ });
71
+ });
72
+
73
+ describe('getGlobalizeLocale', () => {
74
+ it('should return the current locale', () => {
75
+ expect(getGlobalizeLocale()).toBe('en-US');
76
+ });
77
+ });
78
+
79
+ describe('setCurrentCurrencyFormat', () => {
80
+ it('should set the current currency format', () => {
81
+ setCurrentCurrencyFormat('JPY');
82
+ expect(getCurrentCurrencyFormat()).toBe('JPY');
83
+ });
84
+ });
85
+
86
+ describe('loadCurrencyData', () => {
87
+ it('should load supplemental currency data', () => {
88
+ const data = {
89
+ supplemental: {
90
+ currencyData: {
91
+ region: {
92
+ US: [{ USD: {} }],
93
+ },
94
+ },
95
+ },
96
+ };
97
+ setCurrentCurrencyFormat(null);
98
+ loadCurrencyData(data);
99
+ expect(getDefaultCurrencyCode()).toBe('USD');
100
+ });
101
+ });
102
+
103
+ describe('loadGlobalizeData', () => {
104
+ it('should load CLDR data', () => {
105
+ jest.spyOn(Globalize, 'load').mockImplementationOnce((data) => {
106
+ return data;
107
+ });
108
+ const data = { key: 'value' };
109
+ loadGlobalizeData(data);
110
+ expect(Globalize.load).toHaveBeenCalledWith(data);
111
+ jest.clearAllMocks();
112
+ });
113
+ });
114
+
115
+ describe('globalizeNumberFormatter', () => {
116
+ it('should create a number formatter', () => {
117
+ const formatter = globalizeNumberFormatter({
118
+ minimumFractionDigits: 2,
119
+ });
120
+ expect(typeof formatter).toBe('function');
121
+ });
122
+ });
123
+
124
+ describe('globalizeCurrencyFormatter', () => {
125
+ it('should create a currency formatter', () => {
126
+ const formatter = globalizeCurrencyFormatter('USD', {
127
+ style: 'symbol',
128
+ });
129
+ expect(typeof formatter).toBe('function');
130
+ });
131
+ });
132
+
133
+ describe('formatNumberSafely', () => {
134
+ it('should format numbers safely', () => {
135
+ const formattedNumber = formatNumberSafely(
136
+ { maximumFractionDigits: 2, minimumFractionDigits: 2 },
137
+ 123.456,
138
+ );
139
+ expect(formattedNumber).toBe('123.46');
140
+ });
141
+
142
+ it('should return 0 for very small numbers', () => {
143
+ jest.spyOn(Globalize, 'numberFormatter').mockImplementationOnce(
144
+ () => {
145
+ throw new Error('Test Error');
146
+ },
147
+ );
148
+ const formattedNumber = formatNumberSafely({}, 1e-8);
149
+ expect(formattedNumber).toBe('0');
150
+ jest.clearAllMocks();
151
+ });
152
+
153
+ it('should return the string representation for errors', () => {
154
+ jest.spyOn(Globalize, 'numberFormatter').mockImplementationOnce(
155
+ () => {
156
+ throw new Error('Test Error');
157
+ },
158
+ );
159
+ const formattedNumber = formatNumberSafely({}, 123);
160
+ expect(formattedNumber).toBe('123');
161
+ jest.clearAllMocks();
162
+ });
163
+ });
164
+
165
+ describe('sanitizeFormat', () => {
166
+ it('should add zero before decimal point', () => {
167
+ expect(sanitizeFormat('#.##')).toBe('0.##');
168
+ });
169
+
170
+ it('should add zero at the end if no decimal point', () => {
171
+ expect(sanitizeFormat('#%')).toBe('0%');
172
+ });
173
+
174
+ it('should return the same format if no changes are needed', () => {
175
+ expect(sanitizeFormat('0.##')).toBe('0.##');
176
+ });
177
+ });
178
+
179
+ describe('validateNumberFormat', () => {
180
+ it('should return true for valid number formats', () => {
181
+ expect(validateNumberFormat('#.##')).toBe(true);
182
+ });
183
+
184
+ it('should return false for invalid number formats', () => {
185
+ jest.spyOn(Globalize, 'numberFormatter').mockImplementation(() => {
186
+ throw new Error('Test Error');
187
+ });
188
+ expect(validateNumberFormat('#.#')).toBe(false);
189
+ jest.clearAllMocks();
190
+ });
191
+ });
192
+ });
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @file: Initialize Globalize
3
+ *
4
+ * @author Yashvardhan Nehra <yashvardhan.nehra@thoughtspot.com>
5
+ *
6
+ * Copyright: ThoughtSpot Inc. 2024
7
+ */
8
+
9
+ /* eslint-disable import/no-extraneous-dependencies */
10
+ import enCaGregorian from 'cldr-data/main/en/ca-gregorian.json';
11
+ import enCurrencies from 'cldr-data/main/en/currencies.json';
12
+ import enNumbers from 'cldr-data/main/en/numbers.json';
13
+ import currencyData from 'cldr-data/supplemental/currencyData.json';
14
+ import supplemental from 'cldr-data/supplemental/likelySubtags.json';
15
+ import enpluralJson from 'cldr-data/supplemental/plurals.json';
16
+ import Globalize from 'globalize';
17
+ import _ from 'lodash';
18
+ import { create } from '../../main/logger';
19
+
20
+ const logger = create('globalize-initializer');
21
+
22
+ let currentLocale: string;
23
+ let currentCurrencyFormat: any;
24
+ let supplementalCurrencyDataJson: any;
25
+
26
+ /**
27
+ * Extracts the country code from a locale string.
28
+ *
29
+ * @param locale - The locale string (e.g., 'en-US', 'en_US').
30
+ * @returns The country code (e.g., 'US') or the input locale if no delimiter is found.
31
+ */
32
+ export const getCountryCode = (locale: string): string => {
33
+ let parts = locale.split('_'); // // Split by '_'
34
+ if (parts.length === 2) {
35
+ return parts[1].toUpperCase();
36
+ }
37
+ parts = locale.split('-'); // // Split by '-'
38
+ if (parts.length === 2) {
39
+ return parts[1].toUpperCase();
40
+ }
41
+ return locale;
42
+ };
43
+
44
+ /**
45
+ * Retrieves the default currency code for the current locale.
46
+ *
47
+ * @returns The default currency code (e.g., 'USD') or GBP if not found.
48
+ */
49
+ export const getDefaultCurrencyCode = (): string => {
50
+ if (currentCurrencyFormat) {
51
+ return currentCurrencyFormat;
52
+ }
53
+
54
+ const countryCode = getCountryCode(currentLocale);
55
+ const regionData =
56
+ supplementalCurrencyDataJson?.supplemental?.currencyData?.region[
57
+ countryCode.toUpperCase()
58
+ ];
59
+ if (!regionData || regionData.length === 0) {
60
+ logger.warn('No currency data found for country:', countryCode);
61
+ return 'GBP';
62
+ }
63
+ return Object.keys(regionData[regionData.length - 1])[0];
64
+ };
65
+
66
+ /**
67
+ * Sets the current locale for Globalize and updates the global state.
68
+ *
69
+ * @param locale - The locale string to set (e.g., 'en-US').
70
+ */
71
+ export const setGlobalizeLocale = (locale: string): void => {
72
+ Globalize.locale(locale);
73
+ currentLocale = locale;
74
+ };
75
+
76
+ /**
77
+ * Retrieves the current locale set in Globalize.
78
+ *
79
+ * @returns The current locale string (e.g., 'en-US').
80
+ */
81
+ export const getGlobalizeLocale = () => currentLocale;
82
+
83
+ /**
84
+ * Updates the current currency format.
85
+ *
86
+ * @param currencyFormat - The currency format to set.
87
+ */
88
+ export const setCurrentCurrencyFormat = (currencyFormat: any): void => {
89
+ currentCurrencyFormat = currencyFormat;
90
+ };
91
+
92
+ /**
93
+ * Retrieves the current currency format.
94
+ *
95
+ * @returns The current currency format.
96
+ */
97
+ export const getCurrentCurrencyFormat = () => currentCurrencyFormat;
98
+
99
+ /**
100
+ * Loads supplemental currency data for Globalize.
101
+ *
102
+ * @param data - The supplemental currency data to load.
103
+ */
104
+ export const loadCurrencyData = (data: any) => {
105
+ supplementalCurrencyDataJson = data;
106
+ };
107
+
108
+ /**
109
+ * Loads CLDR data into Globalize.
110
+ *
111
+ * @param data - The CLDR data to load.
112
+ */
113
+ export const loadGlobalizeData = (data: any) => {
114
+ Globalize.load(data);
115
+ };
116
+
117
+ /**
118
+ * Initializes Globalize with CLDR data and sets the default locale.
119
+ *
120
+ * @param locale - The locale to initialize Globalize with (default: 'en-GB').
121
+ */
122
+ export const initGlobalize = (locale = 'en-GB') => {
123
+ loadGlobalizeData(enNumbers);
124
+ loadGlobalizeData(enCaGregorian);
125
+ loadGlobalizeData(supplemental);
126
+ loadGlobalizeData(currencyData);
127
+ loadGlobalizeData(enpluralJson);
128
+ loadGlobalizeData(enCurrencies);
129
+
130
+ loadCurrencyData(currencyData);
131
+
132
+ setGlobalizeLocale(locale);
133
+ };
134
+
135
+ /**
136
+ * Creates a number formatter with the given options.
137
+ *
138
+ * @param format - The Globalize number formatter options.
139
+ * @returns A formatter function for numbers.
140
+ */
141
+ export function globalizeNumberFormatter(
142
+ format: Globalize.NumberFormatterOptions,
143
+ ): (num: number) => string {
144
+ return Globalize.numberFormatter(format);
145
+ }
146
+
147
+ /**
148
+ * Creates a currency formatter with the given options.
149
+ *
150
+ * @param currencyCode - The ISO currency code (e.g., 'USD').
151
+ * @param format - The Globalize currency formatter options.
152
+ * @returns A formatter function for currency values.
153
+ */
154
+ export function globalizeCurrencyFormatter(
155
+ currencyCode: string,
156
+ format: Globalize.CurrencyFormatterOptions,
157
+ ): (num: number) => string {
158
+ return Globalize.currencyFormatter(currencyCode, format);
159
+ }
160
+
161
+ /**
162
+ * Formats a number using Globalize, handling errors gracefully.
163
+ *
164
+ * @param format - Globalize number formatter options.
165
+ * @param num - The number to format.
166
+ * @returns The formatted number as a string.
167
+ */
168
+ export function formatNumberSafely<
169
+ FormatOptions extends Globalize.NumberFormatterOptions,
170
+ >(format: FormatOptions, num: number): string {
171
+ try {
172
+ const formatter = globalizeNumberFormatter(format);
173
+ const formattedNumber = formatter(num);
174
+ return formattedNumber;
175
+ } catch (e) {
176
+ logger.error('Error formatting pattern: ', format, num, e);
177
+ if (Math.abs(num) < 1e-7) {
178
+ return '0';
179
+ }
180
+ return String(num);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Sanitizes a number format to ensure compatibility with Globalize.
186
+ *
187
+ * @param format - The raw format string (e.g., '#.##').
188
+ * @returns A sanitized format string (e.g., '0.##').
189
+ */
190
+ export const sanitizeFormat = (format: string): string => {
191
+ // Globalize needs to have a zero before the decimal point
192
+ // or at the end of format if no decimal point
193
+ let sanitizedFormat = format.replace(/#\./, '0.');
194
+ if (!sanitizedFormat.includes('.')) {
195
+ sanitizedFormat = sanitizedFormat.replace(/#(%?)$/, '0$1');
196
+ }
197
+ return sanitizedFormat;
198
+ };
199
+
200
+ /**
201
+ * Validates if a given number format is compatible with Globalize.
202
+ *
203
+ * @param format - The raw format string.
204
+ * @returns True if the format is valid; otherwise, false.
205
+ */
206
+ export const validateNumberFormat = (format: string): boolean => {
207
+ try {
208
+ Globalize.numberFormatter({
209
+ ...({ raw: sanitizeFormat(format) } as any),
210
+ })(123); // Test the formatter with a dummy value
211
+ } catch (e) {
212
+ logger.error('Invalid number format:', format, e);
213
+ return false;
214
+ }
215
+ return true;
216
+ };