@better-intl/compiler 0.1.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/babel.cjs +138 -0
- package/dist/babel.cjs.map +1 -0
- package/dist/babel.d.cts +33 -0
- package/dist/babel.d.ts +33 -0
- package/dist/babel.js +136 -0
- package/dist/babel.js.map +1 -0
- package/dist/chunk-RKXQPVVG.js +178 -0
- package/dist/chunk-RKXQPVVG.js.map +1 -0
- package/dist/index.cjs +374 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +82 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +176 -0
- package/dist/index.js.map +1 -0
- package/dist/webpack-loader.cjs +204 -0
- package/dist/webpack-loader.cjs.map +1 -0
- package/dist/webpack-loader.d.cts +14 -0
- package/dist/webpack-loader.d.ts +14 -0
- package/dist/webpack-loader.js +3 -0
- package/dist/webpack-loader.js.map +1 -0
- package/package.json +56 -0
package/dist/babel.cjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
var core = require('@better-intl/core');
|
|
5
|
+
|
|
6
|
+
// src/babel.ts
|
|
7
|
+
var IGNORED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre", "Helmet"]);
|
|
8
|
+
function betterIntlBabelPlugin({
|
|
9
|
+
types
|
|
10
|
+
}) {
|
|
11
|
+
return {
|
|
12
|
+
name: "better-intl",
|
|
13
|
+
visitor: {
|
|
14
|
+
Program: {
|
|
15
|
+
enter(_path, state) {
|
|
16
|
+
state.functionsNeedingHook = /* @__PURE__ */ new Set();
|
|
17
|
+
},
|
|
18
|
+
exit(path, state) {
|
|
19
|
+
if (!state.needsImport) return;
|
|
20
|
+
const tName = state.opts.tFunctionName ?? "__t";
|
|
21
|
+
const importSource = state.opts.runtimeImport ?? "@better-intl/react";
|
|
22
|
+
path.traverse({
|
|
23
|
+
FunctionDeclaration(fnPath) {
|
|
24
|
+
if (!state.functionsNeedingHook?.has(fnPath.node)) return;
|
|
25
|
+
injectHook(types, fnPath, tName);
|
|
26
|
+
},
|
|
27
|
+
FunctionExpression(fnPath) {
|
|
28
|
+
if (!state.functionsNeedingHook?.has(fnPath.node)) return;
|
|
29
|
+
injectHook(types, fnPath, tName);
|
|
30
|
+
},
|
|
31
|
+
ArrowFunctionExpression(fnPath) {
|
|
32
|
+
if (!state.functionsNeedingHook?.has(fnPath.node)) return;
|
|
33
|
+
injectHook(types, fnPath, tName);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const hasImport = path.node.body.some(
|
|
37
|
+
(node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
|
|
38
|
+
(specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "useTranslation"
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
if (!hasImport) {
|
|
42
|
+
const importDecl = types.importDeclaration(
|
|
43
|
+
[
|
|
44
|
+
types.importSpecifier(
|
|
45
|
+
types.identifier("useTranslation"),
|
|
46
|
+
types.identifier("useTranslation")
|
|
47
|
+
)
|
|
48
|
+
],
|
|
49
|
+
types.stringLiteral(importSource)
|
|
50
|
+
);
|
|
51
|
+
path.unshiftContainer("body", importDecl);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
FunctionDeclaration(path, state) {
|
|
56
|
+
if (path.node.id) state.componentName = path.node.id.name;
|
|
57
|
+
},
|
|
58
|
+
VariableDeclarator(path, state) {
|
|
59
|
+
if (types.isIdentifier(path.node.id) && (types.isArrowFunctionExpression(path.node.init) || types.isFunctionExpression(path.node.init))) {
|
|
60
|
+
state.componentName = path.node.id.name;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
JSXText(path$1, state) {
|
|
64
|
+
const text = path$1.node.value.trim();
|
|
65
|
+
if (!text) return;
|
|
66
|
+
const parent = path$1.parentPath;
|
|
67
|
+
if (!parent?.isJSXElement()) return;
|
|
68
|
+
const opening = parent.node.openingElement;
|
|
69
|
+
const elementName = types.isJSXIdentifier(opening.name) ? opening.name.name : "unknown";
|
|
70
|
+
if (IGNORED_ELEMENTS.has(elementName)) return;
|
|
71
|
+
const mode = state.opts.mode ?? "auto";
|
|
72
|
+
if (mode === "explicit") {
|
|
73
|
+
const hasI18n = opening.attributes.some(
|
|
74
|
+
(a) => types.isJSXAttribute(a) && types.isJSXIdentifier(a.name) && a.name.name === "i18n"
|
|
75
|
+
);
|
|
76
|
+
if (elementName !== "T" && !hasI18n) return;
|
|
77
|
+
}
|
|
78
|
+
const absolutePath = state.filename ?? "unknown";
|
|
79
|
+
const filePath = absolutePath !== "unknown" ? path.relative(process.cwd(), absolutePath) : absolutePath;
|
|
80
|
+
const id = core.generateId({
|
|
81
|
+
text,
|
|
82
|
+
filePath,
|
|
83
|
+
context: state.componentName
|
|
84
|
+
});
|
|
85
|
+
const { locale, messages, tFunctionName = "__t" } = state.opts;
|
|
86
|
+
if (locale && messages?.[id]) {
|
|
87
|
+
path$1.replaceWith(types.jsxText(messages[id]));
|
|
88
|
+
path$1.skip();
|
|
89
|
+
} else {
|
|
90
|
+
state.needsImport = true;
|
|
91
|
+
path$1.replaceWith(
|
|
92
|
+
types.jsxExpressionContainer(
|
|
93
|
+
types.callExpression(types.identifier(tFunctionName), [
|
|
94
|
+
types.stringLiteral(id),
|
|
95
|
+
types.identifier("undefined"),
|
|
96
|
+
types.stringLiteral(text)
|
|
97
|
+
])
|
|
98
|
+
)
|
|
99
|
+
);
|
|
100
|
+
const fnPath = path$1.findParent(
|
|
101
|
+
(parent2) => parent2.isFunctionDeclaration() || parent2.isFunctionExpression() || parent2.isArrowFunctionExpression()
|
|
102
|
+
);
|
|
103
|
+
if (fnPath) {
|
|
104
|
+
state.functionsNeedingHook?.add(fnPath.node);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function injectHook(types, path, tName) {
|
|
112
|
+
const hookCall = types.variableDeclaration("const", [
|
|
113
|
+
types.variableDeclarator(
|
|
114
|
+
types.objectPattern([
|
|
115
|
+
types.objectProperty(
|
|
116
|
+
types.identifier("t"),
|
|
117
|
+
types.identifier(tName),
|
|
118
|
+
false,
|
|
119
|
+
false
|
|
120
|
+
)
|
|
121
|
+
]),
|
|
122
|
+
types.callExpression(types.identifier("useTranslation"), [])
|
|
123
|
+
)
|
|
124
|
+
]);
|
|
125
|
+
const body = path.node.body;
|
|
126
|
+
if (types.isBlockStatement(body)) {
|
|
127
|
+
body.body.unshift(hookCall);
|
|
128
|
+
} else {
|
|
129
|
+
path.node.body = types.blockStatement([
|
|
130
|
+
hookCall,
|
|
131
|
+
types.returnStatement(body)
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = betterIntlBabelPlugin;
|
|
137
|
+
//# sourceMappingURL=babel.cjs.map
|
|
138
|
+
//# sourceMappingURL=babel.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/babel.ts"],"names":["path","relative","generateId","parent"],"mappings":"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","file":"babel.cjs","sourcesContent":["/**\n * Babel plugin for better-intl.\n *\n * Usage in babel config:\n * plugins: [[\"@better-intl/compiler/babel\", { mode: \"auto\" }]]\n *\n * Transforms `<h1>Hello world</h1>` into:\n * const { t: __t } = useTranslation();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n */\n\nimport { relative } from \"node:path\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport type * as BabelTypes from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\ninterface BetterIntlPluginOptions {\n locale?: string;\n messages?: Messages;\n tFunctionName?: string;\n runtimeImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface BetterIntlState extends PluginPass {\n opts: BetterIntlPluginOptions;\n componentName?: string;\n needsImport?: boolean;\n functionsNeedingHook?: Set<BabelTypes.Node>;\n}\n\nexport default function betterIntlBabelPlugin({\n types,\n}: {\n types: typeof BabelTypes;\n}): PluginObj<BetterIntlState> {\n return {\n name: \"better-intl\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.functionsNeedingHook = new Set();\n },\n exit(path, state) {\n if (!state.needsImport) return;\n\n const tName = state.opts.tFunctionName ?? \"__t\";\n const importSource = state.opts.runtimeImport ?? \"@better-intl/react\";\n\n // Inject useTranslation hook into functions that need it\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n });\n\n // Add import { useTranslation } from \"@better-intl/react\" (if not already present)\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n const importDecl = types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"useTranslation\"),\n types.identifier(\"useTranslation\"),\n ),\n ],\n types.stringLiteral(importSource),\n );\n path.unshiftContainer(\"body\", importDecl);\n }\n },\n },\n\n FunctionDeclaration(path, state) {\n if (path.node.id) state.componentName = path.node.id.name;\n },\n\n VariableDeclarator(path, state) {\n if (\n types.isIdentifier(path.node.id) &&\n (types.isArrowFunctionExpression(path.node.init) ||\n types.isFunctionExpression(path.node.init))\n ) {\n state.componentName = path.node.id.name;\n }\n },\n\n JSXText(path, state) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const opening = (parent.node as BabelTypes.JSXElement).openingElement;\n const elementName = types.isJSXIdentifier(opening.name)\n ? opening.name.name\n : \"unknown\";\n\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const mode = state.opts.mode ?? \"auto\";\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a: BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute) =>\n types.isJSXAttribute(a) &&\n types.isJSXIdentifier(a.name) &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n // Use relative path for stable IDs\n const absolutePath = state.filename ?? \"unknown\";\n const filePath =\n absolutePath !== \"unknown\"\n ? relative(process.cwd(), absolutePath)\n : absolutePath;\n\n const id = generateId({\n text,\n filePath,\n context: state.componentName,\n });\n\n const { locale, messages, tFunctionName = \"__t\" } = state.opts;\n\n if (locale && messages?.[id]) {\n path.replaceWith(types.jsxText(messages[id]));\n path.skip();\n } else {\n state.needsImport = true;\n path.replaceWith(\n types.jsxExpressionContainer(\n types.callExpression(types.identifier(tFunctionName), [\n types.stringLiteral(id),\n types.identifier(\"undefined\"),\n types.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark enclosing function\n const fnPath = path.findParent(\n (parent) =>\n parent.isFunctionDeclaration() ||\n parent.isFunctionExpression() ||\n parent.isArrowFunctionExpression(),\n );\n if (fnPath) {\n state.functionsNeedingHook?.add(fnPath.node);\n }\n }\n },\n },\n };\n}\n\nfunction injectHook(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const hookCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.objectPattern([\n types.objectProperty(\n types.identifier(\"t\"),\n types.identifier(tName),\n false,\n false,\n ),\n ]),\n types.callExpression(types.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n // Arrow with expression body\n path.node.body = types.blockStatement([\n hookCall,\n types.returnStatement(body),\n ]);\n }\n}\n"]}
|
package/dist/babel.d.cts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PluginObj, PluginPass } from '@babel/core';
|
|
2
|
+
import * as BabelTypes from '@babel/types';
|
|
3
|
+
import { Messages } from '@better-intl/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Babel plugin for better-intl.
|
|
7
|
+
*
|
|
8
|
+
* Usage in babel config:
|
|
9
|
+
* plugins: [["@better-intl/compiler/babel", { mode: "auto" }]]
|
|
10
|
+
*
|
|
11
|
+
* Transforms `<h1>Hello world</h1>` into:
|
|
12
|
+
* const { t: __t } = useTranslation();
|
|
13
|
+
* <h1>{__t("id", undefined, "Hello world")}</h1>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface BetterIntlPluginOptions {
|
|
17
|
+
locale?: string;
|
|
18
|
+
messages?: Messages;
|
|
19
|
+
tFunctionName?: string;
|
|
20
|
+
runtimeImport?: string;
|
|
21
|
+
mode?: "auto" | "explicit";
|
|
22
|
+
}
|
|
23
|
+
interface BetterIntlState extends PluginPass {
|
|
24
|
+
opts: BetterIntlPluginOptions;
|
|
25
|
+
componentName?: string;
|
|
26
|
+
needsImport?: boolean;
|
|
27
|
+
functionsNeedingHook?: Set<BabelTypes.Node>;
|
|
28
|
+
}
|
|
29
|
+
declare function betterIntlBabelPlugin({ types, }: {
|
|
30
|
+
types: typeof BabelTypes;
|
|
31
|
+
}): PluginObj<BetterIntlState>;
|
|
32
|
+
|
|
33
|
+
export { betterIntlBabelPlugin as default };
|
package/dist/babel.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PluginObj, PluginPass } from '@babel/core';
|
|
2
|
+
import * as BabelTypes from '@babel/types';
|
|
3
|
+
import { Messages } from '@better-intl/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Babel plugin for better-intl.
|
|
7
|
+
*
|
|
8
|
+
* Usage in babel config:
|
|
9
|
+
* plugins: [["@better-intl/compiler/babel", { mode: "auto" }]]
|
|
10
|
+
*
|
|
11
|
+
* Transforms `<h1>Hello world</h1>` into:
|
|
12
|
+
* const { t: __t } = useTranslation();
|
|
13
|
+
* <h1>{__t("id", undefined, "Hello world")}</h1>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface BetterIntlPluginOptions {
|
|
17
|
+
locale?: string;
|
|
18
|
+
messages?: Messages;
|
|
19
|
+
tFunctionName?: string;
|
|
20
|
+
runtimeImport?: string;
|
|
21
|
+
mode?: "auto" | "explicit";
|
|
22
|
+
}
|
|
23
|
+
interface BetterIntlState extends PluginPass {
|
|
24
|
+
opts: BetterIntlPluginOptions;
|
|
25
|
+
componentName?: string;
|
|
26
|
+
needsImport?: boolean;
|
|
27
|
+
functionsNeedingHook?: Set<BabelTypes.Node>;
|
|
28
|
+
}
|
|
29
|
+
declare function betterIntlBabelPlugin({ types, }: {
|
|
30
|
+
types: typeof BabelTypes;
|
|
31
|
+
}): PluginObj<BetterIntlState>;
|
|
32
|
+
|
|
33
|
+
export { betterIntlBabelPlugin as default };
|
package/dist/babel.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { relative } from 'path';
|
|
2
|
+
import { generateId } from '@better-intl/core';
|
|
3
|
+
|
|
4
|
+
// src/babel.ts
|
|
5
|
+
var IGNORED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre", "Helmet"]);
|
|
6
|
+
function betterIntlBabelPlugin({
|
|
7
|
+
types
|
|
8
|
+
}) {
|
|
9
|
+
return {
|
|
10
|
+
name: "better-intl",
|
|
11
|
+
visitor: {
|
|
12
|
+
Program: {
|
|
13
|
+
enter(_path, state) {
|
|
14
|
+
state.functionsNeedingHook = /* @__PURE__ */ new Set();
|
|
15
|
+
},
|
|
16
|
+
exit(path, state) {
|
|
17
|
+
if (!state.needsImport) return;
|
|
18
|
+
const tName = state.opts.tFunctionName ?? "__t";
|
|
19
|
+
const importSource = state.opts.runtimeImport ?? "@better-intl/react";
|
|
20
|
+
path.traverse({
|
|
21
|
+
FunctionDeclaration(fnPath) {
|
|
22
|
+
if (!state.functionsNeedingHook?.has(fnPath.node)) return;
|
|
23
|
+
injectHook(types, fnPath, tName);
|
|
24
|
+
},
|
|
25
|
+
FunctionExpression(fnPath) {
|
|
26
|
+
if (!state.functionsNeedingHook?.has(fnPath.node)) return;
|
|
27
|
+
injectHook(types, fnPath, tName);
|
|
28
|
+
},
|
|
29
|
+
ArrowFunctionExpression(fnPath) {
|
|
30
|
+
if (!state.functionsNeedingHook?.has(fnPath.node)) return;
|
|
31
|
+
injectHook(types, fnPath, tName);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
const hasImport = path.node.body.some(
|
|
35
|
+
(node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
|
|
36
|
+
(specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "useTranslation"
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
if (!hasImport) {
|
|
40
|
+
const importDecl = types.importDeclaration(
|
|
41
|
+
[
|
|
42
|
+
types.importSpecifier(
|
|
43
|
+
types.identifier("useTranslation"),
|
|
44
|
+
types.identifier("useTranslation")
|
|
45
|
+
)
|
|
46
|
+
],
|
|
47
|
+
types.stringLiteral(importSource)
|
|
48
|
+
);
|
|
49
|
+
path.unshiftContainer("body", importDecl);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
FunctionDeclaration(path, state) {
|
|
54
|
+
if (path.node.id) state.componentName = path.node.id.name;
|
|
55
|
+
},
|
|
56
|
+
VariableDeclarator(path, state) {
|
|
57
|
+
if (types.isIdentifier(path.node.id) && (types.isArrowFunctionExpression(path.node.init) || types.isFunctionExpression(path.node.init))) {
|
|
58
|
+
state.componentName = path.node.id.name;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
JSXText(path, state) {
|
|
62
|
+
const text = path.node.value.trim();
|
|
63
|
+
if (!text) return;
|
|
64
|
+
const parent = path.parentPath;
|
|
65
|
+
if (!parent?.isJSXElement()) return;
|
|
66
|
+
const opening = parent.node.openingElement;
|
|
67
|
+
const elementName = types.isJSXIdentifier(opening.name) ? opening.name.name : "unknown";
|
|
68
|
+
if (IGNORED_ELEMENTS.has(elementName)) return;
|
|
69
|
+
const mode = state.opts.mode ?? "auto";
|
|
70
|
+
if (mode === "explicit") {
|
|
71
|
+
const hasI18n = opening.attributes.some(
|
|
72
|
+
(a) => types.isJSXAttribute(a) && types.isJSXIdentifier(a.name) && a.name.name === "i18n"
|
|
73
|
+
);
|
|
74
|
+
if (elementName !== "T" && !hasI18n) return;
|
|
75
|
+
}
|
|
76
|
+
const absolutePath = state.filename ?? "unknown";
|
|
77
|
+
const filePath = absolutePath !== "unknown" ? relative(process.cwd(), absolutePath) : absolutePath;
|
|
78
|
+
const id = generateId({
|
|
79
|
+
text,
|
|
80
|
+
filePath,
|
|
81
|
+
context: state.componentName
|
|
82
|
+
});
|
|
83
|
+
const { locale, messages, tFunctionName = "__t" } = state.opts;
|
|
84
|
+
if (locale && messages?.[id]) {
|
|
85
|
+
path.replaceWith(types.jsxText(messages[id]));
|
|
86
|
+
path.skip();
|
|
87
|
+
} else {
|
|
88
|
+
state.needsImport = true;
|
|
89
|
+
path.replaceWith(
|
|
90
|
+
types.jsxExpressionContainer(
|
|
91
|
+
types.callExpression(types.identifier(tFunctionName), [
|
|
92
|
+
types.stringLiteral(id),
|
|
93
|
+
types.identifier("undefined"),
|
|
94
|
+
types.stringLiteral(text)
|
|
95
|
+
])
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
const fnPath = path.findParent(
|
|
99
|
+
(parent2) => parent2.isFunctionDeclaration() || parent2.isFunctionExpression() || parent2.isArrowFunctionExpression()
|
|
100
|
+
);
|
|
101
|
+
if (fnPath) {
|
|
102
|
+
state.functionsNeedingHook?.add(fnPath.node);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function injectHook(types, path, tName) {
|
|
110
|
+
const hookCall = types.variableDeclaration("const", [
|
|
111
|
+
types.variableDeclarator(
|
|
112
|
+
types.objectPattern([
|
|
113
|
+
types.objectProperty(
|
|
114
|
+
types.identifier("t"),
|
|
115
|
+
types.identifier(tName),
|
|
116
|
+
false,
|
|
117
|
+
false
|
|
118
|
+
)
|
|
119
|
+
]),
|
|
120
|
+
types.callExpression(types.identifier("useTranslation"), [])
|
|
121
|
+
)
|
|
122
|
+
]);
|
|
123
|
+
const body = path.node.body;
|
|
124
|
+
if (types.isBlockStatement(body)) {
|
|
125
|
+
body.body.unshift(hookCall);
|
|
126
|
+
} else {
|
|
127
|
+
path.node.body = types.blockStatement([
|
|
128
|
+
hookCall,
|
|
129
|
+
types.returnStatement(body)
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export { betterIntlBabelPlugin as default };
|
|
135
|
+
//# sourceMappingURL=babel.js.map
|
|
136
|
+
//# sourceMappingURL=babel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/babel.ts"],"names":["parent"],"mappings":"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","file":"babel.js","sourcesContent":["/**\n * Babel plugin for better-intl.\n *\n * Usage in babel config:\n * plugins: [[\"@better-intl/compiler/babel\", { mode: \"auto\" }]]\n *\n * Transforms `<h1>Hello world</h1>` into:\n * const { t: __t } = useTranslation();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n */\n\nimport { relative } from \"node:path\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport type * as BabelTypes from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\ninterface BetterIntlPluginOptions {\n locale?: string;\n messages?: Messages;\n tFunctionName?: string;\n runtimeImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface BetterIntlState extends PluginPass {\n opts: BetterIntlPluginOptions;\n componentName?: string;\n needsImport?: boolean;\n functionsNeedingHook?: Set<BabelTypes.Node>;\n}\n\nexport default function betterIntlBabelPlugin({\n types,\n}: {\n types: typeof BabelTypes;\n}): PluginObj<BetterIntlState> {\n return {\n name: \"better-intl\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.functionsNeedingHook = new Set();\n },\n exit(path, state) {\n if (!state.needsImport) return;\n\n const tName = state.opts.tFunctionName ?? \"__t\";\n const importSource = state.opts.runtimeImport ?? \"@better-intl/react\";\n\n // Inject useTranslation hook into functions that need it\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n });\n\n // Add import { useTranslation } from \"@better-intl/react\" (if not already present)\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n const importDecl = types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"useTranslation\"),\n types.identifier(\"useTranslation\"),\n ),\n ],\n types.stringLiteral(importSource),\n );\n path.unshiftContainer(\"body\", importDecl);\n }\n },\n },\n\n FunctionDeclaration(path, state) {\n if (path.node.id) state.componentName = path.node.id.name;\n },\n\n VariableDeclarator(path, state) {\n if (\n types.isIdentifier(path.node.id) &&\n (types.isArrowFunctionExpression(path.node.init) ||\n types.isFunctionExpression(path.node.init))\n ) {\n state.componentName = path.node.id.name;\n }\n },\n\n JSXText(path, state) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const opening = (parent.node as BabelTypes.JSXElement).openingElement;\n const elementName = types.isJSXIdentifier(opening.name)\n ? opening.name.name\n : \"unknown\";\n\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const mode = state.opts.mode ?? \"auto\";\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a: BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute) =>\n types.isJSXAttribute(a) &&\n types.isJSXIdentifier(a.name) &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n // Use relative path for stable IDs\n const absolutePath = state.filename ?? \"unknown\";\n const filePath =\n absolutePath !== \"unknown\"\n ? relative(process.cwd(), absolutePath)\n : absolutePath;\n\n const id = generateId({\n text,\n filePath,\n context: state.componentName,\n });\n\n const { locale, messages, tFunctionName = \"__t\" } = state.opts;\n\n if (locale && messages?.[id]) {\n path.replaceWith(types.jsxText(messages[id]));\n path.skip();\n } else {\n state.needsImport = true;\n path.replaceWith(\n types.jsxExpressionContainer(\n types.callExpression(types.identifier(tFunctionName), [\n types.stringLiteral(id),\n types.identifier(\"undefined\"),\n types.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark enclosing function\n const fnPath = path.findParent(\n (parent) =>\n parent.isFunctionDeclaration() ||\n parent.isFunctionExpression() ||\n parent.isArrowFunctionExpression(),\n );\n if (fnPath) {\n state.functionsNeedingHook?.add(fnPath.node);\n }\n }\n },\n },\n };\n}\n\nfunction injectHook(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const hookCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.objectPattern([\n types.objectProperty(\n types.identifier(\"t\"),\n types.identifier(tName),\n false,\n false,\n ),\n ]),\n types.callExpression(types.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n // Arrow with expression body\n path.node.body = types.blockStatement([\n hookCall,\n types.returnStatement(body),\n ]);\n }\n}\n"]}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { relative } from 'path';
|
|
2
|
+
import _generate from '@babel/generator';
|
|
3
|
+
import { parse } from '@babel/parser';
|
|
4
|
+
import _traverse from '@babel/traverse';
|
|
5
|
+
import * as t from '@babel/types';
|
|
6
|
+
import { generateId } from '@better-intl/core';
|
|
7
|
+
|
|
8
|
+
// src/webpack-loader.ts
|
|
9
|
+
var traverse = typeof _traverse === "function" ? _traverse : _traverse.default;
|
|
10
|
+
var generate = typeof _generate === "function" ? _generate : _generate.default;
|
|
11
|
+
var IGNORED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre", "Helmet"]);
|
|
12
|
+
function transform(source, options) {
|
|
13
|
+
const {
|
|
14
|
+
filePath,
|
|
15
|
+
locale,
|
|
16
|
+
messages,
|
|
17
|
+
tFunctionName = "__t",
|
|
18
|
+
runtimeImport = "@better-intl/react",
|
|
19
|
+
mode = "auto"
|
|
20
|
+
} = options;
|
|
21
|
+
const ast = parse(source, {
|
|
22
|
+
sourceType: "module",
|
|
23
|
+
plugins: ["jsx", "typescript"]
|
|
24
|
+
});
|
|
25
|
+
let hasTranslations = false;
|
|
26
|
+
let componentName;
|
|
27
|
+
let needsImport = false;
|
|
28
|
+
const functionsNeedingHook = /* @__PURE__ */ new Set();
|
|
29
|
+
traverse(ast, {
|
|
30
|
+
FunctionDeclaration(path) {
|
|
31
|
+
if (path.node.id) componentName = path.node.id.name;
|
|
32
|
+
},
|
|
33
|
+
VariableDeclarator(path) {
|
|
34
|
+
if (path.node.id.type === "Identifier" && (path.node.init?.type === "ArrowFunctionExpression" || path.node.init?.type === "FunctionExpression")) {
|
|
35
|
+
componentName = path.node.id.name;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
JSXText(path) {
|
|
39
|
+
const text = path.node.value.trim();
|
|
40
|
+
if (!text) return;
|
|
41
|
+
const parent = path.parentPath;
|
|
42
|
+
if (!parent?.isJSXElement()) return;
|
|
43
|
+
const elementName = getJSXName(parent.node.openingElement);
|
|
44
|
+
if (IGNORED_ELEMENTS.has(elementName)) return;
|
|
45
|
+
const line = path.node.loc?.start.line ?? 0;
|
|
46
|
+
if (hasIgnoreComment(source, line)) return;
|
|
47
|
+
if (mode === "explicit") {
|
|
48
|
+
const hasI18n = parent.node.openingElement.attributes.some(
|
|
49
|
+
(a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "i18n"
|
|
50
|
+
);
|
|
51
|
+
if (elementName !== "T" && !hasI18n) return;
|
|
52
|
+
}
|
|
53
|
+
const id = generateId({ text, filePath, context: componentName });
|
|
54
|
+
hasTranslations = true;
|
|
55
|
+
if (locale && messages?.[id]) {
|
|
56
|
+
const newNode = t.jsxText(messages[id]);
|
|
57
|
+
path.replaceWith(newNode);
|
|
58
|
+
path.skip();
|
|
59
|
+
} else {
|
|
60
|
+
needsImport = true;
|
|
61
|
+
path.replaceWith(
|
|
62
|
+
t.jsxExpressionContainer(
|
|
63
|
+
t.callExpression(t.identifier(tFunctionName), [
|
|
64
|
+
t.stringLiteral(id),
|
|
65
|
+
t.identifier("undefined"),
|
|
66
|
+
t.stringLiteral(text)
|
|
67
|
+
])
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
const fnPath = path.findParent(
|
|
71
|
+
(p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
|
|
72
|
+
);
|
|
73
|
+
if (fnPath) {
|
|
74
|
+
functionsNeedingHook.add(fnPath.node);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (needsImport) {
|
|
80
|
+
let injectHook2 = function(path) {
|
|
81
|
+
if (!functionsNeedingHook.has(path.node)) return;
|
|
82
|
+
const hookCall = t.variableDeclaration("const", [
|
|
83
|
+
t.variableDeclarator(
|
|
84
|
+
t.objectPattern([
|
|
85
|
+
t.objectProperty(
|
|
86
|
+
t.identifier("t"),
|
|
87
|
+
t.identifier(tFunctionName),
|
|
88
|
+
false,
|
|
89
|
+
false
|
|
90
|
+
)
|
|
91
|
+
]),
|
|
92
|
+
t.callExpression(t.identifier("useTranslation"), [])
|
|
93
|
+
)
|
|
94
|
+
]);
|
|
95
|
+
const body = path.node.body;
|
|
96
|
+
if (t.isBlockStatement(body)) {
|
|
97
|
+
body.body.unshift(hookCall);
|
|
98
|
+
} else {
|
|
99
|
+
path.node.body = t.blockStatement([hookCall, t.returnStatement(body)]);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
traverse(ast, {
|
|
103
|
+
FunctionDeclaration(path) {
|
|
104
|
+
injectHook2(path);
|
|
105
|
+
},
|
|
106
|
+
FunctionExpression(path) {
|
|
107
|
+
injectHook2(path);
|
|
108
|
+
},
|
|
109
|
+
ArrowFunctionExpression(path) {
|
|
110
|
+
injectHook2(path);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const hasImport = ast.program.body.some(
|
|
114
|
+
(node) => t.isImportDeclaration(node) && node.source.value === runtimeImport && node.specifiers.some(
|
|
115
|
+
(s) => t.isImportSpecifier(s) && t.isIdentifier(s.imported) && s.imported.name === "useTranslation"
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
if (!hasImport) {
|
|
119
|
+
const importDecl = t.importDeclaration(
|
|
120
|
+
[
|
|
121
|
+
t.importSpecifier(
|
|
122
|
+
t.identifier("useTranslation"),
|
|
123
|
+
t.identifier("useTranslation")
|
|
124
|
+
)
|
|
125
|
+
],
|
|
126
|
+
t.stringLiteral(runtimeImport)
|
|
127
|
+
);
|
|
128
|
+
ast.program.body.unshift(importDecl);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const output = generate(ast, { sourceMaps: true, sourceFileName: filePath });
|
|
132
|
+
return {
|
|
133
|
+
code: output.code,
|
|
134
|
+
map: output.map,
|
|
135
|
+
hasTranslations
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function getJSXName(opening) {
|
|
139
|
+
if (opening.name.type === "JSXIdentifier") return opening.name.name;
|
|
140
|
+
return "unknown";
|
|
141
|
+
}
|
|
142
|
+
function hasIgnoreComment(source, line) {
|
|
143
|
+
const lines = source.split("\n");
|
|
144
|
+
const prevLine = lines[line - 2];
|
|
145
|
+
return prevLine ? prevLine.includes("i18n-ignore") : false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/webpack-loader.ts
|
|
149
|
+
function betterIntlLoader(source) {
|
|
150
|
+
const options = this.getOptions?.() ?? {};
|
|
151
|
+
const absolutePath = this.resourcePath;
|
|
152
|
+
const filePath = relative(this.rootContext || process.cwd(), absolutePath);
|
|
153
|
+
if (absolutePath.includes("node_modules")) return source;
|
|
154
|
+
if (!/\.[jt]sx$/.test(filePath)) return source;
|
|
155
|
+
if (/\.(test|spec|stories)\.[jt]sx?$/.test(filePath)) return source;
|
|
156
|
+
try {
|
|
157
|
+
const result = transform(source, {
|
|
158
|
+
filePath,
|
|
159
|
+
locale: options.locale,
|
|
160
|
+
messages: options.messages,
|
|
161
|
+
mode: options.mode ?? "auto"
|
|
162
|
+
});
|
|
163
|
+
if (result.hasTranslations) {
|
|
164
|
+
console.log(
|
|
165
|
+
`[better-intl] Transformed ${filePath} (${result.hasTranslations})`
|
|
166
|
+
);
|
|
167
|
+
return result.code;
|
|
168
|
+
}
|
|
169
|
+
return source;
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`[better-intl] Error transforming ${filePath}:`, err);
|
|
172
|
+
return source;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { betterIntlLoader, transform };
|
|
177
|
+
//# sourceMappingURL=chunk-RKXQPVVG.js.map
|
|
178
|
+
//# sourceMappingURL=chunk-RKXQPVVG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transformer.ts","../src/webpack-loader.ts"],"names":["injectHook"],"mappings":"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","file":"chunk-RKXQPVVG.js","sourcesContent":["/**\n * Babel AST transformer.\n *\n * Replaces static JSX text with `__t(\"id\")` calls,\n * or with static text when the locale is known at build time.\n *\n * For runtime mode, injects `const { t: __t } = useTranslation();`\n * at the top of each component function that has translatable text.\n */\n\nimport _generate from \"@babel/generator\";\nimport { parse } from \"@babel/parser\";\nimport type { NodePath } from \"@babel/traverse\";\nimport _traverse from \"@babel/traverse\";\nimport * as t from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst traverse =\n typeof _traverse === \"function\"\n ? _traverse\n : (_traverse as { default: typeof _traverse }).default;\n\nconst generate =\n typeof _generate === \"function\"\n ? _generate\n : (_generate as { default: typeof _generate }).default;\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\nexport interface TransformOptions {\n filePath: string;\n /** When set, inline the translated text directly (zero-runtime). */\n locale?: string;\n messages?: Messages;\n /** The identifier for the `t` function (default: `__t`) */\n tFunctionName?: string;\n /** Import path for the runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\nexport interface TransformResult {\n code: string;\n map: ReturnType<typeof generate>[\"map\"];\n hasTranslations: boolean;\n}\n\nexport function transform(\n source: string,\n options: TransformOptions,\n): TransformResult {\n const {\n filePath,\n locale,\n messages,\n tFunctionName = \"__t\",\n runtimeImport = \"@better-intl/react\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n let hasTranslations = false;\n let componentName: string | undefined;\n let needsImport = false;\n\n // Track which function bodies need the hook injection\n const functionsNeedingHook = new Set<t.Node>();\n\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n if (path.node.id) componentName = path.node.id.name;\n },\n\n VariableDeclarator(path: NodePath<t.VariableDeclarator>) {\n if (\n path.node.id.type === \"Identifier\" &&\n (path.node.init?.type === \"ArrowFunctionExpression\" ||\n path.node.init?.type === \"FunctionExpression\")\n ) {\n componentName = path.node.id.name;\n }\n },\n\n JSXText(path: NodePath<t.JSXText>) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const elementName = getJSXName(parent.node.openingElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n // Check ignore comment\n const line = path.node.loc?.start.line ?? 0;\n if (hasIgnoreComment(source, line)) return;\n\n // In explicit mode, only transform <T> and i18n prop elements\n if (mode === \"explicit\") {\n const hasI18n = parent.node.openingElement.attributes.some(\n (a) =>\n a.type === \"JSXAttribute\" &&\n a.name.type === \"JSXIdentifier\" &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n const id = generateId({ text, filePath, context: componentName });\n hasTranslations = true;\n\n if (locale && messages?.[id]) {\n // Zero-runtime: inline the translated text\n const newNode = t.jsxText(messages[id]);\n path.replaceWith(newNode);\n path.skip();\n } else {\n // Runtime: replace with __t() call\n needsImport = true;\n path.replaceWith(\n t.jsxExpressionContainer(\n t.callExpression(t.identifier(tFunctionName), [\n t.stringLiteral(id),\n t.identifier(\"undefined\"),\n t.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark the enclosing function as needing the hook\n const fnPath = path.findParent(\n (p) =>\n p.isFunctionDeclaration() ||\n p.isFunctionExpression() ||\n p.isArrowFunctionExpression(),\n );\n if (fnPath) {\n functionsNeedingHook.add(fnPath.node);\n }\n }\n },\n });\n\n // Inject `const { t: __t } = useTranslation();` at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectHook(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectHook(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectHook(path);\n },\n });\n\n function injectHook(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n const hookCall = t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.objectPattern([\n t.objectProperty(\n t.identifier(\"t\"),\n t.identifier(tFunctionName),\n false,\n false,\n ),\n ]),\n t.callExpression(t.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n // Arrow function with expression body: convert to block\n path.node.body = t.blockStatement([hookCall, t.returnStatement(body)]);\n }\n }\n\n // Add import for useTranslation (if not already present)\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === runtimeImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n const importDecl = t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n );\n (ast.program.body as t.Statement[]).unshift(importDecl);\n }\n }\n\n const output = generate(ast, { sourceMaps: true, sourceFileName: filePath });\n\n return {\n code: output.code,\n map: output.map,\n hasTranslations,\n };\n}\n\nfunction getJSXName(opening: t.JSXOpeningElement): string {\n if (opening.name.type === \"JSXIdentifier\") return opening.name.name;\n return \"unknown\";\n}\n\nfunction hasIgnoreComment(source: string, line: number): boolean {\n const lines = source.split(\"\\n\");\n const prevLine = lines[line - 2]; // line is 1-indexed, check line above\n return prevLine ? prevLine.includes(\"i18n-ignore\") : false;\n}\n","/**\n * Webpack loader for better-intl.\n *\n * Transforms JSX text nodes into t() calls automatically during build.\n * Used by Next.js integration to enable zero-config i18n.\n */\n\nimport { relative } from \"node:path\";\nimport { transform } from \"./transformer.js\";\n\nexport interface BetterIntlLoaderOptions {\n locale?: string;\n messages?: Record<string, string>;\n mode?: \"auto\" | \"explicit\";\n}\n\n// biome-ignore lint/suspicious/noExplicitAny: webpack loader context type\nexport default function betterIntlLoader(this: any, source: string): string {\n const options: BetterIntlLoaderOptions = this.getOptions?.() ?? {};\n const absolutePath = this.resourcePath;\n const filePath = relative(this.rootContext || process.cwd(), absolutePath);\n\n // Skip node_modules and non-JSX files\n if (absolutePath.includes(\"node_modules\")) return source;\n if (!/\\.[jt]sx$/.test(filePath)) return source;\n\n // Skip test/spec/stories files\n if (/\\.(test|spec|stories)\\.[jt]sx?$/.test(filePath)) return source;\n\n try {\n const result = transform(source, {\n filePath,\n locale: options.locale,\n messages: options.messages,\n mode: options.mode ?? \"auto\",\n });\n\n if (result.hasTranslations) {\n console.log(\n `[better-intl] Transformed ${filePath} (${result.hasTranslations})`,\n );\n return result.code;\n }\n\n return source;\n } catch (err) {\n console.error(`[better-intl] Error transforming ${filePath}:`, err);\n return source;\n }\n}\n"]}
|