fluent-transpiler 0.3.1 → 0.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/README.md +37 -7
- package/cli.js +81 -78
- package/index.d.ts +34 -0
- package/index.js +432 -390
- package/license.json +28 -0
- package/license.template +2 -0
- package/package.json +94 -51
package/index.js
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Copyright 2026 will Farrell, and fluent-transpiler contributors.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import { parse } from "@fluent/syntax";
|
|
5
|
+
import { camelCase, constantCase, pascalCase, snakeCase } from "change-case";
|
|
6
|
+
|
|
7
|
+
const reservedWords = new Set([
|
|
8
|
+
"abstract",
|
|
9
|
+
"arguments",
|
|
10
|
+
"await",
|
|
11
|
+
"boolean",
|
|
12
|
+
"break",
|
|
13
|
+
"byte",
|
|
14
|
+
"case",
|
|
15
|
+
"catch",
|
|
16
|
+
"char",
|
|
17
|
+
"class",
|
|
18
|
+
"const",
|
|
19
|
+
"continue",
|
|
20
|
+
"debugger",
|
|
21
|
+
"default",
|
|
22
|
+
"delete",
|
|
23
|
+
"do",
|
|
24
|
+
"double",
|
|
25
|
+
"else",
|
|
26
|
+
"enum",
|
|
27
|
+
"eval",
|
|
28
|
+
"export",
|
|
29
|
+
"extends",
|
|
30
|
+
"false",
|
|
31
|
+
"final",
|
|
32
|
+
"finally",
|
|
33
|
+
"float",
|
|
34
|
+
"for",
|
|
35
|
+
"function",
|
|
36
|
+
"goto",
|
|
37
|
+
"if",
|
|
38
|
+
"implements",
|
|
39
|
+
"import",
|
|
40
|
+
"in",
|
|
41
|
+
"instanceof",
|
|
42
|
+
"int",
|
|
43
|
+
"interface",
|
|
44
|
+
"let",
|
|
45
|
+
"long",
|
|
46
|
+
"native",
|
|
47
|
+
"new",
|
|
48
|
+
"null",
|
|
49
|
+
"of",
|
|
50
|
+
"package",
|
|
51
|
+
"private",
|
|
52
|
+
"protected",
|
|
53
|
+
"public",
|
|
54
|
+
"return",
|
|
55
|
+
"short",
|
|
56
|
+
"static",
|
|
57
|
+
"super",
|
|
58
|
+
"switch",
|
|
59
|
+
"synchronized",
|
|
60
|
+
"this",
|
|
61
|
+
"throw",
|
|
62
|
+
"throws",
|
|
63
|
+
"transient",
|
|
64
|
+
"true",
|
|
65
|
+
"try",
|
|
66
|
+
"typeof",
|
|
67
|
+
"undefined",
|
|
68
|
+
"var",
|
|
69
|
+
"void",
|
|
70
|
+
"volatile",
|
|
71
|
+
"while",
|
|
72
|
+
"with",
|
|
73
|
+
"yield",
|
|
74
|
+
]);
|
|
3
75
|
|
|
4
76
|
const exportDefault = `(id, params) => {
|
|
5
77
|
const source = __exports[id] ?? __exports['_'+id]
|
|
@@ -7,378 +79,342 @@ const exportDefault = `(id, params) => {
|
|
|
7
79
|
if (typeof source === 'function') return source(params)
|
|
8
80
|
return source
|
|
9
81
|
}
|
|
10
|
-
|
|
82
|
+
`;
|
|
11
83
|
export const compile = (src, opts) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
console.log({ options })
|
|
84
|
+
const options = {
|
|
85
|
+
comments: true,
|
|
86
|
+
errorOnJunk: true,
|
|
87
|
+
includeKey: [],
|
|
88
|
+
excludeKey: [],
|
|
89
|
+
excludeValue: undefined,
|
|
90
|
+
variableNotation: "camelCase",
|
|
91
|
+
disableMinify: false,
|
|
92
|
+
useIsolating: false,
|
|
93
|
+
params: "params",
|
|
94
|
+
exportDefault,
|
|
95
|
+
...opts,
|
|
96
|
+
};
|
|
97
|
+
if (!Array.isArray(options.locale)) options.locale = [options.locale];
|
|
98
|
+
if (!Array.isArray(options.includeKey))
|
|
99
|
+
options.includeKey = [options.includeKey];
|
|
100
|
+
if (!Array.isArray(options.excludeKey))
|
|
101
|
+
options.excludeKey = [options.excludeKey];
|
|
102
|
+
if (options.excludeValue) {
|
|
103
|
+
// cast to template literal
|
|
104
|
+
options.excludeValue = `\`${options.excludeValue}\``;
|
|
105
|
+
}
|
|
36
106
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
107
|
+
const metadata = {};
|
|
108
|
+
const exports = [];
|
|
109
|
+
const functions = {}; // global functions
|
|
110
|
+
let variable;
|
|
41
111
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
112
|
+
const compileAssignment = (data) => {
|
|
113
|
+
variable = compileType(data);
|
|
114
|
+
metadata[variable] = {
|
|
115
|
+
id: data.name,
|
|
116
|
+
term: false,
|
|
117
|
+
params: false,
|
|
118
|
+
};
|
|
119
|
+
return variable;
|
|
120
|
+
};
|
|
52
121
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
122
|
+
const compileFunctionArguments = (data) => {
|
|
123
|
+
const positional = data.arguments?.positional.map((data) => {
|
|
124
|
+
return types[data.type](data);
|
|
125
|
+
});
|
|
126
|
+
const named = data.arguments?.named.reduce((obj, data) => {
|
|
127
|
+
const entry = compileType(data);
|
|
128
|
+
const [key, value] = entry.split(": ");
|
|
129
|
+
obj[key] = value;
|
|
130
|
+
return obj;
|
|
131
|
+
}, {});
|
|
132
|
+
return { positional, named };
|
|
133
|
+
};
|
|
66
134
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
135
|
+
const compileType = (data, parent) => {
|
|
136
|
+
try {
|
|
137
|
+
return types[data.type](data, parent);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
throw new Error(e.message, { cause: { error: e, data } });
|
|
140
|
+
}
|
|
141
|
+
};
|
|
75
142
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
143
|
+
const types = {
|
|
144
|
+
Identifier: (data, parent) => {
|
|
145
|
+
const value =
|
|
146
|
+
parent === "Attribute"
|
|
147
|
+
? data.name
|
|
148
|
+
: variableNotation[options.variableNotation](data.name);
|
|
82
149
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
150
|
+
if (value.includes("-")) {
|
|
151
|
+
return `'${value}'`;
|
|
152
|
+
}
|
|
153
|
+
// Check for reserved words
|
|
154
|
+
if (reservedWords.has(value)) {
|
|
155
|
+
return `_${value}`;
|
|
156
|
+
}
|
|
157
|
+
return value;
|
|
158
|
+
},
|
|
159
|
+
Attribute: (data) => {
|
|
160
|
+
const key = compileType(data.id, data.type);
|
|
161
|
+
const value = compileType(data.value, data.type);
|
|
162
|
+
return ` ${key}: ${value}`;
|
|
163
|
+
},
|
|
164
|
+
Pattern: (data, parent) => {
|
|
165
|
+
return (
|
|
166
|
+
"`" +
|
|
167
|
+
data.elements
|
|
168
|
+
.map((data) => {
|
|
169
|
+
return compileType(data, parent);
|
|
170
|
+
})
|
|
171
|
+
.join("") +
|
|
172
|
+
"`"
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
// resources
|
|
176
|
+
Term: (data) => {
|
|
177
|
+
const assignment = compileAssignment(data.id);
|
|
178
|
+
const templateStringLiteral = compileType(data.value);
|
|
179
|
+
metadata[assignment].term = true;
|
|
180
|
+
if (metadata[assignment].params) {
|
|
181
|
+
return `const ${assignment} = (${options.params}) => ${templateStringLiteral}\n`;
|
|
182
|
+
}
|
|
183
|
+
return `const ${assignment} = ${templateStringLiteral}\n`;
|
|
184
|
+
},
|
|
185
|
+
Message: (data) => {
|
|
186
|
+
const assignment = compileAssignment(data.id);
|
|
120
187
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
188
|
+
if (
|
|
189
|
+
options.includeKey.length &&
|
|
190
|
+
!options.includeKey.includes(assignment)
|
|
191
|
+
) {
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
127
194
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
195
|
+
if (
|
|
196
|
+
options.excludeKey.length &&
|
|
197
|
+
options.excludeKey.includes(assignment)
|
|
198
|
+
) {
|
|
199
|
+
return "";
|
|
200
|
+
}
|
|
134
201
|
|
|
135
|
-
|
|
136
|
-
|
|
202
|
+
let templateStringLiteral =
|
|
203
|
+
data.value && compileType(data.value, data.type);
|
|
137
204
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
205
|
+
if (options.excludeValue === templateStringLiteral) {
|
|
206
|
+
templateStringLiteral = "``";
|
|
207
|
+
}
|
|
141
208
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
message = `(${options.params}) => ({
|
|
209
|
+
metadata[assignment].attributes = data.attributes.length;
|
|
210
|
+
let attributes = {};
|
|
211
|
+
if (metadata[assignment].attributes) {
|
|
212
|
+
attributes = `{\n${data.attributes
|
|
213
|
+
.map((data) => {
|
|
214
|
+
return ` ${compileType(data)}`;
|
|
215
|
+
})
|
|
216
|
+
.join(",\n")}\n }`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
let message = "";
|
|
220
|
+
if (!options.disableMinify) {
|
|
221
|
+
if (metadata[assignment].attributes) {
|
|
222
|
+
if (metadata[assignment].params) {
|
|
223
|
+
message = `(${options.params}) => ({
|
|
158
224
|
value:${templateStringLiteral},
|
|
159
225
|
attributes:${attributes}
|
|
160
|
-
})\n
|
|
161
|
-
|
|
162
|
-
|
|
226
|
+
})\n`;
|
|
227
|
+
} else {
|
|
228
|
+
message = `{
|
|
163
229
|
value: ${templateStringLiteral},
|
|
164
230
|
attributes: ${attributes}
|
|
165
|
-
}\n
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
231
|
+
}\n`;
|
|
232
|
+
}
|
|
233
|
+
} else if (metadata[assignment].params) {
|
|
234
|
+
message = `(${options.params}) => ${templateStringLiteral}\n`;
|
|
235
|
+
} else {
|
|
236
|
+
message = `${templateStringLiteral}\n`;
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
// consistent API
|
|
240
|
+
message = `(${metadata[assignment].params ? options.params : ""}) => ({
|
|
175
241
|
value:${templateStringLiteral},
|
|
176
242
|
attributes:${attributes}
|
|
177
|
-
})\n
|
|
178
|
-
|
|
179
|
-
//if (options.treeShaking) {
|
|
180
|
-
if (assignment === metadata[assignment].id) {
|
|
181
|
-
exports.push(`${assignment}`)
|
|
182
|
-
} else {
|
|
183
|
-
exports.push(`'${metadata[assignment].id}': ${assignment}`)
|
|
184
|
-
}
|
|
185
|
-
return `export const ${assignment} = ${message}`
|
|
186
|
-
/*} else {
|
|
187
|
-
if (assignment === metadata[assignment].id) {
|
|
188
|
-
exports.push(`${assignment}: ${message}`)
|
|
189
|
-
} else {
|
|
190
|
-
exports.push(`'${metadata[assignment].id}': ${message}`)
|
|
191
|
-
}
|
|
192
|
-
}*/
|
|
193
|
-
return ''
|
|
194
|
-
},
|
|
195
|
-
Comment: (data) => {
|
|
196
|
-
if (options.comments) return `// # ${data.content}\n`
|
|
197
|
-
return ''
|
|
198
|
-
},
|
|
199
|
-
GroupComment: (data) => {
|
|
200
|
-
if (options.comments) return `// ## ${data.content}\n`
|
|
201
|
-
return ''
|
|
202
|
-
},
|
|
203
|
-
ResourceComment: (data) => {
|
|
204
|
-
if (options.comments) return `// ### ${data.content}\n`
|
|
205
|
-
return ''
|
|
206
|
-
},
|
|
207
|
-
Junk: (data) => {
|
|
208
|
-
if (options.errorOnJunk) {
|
|
209
|
-
throw new Error('Junk found', { cause: data })
|
|
210
|
-
}
|
|
211
|
-
console.error('Error: Skipping Junk', JSON.stringify(data, null, 2))
|
|
212
|
-
return ''
|
|
213
|
-
},
|
|
214
|
-
// Element
|
|
215
|
-
TextElement: (data) => {
|
|
216
|
-
if (data.value === options.emptyString) return
|
|
217
|
-
return data.value.replaceAll('`', '\\`') // escape string literal
|
|
218
|
-
},
|
|
219
|
-
Placeable: (data, parent) => {
|
|
220
|
-
return `${options.useIsolating ? '\u2068' : ''}\${${compileType(
|
|
221
|
-
data.expression,
|
|
222
|
-
parent
|
|
223
|
-
)}}${options.useIsolating ? '\u2069' : ''}`
|
|
224
|
-
},
|
|
225
|
-
// Expression
|
|
226
|
-
StringLiteral: (data, parent) => {
|
|
227
|
-
// JSON.stringify at parent level
|
|
228
|
-
if (['NamedArgument'].includes(parent)) {
|
|
229
|
-
return `${data.value}`
|
|
230
|
-
}
|
|
231
|
-
return `"${data.value}"`
|
|
232
|
-
},
|
|
233
|
-
NumberLiteral: (data) => {
|
|
234
|
-
const decimal = Number.parseFloat(data.value)
|
|
235
|
-
const number = Number.isInteger(decimal)
|
|
236
|
-
? Number.parseInt(data.value)
|
|
237
|
-
: decimal
|
|
238
|
-
return Intl.NumberFormat(options.locale).format(number)
|
|
239
|
-
},
|
|
240
|
-
VariableReference: (data, parent) => {
|
|
241
|
-
functions.__formatVariable = true
|
|
242
|
-
metadata[variable].params = true
|
|
243
|
-
const value = `${options.params}?.${data.id.name}`
|
|
244
|
-
if (['Message', 'Variant', 'Attribute'].includes(parent)) {
|
|
245
|
-
return `__formatVariable(${value})`
|
|
246
|
-
}
|
|
247
|
-
return value
|
|
248
|
-
},
|
|
249
|
-
MessageReference: (data) => {
|
|
250
|
-
const messageName = compileType(data.id)
|
|
251
|
-
metadata[variable].params ||= metadata[messageName].params
|
|
252
|
-
if (!options.disableMinify) {
|
|
253
|
-
if (metadata[messageName].params) {
|
|
254
|
-
return `${messageName}(${options.params})`
|
|
255
|
-
}
|
|
256
|
-
return `${messageName}`
|
|
257
|
-
}
|
|
258
|
-
return `${messageName}(${
|
|
259
|
-
metadata[messageName].params ? options.params : ''
|
|
260
|
-
})`
|
|
261
|
-
},
|
|
262
|
-
TermReference: (data) => {
|
|
263
|
-
const termName = compileType(data.id)
|
|
264
|
-
metadata[variable].params ||= metadata[termName].params
|
|
243
|
+
})\n`;
|
|
244
|
+
}
|
|
265
245
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
246
|
+
if (assignment === metadata[assignment].id) {
|
|
247
|
+
exports.push(`${assignment}`);
|
|
248
|
+
} else {
|
|
249
|
+
exports.push(`'${metadata[assignment].id}': ${assignment}`);
|
|
250
|
+
}
|
|
251
|
+
return `export const ${assignment} = ${message}`;
|
|
252
|
+
},
|
|
253
|
+
Comment: (data) => {
|
|
254
|
+
if (options.comments) return `// # ${data.content}\n`;
|
|
255
|
+
return "";
|
|
256
|
+
},
|
|
257
|
+
GroupComment: (data) => {
|
|
258
|
+
if (options.comments) return `// ## ${data.content}\n`;
|
|
259
|
+
return "";
|
|
260
|
+
},
|
|
261
|
+
ResourceComment: (data) => {
|
|
262
|
+
if (options.comments) return `// ### ${data.content}\n`;
|
|
263
|
+
return "";
|
|
264
|
+
},
|
|
265
|
+
Junk: (data) => {
|
|
266
|
+
if (options.errorOnJunk) {
|
|
267
|
+
throw new Error("Junk found", { cause: data });
|
|
268
|
+
}
|
|
269
|
+
return "";
|
|
270
|
+
},
|
|
271
|
+
// Element
|
|
272
|
+
TextElement: (data) => {
|
|
273
|
+
return data.value.replaceAll("`", "\\`"); // escape string literal
|
|
274
|
+
},
|
|
275
|
+
Placeable: (data, parent) => {
|
|
276
|
+
return `${options.useIsolating ? "\u2068" : ""}\${${compileType(
|
|
277
|
+
data.expression,
|
|
278
|
+
parent,
|
|
279
|
+
)}}${options.useIsolating ? "\u2069" : ""}`;
|
|
280
|
+
},
|
|
281
|
+
// Expression
|
|
282
|
+
StringLiteral: (data, parent) => {
|
|
283
|
+
// JSON.stringify at parent level
|
|
284
|
+
if (["NamedArgument"].includes(parent)) {
|
|
285
|
+
return `${data.value}`;
|
|
286
|
+
}
|
|
287
|
+
return `"${data.value}"`;
|
|
288
|
+
},
|
|
289
|
+
NumberLiteral: (data) => {
|
|
290
|
+
const decimal = Number.parseFloat(data.value);
|
|
291
|
+
const number = Number.isInteger(decimal)
|
|
292
|
+
? Number.parseInt(data.value, 10)
|
|
293
|
+
: decimal;
|
|
294
|
+
return Intl.NumberFormat(options.locale).format(number);
|
|
295
|
+
},
|
|
296
|
+
VariableReference: (data, parent) => {
|
|
297
|
+
functions.__formatVariable = true;
|
|
298
|
+
metadata[variable].params = true;
|
|
299
|
+
const value = `${options.params}?.${data.id.name}`;
|
|
300
|
+
if (["Message", "Variant", "Attribute"].includes(parent)) {
|
|
301
|
+
return `__formatVariable(${value})`;
|
|
302
|
+
}
|
|
303
|
+
return value;
|
|
304
|
+
},
|
|
305
|
+
MessageReference: (data) => {
|
|
306
|
+
const messageName = compileType(data.id);
|
|
307
|
+
metadata[variable].params ||= metadata[messageName].params;
|
|
308
|
+
if (!options.disableMinify) {
|
|
309
|
+
if (metadata[messageName].params) {
|
|
310
|
+
return `${messageName}(${options.params})`;
|
|
311
|
+
}
|
|
312
|
+
return `${messageName}`;
|
|
313
|
+
}
|
|
314
|
+
return `${messageName}(${
|
|
315
|
+
metadata[messageName].params ? options.params : ""
|
|
316
|
+
})`;
|
|
317
|
+
},
|
|
318
|
+
TermReference: (data) => {
|
|
319
|
+
const termName = compileType(data.id);
|
|
320
|
+
metadata[variable].params ||= metadata[termName].params;
|
|
340
321
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
322
|
+
let params;
|
|
323
|
+
if (metadata[termName].params) {
|
|
324
|
+
let { named } = compileFunctionArguments(data);
|
|
325
|
+
named = JSON.stringify(named);
|
|
326
|
+
if (named) {
|
|
327
|
+
params = `{ ...${options.params}, ${named.substring(
|
|
328
|
+
1,
|
|
329
|
+
named.length - 1,
|
|
330
|
+
)} }`;
|
|
331
|
+
} else {
|
|
332
|
+
params = options.params;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!options.disableMinify) {
|
|
336
|
+
if (metadata[termName].params) {
|
|
337
|
+
return `${termName}(${params})`;
|
|
338
|
+
}
|
|
339
|
+
return `${termName}`;
|
|
340
|
+
}
|
|
341
|
+
return `${termName}(${params ? params : ""})`;
|
|
342
|
+
},
|
|
343
|
+
NamedArgument: (data) => {
|
|
344
|
+
// Inconsistent: `NamedArgument` uses `name` instead of `id` for Identifier
|
|
345
|
+
const key = data.name.name; // Don't transform value
|
|
346
|
+
const value = compileType(data.value, data.type);
|
|
347
|
+
return `${key}: ${value}`;
|
|
348
|
+
},
|
|
349
|
+
SelectExpression: (data) => {
|
|
350
|
+
functions.__select = true;
|
|
351
|
+
metadata[variable].params = true;
|
|
352
|
+
const value = compileType(data.selector);
|
|
353
|
+
let fallback;
|
|
354
|
+
return `__select(\n ${value},\n {\n${data.variants
|
|
355
|
+
.filter((data) => {
|
|
356
|
+
if (data.default) {
|
|
357
|
+
fallback = compileType(data.value, data.type);
|
|
358
|
+
}
|
|
359
|
+
return !data.default;
|
|
360
|
+
})
|
|
361
|
+
.map((data) => {
|
|
362
|
+
return ` ${compileType(data)}`;
|
|
363
|
+
})
|
|
364
|
+
.join(",\n")}\n },\n ${fallback}\n )`;
|
|
365
|
+
},
|
|
366
|
+
Variant: (data, parent) => {
|
|
367
|
+
// Inconsistent: `Variant` uses `key` instead of `id` for Identifier
|
|
368
|
+
const key = compileType(data.key);
|
|
369
|
+
const value = compileType(data.value, data.type);
|
|
370
|
+
return ` '${key}': ${value}`;
|
|
371
|
+
},
|
|
372
|
+
FunctionReference: (data) => {
|
|
373
|
+
return `${types[data.id.name](compileFunctionArguments(data))}`;
|
|
374
|
+
},
|
|
375
|
+
// Functions
|
|
376
|
+
DATETIME: (data) => {
|
|
377
|
+
functions.__formatDateTime = true;
|
|
378
|
+
const { positional, named } = data;
|
|
379
|
+
const value = positional[0];
|
|
380
|
+
return `__formatDateTime(${value}, ${JSON.stringify(named)})`;
|
|
381
|
+
},
|
|
382
|
+
RELATIVETIME: (data) => {
|
|
383
|
+
functions.__formatRelativeTime = true;
|
|
384
|
+
const { positional, named } = data;
|
|
385
|
+
const value = positional[0];
|
|
386
|
+
return `__formatRelativeTime(${value}, ${JSON.stringify(named)})`;
|
|
387
|
+
},
|
|
388
|
+
NUMBER: (data) => {
|
|
389
|
+
functions.__formatNumber = true;
|
|
390
|
+
const { positional, named } = data;
|
|
391
|
+
const value = positional[0];
|
|
392
|
+
return `__formatNumber(${value}, ${JSON.stringify(named)})`;
|
|
393
|
+
},
|
|
394
|
+
};
|
|
347
395
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
translations += compileType(data)
|
|
352
|
-
}
|
|
396
|
+
if (/\t/.test(src)) {
|
|
397
|
+
src = src.replace(/\t/g, " ");
|
|
398
|
+
}
|
|
353
399
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
) {
|
|
360
|
-
output += `const __locales = ${JSON.stringify(opts.locale)}\n`
|
|
361
|
-
}
|
|
362
|
-
/*
|
|
363
|
-
const relativeTimeFormat = new Intl.RelativeTimeFormat(lang, {
|
|
364
|
-
localeMatcher: 'best fit',
|
|
365
|
-
numeric: 'always',
|
|
366
|
-
style: 'long'
|
|
367
|
-
})
|
|
400
|
+
const { body } = parse(src);
|
|
401
|
+
let translations = ``;
|
|
402
|
+
for (const data of body) {
|
|
403
|
+
translations += compileType(data);
|
|
404
|
+
}
|
|
368
405
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
output += `
|
|
406
|
+
let output = ``;
|
|
407
|
+
if (
|
|
408
|
+
functions.__formatVariable ||
|
|
409
|
+
functions.__formatDateTime ||
|
|
410
|
+
functions.__formatNumber ||
|
|
411
|
+
functions.__formatRelativeTime ||
|
|
412
|
+
functions.__select
|
|
413
|
+
) {
|
|
414
|
+
output += `const __locales = ${JSON.stringify(options.locale)}\nconst __intlCache = {}\n`;
|
|
415
|
+
}
|
|
416
|
+
if (functions.__formatRelativeTime) {
|
|
417
|
+
output += `
|
|
382
418
|
const __relativeTimeDiff = (d) => {
|
|
383
419
|
const msPerMinute = 60 * 1000
|
|
384
420
|
const msPerHour = msPerMinute * 60
|
|
@@ -412,60 +448,66 @@ const __formatRelativeTime = (value, options) => {
|
|
|
412
448
|
if (typeof value === 'string') value = new Date(value)
|
|
413
449
|
if (isNaN(value.getTime())) return value
|
|
414
450
|
try {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
`
|
|
451
|
+
const [duration, unit] = __relativeTimeDiff(value)
|
|
452
|
+
const k = JSON.stringify(options) ?? ''
|
|
453
|
+
return (__intlCache['R'+k] ??= new Intl.RelativeTimeFormat(__locales, options)).format(duration, unit)
|
|
454
|
+
} catch (e) {
|
|
455
|
+
// RelativeTimeFormat unsupported or invalid options, fall back to DateTimeFormat
|
|
421
456
|
}
|
|
422
|
-
|
|
423
|
-
|
|
457
|
+
const k = JSON.stringify(options) ?? ''
|
|
458
|
+
return (__intlCache['D'+k] ??= new Intl.DateTimeFormat(__locales, options)).format(value)
|
|
459
|
+
}
|
|
460
|
+
`;
|
|
461
|
+
}
|
|
462
|
+
if (functions.__formatDateTime) {
|
|
463
|
+
output += `
|
|
424
464
|
const __formatDateTime = (value, options) => {
|
|
425
465
|
if (typeof value === 'string') value = new Date(value)
|
|
426
466
|
if (isNaN(value.getTime())) return value
|
|
427
|
-
|
|
467
|
+
const k = JSON.stringify(options) ?? ''
|
|
468
|
+
return (__intlCache['D'+k] ??= new Intl.DateTimeFormat(__locales, options)).format(value)
|
|
428
469
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
if (functions.__formatVariable || functions.__formatNumber) {
|
|
473
|
+
output += `
|
|
433
474
|
const __formatNumber = (value, options) => {
|
|
434
|
-
|
|
475
|
+
const k = JSON.stringify(options) ?? ''
|
|
476
|
+
return (__intlCache['N'+k] ??= new Intl.NumberFormat(__locales, options)).format(value)
|
|
435
477
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
478
|
+
`;
|
|
479
|
+
}
|
|
480
|
+
if (functions.__formatVariable) {
|
|
481
|
+
output += `
|
|
440
482
|
const __formatVariable = (value) => {
|
|
441
483
|
if (typeof value === 'string') return value
|
|
442
|
-
const decimal =
|
|
443
|
-
const number = Number.isInteger(decimal) ? Number.parseInt(value) : decimal
|
|
484
|
+
const decimal = Number.parseFloat(value)
|
|
485
|
+
const number = Number.isInteger(decimal) ? Number.parseInt(value, 10) : decimal
|
|
444
486
|
return __formatNumber(number)
|
|
445
487
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
488
|
+
`;
|
|
489
|
+
}
|
|
490
|
+
if (functions.__select) {
|
|
491
|
+
output += `
|
|
450
492
|
const __select = (value, cases, fallback, options) => {
|
|
451
|
-
const
|
|
452
|
-
const rule =
|
|
493
|
+
const k = JSON.stringify(options) ?? ''
|
|
494
|
+
const rule = (__intlCache['P'+k] ??= new Intl.PluralRules(__locales, options)).select(value)
|
|
453
495
|
return cases[value] ?? cases[rule] ?? fallback
|
|
454
496
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
497
|
+
`;
|
|
498
|
+
}
|
|
499
|
+
output += `\n${translations}`;
|
|
500
|
+
output += `const __exports = {\n ${exports.join(",\n ")}\n}`;
|
|
501
|
+
output += `\nexport default ${options.exportDefault}`;
|
|
460
502
|
|
|
461
|
-
|
|
462
|
-
}
|
|
503
|
+
return output;
|
|
504
|
+
};
|
|
463
505
|
|
|
464
506
|
const variableNotation = {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
507
|
+
camelCase,
|
|
508
|
+
pascalCase,
|
|
509
|
+
snakeCase,
|
|
510
|
+
constantCase,
|
|
511
|
+
};
|
|
470
512
|
|
|
471
|
-
export default compile
|
|
513
|
+
export default compile;
|