@lingo.dev/compiler 0.2.0 → 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.mts +2 -2
- package/build/react/shared/LocaleSwitcher.d.mts +2 -2
- 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 +1 -1
|
@@ -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.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 { 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
|
|
@@ -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"}
|