@lingui/macro 4.8.0-next.0 → 4.8.0-next.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.
@@ -0,0 +1,1063 @@
1
+ 'use strict';
2
+
3
+ const types = require('@babel/types');
4
+ const generateMessageId = require('@lingui/message-utils/generateMessageId');
5
+ const conf = require('@lingui/conf');
6
+
7
+ const metaOptions = ["id", "comment", "props"];
8
+ const escapedMetaOptionsRe = new RegExp(`^_(${metaOptions.join("|")})$`);
9
+ class ICUMessageFormat {
10
+ fromTokens(tokens) {
11
+ return (Array.isArray(tokens) ? tokens : [tokens]).map((token) => this.processToken(token)).filter(Boolean).reduce(
12
+ (props, message) => ({
13
+ ...message,
14
+ message: props.message + message.message,
15
+ values: { ...props.values, ...message.values },
16
+ jsxElements: { ...props.jsxElements, ...message.jsxElements }
17
+ }),
18
+ {
19
+ message: "",
20
+ values: {},
21
+ jsxElements: {}
22
+ }
23
+ );
24
+ }
25
+ processToken(token) {
26
+ const jsxElements = {};
27
+ if (token.type === "text") {
28
+ return {
29
+ message: token.value
30
+ };
31
+ } else if (token.type === "arg") {
32
+ if (token.value !== void 0 && types.isJSXEmptyExpression(token.value)) {
33
+ return null;
34
+ }
35
+ const values = token.value !== void 0 ? { [token.name]: token.value } : {};
36
+ switch (token.format) {
37
+ case "plural":
38
+ case "select":
39
+ case "selectordinal":
40
+ const formatOptions = Object.keys(token.options).filter((key) => token.options[key] != null).map((key) => {
41
+ let value = token.options[key];
42
+ key = key.replace(escapedMetaOptionsRe, "$1");
43
+ if (key === "offset") {
44
+ return `offset:${value}`;
45
+ }
46
+ if (typeof value !== "string") {
47
+ const {
48
+ message,
49
+ values: childValues,
50
+ jsxElements: childJsxElements
51
+ } = this.fromTokens(value);
52
+ Object.assign(values, childValues);
53
+ Object.assign(jsxElements, childJsxElements);
54
+ value = message;
55
+ }
56
+ return `${key} {${value}}`;
57
+ }).join(" ");
58
+ return {
59
+ message: `{${token.name}, ${token.format}, ${formatOptions}}`,
60
+ values,
61
+ jsxElements
62
+ };
63
+ default:
64
+ return {
65
+ message: `{${token.name}}`,
66
+ values
67
+ };
68
+ }
69
+ } else if (token.type === "element") {
70
+ let message = "";
71
+ let elementValues = {};
72
+ Object.assign(jsxElements, { [token.name]: token.value });
73
+ token.children.forEach((child) => {
74
+ const {
75
+ message: childMessage,
76
+ values: childValues,
77
+ jsxElements: childJsxElements
78
+ } = this.fromTokens(child);
79
+ message += childMessage;
80
+ Object.assign(elementValues, childValues);
81
+ Object.assign(jsxElements, childJsxElements);
82
+ });
83
+ return {
84
+ message: token.children.length ? `<${token.name}>${message}</${token.name}>` : `<${token.name}/>`,
85
+ values: elementValues,
86
+ jsxElements
87
+ };
88
+ }
89
+ throw new Error(`Unknown token type ${token.type}`);
90
+ }
91
+ }
92
+
93
+ const makeCounter = (index = 0) => () => index++;
94
+
95
+ const ID = "id";
96
+ const MESSAGE = "message";
97
+ const COMMENT = "comment";
98
+ const EXTRACT_MARK = "i18n";
99
+ const CONTEXT = "context";
100
+ const MACRO_PACKAGE = "@lingui/macro";
101
+ var JsMacroName = /* @__PURE__ */ ((JsMacroName2) => {
102
+ JsMacroName2["t"] = "t";
103
+ JsMacroName2["plural"] = "plural";
104
+ JsMacroName2["select"] = "select";
105
+ JsMacroName2["selectOrdinal"] = "selectOrdinal";
106
+ JsMacroName2["msg"] = "msg";
107
+ JsMacroName2["defineMessage"] = "defineMessage";
108
+ JsMacroName2["useLingui"] = "useLingui";
109
+ return JsMacroName2;
110
+ })(JsMacroName || {});
111
+ var JsxMacroName = /* @__PURE__ */ ((JsxMacroName2) => {
112
+ JsxMacroName2["Trans"] = "Trans";
113
+ JsxMacroName2["Plural"] = "Plural";
114
+ JsxMacroName2["Select"] = "Select";
115
+ JsxMacroName2["SelectOrdinal"] = "SelectOrdinal";
116
+ return JsxMacroName2;
117
+ })(JsxMacroName || {});
118
+
119
+ var __defProp$1 = Object.defineProperty;
120
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
121
+ var __publicField$1 = (obj, key, value) => {
122
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
123
+ return value;
124
+ };
125
+ const pluralRuleRe = /(_[\d\w]+|zero|one|two|few|many|other)/;
126
+ const jsx2icuExactChoice = (value) => value.replace(/_(\d+)/, "=$1").replace(/_(\w+)/, "$1");
127
+ const keepSpaceRe$1 = /\s*(?:\r\n|\r|\n)+\s*/g;
128
+ const stripAroundTagsRe = /(?:([>}])(?:\r\n|\r|\n)+\s*|(?:\r\n|\r|\n)+\s*(?=[<{]))/g;
129
+ function maybeNodeValue(node) {
130
+ if (!node)
131
+ return null;
132
+ if (node.type === "StringLiteral")
133
+ return node.value;
134
+ if (node.type === "JSXAttribute")
135
+ return maybeNodeValue(node.value);
136
+ if (node.type === "JSXExpressionContainer")
137
+ return maybeNodeValue(node.expression);
138
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0)
139
+ return node.quasis[0].value.raw;
140
+ return null;
141
+ }
142
+ function normalizeWhitespace$1(text) {
143
+ return text.replace(stripAroundTagsRe, "$1").replace(keepSpaceRe$1, " ").replace(/\\n/g, "\n").replace(/\\s/g, " ").replace(/(\s+})/gm, "}").replace(/({\s+)/gm, "{").trim();
144
+ }
145
+ class MacroJSX {
146
+ constructor({ types }, opts) {
147
+ __publicField$1(this, "types");
148
+ __publicField$1(this, "expressionIndex", makeCounter());
149
+ __publicField$1(this, "elementIndex", makeCounter());
150
+ __publicField$1(this, "stripNonEssentialProps");
151
+ __publicField$1(this, "transImportName");
152
+ __publicField$1(this, "createStringJsxAttribute", (name, value) => {
153
+ return this.types.jsxAttribute(
154
+ this.types.jsxIdentifier(name),
155
+ this.types.jsxExpressionContainer(this.types.stringLiteral(value))
156
+ );
157
+ });
158
+ __publicField$1(this, "replacePath", (path) => {
159
+ if (!path.isJSXElement()) {
160
+ return false;
161
+ }
162
+ const tokens = this.tokenizeNode(path, true, true);
163
+ if (!tokens) {
164
+ return false;
165
+ }
166
+ const messageFormat = new ICUMessageFormat();
167
+ const {
168
+ message: messageRaw,
169
+ values,
170
+ jsxElements
171
+ } = messageFormat.fromTokens(tokens);
172
+ const message = normalizeWhitespace$1(messageRaw);
173
+ const { attributes, id, comment, context } = this.stripMacroAttributes(
174
+ path
175
+ );
176
+ if (!id && !message) {
177
+ throw new Error("Incorrect usage of Trans");
178
+ }
179
+ if (id) {
180
+ attributes.push(
181
+ this.types.jsxAttribute(
182
+ this.types.jsxIdentifier(ID),
183
+ this.types.stringLiteral(id)
184
+ )
185
+ );
186
+ } else {
187
+ attributes.push(
188
+ this.createStringJsxAttribute(ID, generateMessageId.generateMessageId(message, context))
189
+ );
190
+ }
191
+ if (!this.stripNonEssentialProps) {
192
+ if (message) {
193
+ attributes.push(this.createStringJsxAttribute(MESSAGE, message));
194
+ }
195
+ if (comment) {
196
+ attributes.push(
197
+ this.types.jsxAttribute(
198
+ this.types.jsxIdentifier(COMMENT),
199
+ this.types.stringLiteral(comment)
200
+ )
201
+ );
202
+ }
203
+ if (context) {
204
+ attributes.push(
205
+ this.types.jsxAttribute(
206
+ this.types.jsxIdentifier(CONTEXT),
207
+ this.types.stringLiteral(context)
208
+ )
209
+ );
210
+ }
211
+ }
212
+ const valuesObject = Object.keys(values).map(
213
+ (key) => this.types.objectProperty(this.types.identifier(key), values[key])
214
+ );
215
+ if (valuesObject.length) {
216
+ attributes.push(
217
+ this.types.jsxAttribute(
218
+ this.types.jsxIdentifier("values"),
219
+ this.types.jsxExpressionContainer(
220
+ this.types.objectExpression(valuesObject)
221
+ )
222
+ )
223
+ );
224
+ }
225
+ if (Object.keys(jsxElements).length) {
226
+ attributes.push(
227
+ this.types.jsxAttribute(
228
+ this.types.jsxIdentifier("components"),
229
+ this.types.jsxExpressionContainer(
230
+ this.types.objectExpression(
231
+ Object.keys(jsxElements).map(
232
+ (key) => this.types.objectProperty(
233
+ this.types.identifier(key),
234
+ jsxElements[key]
235
+ )
236
+ )
237
+ )
238
+ )
239
+ )
240
+ );
241
+ }
242
+ const newNode = this.types.jsxElement(
243
+ this.types.jsxOpeningElement(
244
+ this.types.jsxIdentifier(this.transImportName),
245
+ attributes,
246
+ true
247
+ ),
248
+ null,
249
+ [],
250
+ true
251
+ );
252
+ newNode.loc = path.node.loc;
253
+ return newNode;
254
+ });
255
+ __publicField$1(this, "attrName", (names, exclude = false) => {
256
+ const namesRe = new RegExp("^(" + names.join("|") + ")$");
257
+ return (attr) => {
258
+ const name = attr.name.name;
259
+ return exclude ? !namesRe.test(name) : namesRe.test(name);
260
+ };
261
+ });
262
+ __publicField$1(this, "stripMacroAttributes", (path) => {
263
+ const { attributes } = path.node.openingElement;
264
+ const id = attributes.find(this.attrName([ID]));
265
+ const message = attributes.find(this.attrName([MESSAGE]));
266
+ const comment = attributes.find(this.attrName([COMMENT]));
267
+ const context = attributes.find(this.attrName([CONTEXT]));
268
+ let reserved = [ID, MESSAGE, COMMENT, CONTEXT];
269
+ if (this.isChoiceComponent(path)) {
270
+ reserved = [
271
+ ...reserved,
272
+ "_\\w+",
273
+ "_\\d+",
274
+ "zero",
275
+ "one",
276
+ "two",
277
+ "few",
278
+ "many",
279
+ "other",
280
+ "value",
281
+ "offset"
282
+ ];
283
+ }
284
+ return {
285
+ id: maybeNodeValue(id),
286
+ message: maybeNodeValue(message),
287
+ comment: maybeNodeValue(comment),
288
+ context: maybeNodeValue(context),
289
+ attributes: attributes.filter(this.attrName(reserved, true))
290
+ };
291
+ });
292
+ __publicField$1(this, "tokenizeNode", (path, ignoreExpression = false, ignoreElement = false) => {
293
+ if (this.isTransComponent(path)) {
294
+ return this.tokenizeTrans(path);
295
+ }
296
+ const componentName = this.isChoiceComponent(path);
297
+ if (componentName) {
298
+ return [
299
+ this.tokenizeChoiceComponent(
300
+ path,
301
+ componentName
302
+ )
303
+ ];
304
+ }
305
+ if (path.isJSXElement() && !ignoreElement) {
306
+ return [this.tokenizeElement(path)];
307
+ }
308
+ if (!ignoreExpression) {
309
+ return [this.tokenizeExpression(path)];
310
+ }
311
+ });
312
+ __publicField$1(this, "tokenizeTrans", (path) => {
313
+ return path.get("children").flatMap((child) => this.tokenizeChildren(child)).filter(Boolean);
314
+ });
315
+ __publicField$1(this, "tokenizeChildren", (path) => {
316
+ if (path.isJSXExpressionContainer()) {
317
+ const exp = path.get("expression");
318
+ if (exp.isStringLiteral()) {
319
+ return [this.tokenizeText(exp.node.value.replace(/\n/g, "\\n"))];
320
+ }
321
+ if (exp.isTemplateLiteral()) {
322
+ return this.tokenizeTemplateLiteral(exp);
323
+ }
324
+ if (exp.isConditionalExpression()) {
325
+ return [this.tokenizeConditionalExpression(exp)];
326
+ }
327
+ if (exp.isJSXElement()) {
328
+ return this.tokenizeNode(exp);
329
+ }
330
+ return [this.tokenizeExpression(exp)];
331
+ } else if (path.isJSXElement()) {
332
+ return this.tokenizeNode(path);
333
+ } else if (path.isJSXSpreadChild()) ; else if (path.isJSXText()) {
334
+ return [this.tokenizeText(path.node.value)];
335
+ } else ;
336
+ });
337
+ __publicField$1(this, "tokenizeChoiceComponent", (path, componentName) => {
338
+ const element = path.get("openingElement");
339
+ const format = componentName.toLowerCase();
340
+ const props = element.get("attributes").filter((attr) => {
341
+ return this.attrName(
342
+ [
343
+ ID,
344
+ COMMENT,
345
+ MESSAGE,
346
+ CONTEXT,
347
+ "key",
348
+ // we remove <Trans /> react props that are not useful for translation
349
+ "render",
350
+ "component",
351
+ "components"
352
+ ],
353
+ true
354
+ )(attr.node);
355
+ });
356
+ const token = {
357
+ type: "arg",
358
+ format,
359
+ name: null,
360
+ value: void 0,
361
+ options: {
362
+ offset: void 0
363
+ }
364
+ };
365
+ for (const _attr of props) {
366
+ if (_attr.isJSXSpreadAttribute()) {
367
+ continue;
368
+ }
369
+ const attr = _attr;
370
+ if (this.types.isJSXNamespacedName(attr.node.name)) {
371
+ continue;
372
+ }
373
+ const name = attr.node.name.name;
374
+ const value = attr.get("value");
375
+ if (name === "value") {
376
+ const exp = value.isLiteral() ? value : value.get("expression");
377
+ token.name = this.expressionToArgument(exp);
378
+ token.value = exp.node;
379
+ } else if (format !== "select" && name === "offset") {
380
+ token.options.offset = value.isStringLiteral() || value.isNumericLiteral() ? value.node.value : value.get(
381
+ "expression"
382
+ ).node.value;
383
+ } else {
384
+ let option;
385
+ if (value.isStringLiteral()) {
386
+ option = value.node.extra.raw.replace(
387
+ /(["'])(.*)\1/,
388
+ "$2"
389
+ );
390
+ } else {
391
+ option = this.tokenizeChildren(value);
392
+ }
393
+ if (pluralRuleRe.test(name)) {
394
+ token.options[jsx2icuExactChoice(name)] = option;
395
+ } else {
396
+ token.options[name] = option;
397
+ }
398
+ }
399
+ }
400
+ return token;
401
+ });
402
+ __publicField$1(this, "tokenizeElement", (path) => {
403
+ const name = this.elementIndex();
404
+ return {
405
+ type: "element",
406
+ name,
407
+ value: {
408
+ ...path.node,
409
+ children: [],
410
+ openingElement: {
411
+ ...path.node.openingElement,
412
+ selfClosing: true
413
+ }
414
+ },
415
+ children: this.tokenizeTrans(path)
416
+ };
417
+ });
418
+ __publicField$1(this, "tokenizeExpression", (path) => {
419
+ return {
420
+ type: "arg",
421
+ name: this.expressionToArgument(path),
422
+ value: path.node
423
+ };
424
+ });
425
+ __publicField$1(this, "tokenizeConditionalExpression", (exp) => {
426
+ exp.traverse({
427
+ JSXElement: (el) => {
428
+ if (this.isTransComponent(el) || this.isChoiceComponent(el)) {
429
+ this.replacePath(el);
430
+ el.skip();
431
+ }
432
+ }
433
+ });
434
+ return {
435
+ type: "arg",
436
+ name: this.expressionToArgument(exp),
437
+ value: exp.node
438
+ };
439
+ });
440
+ __publicField$1(this, "tokenizeText", (value) => {
441
+ return {
442
+ type: "text",
443
+ value
444
+ };
445
+ });
446
+ __publicField$1(this, "isLinguiComponent", (path, name) => {
447
+ return path.isJSXElement() && path.get("openingElement").get("name").referencesImport(MACRO_PACKAGE, name);
448
+ });
449
+ __publicField$1(this, "isTransComponent", (path) => {
450
+ return this.isLinguiComponent(path, JsxMacroName.Trans);
451
+ });
452
+ __publicField$1(this, "isChoiceComponent", (path) => {
453
+ if (this.isLinguiComponent(path, JsxMacroName.Plural)) {
454
+ return JsxMacroName.Plural;
455
+ }
456
+ if (this.isLinguiComponent(path, JsxMacroName.Select)) {
457
+ return JsxMacroName.Select;
458
+ }
459
+ if (this.isLinguiComponent(path, JsxMacroName.SelectOrdinal)) {
460
+ return JsxMacroName.SelectOrdinal;
461
+ }
462
+ });
463
+ __publicField$1(this, "getJsxTagName", (node) => {
464
+ if (this.types.isJSXIdentifier(node.openingElement.name)) {
465
+ return node.openingElement.name.name;
466
+ }
467
+ });
468
+ this.types = types;
469
+ this.stripNonEssentialProps = opts.stripNonEssentialProps;
470
+ this.transImportName = opts.transImportName;
471
+ }
472
+ tokenizeTemplateLiteral(exp) {
473
+ const expressions = exp.get("expressions");
474
+ return exp.get("quasis").flatMap(({ node: text }, i) => {
475
+ const value = /\\u[a-fA-F0-9]{4}|\\x[a-fA-F0-9]{2}/g.test(text.value.raw) ? text.value.cooked : text.value.raw;
476
+ let argTokens = [];
477
+ const currExp = expressions[i];
478
+ if (currExp) {
479
+ argTokens = currExp.isCallExpression() ? this.tokenizeNode(currExp) : [this.tokenizeExpression(currExp)];
480
+ }
481
+ return [
482
+ ...value ? [this.tokenizeText(this.clearBackslashes(value))] : [],
483
+ ...argTokens
484
+ ];
485
+ });
486
+ }
487
+ expressionToArgument(path) {
488
+ return path.isIdentifier() ? path.node.name : String(this.expressionIndex());
489
+ }
490
+ /**
491
+ * We clean '//\` ' to just '`'
492
+ **/
493
+ clearBackslashes(value) {
494
+ return value.replace(/\\`/g, "`");
495
+ }
496
+ }
497
+
498
+ var __defProp = Object.defineProperty;
499
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
500
+ var __publicField = (obj, key, value) => {
501
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
502
+ return value;
503
+ };
504
+ const keepSpaceRe = /(?:\\(?:\r\n|\r|\n))+\s+/g;
505
+ const keepNewLineRe = /(?:\r\n|\r|\n)+\s+/g;
506
+ function normalizeWhitespace(text) {
507
+ return text.replace(keepSpaceRe, " ").replace(keepNewLineRe, "\n").trim();
508
+ }
509
+ function buildICUFromTokens(tokens) {
510
+ const messageFormat = new ICUMessageFormat();
511
+ const { message, values } = messageFormat.fromTokens(tokens);
512
+ return { message: normalizeWhitespace(message), values };
513
+ }
514
+ class MacroJs {
515
+ constructor({ types }, opts) {
516
+ // Babel Types
517
+ __publicField(this, "types");
518
+ // Identifier of i18n object
519
+ __publicField(this, "i18nImportName");
520
+ __publicField(this, "useLinguiImportName");
521
+ __publicField(this, "stripNonEssentialProps");
522
+ __publicField(this, "needsUseLinguiImport", false);
523
+ __publicField(this, "needsI18nImport", false);
524
+ // Positional expressions counter (e.g. for placeholders `Hello {0}, today is {1}`)
525
+ __publicField(this, "_expressionIndex", makeCounter());
526
+ __publicField(this, "replacePathWithMessage", (path, tokens, linguiInstance) => {
527
+ return this.createI18nCall(
528
+ this.createMessageDescriptorFromTokens(tokens, path.node.loc),
529
+ linguiInstance
530
+ );
531
+ });
532
+ __publicField(this, "replacePath", (path) => {
533
+ this._expressionIndex = makeCounter();
534
+ if (
535
+ //
536
+ path.isCallExpression() && this.isDefineMessage(path.get("callee"))
537
+ ) {
538
+ return this.processDescriptor(
539
+ path.get("arguments")[0]
540
+ );
541
+ }
542
+ if (path.isTaggedTemplateExpression() && this.isDefineMessage(path.get("tag"))) {
543
+ const tokens2 = this.tokenizeTemplateLiteral(path.get("quasi"));
544
+ return this.createMessageDescriptorFromTokens(tokens2, path.node.loc);
545
+ }
546
+ if (path.isTaggedTemplateExpression()) {
547
+ const tag = path.get("tag");
548
+ if (tag.isCallExpression() && tag.get("arguments")[0].isExpression() && this.isLinguiIdentifier(tag.get("callee"), JsMacroName.t)) {
549
+ const i18nInstance = tag.get("arguments")[0].node;
550
+ const tokens2 = this.tokenizeNode(path);
551
+ return this.replacePathWithMessage(path, tokens2, i18nInstance);
552
+ }
553
+ }
554
+ if (path.isCallExpression()) {
555
+ const callee = path.get("callee");
556
+ if (callee.isCallExpression() && callee.get("arguments")[0].isExpression() && this.isLinguiIdentifier(callee.get("callee"), JsMacroName.t)) {
557
+ const i18nInstance = callee.node.arguments[0];
558
+ return this.replaceTAsFunction(
559
+ path,
560
+ i18nInstance
561
+ );
562
+ }
563
+ }
564
+ if (path.isCallExpression() && this.isLinguiIdentifier(path.get("callee"), JsMacroName.t)) {
565
+ this.needsI18nImport = true;
566
+ return this.replaceTAsFunction(path);
567
+ }
568
+ if (path.isCallExpression() && this.isLinguiIdentifier(path.get("callee"), JsMacroName.useLingui)) {
569
+ this.needsUseLinguiImport = true;
570
+ return this.processUseLingui(path);
571
+ }
572
+ const tokens = this.tokenizeNode(path, true);
573
+ if (tokens) {
574
+ this.needsI18nImport = true;
575
+ return this.replacePathWithMessage(path, tokens);
576
+ }
577
+ return false;
578
+ });
579
+ /**
580
+ * macro `t` is called with MessageDescriptor, after that
581
+ * we create a new node to append it to i18n._
582
+ */
583
+ __publicField(this, "replaceTAsFunction", (path, linguiInstance) => {
584
+ const descriptor = this.processDescriptor(
585
+ path.get("arguments")[0]
586
+ );
587
+ return this.createI18nCall(descriptor, linguiInstance);
588
+ });
589
+ /**
590
+ * `processDescriptor` expand macros inside message descriptor.
591
+ * Message descriptor is used in `defineMessage`.
592
+ *
593
+ * {
594
+ * comment: "Description",
595
+ * message: plural("value", { one: "book", other: "books" })
596
+ * }
597
+ *
598
+ * ↓ ↓ ↓ ↓ ↓ ↓
599
+ *
600
+ * {
601
+ * comment: "Description",
602
+ * id: <hash>
603
+ * message: "{value, plural, one {book} other {books}}"
604
+ * }
605
+ *
606
+ */
607
+ __publicField(this, "processDescriptor", (descriptor) => {
608
+ const messageProperty = this.getObjectPropertyByKey(descriptor, MESSAGE);
609
+ const idProperty = this.getObjectPropertyByKey(descriptor, ID);
610
+ const contextProperty = this.getObjectPropertyByKey(descriptor, CONTEXT);
611
+ const commentProperty = this.getObjectPropertyByKey(descriptor, COMMENT);
612
+ const properties = [];
613
+ if (idProperty) {
614
+ properties.push(idProperty.node);
615
+ }
616
+ if (!this.stripNonEssentialProps && contextProperty) {
617
+ properties.push(contextProperty.node);
618
+ }
619
+ if (messageProperty) {
620
+ const messageValue = messageProperty.get("value");
621
+ const tokens = messageValue.isTemplateLiteral() ? this.tokenizeTemplateLiteral(messageValue) : this.tokenizeNode(messageValue, true);
622
+ let messageNode = messageValue.node;
623
+ if (tokens) {
624
+ const { message, values } = buildICUFromTokens(tokens);
625
+ messageNode = this.types.stringLiteral(message);
626
+ properties.push(this.createValuesProperty(values));
627
+ }
628
+ if (!this.stripNonEssentialProps) {
629
+ properties.push(
630
+ this.createObjectProperty(MESSAGE, messageNode)
631
+ );
632
+ }
633
+ if (!idProperty && this.types.isStringLiteral(messageNode)) {
634
+ const context = contextProperty && this.getTextFromExpression(
635
+ contextProperty.get("value").node
636
+ );
637
+ properties.push(this.createIdProperty(messageNode.value, context));
638
+ }
639
+ }
640
+ if (!this.stripNonEssentialProps && commentProperty) {
641
+ properties.push(commentProperty.node);
642
+ }
643
+ return this.createMessageDescriptor(properties, descriptor.node.loc);
644
+ });
645
+ this.types = types;
646
+ this.i18nImportName = opts.i18nImportName;
647
+ this.useLinguiImportName = opts.useLinguiImportName;
648
+ this.stripNonEssentialProps = opts.stripNonEssentialProps;
649
+ }
650
+ /**
651
+ * Receives reference to `useLingui()` call
652
+ *
653
+ * Finds every usage of { t } destructured from the call
654
+ * and process each reference as usual `t` macro.
655
+ *
656
+ * const { t } = useLingui()
657
+ * t`Message`
658
+ *
659
+ * ↓ ↓ ↓ ↓ ↓ ↓
660
+ *
661
+ * const { _: _t } = useLingui()
662
+ * _t({id: <hash>, message: "Message"})
663
+ */
664
+ processUseLingui(path) {
665
+ if (!path.parentPath.isVariableDeclarator()) {
666
+ throw new Error(
667
+ `\`useLingui\` macro must be used in variable declaration.
668
+
669
+ Example:
670
+
671
+ const { t } = useLingui()
672
+ `
673
+ );
674
+ }
675
+ const varDec = path.parentPath.node;
676
+ const _property = this.types.isObjectPattern(varDec.id) ? varDec.id.properties.find(
677
+ (property) => this.types.isObjectProperty(property) && this.types.isIdentifier(property.key) && this.types.isIdentifier(property.value) && property.key.name == "t"
678
+ ) : null;
679
+ if (!_property) {
680
+ throw new Error(
681
+ `You have to destructure \`t\` when using the \`useLingui\` macro, i.e:
682
+ const { t } = useLingui()
683
+ or
684
+ const { t: _ } = useLingui()
685
+ `
686
+ );
687
+ }
688
+ const uniqTIdentifier = path.scope.generateUidIdentifier("t");
689
+ path.scope.getBinding(_property.value.name)?.referencePaths.forEach((refPath) => {
690
+ const currentPath = refPath.parentPath;
691
+ if (currentPath.isTaggedTemplateExpression()) {
692
+ const tokens = this.tokenizeTemplateLiteral(currentPath);
693
+ const descriptor = this.createMessageDescriptorFromTokens(
694
+ tokens,
695
+ currentPath.node.loc
696
+ );
697
+ const callExpr = this.types.callExpression(
698
+ this.types.identifier(uniqTIdentifier.name),
699
+ [descriptor]
700
+ );
701
+ return currentPath.replaceWith(callExpr);
702
+ }
703
+ if (currentPath.isCallExpression() && currentPath.get("arguments")[0].isObjectExpression()) {
704
+ let descriptor = this.processDescriptor(
705
+ currentPath.get("arguments")[0]
706
+ );
707
+ const callExpr = this.types.callExpression(
708
+ this.types.identifier(uniqTIdentifier.name),
709
+ [descriptor]
710
+ );
711
+ return currentPath.replaceWith(callExpr);
712
+ }
713
+ refPath.replaceWith(this.types.identifier(uniqTIdentifier.name));
714
+ });
715
+ _property.key.name = "_";
716
+ _property.value.name = uniqTIdentifier.name;
717
+ return this.types.callExpression(
718
+ this.types.identifier(this.useLinguiImportName),
719
+ []
720
+ );
721
+ }
722
+ createIdProperty(message, context) {
723
+ return this.createObjectProperty(
724
+ ID,
725
+ this.types.stringLiteral(generateMessageId.generateMessageId(message, context))
726
+ );
727
+ }
728
+ createValuesProperty(values) {
729
+ const valuesObject = Object.keys(values).map(
730
+ (key) => this.types.objectProperty(this.types.identifier(key), values[key])
731
+ );
732
+ if (!valuesObject.length)
733
+ return;
734
+ return this.types.objectProperty(
735
+ this.types.identifier("values"),
736
+ this.types.objectExpression(valuesObject)
737
+ );
738
+ }
739
+ tokenizeNode(path, ignoreExpression = false) {
740
+ const node = path.node;
741
+ if (this.isI18nMethod(path)) {
742
+ return this.tokenizeTemplateLiteral(path);
743
+ }
744
+ const choiceMethod = this.isChoiceMethod(path);
745
+ if (choiceMethod) {
746
+ return [
747
+ this.tokenizeChoiceComponent(
748
+ path,
749
+ choiceMethod
750
+ )
751
+ ];
752
+ }
753
+ if (!ignoreExpression) {
754
+ return [this.tokenizeExpression(node)];
755
+ }
756
+ }
757
+ /**
758
+ * `node` is a TemplateLiteral. node.quasi contains
759
+ * text chunks and node.expressions contains expressions.
760
+ * Both arrays must be zipped together to get the final list of tokens.
761
+ */
762
+ tokenizeTemplateLiteral(path) {
763
+ const tpl = path.isTaggedTemplateExpression() ? path.get("quasi") : path;
764
+ const expressions = tpl.get("expressions");
765
+ return tpl.get("quasis").flatMap((text, i) => {
766
+ const value = /\\u[a-fA-F0-9]{4}|\\x[a-fA-F0-9]{2}/g.test(
767
+ text.node.value.raw
768
+ ) ? text.node.value.cooked : text.node.value.raw;
769
+ let argTokens = [];
770
+ const currExp = expressions[i];
771
+ if (currExp) {
772
+ argTokens = currExp.isCallExpression() ? this.tokenizeNode(currExp) : [this.tokenizeExpression(currExp.node)];
773
+ }
774
+ const textToken = {
775
+ type: "text",
776
+ value: this.clearBackslashes(value)
777
+ };
778
+ return [...value ? [textToken] : [], ...argTokens];
779
+ });
780
+ }
781
+ tokenizeChoiceComponent(path, componentName) {
782
+ const format = componentName.toLowerCase();
783
+ const token = {
784
+ ...this.tokenizeExpression(path.node.arguments[0]),
785
+ format,
786
+ options: {
787
+ offset: void 0
788
+ }
789
+ };
790
+ const props = path.get("arguments")[1].get(
791
+ "properties"
792
+ );
793
+ for (const attr of props) {
794
+ if (!attr.isObjectProperty()) {
795
+ throw new Error("Expected an ObjectProperty");
796
+ }
797
+ const key = attr.get("key");
798
+ const attrValue = attr.get("value");
799
+ const name = key.isNumericLiteral() ? `=${key.node.value}` : key.node.name || key.node.value;
800
+ if (format !== "select" && name === "offset") {
801
+ token.options.offset = attrValue.node.value;
802
+ } else {
803
+ let value;
804
+ if (attrValue.isTemplateLiteral()) {
805
+ value = this.tokenizeTemplateLiteral(attrValue);
806
+ } else if (attrValue.isCallExpression()) {
807
+ value = this.tokenizeNode(attrValue);
808
+ } else if (attrValue.isStringLiteral()) {
809
+ value = attrValue.node.value;
810
+ } else if (attrValue.isExpression()) {
811
+ value = this.tokenizeExpression(attrValue.node);
812
+ } else {
813
+ value = attrValue.value;
814
+ }
815
+ token.options[name] = value;
816
+ }
817
+ }
818
+ return token;
819
+ }
820
+ tokenizeExpression(node) {
821
+ return {
822
+ type: "arg",
823
+ name: this.expressionToArgument(node),
824
+ value: node
825
+ };
826
+ }
827
+ expressionToArgument(exp) {
828
+ if (this.types.isIdentifier(exp)) {
829
+ return exp.name;
830
+ } else if (this.types.isStringLiteral(exp)) {
831
+ return exp.value;
832
+ } else {
833
+ return String(this._expressionIndex());
834
+ }
835
+ }
836
+ /**
837
+ * We clean '//\` ' to just '`'
838
+ */
839
+ clearBackslashes(value) {
840
+ return value.replace(/\\`/g, "`");
841
+ }
842
+ createI18nCall(messageDescriptor, linguiInstance) {
843
+ return this.types.callExpression(
844
+ this.types.memberExpression(
845
+ linguiInstance ?? this.types.identifier(this.i18nImportName),
846
+ this.types.identifier("_")
847
+ ),
848
+ [messageDescriptor]
849
+ );
850
+ }
851
+ createMessageDescriptorFromTokens(tokens, oldLoc) {
852
+ const { message, values } = buildICUFromTokens(tokens);
853
+ const properties = [
854
+ this.createIdProperty(message),
855
+ !this.stripNonEssentialProps ? this.createObjectProperty(MESSAGE, this.types.stringLiteral(message)) : null,
856
+ this.createValuesProperty(values)
857
+ ];
858
+ return this.createMessageDescriptor(
859
+ properties,
860
+ // preserve line numbers for extractor
861
+ oldLoc
862
+ );
863
+ }
864
+ createMessageDescriptor(properties, oldLoc) {
865
+ const newDescriptor = this.types.objectExpression(
866
+ properties.filter(Boolean)
867
+ );
868
+ this.types.addComment(newDescriptor, "leading", EXTRACT_MARK);
869
+ if (oldLoc) {
870
+ newDescriptor.loc = oldLoc;
871
+ }
872
+ return newDescriptor;
873
+ }
874
+ createObjectProperty(key, value) {
875
+ return this.types.objectProperty(this.types.identifier(key), value);
876
+ }
877
+ getObjectPropertyByKey(objectExp, key) {
878
+ return objectExp.get("properties").find(
879
+ (property) => property.isObjectProperty() && property.get("key").isIdentifier({
880
+ name: key
881
+ })
882
+ );
883
+ }
884
+ /**
885
+ * Custom matchers
886
+ */
887
+ isLinguiIdentifier(path, name) {
888
+ if (path.isIdentifier() && path.referencesImport(MACRO_PACKAGE, name)) {
889
+ return true;
890
+ }
891
+ }
892
+ isDefineMessage(path) {
893
+ return this.isLinguiIdentifier(path, JsMacroName.defineMessage) || this.isLinguiIdentifier(path, JsMacroName.msg);
894
+ }
895
+ isI18nMethod(path) {
896
+ if (!path.isTaggedTemplateExpression()) {
897
+ return;
898
+ }
899
+ const tag = path.get("tag");
900
+ return this.isLinguiIdentifier(tag, JsMacroName.t) || tag.isCallExpression() && this.isLinguiIdentifier(tag.get("callee"), JsMacroName.t);
901
+ }
902
+ isChoiceMethod(path) {
903
+ if (!path.isCallExpression()) {
904
+ return;
905
+ }
906
+ const callee = path.get("callee");
907
+ if (this.isLinguiIdentifier(callee, JsMacroName.plural)) {
908
+ return JsMacroName.plural;
909
+ }
910
+ if (this.isLinguiIdentifier(callee, JsMacroName.select)) {
911
+ return JsMacroName.select;
912
+ }
913
+ if (this.isLinguiIdentifier(callee, JsMacroName.selectOrdinal)) {
914
+ return JsMacroName.selectOrdinal;
915
+ }
916
+ }
917
+ getTextFromExpression(exp) {
918
+ if (this.types.isStringLiteral(exp)) {
919
+ return exp.value;
920
+ }
921
+ if (this.types.isTemplateLiteral(exp)) {
922
+ if (exp?.quasis.length === 1) {
923
+ return exp.quasis[0]?.value?.cooked;
924
+ }
925
+ }
926
+ }
927
+ }
928
+
929
+ let config;
930
+ function getConfig(_config) {
931
+ if (_config) {
932
+ config = _config;
933
+ }
934
+ if (!config) {
935
+ config = conf.getConfig();
936
+ }
937
+ return config;
938
+ }
939
+ function reportUnsupportedSyntax(path, e) {
940
+ throw path.buildCodeFrameError(
941
+ `Unsupported macro usage. Please check the examples at https://lingui.dev/ref/macro#examples-of-js-macros.
942
+ If you think this is a bug, fill in an issue at https://github.com/lingui/js-lingui/issues
943
+
944
+ Error: ${e.message}`
945
+ );
946
+ }
947
+ function linguiPlugin({
948
+ types: t
949
+ }) {
950
+ function addImport(state, name) {
951
+ const path = state.get(
952
+ "macroImport"
953
+ );
954
+ const config2 = state.get("linguiConfig");
955
+ if (!state.get("has_import_" + name)) {
956
+ state.set("has_import_" + name, true);
957
+ const [moduleSource, importName] = config2.runtimeConfigModule[name];
958
+ const [newPath] = path.insertAfter(
959
+ t.importDeclaration(
960
+ [
961
+ t.importSpecifier(
962
+ getSymbolIdentifier(state, name),
963
+ t.identifier(importName)
964
+ )
965
+ ],
966
+ t.stringLiteral(moduleSource)
967
+ )
968
+ );
969
+ path.parentPath.scope.registerDeclaration(newPath);
970
+ }
971
+ return path.parentPath.scope.getBinding(
972
+ getSymbolIdentifier(state, name).name
973
+ );
974
+ }
975
+ function getMacroImports(path) {
976
+ return path.get("body").filter((statement) => {
977
+ return statement.isImportDeclaration() && statement.get("source").node.value === MACRO_PACKAGE;
978
+ });
979
+ }
980
+ function getSymbolIdentifier(state, name) {
981
+ return state.get("linguiIdentifiers")[name];
982
+ }
983
+ return {
984
+ name: "lingui-macro-plugin",
985
+ visitor: {
986
+ Program: {
987
+ enter(path, state) {
988
+ const macroImports = getMacroImports(path);
989
+ if (!macroImports.length) {
990
+ return;
991
+ }
992
+ state.set("macroImport", macroImports[0]);
993
+ state.set(
994
+ "linguiConfig",
995
+ getConfig(state.opts.linguiConfig)
996
+ );
997
+ state.set("linguiIdentifiers", {
998
+ i18n: path.scope.generateUidIdentifier("i18n"),
999
+ Trans: path.scope.generateUidIdentifier("Trans"),
1000
+ useLingui: path.scope.generateUidIdentifier("useLingui")
1001
+ });
1002
+ path.traverse(
1003
+ {
1004
+ JSXElement(path2, state2) {
1005
+ const macro = new MacroJSX(
1006
+ { types: t },
1007
+ {
1008
+ transImportName: getSymbolIdentifier(state2, "Trans").name,
1009
+ stripNonEssentialProps: process.env.NODE_ENV == "production" && !state2.opts.extract
1010
+ }
1011
+ );
1012
+ let newNode;
1013
+ try {
1014
+ newNode = macro.replacePath(path2);
1015
+ } catch (e) {
1016
+ reportUnsupportedSyntax(path2, e);
1017
+ }
1018
+ if (newNode) {
1019
+ const [newPath] = path2.replaceWith(newNode);
1020
+ addImport(state2, "Trans").reference(newPath);
1021
+ }
1022
+ },
1023
+ "CallExpression|TaggedTemplateExpression"(path2, state2) {
1024
+ const macro = new MacroJs(
1025
+ { types: t },
1026
+ {
1027
+ stripNonEssentialProps: process.env.NODE_ENV == "production" && !state2.opts.extract,
1028
+ i18nImportName: getSymbolIdentifier(state2, "i18n").name,
1029
+ useLinguiImportName: getSymbolIdentifier(state2, "useLingui").name
1030
+ }
1031
+ );
1032
+ let newNode;
1033
+ try {
1034
+ newNode = macro.replacePath(path2);
1035
+ } catch (e) {
1036
+ reportUnsupportedSyntax(path2, e);
1037
+ }
1038
+ if (newNode) {
1039
+ const [newPath] = path2.replaceWith(newNode);
1040
+ if (macro.needsUseLinguiImport) {
1041
+ addImport(state2, "useLingui").reference(newPath);
1042
+ }
1043
+ if (macro.needsI18nImport) {
1044
+ addImport(state2, "i18n").reference(newPath);
1045
+ }
1046
+ }
1047
+ }
1048
+ },
1049
+ state
1050
+ );
1051
+ },
1052
+ exit(path, state) {
1053
+ const macroImports = getMacroImports(path);
1054
+ macroImports.forEach((path2) => path2.remove());
1055
+ }
1056
+ }
1057
+ }
1058
+ };
1059
+ }
1060
+
1061
+ exports.JsMacroName = JsMacroName;
1062
+ exports.JsxMacroName = JsxMacroName;
1063
+ exports.linguiPlugin = linguiPlugin;