diff --git a/src/helpers/money.ts b/src/helpers/money.ts index 1f1f05b..385c677 100644 --- a/src/helpers/money.ts +++ b/src/helpers/money.ts @@ -13,6 +13,14 @@ const getCurrencyFractionLength = (currency: string) => { }; export class Money { + + /** + * A private property that stores cached instances of `Intl.NumberFormat` + * for different locale and currency combinations. + * The key is a string representing the locale and currency, + * and the value is the corresponding `Intl.NumberFormat` instance. + */ + #cachedFormatters: { [key: string]: Intl.NumberFormat } = {}; /** * Formats the Money object based on the provided locale. * @@ -33,18 +41,26 @@ export class Money { * @returns The formatted amount. */ formatAmount(amount: number, currency: string, formattedLocale = 'en-US'): string { - let formatter; - try { - formatter = new Intl.NumberFormat(formattedLocale, { - style: 'currency', - currency: currency - }); - } catch (err) { - // Fallback to en-US if it's an invalid BCP 47 locale - formatter = new Intl.NumberFormat('en-US', { - style: 'currency', - currency: currency - }); + // use the cached formatter if present, to avoid creating a new formatter every time + const cacheKey = `${currency}:${formattedLocale}`; + let formatter = this.#cachedFormatters[cacheKey]; + if (!formatter) { + // create a new formatter for this currency + locale + try { + formatter = new Intl.NumberFormat(formattedLocale, { + style: 'currency', + currencyDisplay: 'symbol', + currency: currency + }); + } catch (err) { + // Fallback to en-US if it's an invalid BCP 47 locale + formatter = new Intl.NumberFormat('en-US', { + style: 'currency', + currencyDisplay: 'symbol', + currency: currency + }); + } + this.#cachedFormatters[cacheKey] = formatter; } amount = this.convertSubunitsToFloat(amount, currency); return formatter.format(amount); diff --git a/test/helpers.money.test.ts b/test/helpers.money.test.ts index ff426ed..6ea506a 100644 --- a/test/helpers.money.test.ts +++ b/test/helpers.money.test.ts @@ -94,4 +94,88 @@ describe('convertSubunitsToFloat', () => { expect(sdk.helpers.money.convertSubunitsToFloat(subunits, currency)).toBe(float); }); }); +}); + +describe('formatMoney', () => { + it('should not show the currency, when the currency matches locale', () => { + const currencyAndLocaleList = [ + { + money: { + currency: 'CAD', + amount: 123456, + formatted: '$1,234.56' + }, + locale: 'en-CA', + }, + { + money: { + currency: 'GBP', + amount: 123456, + formatted: '£1,234.56' + }, + locale: 'en-GB', + }, + { + money: { + currency: 'USD', + amount: 123456, + formatted: '$1,234.56' + }, + locale: 'en-US', + }, + ]; + + currencyAndLocaleList.forEach(({ money, locale }) => { + expect(sdk.helpers.money.formatMoney(money, locale)).toBe(money.formatted); + }); + }); + + it('should show the currency, when the currency does not match locale', () => { + const currencyAndLocaleList = [ + { + money: { + currency: 'CAD', + amount: 123456, + formatted: 'CA$1,234.56' + }, + locale: 'en-US', + }, + { + money: { + currency: 'USD', + amount: 123456, + formatted: 'US$1,234.56' + }, + locale: 'en-CA', + }, + ]; + + currencyAndLocaleList.forEach(({ money, locale }) => { + expect(sdk.helpers.money.formatMoney(money, locale)).toBe(money.formatted); + }); + }); + it('should default to en-US, when the locale is invalid', () => { + const currencyAndLocaleList = [ + { + money: { + currency: 'CAD', + amount: 123456, + formatted: 'CA$1,234.56' + }, + locale: 'fake-FAKE', + }, + { + money: { + currency: 'USD', + amount: 123456, + formatted: '$1,234.56' + }, + locale: 'fake-FAKE', + }, + ]; + + currencyAndLocaleList.forEach(({ money, locale }) => { + expect(sdk.helpers.money.formatMoney(money, locale)).toBe(money.formatted); + }); + }); }); \ No newline at end of file