@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,217 @@
|
|
|
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.callTranslator = callTranslator;
|
|
37
|
+
exports.checkTranslatorHealth = checkTranslatorHealth;
|
|
38
|
+
exports.warmupTranslator = warmupTranslator;
|
|
39
|
+
const logger_1 = require("./logger");
|
|
40
|
+
const translation_quality_1 = require("./translation-quality");
|
|
41
|
+
const byok_provider_1 = require("../modules/translator/client/byok.provider");
|
|
42
|
+
/**
|
|
43
|
+
* Translate a batch. Returns translations only for entries the translator
|
|
44
|
+
* actually produced — never the source text in their place. Entries missing
|
|
45
|
+
* from the result are reported as failed keys by the caller.
|
|
46
|
+
*/
|
|
47
|
+
async function callTranslator(entries, sourceLang, targetLang, byokConfig, brandVoice, llmOptions = {}) {
|
|
48
|
+
if (byokConfig) {
|
|
49
|
+
return callByokTranslator(entries, sourceLang, targetLang, byokConfig, brandVoice, llmOptions);
|
|
50
|
+
}
|
|
51
|
+
const outerSignal = llmOptions.signal;
|
|
52
|
+
const { translatorLoadBalancer } = await Promise.resolve().then(() => __importStar(require('./translator-load-balancer')));
|
|
53
|
+
let rustResult = null;
|
|
54
|
+
try {
|
|
55
|
+
rustResult = await translatorLoadBalancer.executeWithFailover(async (translatorUrl) => {
|
|
56
|
+
const response = await fetch(`${translatorUrl}/translate`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'Content-Type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ sourceLang, targetLang, entries }),
|
|
60
|
+
signal: outerSignal ? AbortSignal.any([AbortSignal.timeout(300000), outerSignal]) : AbortSignal.timeout(300000),
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Translation API error: ${response.status} ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const data = await response.json();
|
|
66
|
+
if (data.metadata) {
|
|
67
|
+
logger_1.engineLogger.info('Translation metadata', {
|
|
68
|
+
dictVersion: data.metadata.dictVersion,
|
|
69
|
+
processingTimeMs: data.metadata.processingTimeMs,
|
|
70
|
+
dictionaryHits: data.metadata.dictionaryHits,
|
|
71
|
+
modelTranslations: data.metadata.modelTranslations,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return { translations: data.translations || [], metadata: data.metadata };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
logger_1.engineLogger.warn('Rust translator unavailable, will try HF Inference API fallback', {
|
|
79
|
+
error: err instanceof Error ? err.message : String(err),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const needsFallback = rustResult === null || (rustResult.translations.length > 0 &&
|
|
83
|
+
rustResult.translations.every((t) => {
|
|
84
|
+
const orig = entries.find((e) => e.key === t.key)?.value || '';
|
|
85
|
+
if (orig.length <= 4)
|
|
86
|
+
return false;
|
|
87
|
+
const val = t.value || '';
|
|
88
|
+
if (orig.toLowerCase() === val.toLowerCase())
|
|
89
|
+
return true;
|
|
90
|
+
return /^\s*(?:[a-z]{3}_[A-Za-z]{4})\s+/.test(val);
|
|
91
|
+
}));
|
|
92
|
+
if (needsFallback) {
|
|
93
|
+
logger_1.engineLogger.warn('Rust translator returned untargeted translations or unavailable, falling back to HF Inference API');
|
|
94
|
+
try {
|
|
95
|
+
const hfResults = await callHuggingFaceNLLB(entries, sourceLang, targetLang);
|
|
96
|
+
if (hfResults.translations.length > 0) {
|
|
97
|
+
return { translations: hfResults.translations, metadata: { ...rustResult?.metadata, fallback: 'hf-inference-api' } };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (hfErr) {
|
|
101
|
+
logger_1.engineLogger.error('HF Inference API fallback also failed', { error: hfErr });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (rustResult !== null && rustResult.translations.length > 0) {
|
|
105
|
+
return rustResult;
|
|
106
|
+
}
|
|
107
|
+
// Never pass the source text off as a translation: an empty result makes
|
|
108
|
+
// every key a reported failure, and the next run retries them.
|
|
109
|
+
logger_1.engineLogger.error(`Translator unavailable for ${sourceLang} → ${targetLang}; no translations produced`);
|
|
110
|
+
return { translations: [], metadata: { translatorUnavailable: true } };
|
|
111
|
+
}
|
|
112
|
+
async function callByokTranslator(entries, sourceLang, targetLang, byokConfig, brandVoice, llmOptions = {}) {
|
|
113
|
+
const client = new byok_provider_1.BYOKTranslatorClient(byokConfig);
|
|
114
|
+
const { signal, ...promptOptions } = llmOptions;
|
|
115
|
+
const hfRequest = {
|
|
116
|
+
entries: entries.map((e) => ({ key: e.key, value: e.value, context: e.parentContext, extractionContext: e.extractionContext })),
|
|
117
|
+
sourceLang,
|
|
118
|
+
targetLang,
|
|
119
|
+
brandVoice,
|
|
120
|
+
...promptOptions,
|
|
121
|
+
};
|
|
122
|
+
const result = await client.translate(hfRequest, signal);
|
|
123
|
+
const byKey = new Map(result.translations.map((t) => [t.key, t]));
|
|
124
|
+
const translations = [];
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const t = byKey.get(entry.key);
|
|
127
|
+
if (!t)
|
|
128
|
+
continue;
|
|
129
|
+
const quality = (0, translation_quality_1.validateTranslationQuality)(entry.value, t.value);
|
|
130
|
+
translations.push({
|
|
131
|
+
key: entry.key,
|
|
132
|
+
value: t.value,
|
|
133
|
+
confidence: quality.isValid ? Math.max(0.5, quality.score / 100) : 0.3,
|
|
134
|
+
engine: byokConfig.provider,
|
|
135
|
+
warnings: quality.issues?.map((i) => `${i.type}:${i.severity}`) || [],
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { translations, metadata: result.metadata };
|
|
139
|
+
}
|
|
140
|
+
async function callHuggingFaceNLLB(entries, sourceLang, targetLang) {
|
|
141
|
+
const hfToken = process.env.HF_TOKEN || process.env.HF_API_TOKEN;
|
|
142
|
+
const model = 'facebook/nllb-200-distilled-600M';
|
|
143
|
+
const baseUrl = `https://api-inference.huggingface.co/models/${model}`;
|
|
144
|
+
const translations = [];
|
|
145
|
+
for (const entry of entries) {
|
|
146
|
+
const response = await fetch(baseUrl, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
...(hfToken ? { Authorization: `Bearer ${hfToken}` } : {}),
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
inputs: entry.value,
|
|
154
|
+
parameters: { src_lang: sourceLang, tgt_lang: targetLang },
|
|
155
|
+
}),
|
|
156
|
+
signal: AbortSignal.timeout(60000),
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
const body = await response.text().catch(() => '');
|
|
160
|
+
throw new Error(`HF Inference API error: ${response.status} — ${body.slice(0, 200)}`);
|
|
161
|
+
}
|
|
162
|
+
const data = await response.json();
|
|
163
|
+
const translatedText = data?.[0]?.translation_text;
|
|
164
|
+
if (!translatedText)
|
|
165
|
+
continue;
|
|
166
|
+
translations.push({
|
|
167
|
+
key: entry.key,
|
|
168
|
+
value: translatedText,
|
|
169
|
+
confidence: 0.9,
|
|
170
|
+
engine: 'hf-nllb-200',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return { translations };
|
|
174
|
+
}
|
|
175
|
+
async function checkTranslatorHealth(byokConfig) {
|
|
176
|
+
try {
|
|
177
|
+
if (byokConfig) {
|
|
178
|
+
const client = new byok_provider_1.BYOKTranslatorClient(byokConfig);
|
|
179
|
+
return await client.health();
|
|
180
|
+
}
|
|
181
|
+
const { DirectTranslatorClient } = await Promise.resolve().then(() => __importStar(require('../modules/translator/client/direct-translator.client')));
|
|
182
|
+
const client = new DirectTranslatorClient();
|
|
183
|
+
return await client.health();
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function warmupTranslator() {
|
|
190
|
+
logger_1.engineLogger.info('[Translator] Starting warmup...');
|
|
191
|
+
const MAX_ATTEMPTS = 3;
|
|
192
|
+
const RETRY_DELAY_MS = 15000;
|
|
193
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
194
|
+
try {
|
|
195
|
+
logger_1.engineLogger.info(`[Translator] Warmup attempt ${attempt}/${MAX_ATTEMPTS}...`);
|
|
196
|
+
const { DirectTranslatorClient } = await Promise.resolve().then(() => __importStar(require('../modules/translator/client/direct-translator.client')));
|
|
197
|
+
const client = new DirectTranslatorClient();
|
|
198
|
+
const ok = await client.warmup();
|
|
199
|
+
if (ok) {
|
|
200
|
+
logger_1.engineLogger.info('[Translator] Warmup successful');
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
logger_1.engineLogger.warn(`[Translator] Warmup attempt ${attempt} failed`);
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
logger_1.engineLogger.warn(`[Translator] Warmup attempt ${attempt} failed:`, {
|
|
207
|
+
error: err instanceof Error ? err.message : String(err),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (attempt < MAX_ATTEMPTS) {
|
|
211
|
+
logger_1.engineLogger.info(`[Translator] Retrying warmup in ${RETRY_DELAY_MS / 1000}s...`);
|
|
212
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
logger_1.engineLogger.warn('[Translator] All warmup attempts failed — running cold');
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translator Load Balancer
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple translator endpoints with:
|
|
5
|
+
* - Health checking
|
|
6
|
+
* - Round-robin load balancing
|
|
7
|
+
* - Automatic failover on errors
|
|
8
|
+
* - Circuit breaker per endpoint
|
|
9
|
+
*/
|
|
10
|
+
export interface TranslatorEndpoint {
|
|
11
|
+
url: string;
|
|
12
|
+
name: string;
|
|
13
|
+
priority: number;
|
|
14
|
+
isHealthy: boolean;
|
|
15
|
+
lastHealthCheck: Date | null;
|
|
16
|
+
consecutiveFailures: number;
|
|
17
|
+
}
|
|
18
|
+
declare class TranslatorLoadBalancer {
|
|
19
|
+
private config;
|
|
20
|
+
private circuitBreakers;
|
|
21
|
+
private healthCheckInterval;
|
|
22
|
+
private currentEndpointIndex;
|
|
23
|
+
constructor();
|
|
24
|
+
/**
|
|
25
|
+
* Initialize endpoints from environment variable
|
|
26
|
+
* Format: url1|name1,url2|name2 or just url1,url2
|
|
27
|
+
*/
|
|
28
|
+
private initializeEndpoints;
|
|
29
|
+
/**
|
|
30
|
+
* Get the next healthy endpoint using weighted round-robin
|
|
31
|
+
*/
|
|
32
|
+
getNextEndpoint(): TranslatorEndpoint | null;
|
|
33
|
+
/**
|
|
34
|
+
* Mark an endpoint as failed
|
|
35
|
+
*/
|
|
36
|
+
markEndpointFailed(endpoint: TranslatorEndpoint): void;
|
|
37
|
+
/**
|
|
38
|
+
* Mark an endpoint as successful
|
|
39
|
+
*/
|
|
40
|
+
markEndpointSuccess(endpoint: TranslatorEndpoint): void;
|
|
41
|
+
/**
|
|
42
|
+
* Execute a translation request with automatic failover and exponential backoff
|
|
43
|
+
*/
|
|
44
|
+
executeWithFailover<T>(requestFn: (url: string) => Promise<T>): Promise<T>;
|
|
45
|
+
/**
|
|
46
|
+
* Sleep utility
|
|
47
|
+
*/
|
|
48
|
+
private sleep;
|
|
49
|
+
/**
|
|
50
|
+
* Perform health check on all endpoints
|
|
51
|
+
*/
|
|
52
|
+
healthCheck(): Promise<Record<string, boolean>>;
|
|
53
|
+
/**
|
|
54
|
+
* Start periodic health checks
|
|
55
|
+
*/
|
|
56
|
+
startHealthChecks(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Stop periodic health checks
|
|
59
|
+
*/
|
|
60
|
+
stopHealthChecks(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Get current status of all endpoints
|
|
63
|
+
*/
|
|
64
|
+
getStatus(): Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
url: string;
|
|
67
|
+
isHealthy: boolean;
|
|
68
|
+
failures: number;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
71
|
+
export declare function getTranslatorLoadBalancer(): TranslatorLoadBalancer;
|
|
72
|
+
export declare const translatorLoadBalancer: TranslatorLoadBalancer;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Translator Load Balancer
|
|
4
|
+
*
|
|
5
|
+
* Manages multiple translator endpoints with:
|
|
6
|
+
* - Health checking
|
|
7
|
+
* - Round-robin load balancing
|
|
8
|
+
* - Automatic failover on errors
|
|
9
|
+
* - Circuit breaker per endpoint
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.translatorLoadBalancer = void 0;
|
|
13
|
+
exports.getTranslatorLoadBalancer = getTranslatorLoadBalancer;
|
|
14
|
+
const circuit_breaker_1 = require("./circuit-breaker");
|
|
15
|
+
const logger_1 = require("./logger");
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
endpoints: [],
|
|
18
|
+
healthCheckInterval: 60000, // 1 minute
|
|
19
|
+
healthCheckTimeout: 10000, // 10 seconds
|
|
20
|
+
maxConsecutiveFailures: 3,
|
|
21
|
+
};
|
|
22
|
+
class TranslatorLoadBalancer {
|
|
23
|
+
constructor() {
|
|
24
|
+
this.circuitBreakers = new Map();
|
|
25
|
+
this.healthCheckInterval = null;
|
|
26
|
+
this.currentEndpointIndex = 0;
|
|
27
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
28
|
+
this.initializeEndpoints();
|
|
29
|
+
if (this.config.endpoints.length > 0) {
|
|
30
|
+
setTimeout(() => this.startHealthChecks(), 0).unref?.();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Initialize endpoints from environment variable
|
|
35
|
+
* Format: url1|name1,url2|name2 or just url1,url2
|
|
36
|
+
*/
|
|
37
|
+
initializeEndpoints() {
|
|
38
|
+
const translatorUrl = process.env.TRANSLATOR_URL;
|
|
39
|
+
const backupUrl = process.env.TRANSLATOR_BACKUP_URL;
|
|
40
|
+
const endpoints = [];
|
|
41
|
+
// Primary endpoint
|
|
42
|
+
if (translatorUrl) {
|
|
43
|
+
endpoints.push({
|
|
44
|
+
url: translatorUrl,
|
|
45
|
+
name: 'primary',
|
|
46
|
+
priority: 1,
|
|
47
|
+
isHealthy: true,
|
|
48
|
+
lastHealthCheck: null,
|
|
49
|
+
consecutiveFailures: 0,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Backup endpoint
|
|
53
|
+
if (backupUrl) {
|
|
54
|
+
endpoints.push({
|
|
55
|
+
url: backupUrl,
|
|
56
|
+
name: 'backup',
|
|
57
|
+
priority: 2,
|
|
58
|
+
isHealthy: true,
|
|
59
|
+
lastHealthCheck: null,
|
|
60
|
+
consecutiveFailures: 0,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Parse additional endpoints from TRANSLATOR_ENDPOINTS env var
|
|
64
|
+
// Format: url1|name1,url2|name2
|
|
65
|
+
const additionalEndpoints = process.env.TRANSLATOR_ENDPOINTS;
|
|
66
|
+
if (additionalEndpoints) {
|
|
67
|
+
additionalEndpoints.split(',').forEach((entry, index) => {
|
|
68
|
+
const [url, name] = entry.trim().split('|');
|
|
69
|
+
if (url) {
|
|
70
|
+
endpoints.push({
|
|
71
|
+
url: url.trim(),
|
|
72
|
+
name: name?.trim() || `endpoint-${index + 3}`,
|
|
73
|
+
priority: index + 3,
|
|
74
|
+
isHealthy: true,
|
|
75
|
+
lastHealthCheck: null,
|
|
76
|
+
consecutiveFailures: 0,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
this.config.endpoints = endpoints;
|
|
82
|
+
// Create circuit breaker for each endpoint
|
|
83
|
+
endpoints.forEach(endpoint => {
|
|
84
|
+
this.circuitBreakers.set(endpoint.name, (0, circuit_breaker_1.getCircuitBreaker)(`translator-${endpoint.name}`, {
|
|
85
|
+
failureThreshold: 5,
|
|
86
|
+
resetTimeout: 30000,
|
|
87
|
+
halfOpenMaxCalls: 3,
|
|
88
|
+
}));
|
|
89
|
+
});
|
|
90
|
+
logger_1.engineLogger.info('Translator load balancer initialized', {
|
|
91
|
+
endpointCount: endpoints.length,
|
|
92
|
+
endpoints: endpoints.map(e => ({ name: e.name, url: e.url.substring(0, 50) })),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get the next healthy endpoint using weighted round-robin
|
|
97
|
+
*/
|
|
98
|
+
getNextEndpoint() {
|
|
99
|
+
const healthyEndpoints = this.config.endpoints
|
|
100
|
+
.filter(e => e.isHealthy)
|
|
101
|
+
.sort((a, b) => a.priority - b.priority);
|
|
102
|
+
if (healthyEndpoints.length === 0) {
|
|
103
|
+
// All endpoints unhealthy, try primary anyway
|
|
104
|
+
if (this.config.endpoints.length > 0) {
|
|
105
|
+
logger_1.engineLogger.warn('All translator endpoints unhealthy, attempting primary');
|
|
106
|
+
return this.config.endpoints[0];
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
// Round-robin among healthy endpoints
|
|
111
|
+
const endpoint = healthyEndpoints[this.currentEndpointIndex % healthyEndpoints.length];
|
|
112
|
+
this.currentEndpointIndex++;
|
|
113
|
+
return endpoint;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Mark an endpoint as failed
|
|
117
|
+
*/
|
|
118
|
+
markEndpointFailed(endpoint) {
|
|
119
|
+
endpoint.consecutiveFailures++;
|
|
120
|
+
if (endpoint.consecutiveFailures >= this.config.maxConsecutiveFailures) {
|
|
121
|
+
endpoint.isHealthy = false;
|
|
122
|
+
logger_1.engineLogger.warn('Translator endpoint marked unhealthy', {
|
|
123
|
+
name: endpoint.name,
|
|
124
|
+
failures: endpoint.consecutiveFailures,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Mark an endpoint as successful
|
|
130
|
+
*/
|
|
131
|
+
markEndpointSuccess(endpoint) {
|
|
132
|
+
if (!endpoint.isHealthy || endpoint.consecutiveFailures > 0) {
|
|
133
|
+
logger_1.engineLogger.info('Translator endpoint recovered', { name: endpoint.name });
|
|
134
|
+
}
|
|
135
|
+
endpoint.consecutiveFailures = 0;
|
|
136
|
+
endpoint.isHealthy = true;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Execute a translation request with automatic failover and exponential backoff
|
|
140
|
+
*/
|
|
141
|
+
async executeWithFailover(requestFn) {
|
|
142
|
+
const maxAttempts = parseInt(process.env.TRANSLATOR_RETRY_ATTEMPTS || '2');
|
|
143
|
+
const triedEndpoints = [];
|
|
144
|
+
let lastError = null;
|
|
145
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
146
|
+
const endpoint = this.getNextEndpoint();
|
|
147
|
+
if (!endpoint) {
|
|
148
|
+
// No endpoints available
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
if (triedEndpoints.includes(endpoint.name)) {
|
|
152
|
+
// If we've already tried this endpoint, allow retry only when
|
|
153
|
+
// it's the only endpoint available (otherwise cycle to next)
|
|
154
|
+
const healthyEndpoints = this.config.endpoints.filter(e => e.isHealthy);
|
|
155
|
+
if (healthyEndpoints.length > 1) {
|
|
156
|
+
continue; // Skip to next round-robin pick
|
|
157
|
+
}
|
|
158
|
+
// Single endpoint — retrying it is our only option
|
|
159
|
+
}
|
|
160
|
+
if (!triedEndpoints.includes(endpoint.name)) {
|
|
161
|
+
triedEndpoints.push(endpoint.name);
|
|
162
|
+
}
|
|
163
|
+
const circuitBreaker = this.circuitBreakers.get(endpoint.name);
|
|
164
|
+
try {
|
|
165
|
+
const result = await (circuitBreaker
|
|
166
|
+
? circuitBreaker.execute(() => requestFn(endpoint.url))
|
|
167
|
+
: requestFn(endpoint.url));
|
|
168
|
+
this.markEndpointSuccess(endpoint);
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
173
|
+
this.markEndpointFailed(endpoint);
|
|
174
|
+
logger_1.engineLogger.warn('Translator endpoint failed, trying fallback', {
|
|
175
|
+
endpoint: endpoint.name,
|
|
176
|
+
error: lastError.message,
|
|
177
|
+
attempt: attempt + 1,
|
|
178
|
+
maxAttempts,
|
|
179
|
+
});
|
|
180
|
+
// Exponential backoff with jitter before next attempt
|
|
181
|
+
// Only delay if we have more attempts remaining
|
|
182
|
+
if (attempt < maxAttempts - 1) {
|
|
183
|
+
const baseDelay = Math.min(200 * Math.pow(2, attempt), 3000); // Max 3s
|
|
184
|
+
const jitter = Math.random() * 200; // 0-200ms jitter
|
|
185
|
+
const delay = baseDelay + jitter;
|
|
186
|
+
logger_1.engineLogger.info('Waiting before retry', {
|
|
187
|
+
delayMs: Math.round(delay),
|
|
188
|
+
nextAttempt: attempt + 2
|
|
189
|
+
});
|
|
190
|
+
await this.sleep(delay);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const endpointsTried = triedEndpoints.length > 0
|
|
195
|
+
? `tried endpoints: [${triedEndpoints.join(', ')}]`
|
|
196
|
+
: 'no endpoints available';
|
|
197
|
+
throw lastError
|
|
198
|
+
? new Error(`Translator request failed after ${maxAttempts} attempts (${endpointsTried}): ${lastError.message}`)
|
|
199
|
+
: new Error(`All translator endpoints failed (${endpointsTried})`);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Sleep utility
|
|
203
|
+
*/
|
|
204
|
+
sleep(ms) {
|
|
205
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Perform health check on all endpoints
|
|
209
|
+
*/
|
|
210
|
+
async healthCheck() {
|
|
211
|
+
const results = {};
|
|
212
|
+
for (const endpoint of this.config.endpoints) {
|
|
213
|
+
let timeout;
|
|
214
|
+
try {
|
|
215
|
+
const controller = new AbortController();
|
|
216
|
+
timeout = setTimeout(() => controller.abort(), this.config.healthCheckTimeout);
|
|
217
|
+
timeout.unref?.();
|
|
218
|
+
const response = await fetch(`${endpoint.url}/health`, {
|
|
219
|
+
method: 'GET',
|
|
220
|
+
signal: controller.signal,
|
|
221
|
+
});
|
|
222
|
+
const isHealthy = response.ok;
|
|
223
|
+
endpoint.isHealthy = isHealthy;
|
|
224
|
+
endpoint.lastHealthCheck = new Date();
|
|
225
|
+
if (isHealthy) {
|
|
226
|
+
endpoint.consecutiveFailures = 0;
|
|
227
|
+
// Reset the circuit breaker so subsequent calls don't fail immediately
|
|
228
|
+
const cb = this.circuitBreakers.get(endpoint.name);
|
|
229
|
+
if (cb) {
|
|
230
|
+
cb.reset();
|
|
231
|
+
logger_1.engineLogger.info('Circuit breaker reset after healthy health check', { endpoint: endpoint.name });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
results[endpoint.name] = isHealthy;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
endpoint.isHealthy = false;
|
|
238
|
+
endpoint.lastHealthCheck = new Date();
|
|
239
|
+
results[endpoint.name] = false;
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
clearTimeout(timeout);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return results;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Start periodic health checks
|
|
249
|
+
*/
|
|
250
|
+
startHealthChecks() {
|
|
251
|
+
if (this.healthCheckInterval)
|
|
252
|
+
return;
|
|
253
|
+
this.healthCheckInterval = setInterval(() => this.healthCheck(), this.config.healthCheckInterval);
|
|
254
|
+
// Background monitoring must not keep the process alive: short-lived
|
|
255
|
+
// hosts (scripts, the local runner) would otherwise never exit.
|
|
256
|
+
this.healthCheckInterval.unref?.();
|
|
257
|
+
// Initial health check
|
|
258
|
+
this.healthCheck();
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Stop periodic health checks
|
|
262
|
+
*/
|
|
263
|
+
stopHealthChecks() {
|
|
264
|
+
if (this.healthCheckInterval) {
|
|
265
|
+
clearInterval(this.healthCheckInterval);
|
|
266
|
+
this.healthCheckInterval = null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Get current status of all endpoints
|
|
271
|
+
*/
|
|
272
|
+
getStatus() {
|
|
273
|
+
return this.config.endpoints.map(e => ({
|
|
274
|
+
name: e.name,
|
|
275
|
+
url: e.url.substring(0, 50) + (e.url.length > 50 ? '...' : ''),
|
|
276
|
+
isHealthy: e.isHealthy,
|
|
277
|
+
failures: e.consecutiveFailures,
|
|
278
|
+
}));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Singleton instance
|
|
282
|
+
// Created on first use, not at import: the constructor reads TRANSLATOR_*
|
|
283
|
+
// env vars and starts health checks, and importing @babelize/engine must not
|
|
284
|
+
// do either — a host may load its env (dotenv, test setup) after the import.
|
|
285
|
+
let instance = null;
|
|
286
|
+
function getTranslatorLoadBalancer() {
|
|
287
|
+
if (!instance)
|
|
288
|
+
instance = new TranslatorLoadBalancer();
|
|
289
|
+
return instance;
|
|
290
|
+
}
|
|
291
|
+
exports.translatorLoadBalancer = new Proxy({}, {
|
|
292
|
+
get(_target, prop) {
|
|
293
|
+
const lb = getTranslatorLoadBalancer();
|
|
294
|
+
const value = Reflect.get(lb, prop, lb);
|
|
295
|
+
return typeof value === 'function' ? value.bind(lb) : value;
|
|
296
|
+
},
|
|
297
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
/**
|
|
3
|
+
* UI copy vs program data in object / array / variable initializers.
|
|
4
|
+
*
|
|
5
|
+
* `const user = { name: "Ada" }` is data: translating it rewrites a user's
|
|
6
|
+
* name (`{ name: t("page.ada") }`). `[{ label: "Home", href: "/" }]` holds UI
|
|
7
|
+
* copy under `label` and data under `href`. A string in an initializer is
|
|
8
|
+
* only extracted when its key says it's copy, or (without a telling key) when
|
|
9
|
+
* it looks like copy: a sentence, a Title Case phrase or a capitalized word.
|
|
10
|
+
* Both lists match the key's last camelCase word (`buttonLabel` → label,
|
|
11
|
+
* `firstName` → name) or the whole key, case-insensitively, and can be
|
|
12
|
+
* extended by callers.
|
|
13
|
+
*/
|
|
14
|
+
export declare const UI_COPY_KEYS: Set<string>;
|
|
15
|
+
export declare const DATA_KEYS: Set<string>;
|
|
16
|
+
/** Two or more words with letters: reads like copy, not an identifier. */
|
|
17
|
+
export declare function isMultiWordCopy(text: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Copy without a key to say so: a sentence or Title Case phrase, or a single
|
|
20
|
+
* capitalized word ("Overview", "Settings"). Data looks different: lowercase
|
|
21
|
+
* tokens ("active"), kebab/snake/camelCase, ALLCAPS, or anything with digits,
|
|
22
|
+
* underscores, slashes or dots in a single token.
|
|
23
|
+
*/
|
|
24
|
+
export declare function looksLikeCopy(text: string): boolean;
|
|
25
|
+
/** Whether a string stored under `name` (a key or a variable) is UI copy. */
|
|
26
|
+
export declare function isUiCopyForName(name: string | null, text: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* For a string literal in an object property value, array element or
|
|
29
|
+
* variable/assignment initializer: is it UI copy? Other positions (JSX,
|
|
30
|
+
* returns, conditionals, …) aren't initializers and are left as they are.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isUiCopyLiteral(node: t.Node, parent: t.Node | null | undefined, text: string): boolean;
|