@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,81 @@
|
|
|
1
|
+
import type { AlwaysTranslateAsEntry, GlossaryEntryType } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Regex source matching `term` as a whole term: not preceded or followed by a
|
|
4
|
+
* letter, digit or underscore in any script ("Café" matches in "Café menu",
|
|
5
|
+
* "app" does not match in "apply" or "appé"). Use with the `u` flag.
|
|
6
|
+
*/
|
|
7
|
+
export declare function termPattern(term: string): string;
|
|
8
|
+
/** Whether `text` contains `term` as a whole term, ignoring case. */
|
|
9
|
+
export declare function containsTerm(text: string, term: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* The words of a source string a glossary rule can apply to: placeholders,
|
|
12
|
+
* tags, URLs, emails and code removed, and for an ICU message its argument
|
|
13
|
+
* names and selector keys too. "Open https://x.com/dashboard" has no
|
|
14
|
+
* "dashboard" a translator would translate. With `neverTranslate`, those
|
|
15
|
+
* terms are removed too: "Dashboard" in "Babelize Dashboard" (never
|
|
16
|
+
* translated) is not a use of a "Dashboard" rule.
|
|
17
|
+
*/
|
|
18
|
+
export declare function translatableText(source: string, neverTranslate?: readonly string[]): string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a glossary term occurs in a source string's translatable text (see
|
|
21
|
+
* translatableText). Pass the never-translate terms when asking about an
|
|
22
|
+
* "always translate as" term: inside a never-translate term it doesn't count.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sourceHasTerm(source: string, term: string, neverTranslate?: readonly string[]): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* BCP-47 casing for a locale code: `pt_br` / `PT-br` → `pt-BR`,
|
|
27
|
+
* `zh-hant` → `zh-Hant`, `es-419` stays. Anything that isn't
|
|
28
|
+
* language[-Script][-REGION] is returned trimmed, otherwise unchanged.
|
|
29
|
+
*/
|
|
30
|
+
export declare function canonicalLocale(code: string): string;
|
|
31
|
+
/** Keys of a locale → value record in canonical form; on a clash the first key (in insertion order) wins. */
|
|
32
|
+
export declare function canonicalizeLocaleKeys(record: Record<string, string> | null | undefined): Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* The value for `locale` in a locale-keyed record: the exact locale (in any
|
|
35
|
+
* casing / separator), else its base language (`pt` serves `pt-BR`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function localeValue(record: Record<string, string> | null | undefined, locale: string): string | undefined;
|
|
38
|
+
export interface GlossaryRuleInput {
|
|
39
|
+
term: string;
|
|
40
|
+
type: GlossaryEntryType | string;
|
|
41
|
+
translations?: Record<string, string> | null;
|
|
42
|
+
}
|
|
43
|
+
/** A project's glossary after normalization: deduplicated, deterministic, conflicts resolved. */
|
|
44
|
+
export interface GlossaryRules {
|
|
45
|
+
neverTranslate: string[];
|
|
46
|
+
alwaysTranslateAs: AlwaysTranslateAsEntry[];
|
|
47
|
+
/** Terms that had conflicting rules, with how the conflict was resolved (for QA notes). */
|
|
48
|
+
conflicts: Array<{
|
|
49
|
+
term: string;
|
|
50
|
+
message: string;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export declare const EMPTY_GLOSSARY: GlossaryRules;
|
|
54
|
+
/**
|
|
55
|
+
* Normalize raw glossary entries. Terms match case-insensitively, so entries
|
|
56
|
+
* are grouped by lowercased term:
|
|
57
|
+
* - never-translate wins over always-translate-as for the same term (keeping
|
|
58
|
+
* a term as-is can't corrupt the output; a translation for the wrong sense can);
|
|
59
|
+
* - several always-translate-as entries merge their locales (the first entry,
|
|
60
|
+
* in term order, wins a locale they both define);
|
|
61
|
+
* - locale keys are canonicalized (`pt_br` → `pt-BR`).
|
|
62
|
+
* The result doesn't depend on the input order.
|
|
63
|
+
*/
|
|
64
|
+
export declare function normalizeGlossary(entries: GlossaryRuleInput[]): GlossaryRules;
|
|
65
|
+
/** Always-translate-as terms with a translation for `locale` (exact, then base language). */
|
|
66
|
+
export declare function fixedTermsFor(rules: GlossaryRules, locale: string): Array<{
|
|
67
|
+
term: string;
|
|
68
|
+
translation: string;
|
|
69
|
+
}>;
|
|
70
|
+
/** Whether `source` contains an always-translate-as term that has a translation for `locale`. */
|
|
71
|
+
export declare function hasFixedTerm(source: string, rules: GlossaryRules, locale: string): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Fingerprint of the glossary rules that shape `source`'s translation into
|
|
74
|
+
* `locale`: the never-translate terms it contains and the always-translate-as
|
|
75
|
+
* terms it contains with their translation for the locale. Empty when no rule
|
|
76
|
+
* applies, so adding a rule for an unrelated term changes nothing. Stored per
|
|
77
|
+
* locale in the lockfile: a key is re-translated when this changes.
|
|
78
|
+
*/
|
|
79
|
+
export declare function glossaryFingerprint(source: string, locale: string, rules: GlossaryRules): string;
|
|
80
|
+
/** QA notes for conflicting rules whose term occurs in `source`. */
|
|
81
|
+
export declare function conflictNotes(source: string, rules: GlossaryRules): string[];
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMPTY_GLOSSARY = void 0;
|
|
4
|
+
exports.termPattern = termPattern;
|
|
5
|
+
exports.containsTerm = containsTerm;
|
|
6
|
+
exports.translatableText = translatableText;
|
|
7
|
+
exports.sourceHasTerm = sourceHasTerm;
|
|
8
|
+
exports.canonicalLocale = canonicalLocale;
|
|
9
|
+
exports.canonicalizeLocaleKeys = canonicalizeLocaleKeys;
|
|
10
|
+
exports.localeValue = localeValue;
|
|
11
|
+
exports.normalizeGlossary = normalizeGlossary;
|
|
12
|
+
exports.fixedTermsFor = fixedTermsFor;
|
|
13
|
+
exports.hasFixedTerm = hasFixedTerm;
|
|
14
|
+
exports.glossaryFingerprint = glossaryFingerprint;
|
|
15
|
+
exports.conflictNotes = conflictNotes;
|
|
16
|
+
/**
|
|
17
|
+
* Project glossary rules, shared by the translate phase, locale QA, prompt
|
|
18
|
+
* hints and the CLI/MCP. One place decides how a term matches, which
|
|
19
|
+
* translation applies to a locale, and what happens when rules conflict.
|
|
20
|
+
*/
|
|
21
|
+
const crypto_1 = require("crypto");
|
|
22
|
+
const placeholder_protection_1 = require("../../utils/placeholder-protection");
|
|
23
|
+
const icu_message_1 = require("../../utils/icu-message");
|
|
24
|
+
function escapeRegex(s) {
|
|
25
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Regex source matching `term` as a whole term: not preceded or followed by a
|
|
29
|
+
* letter, digit or underscore in any script ("Café" matches in "Café menu",
|
|
30
|
+
* "app" does not match in "apply" or "appé"). Use with the `u` flag.
|
|
31
|
+
*/
|
|
32
|
+
function termPattern(term) {
|
|
33
|
+
return `(?<![\\p{L}\\p{N}_])${escapeRegex(term)}(?![\\p{L}\\p{N}_])`;
|
|
34
|
+
}
|
|
35
|
+
const termRegexes = new Map();
|
|
36
|
+
/** Whether `text` contains `term` as a whole term, ignoring case. */
|
|
37
|
+
function containsTerm(text, term) {
|
|
38
|
+
if (!term.trim())
|
|
39
|
+
return false;
|
|
40
|
+
// Cheap pre-check: most (key, term) pairs share nothing.
|
|
41
|
+
if (!text.toLowerCase().includes(term.toLowerCase())) {
|
|
42
|
+
// toLowerCase can change length for a few letters (İ); fall through only then.
|
|
43
|
+
if (text.length === text.toLowerCase().length && term.length === term.toLowerCase().length)
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
let re = termRegexes.get(term);
|
|
47
|
+
if (!re) {
|
|
48
|
+
if (termRegexes.size > 10000)
|
|
49
|
+
termRegexes.clear();
|
|
50
|
+
re = new RegExp(termPattern(term), 'iu');
|
|
51
|
+
termRegexes.set(term, re);
|
|
52
|
+
}
|
|
53
|
+
return re.test(text);
|
|
54
|
+
}
|
|
55
|
+
const NO_TERMS = [];
|
|
56
|
+
/** Per never-translate list (by identity), source → translatable text. */
|
|
57
|
+
const translatableCache = new WeakMap();
|
|
58
|
+
/**
|
|
59
|
+
* The words of a source string a glossary rule can apply to: placeholders,
|
|
60
|
+
* tags, URLs, emails and code removed, and for an ICU message its argument
|
|
61
|
+
* names and selector keys too. "Open https://x.com/dashboard" has no
|
|
62
|
+
* "dashboard" a translator would translate. With `neverTranslate`, those
|
|
63
|
+
* terms are removed too: "Dashboard" in "Babelize Dashboard" (never
|
|
64
|
+
* translated) is not a use of a "Dashboard" rule.
|
|
65
|
+
*/
|
|
66
|
+
function translatableText(source, neverTranslate = NO_TERMS) {
|
|
67
|
+
let cache = translatableCache.get(neverTranslate);
|
|
68
|
+
if (!cache) {
|
|
69
|
+
cache = new Map();
|
|
70
|
+
translatableCache.set(neverTranslate, cache);
|
|
71
|
+
}
|
|
72
|
+
const hit = cache.get(source);
|
|
73
|
+
if (hit !== undefined)
|
|
74
|
+
return hit;
|
|
75
|
+
let text = (0, placeholder_protection_1.protectText)(source, [...neverTranslate]).text.replace(/\[#VAR_\d+#\]/g, ' ');
|
|
76
|
+
if ((0, icu_message_1.isIcuMessage)(source)) {
|
|
77
|
+
text = text
|
|
78
|
+
.replace(/\{\s*[\w.]+\s*,\s*(?:plural|select|selectordinal)\s*,(?:\s*offset\s*:\s*\d+)?/g, ' ')
|
|
79
|
+
.replace(/(?:=\d+|[\p{L}\p{N}_]+)\s*\{/gu, ' ');
|
|
80
|
+
}
|
|
81
|
+
if (cache.size > 20000)
|
|
82
|
+
cache.clear();
|
|
83
|
+
cache.set(source, text);
|
|
84
|
+
return text;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether a glossary term occurs in a source string's translatable text (see
|
|
88
|
+
* translatableText). Pass the never-translate terms when asking about an
|
|
89
|
+
* "always translate as" term: inside a never-translate term it doesn't count.
|
|
90
|
+
*/
|
|
91
|
+
function sourceHasTerm(source, term, neverTranslate = NO_TERMS) {
|
|
92
|
+
return containsTerm(translatableText(source, neverTranslate), term);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* BCP-47 casing for a locale code: `pt_br` / `PT-br` → `pt-BR`,
|
|
96
|
+
* `zh-hant` → `zh-Hant`, `es-419` stays. Anything that isn't
|
|
97
|
+
* language[-Script][-REGION] is returned trimmed, otherwise unchanged.
|
|
98
|
+
*/
|
|
99
|
+
function canonicalLocale(code) {
|
|
100
|
+
const parts = code.trim().replace(/_/g, '-').split('-');
|
|
101
|
+
if (!/^[a-z]{2,3}$/i.test(parts[0] ?? ''))
|
|
102
|
+
return code.trim();
|
|
103
|
+
const out = [parts[0].toLowerCase()];
|
|
104
|
+
for (let i = 1; i < parts.length; i++) {
|
|
105
|
+
const p = parts[i];
|
|
106
|
+
if (/^[a-z]{4}$/i.test(p))
|
|
107
|
+
out.push(p.charAt(0).toUpperCase() + p.slice(1).toLowerCase());
|
|
108
|
+
else if (/^[a-z]{2}$/i.test(p))
|
|
109
|
+
out.push(p.toUpperCase());
|
|
110
|
+
else if (/^\d{3}$/.test(p))
|
|
111
|
+
out.push(p);
|
|
112
|
+
else
|
|
113
|
+
return code.trim();
|
|
114
|
+
}
|
|
115
|
+
return out.join('-');
|
|
116
|
+
}
|
|
117
|
+
/** Keys of a locale → value record in canonical form; on a clash the first key (in insertion order) wins. */
|
|
118
|
+
function canonicalizeLocaleKeys(record) {
|
|
119
|
+
const out = {};
|
|
120
|
+
for (const [k, v] of Object.entries(record ?? {})) {
|
|
121
|
+
if (typeof v !== 'string' || !v.trim())
|
|
122
|
+
continue;
|
|
123
|
+
const key = canonicalLocale(k);
|
|
124
|
+
if (!(key in out))
|
|
125
|
+
out[key] = v;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The value for `locale` in a locale-keyed record: the exact locale (in any
|
|
131
|
+
* casing / separator), else its base language (`pt` serves `pt-BR`).
|
|
132
|
+
*/
|
|
133
|
+
function localeValue(record, locale) {
|
|
134
|
+
if (!record || !locale)
|
|
135
|
+
return undefined;
|
|
136
|
+
const want = canonicalLocale(locale);
|
|
137
|
+
let base;
|
|
138
|
+
const baseLang = want.split('-')[0];
|
|
139
|
+
for (const [k, v] of Object.entries(record)) {
|
|
140
|
+
if (typeof v !== 'string' || !v.trim())
|
|
141
|
+
continue;
|
|
142
|
+
const key = canonicalLocale(k);
|
|
143
|
+
if (key === want)
|
|
144
|
+
return v;
|
|
145
|
+
if (key === baseLang && base === undefined)
|
|
146
|
+
base = v;
|
|
147
|
+
}
|
|
148
|
+
return base;
|
|
149
|
+
}
|
|
150
|
+
exports.EMPTY_GLOSSARY = { neverTranslate: [], alwaysTranslateAs: [], conflicts: [] };
|
|
151
|
+
/**
|
|
152
|
+
* Normalize raw glossary entries. Terms match case-insensitively, so entries
|
|
153
|
+
* are grouped by lowercased term:
|
|
154
|
+
* - never-translate wins over always-translate-as for the same term (keeping
|
|
155
|
+
* a term as-is can't corrupt the output; a translation for the wrong sense can);
|
|
156
|
+
* - several always-translate-as entries merge their locales (the first entry,
|
|
157
|
+
* in term order, wins a locale they both define);
|
|
158
|
+
* - locale keys are canonicalized (`pt_br` → `pt-BR`).
|
|
159
|
+
* The result doesn't depend on the input order.
|
|
160
|
+
*/
|
|
161
|
+
function normalizeGlossary(entries) {
|
|
162
|
+
const groups = new Map();
|
|
163
|
+
for (const e of entries) {
|
|
164
|
+
if (!e || typeof e.term !== 'string')
|
|
165
|
+
continue;
|
|
166
|
+
const term = e.term.trim();
|
|
167
|
+
if (!term)
|
|
168
|
+
continue;
|
|
169
|
+
if (e.type !== 'never_translate' && e.type !== 'always_translate_as')
|
|
170
|
+
continue;
|
|
171
|
+
const k = term.toLowerCase();
|
|
172
|
+
const list = groups.get(k) ?? [];
|
|
173
|
+
list.push({ ...e, term });
|
|
174
|
+
groups.set(k, list);
|
|
175
|
+
}
|
|
176
|
+
const neverTranslate = [];
|
|
177
|
+
const alwaysTranslateAs = [];
|
|
178
|
+
const conflicts = [];
|
|
179
|
+
const byTerm = (a, b) => (a.term < b.term ? -1 : a.term > b.term ? 1 : 0);
|
|
180
|
+
for (const k of [...groups.keys()].sort()) {
|
|
181
|
+
const list = groups.get(k).slice().sort(byTerm);
|
|
182
|
+
const never = list.filter((e) => e.type === 'never_translate');
|
|
183
|
+
const always = list.filter((e) => e.type === 'always_translate_as');
|
|
184
|
+
if (never.length > 0) {
|
|
185
|
+
neverTranslate.push(never[0].term);
|
|
186
|
+
if (always.length > 0) {
|
|
187
|
+
const names = [...new Set(list.map((e) => `"${e.term}"`))].join(' / ');
|
|
188
|
+
conflicts.push({
|
|
189
|
+
term: never[0].term,
|
|
190
|
+
message: `Glossary: ${names} is both "never translate" and "always translate as" (terms match ignoring case); "never translate" was applied`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const translations = {};
|
|
196
|
+
for (const e of always) {
|
|
197
|
+
for (const [loc, v] of Object.entries(canonicalizeLocaleKeys(e.translations))) {
|
|
198
|
+
if (!(loc in translations))
|
|
199
|
+
translations[loc] = v.trim();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
alwaysTranslateAs.push({ term: always[0].term, type: 'always_translate_as', translations });
|
|
203
|
+
}
|
|
204
|
+
return { neverTranslate, alwaysTranslateAs, conflicts };
|
|
205
|
+
}
|
|
206
|
+
/** Always-translate-as terms with a translation for `locale` (exact, then base language). */
|
|
207
|
+
function fixedTermsFor(rules, locale) {
|
|
208
|
+
const out = [];
|
|
209
|
+
for (const e of rules.alwaysTranslateAs) {
|
|
210
|
+
const translation = localeValue(e.translations, locale);
|
|
211
|
+
if (translation)
|
|
212
|
+
out.push({ term: e.term, translation });
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
/** Whether `source` contains an always-translate-as term that has a translation for `locale`. */
|
|
217
|
+
function hasFixedTerm(source, rules, locale) {
|
|
218
|
+
return fixedTermsFor(rules, locale).some((g) => sourceHasTerm(source, g.term, rules.neverTranslate));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Fingerprint of the glossary rules that shape `source`'s translation into
|
|
222
|
+
* `locale`: the never-translate terms it contains and the always-translate-as
|
|
223
|
+
* terms it contains with their translation for the locale. Empty when no rule
|
|
224
|
+
* applies, so adding a rule for an unrelated term changes nothing. Stored per
|
|
225
|
+
* locale in the lockfile: a key is re-translated when this changes.
|
|
226
|
+
*/
|
|
227
|
+
function glossaryFingerprint(source, locale, rules) {
|
|
228
|
+
const parts = [];
|
|
229
|
+
for (const t of rules.neverTranslate) {
|
|
230
|
+
if (sourceHasTerm(source, t))
|
|
231
|
+
parts.push(`n\u0000${t.toLowerCase()}`);
|
|
232
|
+
}
|
|
233
|
+
for (const g of fixedTermsFor(rules, locale)) {
|
|
234
|
+
if (sourceHasTerm(source, g.term, rules.neverTranslate))
|
|
235
|
+
parts.push(`a\u0000${g.term.toLowerCase()}\u0000${g.translation}`);
|
|
236
|
+
}
|
|
237
|
+
if (parts.length === 0)
|
|
238
|
+
return '';
|
|
239
|
+
return (0, crypto_1.createHash)('sha256').update(parts.sort().join('\u0001'), 'utf8').digest('hex').slice(0, 16);
|
|
240
|
+
}
|
|
241
|
+
/** QA notes for conflicting rules whose term occurs in `source`. */
|
|
242
|
+
function conflictNotes(source, rules) {
|
|
243
|
+
return rules.conflicts.filter((c) => sourceHasTerm(source, c.term)).map((c) => c.message);
|
|
244
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type GlossaryEntryType = 'never_translate' | 'always_translate_as';
|
|
2
|
+
export interface GlossaryEntry {
|
|
3
|
+
id?: string;
|
|
4
|
+
project_id: string;
|
|
5
|
+
term: string;
|
|
6
|
+
type: GlossaryEntryType;
|
|
7
|
+
/** For always_translate_as: locale code → fixed translation */
|
|
8
|
+
translations?: Record<string, string>;
|
|
9
|
+
created_at?: string;
|
|
10
|
+
updated_at?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BrandVoice {
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
guidelines?: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Subset used by the masker and translation pipeline */
|
|
18
|
+
export interface AlwaysTranslateAsEntry {
|
|
19
|
+
term: string;
|
|
20
|
+
type: 'always_translate_as';
|
|
21
|
+
translations: Record<string, string>;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { injectTranslations, checkIdempotency } from './inject';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkIdempotency = exports.injectTranslations = void 0;
|
|
4
|
+
var inject_1 = require("./inject");
|
|
5
|
+
Object.defineProperty(exports, "injectTranslations", { enumerable: true, get: function () { return inject_1.injectTranslations; } });
|
|
6
|
+
Object.defineProperty(exports, "checkIdempotency", { enumerable: true, get: function () { return inject_1.checkIdempotency; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FileInjection, InjectionResult, WorkspaceInfo } from '../../runtime/types';
|
|
2
|
+
/**
|
|
3
|
+
* Phase 5: Injection
|
|
4
|
+
*
|
|
5
|
+
* Injects i18n into source files — JS/TS gets Babel AST injection,
|
|
6
|
+
* HTML files get DOM-based data-i18n attribute injection.
|
|
7
|
+
*/
|
|
8
|
+
export declare function injectTranslations(workspace: WorkspaceInfo, keyMapping: Map<string, string>, // extractionId -> key
|
|
9
|
+
confidenceThreshold?: number, dryRun?: boolean,
|
|
10
|
+
/** extractionId → source offsets of the nodes extraction selected. */
|
|
11
|
+
positions?: Map<string, Set<number>>): Promise<InjectionResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Inject translations into a single file
|
|
14
|
+
*/
|
|
15
|
+
export declare function injectIntoFile(content: string, filePath: string, keyMapping: Map<string, string>, confidenceThreshold: number, framework: string, existingDeps?: Record<string, string>, forceTypeScript?: boolean,
|
|
16
|
+
/** extractionId → source offsets of the nodes extraction selected (see ExtractedString.offsets). */
|
|
17
|
+
positions?: Map<string, Set<number>>): FileInjection;
|
|
18
|
+
/**
|
|
19
|
+
* Inject translations into a Vue SFC file.
|
|
20
|
+
* Uses @vue/compiler-sfc to parse, vue/template-scan to find translatable
|
|
21
|
+
* units, vue/template-rewrite to inject $t() calls, and vue/script-inject
|
|
22
|
+
* to add useI18n() in <script setup>.
|
|
23
|
+
*/
|
|
24
|
+
export declare function injectIntoVueFile(content: string, filePath: string, keyMapping: Map<string, string>, _confidenceThreshold: number, _framework: string, _existingDeps?: Record<string, string>): Promise<FileInjection>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if injection would be idempotent
|
|
27
|
+
* (running again would produce the same output)
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkIdempotency(originalContent: string, modifiedContent: string, filePath: string, keyMapping: Map<string, string>, framework?: string): boolean;
|