@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,247 @@
1
+ const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
2
+ const require_parse_xml = require('../parse-xml.cjs');
3
+ const require_model_factory = require('../lingo/model-factory.cjs');
4
+ const require_timeout = require('../../utils/timeout.cjs');
5
+ const require_pattern_detector = require('./pattern-detector.cjs');
6
+ const require_prompt = require('./prompt.cjs');
7
+ const require_shots = require('./shots.cjs');
8
+ const require_icu_validator = require('./icu-validator.cjs');
9
+ let ai = require("ai");
10
+
11
+ //#region src/translators/pluralization/service.ts
12
+ /**
13
+ * Pluralization service with batching and model reuse
14
+ */
15
+ var PluralizationService = class {
16
+ languageModel;
17
+ modelName;
18
+ cache = /* @__PURE__ */ new Map();
19
+ prompt;
20
+ sourceLocale;
21
+ constructor(config, logger) {
22
+ this.logger = logger;
23
+ const localeModel = require_model_factory.parseModelString(config.model);
24
+ if (!localeModel) throw new Error(`Invalid model format: "${config.model}"`);
25
+ const modelsConfig = { "*:*": config.model };
26
+ this.logger.info("Validating API keys for pluralization...");
27
+ const validatedKeys = require_model_factory.validateAndGetApiKeys(modelsConfig);
28
+ this.logger.info("✅ API keys validated for pluralization");
29
+ this.languageModel = require_model_factory.createAiModel(localeModel, validatedKeys);
30
+ this.modelName = `${localeModel.provider}:${localeModel.name}`;
31
+ this.sourceLocale = config.sourceLocale;
32
+ this.prompt = require_prompt.getSystemPrompt({ sourceLocale: config.sourceLocale });
33
+ this.logger.info(`Initialized pluralization service with ${this.modelName}`);
34
+ }
35
+ /**
36
+ * Generate ICU formats for multiple candidates in a single batch
37
+ *
38
+ * @param candidates Array of plural candidates
39
+ * @param batchSize Maximum candidates per batch (default: 10)
40
+ * @returns Map of hash -> ICU generation result
41
+ */
42
+ async generateBatch(candidates, batchSize = 10) {
43
+ const results = /* @__PURE__ */ new Map();
44
+ const uncachedCandidates = candidates.filter((c) => {
45
+ const cached = this.cache.get(c.hash);
46
+ if (cached) {
47
+ results.set(c.hash, cached);
48
+ return false;
49
+ }
50
+ return true;
51
+ });
52
+ if (uncachedCandidates.length === 0) {
53
+ this.logger.debug(`All ${candidates.length} candidates found in cache, skipping LLM call`);
54
+ return results;
55
+ }
56
+ this.logger.info(`Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`);
57
+ for (let i = 0; i < uncachedCandidates.length; i += batchSize) {
58
+ const batch = uncachedCandidates.slice(i, i + batchSize);
59
+ this.logger.info(`Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(uncachedCandidates.length / batchSize)} (${batch.length} candidates)`);
60
+ const batchResults = await this.processBatch(batch);
61
+ for (const [hash, result] of batchResults) {
62
+ results.set(hash, result);
63
+ this.cache.set(hash, result);
64
+ }
65
+ }
66
+ return results;
67
+ }
68
+ /**
69
+ * Process a single batch of candidates
70
+ */
71
+ async processBatch(candidates) {
72
+ const results = /* @__PURE__ */ new Map();
73
+ try {
74
+ const batchRequest = {
75
+ version: .1,
76
+ sourceLocale: this.sourceLocale,
77
+ candidates: { candidate: candidates.map((c) => ({
78
+ hash: c.hash,
79
+ text: c.sourceText
80
+ })) }
81
+ };
82
+ const responseText = (await require_timeout.withTimeout((0, ai.generateText)({
83
+ model: this.languageModel,
84
+ messages: [
85
+ {
86
+ role: "system",
87
+ content: this.prompt
88
+ },
89
+ ...require_shots.shots.flatMap((shotsTuple) => [{
90
+ role: "user",
91
+ content: require_parse_xml.obj2xml(shotsTuple[0])
92
+ }, {
93
+ role: "assistant",
94
+ content: require_parse_xml.obj2xml(shotsTuple[1])
95
+ }]),
96
+ {
97
+ role: "user",
98
+ content: require_parse_xml.obj2xml(batchRequest)
99
+ }
100
+ ]
101
+ }), require_timeout.DEFAULT_TIMEOUTS.AI_API * 2, `Pluralization with ${this.modelName}`)).text.trim();
102
+ this.logger.debug(`LLM XML response: ${responseText.substring(0, 200)}...`);
103
+ const parsed = require_parse_xml.parseXmlFromResponseText(responseText);
104
+ const resultArray = Array.isArray(parsed.results.result) ? parsed.results.result : [parsed.results.result];
105
+ for (const result of resultArray) {
106
+ const candidate = candidates.find((c) => c.hash === result.hash);
107
+ if (!candidate) {
108
+ this.logger.warn(`No candidate found for hash: ${result.hash}`);
109
+ continue;
110
+ }
111
+ if (result.shouldPluralize && result.icuText) {
112
+ this.logger.debug(`✓ ICU format generated for "${candidate.sourceText}": "${result.icuText}"`);
113
+ results.set(result.hash, {
114
+ success: true,
115
+ icuText: result.icuText,
116
+ reasoning: result.reasoning
117
+ });
118
+ } else {
119
+ this.logger.debug(`✗ Pluralization not appropriate for "${candidate.sourceText}": ${result.reasoning}`);
120
+ results.set(result.hash, {
121
+ success: false,
122
+ reasoning: result.reasoning
123
+ });
124
+ }
125
+ }
126
+ for (const candidate of candidates) if (!results.has(candidate.hash)) {
127
+ this.logger.warn(`No result returned for candidate: ${candidate.sourceText}`);
128
+ results.set(candidate.hash, {
129
+ success: false,
130
+ error: "No result returned by LLM"
131
+ });
132
+ }
133
+ } catch (error) {
134
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
135
+ this.logger.error(`Failed to process batch: ${errorMsg}`);
136
+ for (const candidate of candidates) results.set(candidate.hash, {
137
+ success: false,
138
+ error: errorMsg
139
+ });
140
+ }
141
+ return results;
142
+ }
143
+ /**
144
+ * Process metadata entries for pluralization
145
+ *
146
+ * This is the main entry point that:
147
+ * 1. Detects plural candidates using pattern matching
148
+ * 2. Generates ICU format using LLM (batched)
149
+ * 3. Validates the ICU format
150
+ * 4. Updates metadata entries in-place (modifies sourceText)
151
+ * 5. Returns statistics
152
+ * @param metadata Metadata schema with translation entries
153
+
154
+ * @returns Statistics about the pluralization process
155
+ */
156
+ async process(metadata) {
157
+ const startTime = performance.now();
158
+ const totalEntries = Object.keys(metadata.entries).length;
159
+ if (totalEntries === 0) return {
160
+ total: 0,
161
+ candidates: 0,
162
+ pluralized: 0,
163
+ rejected: 0,
164
+ failed: 0,
165
+ durationMs: 0
166
+ };
167
+ this.logger.info(`Starting pluralization processing for ${totalEntries} entries`);
168
+ const candidates = require_pattern_detector.detectPluralCandidates(Object.fromEntries(Object.entries(metadata.entries).map(([hash, entry]) => [hash, entry.sourceText])), this.logger);
169
+ this.logger.info(`Found ${candidates.length} plural candidates (${(candidates.length / totalEntries * 100).toFixed(1)}%)`);
170
+ if (candidates.length === 0) return {
171
+ total: totalEntries,
172
+ candidates: 0,
173
+ pluralized: 0,
174
+ rejected: 0,
175
+ failed: 0,
176
+ durationMs: performance.now() - startTime
177
+ };
178
+ this.logger.debug("Generating ICU formats with batching...");
179
+ const icuResults = await this.generateBatch(candidates, 10);
180
+ this.logger.debug("Validating and updating entries...");
181
+ let pluralized = 0;
182
+ let rejected = 0;
183
+ let failed = 0;
184
+ for (const candidate of candidates) {
185
+ const result = icuResults.get(candidate.hash);
186
+ const entry = metadata.entries[candidate.hash];
187
+ this.logger.debug(`Processing candidate: ${candidate.sourceText}`);
188
+ if (!entry) {
189
+ this.logger.warn(`Entry not found for hash: ${candidate.hash}`);
190
+ failed++;
191
+ continue;
192
+ }
193
+ if (!result) {
194
+ this.logger.warn(`No result for hash: ${candidate.hash}`);
195
+ failed++;
196
+ continue;
197
+ }
198
+ if (result.error) {
199
+ this.logger.warn(`Error generating ICU for "${candidate.sourceText}": ${result.error}`);
200
+ failed++;
201
+ continue;
202
+ }
203
+ if (!result.success || !result.icuText) {
204
+ this.logger.debug(`Rejected pluralization for "${candidate.sourceText}": ${result.reasoning}`);
205
+ rejected++;
206
+ continue;
207
+ }
208
+ if (!require_icu_validator.validateICU(result.icuText, candidate.sourceText, this.logger)) {
209
+ this.logger.warn(`Invalid ICU format generated for "${candidate.sourceText}", falling back to original`);
210
+ failed++;
211
+ continue;
212
+ }
213
+ this.logger.info(`Pluralizing: "${entry.sourceText}" -> "${result.icuText}"`);
214
+ entry.sourceText = result.icuText;
215
+ pluralized++;
216
+ }
217
+ const duration = performance.now() - startTime;
218
+ this.logger.info(`Pluralization completed: ${pluralized} pluralized, ${rejected} rejected, ${failed} failed in ${duration.toFixed(0)}ms`);
219
+ return {
220
+ total: totalEntries,
221
+ candidates: candidates.length,
222
+ pluralized,
223
+ rejected,
224
+ failed,
225
+ durationMs: duration
226
+ };
227
+ }
228
+ /**
229
+ * Clear the cache
230
+ */
231
+ clearCache() {
232
+ this.cache.clear();
233
+ this.logger.debug("Pluralization cache cleared");
234
+ }
235
+ /**
236
+ * Get cache statistics
237
+ */
238
+ getCacheStats() {
239
+ return {
240
+ size: this.cache.size,
241
+ hits: 0
242
+ };
243
+ }
244
+ };
245
+
246
+ //#endregion
247
+ exports.PluralizationService = PluralizationService;
@@ -0,0 +1,247 @@
1
+ import { obj2xml, parseXmlFromResponseText } from "../parse-xml.mjs";
2
+ import { createAiModel, parseModelString, validateAndGetApiKeys } from "../lingo/model-factory.mjs";
3
+ import { DEFAULT_TIMEOUTS, withTimeout } from "../../utils/timeout.mjs";
4
+ import { detectPluralCandidates } from "./pattern-detector.mjs";
5
+ import { getSystemPrompt } from "./prompt.mjs";
6
+ import { shots } from "./shots.mjs";
7
+ import { validateICU } from "./icu-validator.mjs";
8
+ import { generateText } from "ai";
9
+
10
+ //#region src/translators/pluralization/service.ts
11
+ /**
12
+ * Pluralization service with batching and model reuse
13
+ */
14
+ var PluralizationService = class {
15
+ languageModel;
16
+ modelName;
17
+ cache = /* @__PURE__ */ new Map();
18
+ prompt;
19
+ sourceLocale;
20
+ constructor(config, logger) {
21
+ this.logger = logger;
22
+ const localeModel = parseModelString(config.model);
23
+ if (!localeModel) throw new Error(`Invalid model format: "${config.model}"`);
24
+ const modelsConfig = { "*:*": config.model };
25
+ this.logger.info("Validating API keys for pluralization...");
26
+ const validatedKeys = validateAndGetApiKeys(modelsConfig);
27
+ this.logger.info("✅ API keys validated for pluralization");
28
+ this.languageModel = createAiModel(localeModel, validatedKeys);
29
+ this.modelName = `${localeModel.provider}:${localeModel.name}`;
30
+ this.sourceLocale = config.sourceLocale;
31
+ this.prompt = getSystemPrompt({ sourceLocale: config.sourceLocale });
32
+ this.logger.info(`Initialized pluralization service with ${this.modelName}`);
33
+ }
34
+ /**
35
+ * Generate ICU formats for multiple candidates in a single batch
36
+ *
37
+ * @param candidates Array of plural candidates
38
+ * @param batchSize Maximum candidates per batch (default: 10)
39
+ * @returns Map of hash -> ICU generation result
40
+ */
41
+ async generateBatch(candidates, batchSize = 10) {
42
+ const results = /* @__PURE__ */ new Map();
43
+ const uncachedCandidates = candidates.filter((c) => {
44
+ const cached = this.cache.get(c.hash);
45
+ if (cached) {
46
+ results.set(c.hash, cached);
47
+ return false;
48
+ }
49
+ return true;
50
+ });
51
+ if (uncachedCandidates.length === 0) {
52
+ this.logger.debug(`All ${candidates.length} candidates found in cache, skipping LLM call`);
53
+ return results;
54
+ }
55
+ this.logger.info(`Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`);
56
+ for (let i = 0; i < uncachedCandidates.length; i += batchSize) {
57
+ const batch = uncachedCandidates.slice(i, i + batchSize);
58
+ this.logger.info(`Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(uncachedCandidates.length / batchSize)} (${batch.length} candidates)`);
59
+ const batchResults = await this.processBatch(batch);
60
+ for (const [hash, result] of batchResults) {
61
+ results.set(hash, result);
62
+ this.cache.set(hash, result);
63
+ }
64
+ }
65
+ return results;
66
+ }
67
+ /**
68
+ * Process a single batch of candidates
69
+ */
70
+ async processBatch(candidates) {
71
+ const results = /* @__PURE__ */ new Map();
72
+ try {
73
+ const batchRequest = {
74
+ version: .1,
75
+ sourceLocale: this.sourceLocale,
76
+ candidates: { candidate: candidates.map((c) => ({
77
+ hash: c.hash,
78
+ text: c.sourceText
79
+ })) }
80
+ };
81
+ const responseText = (await withTimeout(generateText({
82
+ model: this.languageModel,
83
+ messages: [
84
+ {
85
+ role: "system",
86
+ content: this.prompt
87
+ },
88
+ ...shots.flatMap((shotsTuple) => [{
89
+ role: "user",
90
+ content: obj2xml(shotsTuple[0])
91
+ }, {
92
+ role: "assistant",
93
+ content: obj2xml(shotsTuple[1])
94
+ }]),
95
+ {
96
+ role: "user",
97
+ content: obj2xml(batchRequest)
98
+ }
99
+ ]
100
+ }), DEFAULT_TIMEOUTS.AI_API * 2, `Pluralization with ${this.modelName}`)).text.trim();
101
+ this.logger.debug(`LLM XML response: ${responseText.substring(0, 200)}...`);
102
+ const parsed = parseXmlFromResponseText(responseText);
103
+ const resultArray = Array.isArray(parsed.results.result) ? parsed.results.result : [parsed.results.result];
104
+ for (const result of resultArray) {
105
+ const candidate = candidates.find((c) => c.hash === result.hash);
106
+ if (!candidate) {
107
+ this.logger.warn(`No candidate found for hash: ${result.hash}`);
108
+ continue;
109
+ }
110
+ if (result.shouldPluralize && result.icuText) {
111
+ this.logger.debug(`✓ ICU format generated for "${candidate.sourceText}": "${result.icuText}"`);
112
+ results.set(result.hash, {
113
+ success: true,
114
+ icuText: result.icuText,
115
+ reasoning: result.reasoning
116
+ });
117
+ } else {
118
+ this.logger.debug(`✗ Pluralization not appropriate for "${candidate.sourceText}": ${result.reasoning}`);
119
+ results.set(result.hash, {
120
+ success: false,
121
+ reasoning: result.reasoning
122
+ });
123
+ }
124
+ }
125
+ for (const candidate of candidates) if (!results.has(candidate.hash)) {
126
+ this.logger.warn(`No result returned for candidate: ${candidate.sourceText}`);
127
+ results.set(candidate.hash, {
128
+ success: false,
129
+ error: "No result returned by LLM"
130
+ });
131
+ }
132
+ } catch (error) {
133
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
134
+ this.logger.error(`Failed to process batch: ${errorMsg}`);
135
+ for (const candidate of candidates) results.set(candidate.hash, {
136
+ success: false,
137
+ error: errorMsg
138
+ });
139
+ }
140
+ return results;
141
+ }
142
+ /**
143
+ * Process metadata entries for pluralization
144
+ *
145
+ * This is the main entry point that:
146
+ * 1. Detects plural candidates using pattern matching
147
+ * 2. Generates ICU format using LLM (batched)
148
+ * 3. Validates the ICU format
149
+ * 4. Updates metadata entries in-place (modifies sourceText)
150
+ * 5. Returns statistics
151
+ * @param metadata Metadata schema with translation entries
152
+
153
+ * @returns Statistics about the pluralization process
154
+ */
155
+ async process(metadata) {
156
+ const startTime = performance.now();
157
+ const totalEntries = Object.keys(metadata.entries).length;
158
+ if (totalEntries === 0) return {
159
+ total: 0,
160
+ candidates: 0,
161
+ pluralized: 0,
162
+ rejected: 0,
163
+ failed: 0,
164
+ durationMs: 0
165
+ };
166
+ this.logger.info(`Starting pluralization processing for ${totalEntries} entries`);
167
+ const candidates = detectPluralCandidates(Object.fromEntries(Object.entries(metadata.entries).map(([hash, entry]) => [hash, entry.sourceText])), this.logger);
168
+ this.logger.info(`Found ${candidates.length} plural candidates (${(candidates.length / totalEntries * 100).toFixed(1)}%)`);
169
+ if (candidates.length === 0) return {
170
+ total: totalEntries,
171
+ candidates: 0,
172
+ pluralized: 0,
173
+ rejected: 0,
174
+ failed: 0,
175
+ durationMs: performance.now() - startTime
176
+ };
177
+ this.logger.debug("Generating ICU formats with batching...");
178
+ const icuResults = await this.generateBatch(candidates, 10);
179
+ this.logger.debug("Validating and updating entries...");
180
+ let pluralized = 0;
181
+ let rejected = 0;
182
+ let failed = 0;
183
+ for (const candidate of candidates) {
184
+ const result = icuResults.get(candidate.hash);
185
+ const entry = metadata.entries[candidate.hash];
186
+ this.logger.debug(`Processing candidate: ${candidate.sourceText}`);
187
+ if (!entry) {
188
+ this.logger.warn(`Entry not found for hash: ${candidate.hash}`);
189
+ failed++;
190
+ continue;
191
+ }
192
+ if (!result) {
193
+ this.logger.warn(`No result for hash: ${candidate.hash}`);
194
+ failed++;
195
+ continue;
196
+ }
197
+ if (result.error) {
198
+ this.logger.warn(`Error generating ICU for "${candidate.sourceText}": ${result.error}`);
199
+ failed++;
200
+ continue;
201
+ }
202
+ if (!result.success || !result.icuText) {
203
+ this.logger.debug(`Rejected pluralization for "${candidate.sourceText}": ${result.reasoning}`);
204
+ rejected++;
205
+ continue;
206
+ }
207
+ if (!validateICU(result.icuText, candidate.sourceText, this.logger)) {
208
+ this.logger.warn(`Invalid ICU format generated for "${candidate.sourceText}", falling back to original`);
209
+ failed++;
210
+ continue;
211
+ }
212
+ this.logger.info(`Pluralizing: "${entry.sourceText}" -> "${result.icuText}"`);
213
+ entry.sourceText = result.icuText;
214
+ pluralized++;
215
+ }
216
+ const duration = performance.now() - startTime;
217
+ this.logger.info(`Pluralization completed: ${pluralized} pluralized, ${rejected} rejected, ${failed} failed in ${duration.toFixed(0)}ms`);
218
+ return {
219
+ total: totalEntries,
220
+ candidates: candidates.length,
221
+ pluralized,
222
+ rejected,
223
+ failed,
224
+ durationMs: duration
225
+ };
226
+ }
227
+ /**
228
+ * Clear the cache
229
+ */
230
+ clearCache() {
231
+ this.cache.clear();
232
+ this.logger.debug("Pluralization cache cleared");
233
+ }
234
+ /**
235
+ * Get cache statistics
236
+ */
237
+ getCacheStats() {
238
+ return {
239
+ size: this.cache.size,
240
+ hits: 0
241
+ };
242
+ }
243
+ };
244
+
245
+ //#endregion
246
+ export { PluralizationService };
247
+ //# sourceMappingURL=service.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.mjs","names":["logger: Logger","modelsConfig: Record<string, string>","batchRequest: PluralizationBatch"],"sources":["../../../src/translators/pluralization/service.ts"],"sourcesContent":["/**\n * Pluralization service with batching and caching\n */\n\nimport type { LanguageModel } from \"ai\";\nimport { generateText } from \"ai\";\nimport type {\n ICUGenerationResult,\n PluralCandidate,\n PluralizationBatch,\n PluralizationConfig,\n PluralizationResponse,\n PluralizationStats,\n} from \"./types\";\nimport {\n createAiModel,\n parseModelString,\n validateAndGetApiKeys,\n} from \"../lingo/model-factory\";\nimport { Logger } from \"../../utils/logger\";\nimport { DEFAULT_TIMEOUTS, withTimeout } from \"../../utils/timeout\";\nimport { getSystemPrompt } from \"./prompt\";\nimport { obj2xml, parseXmlFromResponseText } from \"../parse-xml\";\nimport { shots } from \"./shots\";\nimport type { MetadataSchema } from \"../../types\";\nimport { detectPluralCandidates } from \"./pattern-detector\";\nimport { validateICU } from \"./icu-validator\";\n\n/**\n * Pluralization service with batching and model reuse\n */\nexport class PluralizationService {\n private readonly languageModel: LanguageModel;\n private readonly modelName: string;\n private cache = new Map<string, ICUGenerationResult>();\n private readonly prompt: string;\n private readonly sourceLocale: string;\n\n constructor(\n config: PluralizationConfig,\n private logger: Logger,\n ) {\n const localeModel = parseModelString(config.model);\n if (!localeModel) {\n throw new Error(`Invalid model format: \"${config.model}\"`);\n }\n\n // Validate and fetch API keys for the pluralization provider\n // We need to create a models config that validateAndFetchApiKeys can use\n const modelsConfig: Record<string, string> = {\n \"*:*\": config.model, // Single model for pluralization\n };\n\n this.logger.info(\"Validating API keys for pluralization...\");\n const validatedKeys = validateAndGetApiKeys(modelsConfig);\n this.logger.info(\"✅ API keys validated for pluralization\");\n\n this.languageModel = createAiModel(localeModel, validatedKeys);\n this.modelName = `${localeModel.provider}:${localeModel.name}`;\n this.sourceLocale = config.sourceLocale;\n this.prompt = getSystemPrompt({ sourceLocale: config.sourceLocale });\n\n this.logger.info(\n `Initialized pluralization service with ${this.modelName}`,\n );\n }\n\n /**\n * Generate ICU formats for multiple candidates in a single batch\n *\n * @param candidates Array of plural candidates\n * @param batchSize Maximum candidates per batch (default: 10)\n * @returns Map of hash -> ICU generation result\n */\n async generateBatch(\n candidates: PluralCandidate[],\n batchSize: number = 10,\n ): Promise<Map<string, ICUGenerationResult>> {\n const results = new Map<string, ICUGenerationResult>();\n\n // Check cache first\n const uncachedCandidates = candidates.filter((c) => {\n const cached = this.cache.get(c.hash);\n if (cached) {\n results.set(c.hash, cached);\n return false;\n }\n return true;\n });\n\n if (uncachedCandidates.length === 0) {\n this.logger.debug(\n `All ${candidates.length} candidates found in cache, skipping LLM call`,\n );\n return results;\n }\n\n this.logger.info(\n `Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`,\n );\n\n // Process in batches\n for (let i = 0; i < uncachedCandidates.length; i += batchSize) {\n const batch = uncachedCandidates.slice(i, i + batchSize);\n\n this.logger.info(\n `Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(uncachedCandidates.length / batchSize)} (${batch.length} candidates)`,\n );\n\n const batchResults = await this.processBatch(batch);\n\n // Store results and cache them\n for (const [hash, result] of batchResults) {\n results.set(hash, result);\n this.cache.set(hash, result);\n }\n }\n\n return results;\n }\n\n /**\n * Process a single batch of candidates\n */\n private async processBatch(\n candidates: PluralCandidate[],\n ): Promise<Map<string, ICUGenerationResult>> {\n const results = new Map<string, ICUGenerationResult>();\n\n try {\n // Prepare batch request in XML format\n const batchRequest: PluralizationBatch = {\n version: 0.1,\n sourceLocale: this.sourceLocale,\n candidates: {\n candidate: candidates.map((c) => ({\n hash: c.hash,\n text: c.sourceText,\n })),\n },\n };\n\n // Call LLM with XML format and few-shot examples\n const response = await withTimeout(\n generateText({\n model: this.languageModel,\n messages: [\n {\n role: \"system\",\n content: this.prompt,\n },\n // Add few-shot examples\n ...shots.flatMap((shotsTuple) => [\n {\n role: \"user\" as const,\n content: obj2xml(shotsTuple[0]),\n },\n {\n role: \"assistant\" as const,\n content: obj2xml(shotsTuple[1]),\n },\n ]),\n {\n role: \"user\",\n content: obj2xml(batchRequest),\n },\n ],\n }),\n DEFAULT_TIMEOUTS.AI_API * 2, // Double timeout for batch\n `Pluralization with ${this.modelName}`,\n );\n\n const responseText = response.text.trim();\n this.logger.debug(\n `LLM XML response: ${responseText.substring(0, 200)}...`,\n );\n // Parse XML response\n const parsed =\n parseXmlFromResponseText<PluralizationResponse>(responseText);\n\n // Process results\n const resultArray = Array.isArray(parsed.results.result)\n ? parsed.results.result\n : [parsed.results.result];\n\n for (const result of resultArray) {\n const candidate = candidates.find((c) => c.hash === result.hash);\n if (!candidate) {\n this.logger.warn(`No candidate found for hash: ${result.hash}`);\n continue;\n }\n\n if (result.shouldPluralize && result.icuText) {\n this.logger.debug(\n `✓ ICU format generated for \"${candidate.sourceText}\": \"${result.icuText}\"`,\n );\n results.set(result.hash, {\n success: true,\n icuText: result.icuText,\n reasoning: result.reasoning,\n });\n } else {\n this.logger.debug(\n `✗ Pluralization not appropriate for \"${candidate.sourceText}\": ${result.reasoning}`,\n );\n results.set(result.hash, {\n success: false,\n reasoning: result.reasoning,\n });\n }\n }\n\n // Handle missing results (LLM didn't return result for some candidates)\n for (const candidate of candidates) {\n if (!results.has(candidate.hash)) {\n this.logger.warn(\n `No result returned for candidate: ${candidate.sourceText}`,\n );\n results.set(candidate.hash, {\n success: false,\n error: \"No result returned by LLM\",\n });\n }\n }\n } catch (error) {\n const errorMsg = error instanceof Error ? error.message : \"Unknown error\";\n this.logger.error(`Failed to process batch: ${errorMsg}`);\n\n // Mark all candidates as failed\n for (const candidate of candidates) {\n results.set(candidate.hash, {\n success: false,\n error: errorMsg,\n });\n }\n }\n\n return results;\n }\n\n /**\n * Process metadata entries for pluralization\n *\n * This is the main entry point that:\n * 1. Detects plural candidates using pattern matching\n * 2. Generates ICU format using LLM (batched)\n * 3. Validates the ICU format\n * 4. Updates metadata entries in-place (modifies sourceText)\n * 5. Returns statistics\n * @param metadata Metadata schema with translation entries\n\n * @returns Statistics about the pluralization process\n */\n async process(metadata: MetadataSchema): Promise<PluralizationStats> {\n const startTime = performance.now();\n const totalEntries = Object.keys(metadata.entries).length;\n\n if (totalEntries === 0) {\n return {\n total: 0,\n candidates: 0,\n pluralized: 0,\n rejected: 0,\n failed: 0,\n durationMs: 0,\n };\n }\n\n this.logger.info(\n `Starting pluralization processing for ${totalEntries} entries`,\n );\n\n // Step 1: Detect plural candidates using pattern matching\n const entriesMap: Record<string, string> = Object.fromEntries(\n Object.entries(metadata.entries).map(([hash, entry]) => [\n hash,\n entry.sourceText,\n ]),\n );\n\n const candidates = detectPluralCandidates(entriesMap, this.logger);\n\n this.logger.info(\n `Found ${candidates.length} plural candidates (${((candidates.length / totalEntries) * 100).toFixed(1)}%)`,\n );\n\n if (candidates.length === 0) {\n const endTime = performance.now();\n return {\n total: totalEntries,\n candidates: 0,\n pluralized: 0,\n rejected: 0,\n failed: 0,\n durationMs: endTime - startTime,\n };\n }\n\n // Step 2: Generate ICU formats with batching\n this.logger.debug(\"Generating ICU formats with batching...\");\n const icuResults = await this.generateBatch(candidates, 10);\n\n // Step 3: Validate and update metadata entries\n this.logger.debug(\"Validating and updating entries...\");\n let pluralized = 0;\n let rejected = 0;\n let failed = 0;\n\n for (const candidate of candidates) {\n const result = icuResults.get(candidate.hash);\n const entry = metadata.entries[candidate.hash];\n this.logger.debug(`Processing candidate: ${candidate.sourceText}`);\n if (!entry) {\n this.logger.warn(`Entry not found for hash: ${candidate.hash}`);\n failed++;\n continue;\n }\n\n if (!result) {\n this.logger.warn(`No result for hash: ${candidate.hash}`);\n failed++;\n continue;\n }\n\n if (result.error) {\n this.logger.warn(\n `Error generating ICU for \"${candidate.sourceText}\": ${result.error}`,\n );\n failed++;\n continue;\n }\n\n if (!result.success || !result.icuText) {\n this.logger.debug(\n `Rejected pluralization for \"${candidate.sourceText}\": ${result.reasoning}`,\n );\n rejected++;\n continue;\n }\n\n const isValid = validateICU(\n result.icuText,\n candidate.sourceText,\n this.logger,\n );\n\n if (!isValid) {\n this.logger.warn(\n `Invalid ICU format generated for \"${candidate.sourceText}\", falling back to original`,\n );\n failed++;\n continue;\n }\n\n // Update metadata entry in-place\n this.logger.info(\n `Pluralizing: \"${entry.sourceText}\" -> \"${result.icuText}\"`,\n );\n entry.sourceText = result.icuText;\n pluralized++;\n }\n\n const endTime = performance.now();\n const duration = endTime - startTime;\n\n this.logger.info(\n `Pluralization completed: ${pluralized} pluralized, ${rejected} rejected, ${failed} failed in ${duration.toFixed(0)}ms`,\n );\n\n return {\n total: totalEntries,\n candidates: candidates.length,\n pluralized,\n rejected,\n failed,\n durationMs: duration,\n };\n }\n\n /**\n * Clear the cache\n */\n clearCache(): void {\n this.cache.clear();\n this.logger.debug(\"Pluralization cache cleared\");\n }\n\n /**\n * Get cache statistics\n */\n getCacheStats(): { size: number; hits: number } {\n return {\n size: this.cache.size,\n hits: 0, // We don't track hits currently\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+
2
+ //#region src/translators/pluralization/shots.ts
3
+ /**
4
+ * Few-shot examples: [input, output]
5
+ */
6
+ const shots = [[{
7
+ version: .1,
8
+ sourceLocale: "en",
9
+ candidates: { candidate: [{
10
+ hash: "h1",
11
+ text: "You have {count} items in your cart"
12
+ }, {
13
+ hash: "h2",
14
+ text: "{fileCount} files uploaded"
15
+ }] }
16
+ }, {
17
+ version: .1,
18
+ results: { result: [{
19
+ hash: "h1",
20
+ shouldPluralize: true,
21
+ icuText: "You have {count, plural, =0 {no items} one {# item} other {# items}} in your cart",
22
+ reasoning: "Message varies based on item count"
23
+ }, {
24
+ hash: "h2",
25
+ shouldPluralize: true,
26
+ icuText: "{fileCount, plural, =0 {No files uploaded} one {# file uploaded} other {# files uploaded}}",
27
+ reasoning: "Message varies based on file count"
28
+ }] }
29
+ }], [{
30
+ version: .1,
31
+ sourceLocale: "en",
32
+ candidates: { candidate: [{
33
+ hash: "h3",
34
+ text: "Welcome back, {name}"
35
+ }, {
36
+ hash: "h4",
37
+ text: "Your email is {email}"
38
+ }] }
39
+ }, {
40
+ version: .1,
41
+ results: { result: [{
42
+ hash: "h3",
43
+ shouldPluralize: false,
44
+ reasoning: "Variable {name} is a person's name, not a count. No pluralization needed."
45
+ }, {
46
+ hash: "h4",
47
+ shouldPluralize: false,
48
+ reasoning: "Variable {email} is an email address, not a count. No pluralization needed."
49
+ }] }
50
+ }]];
51
+
52
+ //#endregion
53
+ exports.shots = shots;
@@ -0,0 +1,53 @@
1
+ //#region src/translators/pluralization/shots.ts
2
+ /**
3
+ * Few-shot examples: [input, output]
4
+ */
5
+ const shots = [[{
6
+ version: .1,
7
+ sourceLocale: "en",
8
+ candidates: { candidate: [{
9
+ hash: "h1",
10
+ text: "You have {count} items in your cart"
11
+ }, {
12
+ hash: "h2",
13
+ text: "{fileCount} files uploaded"
14
+ }] }
15
+ }, {
16
+ version: .1,
17
+ results: { result: [{
18
+ hash: "h1",
19
+ shouldPluralize: true,
20
+ icuText: "You have {count, plural, =0 {no items} one {# item} other {# items}} in your cart",
21
+ reasoning: "Message varies based on item count"
22
+ }, {
23
+ hash: "h2",
24
+ shouldPluralize: true,
25
+ icuText: "{fileCount, plural, =0 {No files uploaded} one {# file uploaded} other {# files uploaded}}",
26
+ reasoning: "Message varies based on file count"
27
+ }] }
28
+ }], [{
29
+ version: .1,
30
+ sourceLocale: "en",
31
+ candidates: { candidate: [{
32
+ hash: "h3",
33
+ text: "Welcome back, {name}"
34
+ }, {
35
+ hash: "h4",
36
+ text: "Your email is {email}"
37
+ }] }
38
+ }, {
39
+ version: .1,
40
+ results: { result: [{
41
+ hash: "h3",
42
+ shouldPluralize: false,
43
+ reasoning: "Variable {name} is a person's name, not a count. No pluralization needed."
44
+ }, {
45
+ hash: "h4",
46
+ shouldPluralize: false,
47
+ reasoning: "Variable {email} is an email address, not a count. No pluralization needed."
48
+ }] }
49
+ }]];
50
+
51
+ //#endregion
52
+ export { shots };
53
+ //# sourceMappingURL=shots.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shots.mjs","names":["shots: [PluralizationBatch, PluralizationResponse][]"],"sources":["../../../src/translators/pluralization/shots.ts"],"sourcesContent":["import type { PluralizationBatch, PluralizationResponse } from \"./types\";\n\n/**\n * Few-shot examples: [input, output]\n */\nexport const shots: [PluralizationBatch, PluralizationResponse][] = [\n // Example 1: Should pluralize\n [\n {\n version: 0.1,\n sourceLocale: \"en\",\n candidates: {\n candidate: [\n {\n hash: \"h1\",\n text: \"You have {count} items in your cart\",\n },\n {\n hash: \"h2\",\n text: \"{fileCount} files uploaded\",\n },\n ],\n },\n },\n {\n version: 0.1,\n results: {\n result: [\n {\n hash: \"h1\",\n shouldPluralize: true,\n icuText:\n \"You have {count, plural, =0 {no items} one {# item} other {# items}} in your cart\",\n reasoning: \"Message varies based on item count\",\n },\n {\n hash: \"h2\",\n shouldPluralize: true,\n icuText:\n \"{fileCount, plural, =0 {No files uploaded} one {# file uploaded} other {# files uploaded}}\",\n reasoning: \"Message varies based on file count\",\n },\n ],\n },\n },\n ],\n\n // Example 2: Should NOT pluralize\n [\n {\n version: 0.1,\n sourceLocale: \"en\",\n candidates: {\n candidate: [\n {\n hash: \"h3\",\n text: \"Welcome back, {name}\",\n },\n {\n hash: \"h4\",\n text: \"Your email is {email}\",\n },\n ],\n },\n },\n {\n version: 0.1,\n results: {\n result: [\n {\n hash: \"h3\",\n shouldPluralize: false,\n reasoning:\n \"Variable {name} is a person's name, not a count. No pluralization needed.\",\n },\n {\n hash: \"h4\",\n shouldPluralize: false,\n reasoning:\n \"Variable {email} is an email address, not a count. No pluralization needed.\",\n },\n ],\n },\n },\n ],\n];\n"],"mappings":";;;;AAKA,MAAaA,QAAuD,CAElE,CACE;CACE,SAAS;CACT,cAAc;CACd,YAAY,EACV,WAAW,CACT;EACE,MAAM;EACN,MAAM;EACP,EACD;EACE,MAAM;EACN,MAAM;EACP,CACF,EACF;CACF,EACD;CACE,SAAS;CACT,SAAS,EACP,QAAQ,CACN;EACE,MAAM;EACN,iBAAiB;EACjB,SACE;EACF,WAAW;EACZ,EACD;EACE,MAAM;EACN,iBAAiB;EACjB,SACE;EACF,WAAW;EACZ,CACF,EACF;CACF,CACF,EAGD,CACE;CACE,SAAS;CACT,cAAc;CACd,YAAY,EACV,WAAW,CACT;EACE,MAAM;EACN,MAAM;EACP,EACD;EACE,MAAM;EACN,MAAM;EACP,CACF,EACF;CACF,EACD;CACE,SAAS;CACT,SAAS,EACP,QAAQ,CACN;EACE,MAAM;EACN,iBAAiB;EACjB,WACE;EACH,EACD;EACE,MAAM;EACN,iBAAiB;EACjB,WACE;EACH,CACF,EACF;CACF,CACF,CACF"}
@@ -0,0 +1,17 @@
1
+ import { LocaleCode } from "lingo.dev/spec";
2
+
3
+ //#region src/translators/pluralization/types.d.ts
4
+
5
+ type PluralizationConfig = {
6
+ sourceLocale: LocaleCode;
7
+ enabled: boolean;
8
+ /**
9
+ * LLM provider for pluralization detection
10
+ * Format: "provider:model" (e.g., "groq:llama3-8b-8192")
11
+ * @default "groq:llama3-8b-8192"
12
+ */
13
+ model: string;
14
+ };
15
+ //#endregion
16
+ export { PluralizationConfig };
17
+ //# sourceMappingURL=types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../../../src/translators/pluralization/types.ts"],"sourcesContent":[],"mappings":";;;;KAkBY,mBAAA;gBACI"}
@@ -0,0 +1,17 @@
1
+ import { LocaleCode } from "lingo.dev/spec";
2
+
3
+ //#region src/translators/pluralization/types.d.ts
4
+
5
+ type PluralizationConfig = {
6
+ sourceLocale: LocaleCode;
7
+ enabled: boolean;
8
+ /**
9
+ * LLM provider for pluralization detection
10
+ * Format: "provider:model" (e.g., "groq:llama3-8b-8192")
11
+ * @default "groq:llama3-8b-8192"
12
+ */
13
+ model: string;
14
+ };
15
+ //#endregion
16
+ export { PluralizationConfig };
17
+ //# sourceMappingURL=types.d.mts.map