@lingo.dev/compiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/LICENSE.md +201 -0
  2. package/README.md +192 -0
  3. package/build/_virtual/rolldown_runtime.cjs +29 -0
  4. package/build/_virtual/rolldown_runtime.mjs +7 -0
  5. package/build/index.cjs +0 -0
  6. package/build/index.d.cts +2 -0
  7. package/build/index.d.mts +2 -0
  8. package/build/index.mjs +1 -0
  9. package/build/metadata/manager.cjs +131 -0
  10. package/build/metadata/manager.mjs +123 -0
  11. package/build/metadata/manager.mjs.map +1 -0
  12. package/build/plugin/build-translator.cjs +198 -0
  13. package/build/plugin/build-translator.mjs +196 -0
  14. package/build/plugin/build-translator.mjs.map +1 -0
  15. package/build/plugin/cleanup.cjs +20 -0
  16. package/build/plugin/cleanup.mjs +20 -0
  17. package/build/plugin/cleanup.mjs.map +1 -0
  18. package/build/plugin/next-compiler-loader.cjs +41 -0
  19. package/build/plugin/next-compiler-loader.d.cts +12 -0
  20. package/build/plugin/next-compiler-loader.d.cts.map +1 -0
  21. package/build/plugin/next-compiler-loader.d.mts +13 -0
  22. package/build/plugin/next-compiler-loader.d.mts.map +1 -0
  23. package/build/plugin/next-compiler-loader.mjs +42 -0
  24. package/build/plugin/next-compiler-loader.mjs.map +1 -0
  25. package/build/plugin/next-config-loader.cjs +13 -0
  26. package/build/plugin/next-config-loader.d.cts +8 -0
  27. package/build/plugin/next-config-loader.d.cts.map +1 -0
  28. package/build/plugin/next-config-loader.d.mts +9 -0
  29. package/build/plugin/next-config-loader.d.mts.map +1 -0
  30. package/build/plugin/next-config-loader.mjs +14 -0
  31. package/build/plugin/next-config-loader.mjs.map +1 -0
  32. package/build/plugin/next-locale-client-loader.cjs +9 -0
  33. package/build/plugin/next-locale-client-loader.d.cts +8 -0
  34. package/build/plugin/next-locale-client-loader.d.cts.map +1 -0
  35. package/build/plugin/next-locale-client-loader.d.mts +9 -0
  36. package/build/plugin/next-locale-client-loader.d.mts.map +1 -0
  37. package/build/plugin/next-locale-client-loader.mjs +10 -0
  38. package/build/plugin/next-locale-client-loader.mjs.map +1 -0
  39. package/build/plugin/next-locale-server-loader.cjs +9 -0
  40. package/build/plugin/next-locale-server-loader.d.cts +8 -0
  41. package/build/plugin/next-locale-server-loader.d.cts.map +1 -0
  42. package/build/plugin/next-locale-server-loader.d.mts +9 -0
  43. package/build/plugin/next-locale-server-loader.d.mts.map +1 -0
  44. package/build/plugin/next-locale-server-loader.mjs +10 -0
  45. package/build/plugin/next-locale-server-loader.mjs.map +1 -0
  46. package/build/plugin/next.cjs +220 -0
  47. package/build/plugin/next.d.cts +9 -0
  48. package/build/plugin/next.d.cts.map +1 -0
  49. package/build/plugin/next.d.mts +9 -0
  50. package/build/plugin/next.d.mts.map +1 -0
  51. package/build/plugin/next.mjs +222 -0
  52. package/build/plugin/next.mjs.map +1 -0
  53. package/build/plugin/transform/babel-compat.cjs +13 -0
  54. package/build/plugin/transform/babel-compat.mjs +10 -0
  55. package/build/plugin/transform/babel-compat.mjs.map +1 -0
  56. package/build/plugin/transform/index.cjs +44 -0
  57. package/build/plugin/transform/index.mjs +42 -0
  58. package/build/plugin/transform/index.mjs.map +1 -0
  59. package/build/plugin/transform/metadata.cjs +142 -0
  60. package/build/plugin/transform/metadata.mjs +141 -0
  61. package/build/plugin/transform/metadata.mjs.map +1 -0
  62. package/build/plugin/transform/parse-override.cjs +145 -0
  63. package/build/plugin/transform/parse-override.mjs +144 -0
  64. package/build/plugin/transform/parse-override.mjs.map +1 -0
  65. package/build/plugin/transform/process-file.cjs +391 -0
  66. package/build/plugin/transform/process-file.mjs +390 -0
  67. package/build/plugin/transform/process-file.mjs.map +1 -0
  68. package/build/plugin/transform/use-i18n.cjs +8 -0
  69. package/build/plugin/transform/use-i18n.mjs +7 -0
  70. package/build/plugin/transform/use-i18n.mjs.map +1 -0
  71. package/build/plugin/transform/utils.cjs +205 -0
  72. package/build/plugin/transform/utils.mjs +192 -0
  73. package/build/plugin/transform/utils.mjs.map +1 -0
  74. package/build/plugin/unplugin.cjs +188 -0
  75. package/build/plugin/unplugin.d.cts +8 -0
  76. package/build/plugin/unplugin.d.cts.map +1 -0
  77. package/build/plugin/unplugin.d.mts +8 -0
  78. package/build/plugin/unplugin.d.mts.map +1 -0
  79. package/build/plugin/unplugin.mjs +186 -0
  80. package/build/plugin/unplugin.mjs.map +1 -0
  81. package/build/plugin/vite.cjs +28 -0
  82. package/build/plugin/vite.d.cts +9 -0
  83. package/build/plugin/vite.d.cts.map +1 -0
  84. package/build/plugin/vite.d.mts +9 -0
  85. package/build/plugin/vite.d.mts.map +1 -0
  86. package/build/plugin/vite.mjs +29 -0
  87. package/build/plugin/vite.mjs.map +1 -0
  88. package/build/plugin/webpack.cjs +27 -0
  89. package/build/plugin/webpack.d.cts +8 -0
  90. package/build/plugin/webpack.d.cts.map +1 -0
  91. package/build/plugin/webpack.d.mts +8 -0
  92. package/build/plugin/webpack.d.mts.map +1 -0
  93. package/build/plugin/webpack.mjs +28 -0
  94. package/build/plugin/webpack.mjs.map +1 -0
  95. package/build/react/client/index.cjs +9 -0
  96. package/build/react/client/index.d.cts +5 -0
  97. package/build/react/client/index.d.mts +5 -0
  98. package/build/react/client/index.mjs +6 -0
  99. package/build/react/client/useTranslation.cjs +71 -0
  100. package/build/react/client/useTranslation.d.cts +42 -0
  101. package/build/react/client/useTranslation.d.cts.map +1 -0
  102. package/build/react/client/useTranslation.d.mts +42 -0
  103. package/build/react/client/useTranslation.d.mts.map +1 -0
  104. package/build/react/client/useTranslation.mjs +71 -0
  105. package/build/react/client/useTranslation.mjs.map +1 -0
  106. package/build/react/next/client.cjs +25 -0
  107. package/build/react/next/client.d.cts +9 -0
  108. package/build/react/next/client.d.cts.map +1 -0
  109. package/build/react/next/client.d.mts +9 -0
  110. package/build/react/next/client.d.mts.map +1 -0
  111. package/build/react/next/client.mjs +24 -0
  112. package/build/react/next/client.mjs.map +1 -0
  113. package/build/react/next/cookie-locale-resolver.cjs +29 -0
  114. package/build/react/next/cookie-locale-resolver.d.cts +33 -0
  115. package/build/react/next/cookie-locale-resolver.d.cts.map +1 -0
  116. package/build/react/next/cookie-locale-resolver.d.mts +33 -0
  117. package/build/react/next/cookie-locale-resolver.d.mts.map +1 -0
  118. package/build/react/next/cookie-locale-resolver.mjs +29 -0
  119. package/build/react/next/cookie-locale-resolver.mjs.map +1 -0
  120. package/build/react/next/server.cjs +21 -0
  121. package/build/react/next/server.d.cts +13 -0
  122. package/build/react/next/server.d.cts.map +1 -0
  123. package/build/react/next/server.d.mts +14 -0
  124. package/build/react/next/server.d.mts.map +1 -0
  125. package/build/react/next/server.mjs +20 -0
  126. package/build/react/next/server.mjs.map +1 -0
  127. package/build/react/server/ServerLingoProvider.cjs +19 -0
  128. package/build/react/server/ServerLingoProvider.d.cts +12 -0
  129. package/build/react/server/ServerLingoProvider.d.cts.map +1 -0
  130. package/build/react/server/ServerLingoProvider.d.mts +12 -0
  131. package/build/react/server/ServerLingoProvider.d.mts.map +1 -0
  132. package/build/react/server/ServerLingoProvider.mjs +19 -0
  133. package/build/react/server/ServerLingoProvider.mjs.map +1 -0
  134. package/build/react/server/index.cjs +7 -0
  135. package/build/react/server/index.d.cts +4 -0
  136. package/build/react/server/index.d.mts +4 -0
  137. package/build/react/server/index.mjs +5 -0
  138. package/build/react/server/useTranslation.cjs +60 -0
  139. package/build/react/server/useTranslation.d.cts +36 -0
  140. package/build/react/server/useTranslation.d.cts.map +1 -0
  141. package/build/react/server/useTranslation.d.mts +36 -0
  142. package/build/react/server/useTranslation.d.mts.map +1 -0
  143. package/build/react/server/useTranslation.mjs +60 -0
  144. package/build/react/server/useTranslation.mjs.map +1 -0
  145. package/build/react/server-only/index.cjs +42 -0
  146. package/build/react/server-only/index.d.cts +38 -0
  147. package/build/react/server-only/index.d.cts.map +1 -0
  148. package/build/react/server-only/index.d.mts +38 -0
  149. package/build/react/server-only/index.d.mts.map +1 -0
  150. package/build/react/server-only/index.mjs +42 -0
  151. package/build/react/server-only/index.mjs.map +1 -0
  152. package/build/react/server-only/translations.cjs +85 -0
  153. package/build/react/server-only/translations.mjs +85 -0
  154. package/build/react/server-only/translations.mjs.map +1 -0
  155. package/build/react/shared/LingoContext.cjs +14 -0
  156. package/build/react/shared/LingoContext.d.cts +41 -0
  157. package/build/react/shared/LingoContext.d.cts.map +1 -0
  158. package/build/react/shared/LingoContext.d.mts +41 -0
  159. package/build/react/shared/LingoContext.d.mts.map +1 -0
  160. package/build/react/shared/LingoContext.mjs +13 -0
  161. package/build/react/shared/LingoContext.mjs.map +1 -0
  162. package/build/react/shared/LingoProvider.cjs +274 -0
  163. package/build/react/shared/LingoProvider.d.cts +76 -0
  164. package/build/react/shared/LingoProvider.d.cts.map +1 -0
  165. package/build/react/shared/LingoProvider.d.mts +76 -0
  166. package/build/react/shared/LingoProvider.d.mts.map +1 -0
  167. package/build/react/shared/LingoProvider.mjs +274 -0
  168. package/build/react/shared/LingoProvider.mjs.map +1 -0
  169. package/build/react/shared/LocaleSwitcher.cjs +61 -0
  170. package/build/react/shared/LocaleSwitcher.d.cts +71 -0
  171. package/build/react/shared/LocaleSwitcher.d.cts.map +1 -0
  172. package/build/react/shared/LocaleSwitcher.d.mts +71 -0
  173. package/build/react/shared/LocaleSwitcher.d.mts.map +1 -0
  174. package/build/react/shared/LocaleSwitcher.mjs +61 -0
  175. package/build/react/shared/LocaleSwitcher.mjs.map +1 -0
  176. package/build/react/shared/render-rich-text.cjs +55 -0
  177. package/build/react/shared/render-rich-text.d.cts +17 -0
  178. package/build/react/shared/render-rich-text.d.cts.map +1 -0
  179. package/build/react/shared/render-rich-text.d.mts +17 -0
  180. package/build/react/shared/render-rich-text.d.mts.map +1 -0
  181. package/build/react/shared/render-rich-text.mjs +54 -0
  182. package/build/react/shared/render-rich-text.mjs.map +1 -0
  183. package/build/react/shared/utils.cjs +34 -0
  184. package/build/react/shared/utils.mjs +35 -0
  185. package/build/react/shared/utils.mjs.map +1 -0
  186. package/build/react/types.d.cts +16 -0
  187. package/build/react/types.d.cts.map +1 -0
  188. package/build/react/types.d.mts +16 -0
  189. package/build/react/types.d.mts.map +1 -0
  190. package/build/translation-server/logger.cjs +37 -0
  191. package/build/translation-server/logger.mjs +37 -0
  192. package/build/translation-server/logger.mjs.map +1 -0
  193. package/build/translation-server/translation-server.cjs +547 -0
  194. package/build/translation-server/translation-server.mjs +544 -0
  195. package/build/translation-server/translation-server.mjs.map +1 -0
  196. package/build/translation-server/ws-events.cjs +15 -0
  197. package/build/translation-server/ws-events.mjs +15 -0
  198. package/build/translation-server/ws-events.mjs.map +1 -0
  199. package/build/translators/api.cjs +12 -0
  200. package/build/translators/api.mjs +12 -0
  201. package/build/translators/api.mjs.map +1 -0
  202. package/build/translators/cache-factory.cjs +26 -0
  203. package/build/translators/cache-factory.mjs +27 -0
  204. package/build/translators/cache-factory.mjs.map +1 -0
  205. package/build/translators/lingo/model-factory.cjs +179 -0
  206. package/build/translators/lingo/model-factory.mjs +174 -0
  207. package/build/translators/lingo/model-factory.mjs.map +1 -0
  208. package/build/translators/lingo/prompt.cjs +43 -0
  209. package/build/translators/lingo/prompt.mjs +43 -0
  210. package/build/translators/lingo/prompt.mjs.map +1 -0
  211. package/build/translators/lingo/service.cjs +152 -0
  212. package/build/translators/lingo/service.mjs +152 -0
  213. package/build/translators/lingo/service.mjs.map +1 -0
  214. package/build/translators/lingo/shots.cjs +28 -0
  215. package/build/translators/lingo/shots.mjs +28 -0
  216. package/build/translators/lingo/shots.mjs.map +1 -0
  217. package/build/translators/local-cache.cjs +115 -0
  218. package/build/translators/local-cache.mjs +113 -0
  219. package/build/translators/local-cache.mjs.map +1 -0
  220. package/build/translators/parse-xml.cjs +109 -0
  221. package/build/translators/parse-xml.mjs +108 -0
  222. package/build/translators/parse-xml.mjs.map +1 -0
  223. package/build/translators/pluralization/icu-validator.cjs +36 -0
  224. package/build/translators/pluralization/icu-validator.mjs +36 -0
  225. package/build/translators/pluralization/icu-validator.mjs.map +1 -0
  226. package/build/translators/pluralization/pattern-detector.cjs +25 -0
  227. package/build/translators/pluralization/pattern-detector.mjs +25 -0
  228. package/build/translators/pluralization/pattern-detector.mjs.map +1 -0
  229. package/build/translators/pluralization/prompt.cjs +98 -0
  230. package/build/translators/pluralization/prompt.mjs +98 -0
  231. package/build/translators/pluralization/prompt.mjs.map +1 -0
  232. package/build/translators/pluralization/service.cjs +247 -0
  233. package/build/translators/pluralization/service.mjs +247 -0
  234. package/build/translators/pluralization/service.mjs.map +1 -0
  235. package/build/translators/pluralization/shots.cjs +53 -0
  236. package/build/translators/pluralization/shots.mjs +53 -0
  237. package/build/translators/pluralization/shots.mjs.map +1 -0
  238. package/build/translators/pluralization/types.d.cts +17 -0
  239. package/build/translators/pluralization/types.d.cts.map +1 -0
  240. package/build/translators/pluralization/types.d.mts +17 -0
  241. package/build/translators/pluralization/types.d.mts.map +1 -0
  242. package/build/translators/pseudotranslator/index.cjs +129 -0
  243. package/build/translators/pseudotranslator/index.mjs +129 -0
  244. package/build/translators/pseudotranslator/index.mjs.map +1 -0
  245. package/build/translators/translation-service.cjs +182 -0
  246. package/build/translators/translation-service.mjs +183 -0
  247. package/build/translators/translation-service.mjs.map +1 -0
  248. package/build/translators/translator-factory.cjs +49 -0
  249. package/build/translators/translator-factory.mjs +50 -0
  250. package/build/translators/translator-factory.mjs.map +1 -0
  251. package/build/types.d.cts +161 -0
  252. package/build/types.d.cts.map +1 -0
  253. package/build/types.d.mts +161 -0
  254. package/build/types.d.mts.map +1 -0
  255. package/build/utils/config-factory.cjs +58 -0
  256. package/build/utils/config-factory.mjs +58 -0
  257. package/build/utils/config-factory.mjs.map +1 -0
  258. package/build/utils/hash.cjs +17 -0
  259. package/build/utils/hash.mjs +16 -0
  260. package/build/utils/hash.mjs.map +1 -0
  261. package/build/utils/is-valid-locale.cjs +14 -0
  262. package/build/utils/is-valid-locale.mjs +14 -0
  263. package/build/utils/is-valid-locale.mjs.map +1 -0
  264. package/build/utils/logger.cjs +51 -0
  265. package/build/utils/logger.mjs +50 -0
  266. package/build/utils/logger.mjs.map +1 -0
  267. package/build/utils/path-helpers.cjs +49 -0
  268. package/build/utils/path-helpers.mjs +47 -0
  269. package/build/utils/path-helpers.mjs.map +1 -0
  270. package/build/utils/timeout.cjs +42 -0
  271. package/build/utils/timeout.mjs +41 -0
  272. package/build/utils/timeout.mjs.map +1 -0
  273. package/build/virtual/code-generator.cjs +54 -0
  274. package/build/virtual/code-generator.mjs +53 -0
  275. package/build/virtual/code-generator.mjs.map +1 -0
  276. package/build/virtual/config.cjs +10 -0
  277. package/build/virtual/config.d.cts +9 -0
  278. package/build/virtual/config.d.cts.map +1 -0
  279. package/build/virtual/config.d.mts +9 -0
  280. package/build/virtual/config.d.mts.map +1 -0
  281. package/build/virtual/config.mjs +8 -0
  282. package/build/virtual/config.mjs.map +1 -0
  283. package/build/virtual/locale/client.cjs +23 -0
  284. package/build/virtual/locale/client.d.cts +19 -0
  285. package/build/virtual/locale/client.d.cts.map +1 -0
  286. package/build/virtual/locale/client.d.mts +19 -0
  287. package/build/virtual/locale/client.d.mts.map +1 -0
  288. package/build/virtual/locale/client.mjs +22 -0
  289. package/build/virtual/locale/client.mjs.map +1 -0
  290. package/build/virtual/locale/server.cjs +13 -0
  291. package/build/virtual/locale/server.d.cts +13 -0
  292. package/build/virtual/locale/server.d.cts.map +1 -0
  293. package/build/virtual/locale/server.d.mts +13 -0
  294. package/build/virtual/locale/server.d.mts.map +1 -0
  295. package/build/virtual/locale/server.mjs +13 -0
  296. package/build/virtual/locale/server.mjs.map +1 -0
  297. package/build/widget/lingo-dev-widget.cjs +228 -0
  298. package/build/widget/lingo-dev-widget.mjs +229 -0
  299. package/build/widget/lingo-dev-widget.mjs.map +1 -0
  300. package/package.json +189 -0
@@ -0,0 +1,205 @@
1
+ const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
2
+ const require_use_i18n = require('./use-i18n.cjs');
3
+ const require_hash = require('../../utils/hash.cjs');
4
+ let _babel_types = require("@babel/types");
5
+ _babel_types = require_rolldown_runtime.__toESM(_babel_types);
6
+
7
+ //#region src/plugin/transform/utils.ts
8
+ const root = "@lingo.dev/compiler";
9
+ /**
10
+ * Normalize whitespace in translatable text.
11
+ * - Collapses multiple spaces/tabs/newlines into a single space
12
+ * - Preserves single spaces between words
13
+ * - Trims leading and trailing whitespace
14
+ *
15
+ * Example: "Hello\n world \n foo" → "Hello world foo"
16
+ */
17
+ function normalizeWhitespace(text) {
18
+ return text.replace(/\s+/g, " ").trim();
19
+ }
20
+ /**
21
+ * Escape literal angle brackets that are not part of ICU MessageFormat tags
22
+ */
23
+ function escapeTextForICU(text) {
24
+ return text.replace(/'/g, "''").replace(/\{/g, "'{'").replace(/}/g, "'}'").replace(/</g, "'<'").replace(/#/g, "'#'");
25
+ }
26
+ /**
27
+ * Detect if a function is a React component by checking if it returns JSX
28
+ */
29
+ function isReactComponent(path) {
30
+ if (path.isArrowFunctionExpression()) {
31
+ const body = path.node.body;
32
+ if (body.type === "JSXElement" || body.type === "JSXFragment") return true;
33
+ }
34
+ let returnsJSX = false;
35
+ path.traverse({ ReturnStatement(returnPath) {
36
+ const argument = returnPath.node.argument;
37
+ if (argument && (argument.type === "JSXElement" || argument.type === "JSXFragment")) returnsJSX = true;
38
+ } });
39
+ return returnsJSX;
40
+ }
41
+ /**
42
+ * Infer component name from various patterns
43
+ */
44
+ function inferComponentName(path) {
45
+ if (path.node.id && path.node.id.name) return path.node.id.name;
46
+ const parent = path.parent;
47
+ if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier") return parent.id.name;
48
+ if (parent.type === "ExportDefaultDeclaration") return "default";
49
+ return null;
50
+ }
51
+ /**
52
+ * Check for 'use i18n' directive at program level
53
+ */
54
+ function hasUseI18nDirective(program) {
55
+ return (program.node.directives || []).some((directive) => directive.value.value === require_use_i18n.useI18n);
56
+ }
57
+ function createTranslationEntry(type, text, context, filePath, line, column, overrides) {
58
+ const fullContext = {
59
+ ...context,
60
+ filePath
61
+ };
62
+ return {
63
+ type,
64
+ sourceText: text,
65
+ context: fullContext,
66
+ hash: require_hash.generateTranslationHash(text, fullContext),
67
+ location: {
68
+ filePath,
69
+ line,
70
+ column
71
+ },
72
+ overrides: overrides && Object.keys(overrides).length > 0 ? overrides : void 0
73
+ };
74
+ }
75
+ /**
76
+ * Create {t(hash, fallback, { var1, var2, tag0: (chunks) => <Tag>{chunks}</Tag> })} call expression
77
+ */
78
+ function constructTranslationCall(hash, fallbackText, args) {
79
+ const callArguments = [_babel_types.stringLiteral(hash), _babel_types.stringLiteral(fallbackText)];
80
+ if (args) {
81
+ const properties = [];
82
+ for (const varName of args.variables) properties.push(_babel_types.objectProperty(_babel_types.identifier(varName), _babel_types.identifier(varName), false, true));
83
+ for (const [name, expr] of args.expressions) properties.push(_babel_types.objectProperty(_babel_types.identifier(name), expr, false, false));
84
+ for (const [tagName, element] of args.components) {
85
+ const renderFn = element.extra?.shouldTranslate === false ? _babel_types.arrowFunctionExpression([], element) : _babel_types.arrowFunctionExpression([_babel_types.identifier("chunks")], _babel_types.jsxElement(_babel_types.jsxOpeningElement(element.openingElement.name, element.openingElement.attributes, false), _babel_types.jsxClosingElement(element.closingElement?.name || element.openingElement.name), [_babel_types.jsxExpressionContainer(_babel_types.identifier("chunks"))], false));
86
+ properties.push(_babel_types.objectProperty(_babel_types.identifier(tagName), renderFn, false, false));
87
+ }
88
+ if (properties.length > 0) callArguments.push(_babel_types.objectExpression(properties));
89
+ }
90
+ return _babel_types.jsxExpressionContainer(_babel_types.callExpression(_babel_types.identifier("t"), callArguments));
91
+ }
92
+ /**
93
+ * Check if a JSX element is self-closing or empty
94
+ */
95
+ function isVoidElement(element) {
96
+ return element.openingElement.selfClosing || element.children.length === 0;
97
+ }
98
+ /**
99
+ * Create unified import: import { useTranslation } from "@lingo.dev/compiler/react"
100
+ *
101
+ * Via conditional exports, this resolves to:
102
+ * - server.ts in Server Components (React cache + use)
103
+ * - index.ts in Client Components (Context)
104
+ */
105
+ function comstructUnifiedImport() {
106
+ return _babel_types.importDeclaration([_babel_types.importSpecifier(_babel_types.identifier("useTranslation"), _babel_types.identifier("useTranslation"))], _babel_types.stringLiteral(`${root}/react`));
107
+ }
108
+ /**
109
+ * Create import for server components: import { getServerTranslations } from "..."
110
+ */
111
+ function constructServerImport() {
112
+ return _babel_types.importDeclaration([_babel_types.importSpecifier(_babel_types.identifier("getServerTranslations"), _babel_types.identifier("getServerTranslations"))], _babel_types.stringLiteral(`${root}/react/server`));
113
+ }
114
+ /**
115
+ * Constructs a server translation hook call using provided configuration, server port, and hashes.
116
+ * const { t } = await getServerTranslations({ ... })
117
+ *
118
+ * @param {Object} options - The input parameters.
119
+ * @param {string[]} options.hashes - An array of hash strings related to translations.
120
+ * @return {VariableDeclaration} - Returns an object containing the constructed translation hook code to be used on the server.
121
+ */
122
+ function constructServerTranslationHookCall({ hashes, needsLocale = false }) {
123
+ const optionsProperties = [];
124
+ const hashArray = _babel_types.arrayExpression(hashes.map((hash) => _babel_types.stringLiteral(hash)));
125
+ optionsProperties.push(_babel_types.objectProperty(_babel_types.identifier("hashes"), hashArray));
126
+ const destructureProperties = [_babel_types.objectProperty(_babel_types.identifier("t"), _babel_types.identifier("t"), false, true)];
127
+ if (needsLocale) destructureProperties.push(_babel_types.objectProperty(_babel_types.identifier("locale"), _babel_types.identifier("locale"), false, true));
128
+ return _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(_babel_types.objectPattern(destructureProperties), _babel_types.awaitExpression(_babel_types.callExpression(_babel_types.identifier("getServerTranslations"), [_babel_types.objectExpression(optionsProperties)])))]);
129
+ }
130
+ /**
131
+ * Inject unified `const t = useTranslation([...hashes])` at component start
132
+ *
133
+ * This hook works in BOTH Server and Client Components via conditional exports:
134
+ * - In Server Components: loads server.ts (uses React cache() + use())
135
+ * - In Client Components: loads index.ts (uses Context)
136
+ *
137
+ * This is the new default for non-async components!
138
+ *
139
+ * @param componentPath
140
+ * @param hashes
141
+ * @param needsLocale If true, destructures locale from hook: const { t, locale } = ...
142
+ */
143
+ function injectUnifiedHook(componentPath, hashes, needsLocale = false) {
144
+ const body = componentPath.get("body");
145
+ let blockBody;
146
+ if (!body.isBlockStatement()) {
147
+ if (componentPath.isArrowFunctionExpression() && body.isExpression()) {
148
+ const returnStatement = _babel_types.returnStatement(body.node);
149
+ componentPath.node.body = _babel_types.blockStatement([returnStatement]);
150
+ blockBody = componentPath.get("body");
151
+ }
152
+ } else blockBody = body;
153
+ if (!blockBody) return;
154
+ const hashArray = _babel_types.arrayExpression(hashes.map((hash) => _babel_types.stringLiteral(hash)));
155
+ const pattern = _babel_types.objectPattern([_babel_types.objectProperty(_babel_types.identifier("t"), _babel_types.identifier("t"), false, true)]);
156
+ if (needsLocale) pattern.properties.push(_babel_types.objectProperty(_babel_types.identifier("locale"), _babel_types.identifier("locale"), false, true));
157
+ const hookCall = _babel_types.variableDeclaration("const", [_babel_types.variableDeclarator(pattern, _babel_types.callExpression(_babel_types.identifier("useTranslation"), [hashArray]))]);
158
+ blockBody.node.body.unshift(hookCall);
159
+ }
160
+ /**
161
+ * Inject `const { t } = await getServerTranslations([...hashes])` at component start (Server Components)
162
+ * Makes the component async if needed
163
+ *
164
+ * @param componentPath
165
+ * @param hashes
166
+ * @param needsLocale If true, destructures locale from hook: const { t, locale } = ...
167
+ */
168
+ function injectServerHook(componentPath, hashes, needsLocale = false) {
169
+ const body = componentPath.get("body");
170
+ if (!body.isBlockStatement()) {
171
+ if (componentPath.isArrowFunctionExpression() && body.isExpression()) {
172
+ const returnStatement = _babel_types.returnStatement(body.node);
173
+ componentPath.node.body = _babel_types.blockStatement([returnStatement]);
174
+ componentPath.node.async = true;
175
+ const newBody = componentPath.get("body");
176
+ const serverCall$1 = constructServerTranslationHookCall({
177
+ hashes,
178
+ needsLocale
179
+ });
180
+ newBody.node.body.unshift(serverCall$1);
181
+ }
182
+ return;
183
+ }
184
+ if (!componentPath.node.async) componentPath.node.async = true;
185
+ const serverCall = constructServerTranslationHookCall({
186
+ hashes,
187
+ needsLocale
188
+ });
189
+ body.node.body.unshift(serverCall);
190
+ }
191
+
192
+ //#endregion
193
+ exports.comstructUnifiedImport = comstructUnifiedImport;
194
+ exports.constructServerImport = constructServerImport;
195
+ exports.constructServerTranslationHookCall = constructServerTranslationHookCall;
196
+ exports.constructTranslationCall = constructTranslationCall;
197
+ exports.createTranslationEntry = createTranslationEntry;
198
+ exports.escapeTextForICU = escapeTextForICU;
199
+ exports.hasUseI18nDirective = hasUseI18nDirective;
200
+ exports.inferComponentName = inferComponentName;
201
+ exports.injectServerHook = injectServerHook;
202
+ exports.injectUnifiedHook = injectUnifiedHook;
203
+ exports.isReactComponent = isReactComponent;
204
+ exports.isVoidElement = isVoidElement;
205
+ exports.normalizeWhitespace = normalizeWhitespace;
@@ -0,0 +1,192 @@
1
+ import { useI18n } from "./use-i18n.mjs";
2
+ import { generateTranslationHash } from "../../utils/hash.mjs";
3
+ import * as t from "@babel/types";
4
+
5
+ //#region src/plugin/transform/utils.ts
6
+ const root = "@lingo.dev/compiler";
7
+ /**
8
+ * Normalize whitespace in translatable text.
9
+ * - Collapses multiple spaces/tabs/newlines into a single space
10
+ * - Preserves single spaces between words
11
+ * - Trims leading and trailing whitespace
12
+ *
13
+ * Example: "Hello\n world \n foo" → "Hello world foo"
14
+ */
15
+ function normalizeWhitespace(text) {
16
+ return text.replace(/\s+/g, " ").trim();
17
+ }
18
+ /**
19
+ * Escape literal angle brackets that are not part of ICU MessageFormat tags
20
+ */
21
+ function escapeTextForICU(text) {
22
+ return text.replace(/'/g, "''").replace(/\{/g, "'{'").replace(/}/g, "'}'").replace(/</g, "'<'").replace(/#/g, "'#'");
23
+ }
24
+ /**
25
+ * Detect if a function is a React component by checking if it returns JSX
26
+ */
27
+ function isReactComponent(path) {
28
+ if (path.isArrowFunctionExpression()) {
29
+ const body = path.node.body;
30
+ if (body.type === "JSXElement" || body.type === "JSXFragment") return true;
31
+ }
32
+ let returnsJSX = false;
33
+ path.traverse({ ReturnStatement(returnPath) {
34
+ const argument = returnPath.node.argument;
35
+ if (argument && (argument.type === "JSXElement" || argument.type === "JSXFragment")) returnsJSX = true;
36
+ } });
37
+ return returnsJSX;
38
+ }
39
+ /**
40
+ * Infer component name from various patterns
41
+ */
42
+ function inferComponentName(path) {
43
+ if (path.node.id && path.node.id.name) return path.node.id.name;
44
+ const parent = path.parent;
45
+ if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier") return parent.id.name;
46
+ if (parent.type === "ExportDefaultDeclaration") return "default";
47
+ return null;
48
+ }
49
+ /**
50
+ * Check for 'use i18n' directive at program level
51
+ */
52
+ function hasUseI18nDirective(program) {
53
+ return (program.node.directives || []).some((directive) => directive.value.value === useI18n);
54
+ }
55
+ function createTranslationEntry(type, text, context, filePath, line, column, overrides) {
56
+ const fullContext = {
57
+ ...context,
58
+ filePath
59
+ };
60
+ return {
61
+ type,
62
+ sourceText: text,
63
+ context: fullContext,
64
+ hash: generateTranslationHash(text, fullContext),
65
+ location: {
66
+ filePath,
67
+ line,
68
+ column
69
+ },
70
+ overrides: overrides && Object.keys(overrides).length > 0 ? overrides : void 0
71
+ };
72
+ }
73
+ /**
74
+ * Create {t(hash, fallback, { var1, var2, tag0: (chunks) => <Tag>{chunks}</Tag> })} call expression
75
+ */
76
+ function constructTranslationCall(hash, fallbackText, args) {
77
+ const callArguments = [t.stringLiteral(hash), t.stringLiteral(fallbackText)];
78
+ if (args) {
79
+ const properties = [];
80
+ for (const varName of args.variables) properties.push(t.objectProperty(t.identifier(varName), t.identifier(varName), false, true));
81
+ for (const [name, expr] of args.expressions) properties.push(t.objectProperty(t.identifier(name), expr, false, false));
82
+ for (const [tagName, element] of args.components) {
83
+ const renderFn = element.extra?.shouldTranslate === false ? t.arrowFunctionExpression([], element) : t.arrowFunctionExpression([t.identifier("chunks")], t.jsxElement(t.jsxOpeningElement(element.openingElement.name, element.openingElement.attributes, false), t.jsxClosingElement(element.closingElement?.name || element.openingElement.name), [t.jsxExpressionContainer(t.identifier("chunks"))], false));
84
+ properties.push(t.objectProperty(t.identifier(tagName), renderFn, false, false));
85
+ }
86
+ if (properties.length > 0) callArguments.push(t.objectExpression(properties));
87
+ }
88
+ return t.jsxExpressionContainer(t.callExpression(t.identifier("t"), callArguments));
89
+ }
90
+ /**
91
+ * Check if a JSX element is self-closing or empty
92
+ */
93
+ function isVoidElement(element) {
94
+ return element.openingElement.selfClosing || element.children.length === 0;
95
+ }
96
+ /**
97
+ * Create unified import: import { useTranslation } from "@lingo.dev/compiler/react"
98
+ *
99
+ * Via conditional exports, this resolves to:
100
+ * - server.ts in Server Components (React cache + use)
101
+ * - index.ts in Client Components (Context)
102
+ */
103
+ function comstructUnifiedImport() {
104
+ return t.importDeclaration([t.importSpecifier(t.identifier("useTranslation"), t.identifier("useTranslation"))], t.stringLiteral(`${root}/react`));
105
+ }
106
+ /**
107
+ * Create import for server components: import { getServerTranslations } from "..."
108
+ */
109
+ function constructServerImport() {
110
+ return t.importDeclaration([t.importSpecifier(t.identifier("getServerTranslations"), t.identifier("getServerTranslations"))], t.stringLiteral(`${root}/react/server`));
111
+ }
112
+ /**
113
+ * Constructs a server translation hook call using provided configuration, server port, and hashes.
114
+ * const { t } = await getServerTranslations({ ... })
115
+ *
116
+ * @param {Object} options - The input parameters.
117
+ * @param {string[]} options.hashes - An array of hash strings related to translations.
118
+ * @return {VariableDeclaration} - Returns an object containing the constructed translation hook code to be used on the server.
119
+ */
120
+ function constructServerTranslationHookCall({ hashes, needsLocale = false }) {
121
+ const optionsProperties = [];
122
+ const hashArray = t.arrayExpression(hashes.map((hash) => t.stringLiteral(hash)));
123
+ optionsProperties.push(t.objectProperty(t.identifier("hashes"), hashArray));
124
+ const destructureProperties = [t.objectProperty(t.identifier("t"), t.identifier("t"), false, true)];
125
+ if (needsLocale) destructureProperties.push(t.objectProperty(t.identifier("locale"), t.identifier("locale"), false, true));
126
+ return t.variableDeclaration("const", [t.variableDeclarator(t.objectPattern(destructureProperties), t.awaitExpression(t.callExpression(t.identifier("getServerTranslations"), [t.objectExpression(optionsProperties)])))]);
127
+ }
128
+ /**
129
+ * Inject unified `const t = useTranslation([...hashes])` at component start
130
+ *
131
+ * This hook works in BOTH Server and Client Components via conditional exports:
132
+ * - In Server Components: loads server.ts (uses React cache() + use())
133
+ * - In Client Components: loads index.ts (uses Context)
134
+ *
135
+ * This is the new default for non-async components!
136
+ *
137
+ * @param componentPath
138
+ * @param hashes
139
+ * @param needsLocale If true, destructures locale from hook: const { t, locale } = ...
140
+ */
141
+ function injectUnifiedHook(componentPath, hashes, needsLocale = false) {
142
+ const body = componentPath.get("body");
143
+ let blockBody;
144
+ if (!body.isBlockStatement()) {
145
+ if (componentPath.isArrowFunctionExpression() && body.isExpression()) {
146
+ const returnStatement = t.returnStatement(body.node);
147
+ componentPath.node.body = t.blockStatement([returnStatement]);
148
+ blockBody = componentPath.get("body");
149
+ }
150
+ } else blockBody = body;
151
+ if (!blockBody) return;
152
+ const hashArray = t.arrayExpression(hashes.map((hash) => t.stringLiteral(hash)));
153
+ const pattern = t.objectPattern([t.objectProperty(t.identifier("t"), t.identifier("t"), false, true)]);
154
+ if (needsLocale) pattern.properties.push(t.objectProperty(t.identifier("locale"), t.identifier("locale"), false, true));
155
+ const hookCall = t.variableDeclaration("const", [t.variableDeclarator(pattern, t.callExpression(t.identifier("useTranslation"), [hashArray]))]);
156
+ blockBody.node.body.unshift(hookCall);
157
+ }
158
+ /**
159
+ * Inject `const { t } = await getServerTranslations([...hashes])` at component start (Server Components)
160
+ * Makes the component async if needed
161
+ *
162
+ * @param componentPath
163
+ * @param hashes
164
+ * @param needsLocale If true, destructures locale from hook: const { t, locale } = ...
165
+ */
166
+ function injectServerHook(componentPath, hashes, needsLocale = false) {
167
+ const body = componentPath.get("body");
168
+ if (!body.isBlockStatement()) {
169
+ if (componentPath.isArrowFunctionExpression() && body.isExpression()) {
170
+ const returnStatement = t.returnStatement(body.node);
171
+ componentPath.node.body = t.blockStatement([returnStatement]);
172
+ componentPath.node.async = true;
173
+ const newBody = componentPath.get("body");
174
+ const serverCall$1 = constructServerTranslationHookCall({
175
+ hashes,
176
+ needsLocale
177
+ });
178
+ newBody.node.body.unshift(serverCall$1);
179
+ }
180
+ return;
181
+ }
182
+ if (!componentPath.node.async) componentPath.node.async = true;
183
+ const serverCall = constructServerTranslationHookCall({
184
+ hashes,
185
+ needsLocale
186
+ });
187
+ body.node.body.unshift(serverCall);
188
+ }
189
+
190
+ //#endregion
191
+ export { comstructUnifiedImport, constructServerImport, constructServerTranslationHookCall, constructTranslationCall, createTranslationEntry, escapeTextForICU, hasUseI18nDirective, inferComponentName, injectServerHook, injectUnifiedHook, isReactComponent, isVoidElement, normalizeWhitespace };
192
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.mjs","names":["callArguments: t.Expression[]","properties: t.ObjectProperty[]","blockBody: NodePath<t.BlockStatement> | undefined","serverCall"],"sources":["../../../src/plugin/transform/utils.ts"],"sourcesContent":["import type { TranslationEntry } from \"../../types\";\nimport type { NodePath } from \"@babel/traverse\";\nimport type { VariableDeclaration } from \"@babel/types\";\nimport * as t from \"@babel/types\";\nimport { generateTranslationHash } from \"../../utils/hash\";\nimport { useI18n } from \"./use-i18n\";\n\ntype TranslationEntryByType = {\n [T in TranslationEntry as T[\"type\"]]: T;\n};\n\nconst root = \"@lingo.dev/compiler\";\n// TODO (AleksandrSl 28/11/2025): See jsx-content.ts in the old compiler for future improvements.\n\n/**\n * Normalize whitespace in translatable text.\n * - Collapses multiple spaces/tabs/newlines into a single space\n * - Preserves single spaces between words\n * - Trims leading and trailing whitespace\n *\n * Example: \"Hello\\n world \\n foo\" → \"Hello world foo\"\n */\nexport function normalizeWhitespace(text: string): string {\n return text\n .replace(/\\s+/g, \" \") // Collapse all whitespace sequences to single space\n .trim(); // Remove leading/trailing whitespace\n}\n\n/**\n * Escape literal angle brackets that are not part of ICU MessageFormat tags\n */\nexport function escapeTextForICU(text: string): string {\n // Related spec - https://unicode-org.github.io/icu/userguide/format_parse/messages/#quotingescaping\n return text\n .replace(/'/g, \"''\")\n .replace(/\\{/g, \"'{'\")\n .replace(/}/g, \"'}'\")\n .replace(/</g, \"'<'\")\n .replace(/#/g, \"'#'\");\n}\n\n/**\n * Detect if a function is a React component by checking if it returns JSX\n */\nexport function isReactComponent(\n path: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n): boolean {\n // Check for arrow function with JSX expression body: () => <div>...</div>\n if (path.isArrowFunctionExpression()) {\n const body = path.node.body;\n if (body.type === \"JSXElement\" || body.type === \"JSXFragment\") {\n return true;\n }\n }\n\n // Babel traverses with DFS so we get the innermost function return first if any.\n // But if at least one function returns JSX I guess we should transform the whole function.\n // It's a weird corner case.\n\n // Check for explicit return statements with JSX\n // We could also check for the first JSX?\n let returnsJSX = false;\n path.traverse({\n ReturnStatement(returnPath) {\n const argument = returnPath.node.argument;\n if (\n argument &&\n (argument.type === \"JSXElement\" || argument.type === \"JSXFragment\")\n ) {\n returnsJSX = true;\n }\n },\n });\n\n // ts doesn't see what happens inside path.traverse, and erroneously narrows returnsJsx to false\n return returnsJSX as boolean;\n}\n\n/**\n * Infer component name from various patterns\n */\nexport function inferComponentName(path: NodePath<any>): string | null {\n // Named function: function MyComponent() {}\n if (path.node.id && path.node.id.name) {\n return path.node.id.name;\n }\n\n // Variable declaration: const MyComponent = () => {}\n const parent = path.parent;\n if (parent.type === \"VariableDeclarator\" && parent.id.type === \"Identifier\") {\n return parent.id.name;\n }\n\n // TODO (AleksandrSl 25/11/2025): It should support export const $NAME$\n // Export: export default function() {}\n if (parent.type === \"ExportDefaultDeclaration\") {\n return \"default\";\n }\n\n return null;\n}\n\n/**\n * Check for 'use i18n' directive at program level\n */\nexport function hasUseI18nDirective(program: NodePath<t.Program>): boolean {\n const directives = program.node.directives || [];\n return directives.some(\n (directive: t.Directive) => directive.value.value === useI18n,\n );\n}\n\nexport function createTranslationEntry<T extends keyof TranslationEntryByType>(\n type: T,\n text: string,\n context: Omit<TranslationEntryByType[T][\"context\"], \"filePath\">,\n filePath: string,\n line?: number,\n column?: number,\n overrides?: Record<string, string>,\n): TranslationEntryByType[T] {\n const fullContext = { ...context, filePath };\n const hash = generateTranslationHash(text, fullContext);\n\n return {\n type,\n sourceText: text,\n context: fullContext,\n hash,\n location: {\n filePath,\n line,\n column,\n },\n overrides:\n overrides && Object.keys(overrides).length > 0 ? overrides : undefined,\n // Seems like the only approach without the cast is function overloads, which are noisy. The type cast is not that bad since it's inside the function.\n } as TranslationEntryByType[T];\n}\n\n/**\n * Create {t(hash, fallback, { var1, var2, tag0: (chunks) => <Tag>{chunks}</Tag> })} call expression\n */\nexport function constructTranslationCall(\n hash: string,\n fallbackText: string,\n args?: {\n variables: string[];\n expressions: Map<string, t.Expression>;\n components: Map<string, t.JSXElement>;\n },\n): t.JSXExpressionContainer {\n const callArguments: t.Expression[] = [\n t.stringLiteral(hash),\n t.stringLiteral(fallbackText),\n ];\n\n if (args) {\n const properties: t.ObjectProperty[] = [];\n\n // Add variable properties (shorthand)\n for (const varName of args.variables) {\n properties.push(\n t.objectProperty(\n t.identifier(varName),\n t.identifier(varName),\n false,\n true, // shorthand\n ),\n );\n }\n\n for (const [name, expr] of args.expressions) {\n properties.push(t.objectProperty(t.identifier(name), expr, false, false));\n }\n\n // Add component renderer functions\n for (const [tagName, element] of args.components) {\n const renderFn =\n element.extra?.shouldTranslate === false\n ? // Even when doing: tagName: () => <Element />, we need a function for formatjs to work.\n t.arrowFunctionExpression([], element)\n : // Create: tagName: (chunks) => <Element>{chunks}</Element>\n t.arrowFunctionExpression(\n [t.identifier(\"chunks\")],\n t.jsxElement(\n t.jsxOpeningElement(\n element.openingElement.name,\n element.openingElement.attributes,\n false,\n ),\n t.jsxClosingElement(\n element.closingElement?.name || element.openingElement.name,\n ),\n [t.jsxExpressionContainer(t.identifier(\"chunks\"))],\n false,\n ),\n );\n\n properties.push(\n t.objectProperty(t.identifier(tagName), renderFn, false, false),\n );\n }\n\n if (properties.length > 0) {\n callArguments.push(t.objectExpression(properties));\n }\n }\n\n return t.jsxExpressionContainer(\n t.callExpression(t.identifier(\"t\"), callArguments),\n );\n}\n\n/**\n * Check if a JSX element is self-closing or empty\n */\nexport function isVoidElement(element: t.JSXElement): boolean {\n return element.openingElement.selfClosing || element.children.length === 0;\n}\n\n/**\n * Create unified import: import { useTranslation } from \"@lingo.dev/compiler/react\"\n *\n * Via conditional exports, this resolves to:\n * - server.ts in Server Components (React cache + use)\n * - index.ts in Client Components (Context)\n */\nexport function comstructUnifiedImport(): t.ImportDeclaration {\n return t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"useTranslation\"),\n t.identifier(\"useTranslation\"),\n ),\n ],\n t.stringLiteral(`${root}/react`),\n );\n}\n\n/**\n * Create import for server components: import { getServerTranslations } from \"...\"\n */\nexport function constructServerImport(): t.ImportDeclaration {\n return t.importDeclaration(\n [\n t.importSpecifier(\n t.identifier(\"getServerTranslations\"),\n t.identifier(\"getServerTranslations\"),\n ),\n ],\n t.stringLiteral(`${root}/react/server`),\n );\n}\n\n/**\n * Constructs a server translation hook call using provided configuration, server port, and hashes.\n * const { t } = await getServerTranslations({ ... })\n *\n * @param {Object} options - The input parameters.\n * @param {string[]} options.hashes - An array of hash strings related to translations.\n * @return {VariableDeclaration} - Returns an object containing the constructed translation hook code to be used on the server.\n */\nexport function constructServerTranslationHookCall({\n hashes,\n needsLocale = false,\n}: {\n hashes: string[];\n needsLocale?: boolean;\n}): VariableDeclaration {\n const optionsProperties = [];\n\n const hashArray = t.arrayExpression(\n hashes.map((hash) => t.stringLiteral(hash)),\n );\n optionsProperties.push(t.objectProperty(t.identifier(\"hashes\"), hashArray));\n\n const destructureProperties = [\n t.objectProperty(\n t.identifier(\"t\"),\n t.identifier(\"t\"),\n false,\n true, // shorthand\n ),\n ];\n\n if (needsLocale) {\n destructureProperties.push(\n t.objectProperty(\n t.identifier(\"locale\"),\n t.identifier(\"locale\"),\n false,\n true, // shorthand\n ),\n );\n }\n\n return t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n t.objectPattern(destructureProperties),\n t.awaitExpression(\n t.callExpression(t.identifier(\"getServerTranslations\"), [\n t.objectExpression(optionsProperties),\n ]),\n ),\n ),\n ]);\n}\n\n/**\n * Inject unified `const t = useTranslation([...hashes])` at component start\n *\n * This hook works in BOTH Server and Client Components via conditional exports:\n * - In Server Components: loads server.ts (uses React cache() + use())\n * - In Client Components: loads index.ts (uses Context)\n *\n * This is the new default for non-async components!\n *\n * @param componentPath\n * @param hashes\n * @param needsLocale If true, destructures locale from hook: const { t, locale } = ...\n */\nexport function injectUnifiedHook(\n componentPath: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n hashes: string[],\n needsLocale: boolean = false,\n): void {\n const body = componentPath.get(\"body\");\n\n let blockBody: NodePath<t.BlockStatement> | undefined;\n // Handle arrow functions with expression bodies: () => <jsx>\n // Convert to block statement: () => { const t = ...; return <jsx>; }\n if (!body.isBlockStatement()) {\n if (componentPath.isArrowFunctionExpression() && body.isExpression()) {\n const returnStatement = t.returnStatement(body.node as t.Expression);\n componentPath.node.body = t.blockStatement([returnStatement]);\n // Re-get the body after conversion\n blockBody = componentPath.get(\"body\") as NodePath<t.BlockStatement>;\n }\n } else {\n blockBody = body;\n }\n\n if (!blockBody) {\n return;\n }\n\n const hashArray = t.arrayExpression(\n hashes.map((hash) => t.stringLiteral(hash)),\n );\n\n const pattern = t.objectPattern([\n t.objectProperty(t.identifier(\"t\"), t.identifier(\"t\"), false, true),\n ]);\n if (needsLocale) {\n pattern.properties.push(\n t.objectProperty(\n t.identifier(\"locale\"),\n t.identifier(\"locale\"),\n false,\n true,\n ),\n );\n }\n\n const hookCall = t.variableDeclaration(\"const\", [\n t.variableDeclarator(\n pattern,\n t.callExpression(t.identifier(\"useTranslation\"), [hashArray]),\n ),\n ]);\n\n blockBody.node.body.unshift(hookCall);\n}\n\n/**\n * Inject `const { t } = await getServerTranslations([...hashes])` at component start (Server Components)\n * Makes the component async if needed\n *\n * @param componentPath\n * @param hashes\n * @param needsLocale If true, destructures locale from hook: const { t, locale } = ...\n */\nexport function injectServerHook(\n componentPath: NodePath<\n t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression\n >,\n hashes: string[],\n needsLocale: boolean = false,\n): void {\n const body = componentPath.get(\"body\");\n\n // Handle arrow functions with expression bodies: () => <jsx>\n // Convert to block statement: async () => { const { t } = await ...; return <jsx>; }\n if (!body.isBlockStatement()) {\n if (componentPath.isArrowFunctionExpression() && body.isExpression()) {\n const returnStatement = t.returnStatement(body.node as t.Expression);\n componentPath.node.body = t.blockStatement([returnStatement]);\n componentPath.node.async = true;\n // Re-get the body after conversion\n const newBody = componentPath.get(\"body\") as NodePath<t.BlockStatement>;\n\n // Create: const { t } = await getServerTranslations({ ... })\n const serverCall = constructServerTranslationHookCall({\n hashes,\n needsLocale,\n });\n\n newBody.node.body.unshift(serverCall);\n }\n return;\n }\n\n if (!componentPath.node.async) {\n componentPath.node.async = true;\n }\n\n // Create: const { t } = await getServerTranslations({ ... })\n const serverCall = constructServerTranslationHookCall({\n hashes,\n needsLocale,\n });\n\n body.node.body.unshift(serverCall);\n}\n"],"mappings":"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"}
@@ -0,0 +1,188 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_logger = require('../utils/logger.cjs');
3
+ const require_config_factory = require('../utils/config-factory.cjs');
4
+ const require_manager = require('../metadata/manager.cjs');
5
+ const require_translation_server = require('../translation-server/translation-server.cjs');
6
+ const require_build_translator = require('./build-translator.cjs');
7
+ const require_cleanup = require('./cleanup.cjs');
8
+ const require_use_i18n = require('./transform/use-i18n.cjs');
9
+ const require_index = require('./transform/index.cjs');
10
+ const require_code_generator = require('../virtual/code-generator.cjs');
11
+ let path = require("path");
12
+ path = require_rolldown_runtime.__toESM(path);
13
+ let fs = require("fs");
14
+ fs = require_rolldown_runtime.__toESM(fs);
15
+ let unplugin = require("unplugin");
16
+
17
+ //#region src/plugin/unplugin.ts
18
+ let translationServer;
19
+ const PLUGIN_NAME = "lingo-compiler";
20
+ function tryLocalOrReturnVirtual(config, fileName, virtualName) {
21
+ const customPath = path.default.join(config.sourceRoot, config.lingoDir, fileName);
22
+ if (fs.default.existsSync(customPath)) return customPath;
23
+ return virtualName;
24
+ }
25
+ /**
26
+ * Single source of truth for virtual modules
27
+ * Each entry defines both resolver (import path → virtual ID) and loader (virtual ID → code)
28
+ *
29
+ * If customFileCheck is defined, the specified file will be first searched for, and if not found virtual module will be used.
30
+ */
31
+ const virtualModules = {
32
+ "@lingo.dev/compiler/virtual/config": {
33
+ virtualId: "\0virtual:lingo-config",
34
+ loader: (config) => require_code_generator.generateConfigModule(config),
35
+ customFileCheck: void 0
36
+ },
37
+ "@lingo.dev/compiler/virtual/locale/server": {
38
+ virtualId: "\0virtual:locale-resolver.server",
39
+ loader: (config) => require_code_generator.generateServerLocaleModule(config),
40
+ customFileCheck: "locale-resolver.server.ts"
41
+ },
42
+ "@lingo.dev/compiler/virtual/locale/client": {
43
+ virtualId: "\0virtual:locale-resolver.client",
44
+ loader: (config) => require_code_generator.generateClientLocaleModule(config),
45
+ customFileCheck: "locale-resolver.client.ts"
46
+ }
47
+ };
48
+ const virtualModulesResolvers = Object.fromEntries(Object.entries(virtualModules).map(([importPath, module$1]) => [importPath, (config) => module$1.customFileCheck ? tryLocalOrReturnVirtual(config, module$1.customFileCheck, module$1.virtualId) : module$1.virtualId]));
49
+ const virtualModulesLoaders = Object.fromEntries(Object.values(virtualModules).map((value) => [value.virtualId, value.loader]));
50
+ /**
51
+ * Universal plugin for Lingo.dev compiler
52
+ * Supports Vite, Webpack
53
+ */
54
+ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
55
+ const config = require_config_factory.createLingoConfig(options);
56
+ const isDev = config.environment === "development";
57
+ const startPort = config.dev.translationServerStartPort;
58
+ let webpackMode;
59
+ const getMetadataPath$1 = () => {
60
+ return require_manager.getMetadataPath(webpackMode ? {
61
+ ...config,
62
+ environment: webpackMode
63
+ } : config);
64
+ };
65
+ async function startServer() {
66
+ const server = await require_translation_server.startTranslationServer({
67
+ startPort,
68
+ onError: (err) => {
69
+ require_logger.logger.error("Translation server error:", err);
70
+ },
71
+ onReady: (port) => {
72
+ require_logger.logger.info(`Translation server started successfully on port: ${port}`);
73
+ },
74
+ config
75
+ });
76
+ config.dev.translationServerUrl = server.getUrl();
77
+ require_cleanup.registerCleanupOnCurrentProcess({ asyncCleanup: async () => {
78
+ await translationServer.stop();
79
+ } });
80
+ return server;
81
+ }
82
+ return {
83
+ name: PLUGIN_NAME,
84
+ enforce: "pre",
85
+ vite: {
86
+ config() {
87
+ return { optimizeDeps: { exclude: ["@lingo.dev/compiler"] } };
88
+ },
89
+ async buildStart() {
90
+ const metadataFilePath = getMetadataPath$1();
91
+ require_manager.cleanupExistingMetadata(metadataFilePath);
92
+ require_cleanup.registerCleanupOnCurrentProcess({ cleanup: () => require_manager.cleanupExistingMetadata(metadataFilePath) });
93
+ if (isDev && !translationServer) translationServer = await startServer();
94
+ },
95
+ async buildEnd() {
96
+ const metadataFilePath = getMetadataPath$1();
97
+ if (!isDev) try {
98
+ await require_build_translator.processBuildTranslations({
99
+ config,
100
+ publicOutputPath: "public/translations",
101
+ metadataFilePath
102
+ });
103
+ } catch (error) {
104
+ require_logger.logger.error("Build-time translation processing failed:", error);
105
+ }
106
+ }
107
+ },
108
+ webpack(compiler) {
109
+ webpackMode = compiler.options.mode === "development" ? "development" : "production";
110
+ const metadataFilePath = getMetadataPath$1();
111
+ config.environment = webpackMode;
112
+ compiler.hooks.initialize.tap(PLUGIN_NAME, () => {
113
+ require_manager.cleanupExistingMetadata(metadataFilePath);
114
+ require_cleanup.registerCleanupOnCurrentProcess({ cleanup: () => require_manager.cleanupExistingMetadata(metadataFilePath) });
115
+ });
116
+ compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async () => {
117
+ if (webpackMode === "development" && !translationServer) translationServer = await startServer();
118
+ });
119
+ compiler.hooks.additionalPass.tapPromise(PLUGIN_NAME, async () => {
120
+ if (webpackMode === "production") try {
121
+ await require_build_translator.processBuildTranslations({
122
+ config,
123
+ publicOutputPath: "public/translations",
124
+ metadataFilePath
125
+ });
126
+ } catch (error) {
127
+ require_logger.logger.error("Build-time translation processing failed:", error);
128
+ throw error;
129
+ }
130
+ });
131
+ compiler.hooks.shutdown.tapPromise(PLUGIN_NAME, async () => {
132
+ require_manager.cleanupExistingMetadata(metadataFilePath);
133
+ await translationServer?.stop();
134
+ });
135
+ },
136
+ resolveId(id) {
137
+ const handler = virtualModulesResolvers[id];
138
+ if (handler) return handler(config);
139
+ return null;
140
+ },
141
+ load: {
142
+ filter: { id: /virtual:/ },
143
+ handler(id) {
144
+ const handler = virtualModulesLoaders[id];
145
+ if (handler) return handler(config);
146
+ return null;
147
+ }
148
+ },
149
+ transform: {
150
+ filter: {
151
+ id: {
152
+ include: [/\.[tj]sx$/],
153
+ exclude: /node_modules/
154
+ },
155
+ code: config.useDirective ? require_use_i18n.useI18nRegex : void 0
156
+ },
157
+ async handler(code, id) {
158
+ try {
159
+ const result = require_index.transformComponent({
160
+ code,
161
+ filePath: id,
162
+ config
163
+ });
164
+ if (!result.transformed) {
165
+ require_logger.logger.debug(`No transformation needed for ${id}`);
166
+ return null;
167
+ }
168
+ const metadataManager = new require_manager.MetadataManager(getMetadataPath$1());
169
+ if (result.newEntries && result.newEntries.length > 0) {
170
+ await metadataManager.saveMetadataWithEntries(result.newEntries);
171
+ require_logger.logger.debug(`Found ${result.newEntries.length} translatable text(s) in ${id}`);
172
+ }
173
+ require_logger.logger.debug(`Returning transformed code for ${id}`);
174
+ return {
175
+ code: result.code,
176
+ map: result.map
177
+ };
178
+ } catch (error) {
179
+ require_logger.logger.error(`Transform error in ${id}:`, error);
180
+ return null;
181
+ }
182
+ }
183
+ }
184
+ };
185
+ });
186
+
187
+ //#endregion
188
+ exports.lingoUnplugin = lingoUnplugin;
@@ -0,0 +1,8 @@
1
+ import { PartialLingoConfig } from "../types.cjs";
2
+ import "unplugin";
3
+
4
+ //#region src/plugin/unplugin.d.ts
5
+ type LingoPluginOptions = PartialLingoConfig;
6
+ //#endregion
7
+ export { LingoPluginOptions };
8
+ //# sourceMappingURL=unplugin.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unplugin.d.cts","names":[],"sources":["../../src/plugin/unplugin.ts"],"sourcesContent":[],"mappings":";;;;KA6BY,kBAAA,GAAqB"}
@@ -0,0 +1,8 @@
1
+ import { PartialLingoConfig } from "../types.mjs";
2
+ import "unplugin";
3
+
4
+ //#region src/plugin/unplugin.d.ts
5
+ type LingoPluginOptions = PartialLingoConfig;
6
+ //#endregion
7
+ export { LingoPluginOptions };
8
+ //# sourceMappingURL=unplugin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unplugin.d.mts","names":[],"sources":["../../src/plugin/unplugin.ts"],"sourcesContent":[],"mappings":";;;;KA6BY,kBAAA,GAAqB"}