@intlayer/babel 7.6.0-canary.1 → 8.0.0-canary.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/dist/cjs/babel-plugin-intlayer-extract.cjs +108 -229
- package/dist/cjs/babel-plugin-intlayer-extract.cjs.map +1 -1
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs +20 -5
- package/dist/cjs/babel-plugin-intlayer-optimize.cjs.map +1 -1
- package/dist/cjs/getOptimizePluginOptions.cjs +7 -2
- package/dist/cjs/getOptimizePluginOptions.cjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-extract.mjs +108 -229
- package/dist/esm/babel-plugin-intlayer-extract.mjs.map +1 -1
- package/dist/esm/babel-plugin-intlayer-optimize.mjs +20 -5
- package/dist/esm/babel-plugin-intlayer-optimize.mjs.map +1 -1
- package/dist/esm/getOptimizePluginOptions.mjs +7 -2
- package/dist/esm/getOptimizePluginOptions.mjs.map +1 -1
- package/dist/types/babel-plugin-intlayer-extract.d.ts +0 -62
- package/dist/types/babel-plugin-intlayer-extract.d.ts.map +1 -1
- package/dist/types/babel-plugin-intlayer-optimize.d.ts +4 -5
- package/dist/types/babel-plugin-intlayer-optimize.d.ts.map +1 -1
- package/dist/types/getOptimizePluginOptions.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -2,74 +2,16 @@ let node_path = require("node:path");
|
|
|
2
2
|
let _intlayer_chokidar = require("@intlayer/chokidar");
|
|
3
3
|
|
|
4
4
|
//#region src/babel-plugin-intlayer-extract.ts
|
|
5
|
-
/**
|
|
6
|
-
* Extract dictionary key from file path
|
|
7
|
-
*/
|
|
8
5
|
const extractDictionaryKeyFromPath = (filePath) => {
|
|
9
6
|
let baseName = (0, node_path.basename)(filePath, (0, node_path.extname)(filePath));
|
|
10
7
|
if (baseName === "index") baseName = (0, node_path.basename)((0, node_path.dirname)(filePath));
|
|
11
8
|
return `comp-${baseName.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase()}`;
|
|
12
9
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* 2. Auto-injects useIntlayer import and hook call
|
|
19
|
-
* 3. Reports extracted content via onExtract callback (for the compiler to write dictionaries)
|
|
20
|
-
* 4. Replaces extractable strings with content references
|
|
21
|
-
*
|
|
22
|
-
* ## Input
|
|
23
|
-
* ```tsx
|
|
24
|
-
* export const MyComponent = () => {
|
|
25
|
-
* return <div>Hello World</div>;
|
|
26
|
-
* };
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* ## Output
|
|
30
|
-
* ```tsx
|
|
31
|
-
* import { useIntlayer } from 'react-intlayer';
|
|
32
|
-
*
|
|
33
|
-
* export const MyComponent = () => {
|
|
34
|
-
* const content = useIntlayer('comp-my-component');
|
|
35
|
-
* return <div>{content.helloWorld}</div>;
|
|
36
|
-
* };
|
|
37
|
-
* ```
|
|
38
|
-
*
|
|
39
|
-
* ## When useIntlayer is already present
|
|
40
|
-
*
|
|
41
|
-
* If the component already has a `content` variable from an existing `useIntlayer` call,
|
|
42
|
-
* the plugin will use `_compContent` to avoid naming conflicts:
|
|
43
|
-
*
|
|
44
|
-
* ### Input
|
|
45
|
-
* ```tsx
|
|
46
|
-
* export const Page = () => {
|
|
47
|
-
* const content = useIntlayer('page');
|
|
48
|
-
* return <div>{content.title} - Hello World</div>;
|
|
49
|
-
* };
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* ### Output
|
|
53
|
-
* ```tsx
|
|
54
|
-
* export const Page = () => {
|
|
55
|
-
* const _compContent = useIntlayer('comp-page');
|
|
56
|
-
* const content = useIntlayer('page');
|
|
57
|
-
* return <div>{content.title} - {_compContent.helloWorld}</div>;
|
|
58
|
-
* };
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* The extracted content is reported via the `onExtract` callback, allowing the
|
|
62
|
-
* compiler to write the dictionary to disk separately:
|
|
63
|
-
* ```json
|
|
64
|
-
* // my-component.content.json (written by compiler)
|
|
65
|
-
* {
|
|
66
|
-
* "key": "comp-my-component",
|
|
67
|
-
* "content": {
|
|
68
|
-
* "helloWorld": { "nodeType": "translation", "translation": { "en": "Hello World" } }
|
|
69
|
-
* }
|
|
70
|
-
* }
|
|
71
|
-
* ```
|
|
72
|
-
*/
|
|
10
|
+
const unwrapParentheses = (node, t) => {
|
|
11
|
+
let current = node;
|
|
12
|
+
while (t.isParenthesizedExpression(current)) current = current.expression;
|
|
13
|
+
return current;
|
|
14
|
+
};
|
|
73
15
|
const intlayerExtractBabelPlugin = (babel) => {
|
|
74
16
|
const { types: t } = babel;
|
|
75
17
|
return {
|
|
@@ -77,7 +19,6 @@ const intlayerExtractBabelPlugin = (babel) => {
|
|
|
77
19
|
pre() {
|
|
78
20
|
this._extractedContent = {};
|
|
79
21
|
this._existingKeys = /* @__PURE__ */ new Set();
|
|
80
|
-
this._functionsWithExtractedContent = /* @__PURE__ */ new Set();
|
|
81
22
|
this._isIncluded = true;
|
|
82
23
|
this._hasJSX = false;
|
|
83
24
|
this._hasUseIntlayerImport = false;
|
|
@@ -88,12 +29,7 @@ const intlayerExtractBabelPlugin = (babel) => {
|
|
|
88
29
|
const filename = this.file.opts.filename;
|
|
89
30
|
if (this.opts.filesList && filename) {
|
|
90
31
|
const normalizedFilename = filename.replace(/\\/g, "/");
|
|
91
|
-
|
|
92
|
-
return f.replace(/\\/g, "/") === normalizedFilename;
|
|
93
|
-
})) {
|
|
94
|
-
this._isIncluded = false;
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
32
|
+
this._isIncluded = this.opts.filesList.some((f) => f.replace(/\\/g, "/") === normalizedFilename);
|
|
97
33
|
}
|
|
98
34
|
if (filename) this._dictionaryKey = extractDictionaryKeyFromPath(filename);
|
|
99
35
|
},
|
|
@@ -117,63 +53,6 @@ const intlayerExtractBabelPlugin = (babel) => {
|
|
|
117
53
|
if (!state._isIncluded) return;
|
|
118
54
|
state._hasJSX = true;
|
|
119
55
|
},
|
|
120
|
-
JSXText(path, state) {
|
|
121
|
-
if (!state._isIncluded) return;
|
|
122
|
-
const text = path.node.value;
|
|
123
|
-
if ((state.opts.shouldExtract ?? _intlayer_chokidar.shouldExtract)(text)) {
|
|
124
|
-
const key = (0, _intlayer_chokidar.generateKey)(text, state._existingKeys);
|
|
125
|
-
state._existingKeys.add(key);
|
|
126
|
-
state._extractedContent[key] = text.replace(/\s+/g, " ").trim();
|
|
127
|
-
const funcParent = path.getFunctionParent();
|
|
128
|
-
if (funcParent?.node.start != null) state._functionsWithExtractedContent.add(funcParent.node.start);
|
|
129
|
-
path.replaceWith(t.jsxExpressionContainer(t.memberExpression(t.identifier(state._contentVarName), t.identifier(key), false)));
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
JSXAttribute(path, state) {
|
|
133
|
-
if (!state._isIncluded) return;
|
|
134
|
-
const name = path.node.name;
|
|
135
|
-
if (!t.isJSXIdentifier(name)) return;
|
|
136
|
-
const attrName = name.name;
|
|
137
|
-
if (!_intlayer_chokidar.ATTRIBUTES_TO_EXTRACT.includes(attrName)) return;
|
|
138
|
-
const value = path.node.value;
|
|
139
|
-
let text = null;
|
|
140
|
-
if (t.isStringLiteral(value)) text = value.value;
|
|
141
|
-
else if (t.isJSXExpressionContainer(value) && t.isStringLiteral(value.expression)) text = value.expression.value;
|
|
142
|
-
if (text === null) return;
|
|
143
|
-
if ((state.opts.shouldExtract ?? _intlayer_chokidar.shouldExtract)(text)) {
|
|
144
|
-
const key = (0, _intlayer_chokidar.generateKey)(text, state._existingKeys);
|
|
145
|
-
state._existingKeys.add(key);
|
|
146
|
-
state._extractedContent[key] = text.trim();
|
|
147
|
-
const funcParent = path.getFunctionParent();
|
|
148
|
-
if (funcParent?.node.start != null) state._functionsWithExtractedContent.add(funcParent.node.start);
|
|
149
|
-
path.node.value = t.jsxExpressionContainer(t.memberExpression(t.memberExpression(t.identifier(state._contentVarName), t.identifier(key), false), t.identifier("value"), false));
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
StringLiteral(path, state) {
|
|
153
|
-
if (!state._isIncluded) return;
|
|
154
|
-
if (path.parentPath.isJSXAttribute()) return;
|
|
155
|
-
if (path.parentPath.isImportDeclaration()) return;
|
|
156
|
-
if (path.parentPath.isExportDeclaration()) return;
|
|
157
|
-
if (path.parentPath.isImportSpecifier()) return;
|
|
158
|
-
if (path.parentPath.isObjectProperty() && path.key === "key") return;
|
|
159
|
-
if (path.parentPath.isCallExpression()) {
|
|
160
|
-
const callee = path.parentPath.node.callee;
|
|
161
|
-
if (t.isMemberExpression(callee) && t.isIdentifier(callee.object) && callee.object.name === "console") return;
|
|
162
|
-
if (t.isIdentifier(callee) && callee.name === state._useIntlayerLocalName) return;
|
|
163
|
-
if (t.isIdentifier(callee) && callee.name === state._getIntlayerLocalName) return;
|
|
164
|
-
if (callee.type === "Import") return;
|
|
165
|
-
if (t.isIdentifier(callee) && callee.name === "require") return;
|
|
166
|
-
}
|
|
167
|
-
const text = path.node.value;
|
|
168
|
-
if ((state.opts.shouldExtract ?? _intlayer_chokidar.shouldExtract)(text)) {
|
|
169
|
-
const key = (0, _intlayer_chokidar.generateKey)(text, state._existingKeys);
|
|
170
|
-
state._existingKeys.add(key);
|
|
171
|
-
state._extractedContent[key] = text.trim();
|
|
172
|
-
const funcParent = path.getFunctionParent();
|
|
173
|
-
if (funcParent?.node.start != null) state._functionsWithExtractedContent.add(funcParent.node.start);
|
|
174
|
-
path.replaceWith(t.memberExpression(t.identifier(state._contentVarName), t.identifier(key), false));
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
56
|
Program: {
|
|
178
57
|
enter(programPath, state) {
|
|
179
58
|
if (!state._isIncluded) return;
|
|
@@ -184,125 +63,125 @@ const intlayerExtractBabelPlugin = (babel) => {
|
|
|
184
63
|
state._contentVarName = contentVarUsed ? "_compContent" : "content";
|
|
185
64
|
},
|
|
186
65
|
exit(programPath, state) {
|
|
187
|
-
if (!state._isIncluded) return;
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const defaultLocale = state.opts.defaultLocale;
|
|
192
|
-
const packageName = state.opts.packageName;
|
|
193
|
-
if (state.opts.onExtract && state._dictionaryKey && hasExtractedContent) state.opts.onExtract({
|
|
194
|
-
dictionaryKey: state._dictionaryKey,
|
|
195
|
-
filePath: state.file.opts.filename,
|
|
196
|
-
content: { ...state._extractedContent },
|
|
197
|
-
locale: defaultLocale
|
|
198
|
-
});
|
|
199
|
-
let needsUseIntlayer = false;
|
|
200
|
-
let needsGetIntlayer = false;
|
|
201
|
-
const functionsWithContent = state._functionsWithExtractedContent;
|
|
66
|
+
if (!state._isIncluded || !state._hasJSX) return;
|
|
67
|
+
const extractionTargets = [];
|
|
68
|
+
const functionsToInject = /* @__PURE__ */ new Set();
|
|
69
|
+
const shouldExtract = state.opts.shouldExtract ?? _intlayer_chokidar.shouldExtract;
|
|
202
70
|
programPath.traverse({
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
71
|
+
JSXText(path) {
|
|
72
|
+
const text = path.node.value;
|
|
73
|
+
if (shouldExtract(text)) {
|
|
74
|
+
const key = (0, _intlayer_chokidar.generateKey)(text, state._existingKeys);
|
|
75
|
+
state._existingKeys.add(key);
|
|
76
|
+
state._extractedContent[key] = text.replace(/\s+/g, " ").trim();
|
|
77
|
+
extractionTargets.push({
|
|
78
|
+
path,
|
|
79
|
+
key,
|
|
80
|
+
isAttribute: false
|
|
81
|
+
});
|
|
82
|
+
const func = path.getFunctionParent();
|
|
83
|
+
if (func) functionsToInject.add(func);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
JSXAttribute(path) {
|
|
87
|
+
const attrName = path.node.name;
|
|
88
|
+
if (!t.isJSXIdentifier(attrName)) return;
|
|
89
|
+
const isKey = attrName.name === "key";
|
|
90
|
+
if (!_intlayer_chokidar.ATTRIBUTES_TO_EXTRACT.includes(attrName.name) && !isKey) return;
|
|
91
|
+
const value = path.node.value;
|
|
92
|
+
let text = null;
|
|
93
|
+
if (t.isStringLiteral(value)) text = value.value;
|
|
94
|
+
else if (t.isJSXExpressionContainer(value) && t.isStringLiteral(value.expression)) text = value.expression.value;
|
|
95
|
+
if (text && shouldExtract(text)) {
|
|
96
|
+
const key = (0, _intlayer_chokidar.generateKey)(text, state._existingKeys);
|
|
97
|
+
state._existingKeys.add(key);
|
|
98
|
+
state._extractedContent[key] = text.trim();
|
|
99
|
+
extractionTargets.push({
|
|
100
|
+
path,
|
|
101
|
+
key,
|
|
102
|
+
isAttribute: true
|
|
103
|
+
});
|
|
104
|
+
const func = path.getFunctionParent();
|
|
105
|
+
if (func) functionsToInject.add(func);
|
|
208
106
|
}
|
|
209
107
|
},
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
108
|
+
StringLiteral(path) {
|
|
109
|
+
const parent = path.parentPath;
|
|
110
|
+
if (parent.isJSXAttribute() || parent.isImportDeclaration() || parent.isExportDeclaration() || parent.isImportSpecifier()) return;
|
|
111
|
+
if (parent.isObjectProperty() && path.key === "key") return;
|
|
112
|
+
if (parent.isCallExpression()) {
|
|
113
|
+
const callee = parent.node.callee;
|
|
114
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.object) && callee.object.name === "console" || t.isIdentifier(callee) && (callee.name === state._useIntlayerLocalName || callee.name === state._getIntlayerLocalName || callee.name === "require") || callee.type === "Import") return;
|
|
115
|
+
}
|
|
116
|
+
const text = path.node.value;
|
|
117
|
+
if (shouldExtract(text)) {
|
|
118
|
+
const key = (0, _intlayer_chokidar.generateKey)(text, state._existingKeys);
|
|
119
|
+
state._existingKeys.add(key);
|
|
120
|
+
state._extractedContent[key] = text.trim();
|
|
121
|
+
extractionTargets.push({
|
|
122
|
+
path,
|
|
123
|
+
key,
|
|
124
|
+
isAttribute: false
|
|
125
|
+
});
|
|
126
|
+
const func = path.getFunctionParent();
|
|
127
|
+
if (func) functionsToInject.add(func);
|
|
218
128
|
}
|
|
219
129
|
}
|
|
220
130
|
});
|
|
131
|
+
if (extractionTargets.length === 0) return;
|
|
132
|
+
for (const { path, key, isAttribute } of extractionTargets) if (isAttribute) {
|
|
133
|
+
const member = t.memberExpression(t.identifier(state._contentVarName), t.identifier(key));
|
|
134
|
+
path.node.value = t.jsxExpressionContainer(t.memberExpression(member, t.identifier("value")));
|
|
135
|
+
} else if (path.isJSXText()) path.replaceWith(t.jsxExpressionContainer(t.memberExpression(t.identifier(state._contentVarName), t.identifier(key))));
|
|
136
|
+
else path.replaceWith(t.memberExpression(t.identifier(state._contentVarName), t.identifier(key)));
|
|
137
|
+
if (state.opts.onExtract && state._dictionaryKey) state.opts.onExtract({
|
|
138
|
+
dictionaryKey: state._dictionaryKey,
|
|
139
|
+
filePath: state.file.opts.filename,
|
|
140
|
+
content: { ...state._extractedContent },
|
|
141
|
+
locale: state.opts.defaultLocale
|
|
142
|
+
});
|
|
143
|
+
let needsUseIntlayer = false;
|
|
144
|
+
let needsGetIntlayer = false;
|
|
145
|
+
for (const funcPath of functionsToInject) {
|
|
146
|
+
const type = injectHook(funcPath, state, t);
|
|
147
|
+
if (type === "hook") needsUseIntlayer = true;
|
|
148
|
+
if (type === "core") needsGetIntlayer = true;
|
|
149
|
+
}
|
|
221
150
|
if (needsUseIntlayer || needsGetIntlayer) {
|
|
222
|
-
const
|
|
223
|
-
let
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
if (needsUseIntlayer && !state._hasUseIntlayerImport) {
|
|
233
|
-
const importDeclaration = t.importDeclaration([t.importSpecifier(t.identifier("useIntlayer"), t.identifier("useIntlayer"))], t.stringLiteral(packageName));
|
|
234
|
-
programPath.node.body.splice(importInsertPos, 0, importDeclaration);
|
|
235
|
-
importInsertPos++;
|
|
236
|
-
}
|
|
237
|
-
if (needsGetIntlayer && !state._hasGetIntlayerImport) {
|
|
238
|
-
const importDeclaration = t.importDeclaration([t.importSpecifier(t.identifier("getIntlayer"), t.identifier("getIntlayer"))], t.stringLiteral(packageName));
|
|
239
|
-
programPath.node.body.splice(importInsertPos, 0, importDeclaration);
|
|
240
|
-
}
|
|
151
|
+
const pkg = state.opts.packageName;
|
|
152
|
+
let pos = 0;
|
|
153
|
+
const body = programPath.node.body;
|
|
154
|
+
while (pos < body.length && t.isExpressionStatement(body[pos]) && t.isStringLiteral(body[pos].expression)) pos++;
|
|
155
|
+
if (needsUseIntlayer && !state._hasUseIntlayerImport) body.splice(pos++, 0, t.importDeclaration([t.importSpecifier(t.identifier("useIntlayer"), t.identifier("useIntlayer"))], t.stringLiteral(pkg)));
|
|
156
|
+
if (needsGetIntlayer && !state._hasGetIntlayerImport) body.splice(pos, 0, t.importDeclaration([t.importSpecifier(t.identifier("getIntlayer"), t.identifier("getIntlayer"))], t.stringLiteral(pkg)));
|
|
241
157
|
}
|
|
242
158
|
}
|
|
243
159
|
}
|
|
244
160
|
}
|
|
245
161
|
};
|
|
246
162
|
};
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
* Returns 'hook' if useIntlayer was injected (or needed), 'core' if getIntlayer was injected, or null.
|
|
250
|
-
*/
|
|
251
|
-
const injectHookIntoFunction = (funcPath, state, t) => {
|
|
252
|
-
const body = funcPath.node.body;
|
|
253
|
-
if (!t.isBlockStatement(body)) return null;
|
|
254
|
-
let returnsJSX = false;
|
|
255
|
-
funcPath.traverse({ ReturnStatement(returnPath) {
|
|
256
|
-
const arg = returnPath.node.argument;
|
|
257
|
-
if (t.isJSXElement(arg) || t.isJSXFragment(arg)) returnsJSX = true;
|
|
258
|
-
} });
|
|
259
|
-
const contentVarName = state._contentVarName;
|
|
260
|
-
if (returnsJSX) {
|
|
261
|
-
if (body.body.some((stmt) => t.isVariableDeclaration(stmt) && stmt.declarations.some((decl) => t.isIdentifier(decl.id) && decl.id.name === contentVarName && t.isCallExpression(decl.init) && t.isIdentifier(decl.init.callee) && decl.init.callee.name === state._useIntlayerLocalName))) return "hook";
|
|
262
|
-
const hookCall = t.variableDeclaration("const", [t.variableDeclarator(t.identifier(contentVarName), t.callExpression(t.identifier(state._useIntlayerLocalName), [t.stringLiteral(state._dictionaryKey)]))]);
|
|
263
|
-
body.body.unshift(hookCall);
|
|
264
|
-
return "hook";
|
|
265
|
-
} else {
|
|
266
|
-
if (body.body.some((stmt) => t.isVariableDeclaration(stmt) && stmt.declarations.some((decl) => t.isIdentifier(decl.id) && decl.id.name === contentVarName && t.isCallExpression(decl.init) && t.isIdentifier(decl.init.callee) && decl.init.callee.name === state._getIntlayerLocalName))) return "core";
|
|
267
|
-
const call = t.variableDeclaration("const", [t.variableDeclarator(t.identifier(contentVarName), t.callExpression(t.identifier(state._getIntlayerLocalName), [t.stringLiteral(state._dictionaryKey)]))]);
|
|
268
|
-
body.body.unshift(call);
|
|
269
|
-
return "core";
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
/**
|
|
273
|
-
* Inject useIntlayer hook into an arrow function or function expression
|
|
274
|
-
*/
|
|
275
|
-
const injectHookIntoArrowOrExpression = (varPath, init, state, t) => {
|
|
276
|
-
const body = init.body;
|
|
163
|
+
const injectHook = (path, state, t) => {
|
|
164
|
+
const node = path.node;
|
|
277
165
|
const contentVarName = state._contentVarName;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const returnStmt = t.returnStatement(body);
|
|
287
|
-
init.body = t.blockStatement([call, returnStmt]);
|
|
288
|
-
return "core";
|
|
166
|
+
const dictionaryKey = state._dictionaryKey;
|
|
167
|
+
if (!t.isBlockStatement(node.body)) {
|
|
168
|
+
const unwrapped = unwrapParentheses(node.body, t);
|
|
169
|
+
const isJSX = t.isJSXElement(unwrapped) || t.isJSXFragment(unwrapped);
|
|
170
|
+
const hookName$1 = isJSX ? state._useIntlayerLocalName : state._getIntlayerLocalName;
|
|
171
|
+
const hookCall = t.variableDeclaration("const", [t.variableDeclarator(t.identifier(contentVarName), t.callExpression(t.identifier(hookName$1), [t.stringLiteral(dictionaryKey)]))]);
|
|
172
|
+
node.body = t.blockStatement([hookCall, t.returnStatement(node.body)]);
|
|
173
|
+
return isJSX ? "hook" : "core";
|
|
289
174
|
}
|
|
290
175
|
let returnsJSX = false;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
176
|
+
path.traverse({ ReturnStatement(p) {
|
|
177
|
+
if (p.node.argument) {
|
|
178
|
+
const unwrapped = unwrapParentheses(p.node.argument, t);
|
|
179
|
+
if (t.isJSXElement(unwrapped) || t.isJSXFragment(unwrapped)) returnsJSX = true;
|
|
180
|
+
}
|
|
294
181
|
} });
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
body.body.unshift(hookCall);
|
|
299
|
-
return "hook";
|
|
300
|
-
} else {
|
|
301
|
-
if (body.body.some((stmt) => t.isVariableDeclaration(stmt) && stmt.declarations.some((decl) => t.isIdentifier(decl.id) && decl.id.name === contentVarName && t.isCallExpression(decl.init) && t.isIdentifier(decl.init.callee) && decl.init.callee.name === state._getIntlayerLocalName))) return "core";
|
|
302
|
-
const call = t.variableDeclaration("const", [t.variableDeclarator(t.identifier(contentVarName), t.callExpression(t.identifier(state._getIntlayerLocalName), [t.stringLiteral(state._dictionaryKey)]))]);
|
|
303
|
-
body.body.unshift(call);
|
|
304
|
-
return "core";
|
|
305
|
-
}
|
|
182
|
+
const hookName = returnsJSX ? state._useIntlayerLocalName : state._getIntlayerLocalName;
|
|
183
|
+
if (!node.body.body.some((s) => t.isVariableDeclaration(s) && s.declarations.some((d) => t.isIdentifier(d.id) && d.id.name === contentVarName))) node.body.body.unshift(t.variableDeclaration("const", [t.variableDeclarator(t.identifier(contentVarName), t.callExpression(t.identifier(hookName), [t.stringLiteral(dictionaryKey)]))]));
|
|
184
|
+
return returnsJSX ? "hook" : "core";
|
|
306
185
|
};
|
|
307
186
|
|
|
308
187
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"babel-plugin-intlayer-extract.cjs","names":["defaultShouldExtract","ATTRIBUTES_TO_EXTRACT"],"sources":["../../src/babel-plugin-intlayer-extract.ts"],"sourcesContent":["import { basename, dirname, extname } from 'node:path';\nimport type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n shouldExtract as defaultShouldExtract,\n generateKey,\n} from '@intlayer/chokidar';\n\ntype ExtractedContent = Record<string, string>;\n\n/**\n * Extracted content result from a file transformation\n */\nexport type ExtractResult = {\n /** Dictionary key derived from the file path */\n dictionaryKey: string;\n /** File path that was processed */\n filePath: string;\n /** Extracted content key-value pairs */\n content: ExtractedContent;\n /** Default locale used */\n locale: string;\n};\n\n/**\n * Options for the extraction Babel plugin\n */\nexport type ExtractPluginOptions = {\n /**\n * The default locale for the extracted content\n */\n defaultLocale?: string;\n /**\n * The package to import useIntlayer from\n * @default 'react-intlayer'\n */\n packageName?: string;\n /**\n * Files list to traverse. If provided, only files in this list will be processed.\n */\n filesList?: string[];\n /**\n * Custom function to determine if a string should be extracted\n */\n shouldExtract?: (text: string) => boolean;\n /**\n * Callback function called when content is extracted from a file.\n * This allows the compiler to capture the extracted content and write it to files.\n * The dictionary will be updated: new keys added, unused keys removed.\n */\n onExtract?: (result: ExtractResult) => void;\n};\n\ntype State = PluginPass & {\n opts: ExtractPluginOptions;\n /** Extracted content from this file */\n _extractedContent?: ExtractedContent;\n /** Set of existing keys to avoid duplicates */\n _existingKeys?: Set<string>;\n /** The dictionary key for this file */\n _dictionaryKey?: string;\n /** whether the current file is included in the filesList */\n _isIncluded?: boolean;\n /** Whether this file has JSX (React component) */\n _hasJSX?: boolean;\n /** Whether we already have useIntlayer imported */\n _hasUseIntlayerImport?: boolean;\n /** The local name for useIntlayer (in case it's aliased) */\n _useIntlayerLocalName?: string;\n /** Whether we already have getIntlayer imported */\n _hasGetIntlayerImport?: boolean;\n /** The local name for getIntlayer (in case it's aliased) */\n _getIntlayerLocalName?: string;\n /** The variable name to use for content (content or _compContent if content is already used) */\n _contentVarName?: string;\n /** Set of function start positions that have extracted content (only inject hooks into these) */\n _functionsWithExtractedContent?: Set<number>;\n};\n\n/* ────────────────────────────────────────── helpers ─────────────────────── */\n\n/**\n * Extract dictionary key from file path\n */\nconst extractDictionaryKeyFromPath = (filePath: string): string => {\n const ext = extname(filePath);\n let baseName = basename(filePath, ext);\n\n if (baseName === 'index') {\n baseName = basename(dirname(filePath));\n }\n\n // Convert to kebab-case\n const key = baseName\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_]+/g, '-')\n .toLowerCase();\n\n return `comp-${key}`;\n};\n\n/* ────────────────────────────────────────── plugin ──────────────────────── */\n\n/**\n * Autonomous Babel plugin that extracts content and transforms JSX to use useIntlayer.\n *\n * This plugin:\n * 1. Scans files for extractable text (JSX text, attributes)\n * 2. Auto-injects useIntlayer import and hook call\n * 3. Reports extracted content via onExtract callback (for the compiler to write dictionaries)\n * 4. Replaces extractable strings with content references\n *\n * ## Input\n * ```tsx\n * export const MyComponent = () => {\n * return <div>Hello World</div>;\n * };\n * ```\n *\n * ## Output\n * ```tsx\n * import { useIntlayer } from 'react-intlayer';\n *\n * export const MyComponent = () => {\n * const content = useIntlayer('comp-my-component');\n * return <div>{content.helloWorld}</div>;\n * };\n * ```\n *\n * ## When useIntlayer is already present\n *\n * If the component already has a `content` variable from an existing `useIntlayer` call,\n * the plugin will use `_compContent` to avoid naming conflicts:\n *\n * ### Input\n * ```tsx\n * export const Page = () => {\n * const content = useIntlayer('page');\n * return <div>{content.title} - Hello World</div>;\n * };\n * ```\n *\n * ### Output\n * ```tsx\n * export const Page = () => {\n * const _compContent = useIntlayer('comp-page');\n * const content = useIntlayer('page');\n * return <div>{content.title} - {_compContent.helloWorld}</div>;\n * };\n * ```\n *\n * The extracted content is reported via the `onExtract` callback, allowing the\n * compiler to write the dictionary to disk separately:\n * ```json\n * // my-component.content.json (written by compiler)\n * {\n * \"key\": \"comp-my-component\",\n * \"content\": {\n * \"helloWorld\": { \"nodeType\": \"translation\", \"translation\": { \"en\": \"Hello World\" } }\n * }\n * }\n * ```\n */\nexport const intlayerExtractBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-extract',\n\n pre() {\n this._extractedContent = {};\n this._existingKeys = new Set();\n this._functionsWithExtractedContent = new Set();\n this._isIncluded = true;\n this._hasJSX = false;\n this._hasUseIntlayerImport = false;\n this._useIntlayerLocalName = 'useIntlayer';\n this._hasGetIntlayerImport = false;\n this._getIntlayerLocalName = 'getIntlayer';\n this._contentVarName = 'content'; // Will be updated in Program.enter if 'content' is already used\n\n const filename = this.file.opts.filename;\n\n // If filesList is provided, check if current file is included\n if (this.opts.filesList && filename) {\n // Normalize paths for comparison (handle potential path separator issues)\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n const isIncluded = this.opts.filesList.some((f) => {\n const normalizedF = f.replace(/\\\\/g, '/');\n return normalizedF === normalizedFilename;\n });\n\n if (!isIncluded) {\n this._isIncluded = false;\n return;\n }\n }\n\n // Extract dictionary key from filename\n if (filename) {\n this._dictionaryKey = extractDictionaryKeyFromPath(filename);\n }\n },\n\n visitor: {\n /* Check if useIntlayer is already imported */\n ImportDeclaration(path, state) {\n if (!state._isIncluded) return;\n\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n\n if (importedName === 'useIntlayer') {\n state._hasUseIntlayerImport = true;\n state._useIntlayerLocalName = spec.local.name;\n }\n if (importedName === 'getIntlayer') {\n state._hasGetIntlayerImport = true;\n state._getIntlayerLocalName = spec.local.name;\n }\n }\n },\n\n /* Detect JSX elements to know this is a component file */\n JSXElement(_path, state) {\n if (!state._isIncluded) return;\n state._hasJSX = true;\n },\n\n /* Extract JSX text content */\n JSXText(path, state) {\n if (!state._isIncluded) return;\n\n const text = path.node.value;\n const shouldExtract = state.opts.shouldExtract ?? defaultShouldExtract;\n\n if (shouldExtract(text)) {\n const key = generateKey(text, state._existingKeys!);\n state._existingKeys!.add(key);\n\n // Collect extracted content\n state._extractedContent![key] = text.replace(/\\s+/g, ' ').trim();\n\n // Track which function has extracted content\n const funcParent = path.getFunctionParent();\n if (funcParent?.node.start != null) {\n state._functionsWithExtractedContent!.add(funcParent.node.start);\n }\n\n // Replace with {content.key} or {_compContent.key}\n path.replaceWith(\n t.jsxExpressionContainer(\n t.memberExpression(\n t.identifier(state._contentVarName!),\n t.identifier(key),\n false\n )\n )\n );\n }\n },\n\n /* Extract JSX attributes */\n JSXAttribute(path, state) {\n if (!state._isIncluded) return;\n\n const name = path.node.name;\n\n if (!t.isJSXIdentifier(name)) return;\n\n const attrName = name.name;\n if (!ATTRIBUTES_TO_EXTRACT.includes(attrName)) return;\n\n const value = path.node.value;\n\n // Handle both direct StringLiteral and JSXExpressionContainer with StringLiteral\n // Case 1: attr=\"value\" -> value is StringLiteral\n // Case 2: attr={\"value\"} -> value is JSXExpressionContainer containing StringLiteral\n let text: string | null = null;\n\n if (t.isStringLiteral(value)) {\n text = value.value;\n } else if (\n t.isJSXExpressionContainer(value) &&\n t.isStringLiteral(value.expression)\n ) {\n text = value.expression.value;\n }\n\n if (text === null) return;\n\n const shouldExtract = state.opts.shouldExtract ?? defaultShouldExtract;\n\n if (shouldExtract(text)) {\n const key = generateKey(text, state._existingKeys!);\n state._existingKeys!.add(key);\n\n // Collect extracted content\n state._extractedContent![key] = text.trim();\n\n // Track which function has extracted content\n const funcParent = path.getFunctionParent();\n if (funcParent?.node.start != null) {\n state._functionsWithExtractedContent!.add(funcParent.node.start);\n }\n\n // Replace with {content.key.value} or {_compContent.key.value}\n path.node.value = t.jsxExpressionContainer(\n t.memberExpression(\n t.memberExpression(\n t.identifier(state._contentVarName!),\n t.identifier(key),\n false\n ),\n t.identifier('value'),\n false\n )\n );\n }\n },\n\n /* Extract String Literals in code (variables, props, etc.) */\n StringLiteral(path, state) {\n if (!state._isIncluded) return;\n if (path.parentPath.isJSXAttribute()) return; // Already handled\n if (path.parentPath.isImportDeclaration()) return;\n if (path.parentPath.isExportDeclaration()) return;\n if (path.parentPath.isImportSpecifier()) return;\n // Check if it is a key in an object property\n if (path.parentPath.isObjectProperty() && path.key === 'key') return;\n\n // Check if it is a call expression to console or useIntlayer\n if (path.parentPath.isCallExpression()) {\n const callee = path.parentPath.node.callee;\n\n // Check for console.log/error/etc\n if (\n t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console'\n ) {\n return;\n }\n\n // Check for useIntlayer('key')\n if (\n t.isIdentifier(callee) &&\n callee.name === state._useIntlayerLocalName\n ) {\n return;\n }\n\n // Check for getIntlayer('key')\n if (\n t.isIdentifier(callee) &&\n callee.name === state._getIntlayerLocalName\n ) {\n return;\n }\n\n // Check for dynamic import import()\n if (callee.type === 'Import') return;\n\n // Check for require()\n if (t.isIdentifier(callee) && callee.name === 'require') return;\n }\n\n const text = path.node.value;\n const shouldExtract = state.opts.shouldExtract ?? defaultShouldExtract;\n\n if (shouldExtract(text)) {\n const key = generateKey(text, state._existingKeys!);\n state._existingKeys!.add(key);\n\n // Collect extracted content\n state._extractedContent![key] = text.trim();\n\n // Track which function has extracted content\n const funcParent = path.getFunctionParent();\n if (funcParent?.node.start != null) {\n state._functionsWithExtractedContent!.add(funcParent.node.start);\n }\n\n // Replace with content.key or _compContent.key\n path.replaceWith(\n t.memberExpression(\n t.identifier(state._contentVarName!),\n t.identifier(key),\n false\n )\n );\n }\n },\n\n /* Inject useIntlayer hook at program exit */\n Program: {\n enter(programPath, state) {\n if (!state._isIncluded) return;\n\n // Check if 'content' variable is already used in any function\n // If so, we'll use '_compContent' to avoid conflicts\n let contentVarUsed = false;\n\n programPath.traverse({\n VariableDeclarator(varPath) {\n if (\n t.isIdentifier(varPath.node.id) &&\n varPath.node.id.name === 'content'\n ) {\n contentVarUsed = true;\n }\n },\n });\n\n state._contentVarName = contentVarUsed ? '_compContent' : 'content';\n },\n\n exit(programPath, state) {\n if (!state._isIncluded) return;\n\n const extractedKeys = Object.keys(state._extractedContent!);\n const hasExtractedContent = extractedKeys.length > 0;\n\n // If no content was extracted, skip - don't inject useIntlayer for files with no extractable text\n if (!hasExtractedContent) return;\n\n // Only process JSX files (React components)\n if (!state._hasJSX) return;\n\n const defaultLocale = state.opts.defaultLocale;\n const packageName = state.opts.packageName;\n\n // Call the onExtract callback with extracted content\n // This will update the dictionary, adding new keys and removing unused ones\n if (\n state.opts.onExtract &&\n state._dictionaryKey &&\n hasExtractedContent\n ) {\n state.opts.onExtract({\n dictionaryKey: state._dictionaryKey,\n filePath: state.file.opts.filename!,\n content: { ...state._extractedContent! },\n locale: defaultLocale!,\n });\n }\n\n // Track what we need to inject\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n // Now inject hooks only into functions that have extracted content\n const functionsWithContent = state._functionsWithExtractedContent!;\n\n programPath.traverse({\n // Handle function declarations\n FunctionDeclaration(funcPath) {\n // Only inject if this function has extracted content\n if (\n funcPath.node.start != null &&\n functionsWithContent.has(funcPath.node.start)\n ) {\n const type = injectHookIntoFunction(funcPath, state, t);\n if (type === 'hook') needsUseIntlayer = true;\n if (type === 'core') needsGetIntlayer = true;\n }\n },\n\n // Handle arrow functions and function expressions in variable declarations\n VariableDeclarator(varPath) {\n const init = varPath.node.init;\n if (\n t.isArrowFunctionExpression(init) ||\n t.isFunctionExpression(init)\n ) {\n // Only inject if this function has extracted content\n if (\n init.start != null &&\n functionsWithContent.has(init.start)\n ) {\n const type = injectHookIntoArrowOrExpression(\n varPath as NodePath<BabelTypes.VariableDeclarator>,\n init,\n state,\n t\n );\n if (type === 'hook') needsUseIntlayer = true;\n if (type === 'core') needsGetIntlayer = true;\n }\n }\n },\n });\n\n // Add imports if needed\n if (needsUseIntlayer || needsGetIntlayer) {\n const bodyPaths = programPath.get(\n 'body'\n ) as NodePath<BabelTypes.Statement>[];\n\n // Find the best position for import (after directives but before other imports)\n let importInsertPos = 0;\n for (const stmtPath of bodyPaths) {\n const stmt = stmtPath.node;\n if (\n t.isExpressionStatement(stmt) &&\n t.isStringLiteral(stmt.expression)\n ) {\n importInsertPos += 1;\n continue;\n }\n break;\n }\n\n // Inject useIntlayer import\n if (needsUseIntlayer && !state._hasUseIntlayerImport) {\n const importDeclaration = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier('useIntlayer'),\n t.identifier('useIntlayer')\n ),\n ],\n t.stringLiteral(packageName!)\n );\n programPath.node.body.splice(\n importInsertPos,\n 0,\n importDeclaration\n );\n // adjust position for next import\n importInsertPos++;\n }\n\n // Inject getIntlayer import\n if (needsGetIntlayer && !state._hasGetIntlayerImport) {\n const importDeclaration = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier('getIntlayer'),\n t.identifier('getIntlayer')\n ),\n ],\n t.stringLiteral(packageName!)\n );\n programPath.node.body.splice(\n importInsertPos,\n 0,\n importDeclaration\n );\n }\n }\n },\n },\n },\n };\n};\n\n/**\n * Inject useIntlayer hook into a function declaration\n * Returns 'hook' if useIntlayer was injected (or needed), 'core' if getIntlayer was injected, or null.\n */\nconst injectHookIntoFunction = (\n funcPath: NodePath<BabelTypes.FunctionDeclaration>,\n state: State,\n t: typeof BabelTypes\n): 'hook' | 'core' | null => {\n const body = funcPath.node.body;\n if (!t.isBlockStatement(body)) return null;\n\n // Check if this function returns JSX\n let returnsJSX = false;\n funcPath.traverse({\n ReturnStatement(returnPath) {\n const arg = returnPath.node.argument;\n if (t.isJSXElement(arg) || t.isJSXFragment(arg)) {\n returnsJSX = true;\n }\n },\n });\n\n const contentVarName = state._contentVarName!;\n\n if (returnsJSX) {\n // Inject useIntlayer\n\n // Check if hook with this specific variable name is already injected\n const hasHook = body.body.some(\n (stmt) =>\n t.isVariableDeclaration(stmt) &&\n stmt.declarations.some(\n (decl) =>\n t.isIdentifier(decl.id) &&\n decl.id.name === contentVarName &&\n t.isCallExpression(decl.init) &&\n t.isIdentifier(decl.init.callee) &&\n decl.init.callee.name === state._useIntlayerLocalName\n )\n );\n\n if (hasHook) return 'hook';\n\n // Inject: const content = useIntlayer('dictionary-key');\n const hookCall = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(state._useIntlayerLocalName!), [\n t.stringLiteral(state._dictionaryKey!),\n ])\n ),\n ]);\n\n body.body.unshift(hookCall);\n return 'hook';\n } else {\n // Inject getIntlayer\n\n // Check if getIntlayer call with this variable name is already injected\n const hasCall = body.body.some(\n (stmt) =>\n t.isVariableDeclaration(stmt) &&\n stmt.declarations.some(\n (decl) =>\n t.isIdentifier(decl.id) &&\n decl.id.name === contentVarName &&\n t.isCallExpression(decl.init) &&\n t.isIdentifier(decl.init.callee) &&\n decl.init.callee.name === state._getIntlayerLocalName\n )\n );\n\n if (hasCall) return 'core';\n\n // Inject: const content = getIntlayer('dictionary-key');\n const call = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(state._getIntlayerLocalName!), [\n t.stringLiteral(state._dictionaryKey!),\n ])\n ),\n ]);\n\n body.body.unshift(call);\n return 'core';\n }\n};\n\n/**\n * Inject useIntlayer hook into an arrow function or function expression\n */\nconst injectHookIntoArrowOrExpression = (\n varPath: NodePath<BabelTypes.VariableDeclarator>,\n init: BabelTypes.ArrowFunctionExpression | BabelTypes.FunctionExpression,\n state: State,\n t: typeof BabelTypes\n): 'hook' | 'core' | null => {\n const body = init.body;\n const contentVarName = state._contentVarName!;\n\n // If the body is JSX directly (implicit return), wrap it in a block\n if (t.isJSXElement(body) || t.isJSXFragment(body)) {\n // Transform: () => <div>...</div>\n // To: () => { const content = useIntlayer('key'); return <div>...</div>; }\n const hookCall = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(state._useIntlayerLocalName!), [\n t.stringLiteral(state._dictionaryKey!),\n ])\n ),\n ]);\n\n const returnStmt = t.returnStatement(body);\n init.body = t.blockStatement([hookCall, returnStmt]);\n return 'hook';\n }\n\n if (!t.isBlockStatement(body)) {\n // Transform: () => \"string\"\n // To: () => { const content = getIntlayer('key'); return \"string\"; }\n const call = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(state._getIntlayerLocalName!), [\n t.stringLiteral(state._dictionaryKey!),\n ])\n ),\n ]);\n\n const returnStmt = t.returnStatement(body);\n init.body = t.blockStatement([call, returnStmt]);\n return 'core';\n }\n\n // Check if this function returns JSX\n let returnsJSX = false;\n varPath.traverse({\n ReturnStatement(returnPath) {\n const arg = returnPath.node.argument;\n if (t.isJSXElement(arg) || t.isJSXFragment(arg)) {\n returnsJSX = true;\n }\n },\n });\n\n if (returnsJSX) {\n // Inject useIntlayer\n const hasHook = body.body.some(\n (stmt) =>\n t.isVariableDeclaration(stmt) &&\n stmt.declarations.some(\n (decl) =>\n t.isIdentifier(decl.id) &&\n decl.id.name === contentVarName &&\n t.isCallExpression(decl.init) &&\n t.isIdentifier(decl.init.callee) &&\n decl.init.callee.name === state._useIntlayerLocalName\n )\n );\n\n if (hasHook) return 'hook';\n\n const hookCall = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(state._useIntlayerLocalName!), [\n t.stringLiteral(state._dictionaryKey!),\n ])\n ),\n ]);\n\n body.body.unshift(hookCall);\n return 'hook';\n } else {\n // Inject getIntlayer\n const hasCall = body.body.some(\n (stmt) =>\n t.isVariableDeclaration(stmt) &&\n stmt.declarations.some(\n (decl) =>\n t.isIdentifier(decl.id) &&\n decl.id.name === contentVarName &&\n t.isCallExpression(decl.init) &&\n t.isIdentifier(decl.init.callee) &&\n decl.init.callee.name === state._getIntlayerLocalName\n )\n );\n\n if (hasCall) return 'core';\n\n const call = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(state._getIntlayerLocalName!), [\n t.stringLiteral(state._dictionaryKey!),\n ])\n ),\n ]);\n\n body.body.unshift(call);\n return 'core';\n }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"babel-plugin-intlayer-extract.cjs","names":["defaultShouldExtract","ATTRIBUTES_TO_EXTRACT","hookName"],"sources":["../../src/babel-plugin-intlayer-extract.ts"],"sourcesContent":["import { basename, dirname, extname } from 'node:path';\nimport type { NodePath, PluginObj, PluginPass } from '@babel/core';\nimport type * as BabelTypes from '@babel/types';\nimport {\n ATTRIBUTES_TO_EXTRACT,\n shouldExtract as defaultShouldExtract,\n generateKey,\n} from '@intlayer/chokidar';\n\ntype ExtractedContent = Record<string, string>;\n\n/**\n * Extracted content result from a file transformation\n */\nexport type ExtractResult = {\n /** Dictionary key derived from the file path */\n dictionaryKey: string;\n /** File path that was processed */\n filePath: string;\n /** Extracted content key-value pairs */\n content: ExtractedContent;\n /** Default locale used */\n locale: string;\n};\n\n/**\n * Options for the extraction Babel plugin\n */\nexport type ExtractPluginOptions = {\n /**\n * The default locale for the extracted content\n */\n defaultLocale?: string;\n /**\n * The package to import useIntlayer from\n * @default 'react-intlayer'\n */\n packageName?: string;\n /**\n * Files list to traverse. If provided, only files in this list will be processed.\n */\n filesList?: string[];\n /**\n * Custom function to determine if a string should be extracted\n */\n shouldExtract?: (text: string) => boolean;\n /**\n * Callback function called when content is extracted from a file.\n * This allows the compiler to capture the extracted content and write it to files.\n * The dictionary will be updated: new keys added, unused keys removed.\n */\n onExtract?: (result: ExtractResult) => void;\n};\n\ntype State = PluginPass & {\n opts: ExtractPluginOptions;\n /** Extracted content from this file */\n _extractedContent?: ExtractedContent;\n /** Set of existing keys to avoid duplicates */\n _existingKeys?: Set<string>;\n /** The dictionary key for this file */\n _dictionaryKey?: string;\n /** whether the current file is included in the filesList */\n _isIncluded?: boolean;\n /** Whether this file has JSX (React component) */\n _hasJSX?: boolean;\n /** Whether we already have useIntlayer imported */\n _hasUseIntlayerImport?: boolean;\n /** The local name for useIntlayer (in case it's aliased) */\n _useIntlayerLocalName?: string;\n /** Whether we already have getIntlayer imported */\n _hasGetIntlayerImport?: boolean;\n /** The local name for getIntlayer (in case it's aliased) */\n _getIntlayerLocalName?: string;\n /** The variable name to use for content (content or _compContent if content is already used) */\n _contentVarName?: string;\n};\nconst extractDictionaryKeyFromPath = (filePath: string): string => {\n const ext = extname(filePath);\n let baseName = basename(filePath, ext);\n if (baseName === 'index') baseName = basename(dirname(filePath));\n return `comp-${baseName\n .replace(/([a-z])([A-Z])/g, '$1-$2')\n .replace(/[\\s_]+/g, '-')\n .toLowerCase()}`;\n};\n\nconst unwrapParentheses = (\n node: BabelTypes.Node,\n t: typeof BabelTypes\n): BabelTypes.Node => {\n let current = node;\n while (t.isParenthesizedExpression(current)) {\n current = current.expression;\n }\n return current;\n};\n\nexport const intlayerExtractBabelPlugin = (babel: {\n types: typeof BabelTypes;\n}): PluginObj<State> => {\n const { types: t } = babel;\n\n return {\n name: 'babel-plugin-intlayer-extract',\n\n pre() {\n this._extractedContent = {};\n this._existingKeys = new Set();\n this._isIncluded = true;\n this._hasJSX = false;\n this._hasUseIntlayerImport = false;\n this._useIntlayerLocalName = 'useIntlayer';\n this._hasGetIntlayerImport = false;\n this._getIntlayerLocalName = 'getIntlayer';\n this._contentVarName = 'content';\n\n const filename = this.file.opts.filename;\n if (this.opts.filesList && filename) {\n const normalizedFilename = filename.replace(/\\\\/g, '/');\n this._isIncluded = this.opts.filesList.some(\n (f) => f.replace(/\\\\/g, '/') === normalizedFilename\n );\n }\n if (filename)\n this._dictionaryKey = extractDictionaryKeyFromPath(filename);\n },\n\n visitor: {\n ImportDeclaration(path, state) {\n if (!state._isIncluded) return;\n for (const spec of path.node.specifiers) {\n if (!t.isImportSpecifier(spec)) continue;\n const importedName = t.isIdentifier(spec.imported)\n ? spec.imported.name\n : (spec.imported as BabelTypes.StringLiteral).value;\n if (importedName === 'useIntlayer') {\n state._hasUseIntlayerImport = true;\n state._useIntlayerLocalName = spec.local.name;\n }\n if (importedName === 'getIntlayer') {\n state._hasGetIntlayerImport = true;\n state._getIntlayerLocalName = spec.local.name;\n }\n }\n },\n\n JSXElement(_path, state) {\n if (!state._isIncluded) return;\n state._hasJSX = true;\n },\n\n Program: {\n enter(programPath, state) {\n if (!state._isIncluded) return;\n let contentVarUsed = false;\n programPath.traverse({\n VariableDeclarator(varPath) {\n if (\n t.isIdentifier(varPath.node.id) &&\n varPath.node.id.name === 'content'\n )\n contentVarUsed = true;\n },\n });\n state._contentVarName = contentVarUsed ? '_compContent' : 'content';\n },\n\n exit(programPath, state) {\n if (!state._isIncluded || !state._hasJSX) return;\n\n const extractionTargets: {\n path: NodePath<any>;\n key: string;\n isAttribute: boolean;\n }[] = [];\n const functionsToInject = new Set<NodePath<BabelTypes.Function>>();\n const shouldExtract =\n state.opts.shouldExtract ?? defaultShouldExtract;\n\n // Pass 1: Identification (Read only)\n programPath.traverse({\n JSXText(path) {\n const text = path.node.value;\n if (shouldExtract(text)) {\n const key = generateKey(text, state._existingKeys!);\n state._existingKeys!.add(key);\n state._extractedContent![key] = text\n .replace(/\\s+/g, ' ')\n .trim();\n extractionTargets.push({ path, key, isAttribute: false });\n const func = path.getFunctionParent();\n if (func)\n functionsToInject.add(func as NodePath<BabelTypes.Function>);\n }\n },\n JSXAttribute(path) {\n const attrName = path.node.name;\n if (!t.isJSXIdentifier(attrName)) return;\n const isKey = attrName.name === 'key';\n if (!ATTRIBUTES_TO_EXTRACT.includes(attrName.name) && !isKey)\n return;\n\n const value = path.node.value;\n let text: string | null = null;\n if (t.isStringLiteral(value)) text = value.value;\n else if (\n t.isJSXExpressionContainer(value) &&\n t.isStringLiteral(value.expression)\n )\n text = value.expression.value;\n\n if (text && shouldExtract(text)) {\n const key = generateKey(text, state._existingKeys!);\n state._existingKeys!.add(key);\n state._extractedContent![key] = text.trim();\n extractionTargets.push({ path, key, isAttribute: true });\n const func = path.getFunctionParent();\n if (func)\n functionsToInject.add(func as NodePath<BabelTypes.Function>);\n }\n },\n StringLiteral(path) {\n const parent = path.parentPath;\n if (\n parent.isJSXAttribute() ||\n parent.isImportDeclaration() ||\n parent.isExportDeclaration() ||\n parent.isImportSpecifier()\n )\n return;\n if (parent.isObjectProperty() && path.key === 'key') return;\n if (parent.isCallExpression()) {\n const callee = (parent.node as BabelTypes.CallExpression)\n .callee;\n if (\n (t.isMemberExpression(callee) &&\n t.isIdentifier(callee.object) &&\n callee.object.name === 'console') ||\n (t.isIdentifier(callee) &&\n (callee.name === state._useIntlayerLocalName ||\n callee.name === state._getIntlayerLocalName ||\n callee.name === 'require')) ||\n callee.type === 'Import'\n )\n return;\n }\n\n const text = path.node.value;\n if (shouldExtract(text)) {\n const key = generateKey(text, state._existingKeys!);\n state._existingKeys!.add(key);\n state._extractedContent![key] = text.trim();\n extractionTargets.push({ path, key, isAttribute: false });\n const func = path.getFunctionParent();\n if (func)\n functionsToInject.add(func as NodePath<BabelTypes.Function>);\n }\n },\n });\n\n if (extractionTargets.length === 0) return;\n\n // Pass 2: Extraction (Modification)\n for (const { path, key, isAttribute } of extractionTargets) {\n if (isAttribute) {\n const member = t.memberExpression(\n t.identifier(state._contentVarName!),\n t.identifier(key)\n );\n path.node.value = t.jsxExpressionContainer(\n t.memberExpression(member, t.identifier('value'))\n );\n } else if (path.isJSXText()) {\n path.replaceWith(\n t.jsxExpressionContainer(\n t.memberExpression(\n t.identifier(state._contentVarName!),\n t.identifier(key)\n )\n )\n );\n } else {\n path.replaceWith(\n t.memberExpression(\n t.identifier(state._contentVarName!),\n t.identifier(key)\n )\n );\n }\n }\n\n // Report\n if (state.opts.onExtract && state._dictionaryKey) {\n state.opts.onExtract({\n dictionaryKey: state._dictionaryKey,\n filePath: state.file.opts.filename!,\n content: { ...state._extractedContent! },\n locale: state.opts.defaultLocale!,\n });\n }\n\n // Pass 3: Injection\n let needsUseIntlayer = false;\n let needsGetIntlayer = false;\n\n for (const funcPath of functionsToInject) {\n const type = injectHook(funcPath, state, t);\n if (type === 'hook') needsUseIntlayer = true;\n if (type === 'core') needsGetIntlayer = true;\n }\n\n // Pass 4: Imports\n if (needsUseIntlayer || needsGetIntlayer) {\n const pkg = state.opts.packageName!;\n let pos = 0;\n const body = programPath.node.body;\n while (\n pos < body.length &&\n t.isExpressionStatement(body[pos]) &&\n t.isStringLiteral(\n (body[pos] as BabelTypes.ExpressionStatement).expression\n )\n )\n pos++;\n\n if (needsUseIntlayer && !state._hasUseIntlayerImport) {\n body.splice(\n pos++,\n 0,\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier('useIntlayer'),\n t.identifier('useIntlayer')\n ),\n ],\n t.stringLiteral(pkg)\n )\n );\n }\n if (needsGetIntlayer && !state._hasGetIntlayerImport) {\n body.splice(\n pos,\n 0,\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier('getIntlayer'),\n t.identifier('getIntlayer')\n ),\n ],\n t.stringLiteral(pkg)\n )\n );\n }\n }\n },\n },\n },\n };\n};\n\nconst injectHook = (\n path: NodePath<BabelTypes.Function>,\n state: State,\n t: typeof BabelTypes\n): 'hook' | 'core' => {\n const node = path.node;\n const contentVarName = state._contentVarName!;\n const dictionaryKey = state._dictionaryKey!;\n\n if (!t.isBlockStatement(node.body)) {\n const unwrapped = unwrapParentheses(node.body, t);\n const isJSX = t.isJSXElement(unwrapped) || t.isJSXFragment(unwrapped);\n const hookName = isJSX\n ? state._useIntlayerLocalName!\n : state._getIntlayerLocalName!;\n const hookCall = t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(hookName), [\n t.stringLiteral(dictionaryKey),\n ])\n ),\n ]);\n node.body = t.blockStatement([\n hookCall,\n t.returnStatement(node.body as BabelTypes.Expression),\n ]);\n return isJSX ? 'hook' : 'core';\n }\n\n let returnsJSX = false;\n path.traverse({\n ReturnStatement(p) {\n if (p.node.argument) {\n const unwrapped = unwrapParentheses(p.node.argument, t);\n if (t.isJSXElement(unwrapped) || t.isJSXFragment(unwrapped))\n returnsJSX = true;\n }\n },\n });\n\n const hookName = returnsJSX\n ? state._useIntlayerLocalName!\n : state._getIntlayerLocalName!;\n const hasHook = node.body.body.some(\n (s) =>\n t.isVariableDeclaration(s) &&\n s.declarations.some(\n (d) => t.isIdentifier(d.id) && d.id.name === contentVarName\n )\n );\n\n if (!hasHook) {\n node.body.body.unshift(\n t.variableDeclaration('const', [\n t.variableDeclarator(\n t.identifier(contentVarName),\n t.callExpression(t.identifier(hookName), [\n t.stringLiteral(dictionaryKey),\n ])\n ),\n ])\n );\n }\n\n return returnsJSX ? 'hook' : 'core';\n};\n"],"mappings":"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"}
|
|
@@ -117,7 +117,7 @@ const computeImport = (fromFile, dictionariesDir, dynamicDictionariesDir, fetchD
|
|
|
117
117
|
*
|
|
118
118
|
* Uses live-based dictionary loading for remote dictionaries:
|
|
119
119
|
*
|
|
120
|
-
* **Output if `
|
|
120
|
+
* **Output if `dictionaryModeMap` includes the key with "live" value:**
|
|
121
121
|
* ```ts
|
|
122
122
|
* import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };
|
|
123
123
|
* import _dicHash_fetch from '../../.intlayer/fetch_dictionaries/app.mjs';
|
|
@@ -128,7 +128,7 @@ const computeImport = (fromFile, dictionariesDir, dynamicDictionariesDir, fetchD
|
|
|
128
128
|
* const content2 = getIntlayer(_dicHash);
|
|
129
129
|
* ```
|
|
130
130
|
*
|
|
131
|
-
* > If `
|
|
131
|
+
* > If `dictionaryModeMap` does not include the key with "live" value, the plugin will fallback to the dynamic impor
|
|
132
132
|
*
|
|
133
133
|
* ```ts
|
|
134
134
|
* import _dicHash from '../../.intlayer/dictionaries/app.json' with { type: 'json' };
|
|
@@ -181,6 +181,17 @@ const intlayerOptimizeBabelPlugin = (babel) => {
|
|
|
181
181
|
exit(programPath, state) {
|
|
182
182
|
if (state._isDictEntry) return;
|
|
183
183
|
if (!state._isIncluded) return;
|
|
184
|
+
let fileHasDynamicCall = false;
|
|
185
|
+
programPath.traverse({ CallExpression(path) {
|
|
186
|
+
const callee = path.node.callee;
|
|
187
|
+
if (!t.isIdentifier(callee)) return;
|
|
188
|
+
if (callee.name !== "useIntlayer") return;
|
|
189
|
+
const arg = path.node.arguments[0];
|
|
190
|
+
if (!arg || !t.isStringLiteral(arg)) return;
|
|
191
|
+
const key = arg.value;
|
|
192
|
+
const dictionaryOverrideMode = state.opts.dictionaryModeMap?.[key];
|
|
193
|
+
if (dictionaryOverrideMode === "dynamic" || dictionaryOverrideMode === "live") fileHasDynamicCall = true;
|
|
194
|
+
} });
|
|
184
195
|
programPath.traverse({
|
|
185
196
|
ImportDeclaration(path) {
|
|
186
197
|
const src = path.node.source.value;
|
|
@@ -190,7 +201,7 @@ const intlayerOptimizeBabelPlugin = (babel) => {
|
|
|
190
201
|
if (!t.isImportSpecifier(spec)) continue;
|
|
191
202
|
const importedName = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value;
|
|
192
203
|
const importMode = state.opts.importMode;
|
|
193
|
-
const shouldUseDynamicHelpers = (importMode === "dynamic" || importMode === "live") && PACKAGE_LIST_DYNAMIC.includes(src);
|
|
204
|
+
const shouldUseDynamicHelpers = (importMode === "dynamic" || importMode === "live" || fileHasDynamicCall) && PACKAGE_LIST_DYNAMIC.includes(src);
|
|
194
205
|
if (shouldUseDynamicHelpers) state._useDynamicHelpers = true;
|
|
195
206
|
let helperMap;
|
|
196
207
|
if (shouldUseDynamicHelpers) helperMap = {
|
|
@@ -214,9 +225,13 @@ const intlayerOptimizeBabelPlugin = (babel) => {
|
|
|
214
225
|
const isUseIntlayer = callee.name === "useIntlayer";
|
|
215
226
|
const useDynamicHelpers = Boolean(state._useDynamicHelpers);
|
|
216
227
|
let perCallMode = "static";
|
|
228
|
+
const dictionaryOverrideMode = state.opts.dictionaryModeMap?.[key];
|
|
217
229
|
if (isUseIntlayer && useDynamicHelpers) {
|
|
218
|
-
if (
|
|
219
|
-
else if (importMode === "
|
|
230
|
+
if (dictionaryOverrideMode) perCallMode = dictionaryOverrideMode;
|
|
231
|
+
else if (importMode === "dynamic") perCallMode = "dynamic";
|
|
232
|
+
else if (importMode === "live") perCallMode = "live";
|
|
233
|
+
} else if (isUseIntlayer && !useDynamicHelpers) {
|
|
234
|
+
if (dictionaryOverrideMode === "dynamic" || dictionaryOverrideMode === "live") perCallMode = dictionaryOverrideMode;
|
|
220
235
|
}
|
|
221
236
|
let ident;
|
|
222
237
|
if (perCallMode === "live") {
|