@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.
- package/LICENSE.md +201 -0
- package/README.md +192 -0
- package/build/_virtual/rolldown_runtime.cjs +29 -0
- package/build/_virtual/rolldown_runtime.mjs +7 -0
- package/build/index.cjs +0 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.mts +2 -0
- package/build/index.mjs +1 -0
- package/build/metadata/manager.cjs +131 -0
- package/build/metadata/manager.mjs +123 -0
- package/build/metadata/manager.mjs.map +1 -0
- package/build/plugin/build-translator.cjs +198 -0
- package/build/plugin/build-translator.mjs +196 -0
- package/build/plugin/build-translator.mjs.map +1 -0
- package/build/plugin/cleanup.cjs +20 -0
- package/build/plugin/cleanup.mjs +20 -0
- package/build/plugin/cleanup.mjs.map +1 -0
- package/build/plugin/next-compiler-loader.cjs +41 -0
- package/build/plugin/next-compiler-loader.d.cts +12 -0
- package/build/plugin/next-compiler-loader.d.cts.map +1 -0
- package/build/plugin/next-compiler-loader.d.mts +13 -0
- package/build/plugin/next-compiler-loader.d.mts.map +1 -0
- package/build/plugin/next-compiler-loader.mjs +42 -0
- package/build/plugin/next-compiler-loader.mjs.map +1 -0
- package/build/plugin/next-config-loader.cjs +13 -0
- package/build/plugin/next-config-loader.d.cts +8 -0
- package/build/plugin/next-config-loader.d.cts.map +1 -0
- package/build/plugin/next-config-loader.d.mts +9 -0
- package/build/plugin/next-config-loader.d.mts.map +1 -0
- package/build/plugin/next-config-loader.mjs +14 -0
- package/build/plugin/next-config-loader.mjs.map +1 -0
- package/build/plugin/next-locale-client-loader.cjs +9 -0
- package/build/plugin/next-locale-client-loader.d.cts +8 -0
- package/build/plugin/next-locale-client-loader.d.cts.map +1 -0
- package/build/plugin/next-locale-client-loader.d.mts +9 -0
- package/build/plugin/next-locale-client-loader.d.mts.map +1 -0
- package/build/plugin/next-locale-client-loader.mjs +10 -0
- package/build/plugin/next-locale-client-loader.mjs.map +1 -0
- package/build/plugin/next-locale-server-loader.cjs +9 -0
- package/build/plugin/next-locale-server-loader.d.cts +8 -0
- package/build/plugin/next-locale-server-loader.d.cts.map +1 -0
- package/build/plugin/next-locale-server-loader.d.mts +9 -0
- package/build/plugin/next-locale-server-loader.d.mts.map +1 -0
- package/build/plugin/next-locale-server-loader.mjs +10 -0
- package/build/plugin/next-locale-server-loader.mjs.map +1 -0
- package/build/plugin/next.cjs +220 -0
- package/build/plugin/next.d.cts +9 -0
- package/build/plugin/next.d.cts.map +1 -0
- package/build/plugin/next.d.mts +9 -0
- package/build/plugin/next.d.mts.map +1 -0
- package/build/plugin/next.mjs +222 -0
- package/build/plugin/next.mjs.map +1 -0
- package/build/plugin/transform/babel-compat.cjs +13 -0
- package/build/plugin/transform/babel-compat.mjs +10 -0
- package/build/plugin/transform/babel-compat.mjs.map +1 -0
- package/build/plugin/transform/index.cjs +44 -0
- package/build/plugin/transform/index.mjs +42 -0
- package/build/plugin/transform/index.mjs.map +1 -0
- package/build/plugin/transform/metadata.cjs +142 -0
- package/build/plugin/transform/metadata.mjs +141 -0
- package/build/plugin/transform/metadata.mjs.map +1 -0
- package/build/plugin/transform/parse-override.cjs +145 -0
- package/build/plugin/transform/parse-override.mjs +144 -0
- package/build/plugin/transform/parse-override.mjs.map +1 -0
- package/build/plugin/transform/process-file.cjs +391 -0
- package/build/plugin/transform/process-file.mjs +390 -0
- package/build/plugin/transform/process-file.mjs.map +1 -0
- package/build/plugin/transform/use-i18n.cjs +8 -0
- package/build/plugin/transform/use-i18n.mjs +7 -0
- package/build/plugin/transform/use-i18n.mjs.map +1 -0
- package/build/plugin/transform/utils.cjs +205 -0
- package/build/plugin/transform/utils.mjs +192 -0
- package/build/plugin/transform/utils.mjs.map +1 -0
- package/build/plugin/unplugin.cjs +188 -0
- package/build/plugin/unplugin.d.cts +8 -0
- package/build/plugin/unplugin.d.cts.map +1 -0
- package/build/plugin/unplugin.d.mts +8 -0
- package/build/plugin/unplugin.d.mts.map +1 -0
- package/build/plugin/unplugin.mjs +186 -0
- package/build/plugin/unplugin.mjs.map +1 -0
- package/build/plugin/vite.cjs +28 -0
- package/build/plugin/vite.d.cts +9 -0
- package/build/plugin/vite.d.cts.map +1 -0
- package/build/plugin/vite.d.mts +9 -0
- package/build/plugin/vite.d.mts.map +1 -0
- package/build/plugin/vite.mjs +29 -0
- package/build/plugin/vite.mjs.map +1 -0
- package/build/plugin/webpack.cjs +27 -0
- package/build/plugin/webpack.d.cts +8 -0
- package/build/plugin/webpack.d.cts.map +1 -0
- package/build/plugin/webpack.d.mts +8 -0
- package/build/plugin/webpack.d.mts.map +1 -0
- package/build/plugin/webpack.mjs +28 -0
- package/build/plugin/webpack.mjs.map +1 -0
- package/build/react/client/index.cjs +9 -0
- package/build/react/client/index.d.cts +5 -0
- package/build/react/client/index.d.mts +5 -0
- package/build/react/client/index.mjs +6 -0
- package/build/react/client/useTranslation.cjs +71 -0
- package/build/react/client/useTranslation.d.cts +42 -0
- package/build/react/client/useTranslation.d.cts.map +1 -0
- package/build/react/client/useTranslation.d.mts +42 -0
- package/build/react/client/useTranslation.d.mts.map +1 -0
- package/build/react/client/useTranslation.mjs +71 -0
- package/build/react/client/useTranslation.mjs.map +1 -0
- package/build/react/next/client.cjs +25 -0
- package/build/react/next/client.d.cts +9 -0
- package/build/react/next/client.d.cts.map +1 -0
- package/build/react/next/client.d.mts +9 -0
- package/build/react/next/client.d.mts.map +1 -0
- package/build/react/next/client.mjs +24 -0
- package/build/react/next/client.mjs.map +1 -0
- package/build/react/next/cookie-locale-resolver.cjs +29 -0
- package/build/react/next/cookie-locale-resolver.d.cts +33 -0
- package/build/react/next/cookie-locale-resolver.d.cts.map +1 -0
- package/build/react/next/cookie-locale-resolver.d.mts +33 -0
- package/build/react/next/cookie-locale-resolver.d.mts.map +1 -0
- package/build/react/next/cookie-locale-resolver.mjs +29 -0
- package/build/react/next/cookie-locale-resolver.mjs.map +1 -0
- package/build/react/next/server.cjs +21 -0
- package/build/react/next/server.d.cts +13 -0
- package/build/react/next/server.d.cts.map +1 -0
- package/build/react/next/server.d.mts +14 -0
- package/build/react/next/server.d.mts.map +1 -0
- package/build/react/next/server.mjs +20 -0
- package/build/react/next/server.mjs.map +1 -0
- package/build/react/server/ServerLingoProvider.cjs +19 -0
- package/build/react/server/ServerLingoProvider.d.cts +12 -0
- package/build/react/server/ServerLingoProvider.d.cts.map +1 -0
- package/build/react/server/ServerLingoProvider.d.mts +12 -0
- package/build/react/server/ServerLingoProvider.d.mts.map +1 -0
- package/build/react/server/ServerLingoProvider.mjs +19 -0
- package/build/react/server/ServerLingoProvider.mjs.map +1 -0
- package/build/react/server/index.cjs +7 -0
- package/build/react/server/index.d.cts +4 -0
- package/build/react/server/index.d.mts +4 -0
- package/build/react/server/index.mjs +5 -0
- package/build/react/server/useTranslation.cjs +60 -0
- package/build/react/server/useTranslation.d.cts +36 -0
- package/build/react/server/useTranslation.d.cts.map +1 -0
- package/build/react/server/useTranslation.d.mts +36 -0
- package/build/react/server/useTranslation.d.mts.map +1 -0
- package/build/react/server/useTranslation.mjs +60 -0
- package/build/react/server/useTranslation.mjs.map +1 -0
- package/build/react/server-only/index.cjs +42 -0
- package/build/react/server-only/index.d.cts +38 -0
- package/build/react/server-only/index.d.cts.map +1 -0
- package/build/react/server-only/index.d.mts +38 -0
- package/build/react/server-only/index.d.mts.map +1 -0
- package/build/react/server-only/index.mjs +42 -0
- package/build/react/server-only/index.mjs.map +1 -0
- package/build/react/server-only/translations.cjs +85 -0
- package/build/react/server-only/translations.mjs +85 -0
- package/build/react/server-only/translations.mjs.map +1 -0
- package/build/react/shared/LingoContext.cjs +14 -0
- package/build/react/shared/LingoContext.d.cts +41 -0
- package/build/react/shared/LingoContext.d.cts.map +1 -0
- package/build/react/shared/LingoContext.d.mts +41 -0
- package/build/react/shared/LingoContext.d.mts.map +1 -0
- package/build/react/shared/LingoContext.mjs +13 -0
- package/build/react/shared/LingoContext.mjs.map +1 -0
- package/build/react/shared/LingoProvider.cjs +274 -0
- package/build/react/shared/LingoProvider.d.cts +76 -0
- package/build/react/shared/LingoProvider.d.cts.map +1 -0
- package/build/react/shared/LingoProvider.d.mts +76 -0
- package/build/react/shared/LingoProvider.d.mts.map +1 -0
- package/build/react/shared/LingoProvider.mjs +274 -0
- package/build/react/shared/LingoProvider.mjs.map +1 -0
- package/build/react/shared/LocaleSwitcher.cjs +61 -0
- package/build/react/shared/LocaleSwitcher.d.cts +71 -0
- package/build/react/shared/LocaleSwitcher.d.cts.map +1 -0
- package/build/react/shared/LocaleSwitcher.d.mts +71 -0
- package/build/react/shared/LocaleSwitcher.d.mts.map +1 -0
- package/build/react/shared/LocaleSwitcher.mjs +61 -0
- package/build/react/shared/LocaleSwitcher.mjs.map +1 -0
- package/build/react/shared/render-rich-text.cjs +55 -0
- package/build/react/shared/render-rich-text.d.cts +17 -0
- package/build/react/shared/render-rich-text.d.cts.map +1 -0
- package/build/react/shared/render-rich-text.d.mts +17 -0
- package/build/react/shared/render-rich-text.d.mts.map +1 -0
- package/build/react/shared/render-rich-text.mjs +54 -0
- package/build/react/shared/render-rich-text.mjs.map +1 -0
- package/build/react/shared/utils.cjs +34 -0
- package/build/react/shared/utils.mjs +35 -0
- package/build/react/shared/utils.mjs.map +1 -0
- package/build/react/types.d.cts +16 -0
- package/build/react/types.d.cts.map +1 -0
- package/build/react/types.d.mts +16 -0
- package/build/react/types.d.mts.map +1 -0
- package/build/translation-server/logger.cjs +37 -0
- package/build/translation-server/logger.mjs +37 -0
- package/build/translation-server/logger.mjs.map +1 -0
- package/build/translation-server/translation-server.cjs +547 -0
- package/build/translation-server/translation-server.mjs +544 -0
- package/build/translation-server/translation-server.mjs.map +1 -0
- package/build/translation-server/ws-events.cjs +15 -0
- package/build/translation-server/ws-events.mjs +15 -0
- package/build/translation-server/ws-events.mjs.map +1 -0
- package/build/translators/api.cjs +12 -0
- package/build/translators/api.mjs +12 -0
- package/build/translators/api.mjs.map +1 -0
- package/build/translators/cache-factory.cjs +26 -0
- package/build/translators/cache-factory.mjs +27 -0
- package/build/translators/cache-factory.mjs.map +1 -0
- package/build/translators/lingo/model-factory.cjs +179 -0
- package/build/translators/lingo/model-factory.mjs +174 -0
- package/build/translators/lingo/model-factory.mjs.map +1 -0
- package/build/translators/lingo/prompt.cjs +43 -0
- package/build/translators/lingo/prompt.mjs +43 -0
- package/build/translators/lingo/prompt.mjs.map +1 -0
- package/build/translators/lingo/service.cjs +152 -0
- package/build/translators/lingo/service.mjs +152 -0
- package/build/translators/lingo/service.mjs.map +1 -0
- package/build/translators/lingo/shots.cjs +28 -0
- package/build/translators/lingo/shots.mjs +28 -0
- package/build/translators/lingo/shots.mjs.map +1 -0
- package/build/translators/local-cache.cjs +115 -0
- package/build/translators/local-cache.mjs +113 -0
- package/build/translators/local-cache.mjs.map +1 -0
- package/build/translators/parse-xml.cjs +109 -0
- package/build/translators/parse-xml.mjs +108 -0
- package/build/translators/parse-xml.mjs.map +1 -0
- package/build/translators/pluralization/icu-validator.cjs +36 -0
- package/build/translators/pluralization/icu-validator.mjs +36 -0
- package/build/translators/pluralization/icu-validator.mjs.map +1 -0
- package/build/translators/pluralization/pattern-detector.cjs +25 -0
- package/build/translators/pluralization/pattern-detector.mjs +25 -0
- package/build/translators/pluralization/pattern-detector.mjs.map +1 -0
- package/build/translators/pluralization/prompt.cjs +98 -0
- package/build/translators/pluralization/prompt.mjs +98 -0
- package/build/translators/pluralization/prompt.mjs.map +1 -0
- package/build/translators/pluralization/service.cjs +247 -0
- package/build/translators/pluralization/service.mjs +247 -0
- package/build/translators/pluralization/service.mjs.map +1 -0
- package/build/translators/pluralization/shots.cjs +53 -0
- package/build/translators/pluralization/shots.mjs +53 -0
- package/build/translators/pluralization/shots.mjs.map +1 -0
- package/build/translators/pluralization/types.d.cts +17 -0
- package/build/translators/pluralization/types.d.cts.map +1 -0
- package/build/translators/pluralization/types.d.mts +17 -0
- package/build/translators/pluralization/types.d.mts.map +1 -0
- package/build/translators/pseudotranslator/index.cjs +129 -0
- package/build/translators/pseudotranslator/index.mjs +129 -0
- package/build/translators/pseudotranslator/index.mjs.map +1 -0
- package/build/translators/translation-service.cjs +182 -0
- package/build/translators/translation-service.mjs +183 -0
- package/build/translators/translation-service.mjs.map +1 -0
- package/build/translators/translator-factory.cjs +49 -0
- package/build/translators/translator-factory.mjs +50 -0
- package/build/translators/translator-factory.mjs.map +1 -0
- package/build/types.d.cts +161 -0
- package/build/types.d.cts.map +1 -0
- package/build/types.d.mts +161 -0
- package/build/types.d.mts.map +1 -0
- package/build/utils/config-factory.cjs +58 -0
- package/build/utils/config-factory.mjs +58 -0
- package/build/utils/config-factory.mjs.map +1 -0
- package/build/utils/hash.cjs +17 -0
- package/build/utils/hash.mjs +16 -0
- package/build/utils/hash.mjs.map +1 -0
- package/build/utils/is-valid-locale.cjs +14 -0
- package/build/utils/is-valid-locale.mjs +14 -0
- package/build/utils/is-valid-locale.mjs.map +1 -0
- package/build/utils/logger.cjs +51 -0
- package/build/utils/logger.mjs +50 -0
- package/build/utils/logger.mjs.map +1 -0
- package/build/utils/path-helpers.cjs +49 -0
- package/build/utils/path-helpers.mjs +47 -0
- package/build/utils/path-helpers.mjs.map +1 -0
- package/build/utils/timeout.cjs +42 -0
- package/build/utils/timeout.mjs +41 -0
- package/build/utils/timeout.mjs.map +1 -0
- package/build/virtual/code-generator.cjs +54 -0
- package/build/virtual/code-generator.mjs +53 -0
- package/build/virtual/code-generator.mjs.map +1 -0
- package/build/virtual/config.cjs +10 -0
- package/build/virtual/config.d.cts +9 -0
- package/build/virtual/config.d.cts.map +1 -0
- package/build/virtual/config.d.mts +9 -0
- package/build/virtual/config.d.mts.map +1 -0
- package/build/virtual/config.mjs +8 -0
- package/build/virtual/config.mjs.map +1 -0
- package/build/virtual/locale/client.cjs +23 -0
- package/build/virtual/locale/client.d.cts +19 -0
- package/build/virtual/locale/client.d.cts.map +1 -0
- package/build/virtual/locale/client.d.mts +19 -0
- package/build/virtual/locale/client.d.mts.map +1 -0
- package/build/virtual/locale/client.mjs +22 -0
- package/build/virtual/locale/client.mjs.map +1 -0
- package/build/virtual/locale/server.cjs +13 -0
- package/build/virtual/locale/server.d.cts +13 -0
- package/build/virtual/locale/server.d.cts.map +1 -0
- package/build/virtual/locale/server.d.mts +13 -0
- package/build/virtual/locale/server.d.mts.map +1 -0
- package/build/virtual/locale/server.mjs +13 -0
- package/build/virtual/locale/server.mjs.map +1 -0
- package/build/widget/lingo-dev-widget.cjs +228 -0
- package/build/widget/lingo-dev-widget.mjs +229 -0
- package/build/widget/lingo-dev-widget.mjs.map +1 -0
- 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
|