diff --git a/packages/react-i18n/CHANGELOG.md b/packages/react-i18n/CHANGELOG.md index 8c58c73cd5..206b0678bc 100644 --- a/packages/react-i18n/CHANGELOG.md +++ b/packages/react-i18n/CHANGELOG.md @@ -6,7 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). --- - +## [Unreleased] + +### Fixed + +- Fixed memory leaks when server-side rendering for `Intl.DateTimeFormat.format()` and `Intl.NumberFormat.format()` ([#1287](https://github.com/Shopify/quilt/pull/1287)) + +--- ### Fixed diff --git a/packages/react-i18n/src/i18n.ts b/packages/react-i18n/src/i18n.ts index 4c65d95dc9..b4940ace80 100644 --- a/packages/react-i18n/src/i18n.ts +++ b/packages/react-i18n/src/i18n.ts @@ -1,4 +1,5 @@ import { + formatDate, isLessThanOneHourAgo, isLessThanOneMinuteAgo, isLessThanOneWeekAgo, @@ -7,7 +8,6 @@ import { isYesterday, TimeUnit, } from '@shopify/dates'; -import {memoize as memoizeFn} from '@shopify/function-enhancers'; import {memoize} from '@shopify/decorators'; import {languageFromLocale, regionFromLocale} from '@shopify/i18n'; @@ -61,12 +61,6 @@ const DECIMAL_NOT_SUPPORTED = 'N/A'; const PERIOD = '.'; const DECIMAL_VALUE_FOR_CURRENCIES_WITHOUT_DECIMALS = '00'; -const memoizedDateTimeFormatter = memoizeFn( - dateTimeFormatter, - (locale: string, options: Intl.DateTimeFormatOptions = {}) => - `${locale}${JSON.stringify(options)}`, -); - export class I18n { readonly locale: string; readonly pseudolocalize: boolean | string; @@ -296,13 +290,6 @@ export class I18n { const {locale, defaultTimezone} = this; const {timeZone = defaultTimezone} = options; - // Etc/GMT+12 is not supported in most browsers and there is no equivalent fallback - if (timeZone === 'Etc/GMT+12') { - const adjustedDate = new Date(date.valueOf() - 12 * 60 * 60 * 1000); - - return this.formatDate(adjustedDate, {...options, timeZone: 'UTC'}); - } - const {style = undefined, ...formatOptions} = options || {}; if (style) { @@ -311,10 +298,7 @@ export class I18n { : this.formatDate(date, {...formatOptions, ...dateStyle[style]}); } - return memoizedDateTimeFormatter(locale, { - timeZone, - ...formatOptions, - }).format(date); + return formatDate(date, locale, {...formatOptions, timeZone}); } ordinal(amount: number) { @@ -566,10 +550,3 @@ function isTranslateOptions( function defaultOnError(error: I18nError) { throw error; } - -function dateTimeFormatter( - locale: string, - options: Intl.DateTimeFormatOptions = {}, -) { - return new Intl.DateTimeFormat(locale, options); -} diff --git a/packages/react-i18n/src/utilities/translate.tsx b/packages/react-i18n/src/utilities/translate.tsx index 464f866a12..66d3d23ee9 100644 --- a/packages/react-i18n/src/utilities/translate.tsx +++ b/packages/react-i18n/src/utilities/translate.tsx @@ -17,6 +17,17 @@ const MISSING_TRANSLATION = Symbol('Missing translation'); const PLURALIZATION_KEY_NAME = 'count'; const SEPARATOR = '.'; +const numberFormats = new Map(); +export const memoizedNumberFormatter = function(locale, options = {}) { + const key = numberFormatCacheKey(locale, options); + if (numberFormats.has(key)) { + return numberFormats.get(key); + } + const i = new Intl.NumberFormat(locale, options); + numberFormats.set(key, i); + return i; +}; + export const PSEUDOTRANSLATE_OPTIONS: PseudotranslateOptions = { startDelimiter: '{', endDelimiter: '}', @@ -30,19 +41,13 @@ export interface TranslateOptions { pseudotranslate?: boolean | string; } -function numberFormatter( +function numberFormatCacheKey( locale: string, options: Intl.NumberFormatOptions = {}, ) { - return new Intl.NumberFormat(locale, options); + return `${locale}${JSON.stringify(options)}`; } -export const memoizedNumberFormatter = memoizeFn( - numberFormatter, - (locale: string, options: Intl.NumberFormatOptions = {}) => - `${locale}${JSON.stringify(options)}`, -); - function pluralRules(locale: string, options: Intl.PluralRulesOptions = {}) { return new Intl.PluralRules(locale, options); }