intl-messageformat 11.2.0 → 11.2.1

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "intl-messageformat",
3
3
  "description": "Formats ICU Message strings with number, date, plural, and select placeholders to create localized messages.",
4
- "version": "11.2.0",
4
+ "version": "11.2.1",
5
5
  "license": "BSD-3-Clause",
6
6
  "author": "Eric Ferraiuolo <eferraiuolo@gmail.com>",
7
7
  "type": "module",
@@ -11,9 +11,8 @@
11
11
  ".": "./index.js"
12
12
  },
13
13
  "dependencies": {
14
- "@formatjs/ecma402-abstract": "3.2.0",
15
- "@formatjs/icu-messageformat-parser": "3.5.3",
16
- "@formatjs/fast-memoize": "3.1.1"
14
+ "@formatjs/fast-memoize": "3.1.2",
15
+ "@formatjs/icu-messageformat-parser": "3.5.4"
17
16
  },
18
17
  "bugs": "https://github.com/formatjs/formatjs/issues",
19
18
  "contributors": [
package/src/core.d.ts DELETED
@@ -1,26 +0,0 @@
1
- import { type MessageFormatElement, parse, type ParserOptions } from "@formatjs/icu-messageformat-parser";
2
- import { type Formats, type Formatters, type FormatXMLElementFn, type MessageFormatPart, type PrimitiveType } from "./formatters.js";
3
- export interface Options extends Omit<ParserOptions, "locale"> {
4
- formatters?: Formatters;
5
- }
6
- export declare class IntlMessageFormat {
7
- private readonly ast;
8
- private readonly locales;
9
- private readonly resolvedLocale?;
10
- private readonly formatters;
11
- private readonly formats;
12
- private readonly message;
13
- private readonly formatterCache;
14
- constructor(message: string | MessageFormatElement[], locales?: string | string[], overrideFormats?: Partial<Formats>, opts?: Options);
15
- format: <T = void>(values?: Record<string, PrimitiveType | T | FormatXMLElementFn<T>>) => string | T | (string | T)[];
16
- formatToParts: <T>(values?: Record<string, PrimitiveType | T | FormatXMLElementFn<T>>) => MessageFormatPart<T>[];
17
- resolvedOptions: () => {
18
- locale: string;
19
- };
20
- getAst: () => MessageFormatElement[];
21
- private static memoizedDefaultLocale;
22
- static get defaultLocale(): string;
23
- static resolveLocale: (locales: string | string[]) => Intl.Locale | undefined;
24
- static __parse: typeof parse | undefined;
25
- static formats: Formats;
26
- }
package/src/core.js DELETED
@@ -1,201 +0,0 @@
1
- /*
2
- Copyright (c) 2014, Yahoo! Inc. All rights reserved.
3
- Copyrights licensed under the New BSD License.
4
- See the accompanying LICENSE file for terms.
5
- */
6
- import { memoize, strategies } from "@formatjs/fast-memoize";
7
- import { parse } from "@formatjs/icu-messageformat-parser";
8
- import { formatToParts, PART_TYPE } from "./formatters.js";
9
- // -- MessageFormat --------------------------------------------------------
10
- function mergeConfig(c1, c2) {
11
- if (!c2) {
12
- return c1;
13
- }
14
- return {
15
- ...c1,
16
- ...c2,
17
- ...Object.keys(c1).reduce((all, k) => {
18
- all[k] = {
19
- ...c1[k],
20
- ...c2[k]
21
- };
22
- return all;
23
- }, {})
24
- };
25
- }
26
- function mergeConfigs(defaultConfig, configs) {
27
- if (!configs) {
28
- return defaultConfig;
29
- }
30
- return Object.keys(defaultConfig).reduce((all, k) => {
31
- all[k] = mergeConfig(defaultConfig[k], configs[k]);
32
- return all;
33
- }, { ...defaultConfig });
34
- }
35
- function createFastMemoizeCache(store) {
36
- return { create() {
37
- return {
38
- get(key) {
39
- return store[key];
40
- },
41
- set(key, value) {
42
- store[key] = value;
43
- }
44
- };
45
- } };
46
- }
47
- function createDefaultFormatters(cache = {
48
- number: {},
49
- dateTime: {},
50
- pluralRules: {}
51
- }) {
52
- return {
53
- getNumberFormat: memoize((...args) => new Intl.NumberFormat(...args), {
54
- cache: createFastMemoizeCache(cache.number),
55
- strategy: strategies.variadic
56
- }),
57
- getDateTimeFormat: memoize((...args) => new Intl.DateTimeFormat(...args), {
58
- cache: createFastMemoizeCache(cache.dateTime),
59
- strategy: strategies.variadic
60
- }),
61
- getPluralRules: memoize((...args) => new Intl.PluralRules(...args), {
62
- cache: createFastMemoizeCache(cache.pluralRules),
63
- strategy: strategies.variadic
64
- })
65
- };
66
- }
67
- export class IntlMessageFormat {
68
- ast;
69
- locales;
70
- resolvedLocale;
71
- formatters;
72
- formats;
73
- message;
74
- formatterCache = {
75
- number: {},
76
- dateTime: {},
77
- pluralRules: {}
78
- };
79
- constructor(message, locales = IntlMessageFormat.defaultLocale, overrideFormats, opts) {
80
- // Defined first because it's used to build the format pattern.
81
- this.locales = locales;
82
- this.resolvedLocale = IntlMessageFormat.resolveLocale(locales);
83
- if (typeof message === "string") {
84
- this.message = message;
85
- if (!IntlMessageFormat.__parse) {
86
- throw new TypeError("IntlMessageFormat.__parse must be set to process `message` of type `string`");
87
- }
88
- const { ...parseOpts } = opts || {};
89
- // Parse string messages into an AST.
90
- this.ast = IntlMessageFormat.__parse(message, {
91
- ...parseOpts,
92
- locale: this.resolvedLocale
93
- });
94
- } else {
95
- this.ast = message;
96
- }
97
- if (!Array.isArray(this.ast)) {
98
- throw new TypeError("A message must be provided as a String or AST.");
99
- }
100
- // Creates a new object with the specified `formats` merged with the default
101
- // formats.
102
- this.formats = mergeConfigs(IntlMessageFormat.formats, overrideFormats);
103
- this.formatters = opts && opts.formatters || createDefaultFormatters(this.formatterCache);
104
- }
105
- format = (values) => {
106
- const parts = this.formatToParts(values);
107
- // Hot path for straight simple msg translations
108
- if (parts.length === 1) {
109
- return parts[0].value;
110
- }
111
- const result = parts.reduce((all, part) => {
112
- if (!all.length || part.type !== PART_TYPE.literal || typeof all[all.length - 1] !== "string") {
113
- all.push(part.value);
114
- } else {
115
- all[all.length - 1] += part.value;
116
- }
117
- return all;
118
- }, []);
119
- if (result.length <= 1) {
120
- return result[0] || "";
121
- }
122
- return result;
123
- };
124
- formatToParts = (values) => formatToParts(this.ast, this.locales, this.formatters, this.formats, values, undefined, this.message);
125
- resolvedOptions = () => ({ locale: this.resolvedLocale?.toString() || Intl.NumberFormat.supportedLocalesOf(this.locales)[0] });
126
- getAst = () => this.ast;
127
- static memoizedDefaultLocale = null;
128
- static get defaultLocale() {
129
- if (!IntlMessageFormat.memoizedDefaultLocale) {
130
- IntlMessageFormat.memoizedDefaultLocale = new Intl.NumberFormat().resolvedOptions().locale;
131
- }
132
- return IntlMessageFormat.memoizedDefaultLocale;
133
- }
134
- static resolveLocale = (locales) => {
135
- if (typeof Intl.Locale === "undefined") {
136
- return;
137
- }
138
- const supportedLocales = Intl.NumberFormat.supportedLocalesOf(locales);
139
- if (supportedLocales.length > 0) {
140
- return new Intl.Locale(supportedLocales[0]);
141
- }
142
- return new Intl.Locale(typeof locales === "string" ? locales : locales[0]);
143
- };
144
- static __parse = parse;
145
- // Default format options used as the prototype of the `formats` provided to the
146
- // constructor. These are used when constructing the internal Intl.NumberFormat
147
- // and Intl.DateTimeFormat instances.
148
- static formats = {
149
- number: {
150
- integer: { maximumFractionDigits: 0 },
151
- currency: { style: "currency" },
152
- percent: { style: "percent" }
153
- },
154
- date: {
155
- short: {
156
- month: "numeric",
157
- day: "numeric",
158
- year: "2-digit"
159
- },
160
- medium: {
161
- month: "short",
162
- day: "numeric",
163
- year: "numeric"
164
- },
165
- long: {
166
- month: "long",
167
- day: "numeric",
168
- year: "numeric"
169
- },
170
- full: {
171
- weekday: "long",
172
- month: "long",
173
- day: "numeric",
174
- year: "numeric"
175
- }
176
- },
177
- time: {
178
- short: {
179
- hour: "numeric",
180
- minute: "numeric"
181
- },
182
- medium: {
183
- hour: "numeric",
184
- minute: "numeric",
185
- second: "numeric"
186
- },
187
- long: {
188
- hour: "numeric",
189
- minute: "numeric",
190
- second: "numeric",
191
- timeZoneName: "short"
192
- },
193
- full: {
194
- hour: "numeric",
195
- minute: "numeric",
196
- second: "numeric",
197
- timeZoneName: "short"
198
- }
199
- }
200
- };
201
- }
package/src/error.d.ts DELETED
@@ -1,27 +0,0 @@
1
- export declare enum ErrorCode {
2
- MISSING_VALUE = "MISSING_VALUE",
3
- INVALID_VALUE = "INVALID_VALUE",
4
- MISSING_INTL_API = "MISSING_INTL_API"
5
- }
6
- export declare class FormatError extends Error {
7
- readonly code: ErrorCode;
8
- /**
9
- * Original message we're trying to format
10
- * `undefined` if we're only dealing w/ AST
11
- *
12
- * @type {(string | undefined)}
13
- * @memberof FormatError
14
- */
15
- readonly originalMessage: string | undefined;
16
- constructor(msg: string, code: ErrorCode, originalMessage?: string);
17
- toString(): string;
18
- }
19
- export declare class InvalidValueError extends FormatError {
20
- constructor(variableId: string, value: any, options: string[], originalMessage?: string);
21
- }
22
- export declare class InvalidValueTypeError extends FormatError {
23
- constructor(value: any, type: string, originalMessage?: string);
24
- }
25
- export declare class MissingValueError extends FormatError {
26
- constructor(variableId: string, originalMessage?: string);
27
- }
package/src/error.js DELETED
@@ -1,43 +0,0 @@
1
- export let ErrorCode = /* @__PURE__ */ function(ErrorCode) {
2
- // When we have a placeholder but no value to format
3
- ErrorCode["MISSING_VALUE"] = "MISSING_VALUE";
4
- // When value supplied is invalid
5
- ErrorCode["INVALID_VALUE"] = "INVALID_VALUE";
6
- // When we need specific Intl API but it's not available
7
- ErrorCode["MISSING_INTL_API"] = "MISSING_INTL_API";
8
- return ErrorCode;
9
- }({});
10
- export class FormatError extends Error {
11
- code;
12
- /**
13
- * Original message we're trying to format
14
- * `undefined` if we're only dealing w/ AST
15
- *
16
- * @type {(string | undefined)}
17
- * @memberof FormatError
18
- */
19
- originalMessage;
20
- constructor(msg, code, originalMessage) {
21
- super(msg);
22
- this.code = code;
23
- this.originalMessage = originalMessage;
24
- }
25
- toString() {
26
- return `[formatjs Error: ${this.code}] ${this.message}`;
27
- }
28
- }
29
- export class InvalidValueError extends FormatError {
30
- constructor(variableId, value, options, originalMessage) {
31
- super(`Invalid values for "${variableId}": "${value}". Options are "${Object.keys(options).join("\", \"")}"`, ErrorCode.INVALID_VALUE, originalMessage);
32
- }
33
- }
34
- export class InvalidValueTypeError extends FormatError {
35
- constructor(value, type, originalMessage) {
36
- super(`Value for "${value}" must be of type ${type}`, ErrorCode.INVALID_VALUE, originalMessage);
37
- }
38
- }
39
- export class MissingValueError extends FormatError {
40
- constructor(variableId, originalMessage) {
41
- super(`The intl string context variable "${variableId}" was not provided to the string "${originalMessage}"`, ErrorCode.MISSING_VALUE, originalMessage);
42
- }
43
- }
@@ -1,46 +0,0 @@
1
- import { type NumberFormatOptions } from "@formatjs/ecma402-abstract";
2
- import { type MessageFormatElement } from "@formatjs/icu-messageformat-parser";
3
- declare global {
4
- namespace FormatjsIntl {
5
- interface Message {}
6
- interface IntlConfig {}
7
- interface Formats {}
8
- }
9
- }
10
- type Format<Source = string> = Source extends keyof FormatjsIntl.Formats ? FormatjsIntl.Formats[Source] : string;
11
- export interface Formats {
12
- number: Record<Format<"number">, NumberFormatOptions>;
13
- date: Record<Format<"date">, Intl.DateTimeFormatOptions>;
14
- time: Record<Format<"time">, Intl.DateTimeFormatOptions>;
15
- }
16
- export interface FormatterCache {
17
- number: Record<string, NumberFormatOptions>;
18
- dateTime: Record<string, Intl.DateTimeFormat>;
19
- pluralRules: Record<string, Intl.PluralRules>;
20
- }
21
- export interface Formatters {
22
- getNumberFormat(locals?: string | string[], opts?: NumberFormatOptions): Intl.NumberFormat;
23
- getDateTimeFormat(...args: ConstructorParameters<typeof Intl.DateTimeFormat>): Intl.DateTimeFormat;
24
- getPluralRules(...args: ConstructorParameters<typeof Intl.PluralRules>): Intl.PluralRules;
25
- }
26
- export declare enum PART_TYPE {
27
- literal = 0,
28
- object = 1
29
- }
30
- export interface LiteralPart {
31
- type: PART_TYPE.literal;
32
- value: string;
33
- }
34
- export interface ObjectPart<T = any> {
35
- type: PART_TYPE.object;
36
- value: T;
37
- }
38
- export type MessageFormatPart<T> = LiteralPart | ObjectPart<T>;
39
- export type PrimitiveType = string | number | bigint | boolean | null | undefined | Date;
40
- export declare function isFormatXMLElementFn<T>(el: PrimitiveType | T | FormatXMLElementFn<T>): el is FormatXMLElementFn<T>;
41
- export declare function formatToParts<T>(els: MessageFormatElement[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string, PrimitiveType | T | FormatXMLElementFn<T>>, currentPluralValue?: number, originalMessage?: string): MessageFormatPart<T>[];
42
- export type FormatXMLElementFn<
43
- T,
44
- R = string | T | (string | T)[]
45
- > = (parts: Array<string | T>) => R;
46
- export {};
package/src/formatters.js DELETED
@@ -1,166 +0,0 @@
1
- import "@formatjs/ecma402-abstract";
2
- import { isArgumentElement, isDateElement, isDateTimeSkeleton, isLiteralElement, isNumberElement, isNumberSkeleton, isPluralElement, isPoundElement, isSelectElement, isTagElement, isTimeElement } from "@formatjs/icu-messageformat-parser";
3
- import { ErrorCode, FormatError, InvalidValueError, InvalidValueTypeError, MissingValueError } from "./error.js";
4
- export let PART_TYPE = /* @__PURE__ */ function(PART_TYPE) {
5
- PART_TYPE[PART_TYPE["literal"] = 0] = "literal";
6
- PART_TYPE[PART_TYPE["object"] = 1] = "object";
7
- return PART_TYPE;
8
- }({});
9
- function mergeLiteral(parts) {
10
- if (parts.length < 2) {
11
- return parts;
12
- }
13
- return parts.reduce((all, part) => {
14
- const lastPart = all[all.length - 1];
15
- if (!lastPart || lastPart.type !== PART_TYPE.literal || part.type !== PART_TYPE.literal) {
16
- all.push(part);
17
- } else {
18
- lastPart.value += part.value;
19
- }
20
- return all;
21
- }, []);
22
- }
23
- export function isFormatXMLElementFn(el) {
24
- return typeof el === "function";
25
- }
26
- // TODO(skeleton): add skeleton support
27
- export function formatToParts(els, locales, formatters, formats, values, currentPluralValue, originalMessage) {
28
- // Hot path for straight simple msg translations
29
- if (els.length === 1 && isLiteralElement(els[0])) {
30
- return [{
31
- type: PART_TYPE.literal,
32
- value: els[0].value
33
- }];
34
- }
35
- const result = [];
36
- for (const el of els) {
37
- // Exit early for string parts.
38
- if (isLiteralElement(el)) {
39
- result.push({
40
- type: PART_TYPE.literal,
41
- value: el.value
42
- });
43
- continue;
44
- }
45
- // TODO: should this part be literal type?
46
- // Replace `#` in plural rules with the actual numeric value.
47
- if (isPoundElement(el)) {
48
- if (typeof currentPluralValue === "number") {
49
- result.push({
50
- type: PART_TYPE.literal,
51
- value: formatters.getNumberFormat(locales).format(currentPluralValue)
52
- });
53
- }
54
- continue;
55
- }
56
- const { value: varName } = el;
57
- // Enforce that all required values are provided by the caller.
58
- if (!(values && varName in values)) {
59
- throw new MissingValueError(varName, originalMessage);
60
- }
61
- let value = values[varName];
62
- if (isArgumentElement(el)) {
63
- if (!value || typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
64
- value = typeof value === "string" || typeof value === "number" || typeof value === "bigint" ? String(value) : "";
65
- }
66
- result.push({
67
- type: typeof value === "string" ? PART_TYPE.literal : PART_TYPE.object,
68
- value
69
- });
70
- continue;
71
- }
72
- // Recursively format plural and select parts' option — which can be a
73
- // nested pattern structure. The choosing of the option to use is
74
- // abstracted-by and delegated-to the part helper object.
75
- if (isDateElement(el)) {
76
- const style = typeof el.style === "string" ? formats.date[el.style] : isDateTimeSkeleton(el.style) ? el.style.parsedOptions : undefined;
77
- result.push({
78
- type: PART_TYPE.literal,
79
- value: formatters.getDateTimeFormat(locales, style).format(value)
80
- });
81
- continue;
82
- }
83
- if (isTimeElement(el)) {
84
- const style = typeof el.style === "string" ? formats.time[el.style] : isDateTimeSkeleton(el.style) ? el.style.parsedOptions : formats.time.medium;
85
- result.push({
86
- type: PART_TYPE.literal,
87
- value: formatters.getDateTimeFormat(locales, style).format(value)
88
- });
89
- continue;
90
- }
91
- if (isNumberElement(el)) {
92
- const style = typeof el.style === "string" ? formats.number[el.style] : isNumberSkeleton(el.style) ? el.style.parsedOptions : undefined;
93
- if (style && style.scale) {
94
- const scale = style.scale || 1;
95
- // Handle bigint scale multiplication
96
- // BigInt can only be multiplied by BigInt
97
- if (typeof value === "bigint") {
98
- // Check if scale is a safe integer that can be converted to BigInt
99
- if (!Number.isInteger(scale)) {
100
- throw new TypeError(`Cannot apply fractional scale ${scale} to bigint value. Scale must be an integer when formatting bigint.`);
101
- }
102
- value = value * BigInt(scale);
103
- } else {
104
- value = value * scale;
105
- }
106
- }
107
- result.push({
108
- type: PART_TYPE.literal,
109
- value: formatters.getNumberFormat(locales, style).format(value)
110
- });
111
- continue;
112
- }
113
- if (isTagElement(el)) {
114
- const { children, value } = el;
115
- const formatFn = values[value];
116
- if (!isFormatXMLElementFn(formatFn)) {
117
- throw new InvalidValueTypeError(value, "function", originalMessage);
118
- }
119
- const parts = formatToParts(children, locales, formatters, formats, values, currentPluralValue);
120
- let chunks = formatFn(parts.map((p) => p.value));
121
- if (!Array.isArray(chunks)) {
122
- chunks = [chunks];
123
- }
124
- result.push(...chunks.map((c) => {
125
- return {
126
- type: typeof c === "string" ? PART_TYPE.literal : PART_TYPE.object,
127
- value: c
128
- };
129
- }));
130
- }
131
- if (isSelectElement(el)) {
132
- // GH #4490: Use hasOwnProperty to avoid prototype chain issues with keys like "constructor"
133
- const key = value;
134
- const opt = (Object.prototype.hasOwnProperty.call(el.options, key) ? el.options[key] : undefined) || el.options.other;
135
- if (!opt) {
136
- throw new InvalidValueError(el.value, value, Object.keys(el.options), originalMessage);
137
- }
138
- result.push(...formatToParts(opt.value, locales, formatters, formats, values));
139
- continue;
140
- }
141
- if (isPluralElement(el)) {
142
- // GH #4490: Use hasOwnProperty to avoid prototype chain issues
143
- const exactKey = `=${value}`;
144
- let opt = Object.prototype.hasOwnProperty.call(el.options, exactKey) ? el.options[exactKey] : undefined;
145
- if (!opt) {
146
- if (!Intl.PluralRules) {
147
- throw new FormatError(`Intl.PluralRules is not available in this environment.
148
- Try polyfilling it using "@formatjs/intl-pluralrules"
149
- `, ErrorCode.MISSING_INTL_API, originalMessage);
150
- }
151
- // Convert bigint to number for PluralRules (which only accepts number)
152
- const numericValue = typeof value === "bigint" ? Number(value) : value;
153
- const rule = formatters.getPluralRules(locales, { type: el.pluralType }).select(numericValue - (el.offset || 0));
154
- opt = (Object.prototype.hasOwnProperty.call(el.options, rule) ? el.options[rule] : undefined) || el.options.other;
155
- }
156
- if (!opt) {
157
- throw new InvalidValueError(el.value, value, Object.keys(el.options), originalMessage);
158
- }
159
- // Convert bigint to number for currentPluralValue
160
- const numericValue = typeof value === "bigint" ? Number(value) : value;
161
- result.push(...formatToParts(opt.value, locales, formatters, formats, values, numericValue - (el.offset || 0)));
162
- continue;
163
- }
164
- }
165
- return mergeLiteral(result);
166
- }