@lingo.dev/compiler 0.1.13 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/plugin/build-translator.cjs +4 -4
- package/build/plugin/build-translator.mjs +4 -4
- package/build/plugin/build-translator.mjs.map +1 -1
- package/build/react/server/ServerLingoProvider.d.cts +2 -2
- package/build/react/server/ServerLingoProvider.d.mts +2 -2
- package/build/react/shared/LingoProvider.d.cts +2 -2
- package/build/react/shared/LocaleSwitcher.d.cts +2 -2
- package/build/react/shared/LocaleSwitcher.d.mts +2 -2
- package/build/translators/lingo/model-factory.cjs +13 -2
- package/build/translators/lingo/model-factory.mjs +13 -2
- package/build/translators/lingo/model-factory.mjs.map +1 -1
- package/build/translators/pluralization/types.d.cts +1 -1
- package/build/translators/pluralization/types.d.mts +1 -1
- package/build/types.d.cts +3 -1
- package/build/types.d.cts.map +1 -1
- package/build/types.d.mts +3 -1
- package/build/types.d.mts.map +1 -1
- package/build/utils/config-factory.cjs +34 -2
- package/build/utils/config-factory.mjs +34 -2
- package/build/utils/config-factory.mjs.map +1 -1
- package/package.json +3 -2
|
@@ -59,7 +59,7 @@ async function processBuildTranslations(options) {
|
|
|
59
59
|
},
|
|
60
60
|
config
|
|
61
61
|
});
|
|
62
|
-
const needsSourceLocale = config.pluralization?.enabled
|
|
62
|
+
const needsSourceLocale = config.pluralization?.enabled === true;
|
|
63
63
|
const allLocales = needsSourceLocale ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
64
64
|
require_logger.logger.info(`Processing translations for ${allLocales.length} locale(s)${needsSourceLocale ? " (including source locale for pluralization)" : ""}...`);
|
|
65
65
|
const stats = {};
|
|
@@ -110,7 +110,7 @@ async function validateCache(config, metadata, cache) {
|
|
|
110
110
|
const allHashes = Object.keys(metadata.entries);
|
|
111
111
|
const missingLocales = [];
|
|
112
112
|
const incompleteLocales = [];
|
|
113
|
-
const allLocales = config.pluralization?.enabled
|
|
113
|
+
const allLocales = config.pluralization?.enabled === true ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
114
114
|
for (const locale of allLocales) try {
|
|
115
115
|
const entries = await cache.get(locale);
|
|
116
116
|
if (Object.keys(entries).length === 0) {
|
|
@@ -140,7 +140,7 @@ async function validateCache(config, metadata, cache) {
|
|
|
140
140
|
function buildCacheStats(config, metadata) {
|
|
141
141
|
const totalEntries = Object.keys(metadata.entries).length;
|
|
142
142
|
const stats = {};
|
|
143
|
-
const allLocales = config.pluralization?.enabled
|
|
143
|
+
const allLocales = config.pluralization?.enabled === true ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
144
144
|
for (const locale of allLocales) stats[locale] = {
|
|
145
145
|
total: totalEntries,
|
|
146
146
|
translated: totalEntries,
|
|
@@ -153,7 +153,7 @@ async function copyStaticFiles(config, publicOutputPath, metadata, cache) {
|
|
|
153
153
|
await fs_promises.default.mkdir(publicOutputPath, { recursive: true });
|
|
154
154
|
const usedHashes = new Set(Object.keys(metadata.entries));
|
|
155
155
|
require_logger.logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);
|
|
156
|
-
const allLocales = config.pluralization?.enabled
|
|
156
|
+
const allLocales = config.pluralization?.enabled === true ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
157
157
|
for (const locale of allLocales) {
|
|
158
158
|
const publicFilePath = path.default.join(publicOutputPath, `${locale}.json`);
|
|
159
159
|
try {
|
|
@@ -56,7 +56,7 @@ async function processBuildTranslations(options) {
|
|
|
56
56
|
},
|
|
57
57
|
config
|
|
58
58
|
});
|
|
59
|
-
const needsSourceLocale = config.pluralization?.enabled
|
|
59
|
+
const needsSourceLocale = config.pluralization?.enabled === true;
|
|
60
60
|
const allLocales = needsSourceLocale ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
61
61
|
logger.info(`Processing translations for ${allLocales.length} locale(s)${needsSourceLocale ? " (including source locale for pluralization)" : ""}...`);
|
|
62
62
|
const stats = {};
|
|
@@ -107,7 +107,7 @@ async function validateCache(config, metadata, cache) {
|
|
|
107
107
|
const allHashes = Object.keys(metadata.entries);
|
|
108
108
|
const missingLocales = [];
|
|
109
109
|
const incompleteLocales = [];
|
|
110
|
-
const allLocales = config.pluralization?.enabled
|
|
110
|
+
const allLocales = config.pluralization?.enabled === true ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
111
111
|
for (const locale of allLocales) try {
|
|
112
112
|
const entries = await cache.get(locale);
|
|
113
113
|
if (Object.keys(entries).length === 0) {
|
|
@@ -137,7 +137,7 @@ async function validateCache(config, metadata, cache) {
|
|
|
137
137
|
function buildCacheStats(config, metadata) {
|
|
138
138
|
const totalEntries = Object.keys(metadata.entries).length;
|
|
139
139
|
const stats = {};
|
|
140
|
-
const allLocales = config.pluralization?.enabled
|
|
140
|
+
const allLocales = config.pluralization?.enabled === true ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
141
141
|
for (const locale of allLocales) stats[locale] = {
|
|
142
142
|
total: totalEntries,
|
|
143
143
|
translated: totalEntries,
|
|
@@ -150,7 +150,7 @@ async function copyStaticFiles(config, publicOutputPath, metadata, cache) {
|
|
|
150
150
|
await fsPromises.mkdir(publicOutputPath, { recursive: true });
|
|
151
151
|
const usedHashes = new Set(Object.keys(metadata.entries));
|
|
152
152
|
logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);
|
|
153
|
-
const allLocales = config.pluralization?.enabled
|
|
153
|
+
const allLocales = config.pluralization?.enabled === true ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
154
154
|
for (const locale of allLocales) {
|
|
155
155
|
const publicFilePath = path.join(publicOutputPath, `${locale}.json`);
|
|
156
156
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-translator.mjs","names":["translationServer: TranslationServer | undefined","stats: BuildTranslationResult[\"stats\"]","errors: Array<{ locale: LocaleCode; error: string }>","missingLocales: string[]","incompleteLocales: Array<{\n locale: LocaleCode;\n missing: number;\n total: number;\n }>","fs"],"sources":["../../src/plugin/build-translator.ts"],"sourcesContent":["/**\n * Build-time translation processor\n *\n * Handles translation generation and validation at build time\n * Supports two modes:\n * - \"translate\": Generate all translations, fail if translation fails\n * - \"cache-only\": Validate cache completeness, fail if incomplete\n */\n// TODO (AleksandrSl 08/12/2025): Add ICU validation for messages? The problem is that we don't know which will be rendered as a simple text\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport type { LingoConfig, MetadataSchema } from \"../types\";\nimport { logger } from \"../utils/logger\";\nimport { startTranslationServer, type TranslationServer, } from \"../translation-server\";\nimport { loadMetadata } from \"../metadata/manager\";\nimport { createCache, type TranslationCache, TranslationService, } from \"../translators\";\nimport { dictionaryFrom } from \"../translators/api\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\nexport interface BuildTranslationOptions {\n config: LingoConfig;\n publicOutputPath: string;\n metadataFilePath: string;\n}\n\nexport interface BuildTranslationResult {\n /**\n * Whether the build succeeded\n */\n success: boolean;\n\n /**\n * Error message if build failed\n */\n error?: string;\n\n /**\n * Translation statistics per locale\n */\n stats: Record<\n string,\n {\n total: number;\n translated: number;\n failed: number;\n }\n >;\n}\n\n/**\n * Process translations at build time\n *\n * @throws Error if validation or translation fails (causes build to fail)\n */\nexport async function processBuildTranslations(\n options: BuildTranslationOptions,\n): Promise<BuildTranslationResult> {\n const { config, publicOutputPath, metadataFilePath } = options;\n\n // Determine build mode (env var > options > config)\n const buildMode =\n (process.env.LINGO_BUILD_MODE as \"translate\" | \"cache-only\") ||\n config.buildMode;\n\n logger.info(`🌍 Build mode: ${buildMode}`);\n\n const metadata = await loadMetadata(metadataFilePath);\n\n if (!metadata || Object.keys(metadata.entries).length === 0) {\n logger.info(\"No translations to process (metadata is empty)\");\n return {\n success: true,\n stats: {},\n };\n }\n\n const totalEntries = Object.keys(metadata.entries).length;\n logger.info(`📊 Found ${totalEntries} translatable entries`);\n\n const cache = createCache(config);\n\n // Handle cache-only mode\n if (buildMode === \"cache-only\") {\n logger.info(\"🔍 Validating translation cache...\");\n await validateCache(config, metadata, cache);\n logger.info(\"✅ Cache validation passed\");\n\n if (publicOutputPath) {\n await copyStaticFiles(config, publicOutputPath, metadata, cache);\n }\n\n return {\n success: true,\n stats: buildCacheStats(config, metadata),\n };\n }\n\n // Handle translate mode\n logger.info(\"🔄 Generating translations...\");\n let translationServer: TranslationServer | undefined;\n\n try {\n translationServer = await startTranslationServer({\n translationService: new TranslationService(config, logger),\n onError: (err) => {\n logger.error(\"Translation server error:\", err);\n },\n config,\n });\n\n // When pluralization is enabled, we need to generate the source locale file too\n // because pluralization modifies the sourceText\n const needsSourceLocale = config.pluralization?.enabled !== false;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n logger.info(\n `Processing translations for ${allLocales.length} locale(s)${needsSourceLocale ? \" (including source locale for pluralization)\" : \"\"}...`,\n );\n\n const stats: BuildTranslationResult[\"stats\"] = {};\n const errors: Array<{ locale: LocaleCode; error: string }> = [];\n\n // Translate all locales in parallel\n const localePromises = allLocales.map(async (locale) => {\n logger.info(`Translating to ${locale}...`);\n\n const result = await translationServer!.translateAll(locale);\n\n stats[locale] = {\n total: totalEntries,\n translated: Object.keys(result.translations).length,\n failed: result.errors.length,\n };\n\n if (result.errors.length > 0) {\n logger.warn(\n `⚠️ ${result.errors.length} translation error(s) for ${locale}`,\n );\n errors.push({\n locale,\n error: `${result.errors.length} translation(s) failed`,\n });\n } else {\n logger.info(`✅ ${locale} completed successfully`);\n }\n });\n\n await Promise.all(localePromises);\n\n // Fail build if any translations failed in translate mode\n if (errors.length > 0) {\n const errorMsg = formatTranslationErrors(errors);\n logger.error(errorMsg);\n process.exit(1);\n }\n\n // Copy cache to public directory if requested\n if (publicOutputPath) {\n await copyStaticFiles(config, publicOutputPath, metadata, cache);\n }\n\n logger.info(\"✅ Translation generation completed successfully\");\n\n return {\n success: true,\n stats,\n };\n } catch (error) {\n logger.error(\n \"❌ Translation generation failed:\\n\",\n error instanceof Error ? error.message : error,\n );\n process.exit(1);\n } finally {\n if (translationServer) {\n await translationServer.stop();\n logger.info(\"✅ Translation server stopped\");\n }\n }\n}\n\n/**\n * Validate that all required translations exist in cache\n * @throws Error if cache is incomplete or missing\n */\nasync function validateCache(\n config: LingoConfig,\n metadata: MetadataSchema,\n cache: TranslationCache,\n): Promise<void> {\n const allHashes = Object.keys(metadata.entries);\n const missingLocales: string[] = [];\n const incompleteLocales: Array<{\n locale: LocaleCode;\n missing: number;\n total: number;\n }> = [];\n\n // Include source locale if pluralization is enabled\n const needsSourceLocale = config.pluralization?.enabled !== false;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n for (const locale of allLocales) {\n try {\n const entries = await cache.get(locale);\n\n if (Object.keys(entries).length === 0) {\n missingLocales.push(locale);\n logger.debug(`Cache file not found or empty for ${locale}`);\n continue;\n }\n\n const missingHashes = allHashes.filter((hash) => !entries[hash]);\n\n if (missingHashes.length > 0) {\n incompleteLocales.push({\n locale,\n missing: missingHashes.length,\n total: allHashes.length,\n });\n\n // Log first few missing hashes for debugging\n logger.debug(\n `Missing hashes in ${locale}: ${missingHashes.slice(0, 5).join(\", \")}${\n missingHashes.length > 5 ? \"...\" : \"\"\n }`,\n );\n }\n } catch (error) {\n missingLocales.push(locale);\n logger.debug(`Failed to read cache for ${locale}:`, error);\n }\n }\n\n if (missingLocales.length > 0 || incompleteLocales.length > 0) {\n const errorMsg = formatCacheValidationError(\n missingLocales,\n incompleteLocales,\n );\n logger.error(errorMsg);\n process.exit(1);\n }\n}\n\nfunction buildCacheStats(\n config: LingoConfig,\n metadata: MetadataSchema,\n): BuildTranslationResult[\"stats\"] {\n const totalEntries = Object.keys(metadata.entries).length;\n const stats: BuildTranslationResult[\"stats\"] = {};\n\n // Include source locale if pluralization is enabled\n const needsSourceLocale = config.pluralization?.enabled !== false;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n for (const locale of allLocales) {\n stats[locale] = {\n total: totalEntries,\n translated: totalEntries, // Assumed complete if validation passed\n failed: 0,\n };\n }\n\n return stats;\n}\n\nasync function copyStaticFiles(\n config: LingoConfig,\n publicOutputPath: string,\n metadata: MetadataSchema,\n cache: TranslationCache,\n): Promise<void> {\n logger.info(`📦 Generating static translation files in ${publicOutputPath}`);\n\n await fs.mkdir(publicOutputPath, { recursive: true });\n\n const usedHashes = new Set(Object.keys(metadata.entries));\n logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);\n\n // Include source locale if pluralization is enabled\n const needsSourceLocale = config.pluralization?.enabled !== false;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n for (const locale of allLocales) {\n const publicFilePath = path.join(publicOutputPath, `${locale}.json`);\n\n try {\n const entries = await cache.get(locale, Array.from(usedHashes));\n const outputData = dictionaryFrom(locale, entries);\n\n await fs.writeFile(\n publicFilePath,\n JSON.stringify(outputData, null, 2),\n \"utf-8\",\n );\n\n logger.info(\n `✓ Generated ${locale}.json (${Object.keys(entries).length} translations)`,\n );\n } catch (error) {\n logger.error(`❌ Failed to generate ${locale}.json:`, error);\n process.exit(1);\n }\n }\n}\n\nfunction formatCacheValidationError(\n missingLocales: string[],\n incompleteLocales: Array<{\n locale: LocaleCode;\n missing: number;\n total: number;\n }>,\n): string {\n let msg = \"❌ Cache validation failed in cache-only mode:\\n\\n\";\n\n if (missingLocales.length > 0) {\n msg += ` 📁 Missing cache files:\\n`;\n msg += missingLocales.map((locale) => ` - ${locale}.json`).join(\"\\n\");\n msg += \"\\n\\n\";\n }\n\n if (incompleteLocales.length > 0) {\n msg += ` 📊 Incomplete cache:\\n`;\n msg += incompleteLocales\n .map(\n (item) =>\n ` - ${item.locale}: ${item.missing}/${item.total} translations missing`,\n )\n .join(\"\\n\");\n msg += \"\\n\\n\";\n }\n\n msg += ` 💡 To fix:\\n`;\n msg += ` 1. Set LINGO_BUILD_MODE=translate to generate translations\\n`;\n msg += ` 2. Commit the generated .lingo/cache/*.json files\\n`;\n msg += ` 3. Ensure translation API keys are available if generating translations`;\n\n return msg;\n}\n\nfunction formatTranslationErrors(\n errors: Array<{ locale: LocaleCode; error: string }>,\n): string {\n let msg = \"❌ Translation generation failed:\\n\\n\";\n\n msg += errors.map((err) => ` - ${err.locale}: ${err.error}`).join(\"\\n\");\n\n msg += \"\\n\\n\";\n msg += ` 💡 Translation errors must be resolved in \"translate\" mode.\\n`;\n msg += ` Check translation server logs for details.`;\n\n return msg;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"build-translator.mjs","names":["translationServer: TranslationServer | undefined","stats: BuildTranslationResult[\"stats\"]","errors: Array<{ locale: LocaleCode; error: string }>","missingLocales: string[]","incompleteLocales: Array<{\n locale: LocaleCode;\n missing: number;\n total: number;\n }>","fs"],"sources":["../../src/plugin/build-translator.ts"],"sourcesContent":["/**\n * Build-time translation processor\n *\n * Handles translation generation and validation at build time\n * Supports two modes:\n * - \"translate\": Generate all translations, fail if translation fails\n * - \"cache-only\": Validate cache completeness, fail if incomplete\n */\n// TODO (AleksandrSl 08/12/2025): Add ICU validation for messages? The problem is that we don't know which will be rendered as a simple text\nimport fs from \"fs/promises\";\nimport path from \"path\";\nimport type { LingoConfig, MetadataSchema } from \"../types\";\nimport { logger } from \"../utils/logger\";\nimport { startTranslationServer, type TranslationServer, } from \"../translation-server\";\nimport { loadMetadata } from \"../metadata/manager\";\nimport { createCache, type TranslationCache, TranslationService, } from \"../translators\";\nimport { dictionaryFrom } from \"../translators/api\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\nexport interface BuildTranslationOptions {\n config: LingoConfig;\n publicOutputPath: string;\n metadataFilePath: string;\n}\n\nexport interface BuildTranslationResult {\n /**\n * Whether the build succeeded\n */\n success: boolean;\n\n /**\n * Error message if build failed\n */\n error?: string;\n\n /**\n * Translation statistics per locale\n */\n stats: Record<\n string,\n {\n total: number;\n translated: number;\n failed: number;\n }\n >;\n}\n\n/**\n * Process translations at build time\n *\n * @throws Error if validation or translation fails (causes build to fail)\n */\nexport async function processBuildTranslations(\n options: BuildTranslationOptions,\n): Promise<BuildTranslationResult> {\n const { config, publicOutputPath, metadataFilePath } = options;\n\n // Determine build mode (env var > options > config)\n const buildMode =\n (process.env.LINGO_BUILD_MODE as \"translate\" | \"cache-only\") ||\n config.buildMode;\n\n logger.info(`🌍 Build mode: ${buildMode}`);\n\n const metadata = await loadMetadata(metadataFilePath);\n\n if (!metadata || Object.keys(metadata.entries).length === 0) {\n logger.info(\"No translations to process (metadata is empty)\");\n return {\n success: true,\n stats: {},\n };\n }\n\n const totalEntries = Object.keys(metadata.entries).length;\n logger.info(`📊 Found ${totalEntries} translatable entries`);\n\n const cache = createCache(config);\n\n // Handle cache-only mode\n if (buildMode === \"cache-only\") {\n logger.info(\"🔍 Validating translation cache...\");\n await validateCache(config, metadata, cache);\n logger.info(\"✅ Cache validation passed\");\n\n if (publicOutputPath) {\n await copyStaticFiles(config, publicOutputPath, metadata, cache);\n }\n\n return {\n success: true,\n stats: buildCacheStats(config, metadata),\n };\n }\n\n // Handle translate mode\n logger.info(\"🔄 Generating translations...\");\n let translationServer: TranslationServer | undefined;\n\n try {\n translationServer = await startTranslationServer({\n translationService: new TranslationService(config, logger),\n onError: (err) => {\n logger.error(\"Translation server error:\", err);\n },\n config,\n });\n\n // When pluralization is enabled, we need to generate the source locale file too\n // because pluralization modifies the sourceText\n const needsSourceLocale = config.pluralization?.enabled === true;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n logger.info(\n `Processing translations for ${allLocales.length} locale(s)${needsSourceLocale ? \" (including source locale for pluralization)\" : \"\"}...`,\n );\n\n const stats: BuildTranslationResult[\"stats\"] = {};\n const errors: Array<{ locale: LocaleCode; error: string }> = [];\n\n // Translate all locales in parallel\n const localePromises = allLocales.map(async (locale) => {\n logger.info(`Translating to ${locale}...`);\n\n const result = await translationServer!.translateAll(locale);\n\n stats[locale] = {\n total: totalEntries,\n translated: Object.keys(result.translations).length,\n failed: result.errors.length,\n };\n\n if (result.errors.length > 0) {\n logger.warn(\n `⚠️ ${result.errors.length} translation error(s) for ${locale}`,\n );\n errors.push({\n locale,\n error: `${result.errors.length} translation(s) failed`,\n });\n } else {\n logger.info(`✅ ${locale} completed successfully`);\n }\n });\n\n await Promise.all(localePromises);\n\n // Fail build if any translations failed in translate mode\n if (errors.length > 0) {\n const errorMsg = formatTranslationErrors(errors);\n logger.error(errorMsg);\n process.exit(1);\n }\n\n // Copy cache to public directory if requested\n if (publicOutputPath) {\n await copyStaticFiles(config, publicOutputPath, metadata, cache);\n }\n\n logger.info(\"✅ Translation generation completed successfully\");\n\n return {\n success: true,\n stats,\n };\n } catch (error) {\n logger.error(\n \"❌ Translation generation failed:\\n\",\n error instanceof Error ? error.message : error,\n );\n process.exit(1);\n } finally {\n if (translationServer) {\n await translationServer.stop();\n logger.info(\"✅ Translation server stopped\");\n }\n }\n}\n\n/**\n * Validate that all required translations exist in cache\n * @throws Error if cache is incomplete or missing\n */\nasync function validateCache(\n config: LingoConfig,\n metadata: MetadataSchema,\n cache: TranslationCache,\n): Promise<void> {\n const allHashes = Object.keys(metadata.entries);\n const missingLocales: string[] = [];\n const incompleteLocales: Array<{\n locale: LocaleCode;\n missing: number;\n total: number;\n }> = [];\n\n // Include source locale if pluralization is enabled\n const needsSourceLocale = config.pluralization?.enabled === true;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n for (const locale of allLocales) {\n try {\n const entries = await cache.get(locale);\n\n if (Object.keys(entries).length === 0) {\n missingLocales.push(locale);\n logger.debug(`Cache file not found or empty for ${locale}`);\n continue;\n }\n\n const missingHashes = allHashes.filter((hash) => !entries[hash]);\n\n if (missingHashes.length > 0) {\n incompleteLocales.push({\n locale,\n missing: missingHashes.length,\n total: allHashes.length,\n });\n\n // Log first few missing hashes for debugging\n logger.debug(\n `Missing hashes in ${locale}: ${missingHashes.slice(0, 5).join(\", \")}${\n missingHashes.length > 5 ? \"...\" : \"\"\n }`,\n );\n }\n } catch (error) {\n missingLocales.push(locale);\n logger.debug(`Failed to read cache for ${locale}:`, error);\n }\n }\n\n if (missingLocales.length > 0 || incompleteLocales.length > 0) {\n const errorMsg = formatCacheValidationError(\n missingLocales,\n incompleteLocales,\n );\n logger.error(errorMsg);\n process.exit(1);\n }\n}\n\nfunction buildCacheStats(\n config: LingoConfig,\n metadata: MetadataSchema,\n): BuildTranslationResult[\"stats\"] {\n const totalEntries = Object.keys(metadata.entries).length;\n const stats: BuildTranslationResult[\"stats\"] = {};\n\n // Include source locale if pluralization is enabled\n const needsSourceLocale = config.pluralization?.enabled === true;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n for (const locale of allLocales) {\n stats[locale] = {\n total: totalEntries,\n translated: totalEntries, // Assumed complete if validation passed\n failed: 0,\n };\n }\n\n return stats;\n}\n\nasync function copyStaticFiles(\n config: LingoConfig,\n publicOutputPath: string,\n metadata: MetadataSchema,\n cache: TranslationCache,\n): Promise<void> {\n logger.info(`📦 Generating static translation files in ${publicOutputPath}`);\n\n await fs.mkdir(publicOutputPath, { recursive: true });\n\n const usedHashes = new Set(Object.keys(metadata.entries));\n logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);\n\n // Include source locale if pluralization is enabled\n const needsSourceLocale = config.pluralization?.enabled === true;\n const allLocales = needsSourceLocale\n ? [config.sourceLocale, ...config.targetLocales]\n : config.targetLocales;\n\n for (const locale of allLocales) {\n const publicFilePath = path.join(publicOutputPath, `${locale}.json`);\n\n try {\n const entries = await cache.get(locale, Array.from(usedHashes));\n const outputData = dictionaryFrom(locale, entries);\n\n await fs.writeFile(\n publicFilePath,\n JSON.stringify(outputData, null, 2),\n \"utf-8\",\n );\n\n logger.info(\n `✓ Generated ${locale}.json (${Object.keys(entries).length} translations)`,\n );\n } catch (error) {\n logger.error(`❌ Failed to generate ${locale}.json:`, error);\n process.exit(1);\n }\n }\n}\n\nfunction formatCacheValidationError(\n missingLocales: string[],\n incompleteLocales: Array<{\n locale: LocaleCode;\n missing: number;\n total: number;\n }>,\n): string {\n let msg = \"❌ Cache validation failed in cache-only mode:\\n\\n\";\n\n if (missingLocales.length > 0) {\n msg += ` 📁 Missing cache files:\\n`;\n msg += missingLocales.map((locale) => ` - ${locale}.json`).join(\"\\n\");\n msg += \"\\n\\n\";\n }\n\n if (incompleteLocales.length > 0) {\n msg += ` 📊 Incomplete cache:\\n`;\n msg += incompleteLocales\n .map(\n (item) =>\n ` - ${item.locale}: ${item.missing}/${item.total} translations missing`,\n )\n .join(\"\\n\");\n msg += \"\\n\\n\";\n }\n\n msg += ` 💡 To fix:\\n`;\n msg += ` 1. Set LINGO_BUILD_MODE=translate to generate translations\\n`;\n msg += ` 2. Commit the generated .lingo/cache/*.json files\\n`;\n msg += ` 3. Ensure translation API keys are available if generating translations`;\n\n return msg;\n}\n\nfunction formatTranslationErrors(\n errors: Array<{ locale: LocaleCode; error: string }>,\n): string {\n let msg = \"❌ Translation generation failed:\\n\\n\";\n\n msg += errors.map((err) => ` - ${err.locale}: ${err.error}`).join(\"\\n\");\n\n msg += \"\\n\\n\";\n msg += ` 💡 Translation errors must be resolved in \"translate\" mode.\\n`;\n msg += ` Check translation server logs for details.`;\n\n return msg;\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { LingoProviderProps } from "../shared/LingoProvider.cjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/react/server/ServerLingoProvider.d.ts
|
|
5
5
|
declare function LingoProvider({
|
|
6
6
|
initialLocale,
|
|
7
7
|
initialTranslations,
|
|
8
8
|
...rest
|
|
9
|
-
}: LingoProviderProps): Promise<
|
|
9
|
+
}: LingoProviderProps): Promise<react_jsx_runtime2.JSX.Element>;
|
|
10
10
|
//#endregion
|
|
11
11
|
export { LingoProvider };
|
|
12
12
|
//# sourceMappingURL=ServerLingoProvider.d.cts.map
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { LingoProviderProps } from "../shared/LingoProvider.mjs";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/react/server/ServerLingoProvider.d.ts
|
|
5
5
|
declare function LingoProvider({
|
|
6
6
|
initialLocale,
|
|
7
7
|
initialTranslations,
|
|
8
8
|
...rest
|
|
9
|
-
}: LingoProviderProps): Promise<
|
|
9
|
+
}: LingoProviderProps): Promise<react_jsx_runtime2.JSX.Element>;
|
|
10
10
|
//#endregion
|
|
11
11
|
export { LingoProvider };
|
|
12
12
|
//# sourceMappingURL=ServerLingoProvider.d.mts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LocaleCode } from "lingo.dev/spec";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
3
3
|
import { PropsWithChildren } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/react/shared/LingoProvider.d.ts
|
|
@@ -70,7 +70,7 @@ declare function LingoProvider__Dev({
|
|
|
70
70
|
router,
|
|
71
71
|
devWidget,
|
|
72
72
|
children
|
|
73
|
-
}: LingoProviderProps):
|
|
73
|
+
}: LingoProviderProps): react_jsx_runtime1.JSX.Element;
|
|
74
74
|
//#endregion
|
|
75
75
|
export { LingoProvider, LingoProviderProps };
|
|
76
76
|
//# sourceMappingURL=LingoProvider.d.cts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LocaleCode } from "lingo.dev/spec";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
3
3
|
import { CSSProperties } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/react/shared/LocaleSwitcher.d.ts
|
|
@@ -65,7 +65,7 @@ declare function LocaleSwitcher({
|
|
|
65
65
|
style,
|
|
66
66
|
className,
|
|
67
67
|
showLoadingState
|
|
68
|
-
}: LocaleSwitcherProps):
|
|
68
|
+
}: LocaleSwitcherProps): react_jsx_runtime3.JSX.Element;
|
|
69
69
|
//#endregion
|
|
70
70
|
export { LocaleSwitcher };
|
|
71
71
|
//# sourceMappingURL=LocaleSwitcher.d.cts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CSSProperties } from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime1 from "react/jsx-runtime";
|
|
3
3
|
import { LocaleCode } from "lingo.dev/spec";
|
|
4
4
|
|
|
5
5
|
//#region src/react/shared/LocaleSwitcher.d.ts
|
|
@@ -65,7 +65,7 @@ declare function LocaleSwitcher({
|
|
|
65
65
|
style,
|
|
66
66
|
className,
|
|
67
67
|
showLoadingState
|
|
68
|
-
}: LocaleSwitcherProps):
|
|
68
|
+
}: LocaleSwitcherProps): react_jsx_runtime1.JSX.Element;
|
|
69
69
|
//#endregion
|
|
70
70
|
export { LocaleSwitcher };
|
|
71
71
|
//# sourceMappingURL=LocaleSwitcher.d.mts.map
|
|
@@ -7,6 +7,7 @@ let _ai_sdk_google = require("@ai-sdk/google");
|
|
|
7
7
|
let _openrouter_ai_sdk_provider = require("@openrouter/ai-sdk-provider");
|
|
8
8
|
let ai_sdk_ollama = require("ai-sdk-ollama");
|
|
9
9
|
let _ai_sdk_mistral = require("@ai-sdk/mistral");
|
|
10
|
+
let _ai_sdk_openai = require("@ai-sdk/openai");
|
|
10
11
|
let dotenv = require("dotenv");
|
|
11
12
|
dotenv = require_rolldown_runtime.__toESM(dotenv);
|
|
12
13
|
|
|
@@ -105,7 +106,10 @@ function getLocaleModel(localeModels, sourceLocale, targetLocale) {
|
|
|
105
106
|
* @throws Error if format is invalid
|
|
106
107
|
*/
|
|
107
108
|
function parseModelString(modelString) {
|
|
108
|
-
const
|
|
109
|
+
const colonIndex = modelString.indexOf(":");
|
|
110
|
+
if (colonIndex === -1) return;
|
|
111
|
+
const provider = modelString.substring(0, colonIndex);
|
|
112
|
+
const name = modelString.substring(colonIndex + 1);
|
|
109
113
|
if (!provider || !name) return;
|
|
110
114
|
return {
|
|
111
115
|
provider,
|
|
@@ -156,10 +160,17 @@ function createAiModel(model, validatedKeys) {
|
|
|
156
160
|
const providerConfig = providerDetails[model.provider];
|
|
157
161
|
if (!providerConfig) throw new Error(`⚠️ Provider "${model.provider}" is not supported. Supported providers: ${Object.keys(providerDetails).join(", ")}`);
|
|
158
162
|
const apiKey = providerConfig.apiKeyEnvVar ? validatedKeys[model.provider] : void 0;
|
|
159
|
-
if (providerConfig.apiKeyEnvVar && !apiKey) throw new Error(`⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\n\nThis should not happen if
|
|
163
|
+
if (providerConfig.apiKeyEnvVar && !apiKey) throw new Error(`⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\n\nThis should not happen if validateAndGetApiKeys() was called. Please restart the service.`);
|
|
160
164
|
switch (model.provider) {
|
|
161
165
|
case "groq": return (0, _ai_sdk_groq.createGroq)({ apiKey })(model.name);
|
|
162
166
|
case "google": return (0, _ai_sdk_google.createGoogleGenerativeAI)({ apiKey })(model.name);
|
|
167
|
+
case "openai": {
|
|
168
|
+
const baseURL = getKeyFromEnv("OPENAI_BASE_URL");
|
|
169
|
+
return (0, _ai_sdk_openai.createOpenAI)({
|
|
170
|
+
apiKey,
|
|
171
|
+
...baseURL && { baseURL }
|
|
172
|
+
}).chat(model.name);
|
|
173
|
+
}
|
|
163
174
|
case "openrouter": return (0, _openrouter_ai_sdk_provider.createOpenRouter)({ apiKey })(model.name);
|
|
164
175
|
case "ollama": return (0, ai_sdk_ollama.ollama)(model.name);
|
|
165
176
|
case "mistral": return (0, _ai_sdk_mistral.createMistral)({ apiKey })(model.name);
|
|
@@ -5,6 +5,7 @@ import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
|
5
5
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
6
6
|
import { ollama } from "ai-sdk-ollama";
|
|
7
7
|
import { createMistral } from "@ai-sdk/mistral";
|
|
8
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
8
9
|
import * as dotenv from "dotenv";
|
|
9
10
|
|
|
10
11
|
//#region src/translators/lingo/model-factory.ts
|
|
@@ -102,7 +103,10 @@ function getLocaleModel(localeModels, sourceLocale, targetLocale) {
|
|
|
102
103
|
* @throws Error if format is invalid
|
|
103
104
|
*/
|
|
104
105
|
function parseModelString(modelString) {
|
|
105
|
-
const
|
|
106
|
+
const colonIndex = modelString.indexOf(":");
|
|
107
|
+
if (colonIndex === -1) return;
|
|
108
|
+
const provider = modelString.substring(0, colonIndex);
|
|
109
|
+
const name = modelString.substring(colonIndex + 1);
|
|
106
110
|
if (!provider || !name) return;
|
|
107
111
|
return {
|
|
108
112
|
provider,
|
|
@@ -153,10 +157,17 @@ function createAiModel(model, validatedKeys) {
|
|
|
153
157
|
const providerConfig = providerDetails[model.provider];
|
|
154
158
|
if (!providerConfig) throw new Error(`⚠️ Provider "${model.provider}" is not supported. Supported providers: ${Object.keys(providerDetails).join(", ")}`);
|
|
155
159
|
const apiKey = providerConfig.apiKeyEnvVar ? validatedKeys[model.provider] : void 0;
|
|
156
|
-
if (providerConfig.apiKeyEnvVar && !apiKey) throw new Error(`⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\n\nThis should not happen if
|
|
160
|
+
if (providerConfig.apiKeyEnvVar && !apiKey) throw new Error(`⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\n\nThis should not happen if validateAndGetApiKeys() was called. Please restart the service.`);
|
|
157
161
|
switch (model.provider) {
|
|
158
162
|
case "groq": return createGroq({ apiKey })(model.name);
|
|
159
163
|
case "google": return createGoogleGenerativeAI({ apiKey })(model.name);
|
|
164
|
+
case "openai": {
|
|
165
|
+
const baseURL = getKeyFromEnv("OPENAI_BASE_URL");
|
|
166
|
+
return createOpenAI({
|
|
167
|
+
apiKey,
|
|
168
|
+
...baseURL && { baseURL }
|
|
169
|
+
}).chat(model.name);
|
|
170
|
+
}
|
|
160
171
|
case "openrouter": return createOpenRouter({ apiKey })(model.name);
|
|
161
172
|
case "ollama": return ollama(model.name);
|
|
162
173
|
case "mistral": return createMistral({ apiKey })(model.name);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-factory.mjs","names":["path","providerDetails: Record<string, ProviderConfig>","keys: ValidatedApiKeys","missingProviders: string[]","providersToValidate: string[]"],"sources":["../../../src/translators/lingo/model-factory.ts"],"sourcesContent":["/**\n * Shared utilities for creating AI model instances\n */\n\nimport { createGroq } from \"@ai-sdk/groq\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenRouter } from \"@openrouter/ai-sdk-provider\";\nimport { ollama } from \"ai-sdk-ollama\";\nimport { createMistral } from \"@ai-sdk/mistral\";\nimport type { LanguageModel } from \"ai\";\nimport * as dotenv from \"dotenv\";\nimport * as path from \"path\";\nimport { formatNoApiKeysError } from \"./provider-details\";\n\nexport type LocaleModel = {\n provider: string;\n name: string;\n};\n\nexport function getKeyFromEnv(envVarName: string): string | undefined {\n if (process.env[envVarName]) {\n return process.env[envVarName];\n }\n\n const projectRoot = process.cwd();\n\n const result = dotenv.config({\n path: [\n path.resolve(projectRoot, \".env\"),\n path.resolve(projectRoot, \".env.local\"),\n path.resolve(projectRoot, \".env.development\"),\n ],\n });\n\n return result?.parsed?.[envVarName];\n}\n\n/**\n * Pre-validated API keys for all providers\n * Keys are fetched and validated once at initialization\n */\nexport type ValidatedApiKeys = Record<string, string>;\n\n/**\n * Provider configuration including env var names and requirements\n */\ntype ProviderConfig = {\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, ProviderConfig> = {\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 openai: {\n name: \"OpenAI\",\n apiKeyEnvVar: \"OPENAI_API_KEY\",\n apiKeyConfigKey: \"llm.openaiApiKey\",\n getKeyLink: \"https://platform.openai.com/account/api-keys\",\n docsLink: \"https://platform.openai.com/docs\",\n },\n anthropic: {\n name: \"Anthropic\",\n apiKeyEnvVar: \"ANTHROPIC_API_KEY\",\n apiKeyConfigKey: \"llm.anthropicApiKey\",\n getKeyLink: \"https://console.anthropic.com/get-api-key\",\n docsLink: \"https://console.anthropic.com/docs\",\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, // Ollama doesn't require an API key\n apiKeyConfigKey: undefined, // Ollama doesn't require an API key\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 * Get provider and model for a specific locale pair\n */\nexport function getLocaleModel(\n localeModels: Record<string, string>,\n sourceLocale: string,\n targetLocale: string,\n): LocaleModel | undefined {\n const localeKeys = [\n `${sourceLocale}:${targetLocale}`,\n `*:${targetLocale}`,\n `${sourceLocale}:*`,\n \"*:*\",\n ];\n\n const modelKey = localeKeys.find((key) => key in localeModels);\n if (!modelKey) {\n return undefined;\n }\n\n const value = localeModels[modelKey];\n if (!value) {\n return undefined;\n }\n\n return parseModelString(value);\n}\n\n/**\n * Parse provider and model from model string\n * Format: \"provider:model\" (e.g., \"groq:llama3-8b-8192\")\n *\n * @param modelString Model string to parse\n * @returns Object with provider and model\n * @throws Error if format is invalid\n */\nexport function parseModelString(modelString: string): LocaleModel | undefined {\n // Split on first colon only\n const [provider, name] = modelString.split(\":\", 2);\n\n if (!provider || !name) {\n return undefined;\n }\n\n return { provider, name };\n}\n\n/**\n * Validate and fetch all necessary API keys for the given configuration\n * This should be called once at initialization time\n *\n * @param config Model configuration (\"lingo.dev\" or locale-pair mapping)\n * @returns Validated API keys (provider ID -> API key)\n * @throws Error if required keys are missing\n */\nexport function validateAndGetApiKeys(\n config: \"lingo.dev\" | Record<string, string>,\n): ValidatedApiKeys {\n const keys: ValidatedApiKeys = {};\n const missingProviders: string[] = [];\n\n // Determine which providers are configured\n let providersToValidate: string[];\n\n if (config === \"lingo.dev\") {\n // Only need lingo.dev provider\n providersToValidate = [\"lingo.dev\"];\n } else {\n // Extract unique providers from model strings\n const providerSet = new Set<string>();\n Object.values(config).forEach((modelString) => {\n const model = parseModelString(modelString);\n if (model) {\n providerSet.add(model.provider);\n }\n });\n providersToValidate = Array.from(providerSet);\n }\n\n // Validate and fetch keys for each provider\n for (const provider of providersToValidate) {\n const providerConfig = providerDetails[provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Unknown provider \"${provider}\". Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Skip providers that don't require keys (like Ollama)\n if (!providerConfig.apiKeyEnvVar) {\n continue;\n }\n\n const key = getKeyFromEnv(providerConfig.apiKeyEnvVar);\n if (key) {\n keys[provider] = key;\n } else {\n missingProviders.push(provider);\n }\n }\n\n // If any keys are missing, throw with detailed error\n if (missingProviders.length > 0) {\n throw new Error(formatNoApiKeysError(missingProviders));\n }\n\n return keys;\n}\n\n/**\n * Create AI model instance from provider and model ID\n *\n * @param model Provider name (groq, google, openrouter, ollama, mistral) and model identifier\n * @param validatedKeys Pre-validated API keys from validateAndFetchApiKeys()\n * @returns LanguageModel instance\n * @throws Error if provider is not supported or API key is missing\n */\nexport function createAiModel(\n model: LocaleModel,\n validatedKeys: ValidatedApiKeys,\n): LanguageModel {\n const providerConfig = providerDetails[model.provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Provider \"${model.provider}\" is not supported. Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Get API key if required\n const apiKey = providerConfig.apiKeyEnvVar\n ? validatedKeys[model.provider]\n : undefined;\n\n // TODO (AleksandrSl 25/12/2025): Do we really need to make a second check? Maybe creation should be combined with validation.\n // Verify key is present for providers that require it\n if (providerConfig.apiKeyEnvVar && !apiKey) {\n throw new Error(\n `⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\\n\\n` +\n `This should not happen if validateAndFetchApiKeys() was called. Please restart the service.`,\n );\n }\n\n // Create the appropriate model instance\n switch (model.provider) {\n case \"groq\":\n return createGroq({ apiKey: apiKey! })(model.name);\n\n case \"google\":\n return createGoogleGenerativeAI({ apiKey: apiKey! })(model.name);\n\n case \"openrouter\":\n return createOpenRouter({ apiKey: apiKey! })(model.name);\n\n case \"ollama\":\n return ollama(model.name);\n\n case \"mistral\":\n return createMistral({ apiKey: apiKey! })(model.name);\n\n default:\n // This should be unreachable due to check above\n throw new Error(`⚠️ Provider \"${model.provider}\" is not implemented`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"model-factory.mjs","names":["path","providerDetails: Record<string, ProviderConfig>","keys: ValidatedApiKeys","missingProviders: string[]","providersToValidate: string[]"],"sources":["../../../src/translators/lingo/model-factory.ts"],"sourcesContent":["/**\n * Shared utilities for creating AI model instances\n */\n\nimport { createGroq } from \"@ai-sdk/groq\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenRouter } from \"@openrouter/ai-sdk-provider\";\nimport { ollama } from \"ai-sdk-ollama\";\nimport { createMistral } from \"@ai-sdk/mistral\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport type { LanguageModel } from \"ai\";\nimport * as dotenv from \"dotenv\";\nimport * as path from \"path\";\nimport { formatNoApiKeysError } from \"./provider-details\";\n\nexport type LocaleModel = {\n provider: string;\n name: string;\n};\n\nexport function getKeyFromEnv(envVarName: string): string | undefined {\n if (process.env[envVarName]) {\n return process.env[envVarName];\n }\n\n const projectRoot = process.cwd();\n\n const result = dotenv.config({\n path: [\n path.resolve(projectRoot, \".env\"),\n path.resolve(projectRoot, \".env.local\"),\n path.resolve(projectRoot, \".env.development\"),\n ],\n });\n\n return result?.parsed?.[envVarName];\n}\n\n/**\n * Pre-validated API keys for all providers\n * Keys are fetched and validated once at initialization\n */\nexport type ValidatedApiKeys = Record<string, string>;\n\n/**\n * Provider configuration including env var names and requirements\n */\ntype ProviderConfig = {\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, ProviderConfig> = {\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 openai: {\n name: \"OpenAI\",\n apiKeyEnvVar: \"OPENAI_API_KEY\",\n apiKeyConfigKey: \"llm.openaiApiKey\",\n getKeyLink: \"https://platform.openai.com/account/api-keys\",\n docsLink: \"https://platform.openai.com/docs\",\n },\n anthropic: {\n name: \"Anthropic\",\n apiKeyEnvVar: \"ANTHROPIC_API_KEY\",\n apiKeyConfigKey: \"llm.anthropicApiKey\",\n getKeyLink: \"https://console.anthropic.com/get-api-key\",\n docsLink: \"https://console.anthropic.com/docs\",\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, // Ollama doesn't require an API key\n apiKeyConfigKey: undefined, // Ollama doesn't require an API key\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 * Get provider and model for a specific locale pair\n */\nexport function getLocaleModel(\n localeModels: Record<string, string>,\n sourceLocale: string,\n targetLocale: string,\n): LocaleModel | undefined {\n const localeKeys = [\n `${sourceLocale}:${targetLocale}`,\n `*:${targetLocale}`,\n `${sourceLocale}:*`,\n \"*:*\",\n ];\n\n const modelKey = localeKeys.find((key) => key in localeModels);\n if (!modelKey) {\n return undefined;\n }\n\n const value = localeModels[modelKey];\n if (!value) {\n return undefined;\n }\n\n return parseModelString(value);\n}\n\n/**\n * Parse provider and model from model string\n * Format: \"provider:model\" (e.g., \"groq:llama3-8b-8192\")\n *\n * @param modelString Model string to parse\n * @returns Object with provider and model\n * @throws Error if format is invalid\n */\nexport function parseModelString(modelString: string): LocaleModel | undefined {\n // Split on first colon only to allow colons in model names\n const colonIndex = modelString.indexOf(\":\");\n if (colonIndex === -1) {\n return undefined;\n }\n\n const provider = modelString.substring(0, colonIndex);\n const name = modelString.substring(colonIndex + 1);\n\n if (!provider || !name) {\n return undefined;\n }\n\n return { provider, name };\n}\n\n/**\n * Validate and fetch all necessary API keys for the given configuration\n * This should be called once at initialization time\n *\n * @param config Model configuration (\"lingo.dev\" or locale-pair mapping)\n * @returns Validated API keys (provider ID -> API key)\n * @throws Error if required keys are missing\n */\nexport function validateAndGetApiKeys(\n config: \"lingo.dev\" | Record<string, string>,\n): ValidatedApiKeys {\n const keys: ValidatedApiKeys = {};\n const missingProviders: string[] = [];\n\n // Determine which providers are configured\n let providersToValidate: string[];\n\n if (config === \"lingo.dev\") {\n // Only need lingo.dev provider\n providersToValidate = [\"lingo.dev\"];\n } else {\n // Extract unique providers from model strings\n const providerSet = new Set<string>();\n Object.values(config).forEach((modelString) => {\n const model = parseModelString(modelString);\n if (model) {\n providerSet.add(model.provider);\n }\n });\n providersToValidate = Array.from(providerSet);\n }\n\n // Validate and fetch keys for each provider\n for (const provider of providersToValidate) {\n const providerConfig = providerDetails[provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Unknown provider \"${provider}\". Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Skip providers that don't require keys (like Ollama)\n if (!providerConfig.apiKeyEnvVar) {\n continue;\n }\n\n const key = getKeyFromEnv(providerConfig.apiKeyEnvVar);\n if (key) {\n keys[provider] = key;\n } else {\n missingProviders.push(provider);\n }\n }\n\n // If any keys are missing, throw with detailed error\n if (missingProviders.length > 0) {\n throw new Error(formatNoApiKeysError(missingProviders));\n }\n\n return keys;\n}\n\n/**\n * Create AI model instance from provider and model ID\n *\n * @param model Provider name (groq, google, openrouter, ollama, mistral) and model identifier\n * @param validatedKeys Pre-validated API keys from validateAndFetchApiKeys()\n * @returns LanguageModel instance\n * @throws Error if provider is not supported or API key is missing\n */\nexport function createAiModel(\n model: LocaleModel,\n validatedKeys: ValidatedApiKeys,\n): LanguageModel {\n const providerConfig = providerDetails[model.provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Provider \"${model.provider}\" is not supported. Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Get API key if required\n const apiKey = providerConfig.apiKeyEnvVar\n ? validatedKeys[model.provider]\n : undefined;\n\n // TODO (AleksandrSl 25/12/2025): Do we really need to make a second check? Maybe creation should be combined with validation.\n // Verify key is present for providers that require it\n if (providerConfig.apiKeyEnvVar && !apiKey) {\n throw new Error(\n `⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\\n\\n` +\n `This should not happen if validateAndGetApiKeys() was called. Please restart the service.`,\n );\n }\n\n // Create the appropriate model instance\n switch (model.provider) {\n case \"groq\":\n return createGroq({ apiKey: apiKey! })(model.name);\n\n case \"google\":\n return createGoogleGenerativeAI({ apiKey: apiKey! })(model.name);\n\n case \"openai\": {\n // Support custom base URL for OpenAI-compatible providers (e.g., Nebius)\n const baseURL = getKeyFromEnv(\"OPENAI_BASE_URL\");\n\n const provider = createOpenAI({\n apiKey: apiKey!,\n ...(baseURL && { baseURL }),\n });\n\n return provider.chat(model.name);\n }\n\n case \"openrouter\":\n return createOpenRouter({ apiKey: apiKey! })(model.name);\n\n case \"ollama\":\n return ollama(model.name);\n\n case \"mistral\":\n return createMistral({ apiKey: apiKey! })(model.name);\n\n default:\n // This should be unreachable due to check above\n throw new Error(`⚠️ Provider \"${model.provider}\" is not implemented`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ type PluralizationConfig = {
|
|
|
8
8
|
/**
|
|
9
9
|
* LLM provider for pluralization detection
|
|
10
10
|
* Format: "provider:model" (e.g., "groq:llama3-8b-8192")
|
|
11
|
-
*
|
|
11
|
+
* If omitted in user config, the compiler can infer it from translation models.
|
|
12
12
|
*/
|
|
13
13
|
model: string;
|
|
14
14
|
};
|
|
@@ -8,7 +8,7 @@ type PluralizationConfig = {
|
|
|
8
8
|
/**
|
|
9
9
|
* LLM provider for pluralization detection
|
|
10
10
|
* Format: "provider:model" (e.g., "groq:llama3-8b-8192")
|
|
11
|
-
*
|
|
11
|
+
* If omitted in user config, the compiler can infer it from translation models.
|
|
12
12
|
*/
|
|
13
13
|
model: string;
|
|
14
14
|
};
|
package/build/types.d.cts
CHANGED
|
@@ -31,11 +31,13 @@ type LocalePersistenceConfig = {
|
|
|
31
31
|
*/
|
|
32
32
|
type LingoConfigRequiredFields = "sourceLocale" | "targetLocales";
|
|
33
33
|
type LingoInternalFields = "environment" | "cacheType";
|
|
34
|
+
type PartialPluralizationConfig = Partial<Omit<PluralizationConfig, "sourceLocale">>;
|
|
34
35
|
/**
|
|
35
36
|
* Configuration for the Lingo compiler
|
|
36
37
|
*/
|
|
37
|
-
type PartialLingoConfig = Pick<LingoConfig, LingoConfigRequiredFields> & Partial<Omit<LingoConfig, LingoConfigRequiredFields | "dev" | LingoInternalFields> & {
|
|
38
|
+
type PartialLingoConfig = Pick<LingoConfig, LingoConfigRequiredFields> & Partial<Omit<LingoConfig, LingoConfigRequiredFields | "dev" | LingoInternalFields | "pluralization"> & {
|
|
38
39
|
dev: Partial<LingoConfig["dev"]>;
|
|
40
|
+
pluralization: PartialPluralizationConfig;
|
|
39
41
|
}>;
|
|
40
42
|
type LingoEnvironment = "development" | "production";
|
|
41
43
|
/**
|
package/build/types.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;AA4BA;AAKA;AAEA;
|
|
1
|
+
{"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;AA4BA;AAKA;AAEA;AAEY,UA3BK,YAAA,CA2BL;EACL;;;;EAMK,IAAA,EAAA,MAAA;EAA0B;;;;EAIhC,MAAA,EAAA,MAAA;;;;;;AAHJ,KAjBU,uBAAA,GAiBV;EAAO,IAAA,EAAA,QAAA;EAUG,MAAA,EA3BoD,YA2BpC;AAK5B,CAAA;;;;AAsEwB,KAjGZ,yBAAA,GAiGY,cAAA,GAAA,eAAA;AAYF,KA3GV,mBAAA,GA2GU,aAAA,GAAA,WAAA;AAAL,KAzGL,0BAAA,GAA6B,OAyGxB,CAxGf,IAwGe,CAxGV,mBAwGU,EAAA,cAAA,CAAA,CAAA;;;;KAlGL,kBAAA,GAAqB,KAAK,aAAa,6BACjD,QACE,KACE,aACA,oCAAoC;OAE/B,QAAQ;iBACE;;KAIT,gBAAA;;;;KAKA,WAAA;;;;;;;;;;;;;;;;eAkBG;;;;;;;;;;;;;;;;;;;;gBAsBC;;;;;;;;;;;iBAYC;;;;;;;;;;;;;;;;wBAkBO;;;;;;;;;;iBAYP,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkCD"}
|
package/build/types.d.mts
CHANGED
|
@@ -31,11 +31,13 @@ type LocalePersistenceConfig = {
|
|
|
31
31
|
*/
|
|
32
32
|
type LingoConfigRequiredFields = "sourceLocale" | "targetLocales";
|
|
33
33
|
type LingoInternalFields = "environment" | "cacheType";
|
|
34
|
+
type PartialPluralizationConfig = Partial<Omit<PluralizationConfig, "sourceLocale">>;
|
|
34
35
|
/**
|
|
35
36
|
* Configuration for the Lingo compiler
|
|
36
37
|
*/
|
|
37
|
-
type PartialLingoConfig = Pick<LingoConfig, LingoConfigRequiredFields> & Partial<Omit<LingoConfig, LingoConfigRequiredFields | "dev" | LingoInternalFields> & {
|
|
38
|
+
type PartialLingoConfig = Pick<LingoConfig, LingoConfigRequiredFields> & Partial<Omit<LingoConfig, LingoConfigRequiredFields | "dev" | LingoInternalFields | "pluralization"> & {
|
|
38
39
|
dev: Partial<LingoConfig["dev"]>;
|
|
40
|
+
pluralization: PartialPluralizationConfig;
|
|
39
41
|
}>;
|
|
40
42
|
type LingoEnvironment = "development" | "production";
|
|
41
43
|
/**
|
package/build/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;AA4BA;AAKA;AAEA;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;;;;AA4BA;AAKA;AAEA;AAEY,UA3BK,YAAA,CA2BL;EACL;;;;EAMK,IAAA,EAAA,MAAA;EAA0B;;;;EAIhC,MAAA,EAAA,MAAA;;;;;;AAHJ,KAjBU,uBAAA,GAiBV;EAAO,IAAA,EAAA,QAAA;EAUG,MAAA,EA3BoD,YA2BpC;AAK5B,CAAA;;;;AAsEwB,KAjGZ,yBAAA,GAiGY,cAAA,GAAA,eAAA;AAYF,KA3GV,mBAAA,GA2GU,aAAA,GAAA,WAAA;AAAL,KAzGL,0BAAA,GAA6B,OAyGxB,CAxGf,IAwGe,CAxGV,mBAwGU,EAAA,cAAA,CAAA,CAAA;;;;KAlGL,kBAAA,GAAqB,KAAK,aAAa,6BACjD,QACE,KACE,aACA,oCAAoC;OAE/B,QAAQ;iBACE;;KAIT,gBAAA;;;;KAKA,WAAA;;;;;;;;;;;;;;;;eAkBG;;;;;;;;;;;;;;;;;;;;gBAsBC;;;;;;;;;;;iBAYC;;;;;;;;;;;;;;;;wBAkBO;;;;;;;;;;iBAYP,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qBAkCD"}
|
|
@@ -17,11 +17,27 @@ const DEFAULT_CONFIG = {
|
|
|
17
17
|
},
|
|
18
18
|
models: "lingo.dev",
|
|
19
19
|
pluralization: {
|
|
20
|
-
enabled:
|
|
20
|
+
enabled: false,
|
|
21
21
|
model: "groq:llama-3.1-8b-instant"
|
|
22
22
|
},
|
|
23
23
|
buildMode: "translate"
|
|
24
24
|
};
|
|
25
|
+
function getModelStringForLocales(models, sourceLocale, targetLocale) {
|
|
26
|
+
const modelKey = (targetLocale ? [
|
|
27
|
+
`${sourceLocale}:${targetLocale}`,
|
|
28
|
+
`*:${targetLocale}`,
|
|
29
|
+
`${sourceLocale}:*`,
|
|
30
|
+
"*:*"
|
|
31
|
+
] : [`${sourceLocale}:*`, "*:*"]).find((key) => key in models);
|
|
32
|
+
if (modelKey) return models[modelKey];
|
|
33
|
+
const sortedKeys = Object.keys(models).sort();
|
|
34
|
+
if (sortedKeys.length === 0) return;
|
|
35
|
+
return models[sortedKeys[0]];
|
|
36
|
+
}
|
|
37
|
+
function inferPluralizationModel(models, sourceLocale, targetLocales) {
|
|
38
|
+
if (models === "lingo.dev") return;
|
|
39
|
+
return getModelStringForLocales(models, sourceLocale, targetLocales[0]);
|
|
40
|
+
}
|
|
25
41
|
/**
|
|
26
42
|
* Create a LoaderConfig with defaults applied
|
|
27
43
|
*
|
|
@@ -30,7 +46,7 @@ const DEFAULT_CONFIG = {
|
|
|
30
46
|
*
|
|
31
47
|
*/
|
|
32
48
|
function createLingoConfig(options) {
|
|
33
|
-
|
|
49
|
+
const config = {
|
|
34
50
|
...DEFAULT_CONFIG,
|
|
35
51
|
...options,
|
|
36
52
|
environment: options.environment ?? (process.env.NODE_ENV === "development" ? "development" : "production"),
|
|
@@ -52,6 +68,22 @@ function createLingoConfig(options) {
|
|
|
52
68
|
}
|
|
53
69
|
}
|
|
54
70
|
};
|
|
71
|
+
const explicitEnabled = options.pluralization?.enabled;
|
|
72
|
+
const explicitModel = options.pluralization?.model;
|
|
73
|
+
const hasExplicitModel = typeof explicitModel === "string" && explicitModel.trim().length > 0;
|
|
74
|
+
const pluralizationEnabled = typeof explicitEnabled === "boolean" ? explicitEnabled : hasExplicitModel;
|
|
75
|
+
let pluralizationModel = hasExplicitModel ? explicitModel.trim() : config.pluralization.model;
|
|
76
|
+
if (pluralizationEnabled && !hasExplicitModel) {
|
|
77
|
+
const inferredModel = inferPluralizationModel(config.models, config.sourceLocale, config.targetLocales);
|
|
78
|
+
if (!inferredModel) throw new Error("Pluralization is enabled but no \"pluralization.model\" is configured. Please set \"pluralization.model\" explicitly or use direct LLM models (not \"lingo.dev\") so the model can be inferred.");
|
|
79
|
+
pluralizationModel = inferredModel;
|
|
80
|
+
}
|
|
81
|
+
config.pluralization = {
|
|
82
|
+
...config.pluralization,
|
|
83
|
+
enabled: pluralizationEnabled,
|
|
84
|
+
model: pluralizationModel
|
|
85
|
+
};
|
|
86
|
+
return config;
|
|
55
87
|
}
|
|
56
88
|
|
|
57
89
|
//#endregion
|
|
@@ -16,11 +16,27 @@ const DEFAULT_CONFIG = {
|
|
|
16
16
|
},
|
|
17
17
|
models: "lingo.dev",
|
|
18
18
|
pluralization: {
|
|
19
|
-
enabled:
|
|
19
|
+
enabled: false,
|
|
20
20
|
model: "groq:llama-3.1-8b-instant"
|
|
21
21
|
},
|
|
22
22
|
buildMode: "translate"
|
|
23
23
|
};
|
|
24
|
+
function getModelStringForLocales(models, sourceLocale, targetLocale) {
|
|
25
|
+
const modelKey = (targetLocale ? [
|
|
26
|
+
`${sourceLocale}:${targetLocale}`,
|
|
27
|
+
`*:${targetLocale}`,
|
|
28
|
+
`${sourceLocale}:*`,
|
|
29
|
+
"*:*"
|
|
30
|
+
] : [`${sourceLocale}:*`, "*:*"]).find((key) => key in models);
|
|
31
|
+
if (modelKey) return models[modelKey];
|
|
32
|
+
const sortedKeys = Object.keys(models).sort();
|
|
33
|
+
if (sortedKeys.length === 0) return;
|
|
34
|
+
return models[sortedKeys[0]];
|
|
35
|
+
}
|
|
36
|
+
function inferPluralizationModel(models, sourceLocale, targetLocales) {
|
|
37
|
+
if (models === "lingo.dev") return;
|
|
38
|
+
return getModelStringForLocales(models, sourceLocale, targetLocales[0]);
|
|
39
|
+
}
|
|
24
40
|
/**
|
|
25
41
|
* Create a LoaderConfig with defaults applied
|
|
26
42
|
*
|
|
@@ -29,7 +45,7 @@ const DEFAULT_CONFIG = {
|
|
|
29
45
|
*
|
|
30
46
|
*/
|
|
31
47
|
function createLingoConfig(options) {
|
|
32
|
-
|
|
48
|
+
const config = {
|
|
33
49
|
...DEFAULT_CONFIG,
|
|
34
50
|
...options,
|
|
35
51
|
environment: options.environment ?? (process.env.NODE_ENV === "development" ? "development" : "production"),
|
|
@@ -51,6 +67,22 @@ function createLingoConfig(options) {
|
|
|
51
67
|
}
|
|
52
68
|
}
|
|
53
69
|
};
|
|
70
|
+
const explicitEnabled = options.pluralization?.enabled;
|
|
71
|
+
const explicitModel = options.pluralization?.model;
|
|
72
|
+
const hasExplicitModel = typeof explicitModel === "string" && explicitModel.trim().length > 0;
|
|
73
|
+
const pluralizationEnabled = typeof explicitEnabled === "boolean" ? explicitEnabled : hasExplicitModel;
|
|
74
|
+
let pluralizationModel = hasExplicitModel ? explicitModel.trim() : config.pluralization.model;
|
|
75
|
+
if (pluralizationEnabled && !hasExplicitModel) {
|
|
76
|
+
const inferredModel = inferPluralizationModel(config.models, config.sourceLocale, config.targetLocales);
|
|
77
|
+
if (!inferredModel) throw new Error("Pluralization is enabled but no \"pluralization.model\" is configured. Please set \"pluralization.model\" explicitly or use direct LLM models (not \"lingo.dev\") so the model can be inferred.");
|
|
78
|
+
pluralizationModel = inferredModel;
|
|
79
|
+
}
|
|
80
|
+
config.pluralization = {
|
|
81
|
+
...config.pluralization,
|
|
82
|
+
enabled: pluralizationEnabled,
|
|
83
|
+
model: pluralizationModel
|
|
84
|
+
};
|
|
85
|
+
return config;
|
|
54
86
|
}
|
|
55
87
|
|
|
56
88
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-factory.mjs","names":[],"sources":["../../src/utils/config-factory.ts"],"sourcesContent":["/**\n * Config factory for creating LoaderConfig instances\n */\nimport type {\n LingoConfig,\n LingoConfigRequiredFields,\n LingoInternalFields,\n PartialLingoConfig,\n} from \"../types\";\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n sourceRoot: \"src\",\n lingoDir: \"lingo\",\n useDirective: false,\n dev: {\n translationServerStartPort: 60000,\n },\n localePersistence: {\n type: \"cookie\" as const,\n config: {\n name: \"locale\",\n maxAge: 31536000,\n },\n },\n models: \"lingo.dev\",\n pluralization: {\n enabled:
|
|
1
|
+
{"version":3,"file":"config-factory.mjs","names":["config: LingoConfig"],"sources":["../../src/utils/config-factory.ts"],"sourcesContent":["/**\n * Config factory for creating LoaderConfig instances\n */\nimport type {\n LingoConfig,\n LingoConfigRequiredFields,\n LingoInternalFields,\n PartialLingoConfig,\n} from \"../types\";\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n sourceRoot: \"src\",\n lingoDir: \"lingo\",\n useDirective: false,\n dev: {\n translationServerStartPort: 60000,\n },\n localePersistence: {\n type: \"cookie\" as const,\n config: {\n name: \"locale\",\n maxAge: 31536000,\n },\n },\n models: \"lingo.dev\",\n pluralization: {\n enabled: false,\n model: \"groq:llama-3.1-8b-instant\",\n },\n buildMode: \"translate\",\n} satisfies Omit<\n LingoConfig,\n // Looks like we can use LingoInternalFields, but it's only a coincidence that the types match, we may want to provide default for internal fields\n LingoConfigRequiredFields | \"environment\" | \"cacheType\"\n>;\n\nfunction getModelStringForLocales(\n models: Record<string, string>,\n sourceLocale: string,\n targetLocale: string | undefined,\n): string | undefined {\n const localeKeys = targetLocale\n ? [\n `${sourceLocale}:${targetLocale}`,\n `*:${targetLocale}`,\n `${sourceLocale}:*`,\n \"*:*\",\n ]\n : [`${sourceLocale}:*`, \"*:*\"];\n\n const modelKey = localeKeys.find((key) => key in models);\n if (modelKey) {\n return models[modelKey];\n }\n\n const sortedKeys = Object.keys(models).sort();\n if (sortedKeys.length === 0) {\n return undefined;\n }\n\n return models[sortedKeys[0]];\n}\n\nfunction inferPluralizationModel(\n models: \"lingo.dev\" | Record<string, string>,\n sourceLocale: string,\n targetLocales: string[],\n): string | undefined {\n if (models === \"lingo.dev\") {\n return undefined;\n }\n\n return getModelStringForLocales(\n models,\n sourceLocale,\n targetLocales[0],\n );\n}\n\n/**\n * Create a LoaderConfig with defaults applied\n *\n * @param options - Partial config to override defaults\n * @returns Complete LoaderConfig with all defaults applied\n *\n */\nexport function createLingoConfig(\n options: PartialLingoConfig & Partial<Pick<LingoConfig, LingoInternalFields>>,\n): LingoConfig {\n const config: LingoConfig = {\n ...DEFAULT_CONFIG,\n ...options,\n environment:\n options.environment ??\n (process.env.NODE_ENV === \"development\" ? \"development\" : \"production\"),\n cacheType: options.cacheType ?? \"local\",\n dev: {\n ...DEFAULT_CONFIG.dev,\n ...options.dev,\n },\n pluralization: {\n ...DEFAULT_CONFIG.pluralization,\n ...options.pluralization,\n },\n localePersistence: {\n ...DEFAULT_CONFIG.localePersistence,\n ...options.localePersistence,\n config: {\n ...DEFAULT_CONFIG.localePersistence.config,\n ...options.localePersistence?.config,\n },\n },\n };\n\n const explicitEnabled = options.pluralization?.enabled;\n const explicitModel = options.pluralization?.model;\n const hasExplicitModel =\n typeof explicitModel === \"string\" && explicitModel.trim().length > 0;\n const hasExplicitEnabled = typeof explicitEnabled === \"boolean\";\n\n const pluralizationEnabled = hasExplicitEnabled\n ? explicitEnabled\n : hasExplicitModel;\n\n let pluralizationModel = hasExplicitModel\n ? explicitModel!.trim()\n : config.pluralization.model;\n\n if (pluralizationEnabled && !hasExplicitModel) {\n const inferredModel = inferPluralizationModel(\n config.models,\n config.sourceLocale,\n config.targetLocales,\n );\n\n if (!inferredModel) {\n throw new Error(\n 'Pluralization is enabled but no \"pluralization.model\" is configured. Please set \"pluralization.model\" explicitly or use direct LLM models (not \"lingo.dev\") so the model can be inferred.',\n );\n }\n\n pluralizationModel = inferredModel;\n }\n\n config.pluralization = {\n ...config.pluralization,\n enabled: pluralizationEnabled,\n model: pluralizationModel,\n };\n\n return config;\n}\n"],"mappings":";;;;AAaA,MAAa,iBAAiB;CAC5B,YAAY;CACZ,UAAU;CACV,cAAc;CACd,KAAK,EACH,4BAA4B,KAC7B;CACD,mBAAmB;EACjB,MAAM;EACN,QAAQ;GACN,MAAM;GACN,QAAQ;GACT;EACF;CACD,QAAQ;CACR,eAAe;EACb,SAAS;EACT,OAAO;EACR;CACD,WAAW;CACZ;AAMD,SAAS,yBACP,QACA,cACA,cACoB;CAUpB,MAAM,YATa,eACf;EACE,GAAG,aAAa,GAAG;EACnB,KAAK;EACL,GAAG,aAAa;EAChB;EACD,GACD,CAAC,GAAG,aAAa,KAAK,MAAM,EAEJ,MAAM,QAAQ,OAAO,OAAO;AACxD,KAAI,SACF,QAAO,OAAO;CAGhB,MAAM,aAAa,OAAO,KAAK,OAAO,CAAC,MAAM;AAC7C,KAAI,WAAW,WAAW,EACxB;AAGF,QAAO,OAAO,WAAW;;AAG3B,SAAS,wBACP,QACA,cACA,eACoB;AACpB,KAAI,WAAW,YACb;AAGF,QAAO,yBACL,QACA,cACA,cAAc,GACf;;;;;;;;;AAUH,SAAgB,kBACd,SACa;CACb,MAAMA,SAAsB;EAC1B,GAAG;EACH,GAAG;EACH,aACE,QAAQ,gBACP,QAAQ,IAAI,aAAa,gBAAgB,gBAAgB;EAC5D,WAAW,QAAQ,aAAa;EAChC,KAAK;GACH,GAAG,eAAe;GAClB,GAAG,QAAQ;GACZ;EACD,eAAe;GACb,GAAG,eAAe;GAClB,GAAG,QAAQ;GACZ;EACD,mBAAmB;GACjB,GAAG,eAAe;GAClB,GAAG,QAAQ;GACX,QAAQ;IACN,GAAG,eAAe,kBAAkB;IACpC,GAAG,QAAQ,mBAAmB;IAC/B;GACF;EACF;CAED,MAAM,kBAAkB,QAAQ,eAAe;CAC/C,MAAM,gBAAgB,QAAQ,eAAe;CAC7C,MAAM,mBACJ,OAAO,kBAAkB,YAAY,cAAc,MAAM,CAAC,SAAS;CAGrE,MAAM,uBAFqB,OAAO,oBAAoB,YAGlD,kBACA;CAEJ,IAAI,qBAAqB,mBACrB,cAAe,MAAM,GACrB,OAAO,cAAc;AAEzB,KAAI,wBAAwB,CAAC,kBAAkB;EAC7C,MAAM,gBAAgB,wBACpB,OAAO,QACP,OAAO,cACP,OAAO,cACR;AAED,MAAI,CAAC,cACH,OAAM,IAAI,MACR,kMACD;AAGH,uBAAqB;;AAGvB,QAAO,gBAAgB;EACrB,GAAG,OAAO;EACV,SAAS;EACT,OAAO;EACR;AAED,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingo.dev/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Lingo.dev Compiler",
|
|
5
5
|
"private": false,
|
|
6
6
|
"repository": {
|
|
@@ -141,6 +141,7 @@
|
|
|
141
141
|
"@ai-sdk/google": "3.0.1",
|
|
142
142
|
"@ai-sdk/groq": "3.0.1",
|
|
143
143
|
"@ai-sdk/mistral": "3.0.1",
|
|
144
|
+
"@ai-sdk/openai": "3.0.1",
|
|
144
145
|
"@babel/core": "7.26.0",
|
|
145
146
|
"@babel/generator": "7.28.5",
|
|
146
147
|
"@babel/parser": "7.28.5",
|
|
@@ -161,7 +162,7 @@
|
|
|
161
162
|
"posthog-node": "5.14.0",
|
|
162
163
|
"proper-lockfile": "4.1.2",
|
|
163
164
|
"ws": "8.18.3",
|
|
164
|
-
"lingo.dev": "^0.125.
|
|
165
|
+
"lingo.dev": "^0.125.4"
|
|
165
166
|
},
|
|
166
167
|
"peerDependencies": {
|
|
167
168
|
"next": "^15.0.0 || ^16.0.4",
|