@formatjs/icu-messageformat-parser 3.2.1 → 3.4.0
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/date-time-pattern-generator.d.ts +6 -6
- package/date-time-pattern-generator.js +62 -77
- package/error.d.ts +64 -64
- package/error.js +64 -63
- package/index.d.ts +4 -4
- package/index.js +40 -42
- package/manipulator.d.ts +19 -19
- package/manipulator.js +158 -159
- package/no-parser.d.ts +2 -2
- package/no-parser.js +4 -4
- package/package.json +4 -4
- package/parser.d.ts +142 -139
- package/parser.js +839 -900
- package/printer.d.ts +1 -1
- package/printer.js +68 -79
- package/regex.generated.js +2 -2
- package/time-data.generated.js +1162 -1424
- package/types.d.ts +77 -74
- package/types.js +68 -67
package/manipulator.js
CHANGED
|
@@ -1,178 +1,177 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isArgumentElement, isDateElement, isNumberElement, isPluralElement, isPoundElement, isSelectElement, isTagElement, isTimeElement, TYPE, } from './types.js';
|
|
1
|
+
import { isArgumentElement, isDateElement, isNumberElement, isPluralElement, isPoundElement, isSelectElement, isTagElement, isTimeElement, TYPE } from "./types.js";
|
|
3
2
|
function cloneDeep(obj) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
3
|
+
if (Array.isArray(obj)) {
|
|
4
|
+
// @ts-expect-error meh
|
|
5
|
+
return obj.map(cloneDeep);
|
|
6
|
+
}
|
|
7
|
+
if (obj !== null && typeof obj === "object") {
|
|
8
|
+
// @ts-expect-error meh
|
|
9
|
+
return Object.keys(obj).reduce((cloned, k) => {
|
|
10
|
+
// @ts-expect-error meh
|
|
11
|
+
cloned[k] = cloneDeep(obj[k]);
|
|
12
|
+
return cloned;
|
|
13
|
+
}, {});
|
|
14
|
+
}
|
|
15
|
+
return obj;
|
|
17
16
|
}
|
|
18
17
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
* Replace pound elements with number elements referencing the given variable.
|
|
19
|
+
* This is needed when nesting plurals - the # in the outer plural should become
|
|
20
|
+
* an explicit variable reference when nested inside another plural.
|
|
21
|
+
* GH #4202
|
|
22
|
+
*/
|
|
24
23
|
function replacePoundWithArgument(ast, variableName) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
return ast.map((el) => {
|
|
25
|
+
if (isPoundElement(el)) {
|
|
26
|
+
// Replace # with {variableName, number}
|
|
27
|
+
return {
|
|
28
|
+
type: TYPE.number,
|
|
29
|
+
value: variableName,
|
|
30
|
+
style: null,
|
|
31
|
+
location: el.location
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (isPluralElement(el) || isSelectElement(el)) {
|
|
35
|
+
// Recursively process options
|
|
36
|
+
const newOptions = {};
|
|
37
|
+
for (const key of Object.keys(el.options)) {
|
|
38
|
+
newOptions[key] = { value: replacePoundWithArgument(el.options[key].value, variableName) };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
...el,
|
|
42
|
+
options: newOptions
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (isTagElement(el)) {
|
|
46
|
+
return {
|
|
47
|
+
...el,
|
|
48
|
+
children: replacePoundWithArgument(el.children, variableName)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return el;
|
|
52
|
+
});
|
|
51
53
|
}
|
|
52
54
|
function hoistPluralOrSelectElement(ast, el, positionToInject) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
// pull this out of the ast and move it to the top
|
|
56
|
+
const cloned = cloneDeep(el);
|
|
57
|
+
const { options } = cloned;
|
|
58
|
+
// GH #4202: Check if there are other plural/select elements after this one
|
|
59
|
+
const afterElements = ast.slice(positionToInject + 1);
|
|
60
|
+
const hasSubsequentPluralOrSelect = afterElements.some(isPluralOrSelectElement);
|
|
61
|
+
cloned.options = Object.keys(options).reduce((all, k) => {
|
|
62
|
+
let optionValue = options[k].value;
|
|
63
|
+
// GH #4202: If there are subsequent plurals/selects and this is a plural,
|
|
64
|
+
// replace # with explicit variable reference to avoid ambiguity
|
|
65
|
+
if (hasSubsequentPluralOrSelect && isPluralElement(el)) {
|
|
66
|
+
optionValue = replacePoundWithArgument(optionValue, el.value);
|
|
67
|
+
}
|
|
68
|
+
const newValue = hoistSelectors([
|
|
69
|
+
...ast.slice(0, positionToInject),
|
|
70
|
+
...optionValue,
|
|
71
|
+
...afterElements
|
|
72
|
+
]);
|
|
73
|
+
all[k] = { value: newValue };
|
|
74
|
+
return all;
|
|
75
|
+
}, {});
|
|
76
|
+
return cloned;
|
|
73
77
|
}
|
|
74
78
|
function isPluralOrSelectElement(el) {
|
|
75
|
-
|
|
79
|
+
return isPluralElement(el) || isSelectElement(el);
|
|
76
80
|
}
|
|
77
81
|
function findPluralOrSelectElement(ast) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
return !!ast.find((el) => {
|
|
83
|
+
if (isPluralOrSelectElement(el)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (isTagElement(el)) {
|
|
87
|
+
return findPluralOrSelectElement(el.children);
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
});
|
|
87
91
|
}
|
|
88
92
|
/**
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
* Hoist all selectors to the beginning of the AST & flatten the
|
|
94
|
+
* resulting options. E.g:
|
|
95
|
+
* "I have {count, plural, one{a dog} other{many dogs}}"
|
|
96
|
+
* becomes "{count, plural, one{I have a dog} other{I have many dogs}}".
|
|
97
|
+
* If there are multiple selectors, the order of which one is hoisted 1st
|
|
98
|
+
* is non-deterministic.
|
|
99
|
+
* The goal is to provide as many full sentences as possible since fragmented
|
|
100
|
+
* sentences are not translator-friendly
|
|
101
|
+
* @param ast AST
|
|
102
|
+
*/
|
|
99
103
|
export function hoistSelectors(ast) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
for (let i = 0; i < ast.length; i++) {
|
|
105
|
+
const el = ast[i];
|
|
106
|
+
if (isPluralOrSelectElement(el)) {
|
|
107
|
+
return [hoistPluralOrSelectElement(ast, el, i)];
|
|
108
|
+
}
|
|
109
|
+
if (isTagElement(el) && findPluralOrSelectElement([el])) {
|
|
110
|
+
throw new Error("Cannot hoist plural/select within a tag element. Please put the tag element inside each plural/select option");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return ast;
|
|
110
114
|
}
|
|
111
115
|
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
function collectVariables(ast, vars) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
vars.set(el.value, el.type);
|
|
136
|
-
collectVariables(el.children, vars);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
116
|
+
* Collect all variables in an AST to Record<string, TYPE>
|
|
117
|
+
* @param ast AST to collect variables from
|
|
118
|
+
* @param vars Record of variable name to variable type
|
|
119
|
+
*/
|
|
120
|
+
function collectVariables(ast, vars = new Map()) {
|
|
121
|
+
ast.forEach((el) => {
|
|
122
|
+
if (isArgumentElement(el) || isDateElement(el) || isTimeElement(el) || isNumberElement(el)) {
|
|
123
|
+
if (el.value in vars && vars.get(el.value) !== el.type) {
|
|
124
|
+
throw new Error(`Variable ${el.value} has conflicting types`);
|
|
125
|
+
}
|
|
126
|
+
vars.set(el.value, el.type);
|
|
127
|
+
}
|
|
128
|
+
if (isPluralElement(el) || isSelectElement(el)) {
|
|
129
|
+
vars.set(el.value, el.type);
|
|
130
|
+
Object.keys(el.options).forEach((k) => {
|
|
131
|
+
collectVariables(el.options[k].value, vars);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (isTagElement(el)) {
|
|
135
|
+
vars.set(el.value, el.type);
|
|
136
|
+
collectVariables(el.children, vars);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
139
|
}
|
|
140
140
|
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
141
|
+
* Check if 2 ASTs are structurally the same. This primarily means that
|
|
142
|
+
* they have the same variables with the same type
|
|
143
|
+
* @param a
|
|
144
|
+
* @param b
|
|
145
|
+
* @returns
|
|
146
|
+
*/
|
|
147
147
|
export function isStructurallySame(a, b) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}, { success: true });
|
|
148
|
+
const aVars = new Map();
|
|
149
|
+
const bVars = new Map();
|
|
150
|
+
collectVariables(a, aVars);
|
|
151
|
+
collectVariables(b, bVars);
|
|
152
|
+
if (aVars.size !== bVars.size) {
|
|
153
|
+
return {
|
|
154
|
+
success: false,
|
|
155
|
+
error: new Error(`Different number of variables: [${Array.from(aVars.keys()).join(", ")}] vs [${Array.from(bVars.keys()).join(", ")}]`)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return Array.from(aVars.entries()).reduce((result, [key, type]) => {
|
|
159
|
+
if (!result.success) {
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
const bType = bVars.get(key);
|
|
163
|
+
if (bType == null) {
|
|
164
|
+
return {
|
|
165
|
+
success: false,
|
|
166
|
+
error: new Error(`Missing variable ${key} in message`)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (bType !== type) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: new Error(`Variable ${key} has conflicting types: ${TYPE[type]} vs ${TYPE[bType]}`)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}, { success: true });
|
|
178
177
|
}
|
package/no-parser.d.ts
CHANGED
package/no-parser.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function parse() {
|
|
2
|
-
|
|
2
|
+
throw new Error("You're trying to format an uncompiled message with react-intl without parser, please import from 'react-intl' instead");
|
|
3
3
|
}
|
|
4
|
-
export * from
|
|
5
|
-
export
|
|
6
|
-
export { isStructurallySame } from
|
|
4
|
+
export * from "./types.js";
|
|
5
|
+
export const _Parser = undefined;
|
|
6
|
+
export { isStructurallySame } from "./manipulator.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formatjs/icu-messageformat-parser",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"./manipulator.js": "./manipulator.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"tslib": "^2.8.
|
|
15
|
-
"@formatjs/ecma402-abstract": "3.0
|
|
16
|
-
"@formatjs/icu-skeleton-parser": "2.0
|
|
14
|
+
"tslib": "^2.8.1",
|
|
15
|
+
"@formatjs/ecma402-abstract": "3.1.0",
|
|
16
|
+
"@formatjs/icu-skeleton-parser": "2.1.0"
|
|
17
17
|
},
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|