@lingo.dev/compiler 0.1.2 → 0.1.3
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/build/metadata/manager.mjs +12 -12
- package/build/metadata/manager.mjs.map +1 -1
- package/build/plugin/build-translator.cjs +3 -3
- package/build/plugin/build-translator.mjs +6 -6
- package/build/plugin/build-translator.mjs.map +1 -1
- package/build/plugin/next.cjs +3 -3
- package/build/plugin/next.d.cts.map +1 -1
- package/build/plugin/next.d.mts.map +1 -1
- package/build/plugin/next.mjs +3 -3
- package/build/plugin/next.mjs.map +1 -1
- package/build/plugin/unplugin.cjs +3 -2
- package/build/plugin/unplugin.d.cts.map +1 -1
- package/build/plugin/unplugin.d.mts.map +1 -1
- package/build/plugin/unplugin.mjs +3 -2
- package/build/plugin/unplugin.mjs.map +1 -1
- package/build/translation-server/translation-server.cjs +7 -17
- package/build/translation-server/translation-server.mjs +7 -17
- package/build/translation-server/translation-server.mjs.map +1 -1
- package/build/translators/cache-factory.mjs.map +1 -1
- package/build/translators/lingo/model-factory.cjs +5 -10
- package/build/translators/lingo/model-factory.mjs +5 -10
- package/build/translators/lingo/model-factory.mjs.map +1 -1
- package/build/translators/lingo/provider-details.cjs +69 -0
- package/build/translators/lingo/provider-details.mjs +69 -0
- package/build/translators/lingo/provider-details.mjs.map +1 -0
- package/build/translators/lingo/{service.cjs → translator.cjs} +11 -13
- package/build/translators/lingo/{service.mjs → translator.mjs} +12 -14
- package/build/translators/lingo/translator.mjs.map +1 -0
- package/build/translators/local-cache.mjs +8 -8
- package/build/translators/local-cache.mjs.map +1 -1
- package/build/translators/memory-cache.cjs +47 -0
- package/build/translators/memory-cache.mjs +47 -0
- package/build/translators/memory-cache.mjs.map +1 -0
- package/build/translators/pluralization/service.cjs +19 -44
- package/build/translators/pluralization/service.mjs +19 -44
- package/build/translators/pluralization/service.mjs.map +1 -1
- package/build/translators/pseudotranslator/index.cjs +2 -10
- package/build/translators/pseudotranslator/index.mjs +2 -10
- package/build/translators/pseudotranslator/index.mjs.map +1 -1
- package/build/translators/translation-service.cjs +55 -57
- package/build/translators/translation-service.mjs +55 -57
- package/build/translators/translation-service.mjs.map +1 -1
- package/package.json +7 -7
- package/build/translators/lingo/service.mjs.map +0 -1
- package/build/translators/translator-factory.cjs +0 -49
- package/build/translators/translator-factory.mjs +0 -50
- package/build/translators/translator-factory.mjs.map +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//#region src/translators/lingo/provider-details.ts
|
|
2
|
+
const providerDetails = {
|
|
3
|
+
groq: {
|
|
4
|
+
name: "Groq",
|
|
5
|
+
apiKeyEnvVar: "GROQ_API_KEY",
|
|
6
|
+
apiKeyConfigKey: "llm.groqApiKey",
|
|
7
|
+
getKeyLink: "https://groq.com",
|
|
8
|
+
docsLink: "https://console.groq.com/docs/errors"
|
|
9
|
+
},
|
|
10
|
+
google: {
|
|
11
|
+
name: "Google",
|
|
12
|
+
apiKeyEnvVar: "GOOGLE_API_KEY",
|
|
13
|
+
apiKeyConfigKey: "llm.googleApiKey",
|
|
14
|
+
getKeyLink: "https://ai.google.dev/",
|
|
15
|
+
docsLink: "https://ai.google.dev/gemini-api/docs/troubleshooting"
|
|
16
|
+
},
|
|
17
|
+
openrouter: {
|
|
18
|
+
name: "OpenRouter",
|
|
19
|
+
apiKeyEnvVar: "OPENROUTER_API_KEY",
|
|
20
|
+
apiKeyConfigKey: "llm.openrouterApiKey",
|
|
21
|
+
getKeyLink: "https://openrouter.ai",
|
|
22
|
+
docsLink: "https://openrouter.ai/docs"
|
|
23
|
+
},
|
|
24
|
+
ollama: {
|
|
25
|
+
name: "Ollama",
|
|
26
|
+
apiKeyEnvVar: void 0,
|
|
27
|
+
apiKeyConfigKey: void 0,
|
|
28
|
+
getKeyLink: "https://ollama.com/download",
|
|
29
|
+
docsLink: "https://github.com/ollama/ollama/tree/main/docs"
|
|
30
|
+
},
|
|
31
|
+
mistral: {
|
|
32
|
+
name: "Mistral",
|
|
33
|
+
apiKeyEnvVar: "MISTRAL_API_KEY",
|
|
34
|
+
apiKeyConfigKey: "llm.mistralApiKey",
|
|
35
|
+
getKeyLink: "https://console.mistral.ai",
|
|
36
|
+
docsLink: "https://docs.mistral.ai"
|
|
37
|
+
},
|
|
38
|
+
"lingo.dev": {
|
|
39
|
+
name: "Lingo.dev",
|
|
40
|
+
apiKeyEnvVar: "LINGODOTDEV_API_KEY",
|
|
41
|
+
apiKeyConfigKey: "auth.apiKey",
|
|
42
|
+
getKeyLink: "https://lingo.dev",
|
|
43
|
+
docsLink: "https://lingo.dev/docs"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Format error message when API keys are missing for configured providers
|
|
48
|
+
* @param missingProviders List of providers that are missing API keys
|
|
49
|
+
* @param allProviders Optional: list of all configured providers for context
|
|
50
|
+
*/
|
|
51
|
+
function formatNoApiKeysError(missingProviders, allProviders) {
|
|
52
|
+
const lines = [];
|
|
53
|
+
if (missingProviders.length === 0) return "Translation API keys validated successfully.";
|
|
54
|
+
if (allProviders && allProviders.length > missingProviders.length) lines.push(`Missing API keys for ${missingProviders.length} of ${allProviders.length} configured providers.`);
|
|
55
|
+
else lines.push(`Missing API keys for configured translation providers.`);
|
|
56
|
+
lines.push(`Missing API keys for:`);
|
|
57
|
+
for (const providerId of missingProviders) {
|
|
58
|
+
const details = providerDetails[providerId];
|
|
59
|
+
if (details) if (details.apiKeyEnvVar) lines.push(` • ${details.name}: ${details.apiKeyEnvVar} → ${details.getKeyLink}`);
|
|
60
|
+
else lines.push(` • ${details.name}: ${details.getKeyLink}`);
|
|
61
|
+
else lines.push(` • ${providerId}: (unknown provider)`);
|
|
62
|
+
}
|
|
63
|
+
lines.push(``, `👉 Set the required API keys:`, ` 1. Add to .env file (recommended)`, ` 2. Or export in terminal: export API_KEY_NAME=<your-key>`, ``);
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
export { formatNoApiKeysError };
|
|
69
|
+
//# sourceMappingURL=provider-details.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-details.mjs","names":["providerDetails: Record<string, ProviderDetails>","lines: string[]"],"sources":["../../../src/translators/lingo/provider-details.ts"],"sourcesContent":["/**\n * Provider details for error messages and documentation links\n */\n\nexport interface ProviderDetails {\n name: string; // Display name (e.g., \"Groq\", \"Google\")\n apiKeyEnvVar?: string; // Environment variable name (e.g., \"GROQ_API_KEY\")\n apiKeyConfigKey?: string; // Config key if applicable (e.g., \"llm.groqApiKey\")\n getKeyLink: string; // Link to get API key\n docsLink: string; // Link to API docs for troubleshooting\n}\n\nexport const providerDetails: Record<string, ProviderDetails> = {\n groq: {\n name: \"Groq\",\n apiKeyEnvVar: \"GROQ_API_KEY\",\n apiKeyConfigKey: \"llm.groqApiKey\",\n getKeyLink: \"https://groq.com\",\n docsLink: \"https://console.groq.com/docs/errors\",\n },\n google: {\n name: \"Google\",\n apiKeyEnvVar: \"GOOGLE_API_KEY\",\n apiKeyConfigKey: \"llm.googleApiKey\",\n getKeyLink: \"https://ai.google.dev/\",\n docsLink: \"https://ai.google.dev/gemini-api/docs/troubleshooting\",\n },\n openrouter: {\n name: \"OpenRouter\",\n apiKeyEnvVar: \"OPENROUTER_API_KEY\",\n apiKeyConfigKey: \"llm.openrouterApiKey\",\n getKeyLink: \"https://openrouter.ai\",\n docsLink: \"https://openrouter.ai/docs\",\n },\n ollama: {\n name: \"Ollama\",\n apiKeyEnvVar: undefined,\n apiKeyConfigKey: undefined,\n getKeyLink: \"https://ollama.com/download\",\n docsLink: \"https://github.com/ollama/ollama/tree/main/docs\",\n },\n mistral: {\n name: \"Mistral\",\n apiKeyEnvVar: \"MISTRAL_API_KEY\",\n apiKeyConfigKey: \"llm.mistralApiKey\",\n getKeyLink: \"https://console.mistral.ai\",\n docsLink: \"https://docs.mistral.ai\",\n },\n \"lingo.dev\": {\n name: \"Lingo.dev\",\n apiKeyEnvVar: \"LINGODOTDEV_API_KEY\",\n apiKeyConfigKey: \"auth.apiKey\",\n getKeyLink: \"https://lingo.dev\",\n docsLink: \"https://lingo.dev/docs\",\n },\n};\n\n/**\n * Format error message when API keys are missing for configured providers\n * @param missingProviders List of providers that are missing API keys\n * @param allProviders Optional: list of all configured providers for context\n */\nexport function formatNoApiKeysError(\n missingProviders: string[],\n allProviders?: string[],\n): string {\n const lines: string[] = [];\n\n if (missingProviders.length === 0) {\n // No missing providers (shouldn't happen, but handle it)\n return \"Translation API keys validated successfully.\";\n }\n\n // Header\n if (allProviders && allProviders.length > missingProviders.length) {\n lines.push(\n `Missing API keys for ${missingProviders.length} of ${allProviders.length} configured providers.`,\n );\n } else {\n lines.push(`Missing API keys for configured translation providers.`);\n }\n\n // List missing providers with their environment variables and links\n lines.push(`Missing API keys for:`);\n for (const providerId of missingProviders) {\n const details = providerDetails[providerId];\n if (details) {\n if (details.apiKeyEnvVar) {\n lines.push(\n ` • ${details.name}: ${details.apiKeyEnvVar} → ${details.getKeyLink}`,\n );\n } else {\n lines.push(` • ${details.name}: ${details.getKeyLink}`);\n }\n } else {\n lines.push(` • ${providerId}: (unknown provider)`);\n }\n }\n\n lines.push(\n ``,\n `👉 Set the required API keys:`,\n ` 1. Add to .env file (recommended)`,\n ` 2. Or export in terminal: export API_KEY_NAME=<your-key>`,\n ``,\n );\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";AAYA,MAAaA,kBAAmD;CAC9D,MAAM;EACJ,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,QAAQ;EACN,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,YAAY;EACV,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,QAAQ;EACN,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,SAAS;EACP,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,aAAa;EACX,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACF;;;;;;AAOD,SAAgB,qBACd,kBACA,cACQ;CACR,MAAMC,QAAkB,EAAE;AAE1B,KAAI,iBAAiB,WAAW,EAE9B,QAAO;AAIT,KAAI,gBAAgB,aAAa,SAAS,iBAAiB,OACzD,OAAM,KACJ,wBAAwB,iBAAiB,OAAO,MAAM,aAAa,OAAO,wBAC3E;KAED,OAAM,KAAK,yDAAyD;AAItE,OAAM,KAAK,wBAAwB;AACnC,MAAK,MAAM,cAAc,kBAAkB;EACzC,MAAM,UAAU,gBAAgB;AAChC,MAAI,QACF,KAAI,QAAQ,aACV,OAAM,KACJ,QAAQ,QAAQ,KAAK,IAAI,QAAQ,aAAa,OAAO,QAAQ,aAC9D;MAED,OAAM,KAAK,QAAQ,QAAQ,KAAK,IAAI,QAAQ,aAAa;MAG3D,OAAM,KAAK,QAAQ,WAAW,sBAAsB;;AAIxD,OAAM,KACJ,IACA,iCACA,wCACA,+DACA,GACD;AAED,QAAO,MAAM,KAAK,KAAK"}
|
|
@@ -8,11 +8,11 @@ const require_timeout = require('../../utils/timeout.cjs');
|
|
|
8
8
|
let ai = require("ai");
|
|
9
9
|
let lingo_dev_sdk = require("lingo.dev/sdk");
|
|
10
10
|
|
|
11
|
-
//#region src/translators/lingo/
|
|
11
|
+
//#region src/translators/lingo/translator.ts
|
|
12
12
|
/**
|
|
13
13
|
* Lingo translator using AI models
|
|
14
14
|
*/
|
|
15
|
-
var
|
|
15
|
+
var LingoTranslator = class {
|
|
16
16
|
validatedKeys;
|
|
17
17
|
constructor(config, logger) {
|
|
18
18
|
this.config = config;
|
|
@@ -25,31 +25,29 @@ var Service = class {
|
|
|
25
25
|
* Translate multiple entries
|
|
26
26
|
*/
|
|
27
27
|
async translate(locale, entriesMap) {
|
|
28
|
-
this.logger.debug(`
|
|
28
|
+
this.logger.debug(`translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`);
|
|
29
29
|
const sourceDictionary = require_api.dictionaryFrom(this.config.sourceLocale, Object.fromEntries(Object.entries(entriesMap).map(([hash, entry]) => [hash, entry.text])));
|
|
30
|
-
this.logger.debug(`
|
|
30
|
+
this.logger.debug(`Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
|
|
31
31
|
return (await this.translateDictionary(sourceDictionary, locale)).entries || {};
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Translate a complete dictionary
|
|
35
35
|
*/
|
|
36
36
|
async translateDictionary(sourceDictionary, targetLocale) {
|
|
37
|
-
this.logger.debug(`[TRACE-LINGO] Chunking dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
|
|
38
37
|
const chunks = this.chunkDictionary(sourceDictionary);
|
|
39
|
-
this.logger.debug(`
|
|
38
|
+
this.logger.debug(`Split dictionary with ${Object.keys(sourceDictionary.entries).length} into ${chunks.length} chunks`);
|
|
40
39
|
const translatedChunks = [];
|
|
41
40
|
for (let i = 0; i < chunks.length; i++) {
|
|
42
41
|
const chunk = chunks[i];
|
|
43
|
-
this.logger.debug(`
|
|
42
|
+
this.logger.debug(`Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`);
|
|
44
43
|
const chunkStartTime = performance.now();
|
|
45
44
|
const translatedChunk = await this.translateChunk(chunk, targetLocale);
|
|
46
45
|
const chunkEndTime = performance.now();
|
|
47
|
-
this.logger.debug(`
|
|
46
|
+
this.logger.debug(`Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`);
|
|
48
47
|
translatedChunks.push(translatedChunk);
|
|
49
48
|
}
|
|
50
|
-
this.logger.debug(`[TRACE-LINGO] All chunks translated, merging results`);
|
|
51
49
|
const result = this.mergeDictionaries(translatedChunks);
|
|
52
|
-
this.logger.debug(`
|
|
50
|
+
this.logger.debug(`Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`);
|
|
53
51
|
return result;
|
|
54
52
|
}
|
|
55
53
|
/**
|
|
@@ -66,7 +64,7 @@ var Service = class {
|
|
|
66
64
|
async translateWithLingoDotDev(sourceDictionary, targetLocale) {
|
|
67
65
|
const apiKey = this.validatedKeys["lingo.dev"];
|
|
68
66
|
if (!apiKey) throw new Error("Internal error: Lingo.dev API key not found after validation. Please restart the service.");
|
|
69
|
-
this.logger.
|
|
67
|
+
this.logger.debug(`Using Lingo.dev Engine to localize from "${this.config.sourceLocale}" to "${targetLocale}"`);
|
|
70
68
|
const engine = new lingo_dev_sdk.LingoDotDevEngine({ apiKey });
|
|
71
69
|
try {
|
|
72
70
|
return await require_timeout.withTimeout(engine.localizeObject(sourceDictionary, {
|
|
@@ -85,7 +83,7 @@ var Service = class {
|
|
|
85
83
|
async translateWithLLM(sourceDictionary, targetLocale) {
|
|
86
84
|
const localeModel = require_model_factory.getLocaleModel(this.config.models, this.config.sourceLocale, targetLocale);
|
|
87
85
|
if (!localeModel) throw new Error(`No model configured for translation from ${this.config.sourceLocale} to ${targetLocale}`);
|
|
88
|
-
this.logger.
|
|
86
|
+
this.logger.debug(`Using LLM ("${localeModel.provider}":"${localeModel.name}") to translate from "${this.config.sourceLocale}" to "${targetLocale}"`);
|
|
89
87
|
const aiModel = require_model_factory.createAiModel(localeModel, this.validatedKeys);
|
|
90
88
|
try {
|
|
91
89
|
return require_parse_xml.parseXmlFromResponseText((await require_timeout.withTimeout((0, ai.generateText)({
|
|
@@ -149,4 +147,4 @@ var Service = class {
|
|
|
149
147
|
};
|
|
150
148
|
|
|
151
149
|
//#endregion
|
|
152
|
-
exports.
|
|
150
|
+
exports.LingoTranslator = LingoTranslator;
|
|
@@ -7,11 +7,11 @@ import { DEFAULT_TIMEOUTS, withTimeout } from "../../utils/timeout.mjs";
|
|
|
7
7
|
import { generateText } from "ai";
|
|
8
8
|
import { LingoDotDevEngine } from "lingo.dev/sdk";
|
|
9
9
|
|
|
10
|
-
//#region src/translators/lingo/
|
|
10
|
+
//#region src/translators/lingo/translator.ts
|
|
11
11
|
/**
|
|
12
12
|
* Lingo translator using AI models
|
|
13
13
|
*/
|
|
14
|
-
var
|
|
14
|
+
var LingoTranslator = class {
|
|
15
15
|
validatedKeys;
|
|
16
16
|
constructor(config, logger) {
|
|
17
17
|
this.config = config;
|
|
@@ -24,31 +24,29 @@ var Service = class {
|
|
|
24
24
|
* Translate multiple entries
|
|
25
25
|
*/
|
|
26
26
|
async translate(locale, entriesMap) {
|
|
27
|
-
this.logger.debug(`
|
|
27
|
+
this.logger.debug(`translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`);
|
|
28
28
|
const sourceDictionary = dictionaryFrom(this.config.sourceLocale, Object.fromEntries(Object.entries(entriesMap).map(([hash, entry]) => [hash, entry.text])));
|
|
29
|
-
this.logger.debug(`
|
|
29
|
+
this.logger.debug(`Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
|
|
30
30
|
return (await this.translateDictionary(sourceDictionary, locale)).entries || {};
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* Translate a complete dictionary
|
|
34
34
|
*/
|
|
35
35
|
async translateDictionary(sourceDictionary, targetLocale) {
|
|
36
|
-
this.logger.debug(`[TRACE-LINGO] Chunking dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
|
|
37
36
|
const chunks = this.chunkDictionary(sourceDictionary);
|
|
38
|
-
this.logger.debug(`
|
|
37
|
+
this.logger.debug(`Split dictionary with ${Object.keys(sourceDictionary.entries).length} into ${chunks.length} chunks`);
|
|
39
38
|
const translatedChunks = [];
|
|
40
39
|
for (let i = 0; i < chunks.length; i++) {
|
|
41
40
|
const chunk = chunks[i];
|
|
42
|
-
this.logger.debug(`
|
|
41
|
+
this.logger.debug(`Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`);
|
|
43
42
|
const chunkStartTime = performance.now();
|
|
44
43
|
const translatedChunk = await this.translateChunk(chunk, targetLocale);
|
|
45
44
|
const chunkEndTime = performance.now();
|
|
46
|
-
this.logger.debug(`
|
|
45
|
+
this.logger.debug(`Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`);
|
|
47
46
|
translatedChunks.push(translatedChunk);
|
|
48
47
|
}
|
|
49
|
-
this.logger.debug(`[TRACE-LINGO] All chunks translated, merging results`);
|
|
50
48
|
const result = this.mergeDictionaries(translatedChunks);
|
|
51
|
-
this.logger.debug(`
|
|
49
|
+
this.logger.debug(`Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`);
|
|
52
50
|
return result;
|
|
53
51
|
}
|
|
54
52
|
/**
|
|
@@ -65,7 +63,7 @@ var Service = class {
|
|
|
65
63
|
async translateWithLingoDotDev(sourceDictionary, targetLocale) {
|
|
66
64
|
const apiKey = this.validatedKeys["lingo.dev"];
|
|
67
65
|
if (!apiKey) throw new Error("Internal error: Lingo.dev API key not found after validation. Please restart the service.");
|
|
68
|
-
this.logger.
|
|
66
|
+
this.logger.debug(`Using Lingo.dev Engine to localize from "${this.config.sourceLocale}" to "${targetLocale}"`);
|
|
69
67
|
const engine = new LingoDotDevEngine({ apiKey });
|
|
70
68
|
try {
|
|
71
69
|
return await withTimeout(engine.localizeObject(sourceDictionary, {
|
|
@@ -84,7 +82,7 @@ var Service = class {
|
|
|
84
82
|
async translateWithLLM(sourceDictionary, targetLocale) {
|
|
85
83
|
const localeModel = getLocaleModel(this.config.models, this.config.sourceLocale, targetLocale);
|
|
86
84
|
if (!localeModel) throw new Error(`No model configured for translation from ${this.config.sourceLocale} to ${targetLocale}`);
|
|
87
|
-
this.logger.
|
|
85
|
+
this.logger.debug(`Using LLM ("${localeModel.provider}":"${localeModel.name}") to translate from "${this.config.sourceLocale}" to "${targetLocale}"`);
|
|
88
86
|
const aiModel = createAiModel(localeModel, this.validatedKeys);
|
|
89
87
|
try {
|
|
90
88
|
return parseXmlFromResponseText((await withTimeout(generateText({
|
|
@@ -148,5 +146,5 @@ var Service = class {
|
|
|
148
146
|
};
|
|
149
147
|
|
|
150
148
|
//#endregion
|
|
151
|
-
export {
|
|
152
|
-
//# sourceMappingURL=
|
|
149
|
+
export { LingoTranslator };
|
|
150
|
+
//# sourceMappingURL=translator.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translator.mjs","names":["config: LingoTranslatorConfig","logger: Logger","sourceDictionary: DictionarySchema","translatedChunks: DictionarySchema[]","chunks: DictionarySchema[]","mergedEntries: Record<string, string>"],"sources":["../../../src/translators/lingo/translator.ts"],"sourcesContent":["import { generateText } from \"ai\";\nimport { LingoDotDevEngine } from \"lingo.dev/sdk\";\nimport { dictionaryFrom, type DictionarySchema, type TranslatableEntry, type Translator, } from \"../api\";\nimport { getSystemPrompt } from \"./prompt\";\nimport { obj2xml, parseXmlFromResponseText } from \"../parse-xml\";\nimport { shots } from \"./shots\";\nimport { createAiModel, getLocaleModel, validateAndGetApiKeys, type ValidatedApiKeys, } from \"./model-factory\";\nimport { Logger } from \"../../utils/logger\";\nimport { DEFAULT_TIMEOUTS, withTimeout } from \"../../utils/timeout\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\n/**\n * Lingo Translator configuration\n */\nexport interface LingoTranslatorConfig {\n models: \"lingo.dev\" | Record<string, string>;\n sourceLocale: LocaleCode;\n prompt?: string;\n}\n\n/**\n * Lingo translator using AI models\n */\nexport class LingoTranslator implements Translator<LingoTranslatorConfig> {\n private readonly validatedKeys: ValidatedApiKeys;\n\n constructor(\n readonly config: LingoTranslatorConfig,\n private logger: Logger,\n ) {\n this.logger.info(\"Validating API keys for translation...\");\n this.validatedKeys = validateAndGetApiKeys(config.models);\n this.logger.info(\"✅ API keys validated successfully\");\n }\n\n /**\n * Translate multiple entries\n */\n async translate(\n locale: LocaleCode,\n entriesMap: Record<string, TranslatableEntry>,\n ): Promise<Record<string, string>> {\n this.logger.debug(\n `translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`,\n );\n\n const sourceDictionary: DictionarySchema = dictionaryFrom(\n this.config.sourceLocale,\n Object.fromEntries(\n Object.entries(entriesMap).map(([hash, entry]) => [hash, entry.text]),\n ),\n );\n\n this.logger.debug(\n `Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`,\n );\n const translated = await this.translateDictionary(sourceDictionary, locale);\n\n return translated.entries || {};\n }\n\n /**\n * Translate a complete dictionary\n */\n private async translateDictionary(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n const chunks = this.chunkDictionary(sourceDictionary);\n this.logger.debug(\n `Split dictionary with ${Object.keys(sourceDictionary.entries).length} into ${chunks.length} chunks`,\n );\n\n const translatedChunks: DictionarySchema[] = [];\n\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n this.logger.debug(\n `Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`,\n );\n const chunkStartTime = performance.now();\n\n const translatedChunk = await this.translateChunk(chunk, targetLocale);\n\n const chunkEndTime = performance.now();\n this.logger.debug(\n `Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`,\n );\n\n translatedChunks.push(translatedChunk);\n }\n\n const result = this.mergeDictionaries(translatedChunks);\n this.logger.debug(\n `Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`,\n );\n\n return result;\n }\n\n /**\n * Translate a single chunk\n */\n private async translateChunk(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n if (this.config.models === \"lingo.dev\") {\n return this.translateWithLingoDotDev(sourceDictionary, targetLocale);\n } else {\n return this.translateWithLLM(sourceDictionary, targetLocale);\n }\n }\n\n /**\n * Translate using Lingo.dev Engine\n * Times out after 60 seconds to prevent indefinite hangs\n */\n private async translateWithLingoDotDev(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n const apiKey = this.validatedKeys[\"lingo.dev\"];\n if (!apiKey) {\n throw new Error(\n \"Internal error: Lingo.dev API key not found after validation. Please restart the service.\",\n );\n }\n\n this.logger.debug(\n `Using Lingo.dev Engine to localize from \"${this.config.sourceLocale}\" to \"${targetLocale}\"`,\n );\n\n const engine = new LingoDotDevEngine({ apiKey });\n\n try {\n const result = await withTimeout(\n engine.localizeObject(sourceDictionary, {\n sourceLocale: this.config.sourceLocale,\n targetLocale: targetLocale,\n }),\n DEFAULT_TIMEOUTS.AI_API,\n `Lingo.dev API translation to ${targetLocale}`,\n );\n\n return result as DictionarySchema;\n } catch (error) {\n this.logger.error(`translateWithLingoDotDev() failed:`, error);\n throw new Error(\n `Lingo.dev translation failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n }\n\n /**\n * Translate using generic LLM\n * Times out after 60 seconds to prevent indefinite hangs\n */\n private async translateWithLLM(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n const localeModel = getLocaleModel(\n this.config.models as Record<string, string>,\n this.config.sourceLocale,\n targetLocale,\n );\n\n if (!localeModel) {\n throw new Error(\n `No model configured for translation from ${this.config.sourceLocale} to ${targetLocale}`,\n );\n }\n\n this.logger.debug(\n `Using LLM (\"${localeModel.provider}\":\"${localeModel.name}\") to translate from \"${this.config.sourceLocale}\" to \"${targetLocale}\"`,\n );\n\n const aiModel = createAiModel(localeModel, this.validatedKeys);\n\n try {\n const response = await withTimeout(\n generateText({\n model: aiModel,\n messages: [\n {\n role: \"system\",\n content: getSystemPrompt({\n sourceLocale: this.config.sourceLocale,\n targetLocale,\n prompt: this.config.prompt,\n }),\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(sourceDictionary),\n },\n ],\n }),\n DEFAULT_TIMEOUTS.AI_API,\n `${localeModel.provider} LLM translation to ${targetLocale}`,\n );\n\n return parseXmlFromResponseText<DictionarySchema>(response.text);\n } catch (error) {\n throw new Error(\n `LLM translation failed with ${localeModel.provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n }\n\n /**\n * Split dictionary into chunks for processing\n */\n private chunkDictionary(dictionary: DictionarySchema): DictionarySchema[] {\n const MAX_ENTRIES_PER_CHUNK = 100;\n const { entries, ...rest } = dictionary;\n const chunks: DictionarySchema[] = [];\n\n const entryPairs = Object.entries(entries);\n\n // Split entries into chunks of MAX_ENTRIES_PER_CHUNK\n for (let i = 0; i < entryPairs.length; i += MAX_ENTRIES_PER_CHUNK) {\n const chunkEntries = entryPairs.slice(i, i + MAX_ENTRIES_PER_CHUNK);\n chunks.push({\n ...rest,\n entries: Object.fromEntries(chunkEntries),\n });\n }\n\n return chunks;\n }\n\n /**\n * Merge multiple dictionaries into one\n */\n private mergeDictionaries(\n dictionaries: DictionarySchema[],\n ): DictionarySchema {\n if (dictionaries.length === 0) {\n return dictionaryFrom(this.config.sourceLocale, {});\n }\n\n // Merge all entries from all dictionaries\n const mergedEntries: Record<string, string> = {};\n for (const dict of dictionaries) {\n Object.assign(mergedEntries, dict.entries);\n }\n\n return {\n version: dictionaries[0].version,\n locale: dictionaries[0].locale,\n entries: mergedEntries,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAuBA,IAAa,kBAAb,MAA0E;CACxE,AAAiB;CAEjB,YACE,AAASA,QACT,AAAQC,QACR;EAFS;EACD;AAER,OAAK,OAAO,KAAK,yCAAyC;AAC1D,OAAK,gBAAgB,sBAAsB,OAAO,OAAO;AACzD,OAAK,OAAO,KAAK,oCAAoC;;;;;CAMvD,MAAM,UACJ,QACA,YACiC;AACjC,OAAK,OAAO,MACV,0BAA0B,OAAO,QAAQ,OAAO,KAAK,WAAW,CAAC,OAAO,UACzE;EAED,MAAMC,mBAAqC,eACzC,KAAK,OAAO,cACZ,OAAO,YACL,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,MAAM,WAAW,CAAC,MAAM,MAAM,KAAK,CAAC,CACtE,CACF;AAED,OAAK,OAAO,MACV,kCAAkC,OAAO,KAAK,iBAAiB,QAAQ,CAAC,OAAO,UAChF;AAGD,UAFmB,MAAM,KAAK,oBAAoB,kBAAkB,OAAO,EAEzD,WAAW,EAAE;;;;;CAMjC,MAAc,oBACZ,kBACA,cAC2B;EAC3B,MAAM,SAAS,KAAK,gBAAgB,iBAAiB;AACrD,OAAK,OAAO,MACV,yBAAyB,OAAO,KAAK,iBAAiB,QAAQ,CAAC,OAAO,QAAQ,OAAO,OAAO,SAC7F;EAED,MAAMC,mBAAuC,EAAE;AAE/C,OAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACtC,MAAM,QAAQ,OAAO;AACrB,QAAK,OAAO,MACV,qBAAqB,IAAI,EAAE,GAAG,OAAO,OAAO,QAAQ,OAAO,KAAK,MAAM,QAAQ,CAAC,OAAO,UACvF;GACD,MAAM,iBAAiB,YAAY,KAAK;GAExC,MAAM,kBAAkB,MAAM,KAAK,eAAe,OAAO,aAAa;GAEtE,MAAM,eAAe,YAAY,KAAK;AACtC,QAAK,OAAO,MACV,SAAS,IAAI,EAAE,GAAG,OAAO,OAAO,iBAAiB,eAAe,gBAAgB,QAAQ,EAAE,CAAC,IAC5F;AAED,oBAAiB,KAAK,gBAAgB;;EAGxC,MAAM,SAAS,KAAK,kBAAkB,iBAAiB;AACvD,OAAK,OAAO,MACV,yCAAyC,OAAO,KAAK,OAAO,QAAQ,CAAC,OAAO,UAC7E;AAED,SAAO;;;;;CAMT,MAAc,eACZ,kBACA,cAC2B;AAC3B,MAAI,KAAK,OAAO,WAAW,YACzB,QAAO,KAAK,yBAAyB,kBAAkB,aAAa;MAEpE,QAAO,KAAK,iBAAiB,kBAAkB,aAAa;;;;;;CAQhE,MAAc,yBACZ,kBACA,cAC2B;EAC3B,MAAM,SAAS,KAAK,cAAc;AAClC,MAAI,CAAC,OACH,OAAM,IAAI,MACR,4FACD;AAGH,OAAK,OAAO,MACV,4CAA4C,KAAK,OAAO,aAAa,QAAQ,aAAa,GAC3F;EAED,MAAM,SAAS,IAAI,kBAAkB,EAAE,QAAQ,CAAC;AAEhD,MAAI;AAUF,UATe,MAAM,YACnB,OAAO,eAAe,kBAAkB;IACtC,cAAc,KAAK,OAAO;IACZ;IACf,CAAC,EACF,iBAAiB,QACjB,gCAAgC,eACjC;WAGM,OAAO;AACd,QAAK,OAAO,MAAM,sCAAsC,MAAM;AAC9D,SAAM,IAAI,MACR,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,kBAC3E;;;;;;;CAQL,MAAc,iBACZ,kBACA,cAC2B;EAC3B,MAAM,cAAc,eAClB,KAAK,OAAO,QACZ,KAAK,OAAO,cACZ,aACD;AAED,MAAI,CAAC,YACH,OAAM,IAAI,MACR,4CAA4C,KAAK,OAAO,aAAa,MAAM,eAC5E;AAGH,OAAK,OAAO,MACV,eAAe,YAAY,SAAS,KAAK,YAAY,KAAK,wBAAwB,KAAK,OAAO,aAAa,QAAQ,aAAa,GACjI;EAED,MAAM,UAAU,cAAc,aAAa,KAAK,cAAc;AAE9D,MAAI;AAkCF,UAAO,0BAjCU,MAAM,YACrB,aAAa;IACX,OAAO;IACP,UAAU;KACR;MACE,MAAM;MACN,SAAS,gBAAgB;OACvB,cAAc,KAAK,OAAO;OAC1B;OACA,QAAQ,KAAK,OAAO;OACrB,CAAC;MACH;KAED,GAAG,MAAM,SAAS,eAAe,CAC/B;MACE,MAAM;MACN,SAAS,QAAQ,WAAW,GAAG;MAChC,EACD;MACE,MAAM;MACN,SAAS,QAAQ,WAAW,GAAG;MAChC,CACF,CAAC;KACF;MACE,MAAM;MACN,SAAS,QAAQ,iBAAiB;MACnC;KACF;IACF,CAAC,EACF,iBAAiB,QACjB,GAAG,YAAY,SAAS,sBAAsB,eAC/C,EAE0D,KAAK;WACzD,OAAO;AACd,SAAM,IAAI,MACR,+BAA+B,YAAY,SAAS,IAAI,iBAAiB,QAAQ,MAAM,UAAU,kBAClG;;;;;;CAOL,AAAQ,gBAAgB,YAAkD;EACxE,MAAM,wBAAwB;EAC9B,MAAM,EAAE,SAAS,GAAG,SAAS;EAC7B,MAAMC,SAA6B,EAAE;EAErC,MAAM,aAAa,OAAO,QAAQ,QAAQ;AAG1C,OAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,uBAAuB;GACjE,MAAM,eAAe,WAAW,MAAM,GAAG,IAAI,sBAAsB;AACnE,UAAO,KAAK;IACV,GAAG;IACH,SAAS,OAAO,YAAY,aAAa;IAC1C,CAAC;;AAGJ,SAAO;;;;;CAMT,AAAQ,kBACN,cACkB;AAClB,MAAI,aAAa,WAAW,EAC1B,QAAO,eAAe,KAAK,OAAO,cAAc,EAAE,CAAC;EAIrD,MAAMC,gBAAwC,EAAE;AAChD,OAAK,MAAM,QAAQ,aACjB,QAAO,OAAO,eAAe,KAAK,QAAQ;AAG5C,SAAO;GACL,SAAS,aAAa,GAAG;GACzB,QAAQ,aAAa,GAAG;GACxB,SAAS;GACV"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { dictionaryFrom } from "./api.mjs";
|
|
2
2
|
import { DEFAULT_TIMEOUTS, withTimeout } from "../utils/timeout.mjs";
|
|
3
|
-
import * as fs from "fs/promises";
|
|
3
|
+
import * as fs$1 from "fs/promises";
|
|
4
4
|
import * as path$1 from "path";
|
|
5
5
|
|
|
6
6
|
//#region src/translators/local-cache.ts
|
|
@@ -27,7 +27,7 @@ var LocalTranslationCache = class {
|
|
|
27
27
|
async getDictionary(locale) {
|
|
28
28
|
try {
|
|
29
29
|
const cachePath = this.getCachePath(locale);
|
|
30
|
-
const content = await withTimeout(fs.readFile(cachePath, "utf-8"), DEFAULT_TIMEOUTS.FILE_IO, `Read cache for ${locale}`);
|
|
30
|
+
const content = await withTimeout(fs$1.readFile(cachePath, "utf-8"), DEFAULT_TIMEOUTS.FILE_IO, `Read cache for ${locale}`);
|
|
31
31
|
return JSON.parse(content);
|
|
32
32
|
} catch {
|
|
33
33
|
return null;
|
|
@@ -41,8 +41,8 @@ var LocalTranslationCache = class {
|
|
|
41
41
|
try {
|
|
42
42
|
const cachePath = this.getCachePath(locale);
|
|
43
43
|
const cacheDir = path$1.dirname(cachePath);
|
|
44
|
-
await withTimeout(fs.mkdir(cacheDir, { recursive: true }), DEFAULT_TIMEOUTS.FILE_IO, `Create cache directory for ${locale}`);
|
|
45
|
-
await withTimeout(fs.writeFile(cachePath, JSON.stringify(dictionary, null, 2), "utf-8"), DEFAULT_TIMEOUTS.FILE_IO, `Write cache for ${locale}`);
|
|
44
|
+
await withTimeout(fs$1.mkdir(cacheDir, { recursive: true }), DEFAULT_TIMEOUTS.FILE_IO, `Create cache directory for ${locale}`);
|
|
45
|
+
await withTimeout(fs$1.writeFile(cachePath, JSON.stringify(dictionary, null, 2), "utf-8"), DEFAULT_TIMEOUTS.FILE_IO, `Write cache for ${locale}`);
|
|
46
46
|
} catch (error) {
|
|
47
47
|
this.logger.error(`Failed to write cache for locale ${locale}:`, error);
|
|
48
48
|
throw error;
|
|
@@ -82,7 +82,7 @@ var LocalTranslationCache = class {
|
|
|
82
82
|
async has(locale) {
|
|
83
83
|
try {
|
|
84
84
|
const cachePath = this.getCachePath(locale);
|
|
85
|
-
await fs.access(cachePath);
|
|
85
|
+
await fs$1.access(cachePath);
|
|
86
86
|
return true;
|
|
87
87
|
} catch {
|
|
88
88
|
return false;
|
|
@@ -94,7 +94,7 @@ var LocalTranslationCache = class {
|
|
|
94
94
|
async clear(locale) {
|
|
95
95
|
try {
|
|
96
96
|
const cachePath = this.getCachePath(locale);
|
|
97
|
-
await fs.unlink(cachePath);
|
|
97
|
+
await fs$1.unlink(cachePath);
|
|
98
98
|
} catch {}
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
@@ -102,8 +102,8 @@ var LocalTranslationCache = class {
|
|
|
102
102
|
*/
|
|
103
103
|
async clearAll() {
|
|
104
104
|
try {
|
|
105
|
-
const files = await fs.readdir(this.config.cacheDir);
|
|
106
|
-
await Promise.all(files.filter((file) => file.endsWith(".json")).map((file) => fs.unlink(path$1.join(this.config.cacheDir, file))));
|
|
105
|
+
const files = await fs$1.readdir(this.config.cacheDir);
|
|
106
|
+
await Promise.all(files.filter((file) => file.endsWith(".json")).map((file) => fs$1.unlink(path$1.join(this.config.cacheDir, file))));
|
|
107
107
|
} catch {}
|
|
108
108
|
}
|
|
109
109
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-cache.mjs","names":["logger: Logger","path"],"sources":["../../src/translators/local-cache.ts"],"sourcesContent":["/**\n * Local disk-based translation cache implementation\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport type { LocalCacheConfig, TranslationCache } from \"./cache\";\nimport { dictionaryFrom, type DictionarySchema } from \"./api\";\nimport { DEFAULT_TIMEOUTS, withTimeout } from \"../utils/timeout\";\nimport type { Logger } from \"../utils/logger\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\n/**\n * Local file system cache for translations\n * Stores translations as JSON files in .lingo/cache/\n */\nexport class LocalTranslationCache implements TranslationCache {\n private config: LocalCacheConfig;\n\n constructor(\n config: LocalCacheConfig,\n private logger: Logger,\n ) {\n this.config = config;\n }\n\n private getCachePath(locale: LocaleCode): string {\n return path.join(this.config.cacheDir, `${locale}.json`);\n }\n\n /**\n * Read dictionary file from disk\n * Times out after 10 seconds to prevent indefinite hangs\n */\n async getDictionary(locale: LocaleCode): Promise<DictionarySchema | null> {\n try {\n const cachePath = this.getCachePath(locale);\n const content = await withTimeout(\n fs.readFile(cachePath, \"utf-8\"),\n DEFAULT_TIMEOUTS.FILE_IO,\n `Read cache for ${locale}`,\n );\n return JSON.parse(content);\n } catch {\n return null;\n }\n }\n\n /**\n * Write dictionary file to disk\n * Times out after 10 seconds to prevent indefinite hangs\n */\n private async setDictionary(\n locale: LocaleCode,\n dictionary: DictionarySchema,\n ): Promise<void> {\n try {\n const cachePath = this.getCachePath(locale);\n const cacheDir = path.dirname(cachePath);\n\n // Ensure cache directory exists\n await withTimeout(\n fs.mkdir(cacheDir, { recursive: true }),\n DEFAULT_TIMEOUTS.FILE_IO,\n `Create cache directory for ${locale}`,\n );\n\n // Write cache file\n await withTimeout(\n fs.writeFile(cachePath, JSON.stringify(dictionary, null, 2), \"utf-8\"),\n DEFAULT_TIMEOUTS.FILE_IO,\n `Write cache for ${locale}`,\n );\n } catch (error) {\n this.logger.error(`Failed to write cache for locale ${locale}:`, error);\n throw error;\n }\n }\n\n /**\n * Get cached translations for a locale\n */\n async get(\n locale: LocaleCode,\n hashes?: string[],\n ): Promise<Record<string, string>> {\n const dictionary = await this.getDictionary(locale);\n if (!dictionary) {\n return {};\n }\n if (hashes) {\n return hashes.reduce(\n (acc, hash) => ({ ...acc, [hash]: dictionary.entries[hash] }),\n {},\n );\n }\n return dictionary.entries || {};\n }\n\n /**\n * Update cache with new translations (merge)\n */\n async update(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n const existing = await this.get(locale);\n\n const merged = { ...existing, ...translations };\n\n await this.set(locale, merged);\n }\n\n /**\n * Replace entire cache for a locale\n */\n async set(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n await this.setDictionary(locale, dictionaryFrom(locale, translations));\n }\n\n /**\n * Check if cache exists for a locale\n */\n async has(locale: LocaleCode): Promise<boolean> {\n try {\n const cachePath = this.getCachePath(locale);\n await fs.access(cachePath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Clear cache for a specific locale\n */\n async clear(locale: LocaleCode): Promise<void> {\n try {\n const cachePath = this.getCachePath(locale);\n await fs.unlink(cachePath);\n } catch {\n // Ignore errors if file doesn't exist\n }\n }\n\n /**\n * Clear all cached translations\n */\n async clearAll(): Promise<void> {\n try {\n const files = await fs.readdir(this.config.cacheDir);\n\n await Promise.all(\n files\n .filter((file) => file.endsWith(\".json\"))\n .map((file) => fs.unlink(path.join(this.config.cacheDir, file))),\n );\n } catch {\n // Ignore errors if directory doesn't exist\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,IAAa,wBAAb,MAA+D;CAC7D,AAAQ;CAER,YACE,QACA,AAAQA,QACR;EADQ;AAER,OAAK,SAAS;;CAGhB,AAAQ,aAAa,QAA4B;AAC/C,SAAOC,OAAK,KAAK,KAAK,OAAO,UAAU,GAAG,OAAO,OAAO;;;;;;CAO1D,MAAM,cAAc,QAAsD;AACxE,MAAI;GACF,MAAM,YAAY,KAAK,aAAa,OAAO;GAC3C,MAAM,UAAU,MAAM,
|
|
1
|
+
{"version":3,"file":"local-cache.mjs","names":["logger: Logger","path","fs"],"sources":["../../src/translators/local-cache.ts"],"sourcesContent":["/**\n * Local disk-based translation cache implementation\n */\n\nimport * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport type { LocalCacheConfig, TranslationCache } from \"./cache\";\nimport { dictionaryFrom, type DictionarySchema } from \"./api\";\nimport { DEFAULT_TIMEOUTS, withTimeout } from \"../utils/timeout\";\nimport type { Logger } from \"../utils/logger\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\n/**\n * Local file system cache for translations\n * Stores translations as JSON files in .lingo/cache/\n */\nexport class LocalTranslationCache implements TranslationCache {\n private config: LocalCacheConfig;\n\n constructor(\n config: LocalCacheConfig,\n private logger: Logger,\n ) {\n this.config = config;\n }\n\n private getCachePath(locale: LocaleCode): string {\n return path.join(this.config.cacheDir, `${locale}.json`);\n }\n\n /**\n * Read dictionary file from disk\n * Times out after 10 seconds to prevent indefinite hangs\n */\n async getDictionary(locale: LocaleCode): Promise<DictionarySchema | null> {\n try {\n const cachePath = this.getCachePath(locale);\n const content = await withTimeout(\n fs.readFile(cachePath, \"utf-8\"),\n DEFAULT_TIMEOUTS.FILE_IO,\n `Read cache for ${locale}`,\n );\n return JSON.parse(content);\n } catch {\n return null;\n }\n }\n\n /**\n * Write dictionary file to disk\n * Times out after 10 seconds to prevent indefinite hangs\n */\n private async setDictionary(\n locale: LocaleCode,\n dictionary: DictionarySchema,\n ): Promise<void> {\n try {\n const cachePath = this.getCachePath(locale);\n const cacheDir = path.dirname(cachePath);\n\n // Ensure cache directory exists\n await withTimeout(\n fs.mkdir(cacheDir, { recursive: true }),\n DEFAULT_TIMEOUTS.FILE_IO,\n `Create cache directory for ${locale}`,\n );\n\n // Write cache file\n await withTimeout(\n fs.writeFile(cachePath, JSON.stringify(dictionary, null, 2), \"utf-8\"),\n DEFAULT_TIMEOUTS.FILE_IO,\n `Write cache for ${locale}`,\n );\n } catch (error) {\n this.logger.error(`Failed to write cache for locale ${locale}:`, error);\n throw error;\n }\n }\n\n /**\n * Get cached translations for a locale\n */\n async get(\n locale: LocaleCode,\n hashes?: string[],\n ): Promise<Record<string, string>> {\n const dictionary = await this.getDictionary(locale);\n if (!dictionary) {\n return {};\n }\n if (hashes) {\n return hashes.reduce(\n (acc, hash) => ({ ...acc, [hash]: dictionary.entries[hash] }),\n {},\n );\n }\n return dictionary.entries || {};\n }\n\n /**\n * Update cache with new translations (merge)\n */\n async update(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n const existing = await this.get(locale);\n\n const merged = { ...existing, ...translations };\n\n await this.set(locale, merged);\n }\n\n /**\n * Replace entire cache for a locale\n */\n async set(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n await this.setDictionary(locale, dictionaryFrom(locale, translations));\n }\n\n /**\n * Check if cache exists for a locale\n */\n async has(locale: LocaleCode): Promise<boolean> {\n try {\n const cachePath = this.getCachePath(locale);\n await fs.access(cachePath);\n return true;\n } catch {\n return false;\n }\n }\n\n /**\n * Clear cache for a specific locale\n */\n async clear(locale: LocaleCode): Promise<void> {\n try {\n const cachePath = this.getCachePath(locale);\n await fs.unlink(cachePath);\n } catch {\n // Ignore errors if file doesn't exist\n }\n }\n\n /**\n * Clear all cached translations\n */\n async clearAll(): Promise<void> {\n try {\n const files = await fs.readdir(this.config.cacheDir);\n\n await Promise.all(\n files\n .filter((file) => file.endsWith(\".json\"))\n .map((file) => fs.unlink(path.join(this.config.cacheDir, file))),\n );\n } catch {\n // Ignore errors if directory doesn't exist\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,IAAa,wBAAb,MAA+D;CAC7D,AAAQ;CAER,YACE,QACA,AAAQA,QACR;EADQ;AAER,OAAK,SAAS;;CAGhB,AAAQ,aAAa,QAA4B;AAC/C,SAAOC,OAAK,KAAK,KAAK,OAAO,UAAU,GAAG,OAAO,OAAO;;;;;;CAO1D,MAAM,cAAc,QAAsD;AACxE,MAAI;GACF,MAAM,YAAY,KAAK,aAAa,OAAO;GAC3C,MAAM,UAAU,MAAM,YACpBC,KAAG,SAAS,WAAW,QAAQ,EAC/B,iBAAiB,SACjB,kBAAkB,SACnB;AACD,UAAO,KAAK,MAAM,QAAQ;UACpB;AACN,UAAO;;;;;;;CAQX,MAAc,cACZ,QACA,YACe;AACf,MAAI;GACF,MAAM,YAAY,KAAK,aAAa,OAAO;GAC3C,MAAM,WAAWD,OAAK,QAAQ,UAAU;AAGxC,SAAM,YACJC,KAAG,MAAM,UAAU,EAAE,WAAW,MAAM,CAAC,EACvC,iBAAiB,SACjB,8BAA8B,SAC/B;AAGD,SAAM,YACJA,KAAG,UAAU,WAAW,KAAK,UAAU,YAAY,MAAM,EAAE,EAAE,QAAQ,EACrE,iBAAiB,SACjB,mBAAmB,SACpB;WACM,OAAO;AACd,QAAK,OAAO,MAAM,oCAAoC,OAAO,IAAI,MAAM;AACvE,SAAM;;;;;;CAOV,MAAM,IACJ,QACA,QACiC;EACjC,MAAM,aAAa,MAAM,KAAK,cAAc,OAAO;AACnD,MAAI,CAAC,WACH,QAAO,EAAE;AAEX,MAAI,OACF,QAAO,OAAO,QACX,KAAK,UAAU;GAAE,GAAG;IAAM,OAAO,WAAW,QAAQ;GAAO,GAC5D,EAAE,CACH;AAEH,SAAO,WAAW,WAAW,EAAE;;;;;CAMjC,MAAM,OACJ,QACA,cACe;EAGf,MAAM,SAAS;GAAE,GAFA,MAAM,KAAK,IAAI,OAAO;GAET,GAAG;GAAc;AAE/C,QAAM,KAAK,IAAI,QAAQ,OAAO;;;;;CAMhC,MAAM,IACJ,QACA,cACe;AACf,QAAM,KAAK,cAAc,QAAQ,eAAe,QAAQ,aAAa,CAAC;;;;;CAMxE,MAAM,IAAI,QAAsC;AAC9C,MAAI;GACF,MAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,SAAMA,KAAG,OAAO,UAAU;AAC1B,UAAO;UACD;AACN,UAAO;;;;;;CAOX,MAAM,MAAM,QAAmC;AAC7C,MAAI;GACF,MAAM,YAAY,KAAK,aAAa,OAAO;AAC3C,SAAMA,KAAG,OAAO,UAAU;UACpB;;;;;CAQV,MAAM,WAA0B;AAC9B,MAAI;GACF,MAAM,QAAQ,MAAMA,KAAG,QAAQ,KAAK,OAAO,SAAS;AAEpD,SAAM,QAAQ,IACZ,MACG,QAAQ,SAAS,KAAK,SAAS,QAAQ,CAAC,CACxC,KAAK,SAASA,KAAG,OAAOD,OAAK,KAAK,KAAK,OAAO,UAAU,KAAK,CAAC,CAAC,CACnE;UACK"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/translators/memory-cache.ts
|
|
3
|
+
/**
|
|
4
|
+
* In memory translation cache implementation
|
|
5
|
+
*/
|
|
6
|
+
var MemoryTranslationCache = class {
|
|
7
|
+
cache = /* @__PURE__ */ new Map();
|
|
8
|
+
constructor() {}
|
|
9
|
+
async get(locale, hashes) {
|
|
10
|
+
const localeCache = this.cache.get(locale);
|
|
11
|
+
if (!localeCache) return {};
|
|
12
|
+
if (hashes) return hashes.reduce((acc, hash) => ({
|
|
13
|
+
...acc,
|
|
14
|
+
[hash]: localeCache.get(hash)
|
|
15
|
+
}), {});
|
|
16
|
+
return Object.fromEntries(localeCache);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Update cache with new translations (merge)
|
|
20
|
+
*/
|
|
21
|
+
async update(locale, translations) {
|
|
22
|
+
let localeCache = this.cache.get(locale);
|
|
23
|
+
if (!localeCache) {
|
|
24
|
+
localeCache = /* @__PURE__ */ new Map();
|
|
25
|
+
this.cache.set(locale, localeCache);
|
|
26
|
+
}
|
|
27
|
+
for (const [key, value] of Object.entries(translations)) localeCache.set(key, value);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Replace entire cache for a locale
|
|
31
|
+
*/
|
|
32
|
+
async set(locale, translations) {
|
|
33
|
+
this.cache.set(locale, new Map(Object.entries(translations)));
|
|
34
|
+
}
|
|
35
|
+
async has(locale) {
|
|
36
|
+
return this.cache.has(locale);
|
|
37
|
+
}
|
|
38
|
+
async clear(locale) {
|
|
39
|
+
this.cache.delete(locale);
|
|
40
|
+
}
|
|
41
|
+
async clearAll() {
|
|
42
|
+
this.cache.clear();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
exports.MemoryTranslationCache = MemoryTranslationCache;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/translators/memory-cache.ts
|
|
2
|
+
/**
|
|
3
|
+
* In memory translation cache implementation
|
|
4
|
+
*/
|
|
5
|
+
var MemoryTranslationCache = class {
|
|
6
|
+
cache = /* @__PURE__ */ new Map();
|
|
7
|
+
constructor() {}
|
|
8
|
+
async get(locale, hashes) {
|
|
9
|
+
const localeCache = this.cache.get(locale);
|
|
10
|
+
if (!localeCache) return {};
|
|
11
|
+
if (hashes) return hashes.reduce((acc, hash) => ({
|
|
12
|
+
...acc,
|
|
13
|
+
[hash]: localeCache.get(hash)
|
|
14
|
+
}), {});
|
|
15
|
+
return Object.fromEntries(localeCache);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Update cache with new translations (merge)
|
|
19
|
+
*/
|
|
20
|
+
async update(locale, translations) {
|
|
21
|
+
let localeCache = this.cache.get(locale);
|
|
22
|
+
if (!localeCache) {
|
|
23
|
+
localeCache = /* @__PURE__ */ new Map();
|
|
24
|
+
this.cache.set(locale, localeCache);
|
|
25
|
+
}
|
|
26
|
+
for (const [key, value] of Object.entries(translations)) localeCache.set(key, value);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Replace entire cache for a locale
|
|
30
|
+
*/
|
|
31
|
+
async set(locale, translations) {
|
|
32
|
+
this.cache.set(locale, new Map(Object.entries(translations)));
|
|
33
|
+
}
|
|
34
|
+
async has(locale) {
|
|
35
|
+
return this.cache.has(locale);
|
|
36
|
+
}
|
|
37
|
+
async clear(locale) {
|
|
38
|
+
this.cache.delete(locale);
|
|
39
|
+
}
|
|
40
|
+
async clearAll() {
|
|
41
|
+
this.cache.clear();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { MemoryTranslationCache };
|
|
47
|
+
//# sourceMappingURL=memory-cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-cache.mjs","names":[],"sources":["../../src/translators/memory-cache.ts"],"sourcesContent":["import type { TranslationCache } from \"./cache\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\n/**\n * In memory translation cache implementation\n */\nexport class MemoryTranslationCache implements TranslationCache {\n private cache: Map<LocaleCode, Map<string, string>> = new Map();\n\n constructor() {}\n\n async get(\n locale: LocaleCode,\n hashes?: string[],\n ): Promise<Record<string, string>> {\n const localeCache = this.cache.get(locale);\n if (!localeCache) {\n return {};\n }\n if (hashes) {\n return hashes.reduce(\n (acc, hash) => ({ ...acc, [hash]: localeCache.get(hash) }),\n {},\n );\n }\n return Object.fromEntries(localeCache);\n }\n\n /**\n * Update cache with new translations (merge)\n */\n async update(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n let localeCache = this.cache.get(locale);\n if (!localeCache) {\n localeCache = new Map();\n this.cache.set(locale, localeCache);\n }\n for (const [key, value] of Object.entries(translations)) {\n localeCache.set(key, value);\n }\n }\n\n /**\n * Replace entire cache for a locale\n */\n async set(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n this.cache.set(locale, new Map(Object.entries(translations)));\n }\n\n async has(locale: LocaleCode): Promise<boolean> {\n return this.cache.has(locale);\n }\n\n async clear(locale: LocaleCode): Promise<void> {\n this.cache.delete(locale);\n }\n\n async clearAll(): Promise<void> {\n this.cache.clear();\n }\n}\n"],"mappings":";;;;AAMA,IAAa,yBAAb,MAAgE;CAC9D,AAAQ,wBAA8C,IAAI,KAAK;CAE/D,cAAc;CAEd,MAAM,IACJ,QACA,QACiC;EACjC,MAAM,cAAc,KAAK,MAAM,IAAI,OAAO;AAC1C,MAAI,CAAC,YACH,QAAO,EAAE;AAEX,MAAI,OACF,QAAO,OAAO,QACX,KAAK,UAAU;GAAE,GAAG;IAAM,OAAO,YAAY,IAAI,KAAK;GAAE,GACzD,EAAE,CACH;AAEH,SAAO,OAAO,YAAY,YAAY;;;;;CAMxC,MAAM,OACJ,QACA,cACe;EACf,IAAI,cAAc,KAAK,MAAM,IAAI,OAAO;AACxC,MAAI,CAAC,aAAa;AAChB,iCAAc,IAAI,KAAK;AACvB,QAAK,MAAM,IAAI,QAAQ,YAAY;;AAErC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,aAAa,CACrD,aAAY,IAAI,KAAK,MAAM;;;;;CAO/B,MAAM,IACJ,QACA,cACe;AACf,OAAK,MAAM,IAAI,QAAQ,IAAI,IAAI,OAAO,QAAQ,aAAa,CAAC,CAAC;;CAG/D,MAAM,IAAI,QAAsC;AAC9C,SAAO,KAAK,MAAM,IAAI,OAAO;;CAG/B,MAAM,MAAM,QAAmC;AAC7C,OAAK,MAAM,OAAO,OAAO;;CAG3B,MAAM,WAA0B;AAC9B,OAAK,MAAM,OAAO"}
|
|
@@ -14,23 +14,17 @@ let ai = require("ai");
|
|
|
14
14
|
*/
|
|
15
15
|
var PluralizationService = class {
|
|
16
16
|
languageModel;
|
|
17
|
-
modelName;
|
|
18
17
|
cache = /* @__PURE__ */ new Map();
|
|
19
18
|
prompt;
|
|
20
19
|
sourceLocale;
|
|
21
20
|
constructor(config, logger) {
|
|
22
21
|
this.logger = logger;
|
|
23
22
|
const localeModel = require_model_factory.parseModelString(config.model);
|
|
24
|
-
if (!localeModel) throw new Error(`Invalid model format: "${config.model}"`);
|
|
25
|
-
|
|
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}`;
|
|
23
|
+
if (!localeModel) throw new Error(`Invalid model format in pluralization service: "${config.model}"`);
|
|
24
|
+
this.languageModel = require_model_factory.createAiModel(localeModel, require_model_factory.validateAndGetApiKeys({ "*:*": config.model }));
|
|
31
25
|
this.sourceLocale = config.sourceLocale;
|
|
32
26
|
this.prompt = require_prompt.getSystemPrompt({ sourceLocale: config.sourceLocale });
|
|
33
|
-
this.logger.
|
|
27
|
+
this.logger.debug(`Initialized pluralization service with ${localeModel.provider}:${localeModel.name}`);
|
|
34
28
|
}
|
|
35
29
|
/**
|
|
36
30
|
* Generate ICU formats for multiple candidates in a single batch
|
|
@@ -40,23 +34,20 @@ var PluralizationService = class {
|
|
|
40
34
|
* @returns Map of hash -> ICU generation result
|
|
41
35
|
*/
|
|
42
36
|
async generateBatch(candidates, batchSize = 10) {
|
|
43
|
-
const results =
|
|
44
|
-
const uncachedCandidates = candidates.filter((c) => {
|
|
37
|
+
const { uncachedCandidates, results } = candidates.reduce((acc, c) => {
|
|
45
38
|
const cached = this.cache.get(c.hash);
|
|
46
|
-
if (cached)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
if (cached) acc.results.set(c.hash, cached);
|
|
40
|
+
else acc.uncachedCandidates.push(c);
|
|
41
|
+
return acc;
|
|
42
|
+
}, {
|
|
43
|
+
uncachedCandidates: [],
|
|
44
|
+
results: /* @__PURE__ */ new Map()
|
|
51
45
|
});
|
|
52
|
-
if (uncachedCandidates.length === 0)
|
|
53
|
-
|
|
54
|
-
return results;
|
|
55
|
-
}
|
|
56
|
-
this.logger.info(`Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`);
|
|
46
|
+
if (uncachedCandidates.length === 0) return results;
|
|
47
|
+
this.logger.debug(`Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`);
|
|
57
48
|
for (let i = 0; i < uncachedCandidates.length; i += batchSize) {
|
|
58
49
|
const batch = uncachedCandidates.slice(i, i + batchSize);
|
|
59
|
-
this.logger.
|
|
50
|
+
this.logger.debug(`Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(uncachedCandidates.length / batchSize)} (${batch.length} candidates)`);
|
|
60
51
|
const batchResults = await this.processBatch(batch);
|
|
61
52
|
for (const [hash, result] of batchResults) {
|
|
62
53
|
results.set(hash, result);
|
|
@@ -98,7 +89,7 @@ var PluralizationService = class {
|
|
|
98
89
|
content: require_parse_xml.obj2xml(batchRequest)
|
|
99
90
|
}
|
|
100
91
|
]
|
|
101
|
-
}), require_timeout.DEFAULT_TIMEOUTS.AI_API * 2, `Pluralization with ${this.
|
|
92
|
+
}), require_timeout.DEFAULT_TIMEOUTS.AI_API * 2, `Pluralization with ${this.languageModel}`)).text.trim();
|
|
102
93
|
this.logger.debug(`LLM XML response: ${responseText.substring(0, 200)}...`);
|
|
103
94
|
const parsed = require_parse_xml.parseXmlFromResponseText(responseText);
|
|
104
95
|
const resultArray = Array.isArray(parsed.results.result) ? parsed.results.result : [parsed.results.result];
|
|
@@ -124,7 +115,7 @@ var PluralizationService = class {
|
|
|
124
115
|
}
|
|
125
116
|
}
|
|
126
117
|
for (const candidate of candidates) if (!results.has(candidate.hash)) {
|
|
127
|
-
this.logger.warn(`No result returned for candidate: ${candidate.sourceText}`);
|
|
118
|
+
this.logger.warn(`No result returned for a candidate: ${candidate.sourceText}`);
|
|
128
119
|
results.set(candidate.hash, {
|
|
129
120
|
success: false,
|
|
130
121
|
error: "No result returned by LLM"
|
|
@@ -164,9 +155,9 @@ var PluralizationService = class {
|
|
|
164
155
|
failed: 0,
|
|
165
156
|
durationMs: 0
|
|
166
157
|
};
|
|
167
|
-
this.logger.
|
|
158
|
+
this.logger.debug(`Starting pluralization processing for ${totalEntries} entries`);
|
|
168
159
|
const candidates = require_pattern_detector.detectPluralCandidates(Object.fromEntries(Object.entries(metadata.entries).map(([hash, entry]) => [hash, entry.sourceText])), this.logger);
|
|
169
|
-
this.logger.
|
|
160
|
+
this.logger.debug(`Found ${candidates.length} plural candidates (${(candidates.length / totalEntries * 100).toFixed(1)}%)`);
|
|
170
161
|
if (candidates.length === 0) return {
|
|
171
162
|
total: totalEntries,
|
|
172
163
|
candidates: 0,
|
|
@@ -210,12 +201,12 @@ var PluralizationService = class {
|
|
|
210
201
|
failed++;
|
|
211
202
|
continue;
|
|
212
203
|
}
|
|
213
|
-
this.logger.
|
|
204
|
+
this.logger.debug(`Pluralizing: "${entry.sourceText}" -> "${result.icuText}"`);
|
|
214
205
|
entry.sourceText = result.icuText;
|
|
215
206
|
pluralized++;
|
|
216
207
|
}
|
|
217
208
|
const duration = performance.now() - startTime;
|
|
218
|
-
this.logger.
|
|
209
|
+
this.logger.debug(`Pluralization completed: ${pluralized} pluralized, ${rejected} rejected, ${failed} failed in ${duration.toFixed(0)}ms`);
|
|
219
210
|
return {
|
|
220
211
|
total: totalEntries,
|
|
221
212
|
candidates: candidates.length,
|
|
@@ -225,22 +216,6 @@ var PluralizationService = class {
|
|
|
225
216
|
durationMs: duration
|
|
226
217
|
};
|
|
227
218
|
}
|
|
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
219
|
};
|
|
245
220
|
|
|
246
221
|
//#endregion
|