@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,547 @@
1
+ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
+ const require_logger = require('./logger.cjs');
3
+ const require_translator_factory = require('../translators/translator-factory.cjs');
4
+ const require_translation_service = require('../translators/translation-service.cjs');
5
+ const require_cache_factory = require('../translators/cache-factory.cjs');
6
+ const require_manager = require('../metadata/manager.cjs');
7
+ const require_ws_events = require('./ws-events.cjs');
8
+ const require_is_valid_locale = require('../utils/is-valid-locale.cjs');
9
+ let http = require("http");
10
+ http = require_rolldown_runtime.__toESM(http);
11
+ let crypto = require("crypto");
12
+ crypto = require_rolldown_runtime.__toESM(crypto);
13
+ let url = require("url");
14
+ let ws = require("ws");
15
+
16
+ //#region src/translation-server/translation-server.ts
17
+ /**
18
+ * Simple HTTP server for serving translations during development
19
+ *
20
+ * This server:
21
+ * - Finds a free port automatically
22
+ * - Serves translations via:
23
+ * - GET /translations/:locale - Full dictionary (cached)
24
+ * - POST /translations/:locale (body: { hashes: string[] }) - Batch translation
25
+ * - Uses the same translation logic as middleware
26
+ * - Can be started/stopped programmatically
27
+ */
28
+ var TranslationServer = class {
29
+ server = null;
30
+ url = void 0;
31
+ logger;
32
+ config;
33
+ configHash;
34
+ startPort;
35
+ onReadyCallback;
36
+ onErrorCallback;
37
+ translationService = null;
38
+ metadata = null;
39
+ connections = /* @__PURE__ */ new Set();
40
+ wss = null;
41
+ wsClients = /* @__PURE__ */ new Set();
42
+ activeTranslations = 0;
43
+ isBusy = false;
44
+ busyTimeout = null;
45
+ BUSY_DEBOUNCE_MS = 500;
46
+ constructor(options) {
47
+ this.config = options.config;
48
+ this.configHash = hashConfig(options.config);
49
+ this.startPort = options.startPort || 6e4;
50
+ this.onReadyCallback = options.onReady;
51
+ this.onErrorCallback = options.onError;
52
+ this.logger = require_logger.getLogger(this.config);
53
+ }
54
+ /**
55
+ * Start the server and find an available port
56
+ */
57
+ async start() {
58
+ if (this.server) throw new Error("Server is already running");
59
+ this.logger.info(`🔧 Initializing translator...`);
60
+ this.translationService = new require_translation_service.TranslationService(require_translator_factory.createTranslator(this.config, this.logger), require_cache_factory.createCache(this.config), {
61
+ sourceLocale: this.config.sourceLocale,
62
+ pluralization: this.config.pluralization
63
+ }, this.logger);
64
+ const port = await this.findAvailablePort(this.startPort);
65
+ return new Promise((resolve, reject) => {
66
+ this.server = http.default.createServer((req, res) => {
67
+ this.logger.info(`📥 Received: ${req.method} ${req.url}`);
68
+ this.handleRequest(req, res).catch((error) => {
69
+ this.logger.error(`Request handler error:`, error);
70
+ this.logger.error(error.stack);
71
+ if (!res.headersSent) {
72
+ res.writeHead(500, { "Content-Type": "application/json" });
73
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
74
+ }
75
+ });
76
+ });
77
+ this.logger.debug(`Starting translation server on port ${port}`);
78
+ this.server.on("connection", (socket) => {
79
+ this.connections.add(socket);
80
+ socket.once("close", () => {
81
+ this.connections.delete(socket);
82
+ });
83
+ });
84
+ this.server.on("error", (error) => {
85
+ if (error.code === "EADDRINUSE") reject(/* @__PURE__ */ new Error(`Port ${port} is already in use`));
86
+ else {
87
+ this.logger.error(`Translation server error: ${error.message}\n`);
88
+ this.onErrorCallback?.(error);
89
+ reject(error);
90
+ }
91
+ });
92
+ this.server.listen(port, "127.0.0.1", () => {
93
+ this.url = `http://127.0.0.1:${port}`;
94
+ this.logger.info(`Translation server listening on ${this.url}`);
95
+ this.initializeWebSocket();
96
+ this.onReadyCallback?.(port);
97
+ resolve(port);
98
+ });
99
+ });
100
+ }
101
+ /**
102
+ * Initialize WebSocket server for real-time dev widget updates
103
+ */
104
+ initializeWebSocket() {
105
+ if (!this.server) throw new Error("HTTP server must be started before WebSocket");
106
+ this.wss = new ws.WebSocketServer({ server: this.server });
107
+ this.wss.on("connection", (ws$1) => {
108
+ this.wsClients.add(ws$1);
109
+ this.logger.debug(`WebSocket client connected. Total clients: ${this.wsClients.size}`);
110
+ this.sendToClient(ws$1, require_ws_events.createEvent("connected", { serverUrl: this.url }));
111
+ ws$1.on("close", () => {
112
+ this.wsClients.delete(ws$1);
113
+ this.logger.debug(`WebSocket client disconnected. Total clients: ${this.wsClients.size}`);
114
+ });
115
+ ws$1.on("error", (error) => {
116
+ this.logger.error(`WebSocket client error:`, error);
117
+ this.wsClients.delete(ws$1);
118
+ });
119
+ });
120
+ this.wss.on("error", (error) => {
121
+ this.logger.error(`WebSocket server error:`, error);
122
+ this.onErrorCallback?.(error);
123
+ });
124
+ this.logger.info(`WebSocket server initialized`);
125
+ }
126
+ /**
127
+ * Send event to a specific WebSocket client
128
+ */
129
+ sendToClient(ws$1, event) {
130
+ if (ws$1.readyState === ws.WebSocket.OPEN) ws$1.send(JSON.stringify(event));
131
+ }
132
+ /**
133
+ * Broadcast event to all connected WebSocket clients
134
+ */
135
+ broadcast(event) {
136
+ const message = JSON.stringify(event);
137
+ for (const client of this.wsClients) if (client.readyState === ws.WebSocket.OPEN) client.send(message);
138
+ }
139
+ /**
140
+ * Stop the server
141
+ */
142
+ async stop() {
143
+ if (!this.server) {
144
+ this.logger.debug("Translation server is not running. Nothing to stop.");
145
+ return;
146
+ }
147
+ if (this.busyTimeout) {
148
+ clearTimeout(this.busyTimeout);
149
+ this.busyTimeout = null;
150
+ }
151
+ for (const client of this.wsClients) client.close();
152
+ this.wsClients.clear();
153
+ if (this.wss) {
154
+ this.wss.close();
155
+ this.wss = null;
156
+ }
157
+ for (const socket of this.connections) socket.destroy();
158
+ this.connections.clear();
159
+ return new Promise((resolve, reject) => {
160
+ this.server.close((error) => {
161
+ if (error) reject(error);
162
+ else {
163
+ this.logger.info(`Translation server stopped`);
164
+ this.server = null;
165
+ this.url = void 0;
166
+ resolve();
167
+ }
168
+ });
169
+ });
170
+ }
171
+ /**
172
+ * Get the current port (null if not running)
173
+ */
174
+ getUrl() {
175
+ return this.url;
176
+ }
177
+ /**
178
+ * Start a new server or get the URL of an existing one on the preferred port.
179
+ *
180
+ * This method optimizes for the common case where a translation server is already
181
+ * running on port 60000. If that port is taken, it checks if it's our service
182
+ * by calling the health check endpoint. If it is, we reuse it instead of starting
183
+ * a new server on a different port.
184
+ *
185
+ * @returns URL of the running server (new or existing)
186
+ */
187
+ async startOrGetUrl() {
188
+ if (this.server && this.url) {
189
+ this.logger.info(`Using existing server instance at ${this.url}`);
190
+ return this.url;
191
+ }
192
+ const preferredPort = this.startPort;
193
+ const preferredUrl = `http://127.0.0.1:${preferredPort}`;
194
+ if (await this.isPortAvailable(preferredPort)) {
195
+ this.logger.info(`Port ${preferredPort} is available, starting new server...`);
196
+ await this.start();
197
+ return this.url;
198
+ }
199
+ this.logger.info(`Port ${preferredPort} is in use, checking if it's a translation server...`);
200
+ if (await this.checkIfTranslationServer(preferredUrl)) {
201
+ this.logger.info(`✅ Found existing translation server at ${preferredUrl}, reusing it`);
202
+ this.url = preferredUrl;
203
+ return preferredUrl;
204
+ }
205
+ this.logger.info(`Port ${preferredPort} is in use by another service, finding alternative...`);
206
+ await this.start();
207
+ return this.url;
208
+ }
209
+ /**
210
+ * Check if server is running
211
+ */
212
+ isRunning() {
213
+ return this.server !== null && this.url !== null;
214
+ }
215
+ /**
216
+ * Reload metadata from disk
217
+ * Useful when metadata has been updated during runtime (e.g., new transformations)
218
+ */
219
+ async reloadMetadata() {
220
+ try {
221
+ this.metadata = await require_manager.loadMetadata(require_manager.getMetadataPath(this.config));
222
+ this.logger.debug(`Reloaded metadata: ${Object.keys(this.metadata.entries).length} entries`);
223
+ } catch (error) {
224
+ this.logger.warn("Failed to reload metadata:", error);
225
+ this.metadata = require_manager.createEmptyMetadata();
226
+ }
227
+ }
228
+ /**
229
+ * Translate the entire dictionary for a given locale
230
+ *
231
+ * This method always reloads metadata from disk before translating to ensure
232
+ * all entries added during build-time transformations are included.
233
+ *
234
+ * This is the recommended method for build-time translation generation.
235
+ */
236
+ async translateAll(locale) {
237
+ if (!this.translationService) throw new Error("Translation server not initialized");
238
+ await this.reloadMetadata();
239
+ if (!this.metadata) throw new Error("Failed to load metadata");
240
+ const allHashes = Object.keys(this.metadata.entries);
241
+ this.logger.info(`Translating all ${allHashes.length} entries to ${locale}`);
242
+ const startTime = Date.now();
243
+ this.broadcast(require_ws_events.createEvent("batch:start", {
244
+ locale,
245
+ total: allHashes.length,
246
+ hashes: allHashes
247
+ }));
248
+ const result = await this.translationService.translate(locale, this.metadata, allHashes);
249
+ const duration = Date.now() - startTime;
250
+ this.broadcast(require_ws_events.createEvent("batch:complete", {
251
+ locale,
252
+ total: allHashes.length,
253
+ successful: Object.keys(result.translations).length,
254
+ failed: result.errors.length,
255
+ duration
256
+ }));
257
+ return result;
258
+ }
259
+ /**
260
+ * Find an available port starting from the given port
261
+ */
262
+ async findAvailablePort(startPort, maxAttempts = 100) {
263
+ for (let port = startPort; port < startPort + maxAttempts; port++) if (await this.isPortAvailable(port)) return port;
264
+ throw new Error(`Could not find available port in range ${startPort}-${startPort + maxAttempts}`);
265
+ }
266
+ /**
267
+ * Check if a port is available
268
+ */
269
+ async isPortAvailable(port) {
270
+ return new Promise((resolve) => {
271
+ const testServer = http.default.createServer();
272
+ testServer.once("error", (error) => {
273
+ if (error.code === "EADDRINUSE") resolve(false);
274
+ else resolve(false);
275
+ });
276
+ testServer.once("listening", () => {
277
+ testServer.close(() => {
278
+ resolve(true);
279
+ });
280
+ });
281
+ testServer.listen(port, "127.0.0.1");
282
+ });
283
+ }
284
+ /**
285
+ * Mark translation activity start - emits busy event if not already busy
286
+ */
287
+ startTranslationActivity() {
288
+ this.activeTranslations++;
289
+ if (this.busyTimeout) {
290
+ clearTimeout(this.busyTimeout);
291
+ this.busyTimeout = null;
292
+ }
293
+ if (!this.isBusy && this.activeTranslations > 0) {
294
+ this.isBusy = true;
295
+ this.broadcast(require_ws_events.createEvent("server:busy", { activeTranslations: this.activeTranslations }));
296
+ this.logger.debug(`[BUSY] Server is now busy (${this.activeTranslations} active)`);
297
+ }
298
+ }
299
+ /**
300
+ * Mark translation activity end - emits idle event after debounce period
301
+ */
302
+ endTranslationActivity() {
303
+ this.activeTranslations = Math.max(0, this.activeTranslations - 1);
304
+ if (this.activeTranslations === 0 && this.isBusy) {
305
+ if (this.busyTimeout) clearTimeout(this.busyTimeout);
306
+ this.busyTimeout = setTimeout(() => {
307
+ if (this.activeTranslations === 0) {
308
+ this.isBusy = false;
309
+ this.broadcast(require_ws_events.createEvent("server:idle", {}));
310
+ this.logger.debug("[IDLE] Server is now idle");
311
+ }
312
+ }, this.BUSY_DEBOUNCE_MS);
313
+ }
314
+ }
315
+ /**
316
+ * Check if a given URL is running our translation server by calling the health endpoint
317
+ * Also verifies that the config hash matches to ensure compatible configuration
318
+ */
319
+ async checkIfTranslationServer(url$1) {
320
+ return new Promise((resolve) => {
321
+ const healthUrl = `${url$1}/health`;
322
+ const req = http.default.get(healthUrl, { timeout: 2e3 }, (res) => {
323
+ let data = "";
324
+ res.on("data", (chunk) => {
325
+ data += chunk.toString();
326
+ });
327
+ res.on("end", () => {
328
+ try {
329
+ if (res.statusCode === 200) {
330
+ const json = JSON.parse(data);
331
+ if (json.configHash && json.configHash !== this.configHash) {
332
+ this.logger.warn(`Existing server has different config (hash: ${json.configHash} vs ${this.configHash}), will start new server`);
333
+ resolve(false);
334
+ return;
335
+ }
336
+ resolve(true);
337
+ return;
338
+ }
339
+ resolve(false);
340
+ } catch (error) {
341
+ resolve(false);
342
+ }
343
+ });
344
+ });
345
+ req.on("error", () => {
346
+ resolve(false);
347
+ });
348
+ req.on("timeout", () => {
349
+ req.destroy();
350
+ resolve(false);
351
+ });
352
+ });
353
+ }
354
+ /**
355
+ * Handle incoming HTTP request
356
+ */
357
+ async handleRequest(req, res) {
358
+ this.logger.info(`🔄 Processing: ${req.method} ${req.url}`);
359
+ try {
360
+ const url$1 = new url.URL(req.url || "", `http://${req.headers.host}`);
361
+ this.logger.debug(`${req.method} ${url$1.pathname}`);
362
+ res.setHeader("Access-Control-Allow-Origin", "*");
363
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
364
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
365
+ res.setHeader("Access-Control-Expose-Headers", "Content-Type, Cache-Control");
366
+ if (req.method === "OPTIONS") {
367
+ res.writeHead(204);
368
+ res.end();
369
+ return;
370
+ }
371
+ if (url$1.pathname === "/health") {
372
+ res.writeHead(200, { "Content-Type": "application/json" });
373
+ res.end(JSON.stringify({
374
+ port: this.url,
375
+ configHash: this.configHash
376
+ }));
377
+ return;
378
+ }
379
+ const postMatch = url$1.pathname.match(/^\/translations\/([^/]+)$/);
380
+ if (postMatch && req.method === "POST") {
381
+ const [, locale] = postMatch;
382
+ await this.handleBatchTranslationRequest(locale, req, res);
383
+ return;
384
+ }
385
+ const dictMatch = url$1.pathname.match(/^\/translations\/([^/]+)$/);
386
+ if (dictMatch && req.method === "GET") {
387
+ const [, locale] = dictMatch;
388
+ await this.handleDictionaryRequest(locale, res);
389
+ return;
390
+ }
391
+ res.writeHead(404, { "Content-Type": "application/json" });
392
+ res.end(JSON.stringify({
393
+ error: "Not Found",
394
+ message: "Unknown endpoint",
395
+ availableEndpoints: [
396
+ "GET /health",
397
+ "GET /translations/:locale",
398
+ "POST /translations/:locale (with body: { hashes: string[] })"
399
+ ]
400
+ }));
401
+ } catch (error) {
402
+ this.logger.error("Error handling request:", error);
403
+ res.writeHead(500, { "Content-Type": "application/json" });
404
+ res.end(JSON.stringify({
405
+ error: "Internal Server Error",
406
+ message: error instanceof Error ? error.message : "Unknown error"
407
+ }));
408
+ }
409
+ }
410
+ /**
411
+ * Handle batch translation request
412
+ */
413
+ async handleBatchTranslationRequest(locale, req, res) {
414
+ try {
415
+ const parsedLocale = require_is_valid_locale.parseLocaleOrThrow(locale);
416
+ let body = "";
417
+ this.logger.debug("Reading request body...");
418
+ for await (const chunk of req) {
419
+ body += chunk.toString();
420
+ this.logger.debug(`Chunk read, body: ${body}`);
421
+ }
422
+ const { hashes } = JSON.parse(body);
423
+ this.logger.debug(`Parsed hashes: ${hashes.join(",")}`);
424
+ if (!Array.isArray(hashes)) {
425
+ res.writeHead(400, { "Content-Type": "application/json" });
426
+ res.end(JSON.stringify({
427
+ error: "Bad Request",
428
+ message: "Body must contain 'hashes' array"
429
+ }));
430
+ return;
431
+ }
432
+ if (!this.translationService) throw new Error("Translation service not initialized");
433
+ await this.reloadMetadata();
434
+ if (!this.metadata) throw new Error("Failed to load metadata");
435
+ this.logger.info(`🔄 Translating ${hashes.length} hashes to ${locale}`);
436
+ this.logger.debug(`🔄 Hashes: ${hashes.join(", ")}`);
437
+ this.startTranslationActivity();
438
+ try {
439
+ const result = await this.translationService.translate(parsedLocale, this.metadata, hashes);
440
+ res.writeHead(200, {
441
+ "Content-Type": "application/json",
442
+ "Cache-Control": "no-cache"
443
+ });
444
+ res.end(JSON.stringify({
445
+ locale,
446
+ translations: result.translations,
447
+ errors: result.errors
448
+ }));
449
+ } finally {
450
+ this.endTranslationActivity();
451
+ }
452
+ } catch (error) {
453
+ this.logger.error(`Error getting batch translations for ${locale}:`, error);
454
+ res.writeHead(500, { "Content-Type": "application/json" });
455
+ res.end(JSON.stringify({
456
+ error: "Translation generation failed",
457
+ message: error instanceof Error ? error.message : "Unknown error"
458
+ }));
459
+ }
460
+ }
461
+ /**
462
+ * Handle request for full translation dictionary
463
+ */
464
+ async handleDictionaryRequest(locale, res) {
465
+ try {
466
+ const parsedLocale = require_is_valid_locale.parseLocaleOrThrow(locale);
467
+ if (!this.translationService) throw new Error("Translation service not initialized");
468
+ await this.reloadMetadata();
469
+ if (!this.metadata) throw new Error("Failed to load metadata");
470
+ this.logger.info(`🌐 Requesting full dictionary for ${locale}`);
471
+ const allHashes = Object.keys(this.metadata.entries);
472
+ const result = await this.translationService.translate(parsedLocale, this.metadata, allHashes);
473
+ res.writeHead(200, {
474
+ "Content-Type": "application/json",
475
+ "Cache-Control": "public, max-age=3600"
476
+ });
477
+ res.end(JSON.stringify({
478
+ locale,
479
+ translations: result.translations,
480
+ errors: result.errors
481
+ }));
482
+ } catch (error) {
483
+ this.logger.error(`Error getting dictionary for ${locale}:`, error);
484
+ res.writeHead(500, { "Content-Type": "application/json" });
485
+ res.end(JSON.stringify({
486
+ error: "Translation generation failed",
487
+ message: error instanceof Error ? error.message : "Unknown error"
488
+ }));
489
+ }
490
+ }
491
+ };
492
+ function stableStringify(value) {
493
+ const normalize = (v) => {
494
+ if (v === void 0) return void 0;
495
+ if (typeof v === "function") return void 0;
496
+ if (v === null) return null;
497
+ if (Array.isArray(v)) return v.map(normalize).filter((x) => x !== void 0);
498
+ if (typeof v === "object") {
499
+ const out = {};
500
+ for (const key of Object.keys(v).sort()) {
501
+ const next = normalize(v[key]);
502
+ if (next !== void 0) out[key] = next;
503
+ }
504
+ return out;
505
+ }
506
+ return v;
507
+ };
508
+ return JSON.stringify(normalize(value));
509
+ }
510
+ /**
511
+ * Generate a stable hash of a config object
512
+ * Filters out functions and non-serializable values
513
+ * Sorts keys for stability
514
+ */
515
+ function hashConfig(config) {
516
+ const serialized = stableStringify(config);
517
+ return crypto.default.createHash("md5").update(serialized).digest("hex").slice(0, 12);
518
+ }
519
+ /**
520
+ * Create and start a translation server
521
+ */
522
+ async function startTranslationServer(options) {
523
+ const server = new TranslationServer(options);
524
+ await server.start();
525
+ return server;
526
+ }
527
+ /**
528
+ * Create a translation server and start it or reuse an existing one on the preferred port
529
+ *
530
+ * Since we have little control over the dev server start in next, we can start the translation server only in the loader,
531
+ * and loaders could be started from multiple processes (it seems) or similar we need a way to avoid starting multiple servers.
532
+ * This one will try to start a server on the preferred port (which seems to be an atomic operation), and if it fails,
533
+ * it checks if the server already started is ours and returns its url.
534
+ *
535
+ * @returns Object containing the server instance and its URL
536
+ */
537
+ async function startOrGetTranslationServer(options) {
538
+ const server = new TranslationServer(options);
539
+ return {
540
+ server,
541
+ url: await server.startOrGetUrl()
542
+ };
543
+ }
544
+
545
+ //#endregion
546
+ exports.startOrGetTranslationServer = startOrGetTranslationServer;
547
+ exports.startTranslationServer = startTranslationServer;