@lingui/macro 4.0.0-next.3 → 4.0.0-next.5
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/dist/index.cjs +885 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.mjs +883 -0
- package/package.json +27 -10
- package/build/LICENSE +0 -21
- package/build/constants.js +0 -16
- package/build/global.d.ts +0 -211
- package/build/icu.js +0 -102
- package/build/index.d.ts +0 -320
- package/build/index.js +0 -135
- package/build/macroJs.js +0 -340
- package/build/macroJsx.js +0 -313
- package/build/utils.js +0 -8
package/build/macroJs.js
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _types = require("@babel/types");
|
|
8
|
-
var _icu = _interopRequireDefault(require("./icu"));
|
|
9
|
-
var _utils = require("./utils");
|
|
10
|
-
var _constants = require("./constants");
|
|
11
|
-
var _api = require("@lingui/cli/api");
|
|
12
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
-
const keepSpaceRe = /(?:\\(?:\r\n|\r|\n))+\s+/g;
|
|
14
|
-
const keepNewLineRe = /(?:\r\n|\r|\n)+\s+/g;
|
|
15
|
-
function normalizeWhitespace(text) {
|
|
16
|
-
return text.replace(keepSpaceRe, " ").replace(keepNewLineRe, "\n").trim();
|
|
17
|
-
}
|
|
18
|
-
function buildICUFromTokens(tokens) {
|
|
19
|
-
const messageFormat = new _icu.default();
|
|
20
|
-
const {
|
|
21
|
-
message,
|
|
22
|
-
values
|
|
23
|
-
} = messageFormat.fromTokens(tokens);
|
|
24
|
-
return {
|
|
25
|
-
message: normalizeWhitespace(message),
|
|
26
|
-
values
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
class MacroJs {
|
|
30
|
-
// Babel Types
|
|
31
|
-
|
|
32
|
-
// Identifier of i18n object
|
|
33
|
-
|
|
34
|
-
// Positional expressions counter (e.g. for placeholders `Hello {0}, today is {1}`)
|
|
35
|
-
_expressionIndex = (0, _utils.makeCounter)();
|
|
36
|
-
constructor({
|
|
37
|
-
types
|
|
38
|
-
}, opts) {
|
|
39
|
-
this.types = types;
|
|
40
|
-
this.i18nImportName = opts.i18nImportName;
|
|
41
|
-
this.stripNonEssentialProps = opts.stripNonEssentialProps;
|
|
42
|
-
this.nameMap = opts.nameMap;
|
|
43
|
-
this.nameMapReversed = Array.from(opts.nameMap.entries()).reduce((map, [key, value]) => map.set(value, key), new Map());
|
|
44
|
-
}
|
|
45
|
-
replacePathWithMessage = (path, tokens, linguiInstance) => {
|
|
46
|
-
const newNode = this.createI18nCall(this.createMessageDescriptorFromTokens(tokens, path.node.loc), linguiInstance);
|
|
47
|
-
path.replaceWith(newNode);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Returns a boolean indicating if the replacement requires i18n import
|
|
51
|
-
replacePath = path => {
|
|
52
|
-
// reset the expression counter
|
|
53
|
-
this._expressionIndex = (0, _utils.makeCounter)();
|
|
54
|
-
|
|
55
|
-
// defineMessage({ message: "Message", context: "My" }) -> {id: <hash + context>, message: "Message"}
|
|
56
|
-
if (this.types.isCallExpression(path.node) && this.isDefineMessage(path.node.callee)) {
|
|
57
|
-
let descriptor = this.processDescriptor(path.node.arguments[0]);
|
|
58
|
-
path.replaceWith(descriptor);
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// defineMessage`Message` -> {id: <hash>, message: "Message"}
|
|
63
|
-
if (this.types.isTaggedTemplateExpression(path.node) && this.isDefineMessage(path.node.tag)) {
|
|
64
|
-
const tokens = this.tokenizeTemplateLiteral(path.node.quasi);
|
|
65
|
-
const descriptor = this.createMessageDescriptorFromTokens(tokens, path.node.loc);
|
|
66
|
-
path.replaceWith(descriptor);
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// t(i18nInstance)`Message` -> i18nInstance._(messageDescriptor)
|
|
71
|
-
if (this.types.isCallExpression(path.node) && this.types.isTaggedTemplateExpression(path.parentPath.node) && this.types.isExpression(path.node.arguments[0]) && this.isLinguiIdentifier(path.node.callee, "t")) {
|
|
72
|
-
// Use the first argument as i18n instance instead of the default i18n instance
|
|
73
|
-
const i18nInstance = path.node.arguments[0];
|
|
74
|
-
const tokens = this.tokenizeNode(path.parentPath.node);
|
|
75
|
-
this.replacePathWithMessage(path.parentPath, tokens, i18nInstance);
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// t(i18nInstance)(messageDescriptor) -> i18nInstance._(messageDescriptor)
|
|
80
|
-
if (this.types.isCallExpression(path.node) && this.types.isCallExpression(path.parentPath.node) && this.types.isExpression(path.node.arguments[0]) && path.parentPath.node.callee === path.node && this.isLinguiIdentifier(path.node.callee, "t")) {
|
|
81
|
-
const i18nInstance = path.node.arguments[0];
|
|
82
|
-
this.replaceTAsFunction(path.parentPath, i18nInstance);
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// t({...})
|
|
87
|
-
if (this.types.isCallExpression(path.node) && this.isLinguiIdentifier(path.node.callee, "t")) {
|
|
88
|
-
this.replaceTAsFunction(path);
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
const tokens = this.tokenizeNode(path.node);
|
|
92
|
-
this.replacePathWithMessage(path, tokens);
|
|
93
|
-
return true;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* macro `t` is called with MessageDescriptor, after that
|
|
98
|
-
* we create a new node to append it to i18n._
|
|
99
|
-
*/
|
|
100
|
-
replaceTAsFunction = (path, linguiInstance) => {
|
|
101
|
-
const descriptor = this.processDescriptor(path.node.arguments[0]);
|
|
102
|
-
path.replaceWith(this.createI18nCall(descriptor, linguiInstance));
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* `processDescriptor` expand macros inside message descriptor.
|
|
107
|
-
* Message descriptor is used in `defineMessage`.
|
|
108
|
-
*
|
|
109
|
-
* {
|
|
110
|
-
* comment: "Description",
|
|
111
|
-
* message: plural("value", { one: "book", other: "books" })
|
|
112
|
-
* }
|
|
113
|
-
*
|
|
114
|
-
* ↓ ↓ ↓ ↓ ↓ ↓
|
|
115
|
-
*
|
|
116
|
-
* {
|
|
117
|
-
* comment: "Description",
|
|
118
|
-
* id: <hash>
|
|
119
|
-
* message: "{value, plural, one {book} other {books}}"
|
|
120
|
-
* }
|
|
121
|
-
*
|
|
122
|
-
*/
|
|
123
|
-
processDescriptor = descriptor_ => {
|
|
124
|
-
const descriptor = descriptor_;
|
|
125
|
-
const messageProperty = this.getObjectPropertyByKey(descriptor, _constants.MESSAGE);
|
|
126
|
-
const idProperty = this.getObjectPropertyByKey(descriptor, _constants.ID);
|
|
127
|
-
const contextProperty = this.getObjectPropertyByKey(descriptor, _constants.CONTEXT);
|
|
128
|
-
const properties = [idProperty];
|
|
129
|
-
if (!this.stripNonEssentialProps) {
|
|
130
|
-
properties.push(contextProperty);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// if there's `message` property, replace macros with formatted message
|
|
134
|
-
if (messageProperty) {
|
|
135
|
-
// Inside message descriptor the `t` macro in `message` prop is optional.
|
|
136
|
-
// Template strings are always processed as if they were wrapped by `t`.
|
|
137
|
-
const tokens = this.types.isTemplateLiteral(messageProperty.value) ? this.tokenizeTemplateLiteral(messageProperty.value) : this.tokenizeNode(messageProperty.value, true);
|
|
138
|
-
let messageNode = messageProperty.value;
|
|
139
|
-
if (tokens) {
|
|
140
|
-
const {
|
|
141
|
-
message,
|
|
142
|
-
values
|
|
143
|
-
} = buildICUFromTokens(tokens);
|
|
144
|
-
messageNode = this.types.stringLiteral(message);
|
|
145
|
-
properties.push(this.createValuesProperty(values));
|
|
146
|
-
}
|
|
147
|
-
if (!this.stripNonEssentialProps) {
|
|
148
|
-
properties.push(this.createObjectProperty(_constants.MESSAGE, messageNode));
|
|
149
|
-
}
|
|
150
|
-
if (!idProperty && this.types.isStringLiteral(messageNode)) {
|
|
151
|
-
const context = contextProperty && this.getTextFromExpression(contextProperty.value);
|
|
152
|
-
properties.push(this.createIdProperty(messageNode.value, context));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (!this.stripNonEssentialProps) {
|
|
156
|
-
properties.push(this.getObjectPropertyByKey(descriptor, _constants.COMMENT));
|
|
157
|
-
}
|
|
158
|
-
return this.createMessageDescriptor(properties, descriptor.loc);
|
|
159
|
-
};
|
|
160
|
-
createIdProperty(message, context) {
|
|
161
|
-
return this.createObjectProperty(_constants.ID, this.types.stringLiteral((0, _api.generateMessageId)(message, context)));
|
|
162
|
-
}
|
|
163
|
-
createValuesProperty(values) {
|
|
164
|
-
const valuesObject = Object.keys(values).map(key => this.types.objectProperty(this.types.identifier(key), values[key]));
|
|
165
|
-
if (!valuesObject.length) return;
|
|
166
|
-
return this.types.objectProperty(this.types.identifier("values"), this.types.objectExpression(valuesObject));
|
|
167
|
-
}
|
|
168
|
-
tokenizeNode(node, ignoreExpression = false) {
|
|
169
|
-
if (this.isI18nMethod(node)) {
|
|
170
|
-
// t
|
|
171
|
-
return this.tokenizeTemplateLiteral(node);
|
|
172
|
-
} else if (this.isChoiceMethod(node)) {
|
|
173
|
-
// plural, select and selectOrdinal
|
|
174
|
-
return [this.tokenizeChoiceComponent(node)];
|
|
175
|
-
// } else if (isFormatMethod(node.callee)) {
|
|
176
|
-
// // date, number
|
|
177
|
-
// return transformFormatMethod(node, file, props, root)
|
|
178
|
-
} else if (!ignoreExpression) {
|
|
179
|
-
return [this.tokenizeExpression(node)];
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* `node` is a TemplateLiteral. node.quasi contains
|
|
185
|
-
* text chunks and node.expressions contains expressions.
|
|
186
|
-
* Both arrays must be zipped together to get the final list of tokens.
|
|
187
|
-
*/
|
|
188
|
-
tokenizeTemplateLiteral(node) {
|
|
189
|
-
const tpl = this.types.isTaggedTemplateExpression(node) ? node.quasi : node;
|
|
190
|
-
const expressions = tpl.expressions;
|
|
191
|
-
return tpl.quasis.flatMap((text, i) => {
|
|
192
|
-
// if it's an unicode we keep the cooked value because it's the parsed value by babel (without unicode chars)
|
|
193
|
-
// This regex will detect if a string contains unicode chars, when they're we should interpolate them
|
|
194
|
-
// why? because platforms like react native doesn't parse them, just doing a JSON.parse makes them UTF-8 friendly
|
|
195
|
-
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;
|
|
196
|
-
let argTokens = [];
|
|
197
|
-
const currExp = expressions[i];
|
|
198
|
-
if (currExp) {
|
|
199
|
-
argTokens = this.types.isCallExpression(currExp) ? this.tokenizeNode(currExp) : [this.tokenizeExpression(currExp)];
|
|
200
|
-
}
|
|
201
|
-
return [...(value ? [{
|
|
202
|
-
type: "text",
|
|
203
|
-
value: this.clearBackslashes(value)
|
|
204
|
-
}] : []), ...argTokens];
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
tokenizeChoiceComponent(node) {
|
|
208
|
-
const name = node.callee.name;
|
|
209
|
-
const format = (this.nameMapReversed.get(name) || name).toLowerCase();
|
|
210
|
-
const token = {
|
|
211
|
-
...this.tokenizeExpression(node.arguments[0]),
|
|
212
|
-
format,
|
|
213
|
-
options: {
|
|
214
|
-
offset: undefined
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
const props = node.arguments[1].properties;
|
|
218
|
-
for (const attr of props) {
|
|
219
|
-
const {
|
|
220
|
-
key,
|
|
221
|
-
value: attrValue
|
|
222
|
-
} = attr;
|
|
223
|
-
|
|
224
|
-
// name is either:
|
|
225
|
-
// NumericLiteral => convert to `={number}`
|
|
226
|
-
// StringLiteral => key.value
|
|
227
|
-
// Identifier => key.name
|
|
228
|
-
const name = this.types.isNumericLiteral(key) ? `=${key.value}` : key.name || key.value;
|
|
229
|
-
if (format !== "select" && name === "offset") {
|
|
230
|
-
token.options.offset = attrValue.value;
|
|
231
|
-
} else {
|
|
232
|
-
let value;
|
|
233
|
-
if (this.types.isTemplateLiteral(attrValue)) {
|
|
234
|
-
value = this.tokenizeTemplateLiteral(attrValue);
|
|
235
|
-
} else if (this.types.isCallExpression(attrValue)) {
|
|
236
|
-
value = this.tokenizeNode(attrValue);
|
|
237
|
-
} else if (this.types.isStringLiteral(attrValue)) {
|
|
238
|
-
value = attrValue.value;
|
|
239
|
-
} else if (this.types.isExpression(attrValue)) {
|
|
240
|
-
value = this.tokenizeExpression(attrValue);
|
|
241
|
-
} else {
|
|
242
|
-
value = attrValue.value;
|
|
243
|
-
}
|
|
244
|
-
token.options[name] = value;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
return token;
|
|
248
|
-
}
|
|
249
|
-
tokenizeExpression(node) {
|
|
250
|
-
if (this.isArg(node) && this.types.isCallExpression(node)) {
|
|
251
|
-
return {
|
|
252
|
-
type: "arg",
|
|
253
|
-
name: node.arguments[0].value,
|
|
254
|
-
value: undefined
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
return {
|
|
258
|
-
type: "arg",
|
|
259
|
-
name: this.expressionToArgument(node),
|
|
260
|
-
value: node
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
expressionToArgument(exp) {
|
|
264
|
-
if (this.types.isIdentifier(exp)) {
|
|
265
|
-
return exp.name;
|
|
266
|
-
} else if (this.types.isStringLiteral(exp)) {
|
|
267
|
-
return exp.value;
|
|
268
|
-
} else {
|
|
269
|
-
return String(this._expressionIndex());
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* We clean '//\` ' to just '`'
|
|
275
|
-
*/
|
|
276
|
-
clearBackslashes(value) {
|
|
277
|
-
// if not we replace the extra scaped literals
|
|
278
|
-
return value.replace(/\\`/g, "`");
|
|
279
|
-
}
|
|
280
|
-
createI18nCall(messageDescriptor, linguiInstance) {
|
|
281
|
-
return this.types.callExpression(this.types.memberExpression(linguiInstance ?? this.types.identifier(this.i18nImportName), this.types.identifier("_")), [messageDescriptor]);
|
|
282
|
-
}
|
|
283
|
-
createMessageDescriptorFromTokens(tokens, oldLoc) {
|
|
284
|
-
const {
|
|
285
|
-
message,
|
|
286
|
-
values
|
|
287
|
-
} = buildICUFromTokens(tokens);
|
|
288
|
-
const properties = [this.createIdProperty(message), !this.stripNonEssentialProps ? this.createObjectProperty(_constants.MESSAGE, this.types.stringLiteral(message)) : null, this.createValuesProperty(values)];
|
|
289
|
-
return this.createMessageDescriptor(properties,
|
|
290
|
-
// preserve line numbers for extractor
|
|
291
|
-
oldLoc);
|
|
292
|
-
}
|
|
293
|
-
createMessageDescriptor(properties, oldLoc) {
|
|
294
|
-
const newDescriptor = this.types.objectExpression(properties.filter(Boolean));
|
|
295
|
-
this.types.addComment(newDescriptor, "leading", _constants.EXTRACT_MARK);
|
|
296
|
-
if (oldLoc) {
|
|
297
|
-
newDescriptor.loc = oldLoc;
|
|
298
|
-
}
|
|
299
|
-
return newDescriptor;
|
|
300
|
-
}
|
|
301
|
-
createObjectProperty(key, value) {
|
|
302
|
-
return this.types.objectProperty(this.types.identifier(key), value);
|
|
303
|
-
}
|
|
304
|
-
getObjectPropertyByKey(objectExp, key) {
|
|
305
|
-
return objectExp.properties.find(property => (0, _types.isObjectProperty)(property) && this.isLinguiIdentifier(property.key, key));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Custom matchers
|
|
310
|
-
*/
|
|
311
|
-
isLinguiIdentifier(node, name) {
|
|
312
|
-
return this.types.isIdentifier(node, {
|
|
313
|
-
name: this.nameMap.get(name) || name
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
isDefineMessage(node) {
|
|
317
|
-
return this.isLinguiIdentifier(node, "defineMessage") || this.isLinguiIdentifier(node, "msg");
|
|
318
|
-
}
|
|
319
|
-
isArg(node) {
|
|
320
|
-
return this.types.isCallExpression(node) && this.isLinguiIdentifier(node.callee, "arg");
|
|
321
|
-
}
|
|
322
|
-
isI18nMethod(node) {
|
|
323
|
-
return this.types.isTaggedTemplateExpression(node) && (this.isLinguiIdentifier(node.tag, "t") || this.types.isCallExpression(node.tag) && this.isLinguiIdentifier(node.tag.callee, "t"));
|
|
324
|
-
}
|
|
325
|
-
isChoiceMethod(node) {
|
|
326
|
-
return this.types.isCallExpression(node) && (this.isLinguiIdentifier(node.callee, "plural") || this.isLinguiIdentifier(node.callee, "select") || this.isLinguiIdentifier(node.callee, "selectOrdinal"));
|
|
327
|
-
}
|
|
328
|
-
getTextFromExpression(exp) {
|
|
329
|
-
if (this.types.isStringLiteral(exp)) {
|
|
330
|
-
return exp.value;
|
|
331
|
-
}
|
|
332
|
-
if (this.types.isTemplateLiteral(exp)) {
|
|
333
|
-
if ((exp === null || exp === void 0 ? void 0 : exp.quasis.length) === 1) {
|
|
334
|
-
var _exp$quasis$, _exp$quasis$$value;
|
|
335
|
-
return (_exp$quasis$ = exp.quasis[0]) === null || _exp$quasis$ === void 0 ? void 0 : (_exp$quasis$$value = _exp$quasis$.value) === null || _exp$quasis$$value === void 0 ? void 0 : _exp$quasis$$value.cooked;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
exports.default = MacroJs;
|
package/build/macroJsx.js
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
exports.normalizeWhitespace = normalizeWhitespace;
|
|
8
|
-
var _icu = _interopRequireDefault(require("./icu"));
|
|
9
|
-
var _utils = require("./utils");
|
|
10
|
-
var _constants = require("./constants");
|
|
11
|
-
var _api = require("@lingui/cli/api");
|
|
12
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
-
const pluralRuleRe = /(_[\d\w]+|zero|one|two|few|many|other)/;
|
|
14
|
-
const jsx2icuExactChoice = value => value.replace(/_(\d+)/, "=$1").replace(/_(\w+)/, "$1");
|
|
15
|
-
// replace whitespace before/after newline with single space
|
|
16
|
-
const keepSpaceRe = /\s*(?:\r\n|\r|\n)+\s*/g;
|
|
17
|
-
// remove whitespace before/after tag or expression
|
|
18
|
-
const stripAroundTagsRe = /(?:([>}])(?:\r\n|\r|\n)+\s*|(?:\r\n|\r|\n)+\s*(?=[<{]))/g;
|
|
19
|
-
function maybeNodeValue(node) {
|
|
20
|
-
if (!node) return null;
|
|
21
|
-
if (node.type === "StringLiteral") return node.value;
|
|
22
|
-
if (node.type === "JSXAttribute") return maybeNodeValue(node.value);
|
|
23
|
-
if (node.type === "JSXExpressionContainer") return maybeNodeValue(node.expression);
|
|
24
|
-
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis[0].value.raw;
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
function normalizeWhitespace(text) {
|
|
28
|
-
return text.replace(stripAroundTagsRe, "$1").replace(keepSpaceRe, " ")
|
|
29
|
-
// keep escaped newlines
|
|
30
|
-
.replace(/\\n/g, "\n").replace(/\\s/g, " ")
|
|
31
|
-
// we remove trailing whitespace inside Plural
|
|
32
|
-
.replace(/(\s+})/gm, "}")
|
|
33
|
-
// we remove leading whitespace inside Plural
|
|
34
|
-
.replace(/({\s+)/gm, "{").trim();
|
|
35
|
-
}
|
|
36
|
-
class MacroJSX {
|
|
37
|
-
expressionIndex = (0, _utils.makeCounter)();
|
|
38
|
-
elementIndex = (0, _utils.makeCounter)();
|
|
39
|
-
constructor({
|
|
40
|
-
types
|
|
41
|
-
}, opts) {
|
|
42
|
-
this.types = types;
|
|
43
|
-
this.stripNonEssentialProps = opts.stripNonEssentialProps;
|
|
44
|
-
this.nameMap = opts.nameMap;
|
|
45
|
-
this.nameMapReversed = Array.from(opts.nameMap.entries()).reduce((map, [key, value]) => map.set(value, key), new Map());
|
|
46
|
-
}
|
|
47
|
-
createStringJsxAttribute = (name, value) => {
|
|
48
|
-
// This handles quoted JSX attributes and html entities.
|
|
49
|
-
return this.types.jsxAttribute(this.types.jsxIdentifier(name), this.types.jsxExpressionContainer(this.types.stringLiteral(value)));
|
|
50
|
-
};
|
|
51
|
-
replacePath = path => {
|
|
52
|
-
const tokens = this.tokenizeNode(path);
|
|
53
|
-
const messageFormat = new _icu.default();
|
|
54
|
-
const {
|
|
55
|
-
message: messageRaw,
|
|
56
|
-
values,
|
|
57
|
-
jsxElements
|
|
58
|
-
} = messageFormat.fromTokens(tokens);
|
|
59
|
-
const message = normalizeWhitespace(messageRaw);
|
|
60
|
-
const {
|
|
61
|
-
attributes,
|
|
62
|
-
id,
|
|
63
|
-
comment,
|
|
64
|
-
context
|
|
65
|
-
} = this.stripMacroAttributes(path);
|
|
66
|
-
if (!id && !message) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (id) {
|
|
70
|
-
attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier(_constants.ID), this.types.stringLiteral(id)));
|
|
71
|
-
} else {
|
|
72
|
-
attributes.push(this.createStringJsxAttribute(_constants.ID, (0, _api.generateMessageId)(message, context)));
|
|
73
|
-
}
|
|
74
|
-
if (!this.stripNonEssentialProps) {
|
|
75
|
-
if (message) {
|
|
76
|
-
attributes.push(this.createStringJsxAttribute(_constants.MESSAGE, message));
|
|
77
|
-
}
|
|
78
|
-
if (comment) {
|
|
79
|
-
attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier(_constants.COMMENT), this.types.stringLiteral(comment)));
|
|
80
|
-
}
|
|
81
|
-
if (context) {
|
|
82
|
-
attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier(_constants.CONTEXT), this.types.stringLiteral(context)));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Parameters for variable substitution
|
|
87
|
-
const valuesObject = Object.keys(values).map(key => this.types.objectProperty(this.types.identifier(key), values[key]));
|
|
88
|
-
if (valuesObject.length) {
|
|
89
|
-
attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier("values"), this.types.jsxExpressionContainer(this.types.objectExpression(valuesObject))));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Inline elements
|
|
93
|
-
if (Object.keys(jsxElements).length) {
|
|
94
|
-
attributes.push(this.types.jsxAttribute(this.types.jsxIdentifier("components"), this.types.jsxExpressionContainer(this.types.objectExpression(Object.keys(jsxElements).map(key => this.types.objectProperty(this.types.identifier(key), jsxElements[key]))))));
|
|
95
|
-
}
|
|
96
|
-
const newNode = this.types.jsxElement(this.types.jsxOpeningElement(this.types.jsxIdentifier("Trans"), attributes, true), null, [], true);
|
|
97
|
-
newNode.loc = path.node.loc;
|
|
98
|
-
path.replaceWith(newNode);
|
|
99
|
-
};
|
|
100
|
-
attrName = (names, exclude = false) => {
|
|
101
|
-
const namesRe = new RegExp("^(" + names.join("|") + ")$");
|
|
102
|
-
return attr => {
|
|
103
|
-
const name = attr.name.name;
|
|
104
|
-
return exclude ? !namesRe.test(name) : namesRe.test(name);
|
|
105
|
-
};
|
|
106
|
-
};
|
|
107
|
-
stripMacroAttributes = path => {
|
|
108
|
-
const {
|
|
109
|
-
attributes
|
|
110
|
-
} = path.node.openingElement;
|
|
111
|
-
const id = attributes.find(this.attrName([_constants.ID]));
|
|
112
|
-
const message = attributes.find(this.attrName([_constants.MESSAGE]));
|
|
113
|
-
const comment = attributes.find(this.attrName([_constants.COMMENT]));
|
|
114
|
-
const context = attributes.find(this.attrName([_constants.CONTEXT]));
|
|
115
|
-
let reserved = [_constants.ID, _constants.MESSAGE, _constants.COMMENT, _constants.CONTEXT];
|
|
116
|
-
if (this.isChoiceComponent(path)) {
|
|
117
|
-
reserved = [...reserved, "_\\w+", "_\\d+", "zero", "one", "two", "few", "many", "other", "value", "offset"];
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
id: maybeNodeValue(id),
|
|
121
|
-
message: maybeNodeValue(message),
|
|
122
|
-
comment: maybeNodeValue(comment),
|
|
123
|
-
context: maybeNodeValue(context),
|
|
124
|
-
attributes: attributes.filter(this.attrName(reserved, true))
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
tokenizeNode = path => {
|
|
128
|
-
if (this.isTransComponent(path)) {
|
|
129
|
-
// t
|
|
130
|
-
return this.tokenizeTrans(path);
|
|
131
|
-
} else if (this.isChoiceComponent(path)) {
|
|
132
|
-
// plural, select and selectOrdinal
|
|
133
|
-
return [this.tokenizeChoiceComponent(path)];
|
|
134
|
-
} else if (path.isJSXElement()) {
|
|
135
|
-
return [this.tokenizeElement(path)];
|
|
136
|
-
} else {
|
|
137
|
-
return [this.tokenizeExpression(path)];
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
tokenizeTrans = path => {
|
|
141
|
-
return path.get("children").flatMap(child => this.tokenizeChildren(child)).filter(Boolean);
|
|
142
|
-
};
|
|
143
|
-
tokenizeChildren = path => {
|
|
144
|
-
if (path.isJSXExpressionContainer()) {
|
|
145
|
-
const exp = path.get("expression");
|
|
146
|
-
if (exp.isStringLiteral()) {
|
|
147
|
-
// Escape forced newlines to keep them in message.
|
|
148
|
-
return [this.tokenizeText(exp.node.value.replace(/\n/g, "\\n"))];
|
|
149
|
-
}
|
|
150
|
-
if (exp.isTemplateLiteral()) {
|
|
151
|
-
return this.tokenizeTemplateLiteral(exp);
|
|
152
|
-
}
|
|
153
|
-
if (exp.isConditionalExpression()) {
|
|
154
|
-
return [this.tokenizeConditionalExpression(exp)];
|
|
155
|
-
}
|
|
156
|
-
if (exp.isJSXElement()) {
|
|
157
|
-
return this.tokenizeNode(exp);
|
|
158
|
-
}
|
|
159
|
-
return [this.tokenizeExpression(exp)];
|
|
160
|
-
} else if (path.isJSXElement()) {
|
|
161
|
-
return this.tokenizeNode(path);
|
|
162
|
-
} else if (path.isJSXSpreadChild()) {
|
|
163
|
-
// just do nothing
|
|
164
|
-
} else if (path.isJSXText()) {
|
|
165
|
-
return [this.tokenizeText(path.node.value)];
|
|
166
|
-
} else {
|
|
167
|
-
// impossible path
|
|
168
|
-
// return this.tokenizeText(node.value)
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
tokenizeTemplateLiteral(exp) {
|
|
172
|
-
const expressions = exp.get("expressions");
|
|
173
|
-
return exp.get("quasis").flatMap(({
|
|
174
|
-
node: text
|
|
175
|
-
}, i) => {
|
|
176
|
-
// if it's an unicode we keep the cooked value because it's the parsed value by babel (without unicode chars)
|
|
177
|
-
// This regex will detect if a string contains unicode chars, when they're we should interpolate them
|
|
178
|
-
// why? because platforms like react native doesn't parse them, just doing a JSON.parse makes them UTF-8 friendly
|
|
179
|
-
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;
|
|
180
|
-
let argTokens = [];
|
|
181
|
-
const currExp = expressions[i];
|
|
182
|
-
if (currExp) {
|
|
183
|
-
argTokens = currExp.isCallExpression() ? this.tokenizeNode(currExp) : [this.tokenizeExpression(currExp)];
|
|
184
|
-
}
|
|
185
|
-
return [...(value ? [this.tokenizeText(this.clearBackslashes(value))] : []), ...argTokens];
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
tokenizeChoiceComponent = path => {
|
|
189
|
-
const element = path.get("openingElement");
|
|
190
|
-
const name = this.getJsxTagName(path.node);
|
|
191
|
-
const format = (this.nameMapReversed.get(name) || name).toLowerCase();
|
|
192
|
-
const props = element.get("attributes").filter(attr => {
|
|
193
|
-
return this.attrName([_constants.ID, _constants.COMMENT, _constants.MESSAGE, _constants.CONTEXT, "key",
|
|
194
|
-
// we remove <Trans /> react props that are not useful for translation
|
|
195
|
-
"render", "component", "components"], true)(attr.node);
|
|
196
|
-
});
|
|
197
|
-
const token = {
|
|
198
|
-
type: "arg",
|
|
199
|
-
format,
|
|
200
|
-
name: null,
|
|
201
|
-
value: undefined,
|
|
202
|
-
options: {
|
|
203
|
-
offset: undefined
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
for (const _attr of props) {
|
|
207
|
-
if (_attr.isJSXSpreadAttribute()) {
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const attr = _attr;
|
|
211
|
-
if (this.types.isJSXNamespacedName(attr.node.name)) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
const name = attr.node.name.name;
|
|
215
|
-
const value = attr.get("value");
|
|
216
|
-
if (name === "value") {
|
|
217
|
-
const exp = value.isLiteral() ? value : value.get("expression");
|
|
218
|
-
token.name = this.expressionToArgument(exp);
|
|
219
|
-
token.value = exp.node;
|
|
220
|
-
} else if (format !== "select" && name === "offset") {
|
|
221
|
-
// offset is static parameter, so it must be either string or number
|
|
222
|
-
token.options.offset = value.isStringLiteral() || value.isNumericLiteral() ? value.node.value : value.get("expression").node.value;
|
|
223
|
-
} else {
|
|
224
|
-
let option;
|
|
225
|
-
if (value.isStringLiteral()) {
|
|
226
|
-
option = value.node.extra.raw.replace(/(["'])(.*)\1/, "$2");
|
|
227
|
-
} else {
|
|
228
|
-
option = this.tokenizeChildren(value);
|
|
229
|
-
}
|
|
230
|
-
if (pluralRuleRe.test(name)) {
|
|
231
|
-
token.options[jsx2icuExactChoice(name)] = option;
|
|
232
|
-
} else {
|
|
233
|
-
token.options[name] = option;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return token;
|
|
238
|
-
};
|
|
239
|
-
tokenizeElement = path => {
|
|
240
|
-
// !!! Important: Calculate element index before traversing children.
|
|
241
|
-
// That way outside elements are numbered before inner elements. (...and it looks pretty).
|
|
242
|
-
const name = this.elementIndex();
|
|
243
|
-
return {
|
|
244
|
-
type: "element",
|
|
245
|
-
name,
|
|
246
|
-
value: {
|
|
247
|
-
...path.node,
|
|
248
|
-
children: [],
|
|
249
|
-
openingElement: {
|
|
250
|
-
...path.node.openingElement,
|
|
251
|
-
selfClosing: true
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
children: this.tokenizeTrans(path)
|
|
255
|
-
};
|
|
256
|
-
};
|
|
257
|
-
tokenizeExpression = path => {
|
|
258
|
-
return {
|
|
259
|
-
type: "arg",
|
|
260
|
-
name: this.expressionToArgument(path),
|
|
261
|
-
value: path.node
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
tokenizeConditionalExpression = exp => {
|
|
265
|
-
exp.traverse({
|
|
266
|
-
JSXElement: el => {
|
|
267
|
-
if (this.isTransComponent(el) || this.isChoiceComponent(el)) {
|
|
268
|
-
this.replacePath(el);
|
|
269
|
-
el.skip();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
return {
|
|
274
|
-
type: "arg",
|
|
275
|
-
name: this.expressionToArgument(exp),
|
|
276
|
-
value: exp.node
|
|
277
|
-
};
|
|
278
|
-
};
|
|
279
|
-
tokenizeText = value => {
|
|
280
|
-
return {
|
|
281
|
-
type: "text",
|
|
282
|
-
value
|
|
283
|
-
};
|
|
284
|
-
};
|
|
285
|
-
expressionToArgument(path) {
|
|
286
|
-
return path.isIdentifier() ? path.node.name : String(this.expressionIndex());
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* We clean '//\` ' to just '`'
|
|
291
|
-
**/
|
|
292
|
-
clearBackslashes(value) {
|
|
293
|
-
// if not we replace the extra scaped literals
|
|
294
|
-
return value.replace(/\\`/g, "`");
|
|
295
|
-
}
|
|
296
|
-
isLinguiComponent = (path, name) => {
|
|
297
|
-
return path.isJSXElement() && this.types.isJSXIdentifier(path.node.openingElement.name, {
|
|
298
|
-
name: this.nameMap.get(name) || name
|
|
299
|
-
});
|
|
300
|
-
};
|
|
301
|
-
isTransComponent = path => {
|
|
302
|
-
return this.isLinguiComponent(path, "Trans");
|
|
303
|
-
};
|
|
304
|
-
isChoiceComponent = path => {
|
|
305
|
-
return this.isLinguiComponent(path, "Plural") || this.isLinguiComponent(path, "Select") || this.isLinguiComponent(path, "SelectOrdinal");
|
|
306
|
-
};
|
|
307
|
-
getJsxTagName = node => {
|
|
308
|
-
if (this.types.isJSXIdentifier(node.openingElement.name)) {
|
|
309
|
-
return node.openingElement.name.name;
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
exports.default = MacroJSX;
|