intl-messageformat 7.8.4 → 8.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/CHANGELOG.md +65 -0
- package/README.md +19 -16
- package/dist/core.d.ts +3 -4
- package/dist/core.js +20 -4
- package/dist/error.d.ts +16 -0
- package/dist/error.js +44 -0
- package/dist/formatters.d.ts +7 -9
- package/dist/formatters.js +27 -169
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/umd/intl-messageformat.js +767 -416
- package/dist/umd/intl-messageformat.js.map +1 -1
- package/dist/umd/intl-messageformat.min.js +1 -1
- package/dist/umd/intl-messageformat.min.js.map +1 -1
- package/lib/core.d.ts +3 -4
- package/lib/core.js +21 -5
- package/lib/error.d.ts +16 -0
- package/lib/error.js +42 -0
- package/lib/formatters.d.ts +7 -9
- package/lib/formatters.js +28 -168
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/intl-messageformat.d.ts +30 -15
- package/package.json +3 -3
- package/src/core.ts +29 -24
- package/src/error.ts +39 -0
- package/src/formatters.ts +69 -245
- package/src/index.ts +1 -0
package/lib/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parse, MessageFormatElement } from 'intl-messageformat-parser';
|
|
2
|
-
import { FormatterCache, Formatters, Formats, FormatXMLElementFn,
|
|
2
|
+
import { FormatterCache, Formatters, Formats, FormatXMLElementFn, MessageFormatPart } from './formatters';
|
|
3
3
|
export interface Options {
|
|
4
4
|
formatters?: Formatters;
|
|
5
5
|
}
|
|
@@ -12,9 +12,8 @@ export declare class IntlMessageFormat {
|
|
|
12
12
|
private readonly message;
|
|
13
13
|
private readonly formatterCache;
|
|
14
14
|
constructor(message: string | MessageFormatElement[], locales?: string | string[], overrideFormats?: Partial<Formats>, opts?: Options);
|
|
15
|
-
format: (values?: Record<string,
|
|
16
|
-
formatToParts: (values?: Record<string,
|
|
17
|
-
formatHTMLMessage: (values?: Record<string, string | number | boolean | object | Date | FormatXMLElementFn | null | undefined> | undefined) => (string | object)[];
|
|
15
|
+
format: <T = void>(values?: Record<string, string | number | boolean | Date | T | FormatXMLElementFn<T> | null | undefined> | undefined) => string | T | (string | T)[];
|
|
16
|
+
formatToParts: <T>(values?: Record<string, string | number | boolean | Date | T | FormatXMLElementFn<T> | null | undefined> | undefined) => MessageFormatPart<T>[];
|
|
18
17
|
resolvedOptions: () => {
|
|
19
18
|
locale: string;
|
|
20
19
|
};
|
package/lib/core.js
CHANGED
|
@@ -16,7 +16,7 @@ var __assign = (this && this.__assign) || function () {
|
|
|
16
16
|
};
|
|
17
17
|
import { parse } from 'intl-messageformat-parser';
|
|
18
18
|
import memoizeIntlConstructor from 'intl-format-cache';
|
|
19
|
-
import {
|
|
19
|
+
import { formatToParts, } from './formatters';
|
|
20
20
|
// -- MessageFormat --------------------------------------------------------
|
|
21
21
|
function mergeConfig(c1, c2) {
|
|
22
22
|
if (!c2) {
|
|
@@ -58,14 +58,30 @@ var IntlMessageFormat = /** @class */ (function () {
|
|
|
58
58
|
pluralRules: {},
|
|
59
59
|
};
|
|
60
60
|
this.format = function (values) {
|
|
61
|
-
|
|
61
|
+
var parts = _this.formatToParts(values);
|
|
62
|
+
// Hot path for straight simple msg translations
|
|
63
|
+
if (parts.length === 1) {
|
|
64
|
+
return parts[0].value;
|
|
65
|
+
}
|
|
66
|
+
var result = parts.reduce(function (all, part) {
|
|
67
|
+
if (!all.length ||
|
|
68
|
+
part.type !== 0 /* literal */ ||
|
|
69
|
+
typeof all[all.length - 1] !== 'string') {
|
|
70
|
+
all.push(part.value);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
all[all.length - 1] += part.value;
|
|
74
|
+
}
|
|
75
|
+
return all;
|
|
76
|
+
}, []);
|
|
77
|
+
if (result.length <= 1) {
|
|
78
|
+
return result[0] || '';
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
62
81
|
};
|
|
63
82
|
this.formatToParts = function (values) {
|
|
64
83
|
return formatToParts(_this.ast, _this.locales, _this.formatters, _this.formats, values, undefined, _this.message);
|
|
65
84
|
};
|
|
66
|
-
this.formatHTMLMessage = function (values) {
|
|
67
|
-
return formatHTMLMessage(_this.ast, _this.locales, _this.formatters, _this.formats, values, _this.message);
|
|
68
|
-
};
|
|
69
85
|
this.resolvedOptions = function () { return ({
|
|
70
86
|
locale: Intl.NumberFormat.supportedLocalesOf(_this.locales)[0],
|
|
71
87
|
}); };
|
package/lib/error.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const enum ErrorCode {
|
|
2
|
+
MISSING_VALUE = 0,
|
|
3
|
+
INVALID_VALUE = 1,
|
|
4
|
+
MISSING_INTL_API = 2
|
|
5
|
+
}
|
|
6
|
+
export declare class FormatError extends Error {
|
|
7
|
+
readonly code: ErrorCode;
|
|
8
|
+
constructor(msg: string, code: ErrorCode);
|
|
9
|
+
toString(): string;
|
|
10
|
+
}
|
|
11
|
+
export declare class InvalidValueError extends FormatError {
|
|
12
|
+
constructor(variableId: string, value: any, options: string[]);
|
|
13
|
+
}
|
|
14
|
+
export declare class MissingValueError extends FormatError {
|
|
15
|
+
constructor(variableId: string, originalMessage?: string);
|
|
16
|
+
}
|
package/lib/error.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
var __extends = (this && this.__extends) || (function () {
|
|
2
|
+
var extendStatics = function (d, b) {
|
|
3
|
+
extendStatics = Object.setPrototypeOf ||
|
|
4
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
5
|
+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
6
|
+
return extendStatics(d, b);
|
|
7
|
+
};
|
|
8
|
+
return function (d, b) {
|
|
9
|
+
extendStatics(d, b);
|
|
10
|
+
function __() { this.constructor = d; }
|
|
11
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
12
|
+
};
|
|
13
|
+
})();
|
|
14
|
+
var FormatError = /** @class */ (function (_super) {
|
|
15
|
+
__extends(FormatError, _super);
|
|
16
|
+
function FormatError(msg, code) {
|
|
17
|
+
var _this = _super.call(this, msg) || this;
|
|
18
|
+
_this.code = code;
|
|
19
|
+
return _this;
|
|
20
|
+
}
|
|
21
|
+
FormatError.prototype.toString = function () {
|
|
22
|
+
return "[formatjs Error: " + this.code + "] " + this.message;
|
|
23
|
+
};
|
|
24
|
+
return FormatError;
|
|
25
|
+
}(Error));
|
|
26
|
+
export { FormatError };
|
|
27
|
+
var InvalidValueError = /** @class */ (function (_super) {
|
|
28
|
+
__extends(InvalidValueError, _super);
|
|
29
|
+
function InvalidValueError(variableId, value, options) {
|
|
30
|
+
return _super.call(this, "Invalid values for \"" + variableId + "\": \"" + value + "\". Options are \"" + Object.keys(options).join('", "') + "\"", 1 /* INVALID_VALUE */) || this;
|
|
31
|
+
}
|
|
32
|
+
return InvalidValueError;
|
|
33
|
+
}(FormatError));
|
|
34
|
+
export { InvalidValueError };
|
|
35
|
+
var MissingValueError = /** @class */ (function (_super) {
|
|
36
|
+
__extends(MissingValueError, _super);
|
|
37
|
+
function MissingValueError(variableId, originalMessage) {
|
|
38
|
+
return _super.call(this, "The intl string context variable \"" + variableId + "\" was not provided to the string \"" + originalMessage + "\"", 0 /* MISSING_VALUE */) || this;
|
|
39
|
+
}
|
|
40
|
+
return MissingValueError;
|
|
41
|
+
}(FormatError));
|
|
42
|
+
export { MissingValueError };
|
package/lib/formatters.d.ts
CHANGED
|
@@ -16,19 +16,17 @@ export interface Formatters {
|
|
|
16
16
|
}
|
|
17
17
|
export declare const enum PART_TYPE {
|
|
18
18
|
literal = 0,
|
|
19
|
-
|
|
19
|
+
object = 1
|
|
20
20
|
}
|
|
21
21
|
export interface LiteralPart {
|
|
22
22
|
type: PART_TYPE.literal;
|
|
23
23
|
value: string;
|
|
24
24
|
}
|
|
25
|
-
export interface
|
|
26
|
-
type: PART_TYPE.
|
|
27
|
-
value:
|
|
25
|
+
export interface ObjectPart<T = any> {
|
|
26
|
+
type: PART_TYPE.object;
|
|
27
|
+
value: T;
|
|
28
28
|
}
|
|
29
|
-
export declare type MessageFormatPart = LiteralPart |
|
|
29
|
+
export declare type MessageFormatPart<T> = LiteralPart | ObjectPart<T>;
|
|
30
30
|
export declare type PrimitiveType = string | number | boolean | null | undefined | Date;
|
|
31
|
-
export declare function formatToParts(els: MessageFormatElement[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string,
|
|
32
|
-
export declare
|
|
33
|
-
export declare type FormatXMLElementFn = (...args: any[]) => string | object;
|
|
34
|
-
export declare function formatHTMLMessage(els: MessageFormatElement[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string, PrimitiveType | object | FormatXMLElementFn>, originalMessage?: string): Array<string | object>;
|
|
31
|
+
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>[];
|
|
32
|
+
export declare type FormatXMLElementFn<T> = (...args: Array<string | T>) => string | Array<string | T>;
|
package/lib/formatters.js
CHANGED
|
@@ -1,33 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
extendStatics = Object.setPrototypeOf ||
|
|
4
|
-
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
5
|
-
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
|
6
|
-
return extendStatics(d, b);
|
|
7
|
-
};
|
|
8
|
-
return function (d, b) {
|
|
9
|
-
extendStatics(d, b);
|
|
10
|
-
function __() { this.constructor = d; }
|
|
11
|
-
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
12
|
-
};
|
|
13
|
-
})();
|
|
14
|
-
var __spreadArrays = (this && this.__spreadArrays) || function () {
|
|
15
|
-
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
|
|
16
|
-
for (var r = Array(s), k = 0, i = 0; i < il; i++)
|
|
17
|
-
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
|
|
18
|
-
r[k] = a[j];
|
|
19
|
-
return r;
|
|
20
|
-
};
|
|
21
|
-
import { convertNumberSkeletonToNumberFormatOptions, isArgumentElement, isDateElement, isDateTimeSkeleton, isLiteralElement, isNumberElement, isNumberSkeleton, isPluralElement, isPoundElement, isSelectElement, isTimeElement, parseDateTimeSkeleton, } from 'intl-messageformat-parser';
|
|
22
|
-
var FormatError = /** @class */ (function (_super) {
|
|
23
|
-
__extends(FormatError, _super);
|
|
24
|
-
function FormatError(msg, variableId) {
|
|
25
|
-
var _this = _super.call(this, msg) || this;
|
|
26
|
-
_this.variableId = variableId;
|
|
27
|
-
return _this;
|
|
28
|
-
}
|
|
29
|
-
return FormatError;
|
|
30
|
-
}(Error));
|
|
1
|
+
import { convertNumberSkeletonToNumberFormatOptions, isArgumentElement, isDateElement, isDateTimeSkeleton, isLiteralElement, isNumberElement, isNumberSkeleton, isPluralElement, isPoundElement, isSelectElement, isTimeElement, parseDateTimeSkeleton, isTagElement, } from 'intl-messageformat-parser';
|
|
2
|
+
import { MissingValueError, InvalidValueError, FormatError, } from './error';
|
|
31
3
|
function mergeLiteral(parts) {
|
|
32
4
|
if (parts.length < 2) {
|
|
33
5
|
return parts;
|
|
@@ -45,6 +17,9 @@ function mergeLiteral(parts) {
|
|
|
45
17
|
return all;
|
|
46
18
|
}, []);
|
|
47
19
|
}
|
|
20
|
+
function isFormatXMLElementFn(el) {
|
|
21
|
+
return typeof el === 'function';
|
|
22
|
+
}
|
|
48
23
|
// TODO(skeleton): add skeleton support
|
|
49
24
|
export function formatToParts(els, locales, formatters, formats, values, currentPluralValue,
|
|
50
25
|
// For debugging
|
|
@@ -83,7 +58,7 @@ originalMessage) {
|
|
|
83
58
|
var varName = el.value;
|
|
84
59
|
// Enforce that all required values are provided by the caller.
|
|
85
60
|
if (!(values && varName in values)) {
|
|
86
|
-
throw new
|
|
61
|
+
throw new MissingValueError(varName, originalMessage);
|
|
87
62
|
}
|
|
88
63
|
var value = values[varName];
|
|
89
64
|
if (isArgumentElement(el)) {
|
|
@@ -94,7 +69,7 @@ originalMessage) {
|
|
|
94
69
|
: '';
|
|
95
70
|
}
|
|
96
71
|
result.push({
|
|
97
|
-
type: 1 /*
|
|
72
|
+
type: typeof value === 'string' ? 0 /* literal */ : 1 /* object */,
|
|
98
73
|
value: value,
|
|
99
74
|
});
|
|
100
75
|
continue;
|
|
@@ -140,10 +115,28 @@ originalMessage) {
|
|
|
140
115
|
});
|
|
141
116
|
continue;
|
|
142
117
|
}
|
|
118
|
+
if (isTagElement(el)) {
|
|
119
|
+
var children = el.children, value_1 = el.value;
|
|
120
|
+
var formatFn = values[value_1];
|
|
121
|
+
if (!isFormatXMLElementFn(formatFn)) {
|
|
122
|
+
throw new TypeError("Value for \"" + value_1 + "\" must be a function");
|
|
123
|
+
}
|
|
124
|
+
var parts = formatToParts(children, locales, formatters, formats, values);
|
|
125
|
+
var chunks = formatFn.apply(void 0, parts.map(function (p) { return p.value; }));
|
|
126
|
+
if (!Array.isArray(chunks)) {
|
|
127
|
+
chunks = [chunks];
|
|
128
|
+
}
|
|
129
|
+
result.push.apply(result, chunks.map(function (c) {
|
|
130
|
+
return {
|
|
131
|
+
type: typeof c === 'string' ? 0 /* literal */ : 1 /* object */,
|
|
132
|
+
value: c,
|
|
133
|
+
};
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
143
136
|
if (isSelectElement(el)) {
|
|
144
137
|
var opt = el.options[value] || el.options.other;
|
|
145
138
|
if (!opt) {
|
|
146
|
-
throw new
|
|
139
|
+
throw new InvalidValueError(el.value, value, Object.keys(el.options));
|
|
147
140
|
}
|
|
148
141
|
result.push.apply(result, formatToParts(opt.value, locales, formatters, formats, values));
|
|
149
142
|
continue;
|
|
@@ -152,7 +145,7 @@ originalMessage) {
|
|
|
152
145
|
var opt = el.options["=" + value];
|
|
153
146
|
if (!opt) {
|
|
154
147
|
if (!Intl.PluralRules) {
|
|
155
|
-
throw new FormatError("Intl.PluralRules is not available in this environment.\nTry polyfilling it using \"@formatjs/intl-pluralrules\"\n");
|
|
148
|
+
throw new FormatError("Intl.PluralRules is not available in this environment.\nTry polyfilling it using \"@formatjs/intl-pluralrules\"\n", 2 /* MISSING_INTL_API */);
|
|
156
149
|
}
|
|
157
150
|
var rule = formatters
|
|
158
151
|
.getPluralRules(locales, { type: el.pluralType })
|
|
@@ -160,7 +153,7 @@ originalMessage) {
|
|
|
160
153
|
opt = el.options[rule] || el.options.other;
|
|
161
154
|
}
|
|
162
155
|
if (!opt) {
|
|
163
|
-
throw new
|
|
156
|
+
throw new InvalidValueError(el.value, value, Object.keys(el.options));
|
|
164
157
|
}
|
|
165
158
|
result.push.apply(result, formatToParts(opt.value, locales, formatters, formats, values, value - (el.offset || 0)));
|
|
166
159
|
continue;
|
|
@@ -168,136 +161,3 @@ originalMessage) {
|
|
|
168
161
|
}
|
|
169
162
|
return mergeLiteral(result);
|
|
170
163
|
}
|
|
171
|
-
export function formatToString(els, locales, formatters, formats, values,
|
|
172
|
-
// For debugging
|
|
173
|
-
originalMessage) {
|
|
174
|
-
var parts = formatToParts(els, locales, formatters, formats, values, undefined, originalMessage);
|
|
175
|
-
// Hot path for straight simple msg translations
|
|
176
|
-
if (parts.length === 1) {
|
|
177
|
-
return parts[0].value;
|
|
178
|
-
}
|
|
179
|
-
return parts.reduce(function (all, part) { return (all += part.value); }, '');
|
|
180
|
-
}
|
|
181
|
-
// Singleton
|
|
182
|
-
var domParser;
|
|
183
|
-
var TOKEN_DELIMITER = '@@';
|
|
184
|
-
var TOKEN_REGEX = /@@(\d+_\d+)@@/g;
|
|
185
|
-
var counter = 0;
|
|
186
|
-
function generateId() {
|
|
187
|
-
return Date.now() + "_" + ++counter;
|
|
188
|
-
}
|
|
189
|
-
function restoreRichPlaceholderMessage(text, objectParts) {
|
|
190
|
-
return text
|
|
191
|
-
.split(TOKEN_REGEX)
|
|
192
|
-
.filter(Boolean)
|
|
193
|
-
.map(function (c) { return (objectParts[c] != null ? objectParts[c] : c); })
|
|
194
|
-
.reduce(function (all, c) {
|
|
195
|
-
if (!all.length) {
|
|
196
|
-
all.push(c);
|
|
197
|
-
}
|
|
198
|
-
else if (typeof c === 'string' &&
|
|
199
|
-
typeof all[all.length - 1] === 'string') {
|
|
200
|
-
all[all.length - 1] += c;
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
all.push(c);
|
|
204
|
-
}
|
|
205
|
-
return all;
|
|
206
|
-
}, []);
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Not exhaustive, just for sanity check
|
|
210
|
-
*/
|
|
211
|
-
var SIMPLE_XML_REGEX = /(<([0-9a-zA-Z-_]*?)>(.*?)<\/([0-9a-zA-Z-_]*?)>)|(<[0-9a-zA-Z-_]*?\/>)/;
|
|
212
|
-
var TEMPLATE_ID = Date.now() + '@@';
|
|
213
|
-
var VOID_ELEMENTS = [
|
|
214
|
-
'area',
|
|
215
|
-
'base',
|
|
216
|
-
'br',
|
|
217
|
-
'col',
|
|
218
|
-
'embed',
|
|
219
|
-
'hr',
|
|
220
|
-
'img',
|
|
221
|
-
'input',
|
|
222
|
-
'link',
|
|
223
|
-
'meta',
|
|
224
|
-
'param',
|
|
225
|
-
'source',
|
|
226
|
-
'track',
|
|
227
|
-
'wbr',
|
|
228
|
-
];
|
|
229
|
-
function formatHTMLElement(el, objectParts, values) {
|
|
230
|
-
var tagName = el.tagName;
|
|
231
|
-
var outerHTML = el.outerHTML, textContent = el.textContent, childNodes = el.childNodes;
|
|
232
|
-
// Regular text
|
|
233
|
-
if (!tagName) {
|
|
234
|
-
return restoreRichPlaceholderMessage(textContent || '', objectParts);
|
|
235
|
-
}
|
|
236
|
-
tagName = tagName.toLowerCase();
|
|
237
|
-
var isVoidElement = ~VOID_ELEMENTS.indexOf(tagName);
|
|
238
|
-
var formatFnOrValue = values[tagName];
|
|
239
|
-
if (formatFnOrValue && isVoidElement) {
|
|
240
|
-
throw new FormatError(tagName + " is a self-closing tag and can not be used, please use another tag name.");
|
|
241
|
-
}
|
|
242
|
-
if (!childNodes.length) {
|
|
243
|
-
return [outerHTML];
|
|
244
|
-
}
|
|
245
|
-
var chunks = Array.prototype.slice.call(childNodes).reduce(function (all, child) {
|
|
246
|
-
return all.concat(formatHTMLElement(child, objectParts, values));
|
|
247
|
-
}, []);
|
|
248
|
-
// Legacy HTML
|
|
249
|
-
if (!formatFnOrValue) {
|
|
250
|
-
return __spreadArrays(["<" + tagName + ">"], chunks, ["</" + tagName + ">"]);
|
|
251
|
-
}
|
|
252
|
-
// HTML Tag replacement
|
|
253
|
-
if (typeof formatFnOrValue === 'function') {
|
|
254
|
-
return [formatFnOrValue.apply(void 0, chunks)];
|
|
255
|
-
}
|
|
256
|
-
return [formatFnOrValue];
|
|
257
|
-
}
|
|
258
|
-
export function formatHTMLMessage(els, locales, formatters, formats, values,
|
|
259
|
-
// For debugging
|
|
260
|
-
originalMessage) {
|
|
261
|
-
var parts = formatToParts(els, locales, formatters, formats, values, undefined, originalMessage);
|
|
262
|
-
var objectParts = {};
|
|
263
|
-
var formattedMessage = parts.reduce(function (all, part) {
|
|
264
|
-
if (part.type === 0 /* literal */) {
|
|
265
|
-
return (all += part.value);
|
|
266
|
-
}
|
|
267
|
-
var id = generateId();
|
|
268
|
-
objectParts[id] = part.value;
|
|
269
|
-
return (all += "" + TOKEN_DELIMITER + id + TOKEN_DELIMITER);
|
|
270
|
-
}, '');
|
|
271
|
-
// Not designed to filter out aggressively
|
|
272
|
-
if (!SIMPLE_XML_REGEX.test(formattedMessage)) {
|
|
273
|
-
return restoreRichPlaceholderMessage(formattedMessage, objectParts);
|
|
274
|
-
}
|
|
275
|
-
if (!values) {
|
|
276
|
-
throw new FormatError('Message has placeholders but no values was given');
|
|
277
|
-
}
|
|
278
|
-
if (typeof DOMParser === 'undefined') {
|
|
279
|
-
throw new FormatError('Cannot format XML message without DOMParser');
|
|
280
|
-
}
|
|
281
|
-
if (!domParser) {
|
|
282
|
-
domParser = new DOMParser();
|
|
283
|
-
}
|
|
284
|
-
var content = domParser
|
|
285
|
-
.parseFromString("<formatted-message id=\"" + TEMPLATE_ID + "\">" + formattedMessage + "</formatted-message>", 'text/html')
|
|
286
|
-
.getElementById(TEMPLATE_ID);
|
|
287
|
-
if (!content) {
|
|
288
|
-
throw new FormatError("Malformed HTML message " + formattedMessage);
|
|
289
|
-
}
|
|
290
|
-
var tagsToFormat = Object.keys(values).filter(function (varName) { return !!content.getElementsByTagName(varName).length; });
|
|
291
|
-
// No tags to format
|
|
292
|
-
if (!tagsToFormat.length) {
|
|
293
|
-
return restoreRichPlaceholderMessage(formattedMessage, objectParts);
|
|
294
|
-
}
|
|
295
|
-
var caseSensitiveTags = tagsToFormat.filter(function (tagName) { return tagName !== tagName.toLowerCase(); });
|
|
296
|
-
if (caseSensitiveTags.length) {
|
|
297
|
-
throw new FormatError("HTML tag must be lowercased but the following tags are not: " + caseSensitiveTags.join(', '));
|
|
298
|
-
}
|
|
299
|
-
// We're doing this since top node is `<formatted-message/>` which does not have a formatter
|
|
300
|
-
return Array.prototype.slice
|
|
301
|
-
.call(content.childNodes)
|
|
302
|
-
.reduce(function (all, child) { return all.concat(formatHTMLElement(child, objectParts, values)); }, []);
|
|
303
|
-
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { MessageFormatElement } from 'intl-messageformat-parser';
|
|
2
2
|
import { parse } from 'intl-messageformat-parser';
|
|
3
3
|
|
|
4
|
-
export declare interface ArgumentPart {
|
|
5
|
-
type: PART_TYPE.argument;
|
|
6
|
-
value: any;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
4
|
export declare function createDefaultFormatters(cache?: FormatterCache): Formatters;
|
|
10
5
|
|
|
11
|
-
export declare
|
|
6
|
+
export declare const enum ErrorCode {
|
|
7
|
+
MISSING_VALUE = 0,
|
|
8
|
+
INVALID_VALUE = 1,
|
|
9
|
+
MISSING_INTL_API = 2
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export declare class FormatError extends Error {
|
|
13
|
+
readonly code: ErrorCode;
|
|
14
|
+
constructor(msg: string, code: ErrorCode);
|
|
15
|
+
toString(): string;
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
export declare interface Formats {
|
|
14
19
|
number: Record<string, Intl.NumberFormatOptions>;
|
|
@@ -28,11 +33,9 @@ export declare interface Formatters {
|
|
|
28
33
|
getPluralRules(...args: ConstructorParameters<typeof Intl.PluralRules>): Intl.PluralRules;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
|
-
export declare function formatToParts(els: MessageFormatElement[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string,
|
|
32
|
-
|
|
33
|
-
export declare function formatToString(els: MessageFormatElement[], locales: string | string[], formatters: Formatters, formats: Formats, values?: Record<string, PrimitiveType>, originalMessage?: string): string;
|
|
36
|
+
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>[];
|
|
34
37
|
|
|
35
|
-
export declare type FormatXMLElementFn = (...args:
|
|
38
|
+
export declare type FormatXMLElementFn<T> = (...args: Array<string | T>) => string | Array<string | T>;
|
|
36
39
|
|
|
37
40
|
declare class IntlMessageFormat {
|
|
38
41
|
private readonly ast;
|
|
@@ -42,9 +45,8 @@ declare class IntlMessageFormat {
|
|
|
42
45
|
private readonly message;
|
|
43
46
|
private readonly formatterCache;
|
|
44
47
|
constructor(message: string | MessageFormatElement[], locales?: string | string[], overrideFormats?: Partial<Formats>, opts?: Options);
|
|
45
|
-
format: (values?: Record<string,
|
|
46
|
-
formatToParts: (values?: Record<string,
|
|
47
|
-
formatHTMLMessage: (values?: Record<string, string | number | boolean | object | Date | FormatXMLElementFn | null | undefined> | undefined) => (string | object)[];
|
|
48
|
+
format: <T = void>(values?: Record<string, string | number | boolean | Date | T | FormatXMLElementFn<T> | null | undefined> | undefined) => string | T | (string | T)[];
|
|
49
|
+
formatToParts: <T>(values?: Record<string, string | number | boolean | Date | T | FormatXMLElementFn<T> | null | undefined> | undefined) => MessageFormatPart<T>[];
|
|
48
50
|
resolvedOptions: () => {
|
|
49
51
|
locale: string;
|
|
50
52
|
};
|
|
@@ -111,12 +113,25 @@ declare class IntlMessageFormat {
|
|
|
111
113
|
export { IntlMessageFormat }
|
|
112
114
|
export default IntlMessageFormat;
|
|
113
115
|
|
|
116
|
+
export declare class InvalidValueError extends FormatError {
|
|
117
|
+
constructor(variableId: string, value: any, options: string[]);
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
export declare interface LiteralPart {
|
|
115
121
|
type: PART_TYPE.literal;
|
|
116
122
|
value: string;
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
export declare type MessageFormatPart = LiteralPart |
|
|
125
|
+
export declare type MessageFormatPart<T> = LiteralPart | ObjectPart<T>;
|
|
126
|
+
|
|
127
|
+
export declare class MissingValueError extends FormatError {
|
|
128
|
+
constructor(variableId: string, originalMessage?: string);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export declare interface ObjectPart<T = any> {
|
|
132
|
+
type: PART_TYPE.object;
|
|
133
|
+
value: T;
|
|
134
|
+
}
|
|
120
135
|
|
|
121
136
|
export declare interface Options {
|
|
122
137
|
formatters?: Formatters;
|
|
@@ -124,7 +139,7 @@ export declare interface Options {
|
|
|
124
139
|
|
|
125
140
|
export declare const enum PART_TYPE {
|
|
126
141
|
literal = 0,
|
|
127
|
-
|
|
142
|
+
object = 1
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
export declare type PrimitiveType = string | number | boolean | null | undefined | Date;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intl-messageformat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.2.1",
|
|
4
4
|
"description": "Formats ICU Message strings with number, date, plural, and select placeholders to create localized messages.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"types": "lib/intl-messageformat.d.ts",
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"intl-format-cache": "^4.2.21",
|
|
35
|
-
"intl-messageformat-parser": "^
|
|
35
|
+
"intl-messageformat-parser": "^4.1.0"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"dist",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"test": "tests"
|
|
59
59
|
},
|
|
60
60
|
"license": "BSD-3-Clause",
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "e0bb34c71e99fd4c68244f3bc668c748b2f18413"
|
|
62
62
|
}
|
package/src/core.ts
CHANGED
|
@@ -10,12 +10,11 @@ import {
|
|
|
10
10
|
FormatterCache,
|
|
11
11
|
Formatters,
|
|
12
12
|
Formats,
|
|
13
|
-
formatToString,
|
|
14
13
|
formatToParts,
|
|
15
14
|
FormatXMLElementFn,
|
|
16
|
-
formatHTMLMessage,
|
|
17
15
|
PrimitiveType,
|
|
18
16
|
MessageFormatPart,
|
|
17
|
+
PART_TYPE,
|
|
19
18
|
} from './formatters';
|
|
20
19
|
|
|
21
20
|
// -- MessageFormat --------------------------------------------------------
|
|
@@ -122,17 +121,35 @@ export class IntlMessageFormat {
|
|
|
122
121
|
(opts && opts.formatters) || createDefaultFormatters(this.formatterCache);
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
format =
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
)
|
|
124
|
+
format = <T = void>(
|
|
125
|
+
values?: Record<string, PrimitiveType | T | FormatXMLElementFn<T>>
|
|
126
|
+
) => {
|
|
127
|
+
const parts = this.formatToParts(values);
|
|
128
|
+
// Hot path for straight simple msg translations
|
|
129
|
+
if (parts.length === 1) {
|
|
130
|
+
return parts[0].value;
|
|
131
|
+
}
|
|
132
|
+
const result = parts.reduce((all, part) => {
|
|
133
|
+
if (
|
|
134
|
+
!all.length ||
|
|
135
|
+
part.type !== PART_TYPE.literal ||
|
|
136
|
+
typeof all[all.length - 1] !== 'string'
|
|
137
|
+
) {
|
|
138
|
+
all.push(part.value);
|
|
139
|
+
} else {
|
|
140
|
+
all[all.length - 1] += part.value;
|
|
141
|
+
}
|
|
142
|
+
return all;
|
|
143
|
+
}, [] as Array<string | T>);
|
|
134
144
|
|
|
135
|
-
|
|
145
|
+
if (result.length <= 1) {
|
|
146
|
+
return result[0] || '';
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
};
|
|
150
|
+
formatToParts = <T>(
|
|
151
|
+
values?: Record<string, PrimitiveType | T | FormatXMLElementFn<T>>
|
|
152
|
+
): MessageFormatPart<T>[] =>
|
|
136
153
|
formatToParts(
|
|
137
154
|
this.ast,
|
|
138
155
|
this.locales,
|
|
@@ -142,18 +159,6 @@ export class IntlMessageFormat {
|
|
|
142
159
|
undefined,
|
|
143
160
|
this.message
|
|
144
161
|
);
|
|
145
|
-
formatHTMLMessage = (
|
|
146
|
-
values?: Record<string, PrimitiveType | object | FormatXMLElementFn>
|
|
147
|
-
) =>
|
|
148
|
-
formatHTMLMessage(
|
|
149
|
-
this.ast,
|
|
150
|
-
this.locales,
|
|
151
|
-
this.formatters,
|
|
152
|
-
this.formats,
|
|
153
|
-
values,
|
|
154
|
-
this.message
|
|
155
|
-
);
|
|
156
|
-
|
|
157
162
|
resolvedOptions = () => ({
|
|
158
163
|
locale: Intl.NumberFormat.supportedLocalesOf(this.locales)[0],
|
|
159
164
|
});
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const enum ErrorCode {
|
|
2
|
+
// When we have a placeholder but no value to format
|
|
3
|
+
MISSING_VALUE,
|
|
4
|
+
// When value supplied is invalid
|
|
5
|
+
INVALID_VALUE,
|
|
6
|
+
// When we need specific Intl API but it's not available
|
|
7
|
+
MISSING_INTL_API,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class FormatError extends Error {
|
|
11
|
+
public readonly code: ErrorCode;
|
|
12
|
+
constructor(msg: string, code: ErrorCode) {
|
|
13
|
+
super(msg);
|
|
14
|
+
this.code = code;
|
|
15
|
+
}
|
|
16
|
+
public toString() {
|
|
17
|
+
return `[formatjs Error: ${this.code}] ${this.message}`;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class InvalidValueError extends FormatError {
|
|
22
|
+
constructor(variableId: string, value: any, options: string[]) {
|
|
23
|
+
super(
|
|
24
|
+
`Invalid values for "${variableId}": "${value}". Options are "${Object.keys(
|
|
25
|
+
options
|
|
26
|
+
).join('", "')}"`,
|
|
27
|
+
ErrorCode.INVALID_VALUE
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class MissingValueError extends FormatError {
|
|
33
|
+
constructor(variableId: string, originalMessage?: string) {
|
|
34
|
+
super(
|
|
35
|
+
`The intl string context variable "${variableId}" was not provided to the string "${originalMessage}"`,
|
|
36
|
+
ErrorCode.MISSING_VALUE
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|