@lingo.dev/compiler 0.1.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/LICENSE.md +201 -0
- package/README.md +192 -0
- package/build/_virtual/rolldown_runtime.cjs +29 -0
- package/build/_virtual/rolldown_runtime.mjs +7 -0
- package/build/index.cjs +0 -0
- package/build/index.d.cts +2 -0
- package/build/index.d.mts +2 -0
- package/build/index.mjs +1 -0
- package/build/metadata/manager.cjs +131 -0
- package/build/metadata/manager.mjs +123 -0
- package/build/metadata/manager.mjs.map +1 -0
- package/build/plugin/build-translator.cjs +198 -0
- package/build/plugin/build-translator.mjs +196 -0
- package/build/plugin/build-translator.mjs.map +1 -0
- package/build/plugin/cleanup.cjs +20 -0
- package/build/plugin/cleanup.mjs +20 -0
- package/build/plugin/cleanup.mjs.map +1 -0
- package/build/plugin/next-compiler-loader.cjs +41 -0
- package/build/plugin/next-compiler-loader.d.cts +12 -0
- package/build/plugin/next-compiler-loader.d.cts.map +1 -0
- package/build/plugin/next-compiler-loader.d.mts +13 -0
- package/build/plugin/next-compiler-loader.d.mts.map +1 -0
- package/build/plugin/next-compiler-loader.mjs +42 -0
- package/build/plugin/next-compiler-loader.mjs.map +1 -0
- package/build/plugin/next-config-loader.cjs +13 -0
- package/build/plugin/next-config-loader.d.cts +8 -0
- package/build/plugin/next-config-loader.d.cts.map +1 -0
- package/build/plugin/next-config-loader.d.mts +9 -0
- package/build/plugin/next-config-loader.d.mts.map +1 -0
- package/build/plugin/next-config-loader.mjs +14 -0
- package/build/plugin/next-config-loader.mjs.map +1 -0
- package/build/plugin/next-locale-client-loader.cjs +9 -0
- package/build/plugin/next-locale-client-loader.d.cts +8 -0
- package/build/plugin/next-locale-client-loader.d.cts.map +1 -0
- package/build/plugin/next-locale-client-loader.d.mts +9 -0
- package/build/plugin/next-locale-client-loader.d.mts.map +1 -0
- package/build/plugin/next-locale-client-loader.mjs +10 -0
- package/build/plugin/next-locale-client-loader.mjs.map +1 -0
- package/build/plugin/next-locale-server-loader.cjs +9 -0
- package/build/plugin/next-locale-server-loader.d.cts +8 -0
- package/build/plugin/next-locale-server-loader.d.cts.map +1 -0
- package/build/plugin/next-locale-server-loader.d.mts +9 -0
- package/build/plugin/next-locale-server-loader.d.mts.map +1 -0
- package/build/plugin/next-locale-server-loader.mjs +10 -0
- package/build/plugin/next-locale-server-loader.mjs.map +1 -0
- package/build/plugin/next.cjs +220 -0
- package/build/plugin/next.d.cts +9 -0
- package/build/plugin/next.d.cts.map +1 -0
- package/build/plugin/next.d.mts +9 -0
- package/build/plugin/next.d.mts.map +1 -0
- package/build/plugin/next.mjs +222 -0
- package/build/plugin/next.mjs.map +1 -0
- package/build/plugin/transform/babel-compat.cjs +13 -0
- package/build/plugin/transform/babel-compat.mjs +10 -0
- package/build/plugin/transform/babel-compat.mjs.map +1 -0
- package/build/plugin/transform/index.cjs +44 -0
- package/build/plugin/transform/index.mjs +42 -0
- package/build/plugin/transform/index.mjs.map +1 -0
- package/build/plugin/transform/metadata.cjs +142 -0
- package/build/plugin/transform/metadata.mjs +141 -0
- package/build/plugin/transform/metadata.mjs.map +1 -0
- package/build/plugin/transform/parse-override.cjs +145 -0
- package/build/plugin/transform/parse-override.mjs +144 -0
- package/build/plugin/transform/parse-override.mjs.map +1 -0
- package/build/plugin/transform/process-file.cjs +391 -0
- package/build/plugin/transform/process-file.mjs +390 -0
- package/build/plugin/transform/process-file.mjs.map +1 -0
- package/build/plugin/transform/use-i18n.cjs +8 -0
- package/build/plugin/transform/use-i18n.mjs +7 -0
- package/build/plugin/transform/use-i18n.mjs.map +1 -0
- package/build/plugin/transform/utils.cjs +205 -0
- package/build/plugin/transform/utils.mjs +192 -0
- package/build/plugin/transform/utils.mjs.map +1 -0
- package/build/plugin/unplugin.cjs +188 -0
- package/build/plugin/unplugin.d.cts +8 -0
- package/build/plugin/unplugin.d.cts.map +1 -0
- package/build/plugin/unplugin.d.mts +8 -0
- package/build/plugin/unplugin.d.mts.map +1 -0
- package/build/plugin/unplugin.mjs +186 -0
- package/build/plugin/unplugin.mjs.map +1 -0
- package/build/plugin/vite.cjs +28 -0
- package/build/plugin/vite.d.cts +9 -0
- package/build/plugin/vite.d.cts.map +1 -0
- package/build/plugin/vite.d.mts +9 -0
- package/build/plugin/vite.d.mts.map +1 -0
- package/build/plugin/vite.mjs +29 -0
- package/build/plugin/vite.mjs.map +1 -0
- package/build/plugin/webpack.cjs +27 -0
- package/build/plugin/webpack.d.cts +8 -0
- package/build/plugin/webpack.d.cts.map +1 -0
- package/build/plugin/webpack.d.mts +8 -0
- package/build/plugin/webpack.d.mts.map +1 -0
- package/build/plugin/webpack.mjs +28 -0
- package/build/plugin/webpack.mjs.map +1 -0
- package/build/react/client/index.cjs +9 -0
- package/build/react/client/index.d.cts +5 -0
- package/build/react/client/index.d.mts +5 -0
- package/build/react/client/index.mjs +6 -0
- package/build/react/client/useTranslation.cjs +71 -0
- package/build/react/client/useTranslation.d.cts +42 -0
- package/build/react/client/useTranslation.d.cts.map +1 -0
- package/build/react/client/useTranslation.d.mts +42 -0
- package/build/react/client/useTranslation.d.mts.map +1 -0
- package/build/react/client/useTranslation.mjs +71 -0
- package/build/react/client/useTranslation.mjs.map +1 -0
- package/build/react/next/client.cjs +25 -0
- package/build/react/next/client.d.cts +9 -0
- package/build/react/next/client.d.cts.map +1 -0
- package/build/react/next/client.d.mts +9 -0
- package/build/react/next/client.d.mts.map +1 -0
- package/build/react/next/client.mjs +24 -0
- package/build/react/next/client.mjs.map +1 -0
- package/build/react/next/cookie-locale-resolver.cjs +29 -0
- package/build/react/next/cookie-locale-resolver.d.cts +33 -0
- package/build/react/next/cookie-locale-resolver.d.cts.map +1 -0
- package/build/react/next/cookie-locale-resolver.d.mts +33 -0
- package/build/react/next/cookie-locale-resolver.d.mts.map +1 -0
- package/build/react/next/cookie-locale-resolver.mjs +29 -0
- package/build/react/next/cookie-locale-resolver.mjs.map +1 -0
- package/build/react/next/server.cjs +21 -0
- package/build/react/next/server.d.cts +13 -0
- package/build/react/next/server.d.cts.map +1 -0
- package/build/react/next/server.d.mts +14 -0
- package/build/react/next/server.d.mts.map +1 -0
- package/build/react/next/server.mjs +20 -0
- package/build/react/next/server.mjs.map +1 -0
- package/build/react/server/ServerLingoProvider.cjs +19 -0
- package/build/react/server/ServerLingoProvider.d.cts +12 -0
- package/build/react/server/ServerLingoProvider.d.cts.map +1 -0
- package/build/react/server/ServerLingoProvider.d.mts +12 -0
- package/build/react/server/ServerLingoProvider.d.mts.map +1 -0
- package/build/react/server/ServerLingoProvider.mjs +19 -0
- package/build/react/server/ServerLingoProvider.mjs.map +1 -0
- package/build/react/server/index.cjs +7 -0
- package/build/react/server/index.d.cts +4 -0
- package/build/react/server/index.d.mts +4 -0
- package/build/react/server/index.mjs +5 -0
- package/build/react/server/useTranslation.cjs +60 -0
- package/build/react/server/useTranslation.d.cts +36 -0
- package/build/react/server/useTranslation.d.cts.map +1 -0
- package/build/react/server/useTranslation.d.mts +36 -0
- package/build/react/server/useTranslation.d.mts.map +1 -0
- package/build/react/server/useTranslation.mjs +60 -0
- package/build/react/server/useTranslation.mjs.map +1 -0
- package/build/react/server-only/index.cjs +42 -0
- package/build/react/server-only/index.d.cts +38 -0
- package/build/react/server-only/index.d.cts.map +1 -0
- package/build/react/server-only/index.d.mts +38 -0
- package/build/react/server-only/index.d.mts.map +1 -0
- package/build/react/server-only/index.mjs +42 -0
- package/build/react/server-only/index.mjs.map +1 -0
- package/build/react/server-only/translations.cjs +85 -0
- package/build/react/server-only/translations.mjs +85 -0
- package/build/react/server-only/translations.mjs.map +1 -0
- package/build/react/shared/LingoContext.cjs +14 -0
- package/build/react/shared/LingoContext.d.cts +41 -0
- package/build/react/shared/LingoContext.d.cts.map +1 -0
- package/build/react/shared/LingoContext.d.mts +41 -0
- package/build/react/shared/LingoContext.d.mts.map +1 -0
- package/build/react/shared/LingoContext.mjs +13 -0
- package/build/react/shared/LingoContext.mjs.map +1 -0
- package/build/react/shared/LingoProvider.cjs +274 -0
- package/build/react/shared/LingoProvider.d.cts +76 -0
- package/build/react/shared/LingoProvider.d.cts.map +1 -0
- package/build/react/shared/LingoProvider.d.mts +76 -0
- package/build/react/shared/LingoProvider.d.mts.map +1 -0
- package/build/react/shared/LingoProvider.mjs +274 -0
- package/build/react/shared/LingoProvider.mjs.map +1 -0
- package/build/react/shared/LocaleSwitcher.cjs +61 -0
- package/build/react/shared/LocaleSwitcher.d.cts +71 -0
- package/build/react/shared/LocaleSwitcher.d.cts.map +1 -0
- package/build/react/shared/LocaleSwitcher.d.mts +71 -0
- package/build/react/shared/LocaleSwitcher.d.mts.map +1 -0
- package/build/react/shared/LocaleSwitcher.mjs +61 -0
- package/build/react/shared/LocaleSwitcher.mjs.map +1 -0
- package/build/react/shared/render-rich-text.cjs +55 -0
- package/build/react/shared/render-rich-text.d.cts +17 -0
- package/build/react/shared/render-rich-text.d.cts.map +1 -0
- package/build/react/shared/render-rich-text.d.mts +17 -0
- package/build/react/shared/render-rich-text.d.mts.map +1 -0
- package/build/react/shared/render-rich-text.mjs +54 -0
- package/build/react/shared/render-rich-text.mjs.map +1 -0
- package/build/react/shared/utils.cjs +34 -0
- package/build/react/shared/utils.mjs +35 -0
- package/build/react/shared/utils.mjs.map +1 -0
- package/build/react/types.d.cts +16 -0
- package/build/react/types.d.cts.map +1 -0
- package/build/react/types.d.mts +16 -0
- package/build/react/types.d.mts.map +1 -0
- package/build/translation-server/logger.cjs +37 -0
- package/build/translation-server/logger.mjs +37 -0
- package/build/translation-server/logger.mjs.map +1 -0
- package/build/translation-server/translation-server.cjs +547 -0
- package/build/translation-server/translation-server.mjs +544 -0
- package/build/translation-server/translation-server.mjs.map +1 -0
- package/build/translation-server/ws-events.cjs +15 -0
- package/build/translation-server/ws-events.mjs +15 -0
- package/build/translation-server/ws-events.mjs.map +1 -0
- package/build/translators/api.cjs +12 -0
- package/build/translators/api.mjs +12 -0
- package/build/translators/api.mjs.map +1 -0
- package/build/translators/cache-factory.cjs +26 -0
- package/build/translators/cache-factory.mjs +27 -0
- package/build/translators/cache-factory.mjs.map +1 -0
- package/build/translators/lingo/model-factory.cjs +179 -0
- package/build/translators/lingo/model-factory.mjs +174 -0
- package/build/translators/lingo/model-factory.mjs.map +1 -0
- package/build/translators/lingo/prompt.cjs +43 -0
- package/build/translators/lingo/prompt.mjs +43 -0
- package/build/translators/lingo/prompt.mjs.map +1 -0
- package/build/translators/lingo/service.cjs +152 -0
- package/build/translators/lingo/service.mjs +152 -0
- package/build/translators/lingo/service.mjs.map +1 -0
- package/build/translators/lingo/shots.cjs +28 -0
- package/build/translators/lingo/shots.mjs +28 -0
- package/build/translators/lingo/shots.mjs.map +1 -0
- package/build/translators/local-cache.cjs +115 -0
- package/build/translators/local-cache.mjs +113 -0
- package/build/translators/local-cache.mjs.map +1 -0
- package/build/translators/parse-xml.cjs +109 -0
- package/build/translators/parse-xml.mjs +108 -0
- package/build/translators/parse-xml.mjs.map +1 -0
- package/build/translators/pluralization/icu-validator.cjs +36 -0
- package/build/translators/pluralization/icu-validator.mjs +36 -0
- package/build/translators/pluralization/icu-validator.mjs.map +1 -0
- package/build/translators/pluralization/pattern-detector.cjs +25 -0
- package/build/translators/pluralization/pattern-detector.mjs +25 -0
- package/build/translators/pluralization/pattern-detector.mjs.map +1 -0
- package/build/translators/pluralization/prompt.cjs +98 -0
- package/build/translators/pluralization/prompt.mjs +98 -0
- package/build/translators/pluralization/prompt.mjs.map +1 -0
- package/build/translators/pluralization/service.cjs +247 -0
- package/build/translators/pluralization/service.mjs +247 -0
- package/build/translators/pluralization/service.mjs.map +1 -0
- package/build/translators/pluralization/shots.cjs +53 -0
- package/build/translators/pluralization/shots.mjs +53 -0
- package/build/translators/pluralization/shots.mjs.map +1 -0
- package/build/translators/pluralization/types.d.cts +17 -0
- package/build/translators/pluralization/types.d.cts.map +1 -0
- package/build/translators/pluralization/types.d.mts +17 -0
- package/build/translators/pluralization/types.d.mts.map +1 -0
- package/build/translators/pseudotranslator/index.cjs +129 -0
- package/build/translators/pseudotranslator/index.mjs +129 -0
- package/build/translators/pseudotranslator/index.mjs.map +1 -0
- package/build/translators/translation-service.cjs +182 -0
- package/build/translators/translation-service.mjs +183 -0
- package/build/translators/translation-service.mjs.map +1 -0
- package/build/translators/translator-factory.cjs +49 -0
- package/build/translators/translator-factory.mjs +50 -0
- package/build/translators/translator-factory.mjs.map +1 -0
- package/build/types.d.cts +161 -0
- package/build/types.d.cts.map +1 -0
- package/build/types.d.mts +161 -0
- package/build/types.d.mts.map +1 -0
- package/build/utils/config-factory.cjs +58 -0
- package/build/utils/config-factory.mjs +58 -0
- package/build/utils/config-factory.mjs.map +1 -0
- package/build/utils/hash.cjs +17 -0
- package/build/utils/hash.mjs +16 -0
- package/build/utils/hash.mjs.map +1 -0
- package/build/utils/is-valid-locale.cjs +14 -0
- package/build/utils/is-valid-locale.mjs +14 -0
- package/build/utils/is-valid-locale.mjs.map +1 -0
- package/build/utils/logger.cjs +51 -0
- package/build/utils/logger.mjs +50 -0
- package/build/utils/logger.mjs.map +1 -0
- package/build/utils/path-helpers.cjs +49 -0
- package/build/utils/path-helpers.mjs +47 -0
- package/build/utils/path-helpers.mjs.map +1 -0
- package/build/utils/timeout.cjs +42 -0
- package/build/utils/timeout.mjs +41 -0
- package/build/utils/timeout.mjs.map +1 -0
- package/build/virtual/code-generator.cjs +54 -0
- package/build/virtual/code-generator.mjs +53 -0
- package/build/virtual/code-generator.mjs.map +1 -0
- package/build/virtual/config.cjs +10 -0
- package/build/virtual/config.d.cts +9 -0
- package/build/virtual/config.d.cts.map +1 -0
- package/build/virtual/config.d.mts +9 -0
- package/build/virtual/config.d.mts.map +1 -0
- package/build/virtual/config.mjs +8 -0
- package/build/virtual/config.mjs.map +1 -0
- package/build/virtual/locale/client.cjs +23 -0
- package/build/virtual/locale/client.d.cts +19 -0
- package/build/virtual/locale/client.d.cts.map +1 -0
- package/build/virtual/locale/client.d.mts +19 -0
- package/build/virtual/locale/client.d.mts.map +1 -0
- package/build/virtual/locale/client.mjs +22 -0
- package/build/virtual/locale/client.mjs.map +1 -0
- package/build/virtual/locale/server.cjs +13 -0
- package/build/virtual/locale/server.d.cts +13 -0
- package/build/virtual/locale/server.d.cts.map +1 -0
- package/build/virtual/locale/server.d.mts +13 -0
- package/build/virtual/locale/server.d.mts.map +1 -0
- package/build/virtual/locale/server.mjs +13 -0
- package/build/virtual/locale/server.mjs.map +1 -0
- package/build/widget/lingo-dev-widget.cjs +228 -0
- package/build/widget/lingo-dev-widget.mjs +229 -0
- package/build/widget/lingo-dev-widget.mjs.map +1 -0
- package/package.json +189 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.mjs";
|
|
2
|
+
import { DEFAULT_TIMEOUTS, withTimeout } from "../utils/timeout.mjs";
|
|
3
|
+
import { getLingoDir } from "../utils/path-helpers.mjs";
|
|
4
|
+
import fsPromises from "fs/promises";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import lockfile from "proper-lockfile";
|
|
8
|
+
|
|
9
|
+
//#region src/metadata/manager.ts
|
|
10
|
+
function createEmptyMetadata() {
|
|
11
|
+
return {
|
|
12
|
+
entries: {},
|
|
13
|
+
stats: {
|
|
14
|
+
totalEntries: 0,
|
|
15
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function loadMetadata(path$1) {
|
|
20
|
+
return new MetadataManager(path$1).loadMetadata();
|
|
21
|
+
}
|
|
22
|
+
function cleanupExistingMetadata(metadataFilePath) {
|
|
23
|
+
logger.debug(`Attempting to cleanup metadata file: ${metadataFilePath}`);
|
|
24
|
+
try {
|
|
25
|
+
fs.unlinkSync(metadataFilePath);
|
|
26
|
+
logger.info(`🧹 Cleaned up build metadata file: ${metadataFilePath}`);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error.code === "ENOENT") logger.debug(`Metadata file already deleted or doesn't exist: ${metadataFilePath}`);
|
|
29
|
+
else logger.warn(`Failed to cleanup metadata file: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the absolute path to the metadata file
|
|
34
|
+
*
|
|
35
|
+
* @param config - Config with sourceRoot, lingoDir, and environment
|
|
36
|
+
* @returns Absolute path to metadata file
|
|
37
|
+
*/
|
|
38
|
+
function getMetadataPath(config) {
|
|
39
|
+
const filename = config.environment === "development" ? "metadata-dev.json" : "metadata-build.json";
|
|
40
|
+
return path.join(getLingoDir(config), filename);
|
|
41
|
+
}
|
|
42
|
+
var MetadataManager = class {
|
|
43
|
+
constructor(filePath) {
|
|
44
|
+
this.filePath = filePath;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load metadata from disk
|
|
48
|
+
* Creates empty metadata if file doesn't exist
|
|
49
|
+
* Times out after 15 seconds to prevent indefinite hangs
|
|
50
|
+
*/
|
|
51
|
+
async loadMetadata() {
|
|
52
|
+
try {
|
|
53
|
+
const content = await withTimeout(fsPromises.readFile(this.filePath, "utf-8"), DEFAULT_TIMEOUTS.METADATA, "Load metadata");
|
|
54
|
+
return JSON.parse(content);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.code === "ENOENT") return createEmptyMetadata();
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Save metadata to disk
|
|
62
|
+
* Times out after 15 seconds to prevent indefinite hangs
|
|
63
|
+
*/
|
|
64
|
+
async saveMetadata(metadata) {
|
|
65
|
+
await withTimeout(fsPromises.mkdir(path.dirname(this.filePath), { recursive: true }), DEFAULT_TIMEOUTS.FILE_IO, "Create metadata directory");
|
|
66
|
+
metadata.stats = {
|
|
67
|
+
totalEntries: Object.keys(metadata.entries).length,
|
|
68
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
69
|
+
};
|
|
70
|
+
const dir = path.dirname(this.filePath);
|
|
71
|
+
const base = path.basename(this.filePath);
|
|
72
|
+
const tmpPath = path.join(dir, `.${base}.tmp-${process.pid}-${Date.now()}`);
|
|
73
|
+
const json = JSON.stringify(metadata, null, 2);
|
|
74
|
+
await withTimeout(fsPromises.writeFile(tmpPath, json, "utf-8"), DEFAULT_TIMEOUTS.METADATA, "Save metadata (tmp write)");
|
|
75
|
+
try {
|
|
76
|
+
await withTimeout(fsPromises.rename(tmpPath, this.filePath), DEFAULT_TIMEOUTS.METADATA, "Save metadata (atomic rename)");
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error && typeof error === "object" && "code" in error && error.code === "EPERM") {
|
|
79
|
+
await withTimeout(fsPromises.writeFile(this.filePath, json, "utf-8"), DEFAULT_TIMEOUTS.METADATA, "Save metadata (EPERM fallback direct write)");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
} finally {
|
|
84
|
+
await fsPromises.unlink(tmpPath).catch(() => {});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Thread-safe save operation that atomically updates metadata with new entries
|
|
89
|
+
* Uses file locking to prevent concurrent write corruption
|
|
90
|
+
*
|
|
91
|
+
* @param entries - Translation entries to add/update
|
|
92
|
+
* @returns The updated metadata schema
|
|
93
|
+
*/
|
|
94
|
+
async saveMetadataWithEntries(entries) {
|
|
95
|
+
const lockDir = path.dirname(this.filePath);
|
|
96
|
+
await fsPromises.mkdir(lockDir, { recursive: true });
|
|
97
|
+
try {
|
|
98
|
+
await fsPromises.access(this.filePath);
|
|
99
|
+
} catch {
|
|
100
|
+
await fsPromises.writeFile(this.filePath, JSON.stringify(createEmptyMetadata(), null, 2), "utf-8");
|
|
101
|
+
}
|
|
102
|
+
const release = await lockfile.lock(this.filePath, {
|
|
103
|
+
retries: {
|
|
104
|
+
retries: 10,
|
|
105
|
+
minTimeout: 50,
|
|
106
|
+
maxTimeout: 1e3
|
|
107
|
+
},
|
|
108
|
+
stale: 2e3
|
|
109
|
+
});
|
|
110
|
+
try {
|
|
111
|
+
const currentMetadata = await this.loadMetadata();
|
|
112
|
+
for (const entry of entries) currentMetadata.entries[entry.hash] = entry;
|
|
113
|
+
await this.saveMetadata(currentMetadata);
|
|
114
|
+
return currentMetadata;
|
|
115
|
+
} finally {
|
|
116
|
+
await release();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
//#endregion
|
|
122
|
+
export { MetadataManager, cleanupExistingMetadata, createEmptyMetadata, getMetadataPath, loadMetadata };
|
|
123
|
+
//# sourceMappingURL=manager.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.mjs","names":["path","error: any","filePath: string"],"sources":["../../src/metadata/manager.ts"],"sourcesContent":["import fsPromises from \"fs/promises\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport type { MetadataSchema, PathConfig, TranslationEntry } from \"../types\";\nimport { DEFAULT_TIMEOUTS, withTimeout } from \"../utils/timeout\";\nimport { getLingoDir } from \"../utils/path-helpers\";\nimport { logger } from \"../utils/logger\";\n\nexport function createEmptyMetadata(): MetadataSchema {\n return {\n entries: {},\n stats: {\n totalEntries: 0,\n lastUpdated: new Date().toISOString(),\n },\n };\n}\n\nexport function loadMetadata(path: string) {\n return new MetadataManager(path).loadMetadata();\n}\n\nexport function cleanupExistingMetadata(metadataFilePath: string) {\n // General cleanup. Delete metadata and stop the server if any was started.\n logger.debug(`Attempting to cleanup metadata file: ${metadataFilePath}`);\n\n try {\n fs.unlinkSync(metadataFilePath);\n logger.info(`🧹 Cleaned up build metadata file: ${metadataFilePath}`);\n } catch (error: any) {\n // Ignore if file doesn't exist\n if (error.code === \"ENOENT\") {\n logger.debug(\n `Metadata file already deleted or doesn't exist: ${metadataFilePath}`,\n );\n } else {\n logger.warn(`Failed to cleanup metadata file: ${error.message}`);\n }\n }\n}\n\n/**\n * Get the absolute path to the metadata file\n *\n * @param config - Config with sourceRoot, lingoDir, and environment\n * @returns Absolute path to metadata file\n */\nexport function getMetadataPath(config: PathConfig): string {\n const filename =\n // Similar to next keeping dev build separate, let's keep the build metadata clean of any dev mode additions\n config.environment === \"development\"\n ? \"metadata-dev.json\"\n : \"metadata-build.json\";\n return path.join(getLingoDir(config), filename);\n}\n\nexport class MetadataManager {\n constructor(private readonly filePath: string) {}\n\n /**\n * Load metadata from disk\n * Creates empty metadata if file doesn't exist\n * Times out after 15 seconds to prevent indefinite hangs\n */\n async loadMetadata(): Promise<MetadataSchema> {\n try {\n const content = await withTimeout(\n fsPromises.readFile(this.filePath, \"utf-8\"),\n DEFAULT_TIMEOUTS.METADATA,\n \"Load metadata\",\n );\n return JSON.parse(content) as MetadataSchema;\n } catch (error: any) {\n if (error.code === \"ENOENT\") {\n // File doesn't exist, create new metadata\n return createEmptyMetadata();\n }\n throw error;\n }\n }\n\n /**\n * Save metadata to disk\n * Times out after 15 seconds to prevent indefinite hangs\n */\n private async saveMetadata(metadata: MetadataSchema): Promise<void> {\n await withTimeout(\n fsPromises.mkdir(path.dirname(this.filePath), { recursive: true }),\n DEFAULT_TIMEOUTS.FILE_IO,\n \"Create metadata directory\",\n );\n\n metadata.stats = {\n totalEntries: Object.keys(metadata.entries).length,\n lastUpdated: new Date().toISOString(),\n };\n\n // Per LLM writing to a file is not an atomic operation while rename is, so nobody should get partial content.\n // Sounds reasonable.\n const dir = path.dirname(this.filePath);\n const base = path.basename(this.filePath);\n\n // Keep temp file in the same directory to maximize chance that rename is atomic\n const tmpPath = path.join(dir, `.${base}.tmp-${process.pid}-${Date.now()}`);\n\n const json = JSON.stringify(metadata, null, 2);\n\n await withTimeout(\n fsPromises.writeFile(tmpPath, json, \"utf-8\"),\n DEFAULT_TIMEOUTS.METADATA,\n \"Save metadata (tmp write)\",\n );\n\n try {\n // TODO (AleksandrSl 14/12/2025): LLM says that we may want to remove older file first for windows, but it seems lo work fine as is.\n await withTimeout(\n fsPromises.rename(tmpPath, this.filePath),\n DEFAULT_TIMEOUTS.METADATA,\n \"Save metadata (atomic rename)\",\n );\n } catch (error) {\n // On Windows, rename() can fail with EPERM if something briefly holds the file.\n // As a fallback, try writing directly to the destination (not atomic).\n if (\n error &&\n typeof error === \"object\" &&\n \"code\" in error &&\n error.code === \"EPERM\"\n ) {\n await withTimeout(\n fsPromises.writeFile(this.filePath, json, \"utf-8\"),\n DEFAULT_TIMEOUTS.METADATA,\n \"Save metadata (EPERM fallback direct write)\",\n );\n return;\n }\n throw error;\n } finally {\n // Best-effort cleanup if rename failed for some reason\n await fsPromises.unlink(tmpPath).catch(() => {});\n }\n }\n\n /**\n * Thread-safe save operation that atomically updates metadata with new entries\n * Uses file locking to prevent concurrent write corruption\n *\n * @param entries - Translation entries to add/update\n * @returns The updated metadata schema\n */\n async saveMetadataWithEntries(\n entries: TranslationEntry[],\n ): Promise<MetadataSchema> {\n const lockDir = path.dirname(this.filePath);\n\n await fsPromises.mkdir(lockDir, { recursive: true });\n\n try {\n await fsPromises.access(this.filePath);\n } catch {\n await fsPromises.writeFile(\n this.filePath,\n JSON.stringify(createEmptyMetadata(), null, 2),\n \"utf-8\",\n );\n }\n\n const release = await lockfile.lock(this.filePath, {\n retries: {\n retries: 10,\n minTimeout: 50,\n maxTimeout: 1000,\n },\n stale: 2000,\n });\n\n try {\n // Re-load metadata inside lock to get latest state\n const currentMetadata = await this.loadMetadata();\n for (const entry of entries) {\n currentMetadata.entries[entry.hash] = entry;\n }\n await this.saveMetadata(currentMetadata);\n return currentMetadata;\n } finally {\n await release();\n }\n }\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,sBAAsC;AACpD,QAAO;EACL,SAAS,EAAE;EACX,OAAO;GACL,cAAc;GACd,8BAAa,IAAI,MAAM,EAAC,aAAa;GACtC;EACF;;AAGH,SAAgB,aAAa,QAAc;AACzC,QAAO,IAAI,gBAAgBA,OAAK,CAAC,cAAc;;AAGjD,SAAgB,wBAAwB,kBAA0B;AAEhE,QAAO,MAAM,wCAAwC,mBAAmB;AAExE,KAAI;AACF,KAAG,WAAW,iBAAiB;AAC/B,SAAO,KAAK,sCAAsC,mBAAmB;UAC9DC,OAAY;AAEnB,MAAI,MAAM,SAAS,SACjB,QAAO,MACL,mDAAmD,mBACpD;MAED,QAAO,KAAK,oCAAoC,MAAM,UAAU;;;;;;;;;AAWtE,SAAgB,gBAAgB,QAA4B;CAC1D,MAAM,WAEJ,OAAO,gBAAgB,gBACnB,sBACA;AACN,QAAO,KAAK,KAAK,YAAY,OAAO,EAAE,SAAS;;AAGjD,IAAa,kBAAb,MAA6B;CAC3B,YAAY,AAAiBC,UAAkB;EAAlB;;;;;;;CAO7B,MAAM,eAAwC;AAC5C,MAAI;GACF,MAAM,UAAU,MAAM,YACpB,WAAW,SAAS,KAAK,UAAU,QAAQ,EAC3C,iBAAiB,UACjB,gBACD;AACD,UAAO,KAAK,MAAM,QAAQ;WACnBD,OAAY;AACnB,OAAI,MAAM,SAAS,SAEjB,QAAO,qBAAqB;AAE9B,SAAM;;;;;;;CAQV,MAAc,aAAa,UAAyC;AAClE,QAAM,YACJ,WAAW,MAAM,KAAK,QAAQ,KAAK,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC,EAClE,iBAAiB,SACjB,4BACD;AAED,WAAS,QAAQ;GACf,cAAc,OAAO,KAAK,SAAS,QAAQ,CAAC;GAC5C,8BAAa,IAAI,MAAM,EAAC,aAAa;GACtC;EAID,MAAM,MAAM,KAAK,QAAQ,KAAK,SAAS;EACvC,MAAM,OAAO,KAAK,SAAS,KAAK,SAAS;EAGzC,MAAM,UAAU,KAAK,KAAK,KAAK,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG,KAAK,KAAK,GAAG;EAE3E,MAAM,OAAO,KAAK,UAAU,UAAU,MAAM,EAAE;AAE9C,QAAM,YACJ,WAAW,UAAU,SAAS,MAAM,QAAQ,EAC5C,iBAAiB,UACjB,4BACD;AAED,MAAI;AAEF,SAAM,YACJ,WAAW,OAAO,SAAS,KAAK,SAAS,EACzC,iBAAiB,UACjB,gCACD;WACM,OAAO;AAGd,OACE,SACA,OAAO,UAAU,YACjB,UAAU,SACV,MAAM,SAAS,SACf;AACA,UAAM,YACJ,WAAW,UAAU,KAAK,UAAU,MAAM,QAAQ,EAClD,iBAAiB,UACjB,8CACD;AACD;;AAEF,SAAM;YACE;AAER,SAAM,WAAW,OAAO,QAAQ,CAAC,YAAY,GAAG;;;;;;;;;;CAWpD,MAAM,wBACJ,SACyB;EACzB,MAAM,UAAU,KAAK,QAAQ,KAAK,SAAS;AAE3C,QAAM,WAAW,MAAM,SAAS,EAAE,WAAW,MAAM,CAAC;AAEpD,MAAI;AACF,SAAM,WAAW,OAAO,KAAK,SAAS;UAChC;AACN,SAAM,WAAW,UACf,KAAK,UACL,KAAK,UAAU,qBAAqB,EAAE,MAAM,EAAE,EAC9C,QACD;;EAGH,MAAM,UAAU,MAAM,SAAS,KAAK,KAAK,UAAU;GACjD,SAAS;IACP,SAAS;IACT,YAAY;IACZ,YAAY;IACb;GACD,OAAO;GACR,CAAC;AAEF,MAAI;GAEF,MAAM,kBAAkB,MAAM,KAAK,cAAc;AACjD,QAAK,MAAM,SAAS,QAClB,iBAAgB,QAAQ,MAAM,QAAQ;AAExC,SAAM,KAAK,aAAa,gBAAgB;AACxC,UAAO;YACC;AACR,SAAM,SAAS"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_logger = require('../utils/logger.cjs');
|
|
3
|
+
const require_api = require('../translators/api.cjs');
|
|
4
|
+
const require_cache_factory = require('../translators/cache-factory.cjs');
|
|
5
|
+
const require_manager = require('../metadata/manager.cjs');
|
|
6
|
+
const require_translation_server = require('../translation-server/translation-server.cjs');
|
|
7
|
+
let fs_promises = require("fs/promises");
|
|
8
|
+
fs_promises = require_rolldown_runtime.__toESM(fs_promises);
|
|
9
|
+
let path = require("path");
|
|
10
|
+
path = require_rolldown_runtime.__toESM(path);
|
|
11
|
+
|
|
12
|
+
//#region src/plugin/build-translator.ts
|
|
13
|
+
/**
|
|
14
|
+
* Build-time translation processor
|
|
15
|
+
*
|
|
16
|
+
* Handles translation generation and validation at build time
|
|
17
|
+
* Supports two modes:
|
|
18
|
+
* - "translate": Generate all translations, fail if translation fails
|
|
19
|
+
* - "cache-only": Validate cache completeness, fail if incomplete
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Process translations at build time
|
|
23
|
+
*
|
|
24
|
+
* @throws Error if validation or translation fails (causes build to fail)
|
|
25
|
+
*/
|
|
26
|
+
async function processBuildTranslations(options) {
|
|
27
|
+
const { config, publicOutputPath, metadataFilePath } = options;
|
|
28
|
+
const buildMode = process.env.LINGO_BUILD_MODE || config.buildMode;
|
|
29
|
+
require_logger.logger.info(`🌍 Build mode: ${buildMode}`);
|
|
30
|
+
if (metadataFilePath) require_logger.logger.info(`📋 Using build metadata file: ${metadataFilePath}`);
|
|
31
|
+
const metadata = await require_manager.loadMetadata(metadataFilePath);
|
|
32
|
+
if (!metadata || Object.keys(metadata.entries).length === 0) {
|
|
33
|
+
require_logger.logger.info("No translations to process (metadata is empty)");
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
stats: {}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const totalEntries = Object.keys(metadata.entries).length;
|
|
40
|
+
require_logger.logger.info(`📊 Found ${totalEntries} translatable entries`);
|
|
41
|
+
const cache = require_cache_factory.createCache(config);
|
|
42
|
+
if (buildMode === "cache-only") {
|
|
43
|
+
require_logger.logger.info("🔍 Validating translation cache...");
|
|
44
|
+
await validateCache(config, metadata, cache);
|
|
45
|
+
require_logger.logger.info("✅ Cache validation passed");
|
|
46
|
+
if (publicOutputPath) await copyStaticFiles(config, publicOutputPath, metadata, cache);
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
stats: buildCacheStats(config, metadata)
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
require_logger.logger.info("🔄 Generating translations...");
|
|
53
|
+
let translationServer;
|
|
54
|
+
try {
|
|
55
|
+
translationServer = await require_translation_server.startTranslationServer({
|
|
56
|
+
startPort: config.dev.translationServerStartPort,
|
|
57
|
+
onError: (err) => {
|
|
58
|
+
require_logger.logger.error("Translation server error:", err);
|
|
59
|
+
},
|
|
60
|
+
config
|
|
61
|
+
});
|
|
62
|
+
const needsSourceLocale = config.pluralization?.enabled !== false;
|
|
63
|
+
const allLocales = needsSourceLocale ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
64
|
+
require_logger.logger.info(`Processing translations for ${allLocales.length} locale(s)${needsSourceLocale ? " (including source locale for pluralization)" : ""}...`);
|
|
65
|
+
const stats = {};
|
|
66
|
+
const errors = [];
|
|
67
|
+
const localePromises = allLocales.map(async (locale) => {
|
|
68
|
+
require_logger.logger.info(`Translating to ${locale}...`);
|
|
69
|
+
const result = await translationServer.translateAll(locale);
|
|
70
|
+
stats[locale] = {
|
|
71
|
+
total: totalEntries,
|
|
72
|
+
translated: Object.keys(result.translations).length,
|
|
73
|
+
failed: result.errors.length
|
|
74
|
+
};
|
|
75
|
+
if (result.errors.length > 0) {
|
|
76
|
+
require_logger.logger.warn(`⚠️ ${result.errors.length} translation error(s) for ${locale}`);
|
|
77
|
+
errors.push({
|
|
78
|
+
locale,
|
|
79
|
+
error: `${result.errors.length} translation(s) failed`
|
|
80
|
+
});
|
|
81
|
+
} else require_logger.logger.info(`✅ ${locale} completed successfully`);
|
|
82
|
+
});
|
|
83
|
+
await Promise.all(localePromises);
|
|
84
|
+
if (errors.length > 0) {
|
|
85
|
+
const errorMsg = formatTranslationErrors(errors);
|
|
86
|
+
require_logger.logger.error(errorMsg);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
if (publicOutputPath) await copyStaticFiles(config, publicOutputPath, metadata, cache);
|
|
90
|
+
require_logger.logger.info("✅ Translation generation completed successfully");
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
stats
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
require_logger.logger.error("❌ Translation generation failed:", error);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
} finally {
|
|
99
|
+
if (translationServer) {
|
|
100
|
+
await translationServer.stop();
|
|
101
|
+
require_logger.logger.info("✅ Translation server stopped");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Validate that all required translations exist in cache
|
|
107
|
+
* @throws Error if cache is incomplete or missing
|
|
108
|
+
*/
|
|
109
|
+
async function validateCache(config, metadata, cache) {
|
|
110
|
+
const allHashes = Object.keys(metadata.entries);
|
|
111
|
+
const missingLocales = [];
|
|
112
|
+
const incompleteLocales = [];
|
|
113
|
+
const allLocales = config.pluralization?.enabled !== false ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
114
|
+
for (const locale of allLocales) try {
|
|
115
|
+
const entries = await cache.get(locale);
|
|
116
|
+
if (Object.keys(entries).length === 0) {
|
|
117
|
+
missingLocales.push(locale);
|
|
118
|
+
require_logger.logger.debug(`Cache file not found or empty for ${locale}`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const missingHashes = allHashes.filter((hash) => !entries[hash]);
|
|
122
|
+
if (missingHashes.length > 0) {
|
|
123
|
+
incompleteLocales.push({
|
|
124
|
+
locale,
|
|
125
|
+
missing: missingHashes.length,
|
|
126
|
+
total: allHashes.length
|
|
127
|
+
});
|
|
128
|
+
require_logger.logger.debug(`Missing hashes in ${locale}: ${missingHashes.slice(0, 5).join(", ")}${missingHashes.length > 5 ? "..." : ""}`);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
missingLocales.push(locale);
|
|
132
|
+
require_logger.logger.debug(`Failed to read cache for ${locale}:`, error);
|
|
133
|
+
}
|
|
134
|
+
if (missingLocales.length > 0 || incompleteLocales.length > 0) {
|
|
135
|
+
const errorMsg = formatCacheValidationError(missingLocales, incompleteLocales);
|
|
136
|
+
require_logger.logger.error(errorMsg);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function buildCacheStats(config, metadata) {
|
|
141
|
+
const totalEntries = Object.keys(metadata.entries).length;
|
|
142
|
+
const stats = {};
|
|
143
|
+
const allLocales = config.pluralization?.enabled !== false ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
144
|
+
for (const locale of allLocales) stats[locale] = {
|
|
145
|
+
total: totalEntries,
|
|
146
|
+
translated: totalEntries,
|
|
147
|
+
failed: 0
|
|
148
|
+
};
|
|
149
|
+
return stats;
|
|
150
|
+
}
|
|
151
|
+
async function copyStaticFiles(config, publicOutputPath, metadata, cache) {
|
|
152
|
+
require_logger.logger.info(`📦 Generating static translation files in ${publicOutputPath}`);
|
|
153
|
+
await fs_promises.default.mkdir(publicOutputPath, { recursive: true });
|
|
154
|
+
const usedHashes = new Set(Object.keys(metadata.entries));
|
|
155
|
+
require_logger.logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);
|
|
156
|
+
const allLocales = config.pluralization?.enabled !== false ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
157
|
+
for (const locale of allLocales) {
|
|
158
|
+
const publicFilePath = path.default.join(publicOutputPath, `${locale}.json`);
|
|
159
|
+
try {
|
|
160
|
+
const entries = await cache.get(locale, Array.from(usedHashes));
|
|
161
|
+
const outputData = require_api.dictionaryFrom(locale, entries);
|
|
162
|
+
await fs_promises.default.writeFile(publicFilePath, JSON.stringify(outputData, null, 2), "utf-8");
|
|
163
|
+
require_logger.logger.info(`✓ Generated ${locale}.json (${Object.keys(entries).length} translations)`);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
require_logger.logger.error(`❌ Failed to generate ${locale}.json:`, error);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function formatCacheValidationError(missingLocales, incompleteLocales) {
|
|
171
|
+
let msg = "❌ Cache validation failed in cache-only mode:\n\n";
|
|
172
|
+
if (missingLocales.length > 0) {
|
|
173
|
+
msg += ` 📁 Missing cache files:\n`;
|
|
174
|
+
msg += missingLocales.map((locale) => ` - ${locale}.json`).join("\n");
|
|
175
|
+
msg += "\n\n";
|
|
176
|
+
}
|
|
177
|
+
if (incompleteLocales.length > 0) {
|
|
178
|
+
msg += ` 📊 Incomplete cache:\n`;
|
|
179
|
+
msg += incompleteLocales.map((item) => ` - ${item.locale}: ${item.missing}/${item.total} translations missing`).join("\n");
|
|
180
|
+
msg += "\n\n";
|
|
181
|
+
}
|
|
182
|
+
msg += ` 💡 To fix:\n`;
|
|
183
|
+
msg += ` 1. Set LINGO_BUILD_MODE=translate to generate translations\n`;
|
|
184
|
+
msg += ` 2. Commit the generated .lingo/cache/*.json files\n`;
|
|
185
|
+
msg += ` 3. Ensure translation API keys are available if generating translations`;
|
|
186
|
+
return msg;
|
|
187
|
+
}
|
|
188
|
+
function formatTranslationErrors(errors) {
|
|
189
|
+
let msg = "❌ Translation generation failed:\n\n";
|
|
190
|
+
msg += errors.map((err) => ` - ${err.locale}: ${err.error}`).join("\n");
|
|
191
|
+
msg += "\n\n";
|
|
192
|
+
msg += ` 💡 Translation errors must be resolved in "translate" mode.\n`;
|
|
193
|
+
msg += ` Check translation server logs for details.`;
|
|
194
|
+
return msg;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
exports.processBuildTranslations = processBuildTranslations;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.mjs";
|
|
2
|
+
import { dictionaryFrom } from "../translators/api.mjs";
|
|
3
|
+
import { createCache } from "../translators/cache-factory.mjs";
|
|
4
|
+
import { loadMetadata } from "../metadata/manager.mjs";
|
|
5
|
+
import { startTranslationServer } from "../translation-server/translation-server.mjs";
|
|
6
|
+
import fsPromises from "fs/promises";
|
|
7
|
+
import path from "path";
|
|
8
|
+
|
|
9
|
+
//#region src/plugin/build-translator.ts
|
|
10
|
+
/**
|
|
11
|
+
* Build-time translation processor
|
|
12
|
+
*
|
|
13
|
+
* Handles translation generation and validation at build time
|
|
14
|
+
* Supports two modes:
|
|
15
|
+
* - "translate": Generate all translations, fail if translation fails
|
|
16
|
+
* - "cache-only": Validate cache completeness, fail if incomplete
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Process translations at build time
|
|
20
|
+
*
|
|
21
|
+
* @throws Error if validation or translation fails (causes build to fail)
|
|
22
|
+
*/
|
|
23
|
+
async function processBuildTranslations(options) {
|
|
24
|
+
const { config, publicOutputPath, metadataFilePath } = options;
|
|
25
|
+
const buildMode = process.env.LINGO_BUILD_MODE || config.buildMode;
|
|
26
|
+
logger.info(`🌍 Build mode: ${buildMode}`);
|
|
27
|
+
if (metadataFilePath) logger.info(`📋 Using build metadata file: ${metadataFilePath}`);
|
|
28
|
+
const metadata = await loadMetadata(metadataFilePath);
|
|
29
|
+
if (!metadata || Object.keys(metadata.entries).length === 0) {
|
|
30
|
+
logger.info("No translations to process (metadata is empty)");
|
|
31
|
+
return {
|
|
32
|
+
success: true,
|
|
33
|
+
stats: {}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const totalEntries = Object.keys(metadata.entries).length;
|
|
37
|
+
logger.info(`📊 Found ${totalEntries} translatable entries`);
|
|
38
|
+
const cache = createCache(config);
|
|
39
|
+
if (buildMode === "cache-only") {
|
|
40
|
+
logger.info("🔍 Validating translation cache...");
|
|
41
|
+
await validateCache(config, metadata, cache);
|
|
42
|
+
logger.info("✅ Cache validation passed");
|
|
43
|
+
if (publicOutputPath) await copyStaticFiles(config, publicOutputPath, metadata, cache);
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
stats: buildCacheStats(config, metadata)
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
logger.info("🔄 Generating translations...");
|
|
50
|
+
let translationServer;
|
|
51
|
+
try {
|
|
52
|
+
translationServer = await startTranslationServer({
|
|
53
|
+
startPort: config.dev.translationServerStartPort,
|
|
54
|
+
onError: (err) => {
|
|
55
|
+
logger.error("Translation server error:", err);
|
|
56
|
+
},
|
|
57
|
+
config
|
|
58
|
+
});
|
|
59
|
+
const needsSourceLocale = config.pluralization?.enabled !== false;
|
|
60
|
+
const allLocales = needsSourceLocale ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
61
|
+
logger.info(`Processing translations for ${allLocales.length} locale(s)${needsSourceLocale ? " (including source locale for pluralization)" : ""}...`);
|
|
62
|
+
const stats = {};
|
|
63
|
+
const errors = [];
|
|
64
|
+
const localePromises = allLocales.map(async (locale) => {
|
|
65
|
+
logger.info(`Translating to ${locale}...`);
|
|
66
|
+
const result = await translationServer.translateAll(locale);
|
|
67
|
+
stats[locale] = {
|
|
68
|
+
total: totalEntries,
|
|
69
|
+
translated: Object.keys(result.translations).length,
|
|
70
|
+
failed: result.errors.length
|
|
71
|
+
};
|
|
72
|
+
if (result.errors.length > 0) {
|
|
73
|
+
logger.warn(`⚠️ ${result.errors.length} translation error(s) for ${locale}`);
|
|
74
|
+
errors.push({
|
|
75
|
+
locale,
|
|
76
|
+
error: `${result.errors.length} translation(s) failed`
|
|
77
|
+
});
|
|
78
|
+
} else logger.info(`✅ ${locale} completed successfully`);
|
|
79
|
+
});
|
|
80
|
+
await Promise.all(localePromises);
|
|
81
|
+
if (errors.length > 0) {
|
|
82
|
+
const errorMsg = formatTranslationErrors(errors);
|
|
83
|
+
logger.error(errorMsg);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if (publicOutputPath) await copyStaticFiles(config, publicOutputPath, metadata, cache);
|
|
87
|
+
logger.info("✅ Translation generation completed successfully");
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
stats
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error("❌ Translation generation failed:", error);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
} finally {
|
|
96
|
+
if (translationServer) {
|
|
97
|
+
await translationServer.stop();
|
|
98
|
+
logger.info("✅ Translation server stopped");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Validate that all required translations exist in cache
|
|
104
|
+
* @throws Error if cache is incomplete or missing
|
|
105
|
+
*/
|
|
106
|
+
async function validateCache(config, metadata, cache) {
|
|
107
|
+
const allHashes = Object.keys(metadata.entries);
|
|
108
|
+
const missingLocales = [];
|
|
109
|
+
const incompleteLocales = [];
|
|
110
|
+
const allLocales = config.pluralization?.enabled !== false ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
111
|
+
for (const locale of allLocales) try {
|
|
112
|
+
const entries = await cache.get(locale);
|
|
113
|
+
if (Object.keys(entries).length === 0) {
|
|
114
|
+
missingLocales.push(locale);
|
|
115
|
+
logger.debug(`Cache file not found or empty for ${locale}`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const missingHashes = allHashes.filter((hash) => !entries[hash]);
|
|
119
|
+
if (missingHashes.length > 0) {
|
|
120
|
+
incompleteLocales.push({
|
|
121
|
+
locale,
|
|
122
|
+
missing: missingHashes.length,
|
|
123
|
+
total: allHashes.length
|
|
124
|
+
});
|
|
125
|
+
logger.debug(`Missing hashes in ${locale}: ${missingHashes.slice(0, 5).join(", ")}${missingHashes.length > 5 ? "..." : ""}`);
|
|
126
|
+
}
|
|
127
|
+
} catch (error) {
|
|
128
|
+
missingLocales.push(locale);
|
|
129
|
+
logger.debug(`Failed to read cache for ${locale}:`, error);
|
|
130
|
+
}
|
|
131
|
+
if (missingLocales.length > 0 || incompleteLocales.length > 0) {
|
|
132
|
+
const errorMsg = formatCacheValidationError(missingLocales, incompleteLocales);
|
|
133
|
+
logger.error(errorMsg);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function buildCacheStats(config, metadata) {
|
|
138
|
+
const totalEntries = Object.keys(metadata.entries).length;
|
|
139
|
+
const stats = {};
|
|
140
|
+
const allLocales = config.pluralization?.enabled !== false ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
141
|
+
for (const locale of allLocales) stats[locale] = {
|
|
142
|
+
total: totalEntries,
|
|
143
|
+
translated: totalEntries,
|
|
144
|
+
failed: 0
|
|
145
|
+
};
|
|
146
|
+
return stats;
|
|
147
|
+
}
|
|
148
|
+
async function copyStaticFiles(config, publicOutputPath, metadata, cache) {
|
|
149
|
+
logger.info(`📦 Generating static translation files in ${publicOutputPath}`);
|
|
150
|
+
await fsPromises.mkdir(publicOutputPath, { recursive: true });
|
|
151
|
+
const usedHashes = new Set(Object.keys(metadata.entries));
|
|
152
|
+
logger.info(`📊 Filtering translations to ${usedHashes.size} used hash(es)`);
|
|
153
|
+
const allLocales = config.pluralization?.enabled !== false ? [config.sourceLocale, ...config.targetLocales] : config.targetLocales;
|
|
154
|
+
for (const locale of allLocales) {
|
|
155
|
+
const publicFilePath = path.join(publicOutputPath, `${locale}.json`);
|
|
156
|
+
try {
|
|
157
|
+
const entries = await cache.get(locale, Array.from(usedHashes));
|
|
158
|
+
const outputData = dictionaryFrom(locale, entries);
|
|
159
|
+
await fsPromises.writeFile(publicFilePath, JSON.stringify(outputData, null, 2), "utf-8");
|
|
160
|
+
logger.info(`✓ Generated ${locale}.json (${Object.keys(entries).length} translations)`);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
logger.error(`❌ Failed to generate ${locale}.json:`, error);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function formatCacheValidationError(missingLocales, incompleteLocales) {
|
|
168
|
+
let msg = "❌ Cache validation failed in cache-only mode:\n\n";
|
|
169
|
+
if (missingLocales.length > 0) {
|
|
170
|
+
msg += ` 📁 Missing cache files:\n`;
|
|
171
|
+
msg += missingLocales.map((locale) => ` - ${locale}.json`).join("\n");
|
|
172
|
+
msg += "\n\n";
|
|
173
|
+
}
|
|
174
|
+
if (incompleteLocales.length > 0) {
|
|
175
|
+
msg += ` 📊 Incomplete cache:\n`;
|
|
176
|
+
msg += incompleteLocales.map((item) => ` - ${item.locale}: ${item.missing}/${item.total} translations missing`).join("\n");
|
|
177
|
+
msg += "\n\n";
|
|
178
|
+
}
|
|
179
|
+
msg += ` 💡 To fix:\n`;
|
|
180
|
+
msg += ` 1. Set LINGO_BUILD_MODE=translate to generate translations\n`;
|
|
181
|
+
msg += ` 2. Commit the generated .lingo/cache/*.json files\n`;
|
|
182
|
+
msg += ` 3. Ensure translation API keys are available if generating translations`;
|
|
183
|
+
return msg;
|
|
184
|
+
}
|
|
185
|
+
function formatTranslationErrors(errors) {
|
|
186
|
+
let msg = "❌ Translation generation failed:\n\n";
|
|
187
|
+
msg += errors.map((err) => ` - ${err.locale}: ${err.error}`).join("\n");
|
|
188
|
+
msg += "\n\n";
|
|
189
|
+
msg += ` 💡 Translation errors must be resolved in "translate" mode.\n`;
|
|
190
|
+
msg += ` Check translation server logs for details.`;
|
|
191
|
+
return msg;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
export { processBuildTranslations };
|
|
196
|
+
//# sourceMappingURL=build-translator.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/plugin/cleanup.ts
|
|
3
|
+
function registerCleanupOnCurrentProcess({ cleanup, asyncCleanup }) {
|
|
4
|
+
if (asyncCleanup) {
|
|
5
|
+
let shuttingDown = false;
|
|
6
|
+
async function performGracefulShutdown(code) {
|
|
7
|
+
if (shuttingDown) return;
|
|
8
|
+
shuttingDown = true;
|
|
9
|
+
await asyncCleanup?.();
|
|
10
|
+
process.exit(code);
|
|
11
|
+
}
|
|
12
|
+
process.on("SIGINT", () => performGracefulShutdown(0));
|
|
13
|
+
process.on("SIGTERM", () => performGracefulShutdown(143));
|
|
14
|
+
process.on("SIGBREAK", () => performGracefulShutdown(0));
|
|
15
|
+
}
|
|
16
|
+
if (cleanup) process.on("exit", () => cleanup());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
exports.registerCleanupOnCurrentProcess = registerCleanupOnCurrentProcess;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/plugin/cleanup.ts
|
|
2
|
+
function registerCleanupOnCurrentProcess({ cleanup, asyncCleanup }) {
|
|
3
|
+
if (asyncCleanup) {
|
|
4
|
+
let shuttingDown = false;
|
|
5
|
+
async function performGracefulShutdown(code) {
|
|
6
|
+
if (shuttingDown) return;
|
|
7
|
+
shuttingDown = true;
|
|
8
|
+
await asyncCleanup?.();
|
|
9
|
+
process.exit(code);
|
|
10
|
+
}
|
|
11
|
+
process.on("SIGINT", () => performGracefulShutdown(0));
|
|
12
|
+
process.on("SIGTERM", () => performGracefulShutdown(143));
|
|
13
|
+
process.on("SIGBREAK", () => performGracefulShutdown(0));
|
|
14
|
+
}
|
|
15
|
+
if (cleanup) process.on("exit", () => cleanup());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { registerCleanupOnCurrentProcess };
|
|
20
|
+
//# sourceMappingURL=cleanup.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.mjs","names":[],"sources":["../../src/plugin/cleanup.ts"],"sourcesContent":["export function registerCleanupOnCurrentProcess({\n cleanup,\n asyncCleanup,\n}: {\n cleanup?: () => void;\n asyncCleanup?: () => Promise<void>;\n}) {\n if (asyncCleanup) {\n let shuttingDown = false;\n async function performGracefulShutdown(code: number) {\n if (shuttingDown) return;\n shuttingDown = true;\n\n await asyncCleanup?.();\n\n process.exit(code);\n }\n\n process.on(\"SIGINT\", () => performGracefulShutdown(0));\n process.on(\"SIGTERM\", () => performGracefulShutdown(143));\n process.on(\"SIGBREAK\", () => performGracefulShutdown(0));\n }\n\n if (cleanup) {\n process.on(\"exit\", () => cleanup());\n }\n}\n"],"mappings":";AAAA,SAAgB,gCAAgC,EAC9C,SACA,gBAIC;AACD,KAAI,cAAc;EAChB,IAAI,eAAe;EACnB,eAAe,wBAAwB,MAAc;AACnD,OAAI,aAAc;AAClB,kBAAe;AAEf,SAAM,gBAAgB;AAEtB,WAAQ,KAAK,KAAK;;AAGpB,UAAQ,GAAG,gBAAgB,wBAAwB,EAAE,CAAC;AACtD,UAAQ,GAAG,iBAAiB,wBAAwB,IAAI,CAAC;AACzD,UAAQ,GAAG,kBAAkB,wBAAwB,EAAE,CAAC;;AAG1D,KAAI,QACF,SAAQ,GAAG,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const require_logger = require('../utils/logger.cjs');
|
|
2
|
+
const require_manager = require('../metadata/manager.cjs');
|
|
3
|
+
const require_index = require('./transform/index.cjs');
|
|
4
|
+
|
|
5
|
+
//#region src/plugin/next-compiler-loader.ts
|
|
6
|
+
/**
|
|
7
|
+
* Turbopack/Webpack loader for automatic translation
|
|
8
|
+
*
|
|
9
|
+
* This loader transforms React components to inject translation calls automatically.
|
|
10
|
+
*
|
|
11
|
+
* For production builds, translations are generated after compilation completes
|
|
12
|
+
* via Next.js's runAfterProductionCompile hook (see next.ts plugin).
|
|
13
|
+
*/
|
|
14
|
+
async function nextCompilerLoader(source) {
|
|
15
|
+
if (typeof this.async !== "function") throw new Error("This module must be run as a loader");
|
|
16
|
+
const callback = this.async();
|
|
17
|
+
try {
|
|
18
|
+
const config = this.getOptions();
|
|
19
|
+
const metadataManager = new require_manager.MetadataManager(config.metadataFilePath);
|
|
20
|
+
require_logger.logger.debug(`[Turbopack Loader] Processing: ${this.resourcePath}`);
|
|
21
|
+
const result = require_index.transformComponent({
|
|
22
|
+
code: source,
|
|
23
|
+
filePath: this.resourcePath,
|
|
24
|
+
config
|
|
25
|
+
});
|
|
26
|
+
if (!result.transformed) return callback(null, source);
|
|
27
|
+
if (result.newEntries && result.newEntries.length > 0) {
|
|
28
|
+
await metadataManager.saveMetadataWithEntries(result.newEntries);
|
|
29
|
+
require_logger.logger.debug(`[Turbopack Loader] Found ${result.newEntries.length} translatable text(s) in ${this.resourcePath}`);
|
|
30
|
+
}
|
|
31
|
+
const validMap = result.map && result.map.sources && Array.isArray(result.map.sources) && result.map.sources.every((s) => typeof s === "string") ? result.map : void 0;
|
|
32
|
+
callback(null, result.code, validMap);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
require_logger.logger.error(`Compiler failed for ${this.resourcePath}:`);
|
|
35
|
+
require_logger.logger.error("Details:", error, typeof error === "object" && error && "message" in error ? error.message : error, error instanceof Error ? error.stack : void 0);
|
|
36
|
+
callback(error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
module.exports = nextCompilerLoader;
|