diff --git a/packages/format-icu/package.json b/packages/format-icu/package.json index 2704c4cf12..fe3ec4e3cf 100644 --- a/packages/format-icu/package.json +++ b/packages/format-icu/package.json @@ -49,6 +49,9 @@ "src/**/*" ], "devDependencies": { + "@formatjs/ecma402-abstract": "^1.17.2", + "@formatjs/fast-memoize": "^2.2.0", + "@formatjs/icu-messageformat-parser": "^2.6.2", "@rollup/plugin-node-resolve": "13.3.0", "@rollup/plugin-typescript": "8.3.4", "@testing-library/jest-dom": "^5.16.5", @@ -57,7 +60,6 @@ "@types/node": "^17.0.8", "@types/testing-library__jest-dom": "^5.14.5", "concurrently": "7.3.0", - "intl-messageformat": "^9.9.1", "jest": "^27.2.4", "jest-fetch-mock": "^3.0.3", "rollup": "^2.56.3", diff --git a/packages/format-icu/src/IntlMessageFormat/error.ts b/packages/format-icu/src/IntlMessageFormat/error.ts new file mode 100644 index 0000000000..12614347c6 --- /dev/null +++ b/packages/format-icu/src/IntlMessageFormat/error.ts @@ -0,0 +1,65 @@ +export enum ErrorCode { + // When we have a placeholder but no value to format + MISSING_VALUE = 'MISSING_VALUE', + // When value supplied is invalid + INVALID_VALUE = 'INVALID_VALUE', + // When we need specific Intl API but it's not available + MISSING_INTL_API = 'MISSING_INTL_API', +} + +export class FormatError extends Error { + public readonly code: ErrorCode; + /** + * Original message we're trying to format + * `undefined` if we're only dealing w/ AST + * + * @type {(string | undefined)} + * @memberof FormatError + */ + public readonly originalMessage: string | undefined; + constructor(msg: string, code: ErrorCode, originalMessage?: string) { + super(msg); + this.code = code; + this.originalMessage = originalMessage; + } + public toString() { + return `[formatjs Error: ${this.code}] ${this.message}`; + } +} + +export class InvalidValueError extends FormatError { + constructor( + variableId: string, + value: any, + options: string[], + originalMessage?: string + ) { + super( + `Invalid values for "${variableId}": "${value}". Options are "${Object.keys( + options + ).join('", "')}"`, + ErrorCode.INVALID_VALUE, + originalMessage + ); + } +} + +export class InvalidValueTypeError extends FormatError { + constructor(value: any, type: string, originalMessage?: string) { + super( + `Value for "${value}" must be of type ${type}`, + ErrorCode.INVALID_VALUE, + originalMessage + ); + } +} + +export class MissingValueError extends FormatError { + constructor(variableId: string, originalMessage?: string) { + super( + `The intl string context variable "${variableId}" was not provided to the string "${originalMessage}"`, + ErrorCode.MISSING_VALUE, + originalMessage + ); + } +} diff --git a/packages/format-icu/src/IntlMessageFormat/formatters.ts b/packages/format-icu/src/IntlMessageFormat/formatters.ts new file mode 100644 index 0000000000..5264864b60 --- /dev/null +++ b/packages/format-icu/src/IntlMessageFormat/formatters.ts @@ -0,0 +1,326 @@ +import { NumberFormatOptions } from '@formatjs/ecma402-abstract'; +import { + isArgumentElement, + isDateElement, + isDateTimeSkeleton, + isLiteralElement, + isNumberElement, + isNumberSkeleton, + isPluralElement, + isPoundElement, + isSelectElement, + isTimeElement, + MessageFormatElement, + isTagElement, + ExtendedNumberFormatOptions, +} from '@formatjs/icu-messageformat-parser'; +import { + MissingValueError, + InvalidValueError, + ErrorCode, + FormatError, + InvalidValueTypeError, +} from './error'; + +declare global { + namespace FormatjsIntl { + interface Message {} + interface IntlConfig {} + interface Formats {} + } +} + +type Format = Source extends keyof FormatjsIntl.Formats + ? FormatjsIntl.Formats[Source] + : string; + +export interface Formats { + number: Record, NumberFormatOptions>; + date: Record, Intl.DateTimeFormatOptions>; + time: Record, Intl.DateTimeFormatOptions>; +} + +export interface FormatterCache { + number: Record; + dateTime: Record; + pluralRules: Record; +} + +export interface Formatters { + getNumberFormat( + locals?: string | string[], + opts?: NumberFormatOptions + ): Intl.NumberFormat; + getDateTimeFormat( + ...args: ConstructorParameters + ): Intl.DateTimeFormat; + getPluralRules( + ...args: ConstructorParameters + ): Intl.PluralRules; +} + +export enum PART_TYPE { + literal, + object, +} + +export interface LiteralPart { + type: PART_TYPE.literal; + value: string; +} + +export interface ObjectPart { + type: PART_TYPE.object; + value: T; +} + +export type MessageFormatPart = LiteralPart | ObjectPart; + +export type PrimitiveType = string | number | boolean | null | undefined | Date; + +function mergeLiteral( + parts: MessageFormatPart[] +): MessageFormatPart[] { + if (parts.length < 2) { + return parts; + } + return parts.reduce((all, part) => { + const lastPart = all[all.length - 1]; + if ( + !lastPart || + lastPart.type !== PART_TYPE.literal || + part.type !== PART_TYPE.literal + ) { + all.push(part); + } else { + lastPart.value += part.value; + } + return all; + }, [] as MessageFormatPart[]); +} + +export function isFormatXMLElementFn( + el: PrimitiveType | T | FormatXMLElementFn +): el is FormatXMLElementFn { + return typeof el === 'function'; +} + +// TODO(skeleton): add skeleton support +export function formatToParts( + els: MessageFormatElement[], + locales: string | string[], + formatters: Formatters, + formats: Formats, + values?: Record>, + currentPluralValue?: number, + // For debugging + originalMessage?: string +): MessageFormatPart[] { + // Hot path for straight simple msg translations + if (els.length === 1 && isLiteralElement(els[0])) { + return [ + { + type: PART_TYPE.literal, + value: els[0].value, + }, + ]; + } + const result: MessageFormatPart[] = []; + for (const el of els) { + // Exit early for string parts. + if (isLiteralElement(el)) { + result.push({ + type: PART_TYPE.literal, + value: el.value, + }); + continue; + } + // TODO: should this part be literal type? + // Replace `#` in plural rules with the actual numeric value. + if (isPoundElement(el)) { + if (typeof currentPluralValue === 'number') { + result.push({ + type: PART_TYPE.literal, + value: formatters.getNumberFormat(locales).format(currentPluralValue), + }); + } + continue; + } + + const { value: varName } = el; + + // Enforce that all required values are provided by the caller. + if (!(values && varName in values)) { + throw new MissingValueError(varName, originalMessage); + } + + let value = values[varName]; + if (isArgumentElement(el)) { + if (!value || typeof value === 'string' || typeof value === 'number') { + value = + typeof value === 'string' || typeof value === 'number' + ? String(value) + : ''; + } + if (isFormatXMLElementFn(value)) { + let chunks = value([]); + if (!Array.isArray(chunks)) { + chunks = [chunks]; + } + result.push( + ...chunks.map((c): MessageFormatPart => { + return { + type: + typeof c === 'string' ? PART_TYPE.literal : PART_TYPE.object, + value: c, + } as MessageFormatPart; + }) + ); + } + result.push({ + type: typeof value === 'string' ? PART_TYPE.literal : PART_TYPE.object, + value, + } as ObjectPart); + continue; + } + + // Recursively format plural and select parts' option — which can be a + // nested pattern structure. The choosing of the option to use is + // abstracted-by and delegated-to the part helper object. + if (isDateElement(el)) { + const style = + typeof el.style === 'string' + ? formats.date[el.style] + : isDateTimeSkeleton(el.style) + ? el.style.parsedOptions + : undefined; + result.push({ + type: PART_TYPE.literal, + value: formatters + .getDateTimeFormat(locales, style) + .format(value as number), + }); + continue; + } + if (isTimeElement(el)) { + const style = + typeof el.style === 'string' + ? formats.time[el.style] + : isDateTimeSkeleton(el.style) + ? el.style.parsedOptions + : formats.time.medium; + result.push({ + type: PART_TYPE.literal, + value: formatters + .getDateTimeFormat(locales, style) + .format(value as number), + }); + continue; + } + if (isNumberElement(el)) { + const style = + typeof el.style === 'string' + ? formats.number[el.style] + : isNumberSkeleton(el.style) + ? el.style.parsedOptions + : undefined; + + if (style && (style as ExtendedNumberFormatOptions).scale) { + value = + (value as number) * + ((style as ExtendedNumberFormatOptions).scale || 1); + } + result.push({ + type: PART_TYPE.literal, + value: formatters + .getNumberFormat(locales, style) + .format(value as number), + }); + continue; + } + if (isTagElement(el)) { + const { children, value } = el; + const formatFn = values[value]; + if (!isFormatXMLElementFn(formatFn)) { + throw new InvalidValueTypeError(value, 'function', originalMessage); + } + const parts = formatToParts( + children, + locales, + formatters, + formats, + values, + currentPluralValue + ); + let chunks = formatFn(parts.map((p) => p.value)); + if (!Array.isArray(chunks)) { + chunks = [chunks]; + } + result.push( + ...chunks.map((c): MessageFormatPart => { + return { + type: typeof c === 'string' ? PART_TYPE.literal : PART_TYPE.object, + value: c, + } as MessageFormatPart; + }) + ); + } + if (isSelectElement(el)) { + const opt = el.options[value as string] || el.options.other; + if (!opt) { + throw new InvalidValueError( + el.value, + value, + Object.keys(el.options), + originalMessage + ); + } + result.push( + ...formatToParts(opt.value, locales, formatters, formats, values) + ); + continue; + } + if (isPluralElement(el)) { + let opt = el.options[`=${value}`]; + if (!opt) { + if (!Intl.PluralRules) { + throw new FormatError( + `Intl.PluralRules is not available in this environment. +Try polyfilling it using "@formatjs/intl-pluralrules" +`, + ErrorCode.MISSING_INTL_API, + originalMessage + ); + } + const rule = formatters + .getPluralRules(locales, { type: el.pluralType }) + .select((value as number) - (el.offset || 0)); + opt = el.options[rule] || el.options.other; + } + if (!opt) { + throw new InvalidValueError( + el.value, + value, + Object.keys(el.options), + originalMessage + ); + } + result.push( + ...formatToParts( + opt.value, + locales, + formatters, + formats, + values, + (value as number) - (el.offset || 0) + ) + ); + continue; + } + } + return mergeLiteral(result); +} + +export type FormatXMLElementFn = ( + parts: Array +) => R; diff --git a/packages/format-icu/src/IntlMessageFormat/index.ts b/packages/format-icu/src/IntlMessageFormat/index.ts new file mode 100644 index 0000000000..c1add91fe5 --- /dev/null +++ b/packages/format-icu/src/IntlMessageFormat/index.ts @@ -0,0 +1,288 @@ +import { + parse, + MessageFormatElement, + ParserOptions, +} from '@formatjs/icu-messageformat-parser'; +import { memoize, Cache, strategies } from '@formatjs/fast-memoize'; +import { + FormatterCache, + Formatters, + Formats, + formatToParts, + FormatXMLElementFn, + PrimitiveType, + MessageFormatPart, + PART_TYPE, +} from './formatters'; + +// -- MessageFormat -------------------------------------------------------- + +function mergeConfig(c1: Record, c2?: Record) { + if (!c2) { + return c1; + } + return { + ...(c1 || {}), + ...(c2 || {}), + ...Object.keys(c1).reduce((all: Record, k) => { + all[k] = { + ...c1[k], + ...(c2[k] || {}), + }; + return all; + }, {}), + }; +} + +function mergeConfigs( + defaultConfig: Formats, + configs?: Partial +): Formats { + if (!configs) { + return defaultConfig; + } + + return (Object.keys(defaultConfig) as Array).reduce( + (all: Formats, k: keyof Formats) => { + all[k] = mergeConfig(defaultConfig[k], configs[k]); + return all; + }, + { ...defaultConfig } + ); +} + +export interface Options extends Omit { + formatters?: Formatters; +} + +function createFastMemoizeCache( + store: Record +): Cache { + return { + create() { + return { + get(key) { + return store[key]; + }, + set(key, value) { + store[key] = value; + }, + }; + }, + }; +} + +function createDefaultFormatters( + cache: FormatterCache = { + number: {}, + dateTime: {}, + pluralRules: {}, + } +): Formatters { + return { + getNumberFormat: memoize((...args) => new Intl.NumberFormat(...args), { + cache: createFastMemoizeCache(cache.number), + strategy: strategies.variadic, + }), + getDateTimeFormat: memoize((...args) => new Intl.DateTimeFormat(...args), { + cache: createFastMemoizeCache(cache.dateTime), + strategy: strategies.variadic, + }), + getPluralRules: memoize((...args) => new Intl.PluralRules(...args), { + cache: createFastMemoizeCache(cache.pluralRules), + strategy: strategies.variadic, + }), + }; +} + +export class IntlMessageFormat { + private readonly ast: MessageFormatElement[]; + private readonly locales: string | string[]; + private readonly resolvedLocale?: Intl.Locale; + private readonly formatters: Formatters; + private readonly formats: Formats; + private readonly message: string | undefined; + private readonly formatterCache: FormatterCache = { + number: {}, + dateTime: {}, + pluralRules: {}, + }; + constructor( + message: string | MessageFormatElement[], + locales: string | string[] = IntlMessageFormat.defaultLocale, + overrideFormats?: Partial, + opts?: Options + ) { + // Defined first because it's used to build the format pattern. + this.locales = locales; + this.resolvedLocale = IntlMessageFormat.resolveLocale(locales); + + if (typeof message === 'string') { + this.message = message; + if (!IntlMessageFormat.__parse) { + throw new TypeError( + 'IntlMessageFormat.__parse must be set to process `message` of type `string`' + ); + } + const { formatters, ...parseOpts } = opts || {}; + // Parse string messages into an AST. + this.ast = IntlMessageFormat.__parse(message, { + ...parseOpts, + locale: this.resolvedLocale, + }); + } else { + this.ast = message; + } + + if (!Array.isArray(this.ast)) { + throw new TypeError('A message must be provided as a String or AST.'); + } + + // Creates a new object with the specified `formats` merged with the default + // formats. + this.formats = mergeConfigs(IntlMessageFormat.formats, overrideFormats); + + this.formatters = + (opts && opts.formatters) || createDefaultFormatters(this.formatterCache); + } + + format = ( + values?: Record> + ) => { + const parts = this.formatToParts(values); + // Hot path for straight simple msg translations + if (parts.length === 1) { + return parts[0].value; + } + const result = parts.reduce((all, part) => { + if ( + !all.length || + part.type !== PART_TYPE.literal || + typeof all[all.length - 1] !== 'string' + ) { + all.push(part.value); + } else { + all[all.length - 1] += part.value; + } + return all; + }, [] as Array); + + if (result.length <= 1) { + return result[0] || ''; + } + return result; + }; + formatToParts = ( + values?: Record> + ): MessageFormatPart[] => + formatToParts( + this.ast, + this.locales, + this.formatters, + this.formats, + values, + undefined, + this.message + ); + resolvedOptions = () => ({ + locale: + this.resolvedLocale?.toString() || + Intl.NumberFormat.supportedLocalesOf(this.locales)[0], + }); + getAst = () => this.ast; + private static memoizedDefaultLocale: string | null = null; + + static get defaultLocale() { + if (!IntlMessageFormat.memoizedDefaultLocale) { + IntlMessageFormat.memoizedDefaultLocale = + new Intl.NumberFormat().resolvedOptions().locale; + } + + return IntlMessageFormat.memoizedDefaultLocale; + } + static resolveLocale = ( + locales: string | string[] + ): Intl.Locale | undefined => { + if (typeof Intl.Locale === 'undefined') { + return; + } + const supportedLocales = Intl.NumberFormat.supportedLocalesOf(locales); + if (supportedLocales.length > 0) { + return new Intl.Locale(supportedLocales[0]); + } + + return new Intl.Locale(typeof locales === 'string' ? locales : locales[0]); + }; + static __parse: typeof parse | undefined = parse; + // Default format options used as the prototype of the `formats` provided to the + // constructor. These are used when constructing the internal Intl.NumberFormat + // and Intl.DateTimeFormat instances. + static formats: Formats = { + number: { + integer: { + maximumFractionDigits: 0, + }, + currency: { + style: 'currency', + }, + + percent: { + style: 'percent', + }, + }, + + date: { + short: { + month: 'numeric', + day: 'numeric', + year: '2-digit', + }, + + medium: { + month: 'short', + day: 'numeric', + year: 'numeric', + }, + + long: { + month: 'long', + day: 'numeric', + year: 'numeric', + }, + + full: { + weekday: 'long', + month: 'long', + day: 'numeric', + year: 'numeric', + }, + }, + + time: { + short: { + hour: 'numeric', + minute: 'numeric', + }, + + medium: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + }, + + long: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }, + + full: { + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short', + }, + }, + }; +} diff --git a/packages/format-icu/src/createFormatIcu.ts b/packages/format-icu/src/createFormatIcu.ts index c8ff891eaa..dbcee4f3df 100644 --- a/packages/format-icu/src/createFormatIcu.ts +++ b/packages/format-icu/src/createFormatIcu.ts @@ -1,5 +1,5 @@ -import IntlMessageFormat from 'intl-messageformat'; import type { FinalFormatterMiddleware } from '@tolgee/core'; +import { IntlMessageFormat } from './IntlMessageFormat'; export const createFormatIcu = (): FinalFormatterMiddleware => { const locales = new Map() as Map; diff --git a/packages/format-icu/tsconfig.json b/packages/format-icu/tsconfig.json index e5ed6896e3..581366c36d 100644 --- a/packages/format-icu/tsconfig.json +++ b/packages/format-icu/tsconfig.json @@ -12,7 +12,17 @@ /* Language and Environment */ "target": "es2017" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": [ /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "dom", + "ES2017.Intl", + "ES2018.Intl", + "ES2020.Intl", + "es2021.intl", + "ES2022.Intl", + "ESNext.Intl", + "ES2021.String", + "ES2017" + ], // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4498891a46..87c27754b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,6 +175,15 @@ importers: packages/format-icu: devDependencies: + '@formatjs/ecma402-abstract': + specifier: ^1.17.2 + version: 1.17.2 + '@formatjs/fast-memoize': + specifier: ^2.2.0 + version: 2.2.0 + '@formatjs/icu-messageformat-parser': + specifier: ^2.6.2 + version: 2.6.2 '@rollup/plugin-node-resolve': specifier: 13.3.0 version: 13.3.0(rollup@2.79.1) @@ -199,9 +208,6 @@ importers: concurrently: specifier: 7.3.0 version: 7.3.0 - intl-messageformat: - specifier: ^9.9.1 - version: 9.13.0 jest: specifier: ^27.2.4 version: 27.5.1(ts-node@10.9.1) @@ -3293,16 +3299,6 @@ packages: '@babel/helper-plugin-utils': 7.20.2 dev: false - /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.21.3): - resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.3 - '@babel/helper-plugin-utils': 7.20.2 - dev: false - /@babel/plugin-syntax-import-assertions@7.18.6(@babel/core@7.19.3): resolution: {integrity: sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==} engines: {node: '>=6.9.0'} @@ -4583,17 +4579,17 @@ packages: '@babel/types': 7.21.0 dev: false - /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.21.3): + /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.21.0): resolution: {integrity: sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.21.3 + '@babel/core': 7.21.0 '@babel/helper-annotate-as-pure': 7.18.6 '@babel/helper-module-imports': 7.18.6 '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3) + '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.0) '@babel/types': 7.21.3 dev: false @@ -6503,6 +6499,13 @@ packages: tslib: 2.5.0 dev: false + /@formatjs/ecma402-abstract@1.17.2: + resolution: {integrity: sha512-k2mTh0m+IV1HRdU0xXM617tSQTi53tVR2muvYOsBeYcUgEAyxV1FOC7Qj279th3fBVQ+Dj6muvNJZcHSPNdbKg==} + dependencies: + '@formatjs/intl-localematcher': 0.4.2 + tslib: 2.5.0 + dev: true + /@formatjs/fast-memoize@1.2.1: resolution: {integrity: sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==} dependencies: @@ -6514,6 +6517,12 @@ packages: tslib: 2.5.0 dev: false + /@formatjs/fast-memoize@2.2.0: + resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} + dependencies: + tslib: 2.5.0 + dev: true + /@formatjs/icu-messageformat-parser@2.1.0: resolution: {integrity: sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==} dependencies: @@ -6529,6 +6538,14 @@ packages: tslib: 2.5.0 dev: false + /@formatjs/icu-messageformat-parser@2.6.2: + resolution: {integrity: sha512-nF/Iww7sc5h+1MBCDRm68qpHTCG4xvGzYs/x9HFcDETSGScaJ1Fcadk5U/NXjXeCtzD+DhN4BAwKFVclHfKMdA==} + dependencies: + '@formatjs/ecma402-abstract': 1.17.2 + '@formatjs/icu-skeleton-parser': 1.6.2 + tslib: 2.5.0 + dev: true + /@formatjs/icu-skeleton-parser@1.3.18: resolution: {integrity: sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==} dependencies: @@ -6542,6 +6559,13 @@ packages: '@formatjs/ecma402-abstract': 1.11.4 tslib: 2.5.0 + /@formatjs/icu-skeleton-parser@1.6.2: + resolution: {integrity: sha512-VtB9Slo4ZL6QgtDFJ8Injvscf0xiDd4bIV93SOJTBjUF4xe2nAWOoSjLEtqIG+hlIs1sNrVKAaFo3nuTI4r5ZA==} + dependencies: + '@formatjs/ecma402-abstract': 1.17.2 + tslib: 2.5.0 + dev: true + /@formatjs/intl-localematcher@0.2.25: resolution: {integrity: sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==} dependencies: @@ -6553,6 +6577,12 @@ packages: tslib: 2.5.0 dev: false + /@formatjs/intl-localematcher@0.4.2: + resolution: {integrity: sha512-BGdtJFmaNJy5An/Zan4OId/yR9Ih1OojFjcduX/xOvq798OgWSyDtd6Qd5jqJXwJs1ipe4Fxu9+cshic5Ox2tA==} + dependencies: + tslib: 2.5.0 + dev: true + /@gar/promisify@1.1.3: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: true @@ -17795,8 +17825,8 @@ packages: '@babel/plugin-transform-react-jsx': ^7.14.9 eslint: ^8.1.0 dependencies: - '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.3) - '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.3) + '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.0) + '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.0) eslint: 8.47.0 lodash: 4.17.21 string-natural-compare: 3.0.1 @@ -29193,7 +29223,7 @@ packages: peerDependencies: rollup: ^2.0.0 dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.18.6 jest-worker: 26.6.2 rollup: 2.78.1 serialize-javascript: 4.0.0 @@ -29205,7 +29235,7 @@ packages: peerDependencies: rollup: ^2.0.0 dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.18.6 jest-worker: 26.6.2 rollup: 2.79.0 serialize-javascript: 4.0.0 @@ -29228,7 +29258,7 @@ packages: peerDependencies: rollup: ^2.0.0 dependencies: - '@babel/code-frame': 7.22.5 + '@babel/code-frame': 7.18.6 jest-worker: 26.6.2 rollup: 3.18.0 serialize-javascript: 4.0.0