Skip to content

Commit e46b13f

Browse files
authored
refactor(use-i18n): use @formatjs/fast-memoize instead of deprecated intl-format-cache (#317)
* refactor(use-i18n): use @formatjs/fast-memoize instead of deprecated intl-format-cache * chore: remove previous package * fix: correct type * fix: remove unused eslint-disable * test: skip coverage
1 parent 8d54d13 commit e46b13f

File tree

6 files changed

+108
-44
lines changed

6 files changed

+108
-44
lines changed

packages/use-i18n/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
},
2828
"license": "MIT",
2929
"dependencies": {
30+
"@formatjs/fast-memoize": "^1.1.2",
3031
"date-fns": "^2.19.0",
3132
"filesize": "^7.0.0",
32-
"intl-format-cache": "^4.3.1",
3333
"intl-messageformat": "^9.5.3",
3434
"intl-pluralrules": "^1.2.2",
3535
"prop-types": "^15.7.2"

packages/use-i18n/src/__tests__/formatUnit.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1-
import memoizeIntlConstructor from 'intl-format-cache'
2-
import IntlTranslationFormat from 'intl-messageformat'
31
import formatUnit, { FormatUnitOptions, supportedUnits } from '../formatUnit'
42

53
const locales = ['en', 'fr', 'ro']
64

7-
const getTranslationFormat = memoizeIntlConstructor(IntlTranslationFormat)
8-
95
const tests = [
106
...Object.keys(supportedUnits).map(unit => [
117
'should work with',
@@ -57,13 +53,13 @@ describe('formatUnit', () => {
5753
test('should return empty string for unknown unit', () => {
5854
expect(
5955
// @ts-expect-error We test the use case when unit is unknown
60-
formatUnit('fr', 123, { unit: 'unknown' }, getTranslationFormat),
56+
formatUnit('fr', 123, { unit: 'unknown' }),
6157
).toMatchSnapshot()
6258
})
6359

6460
test.each(tests)('%s %o', (_, options, locale, amount) => {
6561
expect(
66-
formatUnit(locale as string, amount as number, options as FormatUnitOptions, getTranslationFormat),
62+
formatUnit(locale as string, amount as number, options as FormatUnitOptions),
6763
).toMatchSnapshot()
6864
})
6965
})

packages/use-i18n/src/formatUnit.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import filesize from 'filesize'
2-
import { Options } from 'intl-messageformat'
2+
import formatters from './formatters'
33

44
// We are on base 10, so we should use IEC standard here ...
55
const exponents = [
@@ -15,6 +15,7 @@ const exponents = [
1515
]
1616

1717
type Exponent = typeof exponents[number]
18+
type ExponentName = '' | 'kilo' | 'mega' | 'giga' | 'tera' | 'peta' | 'exa' | 'zetta' | 'yotta'
1819

1920
const frOctet = {
2021
plural: 'octets',
@@ -55,7 +56,6 @@ const compoundUnitsSymbols = {
5556

5657
type Unit = 'bit' | 'byte'
5758
type CompoundUnit = 'second'
58-
type FormatPlural = (message: string, locales?: string | string[] | undefined, overrideFormats?: undefined, opts?: Options | undefined) => { format: ({ amount }: { amount: number}) => string}
5959

6060
const formatShortUnit = (locale: string, exponent: Exponent, unit: Unit, compoundUnit?: CompoundUnit) => {
6161
let shortenedUnit = symbols.short[unit]
@@ -72,7 +72,7 @@ const formatShortUnit = (locale: string, exponent: Exponent, unit: Unit, compoun
7272
}`
7373
}
7474

75-
const formatLongUnit = (locale: string, exponent: Exponent, unit: Unit, amount: number, messageFormat: FormatPlural) => {
75+
const formatLongUnit = (locale: string, exponent: Exponent, unit: Unit, amount: number) => {
7676
let translation = symbols.long[unit]
7777

7878
if (
@@ -82,14 +82,14 @@ const formatLongUnit = (locale: string, exponent: Exponent, unit: Unit, amount:
8282
translation = localesWhoFavorOctetOverByte[locale as keyof typeof localesWhoFavorOctetOverByte]
8383
}
8484

85-
return `${exponent.name}${messageFormat(
85+
return `${exponent.name}${formatters.getTranslationFormat(
8686
`{amount, plural,
8787
=0 {${translation.singular}}
8888
=1 {${translation.singular}}
8989
other {${translation.plural}}
9090
}`,
9191
locale,
92-
).format({ amount })}`
92+
).format({ amount }) as string}`
9393
}
9494

9595
const format =
@@ -103,7 +103,6 @@ const format =
103103
locale: string,
104104
amount: number,
105105
{ maximumFractionDigits, minimumFractionDigits, short = true }: { maximumFractionDigits?: number, minimumFractionDigits?: number, short?: boolean },
106-
messageFormat: FormatPlural,
107106
): string => {
108107
let computedExponent = exponent
109108
let computedValue = amount
@@ -141,12 +140,16 @@ const format =
141140
computedExponent as Exponent,
142141
unit,
143142
computedValue,
144-
messageFormat,
145143
)
146144
}`
147145
}
148146

149-
export const supportedUnits = {
147+
type SimpleUnits = `${ExponentName}${Unit}${'-humanized' | ''}`
148+
type ComplexUnits = `${Unit}${'s' | ''}${'-humanized' | ''}`
149+
type PerSecondUnit = `bit${'s' | ''}${'-per-second' | ''}${'-humanized' | ''}`
150+
type SupportedUnits = SimpleUnits | ComplexUnits | PerSecondUnit
151+
152+
export const supportedUnits: Partial<Record<SupportedUnits, ReturnType<typeof format>>> = {
150153
// bits
151154
'bits-humanized': format({ humanize: true, unit: 'bit' }),
152155
'bits-per-second-humanized': format({
@@ -195,11 +198,11 @@ export const supportedUnits = {
195198
}
196199

197200
export interface FormatUnitOptions {
198-
unit: keyof typeof supportedUnits
201+
unit: SupportedUnits
199202
short?: boolean
200203
}
201204

202-
const formatUnit = (locale: string, number: number, { unit, ...options }: FormatUnitOptions, messageFormat: FormatPlural): string =>
203-
supportedUnits?.[unit]?.(locale, number, options, messageFormat) ?? ''
205+
const formatUnit = (locale: string, number: number, { unit, ...options }: FormatUnitOptions): string =>
206+
supportedUnits?.[unit]?.(locale, number, options) ?? ''
204207

205208
export default formatUnit

packages/use-i18n/src/formatters.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import memoize, { Cache, strategies } from '@formatjs/fast-memoize'
2+
import IntlTranslationFormat from 'intl-messageformat'
3+
4+
// Deeply inspired by https://github.com/formatjs/formatjs/blob/7406e526a9c5666cee22cc2316dad1fa1d88697c/packages/intl-messageformat/src/core.ts
5+
6+
interface BaseFormatters {
7+
getNumberFormat(
8+
...args: ConstructorParameters<typeof Intl.NumberFormat>
9+
): Intl.NumberFormat
10+
getDateTimeFormat(
11+
...args: ConstructorParameters<typeof Intl.DateTimeFormat>
12+
): Intl.DateTimeFormat
13+
getPluralRules(
14+
...args: ConstructorParameters<typeof Intl.PluralRules>
15+
): Intl.PluralRules
16+
getListFormat(
17+
...args: ConstructorParameters<typeof Intl.ListFormat>
18+
): Intl.ListFormat
19+
}
20+
21+
function createFastMemoizeCache<V>(): Cache<string, V> {
22+
const store: Record<string, V> = {}
23+
24+
return {
25+
create() {
26+
return {
27+
get(key) {
28+
return store[key]
29+
},
30+
// Can be removed once https://github.com/formatjs/formatjs/pull/3102 is merged
31+
/* istanbul ignore next */
32+
has(key) {
33+
return key in store
34+
},
35+
set(key, value) {
36+
store[key] = value
37+
},
38+
}
39+
},
40+
}
41+
}
42+
43+
const baseFormatters: BaseFormatters = {
44+
getDateTimeFormat: memoize((...args) => new Intl.DateTimeFormat(...args), {
45+
cache: createFastMemoizeCache<Intl.DateTimeFormat>(),
46+
strategy: strategies.variadic,
47+
}),
48+
getListFormat: memoize((...args) => new Intl.ListFormat(...args), {
49+
cache: createFastMemoizeCache<Intl.ListFormat>(),
50+
strategy: strategies.variadic,
51+
}),
52+
getNumberFormat: memoize((...args) => new Intl.NumberFormat(...args), {
53+
cache: createFastMemoizeCache<Intl.NumberFormat>(),
54+
strategy: strategies.variadic,
55+
}),
56+
getPluralRules: memoize((...args) => new Intl.PluralRules(...args), {
57+
cache: createFastMemoizeCache<Intl.PluralRules>(),
58+
strategy: strategies.variadic,
59+
}),
60+
}
61+
62+
type Formatters = BaseFormatters & {
63+
getTranslationFormat(
64+
...args: ConstructorParameters<typeof IntlTranslationFormat>
65+
): IntlTranslationFormat
66+
}
67+
68+
type TranslationFormatParameter = ConstructorParameters<typeof IntlTranslationFormat>
69+
70+
const formatters: Formatters = {
71+
...baseFormatters,
72+
getTranslationFormat: memoize((message: TranslationFormatParameter[0], locales: TranslationFormatParameter[1], overrideFormats: TranslationFormatParameter[2], opts: TranslationFormatParameter[3]) => new IntlTranslationFormat(message, locales, overrideFormats, {
73+
formatters: baseFormatters,
74+
...opts,
75+
}), {
76+
cache: createFastMemoizeCache<IntlTranslationFormat>(),
77+
strategy: strategies.variadic,
78+
}),
79+
}
80+
81+
export default formatters

packages/use-i18n/src/usei18n.tsx

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { Locale, formatDistanceToNow, formatDistanceToNowStrict } from 'date-fns'
2-
import memoizeIntlConstructor from 'intl-format-cache'
3-
import IntlTranslationFormat from 'intl-messageformat'
42
import PropTypes from 'prop-types'
53
import React, {
64
ReactElement,
@@ -16,6 +14,7 @@ import ReactDOM from 'react-dom'
1614
import 'intl-pluralrules'
1715
import dateFormat, { FormatDateOptions } from './formatDate'
1816
import unitFormat, { FormatUnitOptions } from './formatUnit'
17+
import formatters from './formatters'
1918

2019
const LOCALE_ITEM_STORAGE = 'locale'
2120

@@ -60,7 +59,7 @@ interface Context {
6059
dateFnsLocale?: Locale,
6160
datetime?: (date: Date | number, options?: Intl.DateTimeFormatOptions) => string,
6261
formatDate?: (value: Date | number | string, options: FormatDateOptions) => string,
63-
formatList?: (listFormat: string[], options?: Intl.ListFormatOptions) => string,
62+
formatList?: (listFormat: [string | undefined], options?: Intl.ListFormatOptions) => string,
6463
formatNumber?: (numb: number, options?: Intl.NumberFormatOptions) => string,
6564
formatUnit?: (value: number, options: FormatUnitOptions) => string,
6665
loadTranslations?: (namespace: string, load?: LoadTranslationsFn) => Promise<string>,
@@ -114,12 +113,6 @@ export const useTranslation = (namespaces: string[] = [], load: LoadTranslations
114113
return { ...context, isLoaded }
115114
}
116115

117-
// https://formatjs.io/docs/intl-messageformat/
118-
const getTranslationFormat = memoizeIntlConstructor(IntlTranslationFormat)
119-
const getNumberFormat = memoizeIntlConstructor(Intl.NumberFormat)
120-
const getDateTimeFormat = memoizeIntlConstructor(Intl.DateTimeFormat)
121-
const getListFormat = memoizeIntlConstructor(Intl.ListFormat)
122-
123116
type LoadTranslationsFn = ({ namespace, locale }: { namespace: string, locale: string}) => Promise<{ default: Translations}>
124117
type LoadLocaleFn = (locale: string) => Promise<Locale>
125118

@@ -216,17 +209,13 @@ const I18nContextProvider = ({
216209
)
217210

218211
const formatNumber = useCallback(
219-
// intl-format-chache does not forwrad return types
220-
// eslint-disable-next-line
221-
(numb: number, options?: Intl.NumberFormatOptions) => getNumberFormat(currentLocale, options).format(numb),
212+
(numb: number, options?: Intl.NumberFormatOptions) => formatters.getNumberFormat(currentLocale, options).format(numb),
222213
[currentLocale],
223214
)
224215

225216
const formatList = useCallback(
226-
(listFormat: string[], options?: Intl.ListFormatOptions) =>
227-
// intl-format-chache does not forwrad return types
228-
// eslint-disable-next-line
229-
getListFormat(currentLocale, options).format(listFormat),
217+
(listFormat: [string | undefined], options?: Intl.ListFormatOptions) =>
218+
formatters.getListFormat(currentLocale, options).format(listFormat),
230219
[currentLocale],
231220
)
232221

@@ -235,7 +224,7 @@ const I18nContextProvider = ({
235224
// be able to use formatNumber directly
236225
const formatUnit = useCallback(
237226
(value: number, options: FormatUnitOptions) =>
238-
unitFormat(currentLocale, value, options, getTranslationFormat),
227+
unitFormat(currentLocale, value, options),
239228
[currentLocale],
240229
)
241230

@@ -248,7 +237,7 @@ const I18nContextProvider = ({
248237
const datetime = useCallback(
249238
// intl-format-chache does not forwrad return types
250239
// eslint-disable-next-line
251-
(date: Date | number, options?: Intl.DateTimeFormatOptions): string => getDateTimeFormat(currentLocale, options).format(date),
240+
(date: Date | number, options?: Intl.DateTimeFormatOptions): string => formatters.getDateTimeFormat(currentLocale, options).format(date),
252241
[currentLocale],
253242
)
254243

@@ -283,7 +272,7 @@ const I18nContextProvider = ({
283272
[dateFnsLocale],
284273
)
285274

286-
const translate = useCallback(
275+
const translate = useCallback<TranslateFn>(
287276
(key: string, context?: Record<string, PrimitiveType>) => {
288277
const value = translations[currentLocale]?.[key]
289278
if (!value) {
@@ -296,7 +285,7 @@ const I18nContextProvider = ({
296285
if (context) {
297286
// intl-format-chache does not forwrad return types
298287
// eslint-disable-next-line
299-
return getTranslationFormat(value, currentLocale).format(context)
288+
return formatters.getTranslationFormat(value, currentLocale).format(context) as string
300289
}
301290

302291
return value

yarn.lock

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,7 +1209,7 @@
12091209
"@formatjs/intl-localematcher" "0.2.19"
12101210
tslib "^2.1.0"
12111211

1212-
"@formatjs/[email protected]":
1212+
"@formatjs/[email protected]", "@formatjs/fast-memoize@^1.1.2":
12131213
version "1.1.2"
12141214
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.1.2.tgz#09c8771484095c07c752b824b142d6c2e2c8264f"
12151215
integrity sha512-HfN6D9yd9vhypWwTVpJHqoSb50KFuoxZ2ZS6AM1XNSyihWk7JJXjZ3mgvboJU4eNSsdxAWwR1ke5KIOobEVwBA==
@@ -5031,11 +5031,6 @@ interpret@^1.0.0:
50315031
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
50325032
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
50335033

5034-
intl-format-cache@^4.3.1:
5035-
version "4.3.1"
5036-
resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-4.3.1.tgz#484d31a9872161e6c02139349b259a6229ade377"
5037-
integrity sha512-OEUYNA7D06agqPOYhbTkl0T8HA3QKSuwWh1HiClEnpd9vw7N+3XsQt5iZ0GUEchp5CW1fQk/tary+NsbF3yQ1Q==
5038-
50395034
intl-messageformat@^9.5.3:
50405035
version "9.8.2"
50415036
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.8.2.tgz#5528334506f9af660ec3f7ea8f3584bba77af8d4"

0 commit comments

Comments
 (0)