@babelize/engine 0.0.0-stage → 0.1.2
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 +202 -0
- package/dist/BabelizeEngine.d.ts +12 -0
- package/dist/BabelizeEngine.js +47 -0
- package/dist/analysis/analyze.d.ts +106 -0
- package/dist/analysis/analyze.js +323 -0
- package/dist/config/env.d.ts +9 -0
- package/dist/config/env.js +16 -0
- package/dist/config.d.ts +74 -0
- package/dist/config.js +47 -0
- package/dist/detection/stack.d.ts +42 -0
- package/dist/detection/stack.js +277 -0
- package/dist/django/catalog.d.ts +92 -0
- package/dist/django/catalog.js +677 -0
- package/dist/django/extract.d.ts +20 -0
- package/dist/django/extract.js +79 -0
- package/dist/django/inject-file.d.ts +6 -0
- package/dist/django/inject-file.js +52 -0
- package/dist/django/inject.d.ts +11 -0
- package/dist/django/inject.js +205 -0
- package/dist/django/keys.d.ts +21 -0
- package/dist/django/keys.js +63 -0
- package/dist/django/locale-names.d.ts +13 -0
- package/dist/django/locale-names.js +60 -0
- package/dist/django/mo-file.d.ts +6 -0
- package/dist/django/mo-file.js +60 -0
- package/dist/django/plural-forms.d.ts +34 -0
- package/dist/django/plural-forms.js +216 -0
- package/dist/django/po-file.d.ts +38 -0
- package/dist/django/po-file.js +273 -0
- package/dist/django/python-extract.d.ts +34 -0
- package/dist/django/python-extract.js +619 -0
- package/dist/django/python-lexer.d.ts +65 -0
- package/dist/django/python-lexer.js +398 -0
- package/dist/django/template-extract.d.ts +32 -0
- package/dist/django/template-extract.js +680 -0
- package/dist/django/types.d.ts +49 -0
- package/dist/django/types.js +15 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +140 -0
- package/dist/injection/InjectionStrategy.d.ts +62 -0
- package/dist/injection/InjectionStrategy.js +2 -0
- package/dist/injection/StrategyFactory.d.ts +4 -0
- package/dist/injection/StrategyFactory.js +29 -0
- package/dist/injection/html/html-injector.d.ts +11 -0
- package/dist/injection/html/html-injector.js +173 -0
- package/dist/injection/strategies/NextIntlStrategy.d.ts +25 -0
- package/dist/injection/strategies/NextIntlStrategy.js +141 -0
- package/dist/injection/strategies/ReactI18nextStrategy.d.ts +19 -0
- package/dist/injection/strategies/ReactI18nextStrategy.js +116 -0
- package/dist/injection/strategies/VanillaStrategy.d.ts +11 -0
- package/dist/injection/strategies/VanillaStrategy.js +73 -0
- package/dist/injection/strategies/VueI18nStrategy.d.ts +11 -0
- package/dist/injection/strategies/VueI18nStrategy.js +84 -0
- package/dist/local/formatter.d.ts +26 -0
- package/dist/local/formatter.js +105 -0
- package/dist/local/git.d.ts +23 -0
- package/dist/local/git.js +136 -0
- package/dist/local/hosted-provider.d.ts +68 -0
- package/dist/local/hosted-provider.js +215 -0
- package/dist/local/index.d.ts +23 -0
- package/dist/local/index.js +75 -0
- package/dist/local/provider.d.ts +56 -0
- package/dist/local/provider.js +119 -0
- package/dist/local/pseudo-provider.d.ts +18 -0
- package/dist/local/pseudo-provider.js +208 -0
- package/dist/local/report.d.ts +177 -0
- package/dist/local/report.js +186 -0
- package/dist/local/translate-core.d.ts +218 -0
- package/dist/local/translate-core.js +499 -0
- package/dist/local/translate-messages.d.ts +49 -0
- package/dist/local/translate-messages.js +89 -0
- package/dist/local/workspace-copy.d.ts +91 -0
- package/dist/local/workspace-copy.js +355 -0
- package/dist/modules/extractor/extract.d.ts +20 -0
- package/dist/modules/extractor/extract.js +709 -0
- package/dist/modules/extractor/index.d.ts +2 -0
- package/dist/modules/extractor/index.js +63 -0
- package/dist/modules/finalization/django-finalize.d.ts +28 -0
- package/dist/modules/finalization/django-finalize.js +412 -0
- package/dist/modules/finalization/finalize.d.ts +38 -0
- package/dist/modules/finalization/finalize.js +745 -0
- package/dist/modules/finalization/index.d.ts +1 -0
- package/dist/modules/finalization/index.js +5 -0
- package/dist/modules/finalization/nextjs-finalize.d.ts +40 -0
- package/dist/modules/finalization/nextjs-finalize.js +328 -0
- package/dist/modules/finalization/static-runtime.d.ts +9 -0
- package/dist/modules/finalization/static-runtime.js +261 -0
- package/dist/modules/finalization/writes.d.ts +29 -0
- package/dist/modules/finalization/writes.js +78 -0
- package/dist/modules/format-injection/index.d.ts +19 -0
- package/dist/modules/format-injection/index.js +273 -0
- package/dist/modules/format-injection/inject-formats.d.ts +31 -0
- package/dist/modules/format-injection/inject-formats.js +208 -0
- package/dist/modules/glossary/index.d.ts +2 -0
- package/dist/modules/glossary/index.js +16 -0
- package/dist/modules/glossary/rules.d.ts +81 -0
- package/dist/modules/glossary/rules.js +244 -0
- package/dist/modules/glossary/types.d.ts +22 -0
- package/dist/modules/glossary/types.js +2 -0
- package/dist/modules/injection/index.d.ts +1 -0
- package/dist/modules/injection/index.js +6 -0
- package/dist/modules/injection/inject.d.ts +29 -0
- package/dist/modules/injection/inject.js +1050 -0
- package/dist/modules/keygen/index.d.ts +1 -0
- package/dist/modules/keygen/index.js +11 -0
- package/dist/modules/keygen/keygen.d.ts +4 -0
- package/dist/modules/keygen/keygen.js +123 -0
- package/dist/modules/keygen/locale-data.d.ts +7 -0
- package/dist/modules/keygen/locale-data.js +73 -0
- package/dist/modules/keygen/public.d.ts +2 -0
- package/dist/modules/keygen/public.js +12 -0
- package/dist/modules/locale-builder/index.d.ts +1 -0
- package/dist/modules/locale-builder/index.js +7 -0
- package/dist/modules/locale-builder/locales.d.ts +13 -0
- package/dist/modules/locale-builder/locales.js +130 -0
- package/dist/modules/lockfile/hasher.d.ts +7 -0
- package/dist/modules/lockfile/hasher.js +13 -0
- package/dist/modules/lockfile/index.d.ts +3 -0
- package/dist/modules/lockfile/index.js +22 -0
- package/dist/modules/lockfile/lockfile-manager.d.ts +99 -0
- package/dist/modules/lockfile/lockfile-manager.js +497 -0
- package/dist/modules/lockfile/schema.d.ts +196 -0
- package/dist/modules/lockfile/schema.js +56 -0
- package/dist/modules/parser/index.d.ts +2 -0
- package/dist/modules/parser/index.js +9 -0
- package/dist/modules/summary/index.d.ts +1 -0
- package/dist/modules/summary/index.js +7 -0
- package/dist/modules/summary/summary.d.ts +9 -0
- package/dist/modules/summary/summary.js +195 -0
- package/dist/modules/sync-detector/index.d.ts +2 -0
- package/dist/modules/sync-detector/index.js +7 -0
- package/dist/modules/sync-detector/reporter.d.ts +3 -0
- package/dist/modules/sync-detector/reporter.js +16 -0
- package/dist/modules/sync-detector/sync-detector.d.ts +39 -0
- package/dist/modules/sync-detector/sync-detector.js +374 -0
- package/dist/modules/translator/batch-optimizer.d.ts +12 -0
- package/dist/modules/translator/batch-optimizer.js +30 -0
- package/dist/modules/translator/client/byok.provider.d.ts +64 -0
- package/dist/modules/translator/client/byok.provider.js +319 -0
- package/dist/modules/translator/client/direct-translator.client.d.ts +12 -0
- package/dist/modules/translator/client/direct-translator.client.js +71 -0
- package/dist/modules/translator/client/gemini.provider.d.ts +1 -0
- package/dist/modules/translator/client/gemini.provider.js +110 -0
- package/dist/modules/translator/client/groq.provider.d.ts +1 -0
- package/dist/modules/translator/client/groq.provider.js +111 -0
- package/dist/modules/translator/client/request.types.d.ts +1 -0
- package/dist/modules/translator/client/request.types.js +2 -0
- package/dist/modules/translator/client/retry.d.ts +29 -0
- package/dist/modules/translator/client/retry.js +94 -0
- package/dist/modules/translator/client/test-utils/mock.client.d.ts +31 -0
- package/dist/modules/translator/client/test-utils/mock.client.js +59 -0
- package/dist/modules/translator/client/translator.interface.d.ts +1 -0
- package/dist/modules/translator/client/translator.interface.js +2 -0
- package/dist/modules/translator/icu-branches.d.ts +138 -0
- package/dist/modules/translator/icu-branches.js +393 -0
- package/dist/modules/translator/language-codes/errors.d.ts +10 -0
- package/dist/modules/translator/language-codes/errors.js +18 -0
- package/dist/modules/translator/language-codes/flores-map.d.ts +1 -0
- package/dist/modules/translator/language-codes/flores-map.js +107 -0
- package/dist/modules/translator/language-codes/index.d.ts +11 -0
- package/dist/modules/translator/language-codes/index.js +24 -0
- package/dist/modules/translator/language-codes/language-mapper.d.ts +25 -0
- package/dist/modules/translator/language-codes/language-mapper.js +132 -0
- package/dist/modules/translator/language-codes/rtl-languages.d.ts +2 -0
- package/dist/modules/translator/language-codes/rtl-languages.js +9 -0
- package/dist/modules/translator/memory/memory-matcher.d.ts +8 -0
- package/dist/modules/translator/memory/memory-matcher.js +218 -0
- package/dist/modules/translator/memory/types.d.ts +22 -0
- package/dist/modules/translator/memory/types.js +2 -0
- package/dist/modules/translator/translate.d.ts +74 -0
- package/dist/modules/translator/translate.js +1047 -0
- package/dist/modules/vue/migrate.d.ts +13 -0
- package/dist/modules/vue/migrate.js +40 -0
- package/dist/modules/vue/script-inject.d.ts +16 -0
- package/dist/modules/vue/script-inject.js +115 -0
- package/dist/modules/vue/sfc.d.ts +11 -0
- package/dist/modules/vue/sfc.js +51 -0
- package/dist/modules/vue/template-rewrite.d.ts +25 -0
- package/dist/modules/vue/template-rewrite.js +76 -0
- package/dist/modules/vue/template-scan.d.ts +29 -0
- package/dist/modules/vue/template-scan.js +264 -0
- package/dist/modules/workspace/index.d.ts +1 -0
- package/dist/modules/workspace/index.js +5 -0
- package/dist/modules/workspace/validate.d.ts +13 -0
- package/dist/modules/workspace/validate.js +333 -0
- package/dist/orchestrator.d.ts +13 -0
- package/dist/orchestrator.js +727 -0
- package/dist/providers/default-translation-provider.d.ts +20 -0
- package/dist/providers/default-translation-provider.js +30 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +21 -0
- package/dist/providers/types.d.ts +153 -0
- package/dist/providers/types.js +2 -0
- package/dist/qa/check-project.d.ts +50 -0
- package/dist/qa/check-project.js +193 -0
- package/dist/qa/gettext-files.d.ts +15 -0
- package/dist/qa/gettext-files.js +115 -0
- package/dist/qa/locale-files.d.ts +44 -0
- package/dist/qa/locale-files.js +159 -0
- package/dist/qa/locale-qa.d.ts +61 -0
- package/dist/qa/locale-qa.js +342 -0
- package/dist/qa/review.d.ts +43 -0
- package/dist/qa/review.js +175 -0
- package/dist/runtime/config.d.ts +8 -0
- package/dist/runtime/config.js +37 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/types.d.ts +427 -0
- package/dist/runtime/types.js +38 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.js +12 -0
- package/dist/utils/ast-helpers.d.ts +57 -0
- package/dist/utils/ast-helpers.js +608 -0
- package/dist/utils/cache-keys.d.ts +8 -0
- package/dist/utils/cache-keys.js +18 -0
- package/dist/utils/circuit-breaker.d.ts +43 -0
- package/dist/utils/circuit-breaker.js +119 -0
- package/dist/utils/concurrent-map.d.ts +4 -0
- package/dist/utils/concurrent-map.js +21 -0
- package/dist/utils/detect-format.d.ts +5 -0
- package/dist/utils/detect-format.js +125 -0
- package/dist/utils/extraction-options.d.ts +29 -0
- package/dist/utils/extraction-options.js +33 -0
- package/dist/utils/file-utils.d.ts +33 -0
- package/dist/utils/file-utils.js +267 -0
- package/dist/utils/glossary-match.d.ts +9 -0
- package/dist/utils/glossary-match.js +25 -0
- package/dist/utils/html-extractor.d.ts +8 -0
- package/dist/utils/html-extractor.js +317 -0
- package/dist/utils/i18next-icu.d.ts +38 -0
- package/dist/utils/i18next-icu.js +159 -0
- package/dist/utils/icu-message.d.ts +65 -0
- package/dist/utils/icu-message.js +346 -0
- package/dist/utils/icu-spans.d.ts +10 -0
- package/dist/utils/icu-spans.js +126 -0
- package/dist/utils/jsx-message.d.ts +93 -0
- package/dist/utils/jsx-message.js +628 -0
- package/dist/utils/key-usage.d.ts +35 -0
- package/dist/utils/key-usage.js +247 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/metadata.d.ts +82 -0
- package/dist/utils/metadata.js +261 -0
- package/dist/utils/module-constants.d.ts +30 -0
- package/dist/utils/module-constants.js +265 -0
- package/dist/utils/not-injected.d.ts +6 -0
- package/dist/utils/not-injected.js +38 -0
- package/dist/utils/placeholder-protection.d.ts +74 -0
- package/dist/utils/placeholder-protection.js +181 -0
- package/dist/utils/pseudo-markers.d.ts +9 -0
- package/dist/utils/pseudo-markers.js +16 -0
- package/dist/utils/string-utils.d.ts +52 -0
- package/dist/utils/string-utils.js +270 -0
- package/dist/utils/translation-quality.d.ts +34 -0
- package/dist/utils/translation-quality.js +156 -0
- package/dist/utils/translation-sanitizer.d.ts +11 -0
- package/dist/utils/translation-sanitizer.js +71 -0
- package/dist/utils/translator-caller.d.ts +32 -0
- package/dist/utils/translator-caller.js +217 -0
- package/dist/utils/translator-load-balancer.d.ts +73 -0
- package/dist/utils/translator-load-balancer.js +297 -0
- package/dist/utils/ui-copy.d.ts +32 -0
- package/dist/utils/ui-copy.js +126 -0
- package/dist/utils/untranslated.d.ts +15 -0
- package/dist/utils/untranslated.js +34 -0
- package/dist/utils/workspace.d.ts +14 -0
- package/dist/utils/workspace.js +89 -0
- package/package.json +64 -4
- package/schemas/translate-report.schema.json +297 -0
- package/README.md +0 -3
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildKeysToTranslateByTargetLocale = buildKeysToTranslateByTargetLocale;
|
|
37
|
+
exports.getLockfilePath = getLockfilePath;
|
|
38
|
+
exports.loadLockfile = loadLockfile;
|
|
39
|
+
exports.saveLockfile = saveLockfile;
|
|
40
|
+
exports.clearLockfile = clearLockfile;
|
|
41
|
+
exports.removeLocaleFromLockfile = removeLocaleFromLockfile;
|
|
42
|
+
exports.glossaryChanged = glossaryChanged;
|
|
43
|
+
exports.isStalePseudoEntry = isStalePseudoEntry;
|
|
44
|
+
exports.partitionKeys = partitionKeys;
|
|
45
|
+
exports.dropFailedTranslations = dropFailedTranslations;
|
|
46
|
+
exports.lockfileChanged = lockfileChanged;
|
|
47
|
+
exports.updateLockfile = updateLockfile;
|
|
48
|
+
exports.collectCachedTranslations = collectCachedTranslations;
|
|
49
|
+
exports.computeLockfileStats = computeLockfileStats;
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const pseudo_markers_1 = require("../../utils/pseudo-markers");
|
|
52
|
+
const fs = __importStar(require("fs-extra"));
|
|
53
|
+
const logger_1 = require("../../utils/logger");
|
|
54
|
+
const hasher_1 = require("./hasher");
|
|
55
|
+
const schema_1 = require("./schema");
|
|
56
|
+
const LOCK_RETRIES = 20;
|
|
57
|
+
const LOCK_RETRY_DELAY_MS = 50;
|
|
58
|
+
async function acquireLock(lockDir) {
|
|
59
|
+
let lastErr = null;
|
|
60
|
+
for (let i = 0; i < LOCK_RETRIES; i++) {
|
|
61
|
+
try {
|
|
62
|
+
await fs.mkdir(lockDir, { recursive: false });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
lastErr = err;
|
|
67
|
+
if (err.code !== 'EEXIST')
|
|
68
|
+
throw err;
|
|
69
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAY_MS));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Could not acquire lockfile lock after ${LOCK_RETRIES} retries: ${lastErr?.message}`);
|
|
73
|
+
}
|
|
74
|
+
async function releaseLock(lockDir) {
|
|
75
|
+
try {
|
|
76
|
+
await fs.rmdir(lockDir);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Best-effort cleanup
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* For each target locale, list of translation keys that must go through the translator
|
|
84
|
+
* (new keys, source-changed keys, or keys missing that locale in the lockfile).
|
|
85
|
+
*/
|
|
86
|
+
function buildKeysToTranslateByTargetLocale(partition, targetLocales) {
|
|
87
|
+
const byLocale = {};
|
|
88
|
+
for (const loc of targetLocales) {
|
|
89
|
+
byLocale[loc] = new Set();
|
|
90
|
+
}
|
|
91
|
+
for (const { key } of partition.newKeys) {
|
|
92
|
+
for (const loc of targetLocales) {
|
|
93
|
+
byLocale[loc].add(key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const { key } of partition.changed) {
|
|
97
|
+
for (const loc of targetLocales) {
|
|
98
|
+
byLocale[loc].add(key);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const { key, missingLocales } of partition.missingLocales) {
|
|
102
|
+
for (const loc of missingLocales) {
|
|
103
|
+
if (byLocale[loc])
|
|
104
|
+
byLocale[loc].add(key);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const result = {};
|
|
108
|
+
for (const loc of targetLocales) {
|
|
109
|
+
result[loc] = Array.from(byLocale[loc]);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
// ─────────────────────────────────────────────────────────────
|
|
114
|
+
// Path helpers
|
|
115
|
+
// ─────────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Resolve the lockfile path for a project:
|
|
118
|
+
* `<dataDir>/.babelize/{projectId}/babelize-lock.json`. `dataDir` defaults to
|
|
119
|
+
* $BABELIZE_DATA_DIR, else `process.cwd()` (the cloud backend). Local runs
|
|
120
|
+
* pass it explicitly so concurrent runs in one process never share it.
|
|
121
|
+
*/
|
|
122
|
+
function getLockfilePath(projectId, dataDir) {
|
|
123
|
+
const baseDir = dataDir || process.env.BABELIZE_DATA_DIR || process.cwd();
|
|
124
|
+
return path.join(baseDir, '.babelize', projectId, 'babelize-lock.json');
|
|
125
|
+
}
|
|
126
|
+
// ─────────────────────────────────────────────────────────────
|
|
127
|
+
// Load
|
|
128
|
+
// ─────────────────────────────────────────────────────────────
|
|
129
|
+
/**
|
|
130
|
+
* Load and validate the lockfile from disk.
|
|
131
|
+
* Returns an empty lockfile if the file doesn't exist or is malformed.
|
|
132
|
+
*/
|
|
133
|
+
async function loadLockfile(projectId, dataDir) {
|
|
134
|
+
const lockfilePath = getLockfilePath(projectId, dataDir);
|
|
135
|
+
try {
|
|
136
|
+
if (!(await fs.pathExists(lockfilePath))) {
|
|
137
|
+
logger_1.engineLogger.info('No existing lockfile found, starting fresh', { projectId });
|
|
138
|
+
return (0, schema_1.createEmptyLockfile)(projectId);
|
|
139
|
+
}
|
|
140
|
+
const raw = await fs.readJson(lockfilePath);
|
|
141
|
+
const parsed = schema_1.lockfileDataSchema.safeParse(raw);
|
|
142
|
+
if (!parsed.success) {
|
|
143
|
+
logger_1.engineLogger.warn('Lockfile validation failed, starting fresh', {
|
|
144
|
+
projectId,
|
|
145
|
+
errors: parsed.error.issues,
|
|
146
|
+
});
|
|
147
|
+
return (0, schema_1.createEmptyLockfile)(projectId);
|
|
148
|
+
}
|
|
149
|
+
logger_1.engineLogger.info('Loaded lockfile', {
|
|
150
|
+
projectId,
|
|
151
|
+
keyCount: Object.keys(parsed.data.keys).length,
|
|
152
|
+
});
|
|
153
|
+
return parsed.data;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
logger_1.engineLogger.warn('Failed to read lockfile, starting fresh', {
|
|
157
|
+
projectId,
|
|
158
|
+
error: err instanceof Error ? err.message : String(err),
|
|
159
|
+
});
|
|
160
|
+
return (0, schema_1.createEmptyLockfile)(projectId);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// ─────────────────────────────────────────────────────────────
|
|
164
|
+
// Save
|
|
165
|
+
// ─────────────────────────────────────────────────────────────
|
|
166
|
+
/**
|
|
167
|
+
* Persist lockfile to disk atomically (write→tmp→rename).
|
|
168
|
+
* Uses advisory file locking via atomic mkdir to prevent concurrent writes
|
|
169
|
+
* from overwriting each other (two simultaneous runs for the same project).
|
|
170
|
+
*/
|
|
171
|
+
async function saveLockfile(projectId, data, dataDir) {
|
|
172
|
+
const lockfilePath = getLockfilePath(projectId, dataDir);
|
|
173
|
+
const lockDir = lockfilePath + '.lock';
|
|
174
|
+
const tmpPath = lockfilePath + '.tmp';
|
|
175
|
+
await fs.ensureDir(path.dirname(lockfilePath));
|
|
176
|
+
await acquireLock(lockDir);
|
|
177
|
+
try {
|
|
178
|
+
// Re-read the current lockfile and merge any keys written by a concurrent run
|
|
179
|
+
// between when we loaded data and now.
|
|
180
|
+
const merged = data;
|
|
181
|
+
try {
|
|
182
|
+
if (await fs.pathExists(lockfilePath)) {
|
|
183
|
+
const current = await fs.readJson(lockfilePath);
|
|
184
|
+
for (const [key, entry] of Object.entries(current.keys || {})) {
|
|
185
|
+
if (!merged.keys[key]) {
|
|
186
|
+
merged.keys[key] = entry;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (readErr) {
|
|
192
|
+
logger_1.engineLogger.warn('Failed to re-read lockfile for merge, proceeding with current data', {
|
|
193
|
+
projectId,
|
|
194
|
+
error: readErr instanceof Error ? readErr.message : String(readErr),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
merged.updatedAt = new Date().toISOString();
|
|
198
|
+
await fs.writeJson(tmpPath, merged, { spaces: 2 });
|
|
199
|
+
await fs.rename(tmpPath, lockfilePath);
|
|
200
|
+
logger_1.engineLogger.info('Saved lockfile', {
|
|
201
|
+
projectId,
|
|
202
|
+
keyCount: Object.keys(merged.keys).length,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
logger_1.engineLogger.error('Failed to save lockfile', {
|
|
207
|
+
projectId,
|
|
208
|
+
error: err instanceof Error ? err.message : String(err),
|
|
209
|
+
});
|
|
210
|
+
await fs.remove(tmpPath).catch((cleanupErr) => {
|
|
211
|
+
logger_1.engineLogger.warn('Failed to clean up temp lockfile', { tmpPath, error: cleanupErr });
|
|
212
|
+
});
|
|
213
|
+
throw err;
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
await releaseLock(lockDir);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// ─────────────────────────────────────────────────────────────
|
|
220
|
+
// Clear (force re-translate)
|
|
221
|
+
// ─────────────────────────────────────────────────────────────
|
|
222
|
+
/**
|
|
223
|
+
* Delete the lockfile for a project (used by "Force full re-translate").
|
|
224
|
+
*/
|
|
225
|
+
async function clearLockfile(projectId, dataDir) {
|
|
226
|
+
const lockfilePath = getLockfilePath(projectId, dataDir);
|
|
227
|
+
try {
|
|
228
|
+
if (await fs.pathExists(lockfilePath)) {
|
|
229
|
+
await fs.remove(lockfilePath);
|
|
230
|
+
logger_1.engineLogger.info('Cleared lockfile', { projectId });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
logger_1.engineLogger.warn('Failed to clear lockfile', {
|
|
235
|
+
projectId,
|
|
236
|
+
error: err instanceof Error ? err.message : String(err),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Purge a specific locale's translations from the lockfile.
|
|
242
|
+
* Used when a language is removed from a project so re-adding it
|
|
243
|
+
* triggers fresh translation instead of resurrecting stale cache.
|
|
244
|
+
*/
|
|
245
|
+
async function removeLocaleFromLockfile(projectId, localeCode, dataDir) {
|
|
246
|
+
const lockfilePath = getLockfilePath(projectId, dataDir);
|
|
247
|
+
try {
|
|
248
|
+
if (!(await fs.pathExists(lockfilePath))) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const lockfile = await loadLockfile(projectId, dataDir);
|
|
252
|
+
let removedCount = 0;
|
|
253
|
+
for (const [key, entry] of Object.entries(lockfile.keys)) {
|
|
254
|
+
if (localeCode in entry.languages) {
|
|
255
|
+
delete entry.languages[localeCode];
|
|
256
|
+
removedCount++;
|
|
257
|
+
}
|
|
258
|
+
if (entry.metadata && localeCode in entry.metadata) {
|
|
259
|
+
delete entry.metadata[localeCode];
|
|
260
|
+
}
|
|
261
|
+
if (Object.keys(entry.languages).length === 0) {
|
|
262
|
+
delete lockfile.keys[key];
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (removedCount > 0) {
|
|
266
|
+
lockfile.updatedAt = new Date().toISOString();
|
|
267
|
+
// Write directly (bypass saveLockfile's merge logic which would
|
|
268
|
+
// re-add deleted keys from the on-disk file)
|
|
269
|
+
const lockDir = lockfilePath + '.lock';
|
|
270
|
+
const tmpPath = lockfilePath + '.tmp';
|
|
271
|
+
await fs.ensureDir(path.dirname(lockfilePath));
|
|
272
|
+
await acquireLock(lockDir);
|
|
273
|
+
try {
|
|
274
|
+
await fs.writeJson(tmpPath, lockfile, { spaces: 2 });
|
|
275
|
+
await fs.rename(tmpPath, lockfilePath);
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
await releaseLock(lockDir);
|
|
279
|
+
}
|
|
280
|
+
logger_1.engineLogger.info('Purged locale from lockfile', {
|
|
281
|
+
projectId,
|
|
282
|
+
localeCode,
|
|
283
|
+
keysAffected: removedCount,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
logger_1.engineLogger.warn('Failed to purge locale from lockfile', {
|
|
289
|
+
projectId,
|
|
290
|
+
localeCode,
|
|
291
|
+
error: err instanceof Error ? err.message : String(err),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/** True when the glossary rules for this key and locale changed since its translation was stored. */
|
|
296
|
+
function glossaryChanged(entry, source, locale, options = {}) {
|
|
297
|
+
if (!options.glossaryHash)
|
|
298
|
+
return false;
|
|
299
|
+
const meta = entry.metadata?.[locale];
|
|
300
|
+
if (meta?.external)
|
|
301
|
+
return false;
|
|
302
|
+
return (meta?.glossaryHash ?? '') !== options.glossaryHash(source, locale);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* True when the stored value for `locale` is pseudo-localization and this run
|
|
306
|
+
* is a real one: it must be translated, never reused. Recognised by the
|
|
307
|
+
* `pseudo` metadata flag, or by the pseudo markers for lockfiles written
|
|
308
|
+
* before the flag existed.
|
|
309
|
+
*/
|
|
310
|
+
function isStalePseudoEntry(entry, locale, options = {}) {
|
|
311
|
+
if (options.pseudo)
|
|
312
|
+
return false;
|
|
313
|
+
return entry.metadata?.[locale]?.pseudo === true || (0, pseudo_markers_1.isPseudoLocalized)(entry.languages[locale] ?? '');
|
|
314
|
+
}
|
|
315
|
+
function partitionKeys(lockfile, keys, targetLocales, options = {}) {
|
|
316
|
+
const result = {
|
|
317
|
+
unchanged: [],
|
|
318
|
+
newKeys: [],
|
|
319
|
+
changed: [],
|
|
320
|
+
missingLocales: [],
|
|
321
|
+
};
|
|
322
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
323
|
+
const currentHash = (0, hasher_1.hashSourceString)(value);
|
|
324
|
+
const entry = lockfile.keys[key];
|
|
325
|
+
if (!entry) {
|
|
326
|
+
// Key not seen before
|
|
327
|
+
result.newKeys.push({ key, value });
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (entry.sourceHash !== currentHash) {
|
|
331
|
+
// Source string changed
|
|
332
|
+
result.changed.push({ key, value });
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
// Hash matches — check if all target locales are present
|
|
336
|
+
const missing = targetLocales.filter((locale) => !(locale in entry.languages) || isStalePseudoEntry(entry, locale, options) ||
|
|
337
|
+
glossaryChanged(entry, value, locale, options));
|
|
338
|
+
if (missing.length === 0) {
|
|
339
|
+
// Fully translated and unchanged
|
|
340
|
+
result.unchanged.push({ key, value });
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Some locales missing
|
|
344
|
+
result.missingLocales.push({ key, value, missingLocales: missing });
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
// ─────────────────────────────────────────────────────────────
|
|
350
|
+
// Update lockfile with new translations
|
|
351
|
+
// ─────────────────────────────────────────────────────────────
|
|
352
|
+
/**
|
|
353
|
+
* Merge newly translated values and metadata into the lockfile.
|
|
354
|
+
*
|
|
355
|
+
* @param lockfile - Current lockfile (will be mutated)
|
|
356
|
+
* @param keys - Flat map of key → source value (for hashing)
|
|
357
|
+
* @param translations - Map of locale → { key → translated string }
|
|
358
|
+
* @param projectId - Project identifier
|
|
359
|
+
* @param metadata - Map of locale → { key → metadata object }
|
|
360
|
+
*/
|
|
361
|
+
/**
|
|
362
|
+
* Remove the stored translation of every key that failed this run.
|
|
363
|
+
* updateLockfile records the new source fingerprint for every key and keeps
|
|
364
|
+
* the previous translation where a locale produced none — for a key whose
|
|
365
|
+
* source text changed and then failed, that previous value translates the
|
|
366
|
+
* *old* text. Dropping it makes the next run treat the locale as missing and
|
|
367
|
+
* retry it, instead of silently reusing a stale translation.
|
|
368
|
+
*/
|
|
369
|
+
function dropFailedTranslations(lockfile, failures) {
|
|
370
|
+
if (failures.length === 0)
|
|
371
|
+
return lockfile;
|
|
372
|
+
const cloned = JSON.parse(JSON.stringify(lockfile));
|
|
373
|
+
for (const { key, locale } of failures) {
|
|
374
|
+
const entry = cloned.keys[key];
|
|
375
|
+
if (entry?.languages)
|
|
376
|
+
delete entry.languages[locale];
|
|
377
|
+
if (entry?.metadata)
|
|
378
|
+
delete entry.metadata[locale];
|
|
379
|
+
}
|
|
380
|
+
return cloned;
|
|
381
|
+
}
|
|
382
|
+
/** The metadata fields the lockfile schema keeps (anything else is dropped on load anyway). */
|
|
383
|
+
function pickLockfileMetadata(meta) {
|
|
384
|
+
const out = {};
|
|
385
|
+
if (meta.pseudo === true)
|
|
386
|
+
out.pseudo = true;
|
|
387
|
+
if (typeof meta.fromMemory === 'boolean')
|
|
388
|
+
out.fromMemory = meta.fromMemory;
|
|
389
|
+
if (typeof meta.sourceHash === 'string')
|
|
390
|
+
out.sourceHash = meta.sourceHash;
|
|
391
|
+
if (typeof meta.inTm === 'boolean')
|
|
392
|
+
out.inTm = meta.inTm;
|
|
393
|
+
if (typeof meta.glossaryHash === 'string' && meta.glossaryHash)
|
|
394
|
+
out.glossaryHash = meta.glossaryHash;
|
|
395
|
+
if (meta.external === true)
|
|
396
|
+
out.external = true;
|
|
397
|
+
return out;
|
|
398
|
+
}
|
|
399
|
+
function sameJson(a, b) {
|
|
400
|
+
return JSON.stringify(a ?? {}) === JSON.stringify(b ?? {});
|
|
401
|
+
}
|
|
402
|
+
/** True when `next` differs from `previous` in anything but timestamps worth saving. */
|
|
403
|
+
function lockfileChanged(previous, next) {
|
|
404
|
+
return previous.projectId !== next.projectId || JSON.stringify(previous.keys) !== JSON.stringify(next.keys);
|
|
405
|
+
}
|
|
406
|
+
function updateLockfile(lockfile, keys, translations, projectId, metadata) {
|
|
407
|
+
// Deep-clone to prevent in-place mutation of the caller's reference.
|
|
408
|
+
// Prevents races when multiple concurrent runs share the same lockfile object.
|
|
409
|
+
const cloned = JSON.parse(JSON.stringify(lockfile));
|
|
410
|
+
const now = new Date().toISOString();
|
|
411
|
+
cloned.projectId = projectId;
|
|
412
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
413
|
+
const sourceHash = (0, hasher_1.hashSourceString)(value);
|
|
414
|
+
const existing = cloned.keys[key];
|
|
415
|
+
// An entry is only touched (and re-stamped) when something in it
|
|
416
|
+
// actually changes, so a no-change rerun leaves the committed
|
|
417
|
+
// lockfile byte-identical.
|
|
418
|
+
const entry = existing ?? { sourceHash, translatedAt: now, languages: {}, metadata: {} };
|
|
419
|
+
let changed = !existing || existing.sourceHash !== sourceHash;
|
|
420
|
+
entry.sourceHash = sourceHash;
|
|
421
|
+
// Merge per-locale translations and metadata
|
|
422
|
+
for (const [locale, translatedKeys] of Object.entries(translations)) {
|
|
423
|
+
if (translatedKeys[key]) {
|
|
424
|
+
// Skip caching identity translations (same as source text) for non-trivial strings.
|
|
425
|
+
// Identity translations mean the translator fell back to source text
|
|
426
|
+
// (e.g., because the service was unavailable). Caching them would cause
|
|
427
|
+
// subsequent runs to skip re-translation and produce fake locale files.
|
|
428
|
+
const translated = translatedKeys[key];
|
|
429
|
+
if (translated === value && value.length > 4) {
|
|
430
|
+
logger_1.engineLogger.warn(`Skipping cache for "${key}" → "${locale}": identity translation (same as source). Translator may be unavailable.`);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (entry.languages[locale] !== translated) {
|
|
434
|
+
entry.languages[locale] = translated;
|
|
435
|
+
changed = true;
|
|
436
|
+
}
|
|
437
|
+
// Metadata from this run replaces the previous run's rather than
|
|
438
|
+
// merging into it, so a stale `fromMemory: true` doesn't survive
|
|
439
|
+
// the key being re-translated. Keys reused from the lockfile
|
|
440
|
+
// carry no new metadata and keep what they had.
|
|
441
|
+
if (metadata?.[locale]?.[key]) {
|
|
442
|
+
const next = pickLockfileMetadata(metadata[locale][key]);
|
|
443
|
+
if (!sameJson(entry.metadata?.[locale], next)) {
|
|
444
|
+
if (!entry.metadata)
|
|
445
|
+
entry.metadata = {};
|
|
446
|
+
entry.metadata[locale] = next;
|
|
447
|
+
changed = true;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (changed) {
|
|
453
|
+
entry.translatedAt = now;
|
|
454
|
+
if (!entry.metadata)
|
|
455
|
+
entry.metadata = {};
|
|
456
|
+
cloned.keys[key] = entry;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return cloned;
|
|
460
|
+
}
|
|
461
|
+
// ─────────────────────────────────────────────────────────────
|
|
462
|
+
// Collect cached translations from lockfile
|
|
463
|
+
// ─────────────────────────────────────────────────────────────
|
|
464
|
+
/**
|
|
465
|
+
* Extract cached translations for unchanged keys from the lockfile.
|
|
466
|
+
*
|
|
467
|
+
* @returns Map of locale → { key → translated string }
|
|
468
|
+
*/
|
|
469
|
+
function collectCachedTranslations(lockfile, unchangedKeys, targetLocales, options = {}) {
|
|
470
|
+
const cached = {};
|
|
471
|
+
for (const locale of targetLocales) {
|
|
472
|
+
cached[locale] = {};
|
|
473
|
+
}
|
|
474
|
+
for (const { key } of unchangedKeys) {
|
|
475
|
+
const entry = lockfile.keys[key];
|
|
476
|
+
if (!entry)
|
|
477
|
+
continue;
|
|
478
|
+
for (const locale of targetLocales) {
|
|
479
|
+
if (entry.languages[locale] && !isStalePseudoEntry(entry, locale, options)) {
|
|
480
|
+
cached[locale][key] = entry.languages[locale];
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return cached;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Compute stats for the UI: X translated, Y reused, Z changed.
|
|
488
|
+
*/
|
|
489
|
+
function computeLockfileStats(partition) {
|
|
490
|
+
return {
|
|
491
|
+
reused: partition.unchanged.length,
|
|
492
|
+
translated: partition.newKeys.length +
|
|
493
|
+
partition.changed.length +
|
|
494
|
+
partition.missingLocales.length,
|
|
495
|
+
changed: partition.changed.length,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const lockfileEntrySchema: z.ZodObject<{
|
|
3
|
+
/** SHA-256 hex digest of the source string */
|
|
4
|
+
sourceHash: z.ZodString;
|
|
5
|
+
/** ISO-8601 timestamp of last translation */
|
|
6
|
+
translatedAt: z.ZodString;
|
|
7
|
+
/** Translated values keyed by locale code */
|
|
8
|
+
languages: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
9
|
+
/** Metadata per locale */
|
|
10
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
11
|
+
fromMemory: z.ZodOptional<z.ZodBoolean>;
|
|
12
|
+
sourceHash: z.ZodOptional<z.ZodString>;
|
|
13
|
+
/** This key's translation for the locale is known to be in TM; skip backfill. */
|
|
14
|
+
inTm: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
/** Pseudo-localized (`--provider pseudo`), not a translation: any real run re-translates it. */
|
|
16
|
+
pseudo: z.ZodOptional<z.ZodBoolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Fingerprint of the glossary rules that applied to this key's source
|
|
19
|
+
* for the locale (see glossaryFingerprint). Absent: none applied.
|
|
20
|
+
* A run whose rules give a different fingerprint re-translates it.
|
|
21
|
+
*/
|
|
22
|
+
glossaryHash: z.ZodOptional<z.ZodString>;
|
|
23
|
+
/**
|
|
24
|
+
* The translation came from the project's own files (e.g. a .po
|
|
25
|
+
* catalogue), not from Babelize: glossary edits don't overwrite it.
|
|
26
|
+
*/
|
|
27
|
+
external: z.ZodOptional<z.ZodBoolean>;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
sourceHash?: string | undefined;
|
|
30
|
+
fromMemory?: boolean | undefined;
|
|
31
|
+
inTm?: boolean | undefined;
|
|
32
|
+
pseudo?: boolean | undefined;
|
|
33
|
+
glossaryHash?: string | undefined;
|
|
34
|
+
external?: boolean | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
sourceHash?: string | undefined;
|
|
37
|
+
fromMemory?: boolean | undefined;
|
|
38
|
+
inTm?: boolean | undefined;
|
|
39
|
+
pseudo?: boolean | undefined;
|
|
40
|
+
glossaryHash?: string | undefined;
|
|
41
|
+
external?: boolean | undefined;
|
|
42
|
+
}>>>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
sourceHash: string;
|
|
45
|
+
translatedAt: string;
|
|
46
|
+
languages: Record<string, string>;
|
|
47
|
+
metadata?: Record<string, {
|
|
48
|
+
sourceHash?: string | undefined;
|
|
49
|
+
fromMemory?: boolean | undefined;
|
|
50
|
+
inTm?: boolean | undefined;
|
|
51
|
+
pseudo?: boolean | undefined;
|
|
52
|
+
glossaryHash?: string | undefined;
|
|
53
|
+
external?: boolean | undefined;
|
|
54
|
+
}> | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
sourceHash: string;
|
|
57
|
+
translatedAt: string;
|
|
58
|
+
languages: Record<string, string>;
|
|
59
|
+
metadata?: Record<string, {
|
|
60
|
+
sourceHash?: string | undefined;
|
|
61
|
+
fromMemory?: boolean | undefined;
|
|
62
|
+
inTm?: boolean | undefined;
|
|
63
|
+
pseudo?: boolean | undefined;
|
|
64
|
+
glossaryHash?: string | undefined;
|
|
65
|
+
external?: boolean | undefined;
|
|
66
|
+
}> | undefined;
|
|
67
|
+
}>;
|
|
68
|
+
export type LockfileEntry = z.infer<typeof lockfileEntrySchema>;
|
|
69
|
+
export declare const lockfileDataSchema: z.ZodObject<{
|
|
70
|
+
version: z.ZodDefault<z.ZodString>;
|
|
71
|
+
projectId: z.ZodString;
|
|
72
|
+
updatedAt: z.ZodString;
|
|
73
|
+
keys: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
74
|
+
/** SHA-256 hex digest of the source string */
|
|
75
|
+
sourceHash: z.ZodString;
|
|
76
|
+
/** ISO-8601 timestamp of last translation */
|
|
77
|
+
translatedAt: z.ZodString;
|
|
78
|
+
/** Translated values keyed by locale code */
|
|
79
|
+
languages: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
80
|
+
/** Metadata per locale */
|
|
81
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
82
|
+
fromMemory: z.ZodOptional<z.ZodBoolean>;
|
|
83
|
+
sourceHash: z.ZodOptional<z.ZodString>;
|
|
84
|
+
/** This key's translation for the locale is known to be in TM; skip backfill. */
|
|
85
|
+
inTm: z.ZodOptional<z.ZodBoolean>;
|
|
86
|
+
/** Pseudo-localized (`--provider pseudo`), not a translation: any real run re-translates it. */
|
|
87
|
+
pseudo: z.ZodOptional<z.ZodBoolean>;
|
|
88
|
+
/**
|
|
89
|
+
* Fingerprint of the glossary rules that applied to this key's source
|
|
90
|
+
* for the locale (see glossaryFingerprint). Absent: none applied.
|
|
91
|
+
* A run whose rules give a different fingerprint re-translates it.
|
|
92
|
+
*/
|
|
93
|
+
glossaryHash: z.ZodOptional<z.ZodString>;
|
|
94
|
+
/**
|
|
95
|
+
* The translation came from the project's own files (e.g. a .po
|
|
96
|
+
* catalogue), not from Babelize: glossary edits don't overwrite it.
|
|
97
|
+
*/
|
|
98
|
+
external: z.ZodOptional<z.ZodBoolean>;
|
|
99
|
+
}, "strip", z.ZodTypeAny, {
|
|
100
|
+
sourceHash?: string | undefined;
|
|
101
|
+
fromMemory?: boolean | undefined;
|
|
102
|
+
inTm?: boolean | undefined;
|
|
103
|
+
pseudo?: boolean | undefined;
|
|
104
|
+
glossaryHash?: string | undefined;
|
|
105
|
+
external?: boolean | undefined;
|
|
106
|
+
}, {
|
|
107
|
+
sourceHash?: string | undefined;
|
|
108
|
+
fromMemory?: boolean | undefined;
|
|
109
|
+
inTm?: boolean | undefined;
|
|
110
|
+
pseudo?: boolean | undefined;
|
|
111
|
+
glossaryHash?: string | undefined;
|
|
112
|
+
external?: boolean | undefined;
|
|
113
|
+
}>>>;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
sourceHash: string;
|
|
116
|
+
translatedAt: string;
|
|
117
|
+
languages: Record<string, string>;
|
|
118
|
+
metadata?: Record<string, {
|
|
119
|
+
sourceHash?: string | undefined;
|
|
120
|
+
fromMemory?: boolean | undefined;
|
|
121
|
+
inTm?: boolean | undefined;
|
|
122
|
+
pseudo?: boolean | undefined;
|
|
123
|
+
glossaryHash?: string | undefined;
|
|
124
|
+
external?: boolean | undefined;
|
|
125
|
+
}> | undefined;
|
|
126
|
+
}, {
|
|
127
|
+
sourceHash: string;
|
|
128
|
+
translatedAt: string;
|
|
129
|
+
languages: Record<string, string>;
|
|
130
|
+
metadata?: Record<string, {
|
|
131
|
+
sourceHash?: string | undefined;
|
|
132
|
+
fromMemory?: boolean | undefined;
|
|
133
|
+
inTm?: boolean | undefined;
|
|
134
|
+
pseudo?: boolean | undefined;
|
|
135
|
+
glossaryHash?: string | undefined;
|
|
136
|
+
external?: boolean | undefined;
|
|
137
|
+
}> | undefined;
|
|
138
|
+
}>>;
|
|
139
|
+
}, "strip", z.ZodTypeAny, {
|
|
140
|
+
keys: Record<string, {
|
|
141
|
+
sourceHash: string;
|
|
142
|
+
translatedAt: string;
|
|
143
|
+
languages: Record<string, string>;
|
|
144
|
+
metadata?: Record<string, {
|
|
145
|
+
sourceHash?: string | undefined;
|
|
146
|
+
fromMemory?: boolean | undefined;
|
|
147
|
+
inTm?: boolean | undefined;
|
|
148
|
+
pseudo?: boolean | undefined;
|
|
149
|
+
glossaryHash?: string | undefined;
|
|
150
|
+
external?: boolean | undefined;
|
|
151
|
+
}> | undefined;
|
|
152
|
+
}>;
|
|
153
|
+
projectId: string;
|
|
154
|
+
version: string;
|
|
155
|
+
updatedAt: string;
|
|
156
|
+
}, {
|
|
157
|
+
keys: Record<string, {
|
|
158
|
+
sourceHash: string;
|
|
159
|
+
translatedAt: string;
|
|
160
|
+
languages: Record<string, string>;
|
|
161
|
+
metadata?: Record<string, {
|
|
162
|
+
sourceHash?: string | undefined;
|
|
163
|
+
fromMemory?: boolean | undefined;
|
|
164
|
+
inTm?: boolean | undefined;
|
|
165
|
+
pseudo?: boolean | undefined;
|
|
166
|
+
glossaryHash?: string | undefined;
|
|
167
|
+
external?: boolean | undefined;
|
|
168
|
+
}> | undefined;
|
|
169
|
+
}>;
|
|
170
|
+
projectId: string;
|
|
171
|
+
updatedAt: string;
|
|
172
|
+
version?: string | undefined;
|
|
173
|
+
}>;
|
|
174
|
+
export type LockfileData = z.infer<typeof lockfileDataSchema>;
|
|
175
|
+
export interface PartitionedKey {
|
|
176
|
+
key: string;
|
|
177
|
+
value: string;
|
|
178
|
+
}
|
|
179
|
+
export interface LockfilePartition {
|
|
180
|
+
/** Keys whose hash matches and all target locales exist in lockfile */
|
|
181
|
+
unchanged: PartitionedKey[];
|
|
182
|
+
/** Keys not present in lockfile at all */
|
|
183
|
+
newKeys: PartitionedKey[];
|
|
184
|
+
/** Keys whose source hash has changed */
|
|
185
|
+
changed: PartitionedKey[];
|
|
186
|
+
/** Keys whose hash matches but are missing some target locales */
|
|
187
|
+
missingLocales: Array<PartitionedKey & {
|
|
188
|
+
missingLocales: string[];
|
|
189
|
+
}>;
|
|
190
|
+
}
|
|
191
|
+
export interface LockfileStats {
|
|
192
|
+
reused: number;
|
|
193
|
+
translated: number;
|
|
194
|
+
changed: number;
|
|
195
|
+
}
|
|
196
|
+
export declare function createEmptyLockfile(projectId: string): LockfileData;
|