@lingo.dev/compiler 0.1.2 → 0.1.4
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 +3 -3
- package/build/plugin/build-translator.mjs +3 -3
- package/build/plugin/build-translator.mjs.map +1 -1
- package/build/plugin/next.cjs +3 -3
- package/build/plugin/next.d.cts.map +1 -1
- package/build/plugin/next.d.mts.map +1 -1
- package/build/plugin/next.mjs +3 -3
- package/build/plugin/next.mjs.map +1 -1
- package/build/plugin/unplugin.cjs +81 -2
- package/build/plugin/unplugin.d.cts.map +1 -1
- package/build/plugin/unplugin.d.mts.map +1 -1
- package/build/plugin/unplugin.mjs +81 -2
- package/build/plugin/unplugin.mjs.map +1 -1
- package/build/react/server/ServerLingoProvider.d.cts +2 -2
- package/build/react/shared/LingoProvider.d.cts +2 -2
- package/build/react/shared/LocaleSwitcher.d.cts +2 -2
- package/build/translation-server/translation-server.cjs +7 -17
- package/build/translation-server/translation-server.mjs +7 -17
- package/build/translation-server/translation-server.mjs.map +1 -1
- package/build/translators/cache-factory.mjs.map +1 -1
- package/build/translators/lingo/model-factory.cjs +5 -10
- package/build/translators/lingo/model-factory.mjs +5 -10
- package/build/translators/lingo/model-factory.mjs.map +1 -1
- package/build/translators/lingo/provider-details.cjs +69 -0
- package/build/translators/lingo/provider-details.mjs +69 -0
- package/build/translators/lingo/provider-details.mjs.map +1 -0
- package/build/translators/lingo/{service.cjs → translator.cjs} +11 -13
- package/build/translators/lingo/{service.mjs → translator.mjs} +12 -14
- package/build/translators/lingo/translator.mjs.map +1 -0
- package/build/translators/memory-cache.cjs +47 -0
- package/build/translators/memory-cache.mjs +47 -0
- package/build/translators/memory-cache.mjs.map +1 -0
- package/build/translators/pluralization/service.cjs +19 -44
- package/build/translators/pluralization/service.mjs +19 -44
- package/build/translators/pluralization/service.mjs.map +1 -1
- package/build/translators/pseudotranslator/index.cjs +2 -10
- package/build/translators/pseudotranslator/index.mjs +2 -10
- package/build/translators/pseudotranslator/index.mjs.map +1 -1
- package/build/translators/translation-service.cjs +55 -57
- package/build/translators/translation-service.mjs +55 -57
- package/build/translators/translation-service.mjs.map +1 -1
- package/build/utils/observability.cjs +84 -0
- package/build/utils/observability.mjs +83 -0
- package/build/utils/observability.mjs.map +1 -0
- package/build/utils/rc.cjs +21 -0
- package/build/utils/rc.mjs +17 -0
- package/build/utils/rc.mjs.map +1 -0
- package/build/utils/repository-id.cjs +64 -0
- package/build/utils/repository-id.mjs +64 -0
- package/build/utils/repository-id.mjs.map +1 -0
- package/build/utils/tracking-events.cjs +28 -0
- package/build/utils/tracking-events.mjs +25 -0
- package/build/utils/tracking-events.mjs.map +1 -0
- package/package.json +12 -8
- package/build/translators/lingo/service.mjs.map +0 -1
- package/build/translators/translator-factory.cjs +0 -49
- package/build/translators/translator-factory.mjs +0 -50
- package/build/translators/translator-factory.mjs.map +0 -1
|
@@ -2,6 +2,7 @@ const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
|
2
2
|
const require_logger = require('../utils/logger.cjs');
|
|
3
3
|
const require_api = require('../translators/api.cjs');
|
|
4
4
|
const require_cache_factory = require('../translators/cache-factory.cjs');
|
|
5
|
+
const require_translation_service = require('../translators/translation-service.cjs');
|
|
5
6
|
const require_manager = require('../metadata/manager.cjs');
|
|
6
7
|
const require_translation_server = require('../translation-server/translation-server.cjs');
|
|
7
8
|
let fs_promises = require("fs/promises");
|
|
@@ -27,7 +28,6 @@ async function processBuildTranslations(options) {
|
|
|
27
28
|
const { config, publicOutputPath, metadataFilePath } = options;
|
|
28
29
|
const buildMode = process.env.LINGO_BUILD_MODE || config.buildMode;
|
|
29
30
|
require_logger.logger.info(`🌍 Build mode: ${buildMode}`);
|
|
30
|
-
if (metadataFilePath) require_logger.logger.info(`📋 Using build metadata file: ${metadataFilePath}`);
|
|
31
31
|
const metadata = await require_manager.loadMetadata(metadataFilePath);
|
|
32
32
|
if (!metadata || Object.keys(metadata.entries).length === 0) {
|
|
33
33
|
require_logger.logger.info("No translations to process (metadata is empty)");
|
|
@@ -53,7 +53,7 @@ async function processBuildTranslations(options) {
|
|
|
53
53
|
let translationServer;
|
|
54
54
|
try {
|
|
55
55
|
translationServer = await require_translation_server.startTranslationServer({
|
|
56
|
-
|
|
56
|
+
translationService: new require_translation_service.TranslationService(config, require_logger.logger),
|
|
57
57
|
onError: (err) => {
|
|
58
58
|
require_logger.logger.error("Translation server error:", err);
|
|
59
59
|
},
|
|
@@ -93,7 +93,7 @@ async function processBuildTranslations(options) {
|
|
|
93
93
|
stats
|
|
94
94
|
};
|
|
95
95
|
} catch (error) {
|
|
96
|
-
require_logger.logger.error("❌ Translation generation failed
|
|
96
|
+
require_logger.logger.error("❌ Translation generation failed:\n", error instanceof Error ? error.message : error);
|
|
97
97
|
process.exit(1);
|
|
98
98
|
} finally {
|
|
99
99
|
if (translationServer) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.mjs";
|
|
2
2
|
import { dictionaryFrom } from "../translators/api.mjs";
|
|
3
3
|
import { createCache } from "../translators/cache-factory.mjs";
|
|
4
|
+
import { TranslationService } from "../translators/translation-service.mjs";
|
|
4
5
|
import { loadMetadata } from "../metadata/manager.mjs";
|
|
5
6
|
import { startTranslationServer } from "../translation-server/translation-server.mjs";
|
|
6
7
|
import fsPromises from "fs/promises";
|
|
@@ -24,7 +25,6 @@ async function processBuildTranslations(options) {
|
|
|
24
25
|
const { config, publicOutputPath, metadataFilePath } = options;
|
|
25
26
|
const buildMode = process.env.LINGO_BUILD_MODE || config.buildMode;
|
|
26
27
|
logger.info(`🌍 Build mode: ${buildMode}`);
|
|
27
|
-
if (metadataFilePath) logger.info(`📋 Using build metadata file: ${metadataFilePath}`);
|
|
28
28
|
const metadata = await loadMetadata(metadataFilePath);
|
|
29
29
|
if (!metadata || Object.keys(metadata.entries).length === 0) {
|
|
30
30
|
logger.info("No translations to process (metadata is empty)");
|
|
@@ -50,7 +50,7 @@ async function processBuildTranslations(options) {
|
|
|
50
50
|
let translationServer;
|
|
51
51
|
try {
|
|
52
52
|
translationServer = await startTranslationServer({
|
|
53
|
-
|
|
53
|
+
translationService: new TranslationService(config, logger),
|
|
54
54
|
onError: (err) => {
|
|
55
55
|
logger.error("Translation server error:", err);
|
|
56
56
|
},
|
|
@@ -90,7 +90,7 @@ async function processBuildTranslations(options) {
|
|
|
90
90
|
stats
|
|
91
91
|
};
|
|
92
92
|
} catch (error) {
|
|
93
|
-
logger.error("❌ Translation generation failed
|
|
93
|
+
logger.error("❌ Translation generation failed:\n", error instanceof Error ? error.message : error);
|
|
94
94
|
process.exit(1);
|
|
95
95
|
} finally {
|
|
96
96
|
if (translationServer) {
|
|
@@ -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 {\n startTranslationServer,\n type TranslationServer,\n} from \"../translation-server\";\nimport { loadMetadata } from \"../metadata/manager\";\nimport { createCache, type TranslationCache } 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 if (metadataFilePath) {\n logger.info(`📋 Using build metadata file: ${metadataFilePath}`);\n }\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 startPort: config.dev.translationServerStartPort,\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(\"❌ Translation generation failed:\", error);\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 !== 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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"}
|
package/build/plugin/next.cjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const require_logger = require('../utils/logger.cjs');
|
|
2
2
|
const require_config_factory = require('../utils/config-factory.cjs');
|
|
3
|
+
const require_translation_service = require('../translators/translation-service.cjs');
|
|
3
4
|
const require_manager = require('../metadata/manager.cjs');
|
|
4
5
|
const require_translation_server = require('../translation-server/translation-server.cjs');
|
|
5
6
|
const require_build_translator = require('./build-translator.cjs');
|
|
@@ -134,7 +135,7 @@ async function withLingo(nextConfig = {}, lingoOptions) {
|
|
|
134
135
|
require_logger.logger.debug(`Initializing Lingo.dev compiler. Is dev mode: ${isDev}. Is main runner: ${isMainRunner()}`);
|
|
135
136
|
if (isDev && !process.env.LINGO_TRANSLATION_SERVER_URL) {
|
|
136
137
|
const translationServer = await require_translation_server.startOrGetTranslationServer({
|
|
137
|
-
|
|
138
|
+
translationService: new require_translation_service.TranslationService(lingoConfig, require_logger.logger),
|
|
138
139
|
onError: (err) => {
|
|
139
140
|
require_logger.logger.error("Translation server error:", err);
|
|
140
141
|
},
|
|
@@ -182,7 +183,6 @@ async function withLingo(nextConfig = {}, lingoOptions) {
|
|
|
182
183
|
projectDir
|
|
183
184
|
});
|
|
184
185
|
require_logger.logger.info("Running post-build translation generation...");
|
|
185
|
-
require_logger.logger.info(`Build mode: Using metadata file: ${metadataFilePath}`);
|
|
186
186
|
try {
|
|
187
187
|
await require_build_translator.processBuildTranslations({
|
|
188
188
|
config: lingoConfig,
|
|
@@ -190,7 +190,7 @@ async function withLingo(nextConfig = {}, lingoOptions) {
|
|
|
190
190
|
metadataFilePath
|
|
191
191
|
});
|
|
192
192
|
} catch (error) {
|
|
193
|
-
require_logger.logger.error("Translation generation failed:", error);
|
|
193
|
+
require_logger.logger.error("Translation generation failed:", error instanceof Error ? error.message : error);
|
|
194
194
|
throw error;
|
|
195
195
|
}
|
|
196
196
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.d.cts","names":[],"sources":["../../src/plugin/next.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"next.d.cts","names":[],"sources":["../../src/plugin/next.ts"],"sourcesContent":[],"mappings":";;;;KAgBY,sBAAA,GAAyB;iBAoLf,SAAA,aACR,sCACE,yBACb,QAAQ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.d.mts","names":[],"sources":["../../src/plugin/next.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"next.d.mts","names":[],"sources":["../../src/plugin/next.ts"],"sourcesContent":[],"mappings":";;;;KAgBY,sBAAA,GAAyB;iBAoLf,SAAA,aACR,sCACE,yBACb,QAAQ"}
|
package/build/plugin/next.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __require } from "../_virtual/rolldown_runtime.mjs";
|
|
2
2
|
import { logger } from "../utils/logger.mjs";
|
|
3
3
|
import { createLingoConfig } from "../utils/config-factory.mjs";
|
|
4
|
+
import { TranslationService } from "../translators/translation-service.mjs";
|
|
4
5
|
import { cleanupExistingMetadata, getMetadataPath } from "../metadata/manager.mjs";
|
|
5
6
|
import { startOrGetTranslationServer } from "../translation-server/translation-server.mjs";
|
|
6
7
|
import { processBuildTranslations } from "./build-translator.mjs";
|
|
@@ -135,7 +136,7 @@ async function withLingo(nextConfig = {}, lingoOptions) {
|
|
|
135
136
|
logger.debug(`Initializing Lingo.dev compiler. Is dev mode: ${isDev}. Is main runner: ${isMainRunner()}`);
|
|
136
137
|
if (isDev && !process.env.LINGO_TRANSLATION_SERVER_URL) {
|
|
137
138
|
const translationServer = await startOrGetTranslationServer({
|
|
138
|
-
|
|
139
|
+
translationService: new TranslationService(lingoConfig, logger),
|
|
139
140
|
onError: (err) => {
|
|
140
141
|
logger.error("Translation server error:", err);
|
|
141
142
|
},
|
|
@@ -183,7 +184,6 @@ async function withLingo(nextConfig = {}, lingoOptions) {
|
|
|
183
184
|
projectDir
|
|
184
185
|
});
|
|
185
186
|
logger.info("Running post-build translation generation...");
|
|
186
|
-
logger.info(`Build mode: Using metadata file: ${metadataFilePath}`);
|
|
187
187
|
try {
|
|
188
188
|
await processBuildTranslations({
|
|
189
189
|
config: lingoConfig,
|
|
@@ -191,7 +191,7 @@ async function withLingo(nextConfig = {}, lingoOptions) {
|
|
|
191
191
|
metadataFilePath
|
|
192
192
|
});
|
|
193
193
|
} catch (error) {
|
|
194
|
-
logger.error("Translation generation failed:", error);
|
|
194
|
+
logger.error("Translation generation failed:", error instanceof Error ? error.message : error);
|
|
195
195
|
throw error;
|
|
196
196
|
}
|
|
197
197
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next.mjs","names":["turbopackConfig: Partial<NextConfig>","webpack: NextJsWebpackConfig"],"sources":["../../src/plugin/next.ts"],"sourcesContent":["import type { NextConfig } from \"next\";\nimport type {\n NextJsWebpackConfig,\n TurbopackOptions,\n WebpackConfigContext,\n} from \"next/dist/server/config-shared\";\nimport { createLingoConfig } from \"../utils/config-factory\";\nimport { logger } from \"../utils/logger\";\nimport type { LingoConfig, PartialLingoConfig } from \"../types\";\nimport { processBuildTranslations } from \"./build-translator\";\nimport { startOrGetTranslationServer } from \"../translation-server/translation-server\";\nimport { cleanupExistingMetadata, getMetadataPath } from \"../metadata/manager\";\nimport { registerCleanupOnCurrentProcess } from \"./cleanup\";\nimport { useI18nRegex } from \"./transform/use-i18n\";\n\nexport type LingoNextPluginOptions = PartialLingoConfig;\n\ntype RuleKey = \"compiler\" | \"devConfig\" | \"localeServer\" | \"localeClient\";\n\nfunction loaders({\n lingoConfig,\n metadataFilePath,\n translationServerUrl,\n}: {\n lingoConfig: LingoConfig;\n metadataFilePath: string;\n translationServerUrl?: string;\n}): Record<RuleKey, { turbopack?: any; webpack?: any }> {\n const common = {\n sourceRoot: lingoConfig.sourceRoot,\n lingoDir: lingoConfig.lingoDir,\n sourceLocale: lingoConfig.sourceLocale,\n };\n\n // TODO (AleksandrSl 14/12/2025): Type options.\n const compilerLoader = {\n loader: \"@lingo.dev/compiler/next-compiler-loader\",\n options: {\n ...common,\n useDirective: lingoConfig.useDirective,\n metadataFilePath,\n },\n };\n\n const configLoader = {\n loader: \"@lingo.dev/compiler/next-config-loader\",\n options: {\n ...common,\n dev: {\n translationServerUrl,\n ...lingoConfig.dev,\n },\n },\n };\n const localeServerLoader = {\n loader: \"@lingo.dev/compiler/next-locale-server-loader\",\n options: {\n ...common,\n localePersistence: lingoConfig.localePersistence,\n },\n };\n\n const localeClientLoader = {\n loader: \"@lingo.dev/compiler/next-locale-client-loader\",\n options: {\n ...common,\n localePersistence: lingoConfig.localePersistence,\n },\n };\n\n return {\n compiler: {\n turbopack: {\n pattern: \"*.{tsx,jsx}\",\n config: {\n condition: {\n content: lingoConfig.useDirective ? useI18nRegex : undefined,\n },\n loaders: [compilerLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /\\.(tsx|jsx)$/i,\n exclude: /node_modules/,\n use: [compilerLoader],\n },\n },\n\n devConfig: translationServerUrl\n ? {\n turbopack: {\n pattern: \"**/virtual/config.mjs\",\n config: {\n loaders: [configLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /virtual\\/config\\.mjs$/i,\n use: [configLoader],\n },\n }\n : {},\n\n localeServer: {\n turbopack: {\n pattern: \"**/virtual/locale/server.mjs\",\n config: {\n loaders: [localeServerLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /virtual\\/locale[\\\\/]server\\.mjs$/i,\n use: [localeServerLoader],\n },\n },\n\n localeClient: {\n turbopack: {\n pattern: \"**/virtual/locale/client.mjs\",\n config: {\n loaders: [localeClientLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /virtual\\/locale[\\\\/]client\\.mjs$/i,\n use: [localeClientLoader],\n },\n },\n };\n}\n\n/**\n * Check if Next.js supports stable turbopack config (Next.js 16+)\n */\nfunction hasStableTurboConfig(): boolean {\n try {\n const nextPackage = require(\"next/package.json\");\n const majorVersion = parseInt(nextPackage.version.split(\".\")[0], 10);\n return majorVersion >= 16;\n } catch {\n return false;\n }\n}\n\nfunction getTurbopackConfig(userConfig: NextConfig): TurbopackOptions {\n return (\n (hasStableTurboConfig()\n ? userConfig?.turbopack?.rules\n : // @ts-expect-error - experimental.turbo for Next.js <16\n userConfig?.experimental?.turbo) || {}\n );\n}\n\n/**\n * Merge Turbopack rules without mutating original\n */\nfunction mergeTurbopackRules(\n existingRules: Record<string, any>,\n newRules: ({ pattern: string; config: any } | undefined)[],\n): Record<string, any> {\n const mergedRules = { ...existingRules };\n\n for (const newRule of newRules) {\n if (!newRule) continue;\n const { pattern, config } = newRule;\n\n if (mergedRules[pattern]) {\n if (Array.isArray(mergedRules[pattern])) {\n mergedRules[pattern] = [...mergedRules[pattern], config];\n } else {\n mergedRules[pattern] = [mergedRules[pattern], config];\n }\n } else {\n mergedRules[pattern] = config;\n }\n }\n\n return mergedRules;\n}\n\n// Next read config several times. Once in the main runner,\n// and ~ twice in the build workers which cannot get the config otherwise, because it's not serializable.\n// Workers start in separate processed by get most of the env from the main loader\nfunction isMainRunner() {\n return (\n !process.env.NEXT_PRIVATE_BUILD_WORKER &&\n !process.env.IS_NEXT_WORKER &&\n !process.env.NEXT_PRIVATE_WORKER\n );\n}\n\nexport async function withLingo(\n nextConfig: NextConfig = {},\n lingoOptions: LingoNextPluginOptions,\n): Promise<NextConfig> {\n const lingoConfig = createLingoConfig(lingoOptions);\n let metadataFilePath = getMetadataPath(lingoConfig);\n const isDev = lingoConfig.environment === \"development\";\n\n logger.debug(\n `Initializing Lingo.dev compiler. Is dev mode: ${isDev}. Is main runner: ${isMainRunner()}`,\n );\n\n // TODO (AleksandrSl 12/12/2025): Add API keys validation too, so we can log it nicely.\n\n // Try to start up the translation server once.\n // We have two barriers, a simple one here and a more complex one inside the startTranslationServer which doesn't start the server if it can find one running.\n // We do not use isMainRunner here, because we need to start the server as early as possible, so the loaders get the translation server url. The main runner in dev mode runs after a dev server process is started.\n if (isDev && !process.env.LINGO_TRANSLATION_SERVER_URL) {\n const translationServer = await startOrGetTranslationServer({\n startPort: lingoConfig.dev.translationServerStartPort,\n onError: (err) => {\n logger.error(\"Translation server error:\", err);\n },\n onReady: (port) => {\n logger.info(`Translation server started successfully on port: ${port}`);\n },\n config: lingoConfig,\n });\n process.env.LINGO_TRANSLATION_SERVER_URL = translationServer.url;\n if (translationServer.server) {\n // We start the server in the same process, so we should be fine without any sync cleanup. Server should be killed with the process.\n registerCleanupOnCurrentProcess({\n asyncCleanup: async () => {\n await translationServer.server.stop();\n },\n });\n }\n }\n\n const translationServerUrl = process.env.LINGO_TRANSLATION_SERVER_URL;\n\n if (isMainRunner()) {\n // We need to cleaup the file only once, to avoid having extra translation introduced into the build, or old translation to pile up.\n cleanupExistingMetadata(metadataFilePath);\n\n registerCleanupOnCurrentProcess({\n cleanup: () => {\n cleanupExistingMetadata(metadataFilePath);\n },\n });\n }\n\n const existingTurbopackConfig = getTurbopackConfig(nextConfig);\n let mergedRules = mergeTurbopackRules(\n existingTurbopackConfig.rules ?? {},\n Object.values(\n loaders({ lingoConfig, metadataFilePath, translationServerUrl }),\n ).map((rules) => rules.turbopack),\n );\n\n const existingResolveAlias = existingTurbopackConfig.resolveAlias;\n const mergedResolveAlias = {\n ...existingResolveAlias,\n // TODO (AleksandrSl 08/12/2025): Describe what have to be done to support custom resolvers\n };\n\n let turbopackConfig: Partial<NextConfig>;\n if (hasStableTurboConfig()) {\n turbopackConfig = {\n turbopack: {\n ...nextConfig.turbopack, // Preserve existing turbopack options\n rules: mergedRules,\n resolveAlias: mergedResolveAlias,\n },\n };\n } else {\n turbopackConfig = {\n experimental: {\n ...nextConfig.experimental, // Preserve ALL experimental options\n // @ts-expect-error - turbo for Next.js <16\n turbo: {\n // @ts-expect-error - turbo for Next.js <16\n ...nextConfig.experimental?.turbo, // Preserve existing turbo options\n rules: mergedRules,\n resolveAlias: mergedResolveAlias,\n },\n },\n };\n }\n\n const runAfterProductionCompile = async ({\n distDir,\n projectDir,\n }: {\n distDir: string;\n projectDir: string;\n }) => {\n if (typeof nextConfig.compiler?.runAfterProductionCompile === \"function\") {\n await nextConfig.compiler.runAfterProductionCompile({\n distDir,\n projectDir,\n });\n }\n\n logger.info(\"Running post-build translation generation...\");\n logger.info(`Build mode: Using metadata file: ${metadataFilePath}`);\n\n try {\n await processBuildTranslations({\n config: lingoConfig,\n publicOutputPath: distDir,\n metadataFilePath,\n });\n } catch (error) {\n logger.error(\"Translation generation failed:\", error);\n throw error;\n }\n };\n\n const webpack: NextJsWebpackConfig = (\n config: any,\n options: WebpackConfigContext,\n ) => {\n // Apply user's webpack config first if it exists\n let modifiedConfig = config;\n if (typeof nextConfig.webpack === \"function\") {\n modifiedConfig = nextConfig.webpack(config, options);\n }\n\n const lingoRules = Object.values(\n loaders({ lingoConfig, metadataFilePath, translationServerUrl }),\n )\n .map((rules) => rules.webpack)\n .filter(Boolean);\n\n // I tried using plugin from unplugin, but with all the loaders stuff it works poorly.\n // Failing with weird errors which appear on the later compilations, probably something with the cache and virtual modules\n modifiedConfig.module.rules.unshift(...lingoRules);\n\n return modifiedConfig;\n };\n\n return {\n ...nextConfig,\n ...turbopackConfig,\n compiler: {\n ...nextConfig.compiler,\n runAfterProductionCompile,\n },\n webpack,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"next.mjs","names":["turbopackConfig: Partial<NextConfig>","webpack: NextJsWebpackConfig"],"sources":["../../src/plugin/next.ts"],"sourcesContent":["import type { NextConfig } from \"next\";\nimport type {\n NextJsWebpackConfig,\n TurbopackOptions,\n WebpackConfigContext,\n} from \"next/dist/server/config-shared\";\nimport { createLingoConfig } from \"../utils/config-factory\";\nimport { logger } from \"../utils/logger\";\nimport type { LingoConfig, PartialLingoConfig } from \"../types\";\nimport { processBuildTranslations } from \"./build-translator\";\nimport { startOrGetTranslationServer } from \"../translation-server/translation-server\";\nimport { cleanupExistingMetadata, getMetadataPath } from \"../metadata/manager\";\nimport { registerCleanupOnCurrentProcess } from \"./cleanup\";\nimport { useI18nRegex } from \"./transform/use-i18n\";\nimport { TranslationService } from \"../translators\";\n\nexport type LingoNextPluginOptions = PartialLingoConfig;\n\ntype RuleKey = \"compiler\" | \"devConfig\" | \"localeServer\" | \"localeClient\";\n\nfunction loaders({\n lingoConfig,\n metadataFilePath,\n translationServerUrl,\n}: {\n lingoConfig: LingoConfig;\n metadataFilePath: string;\n translationServerUrl?: string;\n}): Record<RuleKey, { turbopack?: any; webpack?: any }> {\n const common = {\n sourceRoot: lingoConfig.sourceRoot,\n lingoDir: lingoConfig.lingoDir,\n sourceLocale: lingoConfig.sourceLocale,\n };\n\n // TODO (AleksandrSl 14/12/2025): Type options.\n const compilerLoader = {\n loader: \"@lingo.dev/compiler/next-compiler-loader\",\n options: {\n ...common,\n useDirective: lingoConfig.useDirective,\n metadataFilePath,\n },\n };\n\n const configLoader = {\n loader: \"@lingo.dev/compiler/next-config-loader\",\n options: {\n ...common,\n dev: {\n translationServerUrl,\n ...lingoConfig.dev,\n },\n },\n };\n const localeServerLoader = {\n loader: \"@lingo.dev/compiler/next-locale-server-loader\",\n options: {\n ...common,\n localePersistence: lingoConfig.localePersistence,\n },\n };\n\n const localeClientLoader = {\n loader: \"@lingo.dev/compiler/next-locale-client-loader\",\n options: {\n ...common,\n localePersistence: lingoConfig.localePersistence,\n },\n };\n\n return {\n compiler: {\n turbopack: {\n pattern: \"*.{tsx,jsx}\",\n config: {\n condition: {\n content: lingoConfig.useDirective ? useI18nRegex : undefined,\n },\n loaders: [compilerLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /\\.(tsx|jsx)$/i,\n exclude: /node_modules/,\n use: [compilerLoader],\n },\n },\n\n devConfig: translationServerUrl\n ? {\n turbopack: {\n pattern: \"**/virtual/config.mjs\",\n config: {\n loaders: [configLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /virtual\\/config\\.mjs$/i,\n use: [configLoader],\n },\n }\n : {},\n\n localeServer: {\n turbopack: {\n pattern: \"**/virtual/locale/server.mjs\",\n config: {\n loaders: [localeServerLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /virtual\\/locale[\\\\/]server\\.mjs$/i,\n use: [localeServerLoader],\n },\n },\n\n localeClient: {\n turbopack: {\n pattern: \"**/virtual/locale/client.mjs\",\n config: {\n loaders: [localeClientLoader],\n },\n },\n webpack: {\n enforce: \"pre\",\n test: /virtual\\/locale[\\\\/]client\\.mjs$/i,\n use: [localeClientLoader],\n },\n },\n };\n}\n\n/**\n * Check if Next.js supports stable turbopack config (Next.js 16+)\n */\nfunction hasStableTurboConfig(): boolean {\n try {\n const nextPackage = require(\"next/package.json\");\n const majorVersion = parseInt(nextPackage.version.split(\".\")[0], 10);\n return majorVersion >= 16;\n } catch {\n return false;\n }\n}\n\nfunction getTurbopackConfig(userConfig: NextConfig): TurbopackOptions {\n return (\n (hasStableTurboConfig()\n ? userConfig?.turbopack?.rules\n : // @ts-expect-error - experimental.turbo for Next.js <16\n userConfig?.experimental?.turbo) || {}\n );\n}\n\n/**\n * Merge Turbopack rules without mutating original\n */\nfunction mergeTurbopackRules(\n existingRules: Record<string, any>,\n newRules: ({ pattern: string; config: any } | undefined)[],\n): Record<string, any> {\n const mergedRules = { ...existingRules };\n\n for (const newRule of newRules) {\n if (!newRule) continue;\n const { pattern, config } = newRule;\n\n if (mergedRules[pattern]) {\n if (Array.isArray(mergedRules[pattern])) {\n mergedRules[pattern] = [...mergedRules[pattern], config];\n } else {\n mergedRules[pattern] = [mergedRules[pattern], config];\n }\n } else {\n mergedRules[pattern] = config;\n }\n }\n\n return mergedRules;\n}\n\n// Next read config several times. Once in the main runner,\n// and ~ twice in the build workers which cannot get the config otherwise, because it's not serializable.\n// Workers start in separate processed by get most of the env from the main loader\nfunction isMainRunner() {\n return (\n !process.env.NEXT_PRIVATE_BUILD_WORKER &&\n !process.env.IS_NEXT_WORKER &&\n !process.env.NEXT_PRIVATE_WORKER\n );\n}\n\nexport async function withLingo(\n nextConfig: NextConfig = {},\n lingoOptions: LingoNextPluginOptions,\n): Promise<NextConfig> {\n const lingoConfig = createLingoConfig(lingoOptions);\n let metadataFilePath = getMetadataPath(lingoConfig);\n const isDev = lingoConfig.environment === \"development\";\n\n logger.debug(\n `Initializing Lingo.dev compiler. Is dev mode: ${isDev}. Is main runner: ${isMainRunner()}`,\n );\n\n // Try to start up the translation server once.\n // We have two barriers, a simple one here and a more complex one inside the startTranslationServer which doesn't start the server if it can find one running.\n // We do not use isMainRunner here, because we need to start the server as early as possible, so the loaders get the translation server url. The main runner in dev mode runs after a dev server process is started.\n if (isDev && !process.env.LINGO_TRANSLATION_SERVER_URL) {\n const translationServer = await startOrGetTranslationServer({\n translationService: new TranslationService(lingoConfig, logger),\n onError: (err) => {\n logger.error(\"Translation server error:\", err);\n },\n onReady: (port) => {\n logger.info(`Translation server started successfully on port: ${port}`);\n },\n config: lingoConfig,\n });\n process.env.LINGO_TRANSLATION_SERVER_URL = translationServer.url;\n if (translationServer.server) {\n // We start the server in the same process, so we should be fine without any sync cleanup. Server should be killed with the process.\n registerCleanupOnCurrentProcess({\n asyncCleanup: async () => {\n await translationServer.server.stop();\n },\n });\n }\n }\n\n const translationServerUrl = process.env.LINGO_TRANSLATION_SERVER_URL;\n\n if (isMainRunner()) {\n // We need to cleaup the file only once, to avoid having extra translation introduced into the build, or old translation to pile up.\n cleanupExistingMetadata(metadataFilePath);\n\n registerCleanupOnCurrentProcess({\n cleanup: () => {\n cleanupExistingMetadata(metadataFilePath);\n },\n });\n }\n\n const existingTurbopackConfig = getTurbopackConfig(nextConfig);\n let mergedRules = mergeTurbopackRules(\n existingTurbopackConfig.rules ?? {},\n Object.values(\n loaders({ lingoConfig, metadataFilePath, translationServerUrl }),\n ).map((rules) => rules.turbopack),\n );\n\n const existingResolveAlias = existingTurbopackConfig.resolveAlias;\n const mergedResolveAlias = {\n ...existingResolveAlias,\n // TODO (AleksandrSl 08/12/2025): Describe what have to be done to support custom resolvers\n };\n\n let turbopackConfig: Partial<NextConfig>;\n if (hasStableTurboConfig()) {\n turbopackConfig = {\n turbopack: {\n ...nextConfig.turbopack, // Preserve existing turbopack options\n rules: mergedRules,\n resolveAlias: mergedResolveAlias,\n },\n };\n } else {\n turbopackConfig = {\n experimental: {\n ...nextConfig.experimental, // Preserve ALL experimental options\n // @ts-expect-error - turbo for Next.js <16\n turbo: {\n // @ts-expect-error - turbo for Next.js <16\n ...nextConfig.experimental?.turbo, // Preserve existing turbo options\n rules: mergedRules,\n resolveAlias: mergedResolveAlias,\n },\n },\n };\n }\n\n const runAfterProductionCompile = async ({\n distDir,\n projectDir,\n }: {\n distDir: string;\n projectDir: string;\n }) => {\n if (typeof nextConfig.compiler?.runAfterProductionCompile === \"function\") {\n await nextConfig.compiler.runAfterProductionCompile({\n distDir,\n projectDir,\n });\n }\n\n logger.info(\"Running post-build translation generation...\");\n\n try {\n await processBuildTranslations({\n config: lingoConfig,\n publicOutputPath: distDir,\n metadataFilePath,\n });\n } catch (error) {\n logger.error(\n \"Translation generation failed:\",\n error instanceof Error ? error.message : error,\n );\n throw error;\n }\n };\n\n const webpack: NextJsWebpackConfig = (\n config: any,\n options: WebpackConfigContext,\n ) => {\n // Apply user's webpack config first if it exists\n let modifiedConfig = config;\n if (typeof nextConfig.webpack === \"function\") {\n modifiedConfig = nextConfig.webpack(config, options);\n }\n\n const lingoRules = Object.values(\n loaders({ lingoConfig, metadataFilePath, translationServerUrl }),\n )\n .map((rules) => rules.webpack)\n .filter(Boolean);\n\n // I tried using plugin from unplugin, but with all the loaders stuff it works poorly.\n // Failing with weird errors which appear on the later compilations, probably something with the cache and virtual modules\n modifiedConfig.module.rules.unshift(...lingoRules);\n\n return modifiedConfig;\n };\n\n return {\n ...nextConfig,\n ...turbopackConfig,\n compiler: {\n ...nextConfig.compiler,\n runAfterProductionCompile,\n },\n webpack,\n };\n}\n"],"mappings":";;;;;;;;;;;AAoBA,SAAS,QAAQ,EACf,aACA,kBACA,wBAKsD;CACtD,MAAM,SAAS;EACb,YAAY,YAAY;EACxB,UAAU,YAAY;EACtB,cAAc,YAAY;EAC3B;CAGD,MAAM,iBAAiB;EACrB,QAAQ;EACR,SAAS;GACP,GAAG;GACH,cAAc,YAAY;GAC1B;GACD;EACF;CAED,MAAM,eAAe;EACnB,QAAQ;EACR,SAAS;GACP,GAAG;GACH,KAAK;IACH;IACA,GAAG,YAAY;IAChB;GACF;EACF;CACD,MAAM,qBAAqB;EACzB,QAAQ;EACR,SAAS;GACP,GAAG;GACH,mBAAmB,YAAY;GAChC;EACF;CAED,MAAM,qBAAqB;EACzB,QAAQ;EACR,SAAS;GACP,GAAG;GACH,mBAAmB,YAAY;GAChC;EACF;AAED,QAAO;EACL,UAAU;GACR,WAAW;IACT,SAAS;IACT,QAAQ;KACN,WAAW,EACT,SAAS,YAAY,eAAe,eAAe,QACpD;KACD,SAAS,CAAC,eAAe;KAC1B;IACF;GACD,SAAS;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,KAAK,CAAC,eAAe;IACtB;GACF;EAED,WAAW,uBACP;GACE,WAAW;IACT,SAAS;IACT,QAAQ,EACN,SAAS,CAAC,aAAa,EACxB;IACF;GACD,SAAS;IACP,SAAS;IACT,MAAM;IACN,KAAK,CAAC,aAAa;IACpB;GACF,GACD,EAAE;EAEN,cAAc;GACZ,WAAW;IACT,SAAS;IACT,QAAQ,EACN,SAAS,CAAC,mBAAmB,EAC9B;IACF;GACD,SAAS;IACP,SAAS;IACT,MAAM;IACN,KAAK,CAAC,mBAAmB;IAC1B;GACF;EAED,cAAc;GACZ,WAAW;IACT,SAAS;IACT,QAAQ,EACN,SAAS,CAAC,mBAAmB,EAC9B;IACF;GACD,SAAS;IACP,SAAS;IACT,MAAM;IACN,KAAK,CAAC,mBAAmB;IAC1B;GACF;EACF;;;;;AAMH,SAAS,uBAAgC;AACvC,KAAI;EACF,MAAM,wBAAsB,oBAAoB;AAEhD,SADqB,SAAS,YAAY,QAAQ,MAAM,IAAI,CAAC,IAAI,GAAG,IAC7C;SACjB;AACN,SAAO;;;AAIX,SAAS,mBAAmB,YAA0C;AACpE,SACG,sBAAsB,GACnB,YAAY,WAAW,QAEvB,YAAY,cAAc,UAAU,EAAE;;;;;AAO9C,SAAS,oBACP,eACA,UACqB;CACrB,MAAM,cAAc,EAAE,GAAG,eAAe;AAExC,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,CAAC,QAAS;EACd,MAAM,EAAE,SAAS,WAAW;AAE5B,MAAI,YAAY,SACd,KAAI,MAAM,QAAQ,YAAY,SAAS,CACrC,aAAY,WAAW,CAAC,GAAG,YAAY,UAAU,OAAO;MAExD,aAAY,WAAW,CAAC,YAAY,UAAU,OAAO;MAGvD,aAAY,WAAW;;AAI3B,QAAO;;AAMT,SAAS,eAAe;AACtB,QACE,CAAC,QAAQ,IAAI,6BACb,CAAC,QAAQ,IAAI,kBACb,CAAC,QAAQ,IAAI;;AAIjB,eAAsB,UACpB,aAAyB,EAAE,EAC3B,cACqB;CACrB,MAAM,cAAc,kBAAkB,aAAa;CACnD,IAAI,mBAAmB,gBAAgB,YAAY;CACnD,MAAM,QAAQ,YAAY,gBAAgB;AAE1C,QAAO,MACL,iDAAiD,MAAM,oBAAoB,cAAc,GAC1F;AAKD,KAAI,SAAS,CAAC,QAAQ,IAAI,8BAA8B;EACtD,MAAM,oBAAoB,MAAM,4BAA4B;GAC1D,oBAAoB,IAAI,mBAAmB,aAAa,OAAO;GAC/D,UAAU,QAAQ;AAChB,WAAO,MAAM,6BAA6B,IAAI;;GAEhD,UAAU,SAAS;AACjB,WAAO,KAAK,oDAAoD,OAAO;;GAEzE,QAAQ;GACT,CAAC;AACF,UAAQ,IAAI,+BAA+B,kBAAkB;AAC7D,MAAI,kBAAkB,OAEpB,iCAAgC,EAC9B,cAAc,YAAY;AACxB,SAAM,kBAAkB,OAAO,MAAM;KAExC,CAAC;;CAIN,MAAM,uBAAuB,QAAQ,IAAI;AAEzC,KAAI,cAAc,EAAE;AAElB,0BAAwB,iBAAiB;AAEzC,kCAAgC,EAC9B,eAAe;AACb,2BAAwB,iBAAiB;KAE5C,CAAC;;CAGJ,MAAM,0BAA0B,mBAAmB,WAAW;CAC9D,IAAI,cAAc,oBAChB,wBAAwB,SAAS,EAAE,EACnC,OAAO,OACL,QAAQ;EAAE;EAAa;EAAkB;EAAsB,CAAC,CACjE,CAAC,KAAK,UAAU,MAAM,UAAU,CAClC;CAGD,MAAM,qBAAqB,EACzB,GAF2B,wBAAwB,cAIpD;CAED,IAAIA;AACJ,KAAI,sBAAsB,CACxB,mBAAkB,EAChB,WAAW;EACT,GAAG,WAAW;EACd,OAAO;EACP,cAAc;EACf,EACF;KAED,mBAAkB,EAChB,cAAc;EACZ,GAAG,WAAW;EAEd,OAAO;GAEL,GAAG,WAAW,cAAc;GAC5B,OAAO;GACP,cAAc;GACf;EACF,EACF;CAGH,MAAM,4BAA4B,OAAO,EACvC,SACA,iBAII;AACJ,MAAI,OAAO,WAAW,UAAU,8BAA8B,WAC5D,OAAM,WAAW,SAAS,0BAA0B;GAClD;GACA;GACD,CAAC;AAGJ,SAAO,KAAK,+CAA+C;AAE3D,MAAI;AACF,SAAM,yBAAyB;IAC7B,QAAQ;IACR,kBAAkB;IAClB;IACD,CAAC;WACK,OAAO;AACd,UAAO,MACL,kCACA,iBAAiB,QAAQ,MAAM,UAAU,MAC1C;AACD,SAAM;;;CAIV,MAAMC,WACJ,QACA,YACG;EAEH,IAAI,iBAAiB;AACrB,MAAI,OAAO,WAAW,YAAY,WAChC,kBAAiB,WAAW,QAAQ,QAAQ,QAAQ;EAGtD,MAAM,aAAa,OAAO,OACxB,QAAQ;GAAE;GAAa;GAAkB;GAAsB,CAAC,CACjE,CACE,KAAK,UAAU,MAAM,QAAQ,CAC7B,OAAO,QAAQ;AAIlB,iBAAe,OAAO,MAAM,QAAQ,GAAG,WAAW;AAElD,SAAO;;AAGT,QAAO;EACL,GAAG;EACH,GAAG;EACH,UAAU;GACR,GAAG,WAAW;GACd;GACD;EACD;EACD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
2
|
const require_logger = require('../utils/logger.cjs');
|
|
3
3
|
const require_config_factory = require('../utils/config-factory.cjs');
|
|
4
|
+
const require_translation_service = require('../translators/translation-service.cjs');
|
|
4
5
|
const require_manager = require('../metadata/manager.cjs');
|
|
5
6
|
const require_translation_server = require('../translation-server/translation-server.cjs');
|
|
6
7
|
const require_build_translator = require('./build-translator.cjs');
|
|
@@ -8,6 +9,8 @@ const require_cleanup = require('./cleanup.cjs');
|
|
|
8
9
|
const require_use_i18n = require('./transform/use-i18n.cjs');
|
|
9
10
|
const require_index = require('./transform/index.cjs');
|
|
10
11
|
const require_code_generator = require('../virtual/code-generator.cjs');
|
|
12
|
+
const require_tracking_events = require('../utils/tracking-events.cjs');
|
|
13
|
+
const require_observability = require('../utils/observability.cjs');
|
|
11
14
|
let path = require("path");
|
|
12
15
|
path = require_rolldown_runtime.__toESM(path);
|
|
13
16
|
let fs = require("fs");
|
|
@@ -17,6 +20,12 @@ let unplugin = require("unplugin");
|
|
|
17
20
|
//#region src/plugin/unplugin.ts
|
|
18
21
|
let translationServer;
|
|
19
22
|
const PLUGIN_NAME = "lingo-compiler";
|
|
23
|
+
let alreadySentBuildStartEvent = false;
|
|
24
|
+
let buildStartTime = null;
|
|
25
|
+
let filesTransformedCount = 0;
|
|
26
|
+
let totalEntriesCount = 0;
|
|
27
|
+
let hasTransformErrors = false;
|
|
28
|
+
let currentFramework = null;
|
|
20
29
|
function tryLocalOrReturnVirtual(config, fileName, virtualName) {
|
|
21
30
|
const customPath = path.default.join(config.sourceRoot, config.lingoDir, fileName);
|
|
22
31
|
if (fs.default.existsSync(customPath)) return customPath;
|
|
@@ -48,13 +57,25 @@ const virtualModules = {
|
|
|
48
57
|
const virtualModulesResolvers = Object.fromEntries(Object.entries(virtualModules).map(([importPath, module$1]) => [importPath, (config) => module$1.customFileCheck ? tryLocalOrReturnVirtual(config, module$1.customFileCheck, module$1.virtualId) : module$1.virtualId]));
|
|
49
58
|
const virtualModulesLoaders = Object.fromEntries(Object.values(virtualModules).map((value) => [value.virtualId, value.loader]));
|
|
50
59
|
/**
|
|
60
|
+
* Send build start tracking event
|
|
61
|
+
*/
|
|
62
|
+
function sendBuildStartEvent(framework, config) {
|
|
63
|
+
if (alreadySentBuildStartEvent) return;
|
|
64
|
+
alreadySentBuildStartEvent = true;
|
|
65
|
+
require_observability.default(require_tracking_events.TRACKING_EVENTS.BUILD_START, {
|
|
66
|
+
framework,
|
|
67
|
+
configuration: require_tracking_events.sanitizeConfigForTracking(config),
|
|
68
|
+
environment: config.environment
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
51
72
|
* Universal plugin for Lingo.dev compiler
|
|
52
73
|
* Supports Vite, Webpack
|
|
53
74
|
*/
|
|
54
75
|
const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
55
76
|
const config = require_config_factory.createLingoConfig(options);
|
|
56
77
|
const isDev = config.environment === "development";
|
|
57
|
-
|
|
78
|
+
config.dev.translationServerStartPort;
|
|
58
79
|
let webpackMode;
|
|
59
80
|
const getMetadataPath$1 = () => {
|
|
60
81
|
return require_manager.getMetadataPath(webpackMode ? {
|
|
@@ -64,7 +85,7 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
64
85
|
};
|
|
65
86
|
async function startServer() {
|
|
66
87
|
const server = await require_translation_server.startTranslationServer({
|
|
67
|
-
|
|
88
|
+
translationService: new require_translation_service.TranslationService(config, require_logger.logger),
|
|
68
89
|
onError: (err) => {
|
|
69
90
|
require_logger.logger.error("Translation server error:", err);
|
|
70
91
|
},
|
|
@@ -88,6 +109,12 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
88
109
|
},
|
|
89
110
|
async buildStart() {
|
|
90
111
|
const metadataFilePath = getMetadataPath$1();
|
|
112
|
+
currentFramework = "vite";
|
|
113
|
+
sendBuildStartEvent("vite", config);
|
|
114
|
+
buildStartTime = Date.now();
|
|
115
|
+
filesTransformedCount = 0;
|
|
116
|
+
totalEntriesCount = 0;
|
|
117
|
+
hasTransformErrors = false;
|
|
91
118
|
require_manager.cleanupExistingMetadata(metadataFilePath);
|
|
92
119
|
require_cleanup.registerCleanupOnCurrentProcess({ cleanup: () => require_manager.cleanupExistingMetadata(metadataFilePath) });
|
|
93
120
|
if (isDev && !translationServer) translationServer = await startServer();
|
|
@@ -100,9 +127,27 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
100
127
|
publicOutputPath: "public/translations",
|
|
101
128
|
metadataFilePath
|
|
102
129
|
});
|
|
130
|
+
if (buildStartTime && !hasTransformErrors) require_observability.default(require_tracking_events.TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
131
|
+
framework: "vite",
|
|
132
|
+
stats: {
|
|
133
|
+
totalEntries: totalEntriesCount,
|
|
134
|
+
filesTransformed: filesTransformedCount,
|
|
135
|
+
buildDuration: Date.now() - buildStartTime
|
|
136
|
+
},
|
|
137
|
+
environment: config.environment
|
|
138
|
+
});
|
|
103
139
|
} catch (error) {
|
|
104
140
|
require_logger.logger.error("Build-time translation processing failed:", error);
|
|
105
141
|
}
|
|
142
|
+
else if (buildStartTime && !hasTransformErrors) require_observability.default(require_tracking_events.TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
143
|
+
framework: "vite",
|
|
144
|
+
stats: {
|
|
145
|
+
totalEntries: totalEntriesCount,
|
|
146
|
+
filesTransformed: filesTransformedCount,
|
|
147
|
+
buildDuration: Date.now() - buildStartTime
|
|
148
|
+
},
|
|
149
|
+
environment: config.environment
|
|
150
|
+
});
|
|
106
151
|
}
|
|
107
152
|
},
|
|
108
153
|
webpack(compiler) {
|
|
@@ -110,6 +155,12 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
110
155
|
const metadataFilePath = getMetadataPath$1();
|
|
111
156
|
config.environment = webpackMode;
|
|
112
157
|
compiler.hooks.initialize.tap(PLUGIN_NAME, () => {
|
|
158
|
+
currentFramework = "webpack";
|
|
159
|
+
sendBuildStartEvent("webpack", config);
|
|
160
|
+
buildStartTime = Date.now();
|
|
161
|
+
filesTransformedCount = 0;
|
|
162
|
+
totalEntriesCount = 0;
|
|
163
|
+
hasTransformErrors = false;
|
|
113
164
|
require_manager.cleanupExistingMetadata(metadataFilePath);
|
|
114
165
|
require_cleanup.registerCleanupOnCurrentProcess({ cleanup: () => require_manager.cleanupExistingMetadata(metadataFilePath) });
|
|
115
166
|
});
|
|
@@ -123,10 +174,28 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
123
174
|
publicOutputPath: "public/translations",
|
|
124
175
|
metadataFilePath
|
|
125
176
|
});
|
|
177
|
+
if (buildStartTime && !hasTransformErrors) require_observability.default(require_tracking_events.TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
178
|
+
framework: "webpack",
|
|
179
|
+
stats: {
|
|
180
|
+
totalEntries: totalEntriesCount,
|
|
181
|
+
filesTransformed: filesTransformedCount,
|
|
182
|
+
buildDuration: Date.now() - buildStartTime
|
|
183
|
+
},
|
|
184
|
+
environment: config.environment
|
|
185
|
+
});
|
|
126
186
|
} catch (error) {
|
|
127
187
|
require_logger.logger.error("Build-time translation processing failed:", error);
|
|
128
188
|
throw error;
|
|
129
189
|
}
|
|
190
|
+
else if (buildStartTime && !hasTransformErrors) require_observability.default(require_tracking_events.TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
191
|
+
framework: "webpack",
|
|
192
|
+
stats: {
|
|
193
|
+
totalEntries: totalEntriesCount,
|
|
194
|
+
filesTransformed: filesTransformedCount,
|
|
195
|
+
buildDuration: Date.now() - buildStartTime
|
|
196
|
+
},
|
|
197
|
+
environment: config.environment
|
|
198
|
+
});
|
|
130
199
|
});
|
|
131
200
|
compiler.hooks.shutdown.tapPromise(PLUGIN_NAME, async () => {
|
|
132
201
|
require_manager.cleanupExistingMetadata(metadataFilePath);
|
|
@@ -168,6 +237,8 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
168
237
|
const metadataManager = new require_manager.MetadataManager(getMetadataPath$1());
|
|
169
238
|
if (result.newEntries && result.newEntries.length > 0) {
|
|
170
239
|
await metadataManager.saveMetadataWithEntries(result.newEntries);
|
|
240
|
+
totalEntriesCount += result.newEntries.length;
|
|
241
|
+
filesTransformedCount++;
|
|
171
242
|
require_logger.logger.debug(`Found ${result.newEntries.length} translatable text(s) in ${id}`);
|
|
172
243
|
}
|
|
173
244
|
require_logger.logger.debug(`Returning transformed code for ${id}`);
|
|
@@ -176,6 +247,14 @@ const lingoUnplugin = (0, unplugin.createUnplugin)((options) => {
|
|
|
176
247
|
map: result.map
|
|
177
248
|
};
|
|
178
249
|
} catch (error) {
|
|
250
|
+
hasTransformErrors = true;
|
|
251
|
+
if (currentFramework) require_observability.default(require_tracking_events.TRACKING_EVENTS.BUILD_ERROR, {
|
|
252
|
+
framework: currentFramework,
|
|
253
|
+
errorType: "transform",
|
|
254
|
+
errorMessage: error instanceof Error ? error.message : "Unknown transform error",
|
|
255
|
+
filePath: id,
|
|
256
|
+
environment: config.environment
|
|
257
|
+
});
|
|
179
258
|
require_logger.logger.error(`Transform error in ${id}:`, error);
|
|
180
259
|
return null;
|
|
181
260
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unplugin.d.cts","names":[],"sources":["../../src/plugin/unplugin.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"unplugin.d.cts","names":[],"sources":["../../src/plugin/unplugin.ts"],"sourcesContent":[],"mappings":";;;;KAmCY,kBAAA,GAAqB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unplugin.d.mts","names":[],"sources":["../../src/plugin/unplugin.ts"],"sourcesContent":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"unplugin.d.mts","names":[],"sources":["../../src/plugin/unplugin.ts"],"sourcesContent":[],"mappings":";;;;KAmCY,kBAAA,GAAqB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.mjs";
|
|
2
2
|
import { createLingoConfig } from "../utils/config-factory.mjs";
|
|
3
|
+
import { TranslationService } from "../translators/translation-service.mjs";
|
|
3
4
|
import { MetadataManager, cleanupExistingMetadata, getMetadataPath } from "../metadata/manager.mjs";
|
|
4
5
|
import { startTranslationServer } from "../translation-server/translation-server.mjs";
|
|
5
6
|
import { processBuildTranslations } from "./build-translator.mjs";
|
|
@@ -7,6 +8,8 @@ import { registerCleanupOnCurrentProcess } from "./cleanup.mjs";
|
|
|
7
8
|
import { useI18nRegex } from "./transform/use-i18n.mjs";
|
|
8
9
|
import { transformComponent } from "./transform/index.mjs";
|
|
9
10
|
import { generateClientLocaleModule, generateConfigModule, generateServerLocaleModule } from "../virtual/code-generator.mjs";
|
|
11
|
+
import { TRACKING_EVENTS, sanitizeConfigForTracking } from "../utils/tracking-events.mjs";
|
|
12
|
+
import trackEvent from "../utils/observability.mjs";
|
|
10
13
|
import path from "path";
|
|
11
14
|
import fs from "fs";
|
|
12
15
|
import { createUnplugin } from "unplugin";
|
|
@@ -14,6 +17,12 @@ import { createUnplugin } from "unplugin";
|
|
|
14
17
|
//#region src/plugin/unplugin.ts
|
|
15
18
|
let translationServer;
|
|
16
19
|
const PLUGIN_NAME = "lingo-compiler";
|
|
20
|
+
let alreadySentBuildStartEvent = false;
|
|
21
|
+
let buildStartTime = null;
|
|
22
|
+
let filesTransformedCount = 0;
|
|
23
|
+
let totalEntriesCount = 0;
|
|
24
|
+
let hasTransformErrors = false;
|
|
25
|
+
let currentFramework = null;
|
|
17
26
|
function tryLocalOrReturnVirtual(config, fileName, virtualName) {
|
|
18
27
|
const customPath = path.join(config.sourceRoot, config.lingoDir, fileName);
|
|
19
28
|
if (fs.existsSync(customPath)) return customPath;
|
|
@@ -45,13 +54,25 @@ const virtualModules = {
|
|
|
45
54
|
const virtualModulesResolvers = Object.fromEntries(Object.entries(virtualModules).map(([importPath, module]) => [importPath, (config) => module.customFileCheck ? tryLocalOrReturnVirtual(config, module.customFileCheck, module.virtualId) : module.virtualId]));
|
|
46
55
|
const virtualModulesLoaders = Object.fromEntries(Object.values(virtualModules).map((value) => [value.virtualId, value.loader]));
|
|
47
56
|
/**
|
|
57
|
+
* Send build start tracking event
|
|
58
|
+
*/
|
|
59
|
+
function sendBuildStartEvent(framework, config) {
|
|
60
|
+
if (alreadySentBuildStartEvent) return;
|
|
61
|
+
alreadySentBuildStartEvent = true;
|
|
62
|
+
trackEvent(TRACKING_EVENTS.BUILD_START, {
|
|
63
|
+
framework,
|
|
64
|
+
configuration: sanitizeConfigForTracking(config),
|
|
65
|
+
environment: config.environment
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
48
69
|
* Universal plugin for Lingo.dev compiler
|
|
49
70
|
* Supports Vite, Webpack
|
|
50
71
|
*/
|
|
51
72
|
const lingoUnplugin = createUnplugin((options) => {
|
|
52
73
|
const config = createLingoConfig(options);
|
|
53
74
|
const isDev = config.environment === "development";
|
|
54
|
-
|
|
75
|
+
config.dev.translationServerStartPort;
|
|
55
76
|
let webpackMode;
|
|
56
77
|
const getMetadataPath$1 = () => {
|
|
57
78
|
return getMetadataPath(webpackMode ? {
|
|
@@ -61,7 +82,7 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
61
82
|
};
|
|
62
83
|
async function startServer() {
|
|
63
84
|
const server = await startTranslationServer({
|
|
64
|
-
|
|
85
|
+
translationService: new TranslationService(config, logger),
|
|
65
86
|
onError: (err) => {
|
|
66
87
|
logger.error("Translation server error:", err);
|
|
67
88
|
},
|
|
@@ -85,6 +106,12 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
85
106
|
},
|
|
86
107
|
async buildStart() {
|
|
87
108
|
const metadataFilePath = getMetadataPath$1();
|
|
109
|
+
currentFramework = "vite";
|
|
110
|
+
sendBuildStartEvent("vite", config);
|
|
111
|
+
buildStartTime = Date.now();
|
|
112
|
+
filesTransformedCount = 0;
|
|
113
|
+
totalEntriesCount = 0;
|
|
114
|
+
hasTransformErrors = false;
|
|
88
115
|
cleanupExistingMetadata(metadataFilePath);
|
|
89
116
|
registerCleanupOnCurrentProcess({ cleanup: () => cleanupExistingMetadata(metadataFilePath) });
|
|
90
117
|
if (isDev && !translationServer) translationServer = await startServer();
|
|
@@ -97,9 +124,27 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
97
124
|
publicOutputPath: "public/translations",
|
|
98
125
|
metadataFilePath
|
|
99
126
|
});
|
|
127
|
+
if (buildStartTime && !hasTransformErrors) trackEvent(TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
128
|
+
framework: "vite",
|
|
129
|
+
stats: {
|
|
130
|
+
totalEntries: totalEntriesCount,
|
|
131
|
+
filesTransformed: filesTransformedCount,
|
|
132
|
+
buildDuration: Date.now() - buildStartTime
|
|
133
|
+
},
|
|
134
|
+
environment: config.environment
|
|
135
|
+
});
|
|
100
136
|
} catch (error) {
|
|
101
137
|
logger.error("Build-time translation processing failed:", error);
|
|
102
138
|
}
|
|
139
|
+
else if (buildStartTime && !hasTransformErrors) trackEvent(TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
140
|
+
framework: "vite",
|
|
141
|
+
stats: {
|
|
142
|
+
totalEntries: totalEntriesCount,
|
|
143
|
+
filesTransformed: filesTransformedCount,
|
|
144
|
+
buildDuration: Date.now() - buildStartTime
|
|
145
|
+
},
|
|
146
|
+
environment: config.environment
|
|
147
|
+
});
|
|
103
148
|
}
|
|
104
149
|
},
|
|
105
150
|
webpack(compiler) {
|
|
@@ -107,6 +152,12 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
107
152
|
const metadataFilePath = getMetadataPath$1();
|
|
108
153
|
config.environment = webpackMode;
|
|
109
154
|
compiler.hooks.initialize.tap(PLUGIN_NAME, () => {
|
|
155
|
+
currentFramework = "webpack";
|
|
156
|
+
sendBuildStartEvent("webpack", config);
|
|
157
|
+
buildStartTime = Date.now();
|
|
158
|
+
filesTransformedCount = 0;
|
|
159
|
+
totalEntriesCount = 0;
|
|
160
|
+
hasTransformErrors = false;
|
|
110
161
|
cleanupExistingMetadata(metadataFilePath);
|
|
111
162
|
registerCleanupOnCurrentProcess({ cleanup: () => cleanupExistingMetadata(metadataFilePath) });
|
|
112
163
|
});
|
|
@@ -120,10 +171,28 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
120
171
|
publicOutputPath: "public/translations",
|
|
121
172
|
metadataFilePath
|
|
122
173
|
});
|
|
174
|
+
if (buildStartTime && !hasTransformErrors) trackEvent(TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
175
|
+
framework: "webpack",
|
|
176
|
+
stats: {
|
|
177
|
+
totalEntries: totalEntriesCount,
|
|
178
|
+
filesTransformed: filesTransformedCount,
|
|
179
|
+
buildDuration: Date.now() - buildStartTime
|
|
180
|
+
},
|
|
181
|
+
environment: config.environment
|
|
182
|
+
});
|
|
123
183
|
} catch (error) {
|
|
124
184
|
logger.error("Build-time translation processing failed:", error);
|
|
125
185
|
throw error;
|
|
126
186
|
}
|
|
187
|
+
else if (buildStartTime && !hasTransformErrors) trackEvent(TRACKING_EVENTS.BUILD_SUCCESS, {
|
|
188
|
+
framework: "webpack",
|
|
189
|
+
stats: {
|
|
190
|
+
totalEntries: totalEntriesCount,
|
|
191
|
+
filesTransformed: filesTransformedCount,
|
|
192
|
+
buildDuration: Date.now() - buildStartTime
|
|
193
|
+
},
|
|
194
|
+
environment: config.environment
|
|
195
|
+
});
|
|
127
196
|
});
|
|
128
197
|
compiler.hooks.shutdown.tapPromise(PLUGIN_NAME, async () => {
|
|
129
198
|
cleanupExistingMetadata(metadataFilePath);
|
|
@@ -165,6 +234,8 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
165
234
|
const metadataManager = new MetadataManager(getMetadataPath$1());
|
|
166
235
|
if (result.newEntries && result.newEntries.length > 0) {
|
|
167
236
|
await metadataManager.saveMetadataWithEntries(result.newEntries);
|
|
237
|
+
totalEntriesCount += result.newEntries.length;
|
|
238
|
+
filesTransformedCount++;
|
|
168
239
|
logger.debug(`Found ${result.newEntries.length} translatable text(s) in ${id}`);
|
|
169
240
|
}
|
|
170
241
|
logger.debug(`Returning transformed code for ${id}`);
|
|
@@ -173,6 +244,14 @@ const lingoUnplugin = createUnplugin((options) => {
|
|
|
173
244
|
map: result.map
|
|
174
245
|
};
|
|
175
246
|
} catch (error) {
|
|
247
|
+
hasTransformErrors = true;
|
|
248
|
+
if (currentFramework) trackEvent(TRACKING_EVENTS.BUILD_ERROR, {
|
|
249
|
+
framework: currentFramework,
|
|
250
|
+
errorType: "transform",
|
|
251
|
+
errorMessage: error instanceof Error ? error.message : "Unknown transform error",
|
|
252
|
+
filePath: id,
|
|
253
|
+
environment: config.environment
|
|
254
|
+
});
|
|
176
255
|
logger.error(`Transform error in ${id}:`, error);
|
|
177
256
|
return null;
|
|
178
257
|
}
|