@better-intl/compiler 0.3.1 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import { transform } from './chunk-MDYQVJ3W.js';
1
+ import { transform } from './chunk-NB4KZKAG.js';
2
2
  import { relative } from 'path';
3
3
 
4
4
  function betterIntlLoader(source) {
@@ -29,5 +29,5 @@ function betterIntlLoader(source) {
29
29
  }
30
30
 
31
31
  export { betterIntlLoader };
32
- //# sourceMappingURL=chunk-SF5H7SHR.js.map
33
- //# sourceMappingURL=chunk-SF5H7SHR.js.map
32
+ //# sourceMappingURL=chunk-M4HZ675W.js.map
33
+ //# sourceMappingURL=chunk-M4HZ675W.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/webpack-loader.ts"],"names":[],"mappings":";;;AAiBe,SAAR,iBAA6C,MAAA,EAAwB;AAC1E,EAAA,MAAM,OAAA,GAAmC,IAAA,CAAK,UAAA,IAAa,IAAK,EAAC;AACjE,EAAA,MAAM,eAAe,IAAA,CAAK,YAAA;AAC1B,EAAA,MAAM,WAAW,QAAA,CAAS,IAAA,CAAK,eAAe,OAAA,CAAQ,GAAA,IAAO,YAAY,CAAA;AAGzE,EAAA,IAAI,YAAA,CAAa,QAAA,CAAS,cAAc,CAAA,EAAG,OAAO,MAAA;AAClD,EAAA,IAAI,CAAC,WAAA,CAAY,IAAA,CAAK,QAAQ,GAAG,OAAO,MAAA;AAGxC,EAAA,IAAI,iCAAA,CAAkC,IAAA,CAAK,QAAQ,CAAA,EAAG,OAAO,MAAA;AAE7D,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,UAAU,MAAA,EAAQ;AAAA,MAC/B,QAAA;AAAA,MACA,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,IAAA,EAAM,QAAQ,IAAA,IAAQ;AAAA,KACvB,CAAA;AAED,IAAA,IAAI,OAAO,eAAA,EAAiB;AAC1B,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,CAAA,0BAAA,EAA6B,QAAQ,CAAA,EAAA,EAAK,MAAA,CAAO,eAAe,CAAA,CAAA;AAAA,OAClE;AACA,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,iCAAA,EAAoC,QAAQ,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAClE,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"chunk-SF5H7SHR.js","sourcesContent":["/**\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"]}
1
+ {"version":3,"sources":["../src/webpack-loader.ts"],"names":[],"mappings":";;;AAiBe,SAAR,iBAA6C,MAAA,EAAwB;AAC1E,EAAA,MAAM,OAAA,GAAmC,IAAA,CAAK,UAAA,IAAa,IAAK,EAAC;AACjE,EAAA,MAAM,eAAe,IAAA,CAAK,YAAA;AAC1B,EAAA,MAAM,WAAW,QAAA,CAAS,IAAA,CAAK,eAAe,OAAA,CAAQ,GAAA,IAAO,YAAY,CAAA;AAGzE,EAAA,IAAI,YAAA,CAAa,QAAA,CAAS,cAAc,CAAA,EAAG,OAAO,MAAA;AAClD,EAAA,IAAI,CAAC,WAAA,CAAY,IAAA,CAAK,QAAQ,GAAG,OAAO,MAAA;AAGxC,EAAA,IAAI,iCAAA,CAAkC,IAAA,CAAK,QAAQ,CAAA,EAAG,OAAO,MAAA;AAE7D,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,UAAU,MAAA,EAAQ;AAAA,MAC/B,QAAA;AAAA,MACA,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,IAAA,EAAM,QAAQ,IAAA,IAAQ;AAAA,KACvB,CAAA;AAED,IAAA,IAAI,OAAO,eAAA,EAAiB;AAC1B,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,CAAA,0BAAA,EAA6B,QAAQ,CAAA,EAAA,EAAK,MAAA,CAAO,eAAe,CAAA,CAAA;AAAA,OAClE;AACA,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,iCAAA,EAAoC,QAAQ,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAClE,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"chunk-M4HZ675W.js","sourcesContent":["/**\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"]}
@@ -38,6 +38,52 @@ function transform(source, options) {
38
38
  componentName = path.node.id.name;
39
39
  }
40
40
  },
41
+ // Handle interpolated JSX elements (text mixed with expressions)
42
+ JSXElement(path) {
43
+ const opening = path.node.openingElement;
44
+ const elName = getJSXName(opening);
45
+ if (IGNORED_ELEMENTS.has(elName)) return;
46
+ const elLine = opening.loc?.start.line ?? 0;
47
+ if (hasIgnoreComment(source, elLine)) return;
48
+ if (mode === "explicit") {
49
+ const hasI18n = opening.attributes.some(
50
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "i18n"
51
+ );
52
+ if (elName !== "T" && !hasI18n) return;
53
+ }
54
+ const result = buildICUMessage(path.node.children);
55
+ if (!result) return;
56
+ const { message, variables } = result;
57
+ const id = generateId({ text: message, filePath, context: componentName });
58
+ hasTranslations = true;
59
+ needsImport = true;
60
+ const valuesObj = t.objectExpression(
61
+ variables.map(
62
+ (name) => t.objectProperty(
63
+ t.identifier(name),
64
+ t.identifier(name),
65
+ false,
66
+ true
67
+ )
68
+ )
69
+ );
70
+ path.node.children = [
71
+ t.jsxExpressionContainer(
72
+ t.callExpression(t.identifier(tFunctionName), [
73
+ t.stringLiteral(id),
74
+ valuesObj,
75
+ t.stringLiteral(message)
76
+ ])
77
+ )
78
+ ];
79
+ const fnPath = path.findParent(
80
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
81
+ );
82
+ if (fnPath) {
83
+ functionsNeedingHook.add(fnPath.node);
84
+ }
85
+ path.skip();
86
+ },
41
87
  JSXText(path) {
42
88
  const text = path.node.value.trim();
43
89
  if (!text) return;
@@ -169,7 +215,38 @@ function hasIgnoreComment(source, line) {
169
215
  const prevLine = lines[line - 2];
170
216
  return prevLine ? prevLine.includes("i18n-ignore") : false;
171
217
  }
218
+ function buildICUMessage(children) {
219
+ let hasVariable = false;
220
+ const parts = [];
221
+ const variables = [];
222
+ for (const child of children) {
223
+ if (child.type === "JSXText") {
224
+ parts.push(child.value);
225
+ } else if (child.type === "JSXExpressionContainer") {
226
+ const expr = child.expression;
227
+ if (expr.type === "Identifier") {
228
+ parts.push(`{${expr.name}}`);
229
+ variables.push(expr.name);
230
+ hasVariable = true;
231
+ } else if (expr.type === "StringLiteral") {
232
+ parts.push(expr.value);
233
+ } else if (expr.type === "JSXEmptyExpression") {
234
+ continue;
235
+ } else {
236
+ return null;
237
+ }
238
+ } else {
239
+ return null;
240
+ }
241
+ }
242
+ if (!hasVariable) return null;
243
+ const message = parts.join("").replace(/\s+/g, " ").trim();
244
+ if (!message) return null;
245
+ const withoutVars = message.replace(/\{\w+\}/g, "").trim();
246
+ if (!withoutVars) return null;
247
+ return { message, variables };
248
+ }
172
249
 
173
250
  export { transform };
174
- //# sourceMappingURL=chunk-MDYQVJ3W.js.map
175
- //# sourceMappingURL=chunk-MDYQVJ3W.js.map
251
+ //# sourceMappingURL=chunk-NB4KZKAG.js.map
252
+ //# sourceMappingURL=chunk-NB4KZKAG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transformer.ts"],"names":["injectTranslation"],"mappings":"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","file":"chunk-NB4KZKAG.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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 // Handle interpolated JSX elements (text mixed with expressions)\n JSXElement(path: NodePath<t.JSXElement>) {\n const opening = path.node.openingElement;\n const elName = getJSXName(opening);\n if (IGNORED_ELEMENTS.has(elName)) return;\n\n const elLine = opening.loc?.start.line ?? 0;\n if (hasIgnoreComment(source, elLine)) return;\n\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a) =>\n a.type === \"JSXAttribute\" &&\n a.name.type === \"JSXIdentifier\" &&\n a.name.name === \"i18n\",\n );\n if (elName !== \"T\" && !hasI18n) return;\n }\n\n const result = buildICUMessage(path.node.children);\n if (!result) return;\n\n const { message, variables } = result;\n const id = generateId({ text: message, filePath, context: componentName });\n\n hasTranslations = true;\n needsImport = true;\n\n const valuesObj = t.objectExpression(\n variables.map((name) =>\n t.objectProperty(\n t.identifier(name),\n t.identifier(name),\n false,\n true,\n ),\n ),\n );\n\n path.node.children = [\n t.jsxExpressionContainer(\n t.callExpression(t.identifier(tFunctionName), [\n t.stringLiteral(id),\n valuesObj,\n t.stringLiteral(message),\n ]),\n ),\n ];\n\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 path.skip();\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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/**\n * Build an ICU message from JSX element children, detecting interpolation.\n */\nfunction buildICUMessage(\n children: t.JSXElement[\"children\"],\n): { message: string; variables: string[] } | null {\n let hasVariable = false;\n const parts: string[] = [];\n const variables: string[] = [];\n\n for (const child of children) {\n if (child.type === \"JSXText\") {\n parts.push(child.value);\n } else if (child.type === \"JSXExpressionContainer\") {\n const expr = child.expression;\n if (expr.type === \"Identifier\") {\n parts.push(`{${expr.name}}`);\n variables.push(expr.name);\n hasVariable = true;\n } else if (expr.type === \"StringLiteral\") {\n parts.push(expr.value);\n } else if (expr.type === \"JSXEmptyExpression\") {\n continue;\n } else {\n return null;\n }\n } else {\n return null;\n }\n }\n\n if (!hasVariable) return null;\n\n const message = parts.join(\"\").replace(/\\s+/g, \" \").trim();\n if (!message) return null;\n\n const withoutVars = message.replace(/\\{\\w+\\}/g, \"\").trim();\n if (!withoutVars) return null;\n\n return { message, variables };\n}\n"]}
package/dist/index.cjs CHANGED
@@ -105,6 +105,22 @@ function extract(source, options) {
105
105
  ctx.componentName = path.node.id.name;
106
106
  }
107
107
  },
108
+ // Extract interpolated messages from JSX elements with mixed text/expressions
109
+ JSXElement(path) {
110
+ const opening = path.node.openingElement;
111
+ const elementName = getElementName(opening);
112
+ if (IGNORED_ELEMENTS.has(elementName)) return;
113
+ const line = opening.loc?.start.line ?? 0;
114
+ if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;
115
+ if (ctx.mode === "explicit") {
116
+ const hasI18nProp = hasAttribute(opening, "i18n");
117
+ if (elementName !== "T" && !hasI18nProp) return;
118
+ }
119
+ const result = buildICUMessage(path.node.children);
120
+ if (!result) return;
121
+ addMessage(ctx, result.message, elementName, opening.loc);
122
+ path.skip();
123
+ },
108
124
  // Extract text from JSX
109
125
  JSXText(path) {
110
126
  const text = path.node.value.trim();
@@ -188,6 +204,37 @@ function hasAttribute(opening, name) {
188
204
  (attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === name
189
205
  );
190
206
  }
207
+ function buildICUMessage(children) {
208
+ let hasVariable = false;
209
+ const parts = [];
210
+ const variables = [];
211
+ for (const child of children) {
212
+ if (child.type === "JSXText") {
213
+ parts.push(child.value);
214
+ } else if (child.type === "JSXExpressionContainer") {
215
+ const expr = child.expression;
216
+ if (expr.type === "Identifier") {
217
+ parts.push(`{${expr.name}}`);
218
+ variables.push(expr.name);
219
+ hasVariable = true;
220
+ } else if (expr.type === "StringLiteral") {
221
+ parts.push(expr.value);
222
+ } else if (expr.type === "JSXEmptyExpression") {
223
+ continue;
224
+ } else {
225
+ return null;
226
+ }
227
+ } else {
228
+ return null;
229
+ }
230
+ }
231
+ if (!hasVariable) return null;
232
+ const message = parts.join("").replace(/\s+/g, " ").trim();
233
+ if (!message) return null;
234
+ const withoutVars = message.replace(/\{\w+\}/g, "").trim();
235
+ if (!withoutVars) return null;
236
+ return { message, variables };
237
+ }
191
238
  function collectIgnoreComments(source) {
192
239
  const ignored = /* @__PURE__ */ new Set();
193
240
  const lines = source.split("\n");
@@ -231,6 +278,52 @@ function transform(source, options) {
231
278
  componentName = path.node.id.name;
232
279
  }
233
280
  },
281
+ // Handle interpolated JSX elements (text mixed with expressions)
282
+ JSXElement(path) {
283
+ const opening = path.node.openingElement;
284
+ const elName = getJSXName(opening);
285
+ if (IGNORED_ELEMENTS2.has(elName)) return;
286
+ const elLine = opening.loc?.start.line ?? 0;
287
+ if (hasIgnoreComment(source, elLine)) return;
288
+ if (mode === "explicit") {
289
+ const hasI18n = opening.attributes.some(
290
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "i18n"
291
+ );
292
+ if (elName !== "T" && !hasI18n) return;
293
+ }
294
+ const result = buildICUMessage2(path.node.children);
295
+ if (!result) return;
296
+ const { message, variables } = result;
297
+ const id = core.generateId({ text: message, filePath, context: componentName });
298
+ hasTranslations = true;
299
+ needsImport = true;
300
+ const valuesObj = t__namespace.objectExpression(
301
+ variables.map(
302
+ (name) => t__namespace.objectProperty(
303
+ t__namespace.identifier(name),
304
+ t__namespace.identifier(name),
305
+ false,
306
+ true
307
+ )
308
+ )
309
+ );
310
+ path.node.children = [
311
+ t__namespace.jsxExpressionContainer(
312
+ t__namespace.callExpression(t__namespace.identifier(tFunctionName), [
313
+ t__namespace.stringLiteral(id),
314
+ valuesObj,
315
+ t__namespace.stringLiteral(message)
316
+ ])
317
+ )
318
+ ];
319
+ const fnPath = path.findParent(
320
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
321
+ );
322
+ if (fnPath) {
323
+ functionsNeedingHook.add(fnPath.node);
324
+ }
325
+ path.skip();
326
+ },
234
327
  JSXText(path) {
235
328
  const text = path.node.value.trim();
236
329
  if (!text) return;
@@ -362,6 +455,37 @@ function hasIgnoreComment(source, line) {
362
455
  const prevLine = lines[line - 2];
363
456
  return prevLine ? prevLine.includes("i18n-ignore") : false;
364
457
  }
458
+ function buildICUMessage2(children) {
459
+ let hasVariable = false;
460
+ const parts = [];
461
+ const variables = [];
462
+ for (const child of children) {
463
+ if (child.type === "JSXText") {
464
+ parts.push(child.value);
465
+ } else if (child.type === "JSXExpressionContainer") {
466
+ const expr = child.expression;
467
+ if (expr.type === "Identifier") {
468
+ parts.push(`{${expr.name}}`);
469
+ variables.push(expr.name);
470
+ hasVariable = true;
471
+ } else if (expr.type === "StringLiteral") {
472
+ parts.push(expr.value);
473
+ } else if (expr.type === "JSXEmptyExpression") {
474
+ continue;
475
+ } else {
476
+ return null;
477
+ }
478
+ } else {
479
+ return null;
480
+ }
481
+ }
482
+ if (!hasVariable) return null;
483
+ const message = parts.join("").replace(/\s+/g, " ").trim();
484
+ if (!message) return null;
485
+ const withoutVars = message.replace(/\{\w+\}/g, "").trim();
486
+ if (!withoutVars) return null;
487
+ return { message, variables };
488
+ }
365
489
  function betterIntlLoader(source) {
366
490
  const options = this.getOptions?.() ?? {};
367
491
  const absolutePath = this.resourcePath;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog-generator.ts","../src/extractor.ts","../src/transformer.ts","../src/webpack-loader.ts"],"names":["_traverse","parse","generateId","traverse","_generate","IGNORED_ELEMENTS","t","injectTranslation","relative"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAcO,SAAS,uBACd,QAAA,EACa;AACb,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA,GAAI,GAAA,CAAI,cAAA;AAAA,EACxB;AACA,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,OAAA,EAAQ;AAC3C;AAMO,SAAS,YAAA,CACd,UACA,SAAA,EAMA;AACA,EAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAI,SAAA,CAAU,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AACjD,EAAA,MAAM,cAAc,IAAI,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,CAAA;AAEjD,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,MAAM,YAAsB,EAAC;AAC7B,EAAA,MAAM,WAAqB,EAAC;AAG5B,EAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,IAAA,IAAI,WAAA,CAAY,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,EAAG;AAC3B,MAAA,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA,GAAI,QAAA,CAAS,IAAI,EAAE,CAAA;AAClC,MAAA,SAAA,CAAU,IAAA,CAAK,IAAI,EAAE,CAAA;AAAA,IACvB,CAAA,MAAO;AACL,MAAA,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA,GAAI,GAAA,CAAI,cAAA;AACvB,MAAA,KAAA,CAAM,IAAA,CAAK,IAAI,EAAE,CAAA;AAAA,IACnB;AAAA,EACF;AAGA,EAAA,KAAA,MAAW,MAAM,WAAA,EAAa;AAC5B,IAAA,IAAI,CAAC,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA,EAAG;AACnB,MAAA,OAAA,CAAQ,KAAK,EAAE,CAAA;AAAA,IACjB;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,OAAA,EAAS,SAAA,EAAU;AAC/C;AAKO,SAAS,eAAA,CACd,gBACA,aAAA,EACU;AACV,EAAA,OAAO,MAAA,CAAO,KAAK,cAAc,CAAA,CAAE,OAAO,CAAC,EAAA,KAAO,EAAE,EAAA,IAAM,aAAA,CAAc,CAAA;AAC1E;AAKO,SAAS,YAAA,CACd,SACA,SAAA,EACU;AACV,EAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,SAAA,CAAU,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AACvD,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,CAAO,CAAC,EAAA,KAAO,CAAC,YAAA,CAAa,GAAA,CAAI,EAAE,CAAC,CAAA;AAClE;ACtEA,IAAM,QAAA,GACJ,OAAOA,0BAAA,KAAc,UAAA,GACjBA,6BACCA,0BAAA,CAA4C,OAAA;AAGnD,IAAM,gBAAA,uBAAuB,GAAA,CAAI,CAAC,UAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAG7E,IAAM,qBAAA,GAAwB;AAAA,EAC5B,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAA;AAmBO,SAAS,OAAA,CACd,QACA,OAAA,EACoB;AACpB,EAAA,MAAM,EAAE,QAAA,EAAU,IAAA,GAAO,MAAA,EAAO,GAAI,OAAA;AAGpC,EAAA,IAAI,qBAAA,CAAsB,KAAK,CAAC,CAAA,KAAM,EAAE,IAAA,CAAK,QAAQ,CAAC,CAAA,EAAG;AACvD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,GAAA,GAAMC,aAAM,MAAA,EAAQ;AAAA,IACxB,UAAA,EAAY,QAAA;AAAA,IACZ,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY;AAAA,GAC9B,CAAA;AAED,EAAA,MAAM,GAAA,GAAyB;AAAA,IAC7B,aAAA,EAAe,MAAA;AAAA,IACf,UAAU,EAAC;AAAA,IACX,YAAA,EAAc,sBAAsB,MAAM,CAAA;AAAA,IAC1C,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,QAAA,CAAS,GAAA,EAAK;AAAA;AAAA,IAEZ,oBAAoB,IAAA,EAAuC;AACzD,MAAA,IAAI,IAAA,CAAK,KAAK,EAAA,EAAI;AAChB,QAAA,GAAA,CAAI,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA;AAAA,MACnC;AAAA,IACF,CAAA;AAAA,IAEA,mBAAmB,IAAA,EAAsC;AACvD,MAAA,IACE,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA,KAAS,iBACrB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,IAAA,KAAS,yBAAA,IACxB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,SAAS,oBAAA,CAAA,EAC3B;AACA,QAAA,GAAA,CAAI,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA;AAAA,MACnC;AAAA,IACF,CAAA;AAAA;AAAA,IAGA,QAAQ,IAAA,EAA2B;AACjC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAA,IAAQ,CAAA;AAC1C,MAAA,IAAI,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,IAAI,YAAA,CAAa,GAAA,CAAI,IAAA,GAAO,CAAC,CAAA,EAAG;AAElE,MAAA,MAAM,aAAA,GAAgB,kBAAkB,IAAI,CAAA;AAC5C,MAAA,IAAI,CAAC,aAAA,EAAe;AAEpB,MAAA,MAAM,WAAA,GAAc,eAAe,aAAa,CAAA;AAChD,MAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAGvC,MAAA,IAAI,GAAA,CAAI,SAAS,UAAA,EAAY;AAC3B,QAAA,MAAM,WAAA,GAAc,YAAA,CAAa,aAAA,EAAe,MAAM,CAAA;AACtD,QAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,WAAA,EAAa;AAAA,MAC3C;AAEA,MAAA,UAAA,CAAW,GAAA,EAAK,IAAA,EAAM,WAAA,EAAa,IAAA,CAAK,KAAK,GAAG,CAAA;AAAA,IAClD,CAAA;AAAA;AAAA,IAGA,uBAAuB,IAAA,EAA0C;AAC/D,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,UAAA;AACvB,MAAA,IAAI,IAAA,CAAK,SAAS,eAAA,EAAiB;AAEnC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAC7B,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,EAAK,KAAA,CAAM,IAAA,IAAQ,CAAA;AACrC,MAAA,IAAI,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,IAAI,YAAA,CAAa,GAAA,CAAI,IAAA,GAAO,CAAC,CAAA,EAAG;AAElE,MAAA,MAAM,aAAA,GAAgB,kBAAkB,IAAI,CAAA;AAC5C,MAAA,IAAI,CAAC,aAAA,EAAe;AAEpB,MAAA,MAAM,WAAA,GAAc,eAAe,aAAa,CAAA;AAChD,MAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAEvC,MAAA,IAAI,GAAA,CAAI,SAAS,UAAA,EAAY;AAC3B,QAAA,MAAM,WAAA,GAAc,YAAA,CAAa,aAAA,EAAe,MAAM,CAAA;AACtD,QAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,WAAA,EAAa;AAAA,MAC3C;AAEA,MAAA,UAAA,CAAW,GAAA,EAAK,IAAA,EAAM,WAAA,EAAa,IAAA,CAAK,GAAG,CAAA;AAAA,IAC7C;AAAA,GACD,CAAA;AAED,EAAA,OAAO,GAAA,CAAI,QAAA;AACb;AAEA,SAAS,UAAA,CACP,GAAA,EACA,IAAA,EACA,WAAA,EACA,GAAA,EACM;AACN,EAAA,MAAM,KAAKC,eAAA,CAAW;AAAA,IACpB,IAAA;AAAA,IACA,UAAU,GAAA,CAAI,QAAA;AAAA,IACd,SAAS,GAAA,CAAI;AAAA,GACd,CAAA;AAGD,EAAA,IAAI,GAAA,CAAI,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAA,EAAG;AAE3C,EAAA,GAAA,CAAI,SAAS,IAAA,CAAK;AAAA,IAChB,EAAA;AAAA,IACA,cAAA,EAAgB,IAAA;AAAA,IAChB,WAAA,EAAa,CAAA,EAAG,GAAA,CAAI,QAAQ,IAAI,WAAW,CAAA,CAAA;AAAA,IAC3C,UAAU,GAAA,CAAI,QAAA;AAAA,IACd,eAAe,GAAA,CAAI,aAAA;AAAA,IACnB,WAAA;AAAA,IACA,IAAA,EAAM,GAAA,EAAK,KAAA,CAAM,IAAA,IAAQ,CAAA;AAAA,IACzB,MAAA,EAAQ,GAAA,EAAK,KAAA,CAAM,MAAA,IAAU;AAAA,GAC9B,CAAA;AACH;AAEA,SAAS,kBAAkB,IAAA,EAA4C;AACrE,EAAA,IAAI,UAAU,IAAA,CAAK,UAAA;AACnB,EAAA,OAAO,OAAA,EAAS;AACd,IAAA,IAAI,OAAA,CAAQ,cAAa,EAAG;AAC1B,MAAA,OAAO,QAAQ,IAAA,CAAK,cAAA;AAAA,IACtB;AACA,IAAA,OAAA,GAAU,OAAA,CAAQ,UAAA;AAAA,EACpB;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,eAAe,OAAA,EAAsC;AAC5D,EAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB;AACzC,IAAA,OAAO,QAAQ,IAAA,CAAK,IAAA;AAAA,EACtB;AACA,EAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,IAAA,KAAS,qBAAA,EAAuB;AAC/C,IAAA,OAAO,CAAA,EAAG,aAAA,CAAc,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,cAAc,IAAA,EAAqC;AAC1D,EAAA,MAAM,MAAA,GACJ,IAAA,CAAK,MAAA,CAAO,IAAA,KAAS,eAAA,GACjB,KAAK,MAAA,CAAO,IAAA,GACZ,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA;AAC/B,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,IAAA,CAAK,SAAS,IAAI,CAAA,CAAA;AACxC;AAEA,SAAS,YAAA,CAAa,SAA8B,IAAA,EAAuB;AACzE,EAAA,OAAO,QAAQ,UAAA,CAAW,IAAA;AAAA,IACxB,CAAC,IAAA,KACC,IAAA,CAAK,IAAA,KAAS,cAAA,IACd,IAAA,CAAK,IAAA,CAAK,IAAA,KAAS,eAAA,IACnB,IAAA,CAAK,IAAA,CAAK,IAAA,KAAS;AAAA,GACvB;AACF;AAKA,SAAS,sBAAsB,MAAA,EAA6B;AAC1D,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG;AACpC,MAAA,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,IACnB;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;ACzMA,IAAMC,SAAAA,GACJ,OAAOH,0BAAAA,KAAc,UAAA,GACjBA,6BACCA,0BAAAA,CAA4C,OAAA;AAEnD,IAAM,QAAA,GACJ,OAAOI,0BAAA,KAAc,UAAA,GACjBA,6BACCA,0BAAA,CAA4C,OAAA;AAEnD,IAAMC,iBAAAA,uBAAuB,GAAA,CAAI,CAAC,UAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAsBtE,SAAS,SAAA,CACd,QACA,OAAA,EACiB;AACjB,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,aAAA,GAAgB,KAAA;AAAA,IAChB,aAAA,GAAgB,oBAAA;AAAA,IAChB,SAAA,GAAY,uBAAA;AAAA,IACZ,IAAA,GAAO;AAAA,GACT,GAAI,OAAA;AAEJ,EAAA,MAAM,GAAA,GAAMJ,aAAM,MAAA,EAAQ;AAAA,IACxB,UAAA,EAAY,QAAA;AAAA,IACZ,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY;AAAA,GAC9B,CAAA;AAGD,EAAA,MAAM,iBAAA,GACJ,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,KAAK,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,CAAM,KAAA,KAAU,YAAY,CAAA,IAClE,GAAA,CAAI,QAAQ,IAAA,CAAK,IAAA;AAAA,IACf,CAAC,IAAA,KACGK,YAAA,CAAA,qBAAA,CAAsB,IAAI,CAAA,IAC1BA,YAAA,CAAA,eAAA,CAAgB,IAAA,CAAK,UAAU,CAAA,IACjC,IAAA,CAAK,UAAA,CAAW,KAAA,KAAU;AAAA,GAC9B;AAEF,EAAA,IAAI,eAAA,GAAkB,KAAA;AACtB,EAAA,IAAI,aAAA;AACJ,EAAA,IAAI,WAAA,GAAc,KAAA;AAGlB,EAAA,MAAM,oBAAA,uBAA2B,GAAA,EAAY;AAE7C,EAAAH,UAAS,GAAA,EAAK;AAAA,IACZ,oBAAoB,IAAA,EAAuC;AACzD,MAAA,IAAI,KAAK,IAAA,CAAK,EAAA,EAAI,aAAA,GAAgB,IAAA,CAAK,KAAK,EAAA,CAAG,IAAA;AAAA,IACjD,CAAA;AAAA,IAEA,mBAAmB,IAAA,EAAsC;AACvD,MAAA,IACE,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA,KAAS,iBACrB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,IAAA,KAAS,yBAAA,IACxB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,SAAS,oBAAA,CAAA,EAC3B;AACA,QAAA,aAAA,GAAgB,IAAA,CAAK,KAAK,EAAA,CAAG,IAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AAAA,IAEA,QAAQ,IAAA,EAA2B;AACjC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AACpB,MAAA,IAAI,CAAC,MAAA,EAAQ,YAAA,EAAa,EAAG;AAE7B,MAAA,MAAM,WAAA,GAAc,UAAA,CAAW,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA;AACzD,MAAA,IAAIE,iBAAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAGvC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAA,IAAQ,CAAA;AAC1C,MAAA,IAAI,gBAAA,CAAiB,MAAA,EAAQ,IAAI,CAAA,EAAG;AAGpC,MAAA,IAAI,SAAS,UAAA,EAAY;AACvB,QAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,cAAA,CAAe,UAAA,CAAW,IAAA;AAAA,UACpD,CAAC,CAAA,KACC,CAAA,CAAE,IAAA,KAAS,cAAA,IACX,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,eAAA,IAChB,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS;AAAA,SACpB;AACA,QAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,OAAA,EAAS;AAAA,MACvC;AAEA,MAAA,MAAM,KAAKH,eAAAA,CAAW,EAAE,MAAM,QAAA,EAAU,OAAA,EAAS,eAAe,CAAA;AAChE,MAAA,eAAA,GAAkB,IAAA;AAElB,MAAA,IAAI,MAAA,IAAU,QAAA,GAAW,EAAE,CAAA,EAAG;AAE5B,QAAA,MAAM,OAAA,GAAYI,YAAA,CAAA,OAAA,CAAQ,QAAA,CAAS,EAAE,CAAC,CAAA;AACtC,QAAA,IAAA,CAAK,YAAY,OAAO,CAAA;AACxB,QAAA,IAAA,CAAK,IAAA,EAAK;AAAA,MACZ,CAAA,MAAO;AAEL,QAAA,WAAA,GAAc,IAAA;AACd,QAAA,IAAA,CAAK,WAAA;AAAA,UACDA,YAAA,CAAA,sBAAA;AAAA,YACEA,YAAA,CAAA,cAAA,CAAiBA,YAAA,CAAA,UAAA,CAAW,aAAa,CAAA,EAAG;AAAA,cAC1CA,2BAAc,EAAE,CAAA;AAAA,cAChBA,wBAAW,WAAW,CAAA;AAAA,cACtBA,2BAAc,IAAI;AAAA,aACrB;AAAA;AACH,SACF;AAGA,QAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AAAA,UAClB,CAAC,MACC,CAAA,CAAE,qBAAA,MACF,CAAA,CAAE,oBAAA,EAAqB,IACvB,CAAA,CAAE,yBAAA;AAA0B,SAChC;AACA,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,oBAAA,CAAqB,GAAA,CAAI,OAAO,IAAI,CAAA;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,GACD,CAAA;AAGD,EAAA,IAAI,WAAA,EAAa;AAaf,IAAA,IAASC,kBAAAA,GAAT,SACE,IAAA,EAGA;AACA,MAAA,IAAI,CAAC,oBAAA,CAAqB,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAG;AAI1C,MAAA,MAAM,SAAA,GAAY,iBAAA,GACZD,YAAA,CAAA,mBAAA,CAAoB,OAAA,EAAS;AAAA,QAC3BA,YAAA,CAAA,kBAAA;AAAA,UACEA,YAAA,CAAA,aAAA,CAAc;AAAA,YACZA,YAAA,CAAA,cAAA;AAAA,cACEA,wBAAW,GAAG,CAAA;AAAA,cACdA,wBAAW,aAAa,CAAA;AAAA,cAC1B,KAAA;AAAA,cACA;AAAA;AACF,WACD,CAAA;AAAA,UACCA,YAAA,CAAA,cAAA,CAAiBA,YAAA,CAAA,UAAA,CAAW,gBAAgB,CAAA,EAAG,EAAE;AAAA;AACrD,OACD,CAAA,GACCA,YAAA,CAAA,mBAAA,CAAoB,OAAA,EAAS;AAAA,QAC3BA,YAAA,CAAA,kBAAA;AAAA,UACEA,wBAAW,aAAa,CAAA;AAAA,UACxBA,YAAA,CAAA,cAAA,CAAiBA,YAAA,CAAA,UAAA,CAAW,KAAK,CAAA,EAAG,EAAE;AAAA;AAC1C,OACD,CAAA;AAEL,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACvB,MAAA,IAAMA,YAAA,CAAA,gBAAA,CAAiB,IAAI,CAAA,EAAG;AAC5B,QAAA,IAAA,CAAK,IAAA,CAAK,QAAQ,SAAS,CAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,IAAA,CAAK,OAASA,YAAA,CAAA,cAAA,CAAe,CAAC,WAAaA,YAAA,CAAA,eAAA,CAAgB,IAAI,CAAC,CAAC,CAAA;AAAA,MACxE;AAAA,IACF,CAAA;AAhDA,IAAAH,UAAS,GAAA,EAAK;AAAA,MACZ,oBAAoB,IAAA,EAAuC;AACzD,QAAAI,mBAAkB,IAAI,CAAA;AAAA,MACxB,CAAA;AAAA,MACA,mBAAmB,IAAA,EAAsC;AACvD,QAAAA,mBAAkB,IAAI,CAAA;AAAA,MACxB,CAAA;AAAA,MACA,wBAAwB,IAAA,EAA2C;AACjE,QAAAA,mBAAkB,IAAI,CAAA;AAAA,MACxB;AAAA,KACD,CAAA;AAwCD,IAAA,IAAI,iBAAA,EAAmB;AAErB,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,IAAA,CAAK,IAAA;AAAA,QACjC,CAAC,IAAA,KACGD,YAAA,CAAA,mBAAA,CAAoB,IAAI,CAAA,IAC1B,KAAK,MAAA,CAAO,KAAA,KAAU,aAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,UACd,CAAC,CAAA,KACGA,YAAA,CAAA,iBAAA,CAAkB,CAAC,CAAA,IACnBA,YAAA,CAAA,YAAA,CAAa,CAAA,CAAE,QAAQ,CAAA,IACzB,CAAA,CAAE,QAAA,CAAS,IAAA,KAAS;AAAA;AACxB,OACJ;AAEA,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAC,GAAA,CAAI,QAAQ,IAAA,CAAuB,OAAA;AAAA,UAChCA,YAAA,CAAA,iBAAA;AAAA,YACA;AAAA,cACIA,YAAA,CAAA,eAAA;AAAA,gBACEA,wBAAW,gBAAgB,CAAA;AAAA,gBAC3BA,wBAAW,gBAAgB;AAAA;AAC/B,aACF;AAAA,YACEA,2BAAc,aAAa;AAAA;AAC/B,SACF;AAAA,MACF;AAAA,IACF,CAAA,MAAO;AAEL,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,IAAA,CAAK,IAAA;AAAA,QACjC,CAAC,IAAA,KACGA,YAAA,CAAA,mBAAA,CAAoB,IAAI,CAAA,IAC1B,KAAK,MAAA,CAAO,KAAA,KAAU,SAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,UACd,CAAC,CAAA,KACGA,YAAA,CAAA,iBAAA,CAAkB,CAAC,CAAA,IACnBA,YAAA,CAAA,YAAA,CAAa,CAAA,CAAE,QAAQ,CAAA,IACzB,CAAA,CAAE,QAAA,CAAS,IAAA,KAAS;AAAA;AACxB,OACJ;AAEA,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAC,GAAA,CAAI,QAAQ,IAAA,CAAuB,OAAA;AAAA,UAChCA,YAAA,CAAA,iBAAA;AAAA,YACA,CAAGA,6BAAkBA,YAAA,CAAA,UAAA,CAAW,KAAK,GAAKA,YAAA,CAAA,UAAA,CAAW,KAAK,CAAC,CAAC,CAAA;AAAA,YAC1DA,2BAAc,SAAS;AAAA;AAC3B,SACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,SAAS,GAAA,EAAK,EAAE,YAAY,IAAA,EAAM,cAAA,EAAgB,UAAU,CAAA;AAE3E,EAAA,OAAO;AAAA,IACL,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,KAAK,MAAA,CAAO,GAAA;AAAA,IACZ;AAAA,GACF;AACF;AAEA,SAAS,WAAW,OAAA,EAAsC;AACxD,EAAA,IAAI,QAAQ,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB,OAAO,QAAQ,IAAA,CAAK,IAAA;AAC/D,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,gBAAA,CAAiB,QAAgB,IAAA,EAAuB;AAC/D,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,IAAA,GAAO,CAAC,CAAA;AAC/B,EAAA,OAAO,QAAA,GAAW,QAAA,CAAS,QAAA,CAAS,aAAa,CAAA,GAAI,KAAA;AACvD;AC1Qe,SAAR,iBAA6C,MAAA,EAAwB;AAC1E,EAAA,MAAM,OAAA,GAAmC,IAAA,CAAK,UAAA,IAAa,IAAK,EAAC;AACjE,EAAA,MAAM,eAAe,IAAA,CAAK,YAAA;AAC1B,EAAA,MAAM,WAAWE,aAAA,CAAS,IAAA,CAAK,eAAe,OAAA,CAAQ,GAAA,IAAO,YAAY,CAAA;AAGzE,EAAA,IAAI,YAAA,CAAa,QAAA,CAAS,cAAc,CAAA,EAAG,OAAO,MAAA;AAClD,EAAA,IAAI,CAAC,WAAA,CAAY,IAAA,CAAK,QAAQ,GAAG,OAAO,MAAA;AAGxC,EAAA,IAAI,iCAAA,CAAkC,IAAA,CAAK,QAAQ,CAAA,EAAG,OAAO,MAAA;AAE7D,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,UAAU,MAAA,EAAQ;AAAA,MAC/B,QAAA;AAAA,MACA,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,IAAA,EAAM,QAAQ,IAAA,IAAQ;AAAA,KACvB,CAAA;AAED,IAAA,IAAI,OAAO,eAAA,EAAiB;AAC1B,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,CAAA,0BAAA,EAA6B,QAAQ,CAAA,EAAA,EAAK,MAAA,CAAO,eAAe,CAAA,CAAA;AAAA,OAClE;AACA,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,iCAAA,EAAoC,QAAQ,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAClE,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"index.cjs","sourcesContent":["/**\n * Catalog generator — writes extracted messages to locale JSON files.\n */\n\nimport type { ExtractedMessage, Messages } from \"@better-intl/core\";\n\nexport interface CatalogFile {\n locale: string;\n messages: Messages;\n}\n\n/**\n * Generate a catalog for the default locale from extracted messages.\n */\nexport function generateDefaultCatalog(\n messages: ExtractedMessage[],\n): CatalogFile {\n const catalog: Messages = {};\n for (const msg of messages) {\n catalog[msg.id] = msg.defaultMessage;\n }\n return { locale: \"en\", messages: catalog };\n}\n\n/**\n * Merge new messages into an existing catalog, preserving existing translations.\n * Returns the merged catalog and lists of added/removed keys.\n */\nexport function mergeCatalog(\n existing: Messages,\n extracted: ExtractedMessage[],\n): {\n messages: Messages;\n added: string[];\n removed: string[];\n unchanged: string[];\n} {\n const newIds = new Set(extracted.map((m) => m.id));\n const existingIds = new Set(Object.keys(existing));\n\n const added: string[] = [];\n const removed: string[] = [];\n const unchanged: string[] = [];\n const messages: Messages = {};\n\n // Keep existing translations, mark new ones\n for (const msg of extracted) {\n if (existingIds.has(msg.id)) {\n messages[msg.id] = existing[msg.id];\n unchanged.push(msg.id);\n } else {\n messages[msg.id] = msg.defaultMessage;\n added.push(msg.id);\n }\n }\n\n // Track removed keys\n for (const id of existingIds) {\n if (!newIds.has(id)) {\n removed.push(id);\n }\n }\n\n return { messages, added, removed, unchanged };\n}\n\n/**\n * Find missing translations (keys present in default but not in target locale).\n */\nexport function findMissingKeys(\n defaultCatalog: Messages,\n targetCatalog: Messages,\n): string[] {\n return Object.keys(defaultCatalog).filter((id) => !(id in targetCatalog));\n}\n\n/**\n * Find dead keys (keys in catalog but not in extracted messages).\n */\nexport function findDeadKeys(\n catalog: Messages,\n extracted: ExtractedMessage[],\n): string[] {\n const extractedIds = new Set(extracted.map((m) => m.id));\n return Object.keys(catalog).filter((id) => !extractedIds.has(id));\n}\n","/**\n * AST-based extractor for JSX text nodes.\n *\n * Walks the Babel AST to find translatable text, respecting ignore rules,\n * and emits `ExtractedMessage` descriptors with stable IDs.\n */\n\nimport { parse } from \"@babel/parser\";\nimport type { NodePath } from \"@babel/traverse\";\nimport _traverse from \"@babel/traverse\";\nimport type * as t from \"@babel/types\";\nimport type { ExtractedMessage } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\n// Handle CJS/ESM interop for @babel/traverse\nconst traverse =\n typeof _traverse === \"function\"\n ? _traverse\n : (_traverse as { default: typeof _traverse }).default;\n\n/** Elements whose text content should never be extracted. */\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\n/** File patterns that should be skipped entirely. */\nconst IGNORED_FILE_PATTERNS = [\n /\\.test\\.[tj]sx?$/,\n /\\.spec\\.[tj]sx?$/,\n /\\.stories\\.[tj]sx?$/,\n /\\/__tests__\\//,\n];\n\nexport interface ExtractorOptions {\n filePath: string;\n /** Extraction mode: \"auto\" extracts all text, \"explicit\" only <T> and i18n prop */\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface ExtractionContext {\n componentName: string | undefined;\n messages: ExtractedMessage[];\n ignoredLines: Set<number>;\n filePath: string;\n mode: \"auto\" | \"explicit\";\n}\n\n/**\n * Extract translatable messages from a source file.\n */\nexport function extract(\n source: string,\n options: ExtractorOptions,\n): ExtractedMessage[] {\n const { filePath, mode = \"auto\" } = options;\n\n // Skip ignored file patterns\n if (IGNORED_FILE_PATTERNS.some((p) => p.test(filePath))) {\n return [];\n }\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n const ctx: ExtractionContext = {\n componentName: undefined,\n messages: [],\n ignoredLines: collectIgnoreComments(source),\n filePath,\n mode,\n };\n\n traverse(ast, {\n // Track component name for context\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n if (path.node.id) {\n ctx.componentName = path.node.id.name;\n }\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 ctx.componentName = path.node.id.name;\n }\n },\n\n // Extract text from JSX\n JSXText(path: NodePath<t.JSXText>) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const line = path.node.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n // In explicit mode, only extract from <T> or elements with i18n prop\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, path.node.loc);\n },\n\n // Also handle string literals in JSX expressions like {\"Hello\"}\n JSXExpressionContainer(path: NodePath<t.JSXExpressionContainer>) {\n const expr = path.node.expression;\n if (expr.type !== \"StringLiteral\") return;\n\n const text = expr.value.trim();\n if (!text) return;\n\n const line = expr.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, expr.loc);\n },\n });\n\n return ctx.messages;\n}\n\nfunction addMessage(\n ctx: ExtractionContext,\n text: string,\n elementType: string,\n loc: t.SourceLocation | null | undefined,\n): void {\n const id = generateId({\n text,\n filePath: ctx.filePath,\n context: ctx.componentName,\n });\n\n // Avoid duplicate IDs within the same file\n if (ctx.messages.some((m) => m.id === id)) return;\n\n ctx.messages.push({\n id,\n defaultMessage: text,\n description: `${ctx.filePath}:${elementType}`,\n filePath: ctx.filePath,\n componentName: ctx.componentName,\n elementType,\n line: loc?.start.line ?? 0,\n column: loc?.start.column ?? 0,\n });\n}\n\nfunction findParentElement(path: NodePath): t.JSXOpeningElement | null {\n let current = path.parentPath;\n while (current) {\n if (current.isJSXElement()) {\n return current.node.openingElement;\n }\n current = current.parentPath;\n }\n return null;\n}\n\nfunction getElementName(opening: t.JSXOpeningElement): string {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n if (opening.name.type === \"JSXMemberExpression\") {\n return `${getMemberName(opening.name)}`;\n }\n return \"unknown\";\n}\n\nfunction getMemberName(node: t.JSXMemberExpression): string {\n const object =\n node.object.type === \"JSXIdentifier\"\n ? node.object.name\n : getMemberName(node.object);\n return `${object}.${node.property.name}`;\n}\n\nfunction hasAttribute(opening: t.JSXOpeningElement, name: string): boolean {\n return opening.attributes.some(\n (attr) =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === name,\n );\n}\n\n/**\n * Collect line numbers that have `i18n-ignore` comments.\n */\nfunction collectIgnoreComments(source: string): Set<number> {\n const ignored = new Set<number>();\n const lines = source.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes(\"i18n-ignore\")) {\n ignored.add(i + 1); // 1-indexed\n }\n }\n return ignored;\n}\n","/**\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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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"]}
1
+ {"version":3,"sources":["../src/catalog-generator.ts","../src/extractor.ts","../src/transformer.ts","../src/webpack-loader.ts"],"names":["_traverse","parse","generateId","traverse","_generate","IGNORED_ELEMENTS","t","buildICUMessage","injectTranslation","relative"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Catalog generator — writes extracted messages to locale JSON files.\n */\n\nimport type { ExtractedMessage, Messages } from \"@better-intl/core\";\n\nexport interface CatalogFile {\n locale: string;\n messages: Messages;\n}\n\n/**\n * Generate a catalog for the default locale from extracted messages.\n */\nexport function generateDefaultCatalog(\n messages: ExtractedMessage[],\n): CatalogFile {\n const catalog: Messages = {};\n for (const msg of messages) {\n catalog[msg.id] = msg.defaultMessage;\n }\n return { locale: \"en\", messages: catalog };\n}\n\n/**\n * Merge new messages into an existing catalog, preserving existing translations.\n * Returns the merged catalog and lists of added/removed keys.\n */\nexport function mergeCatalog(\n existing: Messages,\n extracted: ExtractedMessage[],\n): {\n messages: Messages;\n added: string[];\n removed: string[];\n unchanged: string[];\n} {\n const newIds = new Set(extracted.map((m) => m.id));\n const existingIds = new Set(Object.keys(existing));\n\n const added: string[] = [];\n const removed: string[] = [];\n const unchanged: string[] = [];\n const messages: Messages = {};\n\n // Keep existing translations, mark new ones\n for (const msg of extracted) {\n if (existingIds.has(msg.id)) {\n messages[msg.id] = existing[msg.id];\n unchanged.push(msg.id);\n } else {\n messages[msg.id] = msg.defaultMessage;\n added.push(msg.id);\n }\n }\n\n // Track removed keys\n for (const id of existingIds) {\n if (!newIds.has(id)) {\n removed.push(id);\n }\n }\n\n return { messages, added, removed, unchanged };\n}\n\n/**\n * Find missing translations (keys present in default but not in target locale).\n */\nexport function findMissingKeys(\n defaultCatalog: Messages,\n targetCatalog: Messages,\n): string[] {\n return Object.keys(defaultCatalog).filter((id) => !(id in targetCatalog));\n}\n\n/**\n * Find dead keys (keys in catalog but not in extracted messages).\n */\nexport function findDeadKeys(\n catalog: Messages,\n extracted: ExtractedMessage[],\n): string[] {\n const extractedIds = new Set(extracted.map((m) => m.id));\n return Object.keys(catalog).filter((id) => !extractedIds.has(id));\n}\n","/**\n * AST-based extractor for JSX text nodes.\n *\n * Walks the Babel AST to find translatable text, respecting ignore rules,\n * and emits `ExtractedMessage` descriptors with stable IDs.\n */\n\nimport { parse } from \"@babel/parser\";\nimport type { NodePath } from \"@babel/traverse\";\nimport _traverse from \"@babel/traverse\";\nimport type * as t from \"@babel/types\";\nimport type { ExtractedMessage } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\n// Handle CJS/ESM interop for @babel/traverse\nconst traverse =\n typeof _traverse === \"function\"\n ? _traverse\n : (_traverse as { default: typeof _traverse }).default;\n\n/** Elements whose text content should never be extracted. */\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\n/** File patterns that should be skipped entirely. */\nconst IGNORED_FILE_PATTERNS = [\n /\\.test\\.[tj]sx?$/,\n /\\.spec\\.[tj]sx?$/,\n /\\.stories\\.[tj]sx?$/,\n /\\/__tests__\\//,\n];\n\nexport interface ExtractorOptions {\n filePath: string;\n /** Extraction mode: \"auto\" extracts all text, \"explicit\" only <T> and i18n prop */\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface ExtractionContext {\n componentName: string | undefined;\n messages: ExtractedMessage[];\n ignoredLines: Set<number>;\n filePath: string;\n mode: \"auto\" | \"explicit\";\n}\n\n/**\n * Extract translatable messages from a source file.\n */\nexport function extract(\n source: string,\n options: ExtractorOptions,\n): ExtractedMessage[] {\n const { filePath, mode = \"auto\" } = options;\n\n // Skip ignored file patterns\n if (IGNORED_FILE_PATTERNS.some((p) => p.test(filePath))) {\n return [];\n }\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n const ctx: ExtractionContext = {\n componentName: undefined,\n messages: [],\n ignoredLines: collectIgnoreComments(source),\n filePath,\n mode,\n };\n\n traverse(ast, {\n // Track component name for context\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n if (path.node.id) {\n ctx.componentName = path.node.id.name;\n }\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 ctx.componentName = path.node.id.name;\n }\n },\n\n // Extract interpolated messages from JSX elements with mixed text/expressions\n JSXElement(path: NodePath<t.JSXElement>) {\n const opening = path.node.openingElement;\n const elementName = getElementName(opening);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const line = opening.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(opening, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n const result = buildICUMessage(path.node.children);\n if (!result) return;\n\n addMessage(ctx, result.message, elementName, opening.loc);\n path.skip();\n },\n\n // Extract text from JSX\n JSXText(path: NodePath<t.JSXText>) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const line = path.node.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n // In explicit mode, only extract from <T> or elements with i18n prop\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, path.node.loc);\n },\n\n // Also handle string literals in JSX expressions like {\"Hello\"}\n JSXExpressionContainer(path: NodePath<t.JSXExpressionContainer>) {\n const expr = path.node.expression;\n if (expr.type !== \"StringLiteral\") return;\n\n const text = expr.value.trim();\n if (!text) return;\n\n const line = expr.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, expr.loc);\n },\n });\n\n return ctx.messages;\n}\n\nfunction addMessage(\n ctx: ExtractionContext,\n text: string,\n elementType: string,\n loc: t.SourceLocation | null | undefined,\n): void {\n const id = generateId({\n text,\n filePath: ctx.filePath,\n context: ctx.componentName,\n });\n\n // Avoid duplicate IDs within the same file\n if (ctx.messages.some((m) => m.id === id)) return;\n\n ctx.messages.push({\n id,\n defaultMessage: text,\n description: `${ctx.filePath}:${elementType}`,\n filePath: ctx.filePath,\n componentName: ctx.componentName,\n elementType,\n line: loc?.start.line ?? 0,\n column: loc?.start.column ?? 0,\n });\n}\n\nfunction findParentElement(path: NodePath): t.JSXOpeningElement | null {\n let current = path.parentPath;\n while (current) {\n if (current.isJSXElement()) {\n return current.node.openingElement;\n }\n current = current.parentPath;\n }\n return null;\n}\n\nfunction getElementName(opening: t.JSXOpeningElement): string {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n if (opening.name.type === \"JSXMemberExpression\") {\n return `${getMemberName(opening.name)}`;\n }\n return \"unknown\";\n}\n\nfunction getMemberName(node: t.JSXMemberExpression): string {\n const object =\n node.object.type === \"JSXIdentifier\"\n ? node.object.name\n : getMemberName(node.object);\n return `${object}.${node.property.name}`;\n}\n\nfunction hasAttribute(opening: t.JSXOpeningElement, name: string): boolean {\n return opening.attributes.some(\n (attr) =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === name,\n );\n}\n\n/**\n * Build an ICU message from JSX element children, detecting interpolation.\n * Returns null if no variable expressions found, complex expressions exist,\n * or nested JSX elements are present.\n */\nfunction buildICUMessage(\n children: t.JSXElement[\"children\"],\n): { message: string; variables: string[] } | null {\n let hasVariable = false;\n const parts: string[] = [];\n const variables: string[] = [];\n\n for (const child of children) {\n if (child.type === \"JSXText\") {\n parts.push(child.value);\n } else if (child.type === \"JSXExpressionContainer\") {\n const expr = child.expression;\n if (expr.type === \"Identifier\") {\n parts.push(`{${expr.name}}`);\n variables.push(expr.name);\n hasVariable = true;\n } else if (expr.type === \"StringLiteral\") {\n parts.push(expr.value);\n } else if (expr.type === \"JSXEmptyExpression\") {\n continue;\n } else {\n return null;\n }\n } else {\n return null;\n }\n }\n\n if (!hasVariable) return null;\n\n const message = parts.join(\"\").replace(/\\s+/g, \" \").trim();\n if (!message) return null;\n\n // Skip messages that are only variables with no translatable text\n const withoutVars = message.replace(/\\{\\w+\\}/g, \"\").trim();\n if (!withoutVars) return null;\n\n return { message, variables };\n}\n\n/**\n * Collect line numbers that have `i18n-ignore` comments.\n */\nfunction collectIgnoreComments(source: string): Set<number> {\n const ignored = new Set<number>();\n const lines = source.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes(\"i18n-ignore\")) {\n ignored.add(i + 1); // 1-indexed\n }\n }\n return ignored;\n}\n","/**\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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 // Handle interpolated JSX elements (text mixed with expressions)\n JSXElement(path: NodePath<t.JSXElement>) {\n const opening = path.node.openingElement;\n const elName = getJSXName(opening);\n if (IGNORED_ELEMENTS.has(elName)) return;\n\n const elLine = opening.loc?.start.line ?? 0;\n if (hasIgnoreComment(source, elLine)) return;\n\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a) =>\n a.type === \"JSXAttribute\" &&\n a.name.type === \"JSXIdentifier\" &&\n a.name.name === \"i18n\",\n );\n if (elName !== \"T\" && !hasI18n) return;\n }\n\n const result = buildICUMessage(path.node.children);\n if (!result) return;\n\n const { message, variables } = result;\n const id = generateId({ text: message, filePath, context: componentName });\n\n hasTranslations = true;\n needsImport = true;\n\n const valuesObj = t.objectExpression(\n variables.map((name) =>\n t.objectProperty(\n t.identifier(name),\n t.identifier(name),\n false,\n true,\n ),\n ),\n );\n\n path.node.children = [\n t.jsxExpressionContainer(\n t.callExpression(t.identifier(tFunctionName), [\n t.stringLiteral(id),\n valuesObj,\n t.stringLiteral(message),\n ]),\n ),\n ];\n\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 path.skip();\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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/**\n * Build an ICU message from JSX element children, detecting interpolation.\n */\nfunction buildICUMessage(\n children: t.JSXElement[\"children\"],\n): { message: string; variables: string[] } | null {\n let hasVariable = false;\n const parts: string[] = [];\n const variables: string[] = [];\n\n for (const child of children) {\n if (child.type === \"JSXText\") {\n parts.push(child.value);\n } else if (child.type === \"JSXExpressionContainer\") {\n const expr = child.expression;\n if (expr.type === \"Identifier\") {\n parts.push(`{${expr.name}}`);\n variables.push(expr.name);\n hasVariable = true;\n } else if (expr.type === \"StringLiteral\") {\n parts.push(expr.value);\n } else if (expr.type === \"JSXEmptyExpression\") {\n continue;\n } else {\n return null;\n }\n } else {\n return null;\n }\n }\n\n if (!hasVariable) return null;\n\n const message = parts.join(\"\").replace(/\\s+/g, \" \").trim();\n if (!message) return null;\n\n const withoutVars = message.replace(/\\{\\w+\\}/g, \"\").trim();\n if (!withoutVars) return null;\n\n return { message, variables };\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"]}
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { betterIntlLoader as webpackLoader } from './chunk-SF5H7SHR.js';
2
- export { transform } from './chunk-MDYQVJ3W.js';
1
+ export { betterIntlLoader as webpackLoader } from './chunk-M4HZ675W.js';
2
+ export { transform } from './chunk-NB4KZKAG.js';
3
3
  import { parse } from '@babel/parser';
4
4
  import _traverse from '@babel/traverse';
5
5
  import { generateId } from '@better-intl/core';
@@ -78,6 +78,22 @@ function extract(source, options) {
78
78
  ctx.componentName = path.node.id.name;
79
79
  }
80
80
  },
81
+ // Extract interpolated messages from JSX elements with mixed text/expressions
82
+ JSXElement(path) {
83
+ const opening = path.node.openingElement;
84
+ const elementName = getElementName(opening);
85
+ if (IGNORED_ELEMENTS.has(elementName)) return;
86
+ const line = opening.loc?.start.line ?? 0;
87
+ if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;
88
+ if (ctx.mode === "explicit") {
89
+ const hasI18nProp = hasAttribute(opening, "i18n");
90
+ if (elementName !== "T" && !hasI18nProp) return;
91
+ }
92
+ const result = buildICUMessage(path.node.children);
93
+ if (!result) return;
94
+ addMessage(ctx, result.message, elementName, opening.loc);
95
+ path.skip();
96
+ },
81
97
  // Extract text from JSX
82
98
  JSXText(path) {
83
99
  const text = path.node.value.trim();
@@ -161,6 +177,37 @@ function hasAttribute(opening, name) {
161
177
  (attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === name
162
178
  );
163
179
  }
180
+ function buildICUMessage(children) {
181
+ let hasVariable = false;
182
+ const parts = [];
183
+ const variables = [];
184
+ for (const child of children) {
185
+ if (child.type === "JSXText") {
186
+ parts.push(child.value);
187
+ } else if (child.type === "JSXExpressionContainer") {
188
+ const expr = child.expression;
189
+ if (expr.type === "Identifier") {
190
+ parts.push(`{${expr.name}}`);
191
+ variables.push(expr.name);
192
+ hasVariable = true;
193
+ } else if (expr.type === "StringLiteral") {
194
+ parts.push(expr.value);
195
+ } else if (expr.type === "JSXEmptyExpression") {
196
+ continue;
197
+ } else {
198
+ return null;
199
+ }
200
+ } else {
201
+ return null;
202
+ }
203
+ }
204
+ if (!hasVariable) return null;
205
+ const message = parts.join("").replace(/\s+/g, " ").trim();
206
+ if (!message) return null;
207
+ const withoutVars = message.replace(/\{\w+\}/g, "").trim();
208
+ if (!withoutVars) return null;
209
+ return { message, variables };
210
+ }
164
211
  function collectIgnoreComments(source) {
165
212
  const ignored = /* @__PURE__ */ new Set();
166
213
  const lines = source.split("\n");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog-generator.ts","../src/extractor.ts"],"names":[],"mappings":";;;;;;;AAcO,SAAS,uBACd,QAAA,EACa;AACb,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,KAAA,MAAW,OAAO,QAAA,EAAU;AAC1B,IAAA,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA,GAAI,GAAA,CAAI,cAAA;AAAA,EACxB;AACA,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,OAAA,EAAQ;AAC3C;AAMO,SAAS,YAAA,CACd,UACA,SAAA,EAMA;AACA,EAAA,MAAM,MAAA,GAAS,IAAI,GAAA,CAAI,SAAA,CAAU,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AACjD,EAAA,MAAM,cAAc,IAAI,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,QAAQ,CAAC,CAAA;AAEjD,EAAA,MAAM,QAAkB,EAAC;AACzB,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,MAAM,YAAsB,EAAC;AAC7B,EAAA,MAAM,WAAqB,EAAC;AAG5B,EAAA,KAAA,MAAW,OAAO,SAAA,EAAW;AAC3B,IAAA,IAAI,WAAA,CAAY,GAAA,CAAI,GAAA,CAAI,EAAE,CAAA,EAAG;AAC3B,MAAA,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA,GAAI,QAAA,CAAS,IAAI,EAAE,CAAA;AAClC,MAAA,SAAA,CAAU,IAAA,CAAK,IAAI,EAAE,CAAA;AAAA,IACvB,CAAA,MAAO;AACL,MAAA,QAAA,CAAS,GAAA,CAAI,EAAE,CAAA,GAAI,GAAA,CAAI,cAAA;AACvB,MAAA,KAAA,CAAM,IAAA,CAAK,IAAI,EAAE,CAAA;AAAA,IACnB;AAAA,EACF;AAGA,EAAA,KAAA,MAAW,MAAM,WAAA,EAAa;AAC5B,IAAA,IAAI,CAAC,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA,EAAG;AACnB,MAAA,OAAA,CAAQ,KAAK,EAAE,CAAA;AAAA,IACjB;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,QAAA,EAAU,KAAA,EAAO,OAAA,EAAS,SAAA,EAAU;AAC/C;AAKO,SAAS,eAAA,CACd,gBACA,aAAA,EACU;AACV,EAAA,OAAO,MAAA,CAAO,KAAK,cAAc,CAAA,CAAE,OAAO,CAAC,EAAA,KAAO,EAAE,EAAA,IAAM,aAAA,CAAc,CAAA;AAC1E;AAKO,SAAS,YAAA,CACd,SACA,SAAA,EACU;AACV,EAAA,MAAM,YAAA,GAAe,IAAI,GAAA,CAAI,SAAA,CAAU,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AACvD,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,MAAA,CAAO,CAAC,EAAA,KAAO,CAAC,YAAA,CAAa,GAAA,CAAI,EAAE,CAAC,CAAA;AAClE;ACtEA,IAAM,QAAA,GACJ,OAAO,SAAA,KAAc,UAAA,GACjB,YACC,SAAA,CAA4C,OAAA;AAGnD,IAAM,gBAAA,uBAAuB,GAAA,CAAI,CAAC,UAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAG7E,IAAM,qBAAA,GAAwB;AAAA,EAC5B,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA,qBAAA;AAAA,EACA;AACF,CAAA;AAmBO,SAAS,OAAA,CACd,QACA,OAAA,EACoB;AACpB,EAAA,MAAM,EAAE,QAAA,EAAU,IAAA,GAAO,MAAA,EAAO,GAAI,OAAA;AAGpC,EAAA,IAAI,qBAAA,CAAsB,KAAK,CAAC,CAAA,KAAM,EAAE,IAAA,CAAK,QAAQ,CAAC,CAAA,EAAG;AACvD,IAAA,OAAO,EAAC;AAAA,EACV;AAEA,EAAA,MAAM,GAAA,GAAM,MAAM,MAAA,EAAQ;AAAA,IACxB,UAAA,EAAY,QAAA;AAAA,IACZ,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY;AAAA,GAC9B,CAAA;AAED,EAAA,MAAM,GAAA,GAAyB;AAAA,IAC7B,aAAA,EAAe,MAAA;AAAA,IACf,UAAU,EAAC;AAAA,IACX,YAAA,EAAc,sBAAsB,MAAM,CAAA;AAAA,IAC1C,QAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,QAAA,CAAS,GAAA,EAAK;AAAA;AAAA,IAEZ,oBAAoB,IAAA,EAAuC;AACzD,MAAA,IAAI,IAAA,CAAK,KAAK,EAAA,EAAI;AAChB,QAAA,GAAA,CAAI,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA;AAAA,MACnC;AAAA,IACF,CAAA;AAAA,IAEA,mBAAmB,IAAA,EAAsC;AACvD,MAAA,IACE,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA,KAAS,iBACrB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,IAAA,KAAS,yBAAA,IACxB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,SAAS,oBAAA,CAAA,EAC3B;AACA,QAAA,GAAA,CAAI,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA;AAAA,MACnC;AAAA,IACF,CAAA;AAAA;AAAA,IAGA,QAAQ,IAAA,EAA2B;AACjC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAA,IAAQ,CAAA;AAC1C,MAAA,IAAI,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,IAAI,YAAA,CAAa,GAAA,CAAI,IAAA,GAAO,CAAC,CAAA,EAAG;AAElE,MAAA,MAAM,aAAA,GAAgB,kBAAkB,IAAI,CAAA;AAC5C,MAAA,IAAI,CAAC,aAAA,EAAe;AAEpB,MAAA,MAAM,WAAA,GAAc,eAAe,aAAa,CAAA;AAChD,MAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAGvC,MAAA,IAAI,GAAA,CAAI,SAAS,UAAA,EAAY;AAC3B,QAAA,MAAM,WAAA,GAAc,YAAA,CAAa,aAAA,EAAe,MAAM,CAAA;AACtD,QAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,WAAA,EAAa;AAAA,MAC3C;AAEA,MAAA,UAAA,CAAW,GAAA,EAAK,IAAA,EAAM,WAAA,EAAa,IAAA,CAAK,KAAK,GAAG,CAAA;AAAA,IAClD,CAAA;AAAA;AAAA,IAGA,uBAAuB,IAAA,EAA0C;AAC/D,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,UAAA;AACvB,MAAA,IAAI,IAAA,CAAK,SAAS,eAAA,EAAiB;AAEnC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAC7B,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,EAAK,KAAA,CAAM,IAAA,IAAQ,CAAA;AACrC,MAAA,IAAI,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,IAAI,CAAA,IAAK,IAAI,YAAA,CAAa,GAAA,CAAI,IAAA,GAAO,CAAC,CAAA,EAAG;AAElE,MAAA,MAAM,aAAA,GAAgB,kBAAkB,IAAI,CAAA;AAC5C,MAAA,IAAI,CAAC,aAAA,EAAe;AAEpB,MAAA,MAAM,WAAA,GAAc,eAAe,aAAa,CAAA;AAChD,MAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAEvC,MAAA,IAAI,GAAA,CAAI,SAAS,UAAA,EAAY;AAC3B,QAAA,MAAM,WAAA,GAAc,YAAA,CAAa,aAAA,EAAe,MAAM,CAAA;AACtD,QAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,WAAA,EAAa;AAAA,MAC3C;AAEA,MAAA,UAAA,CAAW,GAAA,EAAK,IAAA,EAAM,WAAA,EAAa,IAAA,CAAK,GAAG,CAAA;AAAA,IAC7C;AAAA,GACD,CAAA;AAED,EAAA,OAAO,GAAA,CAAI,QAAA;AACb;AAEA,SAAS,UAAA,CACP,GAAA,EACA,IAAA,EACA,WAAA,EACA,GAAA,EACM;AACN,EAAA,MAAM,KAAK,UAAA,CAAW;AAAA,IACpB,IAAA;AAAA,IACA,UAAU,GAAA,CAAI,QAAA;AAAA,IACd,SAAS,GAAA,CAAI;AAAA,GACd,CAAA;AAGD,EAAA,IAAI,GAAA,CAAI,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,EAAE,CAAA,EAAG;AAE3C,EAAA,GAAA,CAAI,SAAS,IAAA,CAAK;AAAA,IAChB,EAAA;AAAA,IACA,cAAA,EAAgB,IAAA;AAAA,IAChB,WAAA,EAAa,CAAA,EAAG,GAAA,CAAI,QAAQ,IAAI,WAAW,CAAA,CAAA;AAAA,IAC3C,UAAU,GAAA,CAAI,QAAA;AAAA,IACd,eAAe,GAAA,CAAI,aAAA;AAAA,IACnB,WAAA;AAAA,IACA,IAAA,EAAM,GAAA,EAAK,KAAA,CAAM,IAAA,IAAQ,CAAA;AAAA,IACzB,MAAA,EAAQ,GAAA,EAAK,KAAA,CAAM,MAAA,IAAU;AAAA,GAC9B,CAAA;AACH;AAEA,SAAS,kBAAkB,IAAA,EAA4C;AACrE,EAAA,IAAI,UAAU,IAAA,CAAK,UAAA;AACnB,EAAA,OAAO,OAAA,EAAS;AACd,IAAA,IAAI,OAAA,CAAQ,cAAa,EAAG;AAC1B,MAAA,OAAO,QAAQ,IAAA,CAAK,cAAA;AAAA,IACtB;AACA,IAAA,OAAA,GAAU,OAAA,CAAQ,UAAA;AAAA,EACpB;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,eAAe,OAAA,EAAsC;AAC5D,EAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB;AACzC,IAAA,OAAO,QAAQ,IAAA,CAAK,IAAA;AAAA,EACtB;AACA,EAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,IAAA,KAAS,qBAAA,EAAuB;AAC/C,IAAA,OAAO,CAAA,EAAG,aAAA,CAAc,OAAA,CAAQ,IAAI,CAAC,CAAA,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,cAAc,IAAA,EAAqC;AAC1D,EAAA,MAAM,MAAA,GACJ,IAAA,CAAK,MAAA,CAAO,IAAA,KAAS,eAAA,GACjB,KAAK,MAAA,CAAO,IAAA,GACZ,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA;AAC/B,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,IAAA,CAAK,SAAS,IAAI,CAAA,CAAA;AACxC;AAEA,SAAS,YAAA,CAAa,SAA8B,IAAA,EAAuB;AACzE,EAAA,OAAO,QAAQ,UAAA,CAAW,IAAA;AAAA,IACxB,CAAC,IAAA,KACC,IAAA,CAAK,IAAA,KAAS,cAAA,IACd,IAAA,CAAK,IAAA,CAAK,IAAA,KAAS,eAAA,IACnB,IAAA,CAAK,IAAA,CAAK,IAAA,KAAS;AAAA,GACvB;AACF;AAKA,SAAS,sBAAsB,MAAA,EAA6B;AAC1D,EAAA,MAAM,OAAA,uBAAc,GAAA,EAAY;AAChC,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,CAAM,QAAQ,CAAA,EAAA,EAAK;AACrC,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,CAAE,QAAA,CAAS,aAAa,CAAA,EAAG;AACpC,MAAA,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAC,CAAA;AAAA,IACnB;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT","file":"index.js","sourcesContent":["/**\n * Catalog generator — writes extracted messages to locale JSON files.\n */\n\nimport type { ExtractedMessage, Messages } from \"@better-intl/core\";\n\nexport interface CatalogFile {\n locale: string;\n messages: Messages;\n}\n\n/**\n * Generate a catalog for the default locale from extracted messages.\n */\nexport function generateDefaultCatalog(\n messages: ExtractedMessage[],\n): CatalogFile {\n const catalog: Messages = {};\n for (const msg of messages) {\n catalog[msg.id] = msg.defaultMessage;\n }\n return { locale: \"en\", messages: catalog };\n}\n\n/**\n * Merge new messages into an existing catalog, preserving existing translations.\n * Returns the merged catalog and lists of added/removed keys.\n */\nexport function mergeCatalog(\n existing: Messages,\n extracted: ExtractedMessage[],\n): {\n messages: Messages;\n added: string[];\n removed: string[];\n unchanged: string[];\n} {\n const newIds = new Set(extracted.map((m) => m.id));\n const existingIds = new Set(Object.keys(existing));\n\n const added: string[] = [];\n const removed: string[] = [];\n const unchanged: string[] = [];\n const messages: Messages = {};\n\n // Keep existing translations, mark new ones\n for (const msg of extracted) {\n if (existingIds.has(msg.id)) {\n messages[msg.id] = existing[msg.id];\n unchanged.push(msg.id);\n } else {\n messages[msg.id] = msg.defaultMessage;\n added.push(msg.id);\n }\n }\n\n // Track removed keys\n for (const id of existingIds) {\n if (!newIds.has(id)) {\n removed.push(id);\n }\n }\n\n return { messages, added, removed, unchanged };\n}\n\n/**\n * Find missing translations (keys present in default but not in target locale).\n */\nexport function findMissingKeys(\n defaultCatalog: Messages,\n targetCatalog: Messages,\n): string[] {\n return Object.keys(defaultCatalog).filter((id) => !(id in targetCatalog));\n}\n\n/**\n * Find dead keys (keys in catalog but not in extracted messages).\n */\nexport function findDeadKeys(\n catalog: Messages,\n extracted: ExtractedMessage[],\n): string[] {\n const extractedIds = new Set(extracted.map((m) => m.id));\n return Object.keys(catalog).filter((id) => !extractedIds.has(id));\n}\n","/**\n * AST-based extractor for JSX text nodes.\n *\n * Walks the Babel AST to find translatable text, respecting ignore rules,\n * and emits `ExtractedMessage` descriptors with stable IDs.\n */\n\nimport { parse } from \"@babel/parser\";\nimport type { NodePath } from \"@babel/traverse\";\nimport _traverse from \"@babel/traverse\";\nimport type * as t from \"@babel/types\";\nimport type { ExtractedMessage } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\n// Handle CJS/ESM interop for @babel/traverse\nconst traverse =\n typeof _traverse === \"function\"\n ? _traverse\n : (_traverse as { default: typeof _traverse }).default;\n\n/** Elements whose text content should never be extracted. */\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\n/** File patterns that should be skipped entirely. */\nconst IGNORED_FILE_PATTERNS = [\n /\\.test\\.[tj]sx?$/,\n /\\.spec\\.[tj]sx?$/,\n /\\.stories\\.[tj]sx?$/,\n /\\/__tests__\\//,\n];\n\nexport interface ExtractorOptions {\n filePath: string;\n /** Extraction mode: \"auto\" extracts all text, \"explicit\" only <T> and i18n prop */\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface ExtractionContext {\n componentName: string | undefined;\n messages: ExtractedMessage[];\n ignoredLines: Set<number>;\n filePath: string;\n mode: \"auto\" | \"explicit\";\n}\n\n/**\n * Extract translatable messages from a source file.\n */\nexport function extract(\n source: string,\n options: ExtractorOptions,\n): ExtractedMessage[] {\n const { filePath, mode = \"auto\" } = options;\n\n // Skip ignored file patterns\n if (IGNORED_FILE_PATTERNS.some((p) => p.test(filePath))) {\n return [];\n }\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n const ctx: ExtractionContext = {\n componentName: undefined,\n messages: [],\n ignoredLines: collectIgnoreComments(source),\n filePath,\n mode,\n };\n\n traverse(ast, {\n // Track component name for context\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n if (path.node.id) {\n ctx.componentName = path.node.id.name;\n }\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 ctx.componentName = path.node.id.name;\n }\n },\n\n // Extract text from JSX\n JSXText(path: NodePath<t.JSXText>) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const line = path.node.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n // In explicit mode, only extract from <T> or elements with i18n prop\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, path.node.loc);\n },\n\n // Also handle string literals in JSX expressions like {\"Hello\"}\n JSXExpressionContainer(path: NodePath<t.JSXExpressionContainer>) {\n const expr = path.node.expression;\n if (expr.type !== \"StringLiteral\") return;\n\n const text = expr.value.trim();\n if (!text) return;\n\n const line = expr.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, expr.loc);\n },\n });\n\n return ctx.messages;\n}\n\nfunction addMessage(\n ctx: ExtractionContext,\n text: string,\n elementType: string,\n loc: t.SourceLocation | null | undefined,\n): void {\n const id = generateId({\n text,\n filePath: ctx.filePath,\n context: ctx.componentName,\n });\n\n // Avoid duplicate IDs within the same file\n if (ctx.messages.some((m) => m.id === id)) return;\n\n ctx.messages.push({\n id,\n defaultMessage: text,\n description: `${ctx.filePath}:${elementType}`,\n filePath: ctx.filePath,\n componentName: ctx.componentName,\n elementType,\n line: loc?.start.line ?? 0,\n column: loc?.start.column ?? 0,\n });\n}\n\nfunction findParentElement(path: NodePath): t.JSXOpeningElement | null {\n let current = path.parentPath;\n while (current) {\n if (current.isJSXElement()) {\n return current.node.openingElement;\n }\n current = current.parentPath;\n }\n return null;\n}\n\nfunction getElementName(opening: t.JSXOpeningElement): string {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n if (opening.name.type === \"JSXMemberExpression\") {\n return `${getMemberName(opening.name)}`;\n }\n return \"unknown\";\n}\n\nfunction getMemberName(node: t.JSXMemberExpression): string {\n const object =\n node.object.type === \"JSXIdentifier\"\n ? node.object.name\n : getMemberName(node.object);\n return `${object}.${node.property.name}`;\n}\n\nfunction hasAttribute(opening: t.JSXOpeningElement, name: string): boolean {\n return opening.attributes.some(\n (attr) =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === name,\n );\n}\n\n/**\n * Collect line numbers that have `i18n-ignore` comments.\n */\nfunction collectIgnoreComments(source: string): Set<number> {\n const ignored = new Set<number>();\n const lines = source.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes(\"i18n-ignore\")) {\n ignored.add(i + 1); // 1-indexed\n }\n }\n return ignored;\n}\n"]}
1
+ {"version":3,"sources":["../src/catalog-generator.ts","../src/extractor.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Catalog generator — writes extracted messages to locale JSON files.\n */\n\nimport type { ExtractedMessage, Messages } from \"@better-intl/core\";\n\nexport interface CatalogFile {\n locale: string;\n messages: Messages;\n}\n\n/**\n * Generate a catalog for the default locale from extracted messages.\n */\nexport function generateDefaultCatalog(\n messages: ExtractedMessage[],\n): CatalogFile {\n const catalog: Messages = {};\n for (const msg of messages) {\n catalog[msg.id] = msg.defaultMessage;\n }\n return { locale: \"en\", messages: catalog };\n}\n\n/**\n * Merge new messages into an existing catalog, preserving existing translations.\n * Returns the merged catalog and lists of added/removed keys.\n */\nexport function mergeCatalog(\n existing: Messages,\n extracted: ExtractedMessage[],\n): {\n messages: Messages;\n added: string[];\n removed: string[];\n unchanged: string[];\n} {\n const newIds = new Set(extracted.map((m) => m.id));\n const existingIds = new Set(Object.keys(existing));\n\n const added: string[] = [];\n const removed: string[] = [];\n const unchanged: string[] = [];\n const messages: Messages = {};\n\n // Keep existing translations, mark new ones\n for (const msg of extracted) {\n if (existingIds.has(msg.id)) {\n messages[msg.id] = existing[msg.id];\n unchanged.push(msg.id);\n } else {\n messages[msg.id] = msg.defaultMessage;\n added.push(msg.id);\n }\n }\n\n // Track removed keys\n for (const id of existingIds) {\n if (!newIds.has(id)) {\n removed.push(id);\n }\n }\n\n return { messages, added, removed, unchanged };\n}\n\n/**\n * Find missing translations (keys present in default but not in target locale).\n */\nexport function findMissingKeys(\n defaultCatalog: Messages,\n targetCatalog: Messages,\n): string[] {\n return Object.keys(defaultCatalog).filter((id) => !(id in targetCatalog));\n}\n\n/**\n * Find dead keys (keys in catalog but not in extracted messages).\n */\nexport function findDeadKeys(\n catalog: Messages,\n extracted: ExtractedMessage[],\n): string[] {\n const extractedIds = new Set(extracted.map((m) => m.id));\n return Object.keys(catalog).filter((id) => !extractedIds.has(id));\n}\n","/**\n * AST-based extractor for JSX text nodes.\n *\n * Walks the Babel AST to find translatable text, respecting ignore rules,\n * and emits `ExtractedMessage` descriptors with stable IDs.\n */\n\nimport { parse } from \"@babel/parser\";\nimport type { NodePath } from \"@babel/traverse\";\nimport _traverse from \"@babel/traverse\";\nimport type * as t from \"@babel/types\";\nimport type { ExtractedMessage } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\n// Handle CJS/ESM interop for @babel/traverse\nconst traverse =\n typeof _traverse === \"function\"\n ? _traverse\n : (_traverse as { default: typeof _traverse }).default;\n\n/** Elements whose text content should never be extracted. */\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\n/** File patterns that should be skipped entirely. */\nconst IGNORED_FILE_PATTERNS = [\n /\\.test\\.[tj]sx?$/,\n /\\.spec\\.[tj]sx?$/,\n /\\.stories\\.[tj]sx?$/,\n /\\/__tests__\\//,\n];\n\nexport interface ExtractorOptions {\n filePath: string;\n /** Extraction mode: \"auto\" extracts all text, \"explicit\" only <T> and i18n prop */\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface ExtractionContext {\n componentName: string | undefined;\n messages: ExtractedMessage[];\n ignoredLines: Set<number>;\n filePath: string;\n mode: \"auto\" | \"explicit\";\n}\n\n/**\n * Extract translatable messages from a source file.\n */\nexport function extract(\n source: string,\n options: ExtractorOptions,\n): ExtractedMessage[] {\n const { filePath, mode = \"auto\" } = options;\n\n // Skip ignored file patterns\n if (IGNORED_FILE_PATTERNS.some((p) => p.test(filePath))) {\n return [];\n }\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n const ctx: ExtractionContext = {\n componentName: undefined,\n messages: [],\n ignoredLines: collectIgnoreComments(source),\n filePath,\n mode,\n };\n\n traverse(ast, {\n // Track component name for context\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n if (path.node.id) {\n ctx.componentName = path.node.id.name;\n }\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 ctx.componentName = path.node.id.name;\n }\n },\n\n // Extract interpolated messages from JSX elements with mixed text/expressions\n JSXElement(path: NodePath<t.JSXElement>) {\n const opening = path.node.openingElement;\n const elementName = getElementName(opening);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const line = opening.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(opening, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n const result = buildICUMessage(path.node.children);\n if (!result) return;\n\n addMessage(ctx, result.message, elementName, opening.loc);\n path.skip();\n },\n\n // Extract text from JSX\n JSXText(path: NodePath<t.JSXText>) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const line = path.node.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n // In explicit mode, only extract from <T> or elements with i18n prop\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, path.node.loc);\n },\n\n // Also handle string literals in JSX expressions like {\"Hello\"}\n JSXExpressionContainer(path: NodePath<t.JSXExpressionContainer>) {\n const expr = path.node.expression;\n if (expr.type !== \"StringLiteral\") return;\n\n const text = expr.value.trim();\n if (!text) return;\n\n const line = expr.loc?.start.line ?? 0;\n if (ctx.ignoredLines.has(line) || ctx.ignoredLines.has(line - 1)) return;\n\n const parentElement = findParentElement(path);\n if (!parentElement) return;\n\n const elementName = getElementName(parentElement);\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n if (ctx.mode === \"explicit\") {\n const hasI18nProp = hasAttribute(parentElement, \"i18n\");\n if (elementName !== \"T\" && !hasI18nProp) return;\n }\n\n addMessage(ctx, text, elementName, expr.loc);\n },\n });\n\n return ctx.messages;\n}\n\nfunction addMessage(\n ctx: ExtractionContext,\n text: string,\n elementType: string,\n loc: t.SourceLocation | null | undefined,\n): void {\n const id = generateId({\n text,\n filePath: ctx.filePath,\n context: ctx.componentName,\n });\n\n // Avoid duplicate IDs within the same file\n if (ctx.messages.some((m) => m.id === id)) return;\n\n ctx.messages.push({\n id,\n defaultMessage: text,\n description: `${ctx.filePath}:${elementType}`,\n filePath: ctx.filePath,\n componentName: ctx.componentName,\n elementType,\n line: loc?.start.line ?? 0,\n column: loc?.start.column ?? 0,\n });\n}\n\nfunction findParentElement(path: NodePath): t.JSXOpeningElement | null {\n let current = path.parentPath;\n while (current) {\n if (current.isJSXElement()) {\n return current.node.openingElement;\n }\n current = current.parentPath;\n }\n return null;\n}\n\nfunction getElementName(opening: t.JSXOpeningElement): string {\n if (opening.name.type === \"JSXIdentifier\") {\n return opening.name.name;\n }\n if (opening.name.type === \"JSXMemberExpression\") {\n return `${getMemberName(opening.name)}`;\n }\n return \"unknown\";\n}\n\nfunction getMemberName(node: t.JSXMemberExpression): string {\n const object =\n node.object.type === \"JSXIdentifier\"\n ? node.object.name\n : getMemberName(node.object);\n return `${object}.${node.property.name}`;\n}\n\nfunction hasAttribute(opening: t.JSXOpeningElement, name: string): boolean {\n return opening.attributes.some(\n (attr) =>\n attr.type === \"JSXAttribute\" &&\n attr.name.type === \"JSXIdentifier\" &&\n attr.name.name === name,\n );\n}\n\n/**\n * Build an ICU message from JSX element children, detecting interpolation.\n * Returns null if no variable expressions found, complex expressions exist,\n * or nested JSX elements are present.\n */\nfunction buildICUMessage(\n children: t.JSXElement[\"children\"],\n): { message: string; variables: string[] } | null {\n let hasVariable = false;\n const parts: string[] = [];\n const variables: string[] = [];\n\n for (const child of children) {\n if (child.type === \"JSXText\") {\n parts.push(child.value);\n } else if (child.type === \"JSXExpressionContainer\") {\n const expr = child.expression;\n if (expr.type === \"Identifier\") {\n parts.push(`{${expr.name}}`);\n variables.push(expr.name);\n hasVariable = true;\n } else if (expr.type === \"StringLiteral\") {\n parts.push(expr.value);\n } else if (expr.type === \"JSXEmptyExpression\") {\n continue;\n } else {\n return null;\n }\n } else {\n return null;\n }\n }\n\n if (!hasVariable) return null;\n\n const message = parts.join(\"\").replace(/\\s+/g, \" \").trim();\n if (!message) return null;\n\n // Skip messages that are only variables with no translatable text\n const withoutVars = message.replace(/\\{\\w+\\}/g, \"\").trim();\n if (!withoutVars) return null;\n\n return { message, variables };\n}\n\n/**\n * Collect line numbers that have `i18n-ignore` comments.\n */\nfunction collectIgnoreComments(source: string): Set<number> {\n const ignored = new Set<number>();\n const lines = source.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n if (lines[i].includes(\"i18n-ignore\")) {\n ignored.add(i + 1); // 1-indexed\n }\n }\n return ignored;\n}\n"]}
package/dist/loader.cjs CHANGED
@@ -65,6 +65,52 @@ function transform(source, options) {
65
65
  componentName = path.node.id.name;
66
66
  }
67
67
  },
68
+ // Handle interpolated JSX elements (text mixed with expressions)
69
+ JSXElement(path) {
70
+ const opening = path.node.openingElement;
71
+ const elName = getJSXName(opening);
72
+ if (IGNORED_ELEMENTS.has(elName)) return;
73
+ const elLine = opening.loc?.start.line ?? 0;
74
+ if (hasIgnoreComment(source, elLine)) return;
75
+ if (mode === "explicit") {
76
+ const hasI18n = opening.attributes.some(
77
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "i18n"
78
+ );
79
+ if (elName !== "T" && !hasI18n) return;
80
+ }
81
+ const result = buildICUMessage(path.node.children);
82
+ if (!result) return;
83
+ const { message, variables } = result;
84
+ const id = core.generateId({ text: message, filePath, context: componentName });
85
+ hasTranslations = true;
86
+ needsImport = true;
87
+ const valuesObj = t__namespace.objectExpression(
88
+ variables.map(
89
+ (name) => t__namespace.objectProperty(
90
+ t__namespace.identifier(name),
91
+ t__namespace.identifier(name),
92
+ false,
93
+ true
94
+ )
95
+ )
96
+ );
97
+ path.node.children = [
98
+ t__namespace.jsxExpressionContainer(
99
+ t__namespace.callExpression(t__namespace.identifier(tFunctionName), [
100
+ t__namespace.stringLiteral(id),
101
+ valuesObj,
102
+ t__namespace.stringLiteral(message)
103
+ ])
104
+ )
105
+ ];
106
+ const fnPath = path.findParent(
107
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
108
+ );
109
+ if (fnPath) {
110
+ functionsNeedingHook.add(fnPath.node);
111
+ }
112
+ path.skip();
113
+ },
68
114
  JSXText(path) {
69
115
  const text = path.node.value.trim();
70
116
  if (!text) return;
@@ -196,6 +242,37 @@ function hasIgnoreComment(source, line) {
196
242
  const prevLine = lines[line - 2];
197
243
  return prevLine ? prevLine.includes("i18n-ignore") : false;
198
244
  }
245
+ function buildICUMessage(children) {
246
+ let hasVariable = false;
247
+ const parts = [];
248
+ const variables = [];
249
+ for (const child of children) {
250
+ if (child.type === "JSXText") {
251
+ parts.push(child.value);
252
+ } else if (child.type === "JSXExpressionContainer") {
253
+ const expr = child.expression;
254
+ if (expr.type === "Identifier") {
255
+ parts.push(`{${expr.name}}`);
256
+ variables.push(expr.name);
257
+ hasVariable = true;
258
+ } else if (expr.type === "StringLiteral") {
259
+ parts.push(expr.value);
260
+ } else if (expr.type === "JSXEmptyExpression") {
261
+ continue;
262
+ } else {
263
+ return null;
264
+ }
265
+ } else {
266
+ return null;
267
+ }
268
+ }
269
+ if (!hasVariable) return null;
270
+ const message = parts.join("").replace(/\s+/g, " ").trim();
271
+ if (!message) return null;
272
+ const withoutVars = message.replace(/\{\w+\}/g, "").trim();
273
+ if (!withoutVars) return null;
274
+ return { message, variables };
275
+ }
199
276
 
200
277
  // src/loader.ts
201
278
  function betterIntlLoader(source) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transformer.ts","../src/loader.ts"],"names":["_traverse","_generate","parse","t","generateId","injectTranslation","relative"],"mappings":"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","file":"loader.cjs","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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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 * Turbopack/Webpack loader for better-intl.\n *\n * Transforms JSX text nodes into t() calls automatically during build.\n * Detects \"use client\" to choose between useTranslation (client) and rsc() (server).\n *\n * Used by @better-intl/next plugin via turbopack.rules.\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: loader context type\nexport default function betterIntlLoader(this: any, source: string): string {\n const options: BetterIntlLoaderOptions = this.getOptions?.() ?? {};\n const absolutePath: string = this.resourcePath;\n\n // Skip node_modules and non-JSX files\n if (absolutePath.includes(\"node_modules\")) return source;\n if (!/\\.[jt]sx$/.test(absolutePath)) return source;\n\n // Skip test/spec/stories files\n if (/\\.(test|spec|stories)\\.[jt]sx?$/.test(absolutePath)) return source;\n\n const filePath = relative(this.rootContext || process.cwd(), absolutePath);\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 return result.hasTranslations ? result.code : source;\n } catch {\n return source;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/transformer.ts","../src/loader.ts"],"names":["_traverse","_generate","parse","t","generateId","injectTranslation","relative"],"mappings":"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","file":"loader.cjs","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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 // Handle interpolated JSX elements (text mixed with expressions)\n JSXElement(path: NodePath<t.JSXElement>) {\n const opening = path.node.openingElement;\n const elName = getJSXName(opening);\n if (IGNORED_ELEMENTS.has(elName)) return;\n\n const elLine = opening.loc?.start.line ?? 0;\n if (hasIgnoreComment(source, elLine)) return;\n\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a) =>\n a.type === \"JSXAttribute\" &&\n a.name.type === \"JSXIdentifier\" &&\n a.name.name === \"i18n\",\n );\n if (elName !== \"T\" && !hasI18n) return;\n }\n\n const result = buildICUMessage(path.node.children);\n if (!result) return;\n\n const { message, variables } = result;\n const id = generateId({ text: message, filePath, context: componentName });\n\n hasTranslations = true;\n needsImport = true;\n\n const valuesObj = t.objectExpression(\n variables.map((name) =>\n t.objectProperty(\n t.identifier(name),\n t.identifier(name),\n false,\n true,\n ),\n ),\n );\n\n path.node.children = [\n t.jsxExpressionContainer(\n t.callExpression(t.identifier(tFunctionName), [\n t.stringLiteral(id),\n valuesObj,\n t.stringLiteral(message),\n ]),\n ),\n ];\n\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 path.skip();\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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/**\n * Build an ICU message from JSX element children, detecting interpolation.\n */\nfunction buildICUMessage(\n children: t.JSXElement[\"children\"],\n): { message: string; variables: string[] } | null {\n let hasVariable = false;\n const parts: string[] = [];\n const variables: string[] = [];\n\n for (const child of children) {\n if (child.type === \"JSXText\") {\n parts.push(child.value);\n } else if (child.type === \"JSXExpressionContainer\") {\n const expr = child.expression;\n if (expr.type === \"Identifier\") {\n parts.push(`{${expr.name}}`);\n variables.push(expr.name);\n hasVariable = true;\n } else if (expr.type === \"StringLiteral\") {\n parts.push(expr.value);\n } else if (expr.type === \"JSXEmptyExpression\") {\n continue;\n } else {\n return null;\n }\n } else {\n return null;\n }\n }\n\n if (!hasVariable) return null;\n\n const message = parts.join(\"\").replace(/\\s+/g, \" \").trim();\n if (!message) return null;\n\n const withoutVars = message.replace(/\\{\\w+\\}/g, \"\").trim();\n if (!withoutVars) return null;\n\n return { message, variables };\n}\n","/**\n * Turbopack/Webpack loader for better-intl.\n *\n * Transforms JSX text nodes into t() calls automatically during build.\n * Detects \"use client\" to choose between useTranslation (client) and rsc() (server).\n *\n * Used by @better-intl/next plugin via turbopack.rules.\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: loader context type\nexport default function betterIntlLoader(this: any, source: string): string {\n const options: BetterIntlLoaderOptions = this.getOptions?.() ?? {};\n const absolutePath: string = this.resourcePath;\n\n // Skip node_modules and non-JSX files\n if (absolutePath.includes(\"node_modules\")) return source;\n if (!/\\.[jt]sx$/.test(absolutePath)) return source;\n\n // Skip test/spec/stories files\n if (/\\.(test|spec|stories)\\.[jt]sx?$/.test(absolutePath)) return source;\n\n const filePath = relative(this.rootContext || process.cwd(), absolutePath);\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 return result.hasTranslations ? result.code : source;\n } catch {\n return source;\n }\n}\n"]}
package/dist/loader.js CHANGED
@@ -1,4 +1,4 @@
1
- import { transform } from './chunk-MDYQVJ3W.js';
1
+ import { transform } from './chunk-NB4KZKAG.js';
2
2
  import { relative } from 'path';
3
3
 
4
4
  function betterIntlLoader(source) {
@@ -65,6 +65,52 @@ function transform(source, options) {
65
65
  componentName = path.node.id.name;
66
66
  }
67
67
  },
68
+ // Handle interpolated JSX elements (text mixed with expressions)
69
+ JSXElement(path) {
70
+ const opening = path.node.openingElement;
71
+ const elName = getJSXName(opening);
72
+ if (IGNORED_ELEMENTS.has(elName)) return;
73
+ const elLine = opening.loc?.start.line ?? 0;
74
+ if (hasIgnoreComment(source, elLine)) return;
75
+ if (mode === "explicit") {
76
+ const hasI18n = opening.attributes.some(
77
+ (a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === "i18n"
78
+ );
79
+ if (elName !== "T" && !hasI18n) return;
80
+ }
81
+ const result = buildICUMessage(path.node.children);
82
+ if (!result) return;
83
+ const { message, variables } = result;
84
+ const id = core.generateId({ text: message, filePath, context: componentName });
85
+ hasTranslations = true;
86
+ needsImport = true;
87
+ const valuesObj = t__namespace.objectExpression(
88
+ variables.map(
89
+ (name) => t__namespace.objectProperty(
90
+ t__namespace.identifier(name),
91
+ t__namespace.identifier(name),
92
+ false,
93
+ true
94
+ )
95
+ )
96
+ );
97
+ path.node.children = [
98
+ t__namespace.jsxExpressionContainer(
99
+ t__namespace.callExpression(t__namespace.identifier(tFunctionName), [
100
+ t__namespace.stringLiteral(id),
101
+ valuesObj,
102
+ t__namespace.stringLiteral(message)
103
+ ])
104
+ )
105
+ ];
106
+ const fnPath = path.findParent(
107
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
108
+ );
109
+ if (fnPath) {
110
+ functionsNeedingHook.add(fnPath.node);
111
+ }
112
+ path.skip();
113
+ },
68
114
  JSXText(path) {
69
115
  const text = path.node.value.trim();
70
116
  if (!text) return;
@@ -196,6 +242,37 @@ function hasIgnoreComment(source, line) {
196
242
  const prevLine = lines[line - 2];
197
243
  return prevLine ? prevLine.includes("i18n-ignore") : false;
198
244
  }
245
+ function buildICUMessage(children) {
246
+ let hasVariable = false;
247
+ const parts = [];
248
+ const variables = [];
249
+ for (const child of children) {
250
+ if (child.type === "JSXText") {
251
+ parts.push(child.value);
252
+ } else if (child.type === "JSXExpressionContainer") {
253
+ const expr = child.expression;
254
+ if (expr.type === "Identifier") {
255
+ parts.push(`{${expr.name}}`);
256
+ variables.push(expr.name);
257
+ hasVariable = true;
258
+ } else if (expr.type === "StringLiteral") {
259
+ parts.push(expr.value);
260
+ } else if (expr.type === "JSXEmptyExpression") {
261
+ continue;
262
+ } else {
263
+ return null;
264
+ }
265
+ } else {
266
+ return null;
267
+ }
268
+ }
269
+ if (!hasVariable) return null;
270
+ const message = parts.join("").replace(/\s+/g, " ").trim();
271
+ if (!message) return null;
272
+ const withoutVars = message.replace(/\{\w+\}/g, "").trim();
273
+ if (!withoutVars) return null;
274
+ return { message, variables };
275
+ }
199
276
 
200
277
  // src/webpack-loader.ts
201
278
  function betterIntlLoader(source) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/transformer.ts","../src/webpack-loader.ts"],"names":["_traverse","_generate","parse","t","generateId","injectTranslation","relative"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA,IAAM,QAAA,GACJ,OAAOA,0BAAA,KAAc,UAAA,GACjBA,6BACCA,0BAAA,CAA4C,OAAA;AAEnD,IAAM,QAAA,GACJ,OAAOC,0BAAA,KAAc,UAAA,GACjBA,6BACCA,0BAAA,CAA4C,OAAA;AAEnD,IAAM,gBAAA,uBAAuB,GAAA,CAAI,CAAC,UAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAsBtE,SAAS,SAAA,CACd,QACA,OAAA,EACiB;AACjB,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,MAAA;AAAA,IACA,QAAA;AAAA,IACA,aAAA,GAAgB,KAAA;AAAA,IAChB,aAAA,GAAgB,oBAAA;AAAA,IAChB,SAAA,GAAY,uBAAA;AAAA,IACZ,IAAA,GAAO;AAAA,GACT,GAAI,OAAA;AAEJ,EAAA,MAAM,GAAA,GAAMC,aAAM,MAAA,EAAQ;AAAA,IACxB,UAAA,EAAY,QAAA;AAAA,IACZ,OAAA,EAAS,CAAC,KAAA,EAAO,YAAY;AAAA,GAC9B,CAAA;AAGD,EAAA,MAAM,iBAAA,GACJ,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,KAAK,CAAC,CAAA,KAAM,CAAA,CAAE,KAAA,CAAM,KAAA,KAAU,YAAY,CAAA,IAClE,GAAA,CAAI,QAAQ,IAAA,CAAK,IAAA;AAAA,IACf,CAAC,IAAA,KACGC,YAAA,CAAA,qBAAA,CAAsB,IAAI,CAAA,IAC1BA,YAAA,CAAA,eAAA,CAAgB,IAAA,CAAK,UAAU,CAAA,IACjC,IAAA,CAAK,UAAA,CAAW,KAAA,KAAU;AAAA,GAC9B;AAEF,EAAA,IAAI,eAAA,GAAkB,KAAA;AACtB,EAAA,IAAI,aAAA;AACJ,EAAA,IAAI,WAAA,GAAc,KAAA;AAGlB,EAAA,MAAM,oBAAA,uBAA2B,GAAA,EAAY;AAE7C,EAAA,QAAA,CAAS,GAAA,EAAK;AAAA,IACZ,oBAAoB,IAAA,EAAuC;AACzD,MAAA,IAAI,KAAK,IAAA,CAAK,EAAA,EAAI,aAAA,GAAgB,IAAA,CAAK,KAAK,EAAA,CAAG,IAAA;AAAA,IACjD,CAAA;AAAA,IAEA,mBAAmB,IAAA,EAAsC;AACvD,MAAA,IACE,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA,KAAS,iBACrB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,IAAA,KAAS,yBAAA,IACxB,IAAA,CAAK,IAAA,CAAK,IAAA,EAAM,SAAS,oBAAA,CAAA,EAC3B;AACA,QAAA,aAAA,GAAgB,IAAA,CAAK,KAAK,EAAA,CAAG,IAAA;AAAA,MAC/B;AAAA,IACF,CAAA;AAAA,IAEA,QAAQ,IAAA,EAA2B;AACjC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAClC,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AACpB,MAAA,IAAI,CAAC,MAAA,EAAQ,YAAA,EAAa,EAAG;AAE7B,MAAA,MAAM,WAAA,GAAc,UAAA,CAAW,MAAA,CAAO,IAAA,CAAK,cAAc,CAAA;AACzD,MAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAGvC,MAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAA,IAAQ,CAAA;AAC1C,MAAA,IAAI,gBAAA,CAAiB,MAAA,EAAQ,IAAI,CAAA,EAAG;AAGpC,MAAA,IAAI,SAAS,UAAA,EAAY;AACvB,QAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,cAAA,CAAe,UAAA,CAAW,IAAA;AAAA,UACpD,CAAC,CAAA,KACC,CAAA,CAAE,IAAA,KAAS,cAAA,IACX,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,eAAA,IAChB,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS;AAAA,SACpB;AACA,QAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,OAAA,EAAS;AAAA,MACvC;AAEA,MAAA,MAAM,KAAKC,eAAA,CAAW,EAAE,MAAM,QAAA,EAAU,OAAA,EAAS,eAAe,CAAA;AAChE,MAAA,eAAA,GAAkB,IAAA;AAElB,MAAA,IAAI,MAAA,IAAU,QAAA,GAAW,EAAE,CAAA,EAAG;AAE5B,QAAA,MAAM,OAAA,GAAYD,YAAA,CAAA,OAAA,CAAQ,QAAA,CAAS,EAAE,CAAC,CAAA;AACtC,QAAA,IAAA,CAAK,YAAY,OAAO,CAAA;AACxB,QAAA,IAAA,CAAK,IAAA,EAAK;AAAA,MACZ,CAAA,MAAO;AAEL,QAAA,WAAA,GAAc,IAAA;AACd,QAAA,IAAA,CAAK,WAAA;AAAA,UACDA,YAAA,CAAA,sBAAA;AAAA,YACEA,YAAA,CAAA,cAAA,CAAiBA,YAAA,CAAA,UAAA,CAAW,aAAa,CAAA,EAAG;AAAA,cAC1CA,2BAAc,EAAE,CAAA;AAAA,cAChBA,wBAAW,WAAW,CAAA;AAAA,cACtBA,2BAAc,IAAI;AAAA,aACrB;AAAA;AACH,SACF;AAGA,QAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AAAA,UAClB,CAAC,MACC,CAAA,CAAE,qBAAA,MACF,CAAA,CAAE,oBAAA,EAAqB,IACvB,CAAA,CAAE,yBAAA;AAA0B,SAChC;AACA,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,oBAAA,CAAqB,GAAA,CAAI,OAAO,IAAI,CAAA;AAAA,QACtC;AAAA,MACF;AAAA,IACF;AAAA,GACD,CAAA;AAGD,EAAA,IAAI,WAAA,EAAa;AAaf,IAAA,IAASE,kBAAAA,GAAT,SACE,IAAA,EAGA;AACA,MAAA,IAAI,CAAC,oBAAA,CAAqB,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAG;AAI1C,MAAA,MAAM,SAAA,GAAY,iBAAA,GACZF,YAAA,CAAA,mBAAA,CAAoB,OAAA,EAAS;AAAA,QAC3BA,YAAA,CAAA,kBAAA;AAAA,UACEA,YAAA,CAAA,aAAA,CAAc;AAAA,YACZA,YAAA,CAAA,cAAA;AAAA,cACEA,wBAAW,GAAG,CAAA;AAAA,cACdA,wBAAW,aAAa,CAAA;AAAA,cAC1B,KAAA;AAAA,cACA;AAAA;AACF,WACD,CAAA;AAAA,UACCA,YAAA,CAAA,cAAA,CAAiBA,YAAA,CAAA,UAAA,CAAW,gBAAgB,CAAA,EAAG,EAAE;AAAA;AACrD,OACD,CAAA,GACCA,YAAA,CAAA,mBAAA,CAAoB,OAAA,EAAS;AAAA,QAC3BA,YAAA,CAAA,kBAAA;AAAA,UACEA,wBAAW,aAAa,CAAA;AAAA,UACxBA,YAAA,CAAA,cAAA,CAAiBA,YAAA,CAAA,UAAA,CAAW,KAAK,CAAA,EAAG,EAAE;AAAA;AAC1C,OACD,CAAA;AAEL,MAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACvB,MAAA,IAAMA,YAAA,CAAA,gBAAA,CAAiB,IAAI,CAAA,EAAG;AAC5B,QAAA,IAAA,CAAK,IAAA,CAAK,QAAQ,SAAS,CAAA;AAAA,MAC7B,CAAA,MAAO;AACL,QAAA,IAAA,CAAK,IAAA,CAAK,OAASA,YAAA,CAAA,cAAA,CAAe,CAAC,WAAaA,YAAA,CAAA,eAAA,CAAgB,IAAI,CAAC,CAAC,CAAA;AAAA,MACxE;AAAA,IACF,CAAA;AAhDA,IAAA,QAAA,CAAS,GAAA,EAAK;AAAA,MACZ,oBAAoB,IAAA,EAAuC;AACzD,QAAAE,mBAAkB,IAAI,CAAA;AAAA,MACxB,CAAA;AAAA,MACA,mBAAmB,IAAA,EAAsC;AACvD,QAAAA,mBAAkB,IAAI,CAAA;AAAA,MACxB,CAAA;AAAA,MACA,wBAAwB,IAAA,EAA2C;AACjE,QAAAA,mBAAkB,IAAI,CAAA;AAAA,MACxB;AAAA,KACD,CAAA;AAwCD,IAAA,IAAI,iBAAA,EAAmB;AAErB,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,IAAA,CAAK,IAAA;AAAA,QACjC,CAAC,IAAA,KACGF,YAAA,CAAA,mBAAA,CAAoB,IAAI,CAAA,IAC1B,KAAK,MAAA,CAAO,KAAA,KAAU,aAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,UACd,CAAC,CAAA,KACGA,YAAA,CAAA,iBAAA,CAAkB,CAAC,CAAA,IACnBA,YAAA,CAAA,YAAA,CAAa,CAAA,CAAE,QAAQ,CAAA,IACzB,CAAA,CAAE,QAAA,CAAS,IAAA,KAAS;AAAA;AACxB,OACJ;AAEA,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAC,GAAA,CAAI,QAAQ,IAAA,CAAuB,OAAA;AAAA,UAChCA,YAAA,CAAA,iBAAA;AAAA,YACA;AAAA,cACIA,YAAA,CAAA,eAAA;AAAA,gBACEA,wBAAW,gBAAgB,CAAA;AAAA,gBAC3BA,wBAAW,gBAAgB;AAAA;AAC/B,aACF;AAAA,YACEA,2BAAc,aAAa;AAAA;AAC/B,SACF;AAAA,MACF;AAAA,IACF,CAAA,MAAO;AAEL,MAAA,MAAM,SAAA,GAAY,GAAA,CAAI,OAAA,CAAQ,IAAA,CAAK,IAAA;AAAA,QACjC,CAAC,IAAA,KACGA,YAAA,CAAA,mBAAA,CAAoB,IAAI,CAAA,IAC1B,KAAK,MAAA,CAAO,KAAA,KAAU,SAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,UACd,CAAC,CAAA,KACGA,YAAA,CAAA,iBAAA,CAAkB,CAAC,CAAA,IACnBA,YAAA,CAAA,YAAA,CAAa,CAAA,CAAE,QAAQ,CAAA,IACzB,CAAA,CAAE,QAAA,CAAS,IAAA,KAAS;AAAA;AACxB,OACJ;AAEA,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAC,GAAA,CAAI,QAAQ,IAAA,CAAuB,OAAA;AAAA,UAChCA,YAAA,CAAA,iBAAA;AAAA,YACA,CAAGA,6BAAkBA,YAAA,CAAA,UAAA,CAAW,KAAK,GAAKA,YAAA,CAAA,UAAA,CAAW,KAAK,CAAC,CAAC,CAAA;AAAA,YAC1DA,2BAAc,SAAS;AAAA;AAC3B,SACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,SAAS,GAAA,EAAK,EAAE,YAAY,IAAA,EAAM,cAAA,EAAgB,UAAU,CAAA;AAE3E,EAAA,OAAO;AAAA,IACL,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,KAAK,MAAA,CAAO,GAAA;AAAA,IACZ;AAAA,GACF;AACF;AAEA,SAAS,WAAW,OAAA,EAAsC;AACxD,EAAA,IAAI,QAAQ,IAAA,CAAK,IAAA,KAAS,eAAA,EAAiB,OAAO,QAAQ,IAAA,CAAK,IAAA;AAC/D,EAAA,OAAO,SAAA;AACT;AAEA,SAAS,gBAAA,CAAiB,QAAgB,IAAA,EAAuB;AAC/D,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,IAAI,CAAA;AAC/B,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,IAAA,GAAO,CAAC,CAAA;AAC/B,EAAA,OAAO,QAAA,GAAW,QAAA,CAAS,QAAA,CAAS,aAAa,CAAA,GAAI,KAAA;AACvD;;;AC1Qe,SAAR,iBAA6C,MAAA,EAAwB;AAC1E,EAAA,MAAM,OAAA,GAAmC,IAAA,CAAK,UAAA,IAAa,IAAK,EAAC;AACjE,EAAA,MAAM,eAAe,IAAA,CAAK,YAAA;AAC1B,EAAA,MAAM,WAAWG,aAAA,CAAS,IAAA,CAAK,eAAe,OAAA,CAAQ,GAAA,IAAO,YAAY,CAAA;AAGzE,EAAA,IAAI,YAAA,CAAa,QAAA,CAAS,cAAc,CAAA,EAAG,OAAO,MAAA;AAClD,EAAA,IAAI,CAAC,WAAA,CAAY,IAAA,CAAK,QAAQ,GAAG,OAAO,MAAA;AAGxC,EAAA,IAAI,iCAAA,CAAkC,IAAA,CAAK,QAAQ,CAAA,EAAG,OAAO,MAAA;AAE7D,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,UAAU,MAAA,EAAQ;AAAA,MAC/B,QAAA;AAAA,MACA,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,IAAA,EAAM,QAAQ,IAAA,IAAQ;AAAA,KACvB,CAAA;AAED,IAAA,IAAI,OAAO,eAAA,EAAiB;AAC1B,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,CAAA,0BAAA,EAA6B,QAAQ,CAAA,EAAA,EAAK,MAAA,CAAO,eAAe,CAAA,CAAA;AAAA,OAClE;AACA,MAAA,OAAO,MAAA,CAAO,IAAA;AAAA,IAChB;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,iCAAA,EAAoC,QAAQ,CAAA,CAAA,CAAA,EAAK,GAAG,CAAA;AAClE,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"webpack-loader.cjs","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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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"]}
1
+ {"version":3,"sources":["../src/transformer.ts","../src/webpack-loader.ts"],"names":["_traverse","_generate","parse","t","generateId","injectTranslation","relative"],"mappings":"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","file":"webpack-loader.cjs","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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 // Handle interpolated JSX elements (text mixed with expressions)\n JSXElement(path: NodePath<t.JSXElement>) {\n const opening = path.node.openingElement;\n const elName = getJSXName(opening);\n if (IGNORED_ELEMENTS.has(elName)) return;\n\n const elLine = opening.loc?.start.line ?? 0;\n if (hasIgnoreComment(source, elLine)) return;\n\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a) =>\n a.type === \"JSXAttribute\" &&\n a.name.type === \"JSXIdentifier\" &&\n a.name.name === \"i18n\",\n );\n if (elName !== \"T\" && !hasI18n) return;\n }\n\n const result = buildICUMessage(path.node.children);\n if (!result) return;\n\n const { message, variables } = result;\n const id = generateId({ text: message, filePath, context: componentName });\n\n hasTranslations = true;\n needsImport = true;\n\n const valuesObj = t.objectExpression(\n variables.map((name) =>\n t.objectProperty(\n t.identifier(name),\n t.identifier(name),\n false,\n true,\n ),\n ),\n );\n\n path.node.children = [\n t.jsxExpressionContainer(\n t.callExpression(t.identifier(tFunctionName), [\n t.stringLiteral(id),\n valuesObj,\n t.stringLiteral(message),\n ]),\n ),\n ];\n\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 path.skip();\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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/**\n * Build an ICU message from JSX element children, detecting interpolation.\n */\nfunction buildICUMessage(\n children: t.JSXElement[\"children\"],\n): { message: string; variables: string[] } | null {\n let hasVariable = false;\n const parts: string[] = [];\n const variables: string[] = [];\n\n for (const child of children) {\n if (child.type === \"JSXText\") {\n parts.push(child.value);\n } else if (child.type === \"JSXExpressionContainer\") {\n const expr = child.expression;\n if (expr.type === \"Identifier\") {\n parts.push(`{${expr.name}}`);\n variables.push(expr.name);\n hasVariable = true;\n } else if (expr.type === \"StringLiteral\") {\n parts.push(expr.value);\n } else if (expr.type === \"JSXEmptyExpression\") {\n continue;\n } else {\n return null;\n }\n } else {\n return null;\n }\n }\n\n if (!hasVariable) return null;\n\n const message = parts.join(\"\").replace(/\\s+/g, \" \").trim();\n if (!message) return null;\n\n const withoutVars = message.replace(/\\{\\w+\\}/g, \"\").trim();\n if (!withoutVars) return null;\n\n return { message, variables };\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"]}
@@ -1,4 +1,4 @@
1
- export { betterIntlLoader as default } from './chunk-SF5H7SHR.js';
2
- import './chunk-MDYQVJ3W.js';
1
+ export { betterIntlLoader as default } from './chunk-M4HZ675W.js';
2
+ import './chunk-NB4KZKAG.js';
3
3
  //# sourceMappingURL=webpack-loader.js.map
4
4
  //# sourceMappingURL=webpack-loader.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-intl/compiler",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "AST extractor and Babel/SWC transform for better-intl",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -37,7 +37,7 @@
37
37
  "@babel/parser": "^7.26.0",
38
38
  "@babel/traverse": "^7.26.0",
39
39
  "@babel/types": "^7.26.0",
40
- "@better-intl/core": "0.2.0"
40
+ "@better-intl/core": "0.4.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@babel/core": "^7.26.0",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/transformer.ts"],"names":["injectTranslation"],"mappings":"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","file":"chunk-MDYQVJ3W.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 client runtime (default: `@better-intl/react`) */\n runtimeImport?: string;\n /** Import path for the RSC runtime (default: `@better-intl/next/rsc`) */\n rscImport?: 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 rscImport = \"@better-intl/next/rsc\",\n mode = \"auto\",\n } = options;\n\n const ast = parse(source, {\n sourceType: \"module\",\n plugins: [\"jsx\", \"typescript\"],\n });\n\n // Detect \"use client\" directive\n const isClientComponent =\n ast.program.directives?.some((d) => d.value.value === \"use client\") ??\n ast.program.body.some(\n (node) =>\n t.isExpressionStatement(node) &&\n t.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\",\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 translation accessor at the top of each component\n if (needsImport) {\n traverse(ast, {\n FunctionDeclaration(path: NodePath<t.FunctionDeclaration>) {\n injectTranslation(path);\n },\n FunctionExpression(path: NodePath<t.FunctionExpression>) {\n injectTranslation(path);\n },\n ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {\n injectTranslation(path);\n },\n });\n\n function injectTranslation(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n ) {\n if (!functionsNeedingHook.has(path.node)) return;\n\n // Client: const { t: __t } = useTranslation();\n // Server: const __t = rsc();\n const statement = isClientComponent\n ? 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 : t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.identifier(tFunctionName),\n t.callExpression(t.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (t.isBlockStatement(body)) {\n body.body.unshift(statement);\n } else {\n path.node.body = t.blockStatement([statement, t.returnStatement(body)]);\n }\n }\n\n if (isClientComponent) {\n // Add import { useTranslation } from \"@better-intl/react\"\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 (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(runtimeImport),\n ),\n );\n }\n } else {\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = ast.program.body.some(\n (node) =>\n t.isImportDeclaration(node) &&\n node.source.value === rscImport &&\n node.specifiers.some(\n (s) =>\n t.isImportSpecifier(s) &&\n t.isIdentifier(s.imported) &&\n s.imported.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n (ast.program.body as t.Statement[]).unshift(\n t.importDeclaration(\n [t.importSpecifier(t.identifier(\"rsc\"), t.identifier(\"rsc\"))],\n t.stringLiteral(rscImport),\n ),\n );\n }\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"]}