@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,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HOSTED_PROVIDER_ID = exports.BABELIZE_KEY_ENV_VAR = exports.PROVIDER_ENV_VARS = void 0;
|
|
4
|
+
exports.isHostedResolution = isHostedResolution;
|
|
5
|
+
exports.isByokProvider = isByokProvider;
|
|
6
|
+
exports.noKeyMessage = noKeyMessage;
|
|
7
|
+
exports.resolveProvider = resolveProvider;
|
|
8
|
+
const types_1 = require("@babelize/types");
|
|
9
|
+
const hosted_provider_1 = require("./hosted-provider");
|
|
10
|
+
/**
|
|
11
|
+
* Env var holding each provider's key, in auto-detection order: the first one
|
|
12
|
+
* set wins when `--provider` isn't given.
|
|
13
|
+
*/
|
|
14
|
+
exports.PROVIDER_ENV_VARS = [
|
|
15
|
+
{ provider: 'anthropic', envVar: 'ANTHROPIC_API_KEY' },
|
|
16
|
+
{ provider: 'openai', envVar: 'OPENAI_API_KEY' },
|
|
17
|
+
{ provider: 'gemini', envVar: 'GEMINI_API_KEY' },
|
|
18
|
+
{ provider: 'groq', envVar: 'GROQ_API_KEY' },
|
|
19
|
+
{ provider: 'openrouter', envVar: 'OPENROUTER_API_KEY' },
|
|
20
|
+
];
|
|
21
|
+
/** Env var holding a Babelize API key for hosted translation. */
|
|
22
|
+
exports.BABELIZE_KEY_ENV_VAR = 'BABELIZE_API_KEY';
|
|
23
|
+
/** `--provider` value selecting Babelize-hosted translation. */
|
|
24
|
+
exports.HOSTED_PROVIDER_ID = 'babelize';
|
|
25
|
+
function isHostedResolution(r) {
|
|
26
|
+
return r.provider === exports.HOSTED_PROVIDER_ID;
|
|
27
|
+
}
|
|
28
|
+
function isByokProvider(value) {
|
|
29
|
+
return types_1.BYOK_PROVIDERS.includes(value);
|
|
30
|
+
}
|
|
31
|
+
function envVarFor(provider) {
|
|
32
|
+
return exports.PROVIDER_ENV_VARS.find((p) => p.provider === provider).envVar;
|
|
33
|
+
}
|
|
34
|
+
/** Message shown when no key is available. Lists env vars, never values. */
|
|
35
|
+
function noKeyMessage() {
|
|
36
|
+
return [
|
|
37
|
+
'No translation key found. Set one of these to use your own LLM key:',
|
|
38
|
+
...exports.PROVIDER_ENV_VARS.map((p) => ` ${p.envVar.padEnd(20)} (${types_1.PROVIDER_DEFINITIONS[p.provider].name})`),
|
|
39
|
+
`or create a Babelize API key at ${hosted_provider_1.API_KEYS_URL} and set ${exports.BABELIZE_KEY_ENV_VAR} (free tier included).`,
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
function hosted(env, model, autoDetected) {
|
|
43
|
+
if (model) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
code: 'model_not_supported',
|
|
47
|
+
message: '--model applies to your own LLM key only; Babelize-hosted translation picks the model on the server.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const apiKey = env[exports.BABELIZE_KEY_ENV_VAR]?.trim();
|
|
51
|
+
if (!apiKey) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
code: 'missing_key',
|
|
55
|
+
message: `--provider ${exports.HOSTED_PROVIDER_ID} needs ${exports.BABELIZE_KEY_ENV_VAR} to be set. Create a key at ${hosted_provider_1.API_KEYS_URL}.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { ok: true, value: { provider: exports.HOSTED_PROVIDER_ID, apiKey, envVar: exports.BABELIZE_KEY_ENV_VAR, autoDetected } };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Pick the translator, in order of precedence:
|
|
62
|
+
* 1. `--provider` (a BYOK vendor, or `babelize`); its env var must be set.
|
|
63
|
+
* 2. The first BYOK env var set, in PROVIDER_ENV_VARS order.
|
|
64
|
+
* 3. BABELIZE_API_KEY → Babelize-hosted translation.
|
|
65
|
+
* For BYOK the model is `--model` or the provider's default from
|
|
66
|
+
* @babelize/types; hosted translation's model is chosen by the server.
|
|
67
|
+
*/
|
|
68
|
+
function resolveProvider(opts, env = process.env) {
|
|
69
|
+
const model = opts.model?.trim() || undefined;
|
|
70
|
+
if (opts.provider) {
|
|
71
|
+
const requested = opts.provider.trim().toLowerCase();
|
|
72
|
+
if (requested === exports.HOSTED_PROVIDER_ID)
|
|
73
|
+
return hosted(env, model, false);
|
|
74
|
+
if (!isByokProvider(requested)) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
code: 'unknown_provider',
|
|
78
|
+
message: `Unknown provider "${opts.provider}". Use one of: ${[...exports.PROVIDER_ENV_VARS.map((p) => p.provider), exports.HOSTED_PROVIDER_ID].join(', ')}.`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const envVar = envVarFor(requested);
|
|
82
|
+
const apiKey = env[envVar]?.trim();
|
|
83
|
+
if (!apiKey) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
code: 'missing_key',
|
|
87
|
+
message: `--provider ${requested} needs ${envVar} to be set.`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
value: {
|
|
93
|
+
provider: requested,
|
|
94
|
+
apiKey,
|
|
95
|
+
model: model ?? types_1.PROVIDER_DEFINITIONS[requested].defaultModel,
|
|
96
|
+
envVar,
|
|
97
|
+
autoDetected: false,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
for (const { provider, envVar } of exports.PROVIDER_ENV_VARS) {
|
|
102
|
+
const apiKey = env[envVar]?.trim();
|
|
103
|
+
if (apiKey) {
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
value: {
|
|
107
|
+
provider,
|
|
108
|
+
apiKey,
|
|
109
|
+
model: model ?? types_1.PROVIDER_DEFINITIONS[provider].defaultModel,
|
|
110
|
+
envVar,
|
|
111
|
+
autoDetected: true,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (env[exports.BABELIZE_KEY_ENV_VAR]?.trim())
|
|
117
|
+
return hosted(env, model, true);
|
|
118
|
+
return { ok: false, code: 'no_key', message: noKeyMessage() };
|
|
119
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { KeyFailure, KeyResult, TranslationBatchRequest, TranslationProvider } from '../providers/types';
|
|
2
|
+
export { isPseudoLocalized, PSEUDO_CLOSE, PSEUDO_OPEN } from '../utils/pseudo-markers';
|
|
3
|
+
export interface PseudoProviderOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Fail chosen keys, to exercise partial runs: return a failure for the
|
|
6
|
+
* (key, locale) or null to translate it.
|
|
7
|
+
*/
|
|
8
|
+
fail?: (key: string, locale: string) => KeyFailure | null;
|
|
9
|
+
}
|
|
10
|
+
export declare class PseudoTranslationProvider implements TranslationProvider {
|
|
11
|
+
private readonly options;
|
|
12
|
+
/** Output is pseudo-localization: the lockfile marks it and a real run replaces it. */
|
|
13
|
+
readonly pseudo = true;
|
|
14
|
+
/** Every request received, in order. */
|
|
15
|
+
readonly calls: TranslationBatchRequest[];
|
|
16
|
+
constructor(options?: PseudoProviderOptions);
|
|
17
|
+
translateBatch(request: TranslationBatchRequest): Promise<KeyResult[]>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PseudoTranslationProvider = exports.PSEUDO_OPEN = exports.PSEUDO_CLOSE = exports.isPseudoLocalized = void 0;
|
|
4
|
+
const icu_message_1 = require("../utils/icu-message");
|
|
5
|
+
const rules_1 = require("../modules/glossary/rules");
|
|
6
|
+
/**
|
|
7
|
+
* Pseudo-localization: a deterministic, offline TranslationProvider for tests
|
|
8
|
+
* and for checking i18n wiring without an API key (`babelize translate
|
|
9
|
+
* --provider pseudo`). It never produces a real translation.
|
|
10
|
+
*
|
|
11
|
+
* "Save note" → "⟦Sávé ñóté⟧". Placeholder tokens (`[#VAR_0#]`), `{args}`,
|
|
12
|
+
* tags, URLs, code and identifiers are kept verbatim; glossary terms come out
|
|
13
|
+
* as their required translation. ICU plurals get exactly the categories the
|
|
14
|
+
* target locale needs, and a key the engine marks as a count (it sends a
|
|
15
|
+
* pluralization hint) comes back as an ICU plural (`one`/`other` for de, plain
|
|
16
|
+
* text for ja, six forms for ar). So the engine's validators accept the
|
|
17
|
+
* output, as they would a good LLM's.
|
|
18
|
+
*/
|
|
19
|
+
const pseudo_markers_1 = require("../utils/pseudo-markers");
|
|
20
|
+
var pseudo_markers_2 = require("../utils/pseudo-markers");
|
|
21
|
+
Object.defineProperty(exports, "isPseudoLocalized", { enumerable: true, get: function () { return pseudo_markers_2.isPseudoLocalized; } });
|
|
22
|
+
Object.defineProperty(exports, "PSEUDO_CLOSE", { enumerable: true, get: function () { return pseudo_markers_2.PSEUDO_CLOSE; } });
|
|
23
|
+
Object.defineProperty(exports, "PSEUDO_OPEN", { enumerable: true, get: function () { return pseudo_markers_2.PSEUDO_OPEN; } });
|
|
24
|
+
const ACCENTS = {
|
|
25
|
+
a: 'á', e: 'é', i: 'í', o: 'ó', u: 'ú', y: 'ý', c: 'ç', n: 'ñ',
|
|
26
|
+
A: 'Á', E: 'É', I: 'Í', O: 'Ó', U: 'Ú', Y: 'Ý', C: 'Ç', N: 'Ñ',
|
|
27
|
+
};
|
|
28
|
+
/** Spans that must survive untouched: engine tokens, args, tags, URLs, emails, code, identifiers. */
|
|
29
|
+
const PROTECTED = new RegExp([
|
|
30
|
+
String.raw `\[\s*#\s*VAR_\d+\s*#\s*\]`,
|
|
31
|
+
// printf / gettext placeholders and HTML entities: %(name)s, %d, %%, —
|
|
32
|
+
String.raw `%\(\w+\)[-#0 +]*\d*(?:\.\d+)?[sdifr]|%%|%[sdif]`,
|
|
33
|
+
String.raw `&(?:[a-zA-Z][a-zA-Z0-9]{1,31}|#\d{1,7}|#x[0-9a-fA-F]{1,6});`,
|
|
34
|
+
String.raw `\{[^{}]*\}`,
|
|
35
|
+
String.raw `<[^<>]+>`,
|
|
36
|
+
String.raw `https?:\/\/\S+`,
|
|
37
|
+
String.raw `[\w.+-]+@[\w-]+\.[\w.]+`,
|
|
38
|
+
'`[^`]+`',
|
|
39
|
+
String.raw `--?[a-z][\w-]*`,
|
|
40
|
+
String.raw `\b\w+\.\w+\b`,
|
|
41
|
+
String.raw `\b[A-Za-z]*[\d_][\w-]*`,
|
|
42
|
+
String.raw `\b[A-Z][A-Z0-9]+\b`,
|
|
43
|
+
].join('|'), 'g');
|
|
44
|
+
function accent(text) {
|
|
45
|
+
return text.replace(/[aeiouycnAEIOUYCN]/g, (ch) => ACCENTS[ch] ?? ch);
|
|
46
|
+
}
|
|
47
|
+
/** Accent everything outside protected spans; glossary terms become their required translation. */
|
|
48
|
+
function pseudoText(text, glossary = []) {
|
|
49
|
+
const terms = [...glossary].sort((a, b) => b.term.length - a.term.length);
|
|
50
|
+
// Glossary terms in plain text become their required translation, unaccented.
|
|
51
|
+
const plain = (segment) => {
|
|
52
|
+
let out = '';
|
|
53
|
+
let rest = segment;
|
|
54
|
+
while (rest) {
|
|
55
|
+
let best = null;
|
|
56
|
+
for (const g of terms) {
|
|
57
|
+
const m = new RegExp((0, rules_1.termPattern)(g.term), 'iu').exec(rest);
|
|
58
|
+
if (m && (!best || m.index < best.index))
|
|
59
|
+
best = { index: m.index, length: m[0].length, translation: g.translation };
|
|
60
|
+
}
|
|
61
|
+
if (!best)
|
|
62
|
+
return out + accent(rest);
|
|
63
|
+
out += accent(rest.slice(0, best.index)) + best.translation;
|
|
64
|
+
rest = rest.slice(best.index + best.length);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
};
|
|
68
|
+
let out = '';
|
|
69
|
+
let last = 0;
|
|
70
|
+
for (const m of text.matchAll(PROTECTED)) {
|
|
71
|
+
out += plain(text.slice(last, m.index)) + m[0];
|
|
72
|
+
last = m.index + m[0].length;
|
|
73
|
+
}
|
|
74
|
+
return out + plain(text.slice(last));
|
|
75
|
+
}
|
|
76
|
+
/** True when the text is an ICU message (plural/select/selectordinal argument). */
|
|
77
|
+
function isIcu(text) {
|
|
78
|
+
return /\{\s*[\w.]+\s*,\s*(plural|select|selectordinal)\s*,/.test(text);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Pseudo-localize an ICU message: literal text accented, arguments kept,
|
|
82
|
+
* plural/selectordinal branches rebuilt for the locale's categories (a
|
|
83
|
+
* missing category reuses the `other` branch; `=N` branches are kept).
|
|
84
|
+
*/
|
|
85
|
+
function pseudoIcu(msg, locale, glossary) {
|
|
86
|
+
let i = 0;
|
|
87
|
+
const message = () => {
|
|
88
|
+
let out = '';
|
|
89
|
+
let text = '';
|
|
90
|
+
const flush = () => {
|
|
91
|
+
out += pseudoText(text, glossary);
|
|
92
|
+
text = '';
|
|
93
|
+
};
|
|
94
|
+
while (i < msg.length && msg[i] !== '}') {
|
|
95
|
+
if (msg[i] === '{') {
|
|
96
|
+
flush();
|
|
97
|
+
out += argument();
|
|
98
|
+
}
|
|
99
|
+
else if (msg[i] === "'" && /['{}#|]/.test(msg[i + 1] ?? '')) {
|
|
100
|
+
// ICU quoting ('{' … ', or '' for an apostrophe): copy verbatim.
|
|
101
|
+
const end = msg[i + 1] === "'" ? i + 1 : msg.indexOf("'", i + 1);
|
|
102
|
+
const stop = end === -1 ? msg.length : end + 1;
|
|
103
|
+
flush();
|
|
104
|
+
out += msg.slice(i, stop);
|
|
105
|
+
i = stop;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
text += msg[i++];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
flush();
|
|
112
|
+
return out;
|
|
113
|
+
};
|
|
114
|
+
const argument = () => {
|
|
115
|
+
const start = i;
|
|
116
|
+
i++; // {
|
|
117
|
+
const head = /^\s*([\w.]+)\s*(?:,\s*(\w+)\s*(?:,)?)?/.exec(msg.slice(i));
|
|
118
|
+
const [, name, type] = head;
|
|
119
|
+
if (type !== 'plural' && type !== 'selectordinal' && type !== 'select') {
|
|
120
|
+
// Simple or formatted argument: copy as is, to its closing brace.
|
|
121
|
+
let depth = 1;
|
|
122
|
+
i = start + 1;
|
|
123
|
+
while (i < msg.length && depth > 0) {
|
|
124
|
+
if (msg[i] === '{')
|
|
125
|
+
depth++;
|
|
126
|
+
else if (msg[i] === '}')
|
|
127
|
+
depth--;
|
|
128
|
+
i++;
|
|
129
|
+
}
|
|
130
|
+
return msg.slice(start, i);
|
|
131
|
+
}
|
|
132
|
+
i += head[0].length;
|
|
133
|
+
const branches = [];
|
|
134
|
+
let offset = '';
|
|
135
|
+
for (;;) {
|
|
136
|
+
const sel = /^\s*(offset:\s*\d+|=?[\w-]+)\s*/.exec(msg.slice(i));
|
|
137
|
+
if (!sel)
|
|
138
|
+
break;
|
|
139
|
+
i += sel[0].length;
|
|
140
|
+
if (sel[1].startsWith('offset:')) {
|
|
141
|
+
offset = `${sel[1]} `;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (msg[i] !== '{')
|
|
145
|
+
break;
|
|
146
|
+
i++;
|
|
147
|
+
const body = message();
|
|
148
|
+
i++; // }
|
|
149
|
+
branches.push({ selector: sel[1], body });
|
|
150
|
+
}
|
|
151
|
+
while (i < msg.length && msg[i] !== '}')
|
|
152
|
+
i++;
|
|
153
|
+
i++; // closing } of the argument
|
|
154
|
+
let out = branches;
|
|
155
|
+
if (type !== 'select') {
|
|
156
|
+
const needed = type === 'plural' ? (0, icu_message_1.requiredPluralCategories)(locale).cardinal : (0, icu_message_1.requiredPluralCategories)(locale).ordinal;
|
|
157
|
+
const other = branches.find((b) => b.selector === 'other') ?? branches[branches.length - 1];
|
|
158
|
+
out = [
|
|
159
|
+
...branches.filter((b) => b.selector.startsWith('=')),
|
|
160
|
+
...needed.map((c) => branches.find((b) => b.selector === c) ?? { selector: c, body: other.body }),
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
return `{${name}, ${type}, ${offset}${out.map((b) => `${b.selector} {${b.body}}`).join(' ')}}`;
|
|
164
|
+
};
|
|
165
|
+
return message();
|
|
166
|
+
}
|
|
167
|
+
/** The count argument named by the engine's pluralization hint, if any. */
|
|
168
|
+
function countArgFromHint(hint) {
|
|
169
|
+
if (!hint || !/ICU plural/.test(hint))
|
|
170
|
+
return undefined;
|
|
171
|
+
return /^\{([\w.]+)\}/.exec(hint)?.[1];
|
|
172
|
+
}
|
|
173
|
+
/** `You have {count} notes` → `{count, plural, one {⟦… # …⟧} other {⟦… # …⟧}}` for the locale's categories. */
|
|
174
|
+
function pseudoPlural(text, arg, locale, glossary) {
|
|
175
|
+
const categories = (0, icu_message_1.requiredPluralCategories)(locale).cardinal;
|
|
176
|
+
const body = `${pseudo_markers_1.PSEUDO_OPEN}${pseudoText(text, glossary)}${pseudo_markers_1.PSEUDO_CLOSE}`;
|
|
177
|
+
if (categories.length <= 1)
|
|
178
|
+
return body;
|
|
179
|
+
const branch = body.split(`{${arg}}`).join('#');
|
|
180
|
+
return `{${arg}, plural, ${categories.map((c) => `${c} {${branch}}`).join(' ')}}`;
|
|
181
|
+
}
|
|
182
|
+
class PseudoTranslationProvider {
|
|
183
|
+
constructor(options = {}) {
|
|
184
|
+
this.options = options;
|
|
185
|
+
/** Output is pseudo-localization: the lockfile marks it and a real run replaces it. */
|
|
186
|
+
this.pseudo = true;
|
|
187
|
+
/** Every request received, in order. */
|
|
188
|
+
this.calls = [];
|
|
189
|
+
}
|
|
190
|
+
async translateBatch(request) {
|
|
191
|
+
this.calls.push(request);
|
|
192
|
+
const locale = request.targetLocale ?? request.targetLang;
|
|
193
|
+
const glossary = request.glossary ?? [];
|
|
194
|
+
return request.entries.map((entry) => {
|
|
195
|
+
const failure = this.options.fail?.(entry.key, locale);
|
|
196
|
+
if (failure)
|
|
197
|
+
return { key: entry.key, status: 'failed', ...failure };
|
|
198
|
+
const arg = countArgFromHint(request.hints?.[entry.key]);
|
|
199
|
+
const value = arg
|
|
200
|
+
? pseudoPlural(entry.value, arg, locale, glossary)
|
|
201
|
+
: isIcu(entry.value)
|
|
202
|
+
? `${pseudo_markers_1.PSEUDO_OPEN}${pseudoIcu(entry.value, locale, glossary)}${pseudo_markers_1.PSEUDO_CLOSE}`
|
|
203
|
+
: `${pseudo_markers_1.PSEUDO_OPEN}${pseudoText(entry.value, glossary)}${pseudo_markers_1.PSEUDO_CLOSE}`;
|
|
204
|
+
return { key: entry.key, status: 'ok', value, confidence: 1, engine: 'pseudo' };
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
exports.PseudoTranslationProvider = PseudoTranslationProvider;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { KeyFailureReason } from '../providers/types';
|
|
2
|
+
import type { FormatResult } from './formatter';
|
|
3
|
+
import type { FrameworkStatus, LocalTranslateReport } from './translate-core';
|
|
4
|
+
import type { FileDiffStat } from './workspace-copy';
|
|
5
|
+
/**
|
|
6
|
+
* The QA report of a local translate run: what `babelize translate --json`
|
|
7
|
+
* prints and what it writes to `.babelize/local/report.json`. One schema for
|
|
8
|
+
* both, described by `schemas/translate-report.schema.json` in this package.
|
|
9
|
+
* Every per-key list is complete (no caps), unlike the MCP tool's response.
|
|
10
|
+
*/
|
|
11
|
+
export declare const TRANSLATE_REPORT_SCHEMA_VERSION = 1;
|
|
12
|
+
export declare const TRANSLATE_REPORT_SCHEMA_ID = "https://babelize.co/schemas/translate-report.v1.json";
|
|
13
|
+
/** Default report location, relative to the project root. */
|
|
14
|
+
export declare const DEFAULT_REPORT_FILE = ".babelize/local/report.json";
|
|
15
|
+
/** Keeps the report out of git while the lockfile next to it stays committed. */
|
|
16
|
+
export declare const BABELIZE_GITIGNORE = ".babelize/.gitignore";
|
|
17
|
+
/** One key in one locale. */
|
|
18
|
+
export type KeyStatus =
|
|
19
|
+
/** Translated, no QA notes. */
|
|
20
|
+
'translated'
|
|
21
|
+
/** Translated with QA warnings (glossary, length, backup translator…). */
|
|
22
|
+
| 'warning'
|
|
23
|
+
/** Has a value identical to the source text: fine for names and codes, otherwise review it. */
|
|
24
|
+
| 'unchanged'
|
|
25
|
+
/** The translator could not translate it this run; not written, retried next run. */
|
|
26
|
+
| 'failed'
|
|
27
|
+
/** No value in this locale and no failure recorded (e.g. a dry run of a locale left out). */
|
|
28
|
+
| 'missing';
|
|
29
|
+
export interface TranslateReportKeyLocale {
|
|
30
|
+
status: KeyStatus;
|
|
31
|
+
/** The value in the locale file (absent when failed/missing). */
|
|
32
|
+
value?: string;
|
|
33
|
+
reason?: KeyFailureReason;
|
|
34
|
+
message?: string;
|
|
35
|
+
retryable?: boolean;
|
|
36
|
+
warnings?: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface TranslateReportLocale {
|
|
39
|
+
locale: string;
|
|
40
|
+
/** `failed` if any key failed, `warning` for QA warnings or missing keys, else `ok`. */
|
|
41
|
+
status: 'ok' | 'warning' | 'failed';
|
|
42
|
+
total: number;
|
|
43
|
+
translated: number;
|
|
44
|
+
missing: number;
|
|
45
|
+
failed: number;
|
|
46
|
+
qaWarnings: number;
|
|
47
|
+
sameAsSource: number;
|
|
48
|
+
}
|
|
49
|
+
export interface TranslateReport {
|
|
50
|
+
$schema: string;
|
|
51
|
+
schemaVersion: typeof TRANSLATE_REPORT_SCHEMA_VERSION;
|
|
52
|
+
/** False when any key failed: the run is partial (`babelize translate` exits 6). */
|
|
53
|
+
ok: boolean;
|
|
54
|
+
startedAt: string;
|
|
55
|
+
finishedAt: string;
|
|
56
|
+
durationMs: number;
|
|
57
|
+
framework: string;
|
|
58
|
+
frameworkStatus: FrameworkStatus;
|
|
59
|
+
/** Null when a translator was injected (tests, embedding). */
|
|
60
|
+
provider: {
|
|
61
|
+
provider: string;
|
|
62
|
+
name: string;
|
|
63
|
+
model?: string;
|
|
64
|
+
} | null;
|
|
65
|
+
sourceLocale: string;
|
|
66
|
+
targetLocales: string[];
|
|
67
|
+
keptLocales: string[];
|
|
68
|
+
droppedLocales: string[];
|
|
69
|
+
dryRun: boolean;
|
|
70
|
+
applied: boolean;
|
|
71
|
+
filesChanged: {
|
|
72
|
+
added: string[];
|
|
73
|
+
modified: string[];
|
|
74
|
+
deleted: string[];
|
|
75
|
+
};
|
|
76
|
+
diffSummary: FileDiffStat[];
|
|
77
|
+
/** Unified diff, dry runs only. */
|
|
78
|
+
diff?: string;
|
|
79
|
+
stats: LocalTranslateReport['stats'];
|
|
80
|
+
summary: {
|
|
81
|
+
sourceKeys: number;
|
|
82
|
+
/** (key, locale) values present after the run. */
|
|
83
|
+
translations: number;
|
|
84
|
+
/** (key, locale) translations produced by the translator this run. */
|
|
85
|
+
newTranslations: number;
|
|
86
|
+
/** (key, locale) values reused from the lockfile, translation memory or cache. */
|
|
87
|
+
reused: number;
|
|
88
|
+
failed: number;
|
|
89
|
+
qaWarnings: number;
|
|
90
|
+
missing: number;
|
|
91
|
+
unchanged: number;
|
|
92
|
+
notInjected: number;
|
|
93
|
+
};
|
|
94
|
+
locales: TranslateReportLocale[];
|
|
95
|
+
failures: {
|
|
96
|
+
total: number;
|
|
97
|
+
items: Array<{
|
|
98
|
+
locale: string;
|
|
99
|
+
key: string;
|
|
100
|
+
source: string;
|
|
101
|
+
reason: KeyFailureReason;
|
|
102
|
+
retryable: boolean;
|
|
103
|
+
message?: string;
|
|
104
|
+
}>;
|
|
105
|
+
};
|
|
106
|
+
qaWarnings: {
|
|
107
|
+
total: number;
|
|
108
|
+
items: Array<{
|
|
109
|
+
locale: string;
|
|
110
|
+
key: string;
|
|
111
|
+
warnings: string[];
|
|
112
|
+
}>;
|
|
113
|
+
};
|
|
114
|
+
/** Keys without a value that did not fail this run (failures are listed under `failures`). */
|
|
115
|
+
missing: {
|
|
116
|
+
total: number;
|
|
117
|
+
items: Array<{
|
|
118
|
+
locale: string;
|
|
119
|
+
key: string;
|
|
120
|
+
}>;
|
|
121
|
+
};
|
|
122
|
+
unchanged: {
|
|
123
|
+
total: number;
|
|
124
|
+
items: Array<{
|
|
125
|
+
locale: string;
|
|
126
|
+
key: string;
|
|
127
|
+
value: string;
|
|
128
|
+
}>;
|
|
129
|
+
};
|
|
130
|
+
notInjected: {
|
|
131
|
+
total: number;
|
|
132
|
+
items: Array<{
|
|
133
|
+
file: string;
|
|
134
|
+
key: string;
|
|
135
|
+
text: string;
|
|
136
|
+
reason: string;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
unusedKeys: {
|
|
140
|
+
total: number;
|
|
141
|
+
items: string[];
|
|
142
|
+
};
|
|
143
|
+
dynamicKeyFiles: string[];
|
|
144
|
+
/** Every source key with its outcome per target locale. */
|
|
145
|
+
keys: Array<{
|
|
146
|
+
key: string;
|
|
147
|
+
source: string;
|
|
148
|
+
locales: Record<string, TranslateReportKeyLocale>;
|
|
149
|
+
}>;
|
|
150
|
+
warnings: Array<{
|
|
151
|
+
type: string;
|
|
152
|
+
file: string;
|
|
153
|
+
line?: number;
|
|
154
|
+
message: string;
|
|
155
|
+
}>;
|
|
156
|
+
notes: string[];
|
|
157
|
+
engineLog: Array<{
|
|
158
|
+
level: 'warn' | 'error';
|
|
159
|
+
message: string;
|
|
160
|
+
}>;
|
|
161
|
+
formatter?: FormatResult;
|
|
162
|
+
lockfile: string;
|
|
163
|
+
lockfileIgnored: boolean;
|
|
164
|
+
}
|
|
165
|
+
/** The report of a finished run (`r` from runLocalTranslate). */
|
|
166
|
+
export declare function buildTranslateReport(r: LocalTranslateReport): TranslateReport;
|
|
167
|
+
/**
|
|
168
|
+
* Write the report as JSON. `file` is resolved against `projectDir`
|
|
169
|
+
* (default {@link DEFAULT_REPORT_FILE}); returns the absolute path written.
|
|
170
|
+
*/
|
|
171
|
+
export declare function writeTranslateReport(projectDir: string, report: TranslateReport, file?: string): Promise<string>;
|
|
172
|
+
/**
|
|
173
|
+
* Make `.babelize/.gitignore` (in `root`) ignore the report, creating the
|
|
174
|
+
* file if needed. Returns true when it changed. The lockfile in the same
|
|
175
|
+
* folder stays committed.
|
|
176
|
+
*/
|
|
177
|
+
export declare function ensureReportGitignored(root: string): Promise<boolean>;
|