@lingui/macro 3.15.0 → 3.16.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/build/index.js ADDED
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var _babelPluginMacros = require("babel-plugin-macros");
9
+
10
+ var _conf = require("@lingui/conf");
11
+
12
+ var _macroJs = _interopRequireDefault(require("./macroJs"));
13
+
14
+ var _macroJsx = _interopRequireDefault(require("./macroJsx"));
15
+
16
+ var _types = require("@babel/types");
17
+
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+
20
+ const config = (0, _conf.getConfig)({
21
+ configPath: process.env.LINGUI_CONFIG
22
+ });
23
+
24
+ const getSymbolSource = name => {
25
+ if (Array.isArray(config.runtimeConfigModule)) {
26
+ if (name === "i18n") {
27
+ return config.runtimeConfigModule;
28
+ } else {
29
+ return ["@lingui/react", name];
30
+ }
31
+ } else {
32
+ if (config.runtimeConfigModule[name]) {
33
+ return config.runtimeConfigModule[name];
34
+ } else {
35
+ return ["@lingui/react", name];
36
+ }
37
+ }
38
+ };
39
+
40
+ const [i18nImportModule, i18nImportName = "i18n"] = getSymbolSource("i18n");
41
+ const [TransImportModule, TransImportName = "Trans"] = getSymbolSource("Trans");
42
+
43
+ function macro({
44
+ references,
45
+ state,
46
+ babel
47
+ }) {
48
+ const jsxNodes = [];
49
+ const jsNodes = [];
50
+ let needsI18nImport = false;
51
+ Object.keys(references).forEach(tagName => {
52
+ const nodes = references[tagName];
53
+ const macroType = getMacroType(tagName);
54
+
55
+ if (macroType == null) {
56
+ throw nodes[0].buildCodeFrameError(`Unknown macro ${tagName}`);
57
+ }
58
+
59
+ if (macroType === "js") {
60
+ nodes.forEach(node => {
61
+ jsNodes.push(node.parentPath);
62
+ });
63
+ } else {
64
+ nodes.forEach(node => {
65
+ // identifier.openingElement.jsxElement
66
+ jsxNodes.push(node.parentPath.parentPath);
67
+ });
68
+ }
69
+ });
70
+ jsNodes.filter(isRootPath(jsNodes)).forEach(path => {
71
+ if (alreadyVisited(path)) return;
72
+ const macro = new _macroJs.default(babel, {
73
+ i18nImportName
74
+ });
75
+ if (macro.replacePath(path)) needsI18nImport = true;
76
+ });
77
+ jsxNodes.filter(isRootPath(jsxNodes)).forEach(path => {
78
+ if (alreadyVisited(path)) return;
79
+ const macro = new _macroJsx.default(babel);
80
+ macro.replacePath(path);
81
+ });
82
+
83
+ if (needsI18nImport) {
84
+ addImport(babel, state, i18nImportModule, i18nImportName);
85
+ }
86
+
87
+ if (jsxNodes.length) {
88
+ addImport(babel, state, TransImportModule, TransImportName);
89
+ }
90
+
91
+ if (process.env.LINGUI_EXTRACT === "1") {
92
+ return {
93
+ keepImports: true
94
+ };
95
+ }
96
+ }
97
+
98
+ function addImport(babel, state, module, importName) {
99
+ const {
100
+ types: t
101
+ } = babel;
102
+ const linguiImport = state.file.path.node.body.find(importNode => t.isImportDeclaration(importNode) && importNode.source.value === module && // https://github.com/lingui/js-lingui/issues/777
103
+ importNode.importKind !== "type");
104
+ const tIdentifier = t.identifier(importName); // Handle adding the import or altering the existing import
105
+
106
+ if (linguiImport) {
107
+ if (linguiImport.specifiers.findIndex(specifier => (0, _types.isImportSpecifier)(specifier) && (0, _types.isIdentifier)(specifier.imported, {
108
+ name: importName
109
+ })) === -1) {
110
+ linguiImport.specifiers.push(t.importSpecifier(tIdentifier, tIdentifier));
111
+ }
112
+ } else {
113
+ state.file.path.node.body.unshift(t.importDeclaration([t.importSpecifier(tIdentifier, tIdentifier)], t.stringLiteral(module)));
114
+ }
115
+ }
116
+
117
+ function isRootPath(allPath) {
118
+ return node => function traverse(path) {
119
+ if (!path.parentPath) {
120
+ return true;
121
+ } else {
122
+ return !allPath.includes(path.parentPath) && traverse(path.parentPath);
123
+ }
124
+ }(node);
125
+ }
126
+
127
+ const alreadyVisitedCache = new WeakSet();
128
+
129
+ const alreadyVisited = path => {
130
+ if (alreadyVisitedCache.has(path)) {
131
+ return true;
132
+ } else {
133
+ alreadyVisitedCache.add(path);
134
+ return false;
135
+ }
136
+ };
137
+
138
+ function getMacroType(tagName) {
139
+ switch (tagName) {
140
+ case "defineMessage":
141
+ case "arg":
142
+ case "t":
143
+ case "plural":
144
+ case "select":
145
+ case "selectOrdinal":
146
+ return "js";
147
+
148
+ case "Trans":
149
+ case "Plural":
150
+ case "Select":
151
+ case "SelectOrdinal":
152
+ return "jsx";
153
+ }
154
+ }
155
+
156
+ var _default = (0, _babelPluginMacros.createMacro)(macro);
157
+
158
+ exports.default = _default;
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+
8
+ var R = _interopRequireWildcard(require("ramda"));
9
+
10
+ var _types = require("@babel/types");
11
+
12
+ var _icu = _interopRequireDefault(require("./icu"));
13
+
14
+ var _utils = require("./utils");
15
+
16
+ var _constants = require("./constants");
17
+
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
19
+
20
+ function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
21
+
22
+ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
23
+
24
+ const keepSpaceRe = /(?:\\(?:\r\n|\r|\n))+\s+/g;
25
+ const keepNewLineRe = /(?:\r\n|\r|\n)+\s+/g;
26
+
27
+ function normalizeWhitespace(text) {
28
+ return text.replace(keepSpaceRe, " ").replace(keepNewLineRe, "\n").trim();
29
+ }
30
+
31
+ class MacroJs {
32
+ // Babel Types
33
+ // Identifier of i18n object
34
+ // Positional expressions counter (e.g. for placeholders `Hello {0}, today is {1}`)
35
+ _expressionIndex = (0, _utils.makeCounter)();
36
+
37
+ constructor({
38
+ types
39
+ }, {
40
+ i18nImportName
41
+ }) {
42
+ this.types = types;
43
+ this.i18nImportName = i18nImportName;
44
+ }
45
+
46
+ replacePathWithMessage = (path, {
47
+ message,
48
+ values
49
+ }, linguiInstance) => {
50
+ const args = [];
51
+ args.push(isString(message) ? this.types.stringLiteral(message) : message);
52
+
53
+ if (Object.keys(values).length) {
54
+ const valuesObject = Object.keys(values).map(key => this.types.objectProperty(this.types.identifier(key), values[key]));
55
+ args.push(this.types.objectExpression(valuesObject));
56
+ }
57
+
58
+ const newNode = this.types.callExpression(this.types.memberExpression(linguiInstance ?? this.types.identifier(this.i18nImportName), this.types.identifier("_")), args); // preserve line number
59
+
60
+ newNode.loc = path.node.loc;
61
+ path.addComment("leading", _constants.EXTRACT_MARK);
62
+ path.replaceWith(newNode);
63
+ }; // Returns a boolean indicating if the replacement requires i18n import
64
+
65
+ replacePath = path => {
66
+ // reset the expression counter
67
+ this._expressionIndex = (0, _utils.makeCounter)();
68
+
69
+ if (this.isDefineMessage(path.node)) {
70
+ this.replaceDefineMessage(path);
71
+ return true;
72
+ } // t(i18nInstance)`Message` -> i18nInstance._('Message')
73
+
74
+
75
+ if (this.types.isCallExpression(path.node) && this.types.isTaggedTemplateExpression(path.parentPath.node) && this.types.isIdentifier(path.node.arguments[0]) && this.isIdentifier(path.node.callee, "t")) {
76
+ // Use the first argument as i18n instance instead of the default i18n instance
77
+ const i18nInstance = path.node.arguments[0];
78
+ const tokens = this.tokenizeNode(path.parentPath.node);
79
+ const messageFormat = new _icu.default();
80
+ const {
81
+ message: messageRaw,
82
+ values
83
+ } = messageFormat.fromTokens(tokens);
84
+ const message = normalizeWhitespace(messageRaw);
85
+ this.replacePathWithMessage(path.parentPath, {
86
+ message,
87
+ values
88
+ }, i18nInstance);
89
+ return false;
90
+ } // t(i18nInstance)(messageDescriptor) -> i18nInstance._(messageDescriptor)
91
+
92
+
93
+ if (this.types.isCallExpression(path.node) && this.types.isCallExpression(path.parentPath.node) && this.types.isIdentifier(path.node.arguments[0]) && this.isIdentifier(path.node.callee, "t")) {
94
+ const i18nInstance = path.node.arguments[0];
95
+ this.replaceTAsFunction(path.parentPath, i18nInstance);
96
+ return false;
97
+ }
98
+
99
+ if (this.types.isCallExpression(path.node) && this.isIdentifier(path.node.callee, "t")) {
100
+ this.replaceTAsFunction(path);
101
+ return true;
102
+ }
103
+
104
+ const tokens = this.tokenizeNode(path.node);
105
+ const messageFormat = new _icu.default();
106
+ const {
107
+ message: messageRaw,
108
+ values
109
+ } = messageFormat.fromTokens(tokens);
110
+ const message = normalizeWhitespace(messageRaw);
111
+ this.replacePathWithMessage(path, {
112
+ message,
113
+ values
114
+ });
115
+ return true;
116
+ };
117
+ /**
118
+ * macro `defineMessage` is called with MessageDescriptor. The only
119
+ * thing that happens is that any macros used in `message` property
120
+ * are replaced with formatted message.
121
+ *
122
+ * import { defineMessage, plural } from '@lingui/macro';
123
+ * const message = defineMessage({
124
+ * id: "msg.id",
125
+ * comment: "Description",
126
+ * message: plural(value, { one: "book", other: "books" })
127
+ * })
128
+ *
129
+ * ↓ ↓ ↓ ↓ ↓ ↓
130
+ *
131
+ * const message = {
132
+ * id: "msg.id",
133
+ * comment: "Description",
134
+ * message: "{value, plural, one {book} other {books}}"
135
+ * }
136
+ *
137
+ */
138
+
139
+ replaceDefineMessage = path => {
140
+ // reset the expression counter
141
+ this._expressionIndex = (0, _utils.makeCounter)();
142
+ const descriptor = this.processDescriptor(path.node.arguments[0]);
143
+ path.replaceWith(descriptor);
144
+ };
145
+ /**
146
+ * macro `t` is called with MessageDescriptor, after that
147
+ * we create a new node to append it to i18n._
148
+ */
149
+
150
+ replaceTAsFunction = (path, linguiInstance) => {
151
+ const descriptor = this.processDescriptor(path.node.arguments[0]);
152
+ const newNode = this.types.callExpression(this.types.memberExpression(linguiInstance ?? this.types.identifier(this.i18nImportName), this.types.identifier("_")), [descriptor]);
153
+ path.replaceWith(newNode);
154
+ };
155
+ /**
156
+ * `processDescriptor` expand macros inside message descriptor.
157
+ * Message descriptor is used in `defineMessage`.
158
+ *
159
+ * {
160
+ * comment: "Description",
161
+ * message: plural("value", { one: "book", other: "books" })
162
+ * }
163
+ *
164
+ * ↓ ↓ ↓ ↓ ↓ ↓
165
+ *
166
+ * {
167
+ * comment: "Description",
168
+ * id: "{value, plural, one {book} other {books}}"
169
+ * }
170
+ *
171
+ */
172
+
173
+ processDescriptor = descriptor_ => {
174
+ const descriptor = descriptor_;
175
+ this.types.addComment(descriptor, "leading", _constants.EXTRACT_MARK);
176
+ const messageIndex = descriptor.properties.findIndex(property => (0, _types.isObjectProperty)(property) && this.isIdentifier(property.key, _constants.MESSAGE));
177
+
178
+ if (messageIndex === -1) {
179
+ return descriptor;
180
+ } // if there's `message` property, replace macros with formatted message
181
+
182
+
183
+ const node = descriptor.properties[messageIndex]; // Inside message descriptor the `t` macro in `message` prop is optional.
184
+ // Template strings are always processed as if they were wrapped by `t`.
185
+
186
+ const tokens = this.types.isTemplateLiteral(node.value) ? this.tokenizeTemplateLiteral(node.value) : this.tokenizeNode(node.value, true);
187
+ let messageNode = node.value;
188
+
189
+ if (tokens != null) {
190
+ const messageFormat = new _icu.default();
191
+ const {
192
+ message: messageRaw,
193
+ values
194
+ } = messageFormat.fromTokens(tokens);
195
+ const message = normalizeWhitespace(messageRaw);
196
+ messageNode = this.types.stringLiteral(message);
197
+ this.addValues(descriptor.properties, values);
198
+ } // Don't override custom ID
199
+
200
+
201
+ const hasId = descriptor.properties.findIndex(property => (0, _types.isObjectProperty)(property) && this.isIdentifier(property.key, _constants.ID)) !== -1;
202
+ descriptor.properties[messageIndex] = this.types.objectProperty(this.types.identifier(hasId ? _constants.MESSAGE : _constants.ID), messageNode);
203
+ return descriptor;
204
+ };
205
+ addValues = (obj, values) => {
206
+ const valuesObject = Object.keys(values).map(key => this.types.objectProperty(this.types.identifier(key), values[key]));
207
+ if (!valuesObject.length) return;
208
+ obj.push(this.types.objectProperty(this.types.identifier("values"), this.types.objectExpression(valuesObject)));
209
+ };
210
+ tokenizeNode = (node, ignoreExpression = false) => {
211
+ if (this.isI18nMethod(node)) {
212
+ // t
213
+ return this.tokenizeTemplateLiteral(node);
214
+ } else if (this.isChoiceMethod(node)) {
215
+ // plural, select and selectOrdinal
216
+ return [this.tokenizeChoiceComponent(node)]; // } else if (isFormatMethod(node.callee)) {
217
+ // // date, number
218
+ // return transformFormatMethod(node, file, props, root)
219
+ } else if (!ignoreExpression) {
220
+ return this.tokenizeExpression(node);
221
+ }
222
+ };
223
+ /**
224
+ * `node` is a TemplateLiteral. node.quasi contains
225
+ * text chunks and node.expressions contains expressions.
226
+ * Both arrays must be zipped together to get the final list of tokens.
227
+ */
228
+
229
+ tokenizeTemplateLiteral = node => {
230
+ const tokenize = R.pipe(R.evolve({
231
+ quasis: R.map(text => {
232
+ // Don't output tokens without text.
233
+ // if it's an unicode we keep the cooked value because it's the parsed value by babel (without unicode chars)
234
+ // This regex will detect if a string contains unicode chars, when they're we should interpolate them
235
+ // why? because platforms like react native doesn't parse them, just doing a JSON.parse makes them UTF-8 friendly
236
+ 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;
237
+ if (value === "") return null;
238
+ return {
239
+ type: "text",
240
+ value: this.clearBackslashes(value)
241
+ };
242
+ }),
243
+ expressions: R.map(exp => this.types.isCallExpression(exp) ? this.tokenizeNode(exp) : this.tokenizeExpression(exp))
244
+ }), exp => (0, _utils.zip)(exp.quasis, exp.expressions), R.flatten, R.filter(Boolean));
245
+ return tokenize(this.types.isTaggedTemplateExpression(node) ? node.quasi : node);
246
+ };
247
+ tokenizeChoiceComponent = node => {
248
+ const format = node.callee.name.toLowerCase();
249
+ const token = { ...this.tokenizeExpression(node.arguments[0]),
250
+ format,
251
+ options: {
252
+ offset: undefined
253
+ }
254
+ };
255
+ const props = node.arguments[1].properties;
256
+
257
+ for (const attr of props) {
258
+ const {
259
+ key,
260
+ value: attrValue
261
+ } = attr; // name is either:
262
+ // NumericLiteral => convert to `={number}`
263
+ // StringLiteral => key.value
264
+ // Identifier => key.name
265
+
266
+ const name = this.types.isNumericLiteral(key) ? `=${key.value}` : key.name || key.value;
267
+
268
+ if (format !== "select" && name === "offset") {
269
+ token.options.offset = attrValue.value;
270
+ } else {
271
+ let value;
272
+
273
+ if (this.types.isTemplateLiteral(attrValue)) {
274
+ value = this.tokenizeTemplateLiteral(attrValue);
275
+ } else if (this.types.isCallExpression(attrValue)) {
276
+ value = this.tokenizeNode(attrValue);
277
+ } else {
278
+ value = attrValue.value;
279
+ }
280
+
281
+ token.options[name] = value;
282
+ }
283
+ }
284
+
285
+ return token;
286
+ };
287
+ tokenizeExpression = node => {
288
+ if (this.isArg(node) && this.types.isCallExpression(node)) {
289
+ return {
290
+ type: "arg",
291
+ name: node.arguments[0].value,
292
+ value: undefined
293
+ };
294
+ }
295
+
296
+ return {
297
+ type: "arg",
298
+ name: this.expressionToArgument(node),
299
+ value: node
300
+ };
301
+ };
302
+ expressionToArgument = exp => {
303
+ if (this.types.isIdentifier(exp)) {
304
+ return exp.name;
305
+ } else if (this.types.isStringLiteral(exp)) {
306
+ return exp.value;
307
+ } else {
308
+ return String(this._expressionIndex());
309
+ }
310
+ };
311
+ /**
312
+ * We clean '//\` ' to just '`'
313
+ */
314
+
315
+ clearBackslashes(value) {
316
+ // if not we replace the extra scaped literals
317
+ return value.replace(/\\`/g, "`");
318
+ }
319
+ /**
320
+ * Custom matchers
321
+ */
322
+
323
+
324
+ isIdentifier = (node, name) => {
325
+ return this.types.isIdentifier(node, {
326
+ name
327
+ });
328
+ };
329
+ isDefineMessage = node => {
330
+ return this.types.isCallExpression(node) && this.isIdentifier(node.callee, "defineMessage");
331
+ };
332
+ isArg = node => {
333
+ return this.types.isCallExpression(node) && this.isIdentifier(node.callee, "arg");
334
+ };
335
+ isI18nMethod = node => {
336
+ return this.types.isTaggedTemplateExpression(node) && (this.isIdentifier(node.tag, "t") || this.types.isCallExpression(node.tag) && this.isIdentifier(node.tag.callee, "t"));
337
+ };
338
+ isChoiceMethod = node => {
339
+ return this.types.isCallExpression(node) && (this.isIdentifier(node.callee, "plural") || this.isIdentifier(node.callee, "select") || this.isIdentifier(node.callee, "selectOrdinal"));
340
+ };
341
+ }
342
+
343
+ exports.default = MacroJs;
344
+
345
+ const isString = s => typeof s === "string";