@better-intl/compiler 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/babel.cjs CHANGED
@@ -5,6 +5,17 @@ var core = require('@better-intl/core');
5
5
 
6
6
  // src/babel.ts
7
7
  var IGNORED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre", "Helmet"]);
8
+ function hasUseClientDirective(types, body) {
9
+ for (const node of body) {
10
+ if (types.isExpressionStatement(node) && types.isStringLiteral(node.expression) && node.expression.value === "use client") {
11
+ return true;
12
+ }
13
+ if (!types.isExpressionStatement(node) || !types.isStringLiteral(node.expression)) {
14
+ break;
15
+ }
16
+ }
17
+ return false;
18
+ }
8
19
  function betterIntlBabelPlugin({
9
20
  types
10
21
  }) {
@@ -12,43 +23,86 @@ function betterIntlBabelPlugin({
12
23
  name: "better-intl",
13
24
  visitor: {
14
25
  Program: {
15
- enter(_path, state) {
26
+ enter(path, state) {
16
27
  state.functionsNeedingHook = /* @__PURE__ */ new Set();
28
+ state.isClientComponent = hasUseClientDirective(
29
+ types,
30
+ path.node.body
31
+ );
17
32
  },
18
33
  exit(path, state) {
19
34
  if (!state.needsImport) return;
20
35
  const tName = state.opts.tFunctionName ?? "__t";
21
- const importSource = state.opts.runtimeImport ?? "@better-intl/react";
22
- path.traverse({
23
- FunctionDeclaration(fnPath) {
24
- if (!state.functionsNeedingHook?.has(fnPath.node)) return;
25
- injectHook(types, fnPath, tName);
26
- },
27
- FunctionExpression(fnPath) {
28
- if (!state.functionsNeedingHook?.has(fnPath.node)) return;
29
- injectHook(types, fnPath, tName);
30
- },
31
- ArrowFunctionExpression(fnPath) {
32
- if (!state.functionsNeedingHook?.has(fnPath.node)) return;
33
- injectHook(types, fnPath, tName);
34
- }
35
- });
36
- const hasImport = path.node.body.some(
37
- (node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
38
- (specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "useTranslation"
39
- )
40
- );
41
- if (!hasImport) {
42
- const importDecl = types.importDeclaration(
43
- [
44
- types.importSpecifier(
45
- types.identifier("useTranslation"),
46
- types.identifier("useTranslation")
36
+ if (state.isClientComponent) {
37
+ const importSource = state.opts.runtimeImport ?? "@better-intl/react";
38
+ path.traverse({
39
+ FunctionDeclaration(fnPath) {
40
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
41
+ injectClientHook(types, fnPath, tName);
42
+ },
43
+ FunctionExpression(fnPath) {
44
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
45
+ injectClientHook(types, fnPath, tName);
46
+ },
47
+ ArrowFunctionExpression(fnPath) {
48
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
49
+ injectClientHook(types, fnPath, tName);
50
+ }
51
+ });
52
+ const hasImport = path.node.body.some(
53
+ (node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
54
+ (specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "useTranslation"
55
+ )
56
+ );
57
+ if (!hasImport) {
58
+ path.unshiftContainer(
59
+ "body",
60
+ types.importDeclaration(
61
+ [
62
+ types.importSpecifier(
63
+ types.identifier("useTranslation"),
64
+ types.identifier("useTranslation")
65
+ )
66
+ ],
67
+ types.stringLiteral(importSource)
47
68
  )
48
- ],
49
- types.stringLiteral(importSource)
69
+ );
70
+ }
71
+ } else {
72
+ const importSource = state.opts.rscImport ?? "@better-intl/next/rsc";
73
+ path.traverse({
74
+ FunctionDeclaration(fnPath) {
75
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
76
+ injectServerRsc(types, fnPath, tName);
77
+ },
78
+ FunctionExpression(fnPath) {
79
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
80
+ injectServerRsc(types, fnPath, tName);
81
+ },
82
+ ArrowFunctionExpression(fnPath) {
83
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
84
+ injectServerRsc(types, fnPath, tName);
85
+ }
86
+ });
87
+ const hasImport = path.node.body.some(
88
+ (node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
89
+ (specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "rsc"
90
+ )
50
91
  );
51
- path.unshiftContainer("body", importDecl);
92
+ if (!hasImport) {
93
+ path.unshiftContainer(
94
+ "body",
95
+ types.importDeclaration(
96
+ [
97
+ types.importSpecifier(
98
+ types.identifier("rsc"),
99
+ types.identifier("rsc")
100
+ )
101
+ ],
102
+ types.stringLiteral(importSource)
103
+ )
104
+ );
105
+ }
52
106
  }
53
107
  }
54
108
  },
@@ -98,7 +152,7 @@ function betterIntlBabelPlugin({
98
152
  )
99
153
  );
100
154
  const fnPath = path$1.findParent(
101
- (parent2) => parent2.isFunctionDeclaration() || parent2.isFunctionExpression() || parent2.isArrowFunctionExpression()
155
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
102
156
  );
103
157
  if (fnPath) {
104
158
  state.functionsNeedingHook?.add(fnPath.node);
@@ -108,7 +162,7 @@ function betterIntlBabelPlugin({
108
162
  }
109
163
  };
110
164
  }
111
- function injectHook(types, path, tName) {
165
+ function injectClientHook(types, path, tName) {
112
166
  const hookCall = types.variableDeclaration("const", [
113
167
  types.variableDeclarator(
114
168
  types.objectPattern([
@@ -132,6 +186,23 @@ function injectHook(types, path, tName) {
132
186
  ]);
133
187
  }
134
188
  }
189
+ function injectServerRsc(types, path, tName) {
190
+ const rscCall = types.variableDeclaration("const", [
191
+ types.variableDeclarator(
192
+ types.identifier(tName),
193
+ types.callExpression(types.identifier("rsc"), [])
194
+ )
195
+ ]);
196
+ const body = path.node.body;
197
+ if (types.isBlockStatement(body)) {
198
+ body.body.unshift(rscCall);
199
+ } else {
200
+ path.node.body = types.blockStatement([
201
+ rscCall,
202
+ types.returnStatement(body)
203
+ ]);
204
+ }
205
+ }
135
206
 
136
207
  module.exports = betterIntlBabelPlugin;
137
208
  //# sourceMappingURL=babel.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/babel.ts"],"names":["path","relative","generateId","parent"],"mappings":"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","file":"babel.cjs","sourcesContent":["/**\n * Babel plugin for better-intl.\n *\n * Usage in babel config:\n * plugins: [[\"@better-intl/compiler/babel\", { mode: \"auto\" }]]\n *\n * Transforms `<h1>Hello world</h1>` into:\n * const { t: __t } = useTranslation();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n */\n\nimport { relative } from \"node:path\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport type * as BabelTypes from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\ninterface BetterIntlPluginOptions {\n locale?: string;\n messages?: Messages;\n tFunctionName?: string;\n runtimeImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface BetterIntlState extends PluginPass {\n opts: BetterIntlPluginOptions;\n componentName?: string;\n needsImport?: boolean;\n functionsNeedingHook?: Set<BabelTypes.Node>;\n}\n\nexport default function betterIntlBabelPlugin({\n types,\n}: {\n types: typeof BabelTypes;\n}): PluginObj<BetterIntlState> {\n return {\n name: \"better-intl\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.functionsNeedingHook = new Set();\n },\n exit(path, state) {\n if (!state.needsImport) return;\n\n const tName = state.opts.tFunctionName ?? \"__t\";\n const importSource = state.opts.runtimeImport ?? \"@better-intl/react\";\n\n // Inject useTranslation hook into functions that need it\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n });\n\n // Add import { useTranslation } from \"@better-intl/react\" (if not already present)\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n const importDecl = types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"useTranslation\"),\n types.identifier(\"useTranslation\"),\n ),\n ],\n types.stringLiteral(importSource),\n );\n path.unshiftContainer(\"body\", importDecl);\n }\n },\n },\n\n FunctionDeclaration(path, state) {\n if (path.node.id) state.componentName = path.node.id.name;\n },\n\n VariableDeclarator(path, state) {\n if (\n types.isIdentifier(path.node.id) &&\n (types.isArrowFunctionExpression(path.node.init) ||\n types.isFunctionExpression(path.node.init))\n ) {\n state.componentName = path.node.id.name;\n }\n },\n\n JSXText(path, state) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const opening = (parent.node as BabelTypes.JSXElement).openingElement;\n const elementName = types.isJSXIdentifier(opening.name)\n ? opening.name.name\n : \"unknown\";\n\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const mode = state.opts.mode ?? \"auto\";\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a: BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute) =>\n types.isJSXAttribute(a) &&\n types.isJSXIdentifier(a.name) &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n // Use relative path for stable IDs\n const absolutePath = state.filename ?? \"unknown\";\n const filePath =\n absolutePath !== \"unknown\"\n ? relative(process.cwd(), absolutePath)\n : absolutePath;\n\n const id = generateId({\n text,\n filePath,\n context: state.componentName,\n });\n\n const { locale, messages, tFunctionName = \"__t\" } = state.opts;\n\n if (locale && messages?.[id]) {\n path.replaceWith(types.jsxText(messages[id]));\n path.skip();\n } else {\n state.needsImport = true;\n path.replaceWith(\n types.jsxExpressionContainer(\n types.callExpression(types.identifier(tFunctionName), [\n types.stringLiteral(id),\n types.identifier(\"undefined\"),\n types.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark enclosing function\n const fnPath = path.findParent(\n (parent) =>\n parent.isFunctionDeclaration() ||\n parent.isFunctionExpression() ||\n parent.isArrowFunctionExpression(),\n );\n if (fnPath) {\n state.functionsNeedingHook?.add(fnPath.node);\n }\n }\n },\n },\n };\n}\n\nfunction injectHook(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const hookCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.objectPattern([\n types.objectProperty(\n types.identifier(\"t\"),\n types.identifier(tName),\n false,\n false,\n ),\n ]),\n types.callExpression(types.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n // Arrow with expression body\n path.node.body = types.blockStatement([\n hookCall,\n types.returnStatement(body),\n ]);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/babel.ts"],"names":["path","relative","generateId"],"mappings":";;;;;;AAuBA,IAAM,gBAAA,uBAAuB,GAAA,CAAI,CAAC,UAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAmB7E,SAAS,qBAAA,CACP,OACA,IAAA,EACS;AACT,EAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,IAAA,IACE,KAAA,CAAM,qBAAA,CAAsB,IAAI,CAAA,IAChC,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,UAAU,CAAA,IACrC,IAAA,CAAK,UAAA,CAAW,KAAA,KAAU,YAAA,EAC1B;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,IACE,CAAC,KAAA,CAAM,qBAAA,CAAsB,IAAI,CAAA,IACjC,CAAC,KAAA,CAAM,eAAA,CAAgB,IAAA,CAAK,UAAU,CAAA,EACtC;AACA,MAAA;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAEe,SAAR,qBAAA,CAAuC;AAAA,EAC5C;AACF,CAAA,EAE+B;AAC7B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,aAAA;AAAA,IAEN,OAAA,EAAS;AAAA,MACP,OAAA,EAAS;AAAA,QACP,KAAA,CAAM,MAAM,KAAA,EAAO;AACjB,UAAA,KAAA,CAAM,oBAAA,uBAA2B,GAAA,EAAI;AACrC,UAAA,KAAA,CAAM,iBAAA,GAAoB,qBAAA;AAAA,YACxB,KAAA;AAAA,YACA,KAAK,IAAA,CAAK;AAAA,WACZ;AAAA,QACF,CAAA;AAAA,QACA,IAAA,CAAK,MAAM,KAAA,EAAO;AAChB,UAAA,IAAI,CAAC,MAAM,WAAA,EAAa;AAExB,UAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,aAAA,IAAiB,KAAA;AAE1C,UAAA,IAAI,MAAM,iBAAA,EAAmB;AAE3B,YAAA,MAAM,YAAA,GACJ,KAAA,CAAM,IAAA,CAAK,aAAA,IAAiB,oBAAA;AAE9B,YAAA,IAAA,CAAK,QAAA,CAAS;AAAA,cACZ,oBAAoB,MAAA,EAAQ;AAC1B,gBAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,gBAAA,gBAAA,CAAiB,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,cACvC,CAAA;AAAA,cACA,mBAAmB,MAAA,EAAQ;AACzB,gBAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,gBAAA,gBAAA,CAAiB,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,cACvC,CAAA;AAAA,cACA,wBAAwB,MAAA,EAAQ;AAC9B,gBAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,gBAAA,gBAAA,CAAiB,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,cACvC;AAAA,aACD,CAAA;AAGD,YAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAA;AAAA,cAC/B,CAAC,IAAA,KACC,KAAA,CAAM,mBAAA,CAAoB,IAAI,CAAA,IAC9B,IAAA,CAAK,MAAA,CAAO,KAAA,KAAU,YAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,gBACd,CAAC,SAAA,KACC,KAAA,CAAM,iBAAA,CAAkB,SAAS,CAAA,IACjC,KAAA,CAAM,YAAA,CAAa,SAAA,CAAU,KAAK,CAAA,IAClC,SAAA,CAAU,MAAM,IAAA,KAAS;AAAA;AAC7B,aACJ;AAEA,YAAA,IAAI,CAAC,SAAA,EAAW;AACd,cAAA,IAAA,CAAK,gBAAA;AAAA,gBACH,MAAA;AAAA,gBACA,KAAA,CAAM,iBAAA;AAAA,kBACJ;AAAA,oBACE,KAAA,CAAM,eAAA;AAAA,sBACJ,KAAA,CAAM,WAAW,gBAAgB,CAAA;AAAA,sBACjC,KAAA,CAAM,WAAW,gBAAgB;AAAA;AACnC,mBACF;AAAA,kBACA,KAAA,CAAM,cAAc,YAAY;AAAA;AAClC,eACF;AAAA,YACF;AAAA,UACF,CAAA,MAAO;AAEL,YAAA,MAAM,YAAA,GACJ,KAAA,CAAM,IAAA,CAAK,SAAA,IAAa,uBAAA;AAE1B,YAAA,IAAA,CAAK,QAAA,CAAS;AAAA,cACZ,oBAAoB,MAAA,EAAQ;AAC1B,gBAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,gBAAA,eAAA,CAAgB,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,cACtC,CAAA;AAAA,cACA,mBAAmB,MAAA,EAAQ;AACzB,gBAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,gBAAA,eAAA,CAAgB,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,cACtC,CAAA;AAAA,cACA,wBAAwB,MAAA,EAAQ;AAC9B,gBAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,gBAAA,eAAA,CAAgB,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,cACtC;AAAA,aACD,CAAA;AAGD,YAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAA;AAAA,cAC/B,CAAC,IAAA,KACC,KAAA,CAAM,mBAAA,CAAoB,IAAI,CAAA,IAC9B,IAAA,CAAK,MAAA,CAAO,KAAA,KAAU,YAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,gBACd,CAAC,SAAA,KACC,KAAA,CAAM,iBAAA,CAAkB,SAAS,CAAA,IACjC,KAAA,CAAM,YAAA,CAAa,SAAA,CAAU,KAAK,CAAA,IAClC,SAAA,CAAU,MAAM,IAAA,KAAS;AAAA;AAC7B,aACJ;AAEA,YAAA,IAAI,CAAC,SAAA,EAAW;AACd,cAAA,IAAA,CAAK,gBAAA;AAAA,gBACH,MAAA;AAAA,gBACA,KAAA,CAAM,iBAAA;AAAA,kBACJ;AAAA,oBACE,KAAA,CAAM,eAAA;AAAA,sBACJ,KAAA,CAAM,WAAW,KAAK,CAAA;AAAA,sBACtB,KAAA,CAAM,WAAW,KAAK;AAAA;AACxB,mBACF;AAAA,kBACA,KAAA,CAAM,cAAc,YAAY;AAAA;AAClC,eACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,OACF;AAAA,MAEA,mBAAA,CAAoB,MAAM,KAAA,EAAO;AAC/B,QAAA,IAAI,KAAK,IAAA,CAAK,EAAA,QAAU,aAAA,GAAgB,IAAA,CAAK,KAAK,EAAA,CAAG,IAAA;AAAA,MACvD,CAAA;AAAA,MAEA,kBAAA,CAAmB,MAAM,KAAA,EAAO;AAC9B,QAAA,IACE,MAAM,YAAA,CAAa,IAAA,CAAK,IAAA,CAAK,EAAE,MAC9B,KAAA,CAAM,yBAAA,CAA0B,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,IAC7C,KAAA,CAAM,qBAAqB,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,CAAA,EAC3C;AACA,UAAA,KAAA,CAAM,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA;AAAA,QACrC;AAAA,MACF,CAAA;AAAA,MAEA,OAAA,CAAQA,QAAM,KAAA,EAAO;AACnB,QAAA,MAAM,IAAA,GAAOA,MAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAClC,QAAA,IAAI,CAAC,IAAA,EAAM;AAEX,QAAA,MAAM,SAASA,MAAA,CAAK,UAAA;AACpB,QAAA,IAAI,CAAC,MAAA,EAAQ,YAAA,EAAa,EAAG;AAE7B,QAAA,MAAM,OAAA,GAAW,OAAO,IAAA,CAA+B,cAAA;AACvD,QAAA,MAAM,WAAA,GAAc,MAAM,eAAA,CAAgB,OAAA,CAAQ,IAAI,CAAA,GAClD,OAAA,CAAQ,KAAK,IAAA,GACb,SAAA;AAEJ,QAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAEvC,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,IAAA,IAAQ,MAAA;AAChC,QAAA,IAAI,SAAS,UAAA,EAAY;AACvB,UAAA,MAAM,OAAA,GAAU,QAAQ,UAAA,CAAW,IAAA;AAAA,YACjC,CAAC,CAAA,KACC,KAAA,CAAM,cAAA,CAAe,CAAC,CAAA,IACtB,KAAA,CAAM,eAAA,CAAgB,CAAA,CAAE,IAAI,CAAA,IAC5B,CAAA,CAAE,KAAK,IAAA,KAAS;AAAA,WACpB;AACA,UAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,OAAA,EAAS;AAAA,QACvC;AAGA,QAAA,MAAM,YAAA,GAAe,MAAM,QAAA,IAAY,SAAA;AACvC,QAAA,MAAM,QAAA,GACJ,iBAAiB,SAAA,GACbC,aAAA,CAAS,QAAQ,GAAA,EAAI,EAAG,YAAY,CAAA,GACpC,YAAA;AAEN,QAAA,MAAM,KAAKC,eAAA,CAAW;AAAA,UACpB,IAAA;AAAA,UACA,QAAA;AAAA,UACA,SAAS,KAAA,CAAM;AAAA,SAChB,CAAA;AAED,QAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAU,aAAA,GAAgB,KAAA,KAAU,KAAA,CAAM,IAAA;AAE1D,QAAA,IAAI,MAAA,IAAU,QAAA,GAAW,EAAE,CAAA,EAAG;AAE5B,UAAAF,MAAA,CAAK,YAAY,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,EAAE,CAAC,CAAC,CAAA;AAC5C,UAAAA,MAAA,CAAK,IAAA,EAAK;AAAA,QACZ,CAAA,MAAO;AACL,UAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AACpB,UAAAA,MAAA,CAAK,WAAA;AAAA,YACH,KAAA,CAAM,sBAAA;AAAA,cACJ,KAAA,CAAM,cAAA,CAAe,KAAA,CAAM,UAAA,CAAW,aAAa,CAAA,EAAG;AAAA,gBACpD,KAAA,CAAM,cAAc,EAAE,CAAA;AAAA,gBACtB,KAAA,CAAM,WAAW,WAAW,CAAA;AAAA,gBAC5B,KAAA,CAAM,cAAc,IAAI;AAAA,eACzB;AAAA;AACH,WACF;AAGA,UAAA,MAAM,SAASA,MAAA,CAAK,UAAA;AAAA,YAClB,CAAC,MACC,CAAA,CAAE,qBAAA,MACF,CAAA,CAAE,oBAAA,EAAqB,IACvB,CAAA,CAAE,yBAAA;AAA0B,WAChC;AACA,UAAA,IAAI,MAAA,EAAQ;AACV,YAAA,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA;AACF,GACF;AACF;AAKA,SAAS,gBAAA,CACP,KAAA,EACA,IAAA,EACA,KAAA,EACA;AACA,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,mBAAA,CAAoB,OAAA,EAAS;AAAA,IAClD,KAAA,CAAM,kBAAA;AAAA,MACJ,MAAM,aAAA,CAAc;AAAA,QAClB,KAAA,CAAM,cAAA;AAAA,UACJ,KAAA,CAAM,WAAW,GAAG,CAAA;AAAA,UACpB,KAAA,CAAM,WAAW,KAAK,CAAA;AAAA,UACtB,KAAA;AAAA,UACA;AAAA;AACF,OACD,CAAA;AAAA,MACD,MAAM,cAAA,CAAe,KAAA,CAAM,WAAW,gBAAgB,CAAA,EAAG,EAAE;AAAA;AAC7D,GACD,CAAA;AAED,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACvB,EAAA,IAAI,KAAA,CAAM,gBAAA,CAAiB,IAAI,CAAA,EAAG;AAChC,IAAA,IAAA,CAAK,IAAA,CAAK,QAAQ,QAAQ,CAAA;AAAA,EAC5B,CAAA,MAAO;AACL,IAAA,IAAA,CAAK,IAAA,CAAK,IAAA,GAAO,KAAA,CAAM,cAAA,CAAe;AAAA,MACpC,QAAA;AAAA,MACA,KAAA,CAAM,gBAAgB,IAAI;AAAA,KAC3B,CAAA;AAAA,EACH;AACF;AAKA,SAAS,eAAA,CACP,KAAA,EACA,IAAA,EACA,KAAA,EACA;AACA,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,mBAAA,CAAoB,OAAA,EAAS;AAAA,IACjD,KAAA,CAAM,kBAAA;AAAA,MACJ,KAAA,CAAM,WAAW,KAAK,CAAA;AAAA,MACtB,MAAM,cAAA,CAAe,KAAA,CAAM,WAAW,KAAK,CAAA,EAAG,EAAE;AAAA;AAClD,GACD,CAAA;AAED,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACvB,EAAA,IAAI,KAAA,CAAM,gBAAA,CAAiB,IAAI,CAAA,EAAG;AAChC,IAAA,IAAA,CAAK,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EAC3B,CAAA,MAAO;AACL,IAAA,IAAA,CAAK,IAAA,CAAK,IAAA,GAAO,KAAA,CAAM,cAAA,CAAe;AAAA,MACpC,OAAA;AAAA,MACA,KAAA,CAAM,gBAAgB,IAAI;AAAA,KAC3B,CAAA;AAAA,EACH;AACF","file":"babel.cjs","sourcesContent":["/**\n * Babel plugin for better-intl.\n *\n * Usage in babel config:\n * plugins: [[\"@better-intl/compiler/babel\", { mode: \"auto\" }]]\n *\n * Client components (\"use client\"):\n * <h1>Hello world</h1>\n * → const { t: __t } = useTranslation();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n *\n * Server components (no \"use client\"):\n * <h1>Hello world</h1>\n * → const __t = rsc();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n */\n\nimport { relative } from \"node:path\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport type * as BabelTypes from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\ninterface BetterIntlPluginOptions {\n locale?: string;\n messages?: Messages;\n tFunctionName?: string;\n runtimeImport?: string;\n rscImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface BetterIntlState extends PluginPass {\n opts: BetterIntlPluginOptions;\n componentName?: string;\n needsImport?: boolean;\n isClientComponent?: boolean;\n functionsNeedingHook?: Set<BabelTypes.Node>;\n}\n\nfunction hasUseClientDirective(\n types: typeof BabelTypes,\n body: BabelTypes.Statement[],\n): boolean {\n for (const node of body) {\n if (\n types.isExpressionStatement(node) &&\n types.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\"\n ) {\n return true;\n }\n // Directives must be at the top, stop at first non-directive\n if (\n !types.isExpressionStatement(node) ||\n !types.isStringLiteral(node.expression)\n ) {\n break;\n }\n }\n return false;\n}\n\nexport default function betterIntlBabelPlugin({\n types,\n}: {\n types: typeof BabelTypes;\n}): PluginObj<BetterIntlState> {\n return {\n name: \"better-intl\",\n\n visitor: {\n Program: {\n enter(path, state) {\n state.functionsNeedingHook = new Set();\n state.isClientComponent = hasUseClientDirective(\n types,\n path.node.body,\n );\n },\n exit(path, state) {\n if (!state.needsImport) return;\n\n const tName = state.opts.tFunctionName ?? \"__t\";\n\n if (state.isClientComponent) {\n // Client component → inject useTranslation() hook\n const importSource =\n state.opts.runtimeImport ?? \"@better-intl/react\";\n\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectClientHook(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectClientHook(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectClientHook(types, fnPath, tName);\n },\n });\n\n // Add import { useTranslation } from \"@better-intl/react\"\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n path.unshiftContainer(\n \"body\",\n types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"useTranslation\"),\n types.identifier(\"useTranslation\"),\n ),\n ],\n types.stringLiteral(importSource),\n ),\n );\n }\n } else {\n // Server component → inject rsc() call\n const importSource =\n state.opts.rscImport ?? \"@better-intl/next/rsc\";\n\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectServerRsc(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectServerRsc(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectServerRsc(types, fnPath, tName);\n },\n });\n\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n path.unshiftContainer(\n \"body\",\n types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"rsc\"),\n types.identifier(\"rsc\"),\n ),\n ],\n types.stringLiteral(importSource),\n ),\n );\n }\n }\n },\n },\n\n FunctionDeclaration(path, state) {\n if (path.node.id) state.componentName = path.node.id.name;\n },\n\n VariableDeclarator(path, state) {\n if (\n types.isIdentifier(path.node.id) &&\n (types.isArrowFunctionExpression(path.node.init) ||\n types.isFunctionExpression(path.node.init))\n ) {\n state.componentName = path.node.id.name;\n }\n },\n\n JSXText(path, state) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const opening = (parent.node as BabelTypes.JSXElement).openingElement;\n const elementName = types.isJSXIdentifier(opening.name)\n ? opening.name.name\n : \"unknown\";\n\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const mode = state.opts.mode ?? \"auto\";\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a: BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute) =>\n types.isJSXAttribute(a) &&\n types.isJSXIdentifier(a.name) &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n // Use relative path for stable IDs\n const absolutePath = state.filename ?? \"unknown\";\n const filePath =\n absolutePath !== \"unknown\"\n ? relative(process.cwd(), absolutePath)\n : absolutePath;\n\n const id = generateId({\n text,\n filePath,\n context: state.componentName,\n });\n\n const { locale, messages, tFunctionName = \"__t\" } = state.opts;\n\n if (locale && messages?.[id]) {\n // Zero-runtime: inline translated text\n path.replaceWith(types.jsxText(messages[id]));\n path.skip();\n } else {\n state.needsImport = true;\n path.replaceWith(\n types.jsxExpressionContainer(\n types.callExpression(types.identifier(tFunctionName), [\n types.stringLiteral(id),\n types.identifier(\"undefined\"),\n types.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark enclosing function as needing hook/rsc injection\n const fnPath = path.findParent(\n (p) =>\n p.isFunctionDeclaration() ||\n p.isFunctionExpression() ||\n p.isArrowFunctionExpression(),\n );\n if (fnPath) {\n state.functionsNeedingHook?.add(fnPath.node);\n }\n }\n },\n },\n };\n}\n\n/**\n * Inject `const { t: __t } = useTranslation();` for client components.\n */\nfunction injectClientHook(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const hookCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.objectPattern([\n types.objectProperty(\n types.identifier(\"t\"),\n types.identifier(tName),\n false,\n false,\n ),\n ]),\n types.callExpression(types.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n path.node.body = types.blockStatement([\n hookCall,\n types.returnStatement(body),\n ]);\n }\n}\n\n/**\n * Inject `const __t = rsc();` for server components.\n */\nfunction injectServerRsc(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const rscCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.identifier(tName),\n types.callExpression(types.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(rscCall);\n } else {\n path.node.body = types.blockStatement([\n rscCall,\n types.returnStatement(body),\n ]);\n }\n}\n"]}
package/dist/babel.d.cts CHANGED
@@ -8,9 +8,15 @@ import { Messages } from '@better-intl/core';
8
8
  * Usage in babel config:
9
9
  * plugins: [["@better-intl/compiler/babel", { mode: "auto" }]]
10
10
  *
11
- * Transforms `<h1>Hello world</h1>` into:
12
- * const { t: __t } = useTranslation();
13
- * <h1>{__t("id", undefined, "Hello world")}</h1>
11
+ * Client components ("use client"):
12
+ * <h1>Hello world</h1>
13
+ * → const { t: __t } = useTranslation();
14
+ * <h1>{__t("id", undefined, "Hello world")}</h1>
15
+ *
16
+ * Server components (no "use client"):
17
+ * <h1>Hello world</h1>
18
+ * → const __t = rsc();
19
+ * <h1>{__t("id", undefined, "Hello world")}</h1>
14
20
  */
15
21
 
16
22
  interface BetterIntlPluginOptions {
@@ -18,12 +24,14 @@ interface BetterIntlPluginOptions {
18
24
  messages?: Messages;
19
25
  tFunctionName?: string;
20
26
  runtimeImport?: string;
27
+ rscImport?: string;
21
28
  mode?: "auto" | "explicit";
22
29
  }
23
30
  interface BetterIntlState extends PluginPass {
24
31
  opts: BetterIntlPluginOptions;
25
32
  componentName?: string;
26
33
  needsImport?: boolean;
34
+ isClientComponent?: boolean;
27
35
  functionsNeedingHook?: Set<BabelTypes.Node>;
28
36
  }
29
37
  declare function betterIntlBabelPlugin({ types, }: {
package/dist/babel.d.ts CHANGED
@@ -8,9 +8,15 @@ import { Messages } from '@better-intl/core';
8
8
  * Usage in babel config:
9
9
  * plugins: [["@better-intl/compiler/babel", { mode: "auto" }]]
10
10
  *
11
- * Transforms `<h1>Hello world</h1>` into:
12
- * const { t: __t } = useTranslation();
13
- * <h1>{__t("id", undefined, "Hello world")}</h1>
11
+ * Client components ("use client"):
12
+ * <h1>Hello world</h1>
13
+ * → const { t: __t } = useTranslation();
14
+ * <h1>{__t("id", undefined, "Hello world")}</h1>
15
+ *
16
+ * Server components (no "use client"):
17
+ * <h1>Hello world</h1>
18
+ * → const __t = rsc();
19
+ * <h1>{__t("id", undefined, "Hello world")}</h1>
14
20
  */
15
21
 
16
22
  interface BetterIntlPluginOptions {
@@ -18,12 +24,14 @@ interface BetterIntlPluginOptions {
18
24
  messages?: Messages;
19
25
  tFunctionName?: string;
20
26
  runtimeImport?: string;
27
+ rscImport?: string;
21
28
  mode?: "auto" | "explicit";
22
29
  }
23
30
  interface BetterIntlState extends PluginPass {
24
31
  opts: BetterIntlPluginOptions;
25
32
  componentName?: string;
26
33
  needsImport?: boolean;
34
+ isClientComponent?: boolean;
27
35
  functionsNeedingHook?: Set<BabelTypes.Node>;
28
36
  }
29
37
  declare function betterIntlBabelPlugin({ types, }: {
package/dist/babel.js CHANGED
@@ -3,6 +3,17 @@ import { generateId } from '@better-intl/core';
3
3
 
4
4
  // src/babel.ts
5
5
  var IGNORED_ELEMENTS = /* @__PURE__ */ new Set(["script", "style", "code", "pre", "Helmet"]);
6
+ function hasUseClientDirective(types, body) {
7
+ for (const node of body) {
8
+ if (types.isExpressionStatement(node) && types.isStringLiteral(node.expression) && node.expression.value === "use client") {
9
+ return true;
10
+ }
11
+ if (!types.isExpressionStatement(node) || !types.isStringLiteral(node.expression)) {
12
+ break;
13
+ }
14
+ }
15
+ return false;
16
+ }
6
17
  function betterIntlBabelPlugin({
7
18
  types
8
19
  }) {
@@ -10,43 +21,86 @@ function betterIntlBabelPlugin({
10
21
  name: "better-intl",
11
22
  visitor: {
12
23
  Program: {
13
- enter(_path, state) {
24
+ enter(path, state) {
14
25
  state.functionsNeedingHook = /* @__PURE__ */ new Set();
26
+ state.isClientComponent = hasUseClientDirective(
27
+ types,
28
+ path.node.body
29
+ );
15
30
  },
16
31
  exit(path, state) {
17
32
  if (!state.needsImport) return;
18
33
  const tName = state.opts.tFunctionName ?? "__t";
19
- const importSource = state.opts.runtimeImport ?? "@better-intl/react";
20
- path.traverse({
21
- FunctionDeclaration(fnPath) {
22
- if (!state.functionsNeedingHook?.has(fnPath.node)) return;
23
- injectHook(types, fnPath, tName);
24
- },
25
- FunctionExpression(fnPath) {
26
- if (!state.functionsNeedingHook?.has(fnPath.node)) return;
27
- injectHook(types, fnPath, tName);
28
- },
29
- ArrowFunctionExpression(fnPath) {
30
- if (!state.functionsNeedingHook?.has(fnPath.node)) return;
31
- injectHook(types, fnPath, tName);
32
- }
33
- });
34
- const hasImport = path.node.body.some(
35
- (node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
36
- (specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "useTranslation"
37
- )
38
- );
39
- if (!hasImport) {
40
- const importDecl = types.importDeclaration(
41
- [
42
- types.importSpecifier(
43
- types.identifier("useTranslation"),
44
- types.identifier("useTranslation")
34
+ if (state.isClientComponent) {
35
+ const importSource = state.opts.runtimeImport ?? "@better-intl/react";
36
+ path.traverse({
37
+ FunctionDeclaration(fnPath) {
38
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
39
+ injectClientHook(types, fnPath, tName);
40
+ },
41
+ FunctionExpression(fnPath) {
42
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
43
+ injectClientHook(types, fnPath, tName);
44
+ },
45
+ ArrowFunctionExpression(fnPath) {
46
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
47
+ injectClientHook(types, fnPath, tName);
48
+ }
49
+ });
50
+ const hasImport = path.node.body.some(
51
+ (node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
52
+ (specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "useTranslation"
53
+ )
54
+ );
55
+ if (!hasImport) {
56
+ path.unshiftContainer(
57
+ "body",
58
+ types.importDeclaration(
59
+ [
60
+ types.importSpecifier(
61
+ types.identifier("useTranslation"),
62
+ types.identifier("useTranslation")
63
+ )
64
+ ],
65
+ types.stringLiteral(importSource)
45
66
  )
46
- ],
47
- types.stringLiteral(importSource)
67
+ );
68
+ }
69
+ } else {
70
+ const importSource = state.opts.rscImport ?? "@better-intl/next/rsc";
71
+ path.traverse({
72
+ FunctionDeclaration(fnPath) {
73
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
74
+ injectServerRsc(types, fnPath, tName);
75
+ },
76
+ FunctionExpression(fnPath) {
77
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
78
+ injectServerRsc(types, fnPath, tName);
79
+ },
80
+ ArrowFunctionExpression(fnPath) {
81
+ if (!state.functionsNeedingHook?.has(fnPath.node)) return;
82
+ injectServerRsc(types, fnPath, tName);
83
+ }
84
+ });
85
+ const hasImport = path.node.body.some(
86
+ (node) => types.isImportDeclaration(node) && node.source.value === importSource && node.specifiers.some(
87
+ (specifier) => types.isImportSpecifier(specifier) && types.isIdentifier(specifier.local) && specifier.local.name === "rsc"
88
+ )
48
89
  );
49
- path.unshiftContainer("body", importDecl);
90
+ if (!hasImport) {
91
+ path.unshiftContainer(
92
+ "body",
93
+ types.importDeclaration(
94
+ [
95
+ types.importSpecifier(
96
+ types.identifier("rsc"),
97
+ types.identifier("rsc")
98
+ )
99
+ ],
100
+ types.stringLiteral(importSource)
101
+ )
102
+ );
103
+ }
50
104
  }
51
105
  }
52
106
  },
@@ -96,7 +150,7 @@ function betterIntlBabelPlugin({
96
150
  )
97
151
  );
98
152
  const fnPath = path.findParent(
99
- (parent2) => parent2.isFunctionDeclaration() || parent2.isFunctionExpression() || parent2.isArrowFunctionExpression()
153
+ (p) => p.isFunctionDeclaration() || p.isFunctionExpression() || p.isArrowFunctionExpression()
100
154
  );
101
155
  if (fnPath) {
102
156
  state.functionsNeedingHook?.add(fnPath.node);
@@ -106,7 +160,7 @@ function betterIntlBabelPlugin({
106
160
  }
107
161
  };
108
162
  }
109
- function injectHook(types, path, tName) {
163
+ function injectClientHook(types, path, tName) {
110
164
  const hookCall = types.variableDeclaration("const", [
111
165
  types.variableDeclarator(
112
166
  types.objectPattern([
@@ -130,6 +184,23 @@ function injectHook(types, path, tName) {
130
184
  ]);
131
185
  }
132
186
  }
187
+ function injectServerRsc(types, path, tName) {
188
+ const rscCall = types.variableDeclaration("const", [
189
+ types.variableDeclarator(
190
+ types.identifier(tName),
191
+ types.callExpression(types.identifier("rsc"), [])
192
+ )
193
+ ]);
194
+ const body = path.node.body;
195
+ if (types.isBlockStatement(body)) {
196
+ body.body.unshift(rscCall);
197
+ } else {
198
+ path.node.body = types.blockStatement([
199
+ rscCall,
200
+ types.returnStatement(body)
201
+ ]);
202
+ }
203
+ }
133
204
 
134
205
  export { betterIntlBabelPlugin as default };
135
206
  //# sourceMappingURL=babel.js.map
package/dist/babel.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/babel.ts"],"names":["parent"],"mappings":";;;;AAiBA,IAAM,gBAAA,uBAAuB,GAAA,CAAI,CAAC,UAAU,OAAA,EAAS,MAAA,EAAQ,KAAA,EAAO,QAAQ,CAAC,CAAA;AAiB9D,SAAR,qBAAA,CAAuC;AAAA,EAC5C;AACF,CAAA,EAE+B;AAC7B,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,aAAA;AAAA,IAEN,OAAA,EAAS;AAAA,MACP,OAAA,EAAS;AAAA,QACP,KAAA,CAAM,OAAO,KAAA,EAAO;AAClB,UAAA,KAAA,CAAM,oBAAA,uBAA2B,GAAA,EAAI;AAAA,QACvC,CAAA;AAAA,QACA,IAAA,CAAK,MAAM,KAAA,EAAO;AAChB,UAAA,IAAI,CAAC,MAAM,WAAA,EAAa;AAExB,UAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,aAAA,IAAiB,KAAA;AAC1C,UAAA,MAAM,YAAA,GAAe,KAAA,CAAM,IAAA,CAAK,aAAA,IAAiB,oBAAA;AAGjD,UAAA,IAAA,CAAK,QAAA,CAAS;AAAA,YACZ,oBAAoB,MAAA,EAAQ;AAC1B,cAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,cAAA,UAAA,CAAW,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,YACjC,CAAA;AAAA,YACA,mBAAmB,MAAA,EAAQ;AACzB,cAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,cAAA,UAAA,CAAW,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,YACjC,CAAA;AAAA,YACA,wBAAwB,MAAA,EAAQ;AAC9B,cAAA,IAAI,CAAC,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA,EAAG;AACnD,cAAA,UAAA,CAAW,KAAA,EAAO,QAAQ,KAAK,CAAA;AAAA,YACjC;AAAA,WACD,CAAA;AAGD,UAAA,MAAM,SAAA,GAAY,IAAA,CAAK,IAAA,CAAK,IAAA,CAAK,IAAA;AAAA,YAC/B,CAAC,IAAA,KACC,KAAA,CAAM,mBAAA,CAAoB,IAAI,CAAA,IAC9B,IAAA,CAAK,MAAA,CAAO,KAAA,KAAU,YAAA,IACtB,IAAA,CAAK,UAAA,CAAW,IAAA;AAAA,cACd,CAAC,SAAA,KACC,KAAA,CAAM,iBAAA,CAAkB,SAAS,CAAA,IACjC,KAAA,CAAM,YAAA,CAAa,SAAA,CAAU,KAAK,CAAA,IAClC,SAAA,CAAU,MAAM,IAAA,KAAS;AAAA;AAC7B,WACJ;AAEA,UAAA,IAAI,CAAC,SAAA,EAAW;AACd,YAAA,MAAM,aAAa,KAAA,CAAM,iBAAA;AAAA,cACvB;AAAA,gBACE,KAAA,CAAM,eAAA;AAAA,kBACJ,KAAA,CAAM,WAAW,gBAAgB,CAAA;AAAA,kBACjC,KAAA,CAAM,WAAW,gBAAgB;AAAA;AACnC,eACF;AAAA,cACA,KAAA,CAAM,cAAc,YAAY;AAAA,aAClC;AACA,YAAA,IAAA,CAAK,gBAAA,CAAiB,QAAQ,UAAU,CAAA;AAAA,UAC1C;AAAA,QACF;AAAA,OACF;AAAA,MAEA,mBAAA,CAAoB,MAAM,KAAA,EAAO;AAC/B,QAAA,IAAI,KAAK,IAAA,CAAK,EAAA,QAAU,aAAA,GAAgB,IAAA,CAAK,KAAK,EAAA,CAAG,IAAA;AAAA,MACvD,CAAA;AAAA,MAEA,kBAAA,CAAmB,MAAM,KAAA,EAAO;AAC9B,QAAA,IACE,MAAM,YAAA,CAAa,IAAA,CAAK,IAAA,CAAK,EAAE,MAC9B,KAAA,CAAM,yBAAA,CAA0B,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,IAC7C,KAAA,CAAM,qBAAqB,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,CAAA,EAC3C;AACA,UAAA,KAAA,CAAM,aAAA,GAAgB,IAAA,CAAK,IAAA,CAAK,EAAA,CAAG,IAAA;AAAA,QACrC;AAAA,MACF,CAAA;AAAA,MAEA,OAAA,CAAQ,MAAM,KAAA,EAAO;AACnB,QAAA,MAAM,IAAA,GAAO,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,EAAK;AAClC,QAAA,IAAI,CAAC,IAAA,EAAM;AAEX,QAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AACpB,QAAA,IAAI,CAAC,MAAA,EAAQ,YAAA,EAAa,EAAG;AAE7B,QAAA,MAAM,OAAA,GAAW,OAAO,IAAA,CAA+B,cAAA;AACvD,QAAA,MAAM,WAAA,GAAc,MAAM,eAAA,CAAgB,OAAA,CAAQ,IAAI,CAAA,GAClD,OAAA,CAAQ,KAAK,IAAA,GACb,SAAA;AAEJ,QAAA,IAAI,gBAAA,CAAiB,GAAA,CAAI,WAAW,CAAA,EAAG;AAEvC,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,CAAK,IAAA,IAAQ,MAAA;AAChC,QAAA,IAAI,SAAS,UAAA,EAAY;AACvB,UAAA,MAAM,OAAA,GAAU,QAAQ,UAAA,CAAW,IAAA;AAAA,YACjC,CAAC,CAAA,KACC,KAAA,CAAM,cAAA,CAAe,CAAC,CAAA,IACtB,KAAA,CAAM,eAAA,CAAgB,CAAA,CAAE,IAAI,CAAA,IAC5B,CAAA,CAAE,KAAK,IAAA,KAAS;AAAA,WACpB;AACA,UAAA,IAAI,WAAA,KAAgB,GAAA,IAAO,CAAC,OAAA,EAAS;AAAA,QACvC;AAGA,QAAA,MAAM,YAAA,GAAe,MAAM,QAAA,IAAY,SAAA;AACvC,QAAA,MAAM,QAAA,GACJ,iBAAiB,SAAA,GACb,QAAA,CAAS,QAAQ,GAAA,EAAI,EAAG,YAAY,CAAA,GACpC,YAAA;AAEN,QAAA,MAAM,KAAK,UAAA,CAAW;AAAA,UACpB,IAAA;AAAA,UACA,QAAA;AAAA,UACA,SAAS,KAAA,CAAM;AAAA,SAChB,CAAA;AAED,QAAA,MAAM,EAAE,MAAA,EAAQ,QAAA,EAAU,aAAA,GAAgB,KAAA,KAAU,KAAA,CAAM,IAAA;AAE1D,QAAA,IAAI,MAAA,IAAU,QAAA,GAAW,EAAE,CAAA,EAAG;AAC5B,UAAA,IAAA,CAAK,YAAY,KAAA,CAAM,OAAA,CAAQ,QAAA,CAAS,EAAE,CAAC,CAAC,CAAA;AAC5C,UAAA,IAAA,CAAK,IAAA,EAAK;AAAA,QACZ,CAAA,MAAO;AACL,UAAA,KAAA,CAAM,WAAA,GAAc,IAAA;AACpB,UAAA,IAAA,CAAK,WAAA;AAAA,YACH,KAAA,CAAM,sBAAA;AAAA,cACJ,KAAA,CAAM,cAAA,CAAe,KAAA,CAAM,UAAA,CAAW,aAAa,CAAA,EAAG;AAAA,gBACpD,KAAA,CAAM,cAAc,EAAE,CAAA;AAAA,gBACtB,KAAA,CAAM,WAAW,WAAW,CAAA;AAAA,gBAC5B,KAAA,CAAM,cAAc,IAAI;AAAA,eACzB;AAAA;AACH,WACF;AAGA,UAAA,MAAM,SAAS,IAAA,CAAK,UAAA;AAAA,YAClB,CAACA,YACCA,OAAAA,CAAO,qBAAA,MACPA,OAAAA,CAAO,oBAAA,EAAqB,IAC5BA,OAAAA,CAAO,yBAAA;AAA0B,WACrC;AACA,UAAA,IAAI,MAAA,EAAQ;AACV,YAAA,KAAA,CAAM,oBAAA,EAAsB,GAAA,CAAI,MAAA,CAAO,IAAI,CAAA;AAAA,UAC7C;AAAA,QACF;AAAA,MACF;AAAA;AACF,GACF;AACF;AAEA,SAAS,UAAA,CACP,KAAA,EACA,IAAA,EACA,KAAA,EACA;AACA,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,mBAAA,CAAoB,OAAA,EAAS;AAAA,IAClD,KAAA,CAAM,kBAAA;AAAA,MACJ,MAAM,aAAA,CAAc;AAAA,QAClB,KAAA,CAAM,cAAA;AAAA,UACJ,KAAA,CAAM,WAAW,GAAG,CAAA;AAAA,UACpB,KAAA,CAAM,WAAW,KAAK,CAAA;AAAA,UACtB,KAAA;AAAA,UACA;AAAA;AACF,OACD,CAAA;AAAA,MACD,MAAM,cAAA,CAAe,KAAA,CAAM,WAAW,gBAAgB,CAAA,EAAG,EAAE;AAAA;AAC7D,GACD,CAAA;AAED,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,CAAK,IAAA;AACvB,EAAA,IAAI,KAAA,CAAM,gBAAA,CAAiB,IAAI,CAAA,EAAG;AAChC,IAAA,IAAA,CAAK,IAAA,CAAK,QAAQ,QAAQ,CAAA;AAAA,EAC5B,CAAA,MAAO;AAEL,IAAA,IAAA,CAAK,IAAA,CAAK,IAAA,GAAO,KAAA,CAAM,cAAA,CAAe;AAAA,MACpC,QAAA;AAAA,MACA,KAAA,CAAM,gBAAgB,IAAI;AAAA,KAC3B,CAAA;AAAA,EACH;AACF","file":"babel.js","sourcesContent":["/**\n * Babel plugin for better-intl.\n *\n * Usage in babel config:\n * plugins: [[\"@better-intl/compiler/babel\", { mode: \"auto\" }]]\n *\n * Transforms `<h1>Hello world</h1>` into:\n * const { t: __t } = useTranslation();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n */\n\nimport { relative } from \"node:path\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport type * as BabelTypes from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\ninterface BetterIntlPluginOptions {\n locale?: string;\n messages?: Messages;\n tFunctionName?: string;\n runtimeImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface BetterIntlState extends PluginPass {\n opts: BetterIntlPluginOptions;\n componentName?: string;\n needsImport?: boolean;\n functionsNeedingHook?: Set<BabelTypes.Node>;\n}\n\nexport default function betterIntlBabelPlugin({\n types,\n}: {\n types: typeof BabelTypes;\n}): PluginObj<BetterIntlState> {\n return {\n name: \"better-intl\",\n\n visitor: {\n Program: {\n enter(_path, state) {\n state.functionsNeedingHook = new Set();\n },\n exit(path, state) {\n if (!state.needsImport) return;\n\n const tName = state.opts.tFunctionName ?? \"__t\";\n const importSource = state.opts.runtimeImport ?? \"@better-intl/react\";\n\n // Inject useTranslation hook into functions that need it\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectHook(types, fnPath, tName);\n },\n });\n\n // Add import { useTranslation } from \"@better-intl/react\" (if not already present)\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n const importDecl = types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"useTranslation\"),\n types.identifier(\"useTranslation\"),\n ),\n ],\n types.stringLiteral(importSource),\n );\n path.unshiftContainer(\"body\", importDecl);\n }\n },\n },\n\n FunctionDeclaration(path, state) {\n if (path.node.id) state.componentName = path.node.id.name;\n },\n\n VariableDeclarator(path, state) {\n if (\n types.isIdentifier(path.node.id) &&\n (types.isArrowFunctionExpression(path.node.init) ||\n types.isFunctionExpression(path.node.init))\n ) {\n state.componentName = path.node.id.name;\n }\n },\n\n JSXText(path, state) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const opening = (parent.node as BabelTypes.JSXElement).openingElement;\n const elementName = types.isJSXIdentifier(opening.name)\n ? opening.name.name\n : \"unknown\";\n\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const mode = state.opts.mode ?? \"auto\";\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a: BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute) =>\n types.isJSXAttribute(a) &&\n types.isJSXIdentifier(a.name) &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n // Use relative path for stable IDs\n const absolutePath = state.filename ?? \"unknown\";\n const filePath =\n absolutePath !== \"unknown\"\n ? relative(process.cwd(), absolutePath)\n : absolutePath;\n\n const id = generateId({\n text,\n filePath,\n context: state.componentName,\n });\n\n const { locale, messages, tFunctionName = \"__t\" } = state.opts;\n\n if (locale && messages?.[id]) {\n path.replaceWith(types.jsxText(messages[id]));\n path.skip();\n } else {\n state.needsImport = true;\n path.replaceWith(\n types.jsxExpressionContainer(\n types.callExpression(types.identifier(tFunctionName), [\n types.stringLiteral(id),\n types.identifier(\"undefined\"),\n types.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark enclosing function\n const fnPath = path.findParent(\n (parent) =>\n parent.isFunctionDeclaration() ||\n parent.isFunctionExpression() ||\n parent.isArrowFunctionExpression(),\n );\n if (fnPath) {\n state.functionsNeedingHook?.add(fnPath.node);\n }\n }\n },\n },\n };\n}\n\nfunction injectHook(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const hookCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.objectPattern([\n types.objectProperty(\n types.identifier(\"t\"),\n types.identifier(tName),\n false,\n false,\n ),\n ]),\n types.callExpression(types.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n // Arrow with expression body\n path.node.body = types.blockStatement([\n hookCall,\n types.returnStatement(body),\n ]);\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/babel.ts"],"names":[],"mappings":"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","file":"babel.js","sourcesContent":["/**\n * Babel plugin for better-intl.\n *\n * Usage in babel config:\n * plugins: [[\"@better-intl/compiler/babel\", { mode: \"auto\" }]]\n *\n * Client components (\"use client\"):\n * <h1>Hello world</h1>\n * → const { t: __t } = useTranslation();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n *\n * Server components (no \"use client\"):\n * <h1>Hello world</h1>\n * → const __t = rsc();\n * <h1>{__t(\"id\", undefined, \"Hello world\")}</h1>\n */\n\nimport { relative } from \"node:path\";\nimport type { PluginObj, PluginPass } from \"@babel/core\";\nimport type * as BabelTypes from \"@babel/types\";\nimport type { Messages } from \"@better-intl/core\";\nimport { generateId } from \"@better-intl/core\";\n\nconst IGNORED_ELEMENTS = new Set([\"script\", \"style\", \"code\", \"pre\", \"Helmet\"]);\n\ninterface BetterIntlPluginOptions {\n locale?: string;\n messages?: Messages;\n tFunctionName?: string;\n runtimeImport?: string;\n rscImport?: string;\n mode?: \"auto\" | \"explicit\";\n}\n\ninterface BetterIntlState extends PluginPass {\n opts: BetterIntlPluginOptions;\n componentName?: string;\n needsImport?: boolean;\n isClientComponent?: boolean;\n functionsNeedingHook?: Set<BabelTypes.Node>;\n}\n\nfunction hasUseClientDirective(\n types: typeof BabelTypes,\n body: BabelTypes.Statement[],\n): boolean {\n for (const node of body) {\n if (\n types.isExpressionStatement(node) &&\n types.isStringLiteral(node.expression) &&\n node.expression.value === \"use client\"\n ) {\n return true;\n }\n // Directives must be at the top, stop at first non-directive\n if (\n !types.isExpressionStatement(node) ||\n !types.isStringLiteral(node.expression)\n ) {\n break;\n }\n }\n return false;\n}\n\nexport default function betterIntlBabelPlugin({\n types,\n}: {\n types: typeof BabelTypes;\n}): PluginObj<BetterIntlState> {\n return {\n name: \"better-intl\",\n\n visitor: {\n Program: {\n enter(path, state) {\n state.functionsNeedingHook = new Set();\n state.isClientComponent = hasUseClientDirective(\n types,\n path.node.body,\n );\n },\n exit(path, state) {\n if (!state.needsImport) return;\n\n const tName = state.opts.tFunctionName ?? \"__t\";\n\n if (state.isClientComponent) {\n // Client component → inject useTranslation() hook\n const importSource =\n state.opts.runtimeImport ?? \"@better-intl/react\";\n\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectClientHook(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectClientHook(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectClientHook(types, fnPath, tName);\n },\n });\n\n // Add import { useTranslation } from \"@better-intl/react\"\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"useTranslation\",\n ),\n );\n\n if (!hasImport) {\n path.unshiftContainer(\n \"body\",\n types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"useTranslation\"),\n types.identifier(\"useTranslation\"),\n ),\n ],\n types.stringLiteral(importSource),\n ),\n );\n }\n } else {\n // Server component → inject rsc() call\n const importSource =\n state.opts.rscImport ?? \"@better-intl/next/rsc\";\n\n path.traverse({\n FunctionDeclaration(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectServerRsc(types, fnPath, tName);\n },\n FunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectServerRsc(types, fnPath, tName);\n },\n ArrowFunctionExpression(fnPath) {\n if (!state.functionsNeedingHook?.has(fnPath.node)) return;\n injectServerRsc(types, fnPath, tName);\n },\n });\n\n // Add import { rsc } from \"@better-intl/next/rsc\"\n const hasImport = path.node.body.some(\n (node) =>\n types.isImportDeclaration(node) &&\n node.source.value === importSource &&\n node.specifiers.some(\n (specifier) =>\n types.isImportSpecifier(specifier) &&\n types.isIdentifier(specifier.local) &&\n specifier.local.name === \"rsc\",\n ),\n );\n\n if (!hasImport) {\n path.unshiftContainer(\n \"body\",\n types.importDeclaration(\n [\n types.importSpecifier(\n types.identifier(\"rsc\"),\n types.identifier(\"rsc\"),\n ),\n ],\n types.stringLiteral(importSource),\n ),\n );\n }\n }\n },\n },\n\n FunctionDeclaration(path, state) {\n if (path.node.id) state.componentName = path.node.id.name;\n },\n\n VariableDeclarator(path, state) {\n if (\n types.isIdentifier(path.node.id) &&\n (types.isArrowFunctionExpression(path.node.init) ||\n types.isFunctionExpression(path.node.init))\n ) {\n state.componentName = path.node.id.name;\n }\n },\n\n JSXText(path, state) {\n const text = path.node.value.trim();\n if (!text) return;\n\n const parent = path.parentPath;\n if (!parent?.isJSXElement()) return;\n\n const opening = (parent.node as BabelTypes.JSXElement).openingElement;\n const elementName = types.isJSXIdentifier(opening.name)\n ? opening.name.name\n : \"unknown\";\n\n if (IGNORED_ELEMENTS.has(elementName)) return;\n\n const mode = state.opts.mode ?? \"auto\";\n if (mode === \"explicit\") {\n const hasI18n = opening.attributes.some(\n (a: BabelTypes.JSXAttribute | BabelTypes.JSXSpreadAttribute) =>\n types.isJSXAttribute(a) &&\n types.isJSXIdentifier(a.name) &&\n a.name.name === \"i18n\",\n );\n if (elementName !== \"T\" && !hasI18n) return;\n }\n\n // Use relative path for stable IDs\n const absolutePath = state.filename ?? \"unknown\";\n const filePath =\n absolutePath !== \"unknown\"\n ? relative(process.cwd(), absolutePath)\n : absolutePath;\n\n const id = generateId({\n text,\n filePath,\n context: state.componentName,\n });\n\n const { locale, messages, tFunctionName = \"__t\" } = state.opts;\n\n if (locale && messages?.[id]) {\n // Zero-runtime: inline translated text\n path.replaceWith(types.jsxText(messages[id]));\n path.skip();\n } else {\n state.needsImport = true;\n path.replaceWith(\n types.jsxExpressionContainer(\n types.callExpression(types.identifier(tFunctionName), [\n types.stringLiteral(id),\n types.identifier(\"undefined\"),\n types.stringLiteral(text),\n ]),\n ),\n );\n\n // Mark enclosing function as needing hook/rsc injection\n const fnPath = path.findParent(\n (p) =>\n p.isFunctionDeclaration() ||\n p.isFunctionExpression() ||\n p.isArrowFunctionExpression(),\n );\n if (fnPath) {\n state.functionsNeedingHook?.add(fnPath.node);\n }\n }\n },\n },\n };\n}\n\n/**\n * Inject `const { t: __t } = useTranslation();` for client components.\n */\nfunction injectClientHook(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const hookCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.objectPattern([\n types.objectProperty(\n types.identifier(\"t\"),\n types.identifier(tName),\n false,\n false,\n ),\n ]),\n types.callExpression(types.identifier(\"useTranslation\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(hookCall);\n } else {\n path.node.body = types.blockStatement([\n hookCall,\n types.returnStatement(body),\n ]);\n }\n}\n\n/**\n * Inject `const __t = rsc();` for server components.\n */\nfunction injectServerRsc(\n types: typeof BabelTypes,\n path: { node: { body: BabelTypes.BlockStatement | BabelTypes.Expression } },\n tName: string,\n) {\n const rscCall = types.variableDeclaration(\"const\", [\n types.variableDeclarator(\n types.identifier(tName),\n types.callExpression(types.identifier(\"rsc\"), []),\n ),\n ]);\n\n const body = path.node.body;\n if (types.isBlockStatement(body)) {\n body.body.unshift(rscCall);\n } else {\n path.node.body = types.blockStatement([\n rscCall,\n types.returnStatement(body),\n ]);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-intl/compiler",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "description": "AST extractor and Babel/SWC transform for better-intl",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -28,14 +28,14 @@
28
28
  "README.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@better-intl/core": "0.1.2"
32
- },
33
- "devDependencies": {
34
- "@babel/core": "^7.26.0",
35
31
  "@babel/generator": "^7.26.0",
36
32
  "@babel/parser": "^7.26.0",
37
33
  "@babel/traverse": "^7.26.0",
38
34
  "@babel/types": "^7.26.0",
35
+ "@better-intl/core": "0.1.2"
36
+ },
37
+ "devDependencies": {
38
+ "@babel/core": "^7.26.0",
39
39
  "@types/babel__core": "^7.20.0",
40
40
  "@types/babel__generator": "^7.27.0",
41
41
  "@types/babel__traverse": "^7.20.0"