@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
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
import { PseudoTranslator } from "./pseudotranslator/index.mjs";
|
|
2
|
+
import { LingoTranslator } from "./lingo/translator.mjs";
|
|
2
3
|
import { PluralizationService } from "./pluralization/service.mjs";
|
|
4
|
+
import { createCache } from "./cache-factory.mjs";
|
|
5
|
+
import { MemoryTranslationCache } from "./memory-cache.mjs";
|
|
3
6
|
|
|
4
7
|
//#region src/translators/translation-service.ts
|
|
5
8
|
var TranslationService = class {
|
|
6
|
-
useCache = true;
|
|
7
9
|
pluralizationService;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
translator;
|
|
11
|
+
cache;
|
|
12
|
+
constructor(config, logger) {
|
|
11
13
|
this.config = config;
|
|
12
14
|
this.logger = logger;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
15
|
+
const isDev = config.environment === "development";
|
|
16
|
+
if (isDev && config.dev?.usePseudotranslator) {
|
|
17
|
+
this.logger.info("📝 Using pseudotranslator (dev.usePseudotranslator enabled)");
|
|
18
|
+
this.translator = new PseudoTranslator({ delayMedian: 100 }, logger);
|
|
19
|
+
this.cache = new MemoryTranslationCache();
|
|
20
|
+
} else try {
|
|
21
|
+
const models = config.models;
|
|
22
|
+
this.logger.debug(`Creating Lingo translator with models: ${JSON.stringify(models)}`);
|
|
23
|
+
this.cache = createCache(config);
|
|
24
|
+
this.translator = new LingoTranslator({
|
|
25
|
+
models,
|
|
26
|
+
sourceLocale: config.sourceLocale,
|
|
27
|
+
prompt: config.prompt
|
|
28
|
+
}, this.logger);
|
|
29
|
+
if (this.config.pluralization?.enabled) this.pluralizationService = new PluralizationService({
|
|
18
30
|
...this.config.pluralization,
|
|
19
31
|
sourceLocale: this.config.sourceLocale
|
|
20
32
|
}, this.logger);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (isDev) {
|
|
35
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
36
|
+
this.logger.warn(`⚠️ Translation setup error: \n${errorMsg}\n
|
|
37
|
+
⚠️ Auto-fallback to pseudotranslator in development mode.
|
|
38
|
+
Set the required API keys for real translations.`);
|
|
39
|
+
this.translator = new PseudoTranslator({ delayMedian: 100 }, this.logger);
|
|
40
|
+
this.cache = new MemoryTranslationCache();
|
|
41
|
+
} else throw error;
|
|
21
42
|
}
|
|
22
43
|
}
|
|
23
44
|
/**
|
|
@@ -31,77 +52,59 @@ var TranslationService = class {
|
|
|
31
52
|
async translate(locale, metadata, requestedHashes) {
|
|
32
53
|
const startTime = performance.now();
|
|
33
54
|
const workingHashes = requestedHashes || Object.keys(metadata.entries);
|
|
34
|
-
this.logger.
|
|
35
|
-
this.
|
|
36
|
-
const cacheStartTime = performance.now();
|
|
37
|
-
const cachedTranslations = this.useCache ? await this.cache.get(locale) : {};
|
|
38
|
-
const cacheEndTime = performance.now();
|
|
39
|
-
this.logger.debug(`[TRACE] Cache check completed in ${(cacheEndTime - cacheStartTime).toFixed(2)}ms, found ${Object.keys(cachedTranslations).length} entries`);
|
|
55
|
+
this.logger.debug(`Translation requested for ${workingHashes.length} hashes in locale: ${locale}`);
|
|
56
|
+
const cachedTranslations = await this.cache.get(locale);
|
|
40
57
|
const uncachedHashes = workingHashes.filter((hash) => !cachedTranslations[hash]);
|
|
41
|
-
this.logger.debug(
|
|
58
|
+
this.logger.debug(`${uncachedHashes.length} hashes need processing, ${workingHashes.length - uncachedHashes.length} are cached`);
|
|
42
59
|
const cachedCount = workingHashes.length - uncachedHashes.length;
|
|
43
|
-
if (uncachedHashes.length === 0) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
this.logger.info(`Generating translations for ${uncachedHashes.length} uncached hashes in ${locale}...`);
|
|
60
|
+
if (uncachedHashes.length === 0) return {
|
|
61
|
+
translations: this.pickTranslations(cachedTranslations, workingHashes),
|
|
62
|
+
errors: [],
|
|
63
|
+
stats: {
|
|
64
|
+
total: workingHashes.length,
|
|
65
|
+
cached: cachedCount,
|
|
66
|
+
translated: 0,
|
|
67
|
+
failed: 0
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
this.logger.debug(`Generating translations for ${uncachedHashes.length} uncached hashes in ${locale}...`);
|
|
58
71
|
const filteredMetadata = {
|
|
59
72
|
...metadata,
|
|
60
73
|
entries: Object.fromEntries(uncachedHashes.map((hash) => [hash, metadata.entries[hash]]).filter(([_, entry]) => entry !== void 0))
|
|
61
74
|
};
|
|
62
75
|
if (this.pluralizationService) {
|
|
63
|
-
this.logger.
|
|
76
|
+
this.logger.debug(`Processing pluralization for ${Object.keys(filteredMetadata.entries).length} entries...`);
|
|
64
77
|
const pluralStats = await this.pluralizationService.process(filteredMetadata);
|
|
65
|
-
this.logger.
|
|
78
|
+
this.logger.debug(`Pluralization stats: ${pluralStats.pluralized} pluralized, ${pluralStats.rejected} rejected, ${pluralStats.failed} failed`);
|
|
66
79
|
}
|
|
67
80
|
const overriddenTranslations = {};
|
|
68
81
|
const hashesNeedingTranslation = [];
|
|
69
|
-
this.logger.debug(`
|
|
82
|
+
this.logger.debug(`Checking for overrides in ${uncachedHashes.length} entries`);
|
|
70
83
|
for (const hash of uncachedHashes) {
|
|
71
84
|
const entry = filteredMetadata.entries[hash];
|
|
72
85
|
if (!entry) continue;
|
|
73
86
|
if (entry.overrides && entry.overrides[locale]) {
|
|
74
87
|
overriddenTranslations[hash] = entry.overrides[locale];
|
|
75
|
-
this.logger.debug(`
|
|
88
|
+
this.logger.debug(`Using override for ${hash} in locale ${locale}: "${entry.overrides[locale]}"`);
|
|
76
89
|
} else hashesNeedingTranslation.push(hash);
|
|
77
90
|
}
|
|
78
|
-
|
|
79
|
-
if (overrideCount > 0) this.logger.info(`Found ${overrideCount} override(s) for locale ${locale}, skipping AI translation for these entries`);
|
|
80
|
-
this.logger.debug(`[TRACE] Preparing ${hashesNeedingTranslation.length} entries for translation (after overrides)`);
|
|
91
|
+
Object.keys(overriddenTranslations).length;
|
|
81
92
|
const entriesToTranslate = this.prepareEntries(filteredMetadata, hashesNeedingTranslation);
|
|
82
|
-
this.logger.debug(`[TRACE] Prepared ${Object.keys(entriesToTranslate).length} entries`);
|
|
83
93
|
let newTranslations = { ...overriddenTranslations };
|
|
84
94
|
const errors = [];
|
|
85
95
|
if (locale === this.config.sourceLocale) {
|
|
86
|
-
this.logger.debug(`
|
|
96
|
+
this.logger.debug(`Source locale detected, returning sourceText for ${hashesNeedingTranslation.length} entries`);
|
|
87
97
|
for (const [hash, entry] of Object.entries(entriesToTranslate)) newTranslations[hash] = entry.text;
|
|
88
98
|
} else if (Object.keys(entriesToTranslate).length > 0) {
|
|
89
99
|
try {
|
|
90
|
-
this.logger.debug(`
|
|
91
|
-
this.logger.debug(`[TRACE] About to await translator.translate()...`);
|
|
92
|
-
const translateStartTime = performance.now();
|
|
93
|
-
this.logger.debug(`[TRACE] Executing translator.translate() NOW`);
|
|
100
|
+
this.logger.debug(`Translating ${locale} with ${Object.keys(entriesToTranslate).length} entries`);
|
|
94
101
|
const translatedTexts = await this.translator.translate(locale, entriesToTranslate);
|
|
95
|
-
this.logger.debug(`[TRACE] translator.translate() returned`);
|
|
96
102
|
newTranslations = {
|
|
97
103
|
...overriddenTranslations,
|
|
98
104
|
...translatedTexts
|
|
99
105
|
};
|
|
100
|
-
const translateEndTime = performance.now();
|
|
101
|
-
this.logger.debug(`[TRACE] translator.translate() completed in ${(translateEndTime - translateStartTime).toFixed(2)}ms`);
|
|
102
|
-
this.logger.debug(`[TRACE] Received ${Object.keys(translatedTexts).length} translations (+ ${overrideCount} overrides)`);
|
|
103
106
|
} catch (error) {
|
|
104
|
-
this.logger.error(`Translation failed
|
|
107
|
+
this.logger.error(`Translation failed:`, error);
|
|
105
108
|
return {
|
|
106
109
|
translations: this.pickTranslations(cachedTranslations, workingHashes),
|
|
107
110
|
errors: [{
|
|
@@ -122,17 +125,12 @@ var TranslationService = class {
|
|
|
122
125
|
errors.push({
|
|
123
126
|
hash,
|
|
124
127
|
sourceText: entry?.sourceText || "",
|
|
125
|
-
error: "
|
|
128
|
+
error: "Translator doesn't return translation"
|
|
126
129
|
});
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
|
-
if (
|
|
130
|
-
this.logger.debug(`[TRACE] Updating cache with ${Object.keys(newTranslations).length} translations for ${locale}`);
|
|
131
|
-
const updateStartTime = performance.now();
|
|
132
|
+
if (Object.keys(newTranslations).length > 0) try {
|
|
132
133
|
await this.cache.update(locale, newTranslations);
|
|
133
|
-
const updateEndTime = performance.now();
|
|
134
|
-
this.logger.debug(`[TRACE] Cache update completed in ${(updateEndTime - updateStartTime).toFixed(2)}ms`);
|
|
135
|
-
this.logger.info(`Updated cache with ${Object.keys(newTranslations).length} translations for ${locale}`);
|
|
136
134
|
} catch (error) {
|
|
137
135
|
this.logger.error(`Failed to update cache:`, error);
|
|
138
136
|
}
|
|
@@ -142,7 +140,7 @@ var TranslationService = class {
|
|
|
142
140
|
};
|
|
143
141
|
const result = this.pickTranslations(allTranslations, workingHashes);
|
|
144
142
|
const endTime = performance.now();
|
|
145
|
-
this.logger.
|
|
143
|
+
this.logger.debug(`Translation completed for ${locale}: ${Object.keys(newTranslations).length} new, ${cachedCount} cached, ${errors.length} errors in ${(endTime - startTime).toFixed(2)}ms`);
|
|
146
144
|
return {
|
|
147
145
|
translations: result,
|
|
148
146
|
errors,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-service.mjs","names":["translator: Translator<any>","cache: TranslationCache","config: TranslationServiceConfig","logger: Logger","endTime","filteredMetadata: MetadataSchema","overriddenTranslations: Record<string, string>","hashesNeedingTranslation: string[]","newTranslations: Record<string, string>","errors: TranslationError[]","entries: Record<string, TranslatableEntry>","result: Record<string, string>"],"sources":["../../src/translators/translation-service.ts"],"sourcesContent":["/**\n * TranslationService - Main orchestrator for translation workflow\n *\n * Responsibilities:\n * - Coordinates between metadata, cache, and translator\n * - Determines what needs translation\n * - Handles caching strategy\n * - Manages partial failures\n */\n\nimport type { TranslationCache } from \"./cache\";\nimport type { TranslatableEntry, Translator } from \"./api\";\nimport type { MetadataSchema } from \"../types\";\nimport {\n type PluralizationConfig,\n PluralizationService,\n} from \"./pluralization\";\nimport type { Logger } from \"../utils/logger\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\nimport { PseudoTranslator } from \"./pseudotranslator\";\n\nexport interface TranslationServiceConfig {\n /**\n * Source locale (e.g., \"en\")\n */\n sourceLocale: LocaleCode;\n\n /**\n * Pluralization configuration\n * If provided, enables automatic pluralization of source messages\n */\n pluralization: Omit<PluralizationConfig, \"sourceLocale\">;\n}\n\nexport interface TranslationResult {\n /**\n * Successfully translated entries (hash -> translated text)\n */\n translations: Record<string, string>;\n\n errors: TranslationError[];\n\n stats: {\n total: number;\n cached: number;\n translated: number;\n failed: number;\n };\n}\n\nexport interface TranslationError {\n hash: string;\n sourceText: string;\n error: string;\n}\n\nexport class TranslationService {\n private useCache = true;\n private pluralizationService?: PluralizationService;\n\n constructor(\n private translator: Translator<any>,\n private cache: TranslationCache,\n private config: TranslationServiceConfig,\n private logger: Logger,\n ) {\n const isPseudo = this.translator instanceof PseudoTranslator;\n this.useCache = !isPseudo;\n\n // Initialize pluralization service if enabled\n // Do this once at construction to avoid repeated API key validation and model creation\n if (this.config.pluralization?.enabled !== false && !isPseudo) {\n this.logger.info(\"Initializing pluralization service...\");\n this.pluralizationService = new PluralizationService(\n {\n ...this.config.pluralization,\n sourceLocale: this.config.sourceLocale,\n },\n this.logger,\n );\n }\n }\n\n /**\n * Translate entries to target locale\n *\n * @param locale Target locale (including source locale)\n * @param metadata Metadata schema with all entries\n * @param requestedHashes Optional: only translate specific hashes\n * @returns Translation result with translations and errors\n */\n async translate(\n locale: LocaleCode,\n metadata: MetadataSchema,\n requestedHashes?: string[],\n ): Promise<TranslationResult> {\n const startTime = performance.now();\n\n // Step 1: Determine which hashes we need to work with\n const workingHashes = requestedHashes || Object.keys(metadata.entries);\n\n this.logger.info(\n `Translation requested for ${workingHashes.length} hashes in locale: ${locale}`,\n );\n\n // Step 2: Check cache first (same for all locales, including source)\n this.logger.debug(`[TRACE] Checking cache for locale: ${locale}`);\n const cacheStartTime = performance.now();\n const cachedTranslations = this.useCache\n ? await this.cache.get(locale)\n : {};\n const cacheEndTime = performance.now();\n this.logger.debug(\n `[TRACE] Cache check completed in ${(cacheEndTime - cacheStartTime).toFixed(2)}ms, found ${Object.keys(cachedTranslations).length} entries`,\n );\n\n // Step 3: Determine what needs translation/pluralization\n const uncachedHashes = workingHashes.filter(\n (hash) => !cachedTranslations[hash],\n );\n this.logger.debug(\n `[TRACE] ${uncachedHashes.length} hashes need processing, ${workingHashes.length - uncachedHashes.length} are cached`,\n );\n\n const cachedCount = workingHashes.length - uncachedHashes.length;\n\n if (uncachedHashes.length === 0) {\n // All cached!\n const endTime = performance.now();\n this.logger.info(\n `Cache hit for all ${workingHashes.length} hashes in ${locale} in ${(endTime - startTime).toFixed(2)}ms`,\n );\n\n return {\n translations: this.pickTranslations(cachedTranslations, workingHashes),\n errors: [],\n stats: {\n total: workingHashes.length,\n cached: cachedCount,\n translated: 0,\n failed: 0,\n },\n };\n }\n\n this.logger.info(\n `Generating translations for ${uncachedHashes.length} uncached hashes in ${locale}...`,\n );\n\n // Step 4: Filter metadata to only uncached entries\n const filteredMetadata: MetadataSchema = {\n ...metadata,\n entries: Object.fromEntries(\n uncachedHashes\n .map((hash) => [hash, metadata.entries[hash]])\n .filter(([_, entry]) => entry !== undefined),\n ),\n };\n\n // Step 5: Process pluralization for filtered entries\n if (this.pluralizationService) {\n this.logger.info(\n `Processing pluralization for ${Object.keys(filteredMetadata.entries).length} entries...`,\n );\n const pluralStats =\n await this.pluralizationService.process(filteredMetadata);\n this.logger.info(\n `Pluralization stats: ${pluralStats.pluralized} pluralized, ${pluralStats.rejected} rejected, ${pluralStats.failed} failed`,\n );\n }\n\n // Step 6: Separate overridden entries from entries that need translation\n const overriddenTranslations: Record<string, string> = {};\n const hashesNeedingTranslation: string[] = [];\n\n this.logger.debug(\n `[TRACE] Checking for overrides in ${uncachedHashes.length} entries`,\n );\n\n for (const hash of uncachedHashes) {\n const entry = filteredMetadata.entries[hash];\n if (!entry) continue;\n\n // Check if this entry has an override for the current locale\n if (entry.overrides && entry.overrides[locale]) {\n overriddenTranslations[hash] = entry.overrides[locale];\n this.logger.debug(\n `[TRACE] Using override for ${hash} in locale ${locale}: \"${entry.overrides[locale]}\"`,\n );\n } else {\n hashesNeedingTranslation.push(hash);\n }\n }\n\n const overrideCount = Object.keys(overriddenTranslations).length;\n if (overrideCount > 0) {\n this.logger.info(\n `Found ${overrideCount} override(s) for locale ${locale}, skipping AI translation for these entries`,\n );\n }\n\n // Step 7: Prepare entries for translation (excluding overridden ones)\n this.logger.debug(\n `[TRACE] Preparing ${hashesNeedingTranslation.length} entries for translation (after overrides)`,\n );\n const entriesToTranslate = this.prepareEntries(\n filteredMetadata,\n hashesNeedingTranslation,\n );\n this.logger.debug(\n `[TRACE] Prepared ${Object.keys(entriesToTranslate).length} entries`,\n );\n\n // Step 8: Translate or return source text\n let newTranslations: Record<string, string> = { ...overriddenTranslations };\n const errors: TranslationError[] = [];\n\n if (locale === this.config.sourceLocale) {\n // For source locale, just return the (possibly pluralized) sourceText\n this.logger.debug(\n `[TRACE] Source locale detected, returning sourceText for ${hashesNeedingTranslation.length} entries`,\n );\n for (const [hash, entry] of Object.entries(entriesToTranslate)) {\n newTranslations[hash] = entry.text;\n }\n } else if (Object.keys(entriesToTranslate).length > 0) {\n // For other locales, translate only entries without overrides\n try {\n this.logger.debug(\n `[TRACE] Calling translator.translate() for ${locale} with ${Object.keys(entriesToTranslate).length} entries`,\n );\n this.logger.debug(`[TRACE] About to await translator.translate()...`);\n const translateStartTime = performance.now();\n this.logger.debug(`[TRACE] Executing translator.translate() NOW`);\n const translatedTexts = await this.translator.translate(\n locale,\n entriesToTranslate,\n );\n this.logger.debug(`[TRACE] translator.translate() returned`);\n\n // Merge translated texts with overridden translations\n newTranslations = { ...overriddenTranslations, ...translatedTexts };\n\n const translateEndTime = performance.now();\n this.logger.debug(\n `[TRACE] translator.translate() completed in ${(translateEndTime - translateStartTime).toFixed(2)}ms`,\n );\n this.logger.debug(\n `[TRACE] Received ${Object.keys(translatedTexts).length} translations (+ ${overrideCount} overrides)`,\n );\n } catch (error) {\n // Complete failure - log and return what we have from cache\n this.logger.error(`Translation failed completely:`, error);\n\n return {\n translations: this.pickTranslations(\n cachedTranslations,\n workingHashes,\n ),\n errors: [\n {\n hash: \"all\",\n sourceText: \"all\",\n error:\n error instanceof Error\n ? error.message\n : \"Unknown translation error\",\n },\n ],\n stats: {\n total: workingHashes.length,\n cached: cachedCount,\n translated: 0,\n failed: uncachedHashes.length,\n },\n };\n }\n\n // Check for partial failures (some hashes didn't get translated)\n for (const hash of uncachedHashes) {\n if (!newTranslations[hash]) {\n const entry = filteredMetadata.entries[hash];\n errors.push({\n hash,\n sourceText: entry?.sourceText || \"\",\n error: \"Translation not returned by translator\",\n });\n }\n }\n }\n\n // Step 5: Update cache with successful translations (skip for pseudo)\n if (this.useCache && Object.keys(newTranslations).length > 0) {\n try {\n this.logger.debug(\n `[TRACE] Updating cache with ${Object.keys(newTranslations).length} translations for ${locale}`,\n );\n const updateStartTime = performance.now();\n await this.cache.update(locale, newTranslations);\n const updateEndTime = performance.now();\n this.logger.debug(\n `[TRACE] Cache update completed in ${(updateEndTime - updateStartTime).toFixed(2)}ms`,\n );\n this.logger.info(\n `Updated cache with ${Object.keys(newTranslations).length} translations for ${locale}`,\n );\n } catch (error) {\n this.logger.error(`Failed to update cache:`, error);\n // Don't fail the request if cache update fails\n }\n }\n\n // Step 6: Merge and return\n const allTranslations = { ...cachedTranslations, ...newTranslations };\n const result = this.pickTranslations(allTranslations, workingHashes);\n\n const endTime = performance.now();\n this.logger.info(\n `Translation completed for ${locale}: ${Object.keys(newTranslations).length} new, ${cachedCount} cached, ${errors.length} errors in ${(endTime - startTime).toFixed(2)}ms`,\n );\n\n return {\n translations: result,\n errors,\n stats: {\n total: workingHashes.length,\n cached: cachedCount,\n translated: Object.keys(newTranslations).length,\n failed: errors.length,\n },\n };\n }\n\n /**\n * Prepare metadata entries for translation\n */\n private prepareEntries(\n metadata: MetadataSchema,\n hashes: string[],\n ): Record<string, TranslatableEntry> {\n const entries: Record<string, TranslatableEntry> = {};\n\n for (const hash of hashes) {\n const entry = metadata.entries[hash];\n if (entry) {\n entries[hash] = {\n text: entry.sourceText,\n context: entry.context || {},\n };\n }\n }\n\n return entries;\n }\n\n /**\n * Pick only requested translations from the full set\n */\n // TODO (AleksandrSl 14/12/2025): SHould I use this in the build somehow?\n private pickTranslations(\n allTranslations: Record<string, string>,\n requestedHashes: string[],\n ): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const hash of requestedHashes) {\n if (allTranslations[hash]) {\n result[hash] = allTranslations[hash];\n }\n }\n\n return result;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translation-service.mjs","names":["config: TranslationServiceConfig","logger: Logger","filteredMetadata: MetadataSchema","overriddenTranslations: Record<string, string>","hashesNeedingTranslation: string[]","newTranslations: Record<string, string>","errors: TranslationError[]","entries: Record<string, TranslatableEntry>","result: Record<string, string>"],"sources":["../../src/translators/translation-service.ts"],"sourcesContent":["/**\n * TranslationService - Main orchestrator for translation workflow\n *\n * Responsibilities:\n * - Coordinates between metadata, cache, and translator\n * - Determines what needs translation\n * - Handles caching strategy\n * - Manages partial failures\n */\n\nimport type { TranslationCache } from \"./cache\";\nimport type { TranslatableEntry, Translator } from \"./api\";\nimport type { LingoEnvironment, MetadataSchema } from \"../types\";\nimport {\n type PluralizationConfig,\n PluralizationService,\n} from \"./pluralization\";\nimport type { Logger } from \"../utils/logger\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\nimport { PseudoTranslator } from \"./pseudotranslator\";\nimport { LingoTranslator } from \"./lingo\";\nimport { type CacheConfig, createCache } from \"./cache-factory\";\nimport { MemoryTranslationCache } from \"./memory-cache\";\n\nexport type TranslationServiceConfig = {\n /**\n * Source locale (e.g., \"en\")\n */\n sourceLocale: LocaleCode;\n\n /**\n * Pluralization configuration\n * If provided, enables automatic pluralization of source messages\n */\n pluralization: Omit<PluralizationConfig, \"sourceLocale\">;\n models: \"lingo.dev\" | Record<string, string>;\n prompt?: string;\n environment: LingoEnvironment;\n dev?: {\n usePseudotranslator?: boolean;\n };\n} & CacheConfig;\n\nexport interface TranslationResult {\n /**\n * Successfully translated entries (hash -> translated text)\n */\n translations: Record<string, string>;\n\n errors: TranslationError[];\n\n stats: {\n total: number;\n cached: number;\n translated: number;\n failed: number;\n };\n}\n\nexport interface TranslationError {\n hash: string;\n sourceText: string;\n error: string;\n}\n\nexport class TranslationService {\n private pluralizationService?: PluralizationService;\n private translator: Translator<any>;\n private cache: TranslationCache;\n\n constructor(\n private config: TranslationServiceConfig,\n private logger: Logger,\n ) {\n const isDev = config.environment === \"development\";\n\n // 1. Explicit dev override takes precedence\n if (isDev && config.dev?.usePseudotranslator) {\n this.logger.info(\n \"📝 Using pseudotranslator (dev.usePseudotranslator enabled)\",\n );\n this.translator = new PseudoTranslator({ delayMedian: 100 }, logger);\n this.cache = new MemoryTranslationCache();\n } else {\n // 2. Try to create real translator\n // LingoTranslator constructor will validate and fetch API keys\n // If validation fails, it will throw an error with helpful message\n try {\n const models = config.models;\n\n this.logger.debug(\n `Creating Lingo translator with models: ${JSON.stringify(models)}`,\n );\n\n this.cache = createCache(config);\n this.translator = new LingoTranslator(\n {\n models,\n sourceLocale: config.sourceLocale,\n prompt: config.prompt,\n },\n this.logger,\n );\n\n if (this.config.pluralization?.enabled) {\n this.pluralizationService = new PluralizationService(\n {\n ...this.config.pluralization,\n sourceLocale: this.config.sourceLocale,\n },\n this.logger,\n );\n }\n } catch (error) {\n // 3. Auto-fallback in dev mode if creation fails\n if (isDev) {\n // Use console.error to ensure visibility in all contexts (loader, server, etc.)\n const errorMsg =\n error instanceof Error ? error.message : String(error);\n this.logger.warn(`⚠️ Translation setup error: \\n${errorMsg}\\n\n⚠️ Auto-fallback to pseudotranslator in development mode.\nSet the required API keys for real translations.`);\n\n this.translator = new PseudoTranslator(\n { delayMedian: 100 },\n this.logger,\n );\n this.cache = new MemoryTranslationCache();\n } else {\n // 4. Fail in production\n throw error;\n }\n }\n }\n }\n\n /**\n * Translate entries to target locale\n *\n * @param locale Target locale (including source locale)\n * @param metadata Metadata schema with all entries\n * @param requestedHashes Optional: only translate specific hashes\n * @returns Translation result with translations and errors\n */\n async translate(\n locale: LocaleCode,\n metadata: MetadataSchema,\n requestedHashes?: string[],\n ): Promise<TranslationResult> {\n const startTime = performance.now();\n\n // Step 1: Determine which hashes we need to work with\n const workingHashes = requestedHashes || Object.keys(metadata.entries);\n\n this.logger.debug(\n `Translation requested for ${workingHashes.length} hashes in locale: ${locale}`,\n );\n\n // Step 2: Check cache first (same for all locales, including source)\n const cachedTranslations = await this.cache.get(locale);\n\n // Step 3: Determine what needs translation/pluralization\n const uncachedHashes = workingHashes.filter(\n (hash) => !cachedTranslations[hash],\n );\n this.logger.debug(\n `${uncachedHashes.length} hashes need processing, ${workingHashes.length - uncachedHashes.length} are cached`,\n );\n\n const cachedCount = workingHashes.length - uncachedHashes.length;\n\n if (uncachedHashes.length === 0) {\n return {\n translations: this.pickTranslations(cachedTranslations, workingHashes),\n errors: [],\n stats: {\n total: workingHashes.length,\n cached: cachedCount,\n translated: 0,\n failed: 0,\n },\n };\n }\n\n this.logger.debug(\n `Generating translations for ${uncachedHashes.length} uncached hashes in ${locale}...`,\n );\n\n // Step 4: Filter metadata to only uncached entries\n const filteredMetadata: MetadataSchema = {\n ...metadata,\n entries: Object.fromEntries(\n uncachedHashes\n .map((hash) => [hash, metadata.entries[hash]])\n .filter(([_, entry]) => entry !== undefined),\n ),\n };\n\n // Step 5: Process pluralization for filtered entries\n if (this.pluralizationService) {\n this.logger.debug(\n `Processing pluralization for ${Object.keys(filteredMetadata.entries).length} entries...`,\n );\n const pluralStats =\n await this.pluralizationService.process(filteredMetadata);\n this.logger.debug(\n `Pluralization stats: ${pluralStats.pluralized} pluralized, ${pluralStats.rejected} rejected, ${pluralStats.failed} failed`,\n );\n }\n\n // Step 6: Separate overridden entries from entries that need translation\n const overriddenTranslations: Record<string, string> = {};\n const hashesNeedingTranslation: string[] = [];\n\n this.logger.debug(\n `Checking for overrides in ${uncachedHashes.length} entries`,\n );\n\n for (const hash of uncachedHashes) {\n const entry = filteredMetadata.entries[hash];\n if (!entry) continue;\n\n // Check if this entry has an override for the current locale\n if (entry.overrides && entry.overrides[locale]) {\n overriddenTranslations[hash] = entry.overrides[locale];\n this.logger.debug(\n `Using override for ${hash} in locale ${locale}: \"${entry.overrides[locale]}\"`,\n );\n } else {\n hashesNeedingTranslation.push(hash);\n }\n }\n\n const overrideCount = Object.keys(overriddenTranslations).length;\n\n // Step 7: Prepare entries for translation (excluding overridden ones)\n const entriesToTranslate = this.prepareEntries(\n filteredMetadata,\n hashesNeedingTranslation,\n );\n\n // Step 8: Translate or return source text\n let newTranslations: Record<string, string> = { ...overriddenTranslations };\n const errors: TranslationError[] = [];\n\n if (locale === this.config.sourceLocale) {\n // For source locale, just return the (possibly pluralized) sourceText\n this.logger.debug(\n `Source locale detected, returning sourceText for ${hashesNeedingTranslation.length} entries`,\n );\n for (const [hash, entry] of Object.entries(entriesToTranslate)) {\n newTranslations[hash] = entry.text;\n }\n } else if (Object.keys(entriesToTranslate).length > 0) {\n // For other locales, translate only entries without overrides\n try {\n this.logger.debug(\n `Translating ${locale} with ${Object.keys(entriesToTranslate).length} entries`,\n );\n const translatedTexts = await this.translator.translate(\n locale,\n entriesToTranslate,\n );\n // Merge translated texts with overridden translations\n newTranslations = { ...overriddenTranslations, ...translatedTexts };\n } catch (error) {\n this.logger.error(`Translation failed:`, error);\n\n return {\n translations: this.pickTranslations(\n cachedTranslations,\n workingHashes,\n ),\n errors: [\n {\n hash: \"all\",\n sourceText: \"all\",\n error:\n error instanceof Error\n ? error.message\n : \"Unknown translation error\",\n },\n ],\n stats: {\n total: workingHashes.length,\n cached: cachedCount,\n translated: 0,\n failed: uncachedHashes.length,\n },\n };\n }\n\n // Check for partial failures (some hashes didn't get translated)\n for (const hash of uncachedHashes) {\n if (!newTranslations[hash]) {\n const entry = filteredMetadata.entries[hash];\n errors.push({\n hash,\n sourceText: entry?.sourceText || \"\",\n error: \"Translator doesn't return translation\",\n });\n }\n }\n }\n\n // Step 5: Update cache with successful translations (skip for pseudo)\n if (Object.keys(newTranslations).length > 0) {\n try {\n await this.cache.update(locale, newTranslations);\n } catch (error) {\n this.logger.error(`Failed to update cache:`, error);\n // Don't fail the request if cache update fails\n }\n }\n\n // Step 6: Merge and return\n const allTranslations = { ...cachedTranslations, ...newTranslations };\n const result = this.pickTranslations(allTranslations, workingHashes);\n\n const endTime = performance.now();\n this.logger.debug(\n `Translation completed for ${locale}: ${Object.keys(newTranslations).length} new, ${cachedCount} cached, ${errors.length} errors in ${(endTime - startTime).toFixed(2)}ms`,\n );\n\n return {\n translations: result,\n errors,\n stats: {\n total: workingHashes.length,\n cached: cachedCount,\n translated: Object.keys(newTranslations).length,\n failed: errors.length,\n },\n };\n }\n\n /**\n * Prepare metadata entries for translation\n */\n private prepareEntries(\n metadata: MetadataSchema,\n hashes: string[],\n ): Record<string, TranslatableEntry> {\n const entries: Record<string, TranslatableEntry> = {};\n\n for (const hash of hashes) {\n const entry = metadata.entries[hash];\n if (entry) {\n entries[hash] = {\n text: entry.sourceText,\n context: entry.context || {},\n };\n }\n }\n\n return entries;\n }\n\n /**\n * Pick only requested translations from the full set\n */\n // TODO (AleksandrSl 14/12/2025): SHould I use this in the build somehow?\n private pickTranslations(\n allTranslations: Record<string, string>,\n requestedHashes: string[],\n ): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const hash of requestedHashes) {\n if (allTranslations[hash]) {\n result[hash] = allTranslations[hash];\n }\n }\n\n return result;\n }\n}\n"],"mappings":";;;;;;;AAiEA,IAAa,qBAAb,MAAgC;CAC9B,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YACE,AAAQA,QACR,AAAQC,QACR;EAFQ;EACA;EAER,MAAM,QAAQ,OAAO,gBAAgB;AAGrC,MAAI,SAAS,OAAO,KAAK,qBAAqB;AAC5C,QAAK,OAAO,KACV,8DACD;AACD,QAAK,aAAa,IAAI,iBAAiB,EAAE,aAAa,KAAK,EAAE,OAAO;AACpE,QAAK,QAAQ,IAAI,wBAAwB;QAKzC,KAAI;GACF,MAAM,SAAS,OAAO;AAEtB,QAAK,OAAO,MACV,0CAA0C,KAAK,UAAU,OAAO,GACjE;AAED,QAAK,QAAQ,YAAY,OAAO;AAChC,QAAK,aAAa,IAAI,gBACpB;IACE;IACA,cAAc,OAAO;IACrB,QAAQ,OAAO;IAChB,EACD,KAAK,OACN;AAED,OAAI,KAAK,OAAO,eAAe,QAC7B,MAAK,uBAAuB,IAAI,qBAC9B;IACE,GAAG,KAAK,OAAO;IACf,cAAc,KAAK,OAAO;IAC3B,EACD,KAAK,OACN;WAEI,OAAO;AAEd,OAAI,OAAO;IAET,MAAM,WACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACxD,SAAK,OAAO,KAAK,iCAAiC,SAAS;;kDAEnB;AAExC,SAAK,aAAa,IAAI,iBACpB,EAAE,aAAa,KAAK,EACpB,KAAK,OACN;AACD,SAAK,QAAQ,IAAI,wBAAwB;SAGzC,OAAM;;;;;;;;;;;CAcd,MAAM,UACJ,QACA,UACA,iBAC4B;EAC5B,MAAM,YAAY,YAAY,KAAK;EAGnC,MAAM,gBAAgB,mBAAmB,OAAO,KAAK,SAAS,QAAQ;AAEtE,OAAK,OAAO,MACV,6BAA6B,cAAc,OAAO,qBAAqB,SACxE;EAGD,MAAM,qBAAqB,MAAM,KAAK,MAAM,IAAI,OAAO;EAGvD,MAAM,iBAAiB,cAAc,QAClC,SAAS,CAAC,mBAAmB,MAC/B;AACD,OAAK,OAAO,MACV,GAAG,eAAe,OAAO,2BAA2B,cAAc,SAAS,eAAe,OAAO,aAClG;EAED,MAAM,cAAc,cAAc,SAAS,eAAe;AAE1D,MAAI,eAAe,WAAW,EAC5B,QAAO;GACL,cAAc,KAAK,iBAAiB,oBAAoB,cAAc;GACtE,QAAQ,EAAE;GACV,OAAO;IACL,OAAO,cAAc;IACrB,QAAQ;IACR,YAAY;IACZ,QAAQ;IACT;GACF;AAGH,OAAK,OAAO,MACV,+BAA+B,eAAe,OAAO,sBAAsB,OAAO,KACnF;EAGD,MAAMC,mBAAmC;GACvC,GAAG;GACH,SAAS,OAAO,YACd,eACG,KAAK,SAAS,CAAC,MAAM,SAAS,QAAQ,MAAM,CAAC,CAC7C,QAAQ,CAAC,GAAG,WAAW,UAAU,OAAU,CAC/C;GACF;AAGD,MAAI,KAAK,sBAAsB;AAC7B,QAAK,OAAO,MACV,gCAAgC,OAAO,KAAK,iBAAiB,QAAQ,CAAC,OAAO,aAC9E;GACD,MAAM,cACJ,MAAM,KAAK,qBAAqB,QAAQ,iBAAiB;AAC3D,QAAK,OAAO,MACV,wBAAwB,YAAY,WAAW,eAAe,YAAY,SAAS,aAAa,YAAY,OAAO,SACpH;;EAIH,MAAMC,yBAAiD,EAAE;EACzD,MAAMC,2BAAqC,EAAE;AAE7C,OAAK,OAAO,MACV,6BAA6B,eAAe,OAAO,UACpD;AAED,OAAK,MAAM,QAAQ,gBAAgB;GACjC,MAAM,QAAQ,iBAAiB,QAAQ;AACvC,OAAI,CAAC,MAAO;AAGZ,OAAI,MAAM,aAAa,MAAM,UAAU,SAAS;AAC9C,2BAAuB,QAAQ,MAAM,UAAU;AAC/C,SAAK,OAAO,MACV,sBAAsB,KAAK,aAAa,OAAO,KAAK,MAAM,UAAU,QAAQ,GAC7E;SAED,0BAAyB,KAAK,KAAK;;AAIjB,SAAO,KAAK,uBAAuB,CAAC;EAG1D,MAAM,qBAAqB,KAAK,eAC9B,kBACA,yBACD;EAGD,IAAIC,kBAA0C,EAAE,GAAG,wBAAwB;EAC3E,MAAMC,SAA6B,EAAE;AAErC,MAAI,WAAW,KAAK,OAAO,cAAc;AAEvC,QAAK,OAAO,MACV,oDAAoD,yBAAyB,OAAO,UACrF;AACD,QAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,mBAAmB,CAC5D,iBAAgB,QAAQ,MAAM;aAEvB,OAAO,KAAK,mBAAmB,CAAC,SAAS,GAAG;AAErD,OAAI;AACF,SAAK,OAAO,MACV,eAAe,OAAO,QAAQ,OAAO,KAAK,mBAAmB,CAAC,OAAO,UACtE;IACD,MAAM,kBAAkB,MAAM,KAAK,WAAW,UAC5C,QACA,mBACD;AAED,sBAAkB;KAAE,GAAG;KAAwB,GAAG;KAAiB;YAC5D,OAAO;AACd,SAAK,OAAO,MAAM,uBAAuB,MAAM;AAE/C,WAAO;KACL,cAAc,KAAK,iBACjB,oBACA,cACD;KACD,QAAQ,CACN;MACE,MAAM;MACN,YAAY;MACZ,OACE,iBAAiB,QACb,MAAM,UACN;MACP,CACF;KACD,OAAO;MACL,OAAO,cAAc;MACrB,QAAQ;MACR,YAAY;MACZ,QAAQ,eAAe;MACxB;KACF;;AAIH,QAAK,MAAM,QAAQ,eACjB,KAAI,CAAC,gBAAgB,OAAO;IAC1B,MAAM,QAAQ,iBAAiB,QAAQ;AACvC,WAAO,KAAK;KACV;KACA,YAAY,OAAO,cAAc;KACjC,OAAO;KACR,CAAC;;;AAMR,MAAI,OAAO,KAAK,gBAAgB,CAAC,SAAS,EACxC,KAAI;AACF,SAAM,KAAK,MAAM,OAAO,QAAQ,gBAAgB;WACzC,OAAO;AACd,QAAK,OAAO,MAAM,2BAA2B,MAAM;;EAMvD,MAAM,kBAAkB;GAAE,GAAG;GAAoB,GAAG;GAAiB;EACrE,MAAM,SAAS,KAAK,iBAAiB,iBAAiB,cAAc;EAEpE,MAAM,UAAU,YAAY,KAAK;AACjC,OAAK,OAAO,MACV,6BAA6B,OAAO,IAAI,OAAO,KAAK,gBAAgB,CAAC,OAAO,QAAQ,YAAY,WAAW,OAAO,OAAO,cAAc,UAAU,WAAW,QAAQ,EAAE,CAAC,IACxK;AAED,SAAO;GACL,cAAc;GACd;GACA,OAAO;IACL,OAAO,cAAc;IACrB,QAAQ;IACR,YAAY,OAAO,KAAK,gBAAgB,CAAC;IACzC,QAAQ,OAAO;IAChB;GACF;;;;;CAMH,AAAQ,eACN,UACA,QACmC;EACnC,MAAMC,UAA6C,EAAE;AAErD,OAAK,MAAM,QAAQ,QAAQ;GACzB,MAAM,QAAQ,SAAS,QAAQ;AAC/B,OAAI,MACF,SAAQ,QAAQ;IACd,MAAM,MAAM;IACZ,SAAS,MAAM,WAAW,EAAE;IAC7B;;AAIL,SAAO;;;;;CAOT,AAAQ,iBACN,iBACA,iBACwB;EACxB,MAAMC,SAAiC,EAAE;AAEzC,OAAK,MAAM,QAAQ,gBACjB,KAAI,gBAAgB,MAClB,QAAO,QAAQ,gBAAgB;AAInC,SAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingo.dev/compiler",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Lingo.dev Compiler",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"dotenv": "^17.2.3",
|
|
155
155
|
"fast-xml-parser": "^5.3.3",
|
|
156
156
|
"intl-messageformat": "^11.0.6",
|
|
157
|
-
"lingo.dev": "^0.117.
|
|
157
|
+
"lingo.dev": "^0.117.23",
|
|
158
158
|
"lodash": "^4.17.21",
|
|
159
159
|
"proper-lockfile": "^4.1.2",
|
|
160
160
|
"ws": "^8.18.3"
|
|
@@ -176,11 +176,11 @@
|
|
|
176
176
|
"clean": "rm -rf build",
|
|
177
177
|
"test": "vitest --run",
|
|
178
178
|
"test:watch": "vitest -w",
|
|
179
|
-
"test:prepare": "
|
|
180
|
-
"test:e2e": "playwright test
|
|
181
|
-
"test:e2e:next": "playwright test --grep next
|
|
182
|
-
"test:e2e:vite": "playwright test --grep vite
|
|
183
|
-
"test:e2e:shared": "playwright test tests/e2e/shared
|
|
179
|
+
"test:e2e:prepare": "tsx tests/helpers/prepare-fixtures.ts",
|
|
180
|
+
"test:e2e": "playwright test",
|
|
181
|
+
"test:e2e:next": "playwright test --grep next",
|
|
182
|
+
"test:e2e:vite": "playwright test --grep vite",
|
|
183
|
+
"test:e2e:shared": "playwright test tests/e2e/shared",
|
|
184
184
|
"test:e2e:headed": "playwright test --headed",
|
|
185
185
|
"test:e2e:ui": "playwright test --ui",
|
|
186
186
|
"test:e2e:debug": "playwright test --debug",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":["config: LingoTranslatorConfig","logger: Logger","sourceDictionary: DictionarySchema","translatedChunks: DictionarySchema[]","chunks: DictionarySchema[]","mergedEntries: Record<string, string>"],"sources":["../../../src/translators/lingo/service.ts"],"sourcesContent":["import { generateText } from \"ai\";\nimport { LingoDotDevEngine } from \"lingo.dev/sdk\";\nimport {\n dictionaryFrom,\n type DictionarySchema,\n type TranslatableEntry,\n type Translator,\n} from \"../api\";\nimport { getSystemPrompt } from \"./prompt\";\nimport { obj2xml, parseXmlFromResponseText } from \"../parse-xml\";\nimport { shots } from \"./shots\";\nimport {\n createAiModel,\n getLocaleModel,\n validateAndGetApiKeys,\n type ValidatedApiKeys,\n} 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 Service 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 `[TRACE-LINGO] 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 `[TRACE-LINGO] 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 this.logger.debug(\n `[TRACE-LINGO] Chunking dictionary with ${Object.keys(sourceDictionary.entries).length} entries`,\n );\n const chunks = this.chunkDictionary(sourceDictionary);\n this.logger.debug(`[TRACE-LINGO] Split into ${chunks.length} chunks`);\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 `[TRACE-LINGO] 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 `[TRACE-LINGO] Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`,\n );\n\n translatedChunks.push(translatedChunk);\n }\n\n this.logger.debug(`[TRACE-LINGO] All chunks translated, merging results`);\n const result = this.mergeDictionaries(translatedChunks);\n this.logger.debug(\n `[TRACE-LINGO] 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.info(\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.info(\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":"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"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const require_index = require('./pseudotranslator/index.cjs');
|
|
2
|
-
const require_service = require('./lingo/service.cjs');
|
|
3
|
-
|
|
4
|
-
//#region src/translators/translator-factory.ts
|
|
5
|
-
/**
|
|
6
|
-
* Create a translator instance based on configuration
|
|
7
|
-
*
|
|
8
|
-
* Development mode behavior:
|
|
9
|
-
* - If translator is "pseudo" or dev.usePseudotranslator is true, use pseudotranslator
|
|
10
|
-
* - If API keys are missing, auto-fallback to pseudotranslator (with warning)
|
|
11
|
-
* - Otherwise, create real translator
|
|
12
|
-
*
|
|
13
|
-
* Production mode behavior:
|
|
14
|
-
* - Always require real translator with valid API keys
|
|
15
|
-
* - Throw error if API keys are missing
|
|
16
|
-
*
|
|
17
|
-
* Note: Translators are stateless and don't handle caching.
|
|
18
|
-
* Caching is handled by TranslationService layer.
|
|
19
|
-
*
|
|
20
|
-
* API key validation is now done in the LingoTranslator constructor
|
|
21
|
-
* which validates and fetches all keys once at initialization.
|
|
22
|
-
*/
|
|
23
|
-
function createTranslator(config, logger) {
|
|
24
|
-
const isDev = config.environment === "development";
|
|
25
|
-
if (isDev && config.dev?.usePseudotranslator) {
|
|
26
|
-
logger.info("📝 Using pseudotranslator (dev.usePseudotranslator enabled)");
|
|
27
|
-
return new require_index.PseudoTranslator({ delayMedian: 100 }, logger);
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
const models = config.models;
|
|
31
|
-
logger.info(`Creating Lingo translator with models: ${JSON.stringify(models)}`);
|
|
32
|
-
return new require_service.Service({
|
|
33
|
-
models,
|
|
34
|
-
sourceLocale: config.sourceLocale,
|
|
35
|
-
prompt: config.prompt
|
|
36
|
-
}, logger);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (isDev) {
|
|
39
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
40
|
-
logger.error(`\n❌ [Lingo] Translation setup error: ${errorMsg}\n`);
|
|
41
|
-
logger.warn("⚠️ [Lingo] Auto-fallback to pseudotranslator in development mode.\n Set the required API keys for real translations.\n");
|
|
42
|
-
return new require_index.PseudoTranslator({ delayMedian: 100 }, logger);
|
|
43
|
-
}
|
|
44
|
-
throw error;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
exports.createTranslator = createTranslator;
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { PseudoTranslator } from "./pseudotranslator/index.mjs";
|
|
2
|
-
import { Service } from "./lingo/service.mjs";
|
|
3
|
-
|
|
4
|
-
//#region src/translators/translator-factory.ts
|
|
5
|
-
/**
|
|
6
|
-
* Create a translator instance based on configuration
|
|
7
|
-
*
|
|
8
|
-
* Development mode behavior:
|
|
9
|
-
* - If translator is "pseudo" or dev.usePseudotranslator is true, use pseudotranslator
|
|
10
|
-
* - If API keys are missing, auto-fallback to pseudotranslator (with warning)
|
|
11
|
-
* - Otherwise, create real translator
|
|
12
|
-
*
|
|
13
|
-
* Production mode behavior:
|
|
14
|
-
* - Always require real translator with valid API keys
|
|
15
|
-
* - Throw error if API keys are missing
|
|
16
|
-
*
|
|
17
|
-
* Note: Translators are stateless and don't handle caching.
|
|
18
|
-
* Caching is handled by TranslationService layer.
|
|
19
|
-
*
|
|
20
|
-
* API key validation is now done in the LingoTranslator constructor
|
|
21
|
-
* which validates and fetches all keys once at initialization.
|
|
22
|
-
*/
|
|
23
|
-
function createTranslator(config, logger) {
|
|
24
|
-
const isDev = config.environment === "development";
|
|
25
|
-
if (isDev && config.dev?.usePseudotranslator) {
|
|
26
|
-
logger.info("📝 Using pseudotranslator (dev.usePseudotranslator enabled)");
|
|
27
|
-
return new PseudoTranslator({ delayMedian: 100 }, logger);
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
const models = config.models;
|
|
31
|
-
logger.info(`Creating Lingo translator with models: ${JSON.stringify(models)}`);
|
|
32
|
-
return new Service({
|
|
33
|
-
models,
|
|
34
|
-
sourceLocale: config.sourceLocale,
|
|
35
|
-
prompt: config.prompt
|
|
36
|
-
}, logger);
|
|
37
|
-
} catch (error) {
|
|
38
|
-
if (isDev) {
|
|
39
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
40
|
-
logger.error(`\n❌ [Lingo] Translation setup error: ${errorMsg}\n`);
|
|
41
|
-
logger.warn("⚠️ [Lingo] Auto-fallback to pseudotranslator in development mode.\n Set the required API keys for real translations.\n");
|
|
42
|
-
return new PseudoTranslator({ delayMedian: 100 }, logger);
|
|
43
|
-
}
|
|
44
|
-
throw error;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
export { createTranslator };
|
|
50
|
-
//# sourceMappingURL=translator-factory.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"translator-factory.mjs","names":[],"sources":["../../src/translators/translator-factory.ts"],"sourcesContent":["/**\n * Factory for creating translators based on configuration\n */\n\nimport type { Translator } from \"./api\";\nimport { PseudoTranslator } from \"./pseudotranslator\";\nimport { Service } from \"./lingo\";\nimport { Logger } from \"../utils/logger\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\nimport type { LingoEnvironment } from \"../types\";\n\ninterface TranslatorFactoryConfig {\n sourceLocale: LocaleCode;\n models: \"lingo.dev\" | Record<string, string>;\n prompt?: string;\n environment: LingoEnvironment;\n dev?: {\n usePseudotranslator?: boolean;\n };\n}\n\n/**\n * Create a translator instance based on configuration\n *\n * Development mode behavior:\n * - If translator is \"pseudo\" or dev.usePseudotranslator is true, use pseudotranslator\n * - If API keys are missing, auto-fallback to pseudotranslator (with warning)\n * - Otherwise, create real translator\n *\n * Production mode behavior:\n * - Always require real translator with valid API keys\n * - Throw error if API keys are missing\n *\n * Note: Translators are stateless and don't handle caching.\n * Caching is handled by TranslationService layer.\n *\n * API key validation is now done in the LingoTranslator constructor\n * which validates and fetches all keys once at initialization.\n */\nexport function createTranslator(\n config: TranslatorFactoryConfig,\n logger: Logger,\n): Translator<unknown> {\n const isDev = config.environment === \"development\";\n\n // 1. Explicit dev override takes precedence\n if (isDev && config.dev?.usePseudotranslator) {\n logger.info(\"📝 Using pseudotranslator (dev.usePseudotranslator enabled)\");\n return new PseudoTranslator({ delayMedian: 100 }, logger);\n }\n\n // 2. Try to create real translator\n // LingoTranslator constructor will validate and fetch API keys\n // If validation fails, it will throw an error with helpful message\n try {\n const models = config.models;\n\n logger.info(\n `Creating Lingo translator with models: ${JSON.stringify(models)}`,\n );\n\n return new Service(\n {\n models,\n sourceLocale: config.sourceLocale,\n prompt: config.prompt,\n },\n logger,\n );\n } catch (error) {\n // 3. Auto-fallback in dev mode if creation fails\n if (isDev) {\n // Use console.error to ensure visibility in all contexts (loader, server, etc.)\n const errorMsg = error instanceof Error ? error.message : String(error);\n logger.error(`\\n❌ [Lingo] Translation setup error: ${errorMsg}\\n`);\n logger.warn(\n `⚠️ [Lingo] Auto-fallback to pseudotranslator in development mode.\\n` +\n ` Set the required API keys for real translations.\\n`,\n );\n\n return new PseudoTranslator({ delayMedian: 100 }, logger);\n }\n\n // 4. Fail in production\n throw error;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,iBACd,QACA,QACqB;CACrB,MAAM,QAAQ,OAAO,gBAAgB;AAGrC,KAAI,SAAS,OAAO,KAAK,qBAAqB;AAC5C,SAAO,KAAK,8DAA8D;AAC1E,SAAO,IAAI,iBAAiB,EAAE,aAAa,KAAK,EAAE,OAAO;;AAM3D,KAAI;EACF,MAAM,SAAS,OAAO;AAEtB,SAAO,KACL,0CAA0C,KAAK,UAAU,OAAO,GACjE;AAED,SAAO,IAAI,QACT;GACE;GACA,cAAc,OAAO;GACrB,QAAQ,OAAO;GAChB,EACD,OACD;UACM,OAAO;AAEd,MAAI,OAAO;GAET,MAAM,WAAW,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACvE,UAAO,MAAM,wCAAwC,SAAS,IAAI;AAClE,UAAO,KACL,4HAED;AAED,UAAO,IAAI,iBAAiB,EAAE,aAAa,KAAK,EAAE,OAAO;;AAI3D,QAAM"}
|