@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,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small Python tokenizer: enough structure to find string literals, the
|
|
3
|
+
* calls/brackets around them and the def/class scopes they live in, without
|
|
4
|
+
* a Python runtime. It never rewrites anything itself — the extractor and
|
|
5
|
+
* injector use token offsets to edit the original text byte-for-byte.
|
|
6
|
+
*/
|
|
7
|
+
export type PyTokenType = 'name' | 'number' | 'string' | 'op' | 'comment';
|
|
8
|
+
export interface PyStringInfo {
|
|
9
|
+
/** Lower-cased prefix letters, e.g. '', 'r', 'f', 'rb'. */
|
|
10
|
+
prefix: string;
|
|
11
|
+
/** The quote sequence: ', ", ''' or """. */
|
|
12
|
+
quote: string;
|
|
13
|
+
raw: boolean;
|
|
14
|
+
f: boolean;
|
|
15
|
+
bytes: boolean;
|
|
16
|
+
/** Offsets of the text between the quotes. */
|
|
17
|
+
bodyStart: number;
|
|
18
|
+
bodyEnd: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PyToken {
|
|
21
|
+
type: PyTokenType;
|
|
22
|
+
value: string;
|
|
23
|
+
start: number;
|
|
24
|
+
end: number;
|
|
25
|
+
/** 1-based line of the token start. */
|
|
26
|
+
line: number;
|
|
27
|
+
str?: PyStringInfo;
|
|
28
|
+
}
|
|
29
|
+
export interface PyLogicalLine {
|
|
30
|
+
/** Indentation (columns) of the first token. */
|
|
31
|
+
indent: number;
|
|
32
|
+
tokens: PyToken[];
|
|
33
|
+
/** Comments that sit on the physical lines of this logical line. */
|
|
34
|
+
comments: PyToken[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* End (exclusive, after the closing `}`) of an f-string replacement field
|
|
38
|
+
* whose expression starts at `i`. Nested brackets and strings (including the
|
|
39
|
+
* same quote type, legal since Python 3.12) are skipped.
|
|
40
|
+
*/
|
|
41
|
+
export declare function scanFieldEnd(src: string, i: number): number;
|
|
42
|
+
export declare function tokenizePython(src: string): PyToken[];
|
|
43
|
+
/**
|
|
44
|
+
* Group tokens into logical lines: a newline ends a statement unless it is
|
|
45
|
+
* inside brackets (or a string, or after a backslash continuation).
|
|
46
|
+
*/
|
|
47
|
+
export declare function logicalLines(src: string, tokens: PyToken[]): PyLogicalLine[];
|
|
48
|
+
/** The runtime value of a (non-f) string literal's body. */
|
|
49
|
+
export declare function decodePythonString(body: string, raw: boolean): string;
|
|
50
|
+
/** A double-quoted Python string literal whose value is `value`. */
|
|
51
|
+
export declare function pythonStringLiteral(value: string): string;
|
|
52
|
+
export interface FStringPart {
|
|
53
|
+
kind: 'text' | 'field';
|
|
54
|
+
/** Text (already unescaped, with {{ }} collapsed) or the field's expression source. */
|
|
55
|
+
value: string;
|
|
56
|
+
conversion?: string;
|
|
57
|
+
formatSpec?: string;
|
|
58
|
+
/** `{x=}` self-documenting expression. */
|
|
59
|
+
debug?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Split an f-string body into literal text and replacement fields. Returns
|
|
63
|
+
* null for fields this tool won't rewrite (nested fields in format specs).
|
|
64
|
+
*/
|
|
65
|
+
export declare function parseFString(body: string, raw: boolean): FStringPart[] | null;
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A small Python tokenizer: enough structure to find string literals, the
|
|
4
|
+
* calls/brackets around them and the def/class scopes they live in, without
|
|
5
|
+
* a Python runtime. It never rewrites anything itself — the extractor and
|
|
6
|
+
* injector use token offsets to edit the original text byte-for-byte.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.scanFieldEnd = scanFieldEnd;
|
|
10
|
+
exports.tokenizePython = tokenizePython;
|
|
11
|
+
exports.logicalLines = logicalLines;
|
|
12
|
+
exports.decodePythonString = decodePythonString;
|
|
13
|
+
exports.pythonStringLiteral = pythonStringLiteral;
|
|
14
|
+
exports.parseFString = parseFString;
|
|
15
|
+
const OPERATORS = [
|
|
16
|
+
'**=', '//=', '>>=', '<<=', '...', '->', ':=', '==', '!=', '<=', '>=', '**', '//', '<<', '>>',
|
|
17
|
+
'+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '@=',
|
|
18
|
+
];
|
|
19
|
+
const STRING_PREFIX = /^(?:[rRuUbBfF]|[rR][bBfF]|[bBfF][rR])$/;
|
|
20
|
+
/** Position after a string literal that starts with its quote at `q` (prefix already consumed). */
|
|
21
|
+
function scanStringEnd(src, q, f) {
|
|
22
|
+
const quote = src.startsWith(src[q].repeat(3), q) ? src[q].repeat(3) : src[q];
|
|
23
|
+
let i = q + quote.length;
|
|
24
|
+
const triple = quote.length === 3;
|
|
25
|
+
while (i < src.length) {
|
|
26
|
+
const ch = src[i];
|
|
27
|
+
if (ch === '\\') {
|
|
28
|
+
i += 2;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (!triple && ch === '\n')
|
|
32
|
+
return null; // unterminated single-line string
|
|
33
|
+
if (src.startsWith(quote, i))
|
|
34
|
+
return { end: i + quote.length, quote };
|
|
35
|
+
if (f && ch === '{') {
|
|
36
|
+
if (src[i + 1] === '{') {
|
|
37
|
+
i += 2;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const fieldEnd = scanFieldEnd(src, i + 1);
|
|
41
|
+
if (fieldEnd === -1)
|
|
42
|
+
return null;
|
|
43
|
+
i = fieldEnd;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* End (exclusive, after the closing `}`) of an f-string replacement field
|
|
52
|
+
* whose expression starts at `i`. Nested brackets and strings (including the
|
|
53
|
+
* same quote type, legal since Python 3.12) are skipped.
|
|
54
|
+
*/
|
|
55
|
+
function scanFieldEnd(src, i) {
|
|
56
|
+
let depth = 0;
|
|
57
|
+
while (i < src.length) {
|
|
58
|
+
const ch = src[i];
|
|
59
|
+
if (ch === '\'' || ch === '"') {
|
|
60
|
+
// A nested string literal, possibly with a prefix just before it.
|
|
61
|
+
let p = i;
|
|
62
|
+
while (p > 0 && /[rRbBfFuU]/.test(src[p - 1]))
|
|
63
|
+
p--;
|
|
64
|
+
const nestedF = /[fF]/.test(src.slice(p, i));
|
|
65
|
+
const r = scanStringEnd(src, i, nestedF);
|
|
66
|
+
if (!r)
|
|
67
|
+
return -1;
|
|
68
|
+
i = r.end;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (ch === '(' || ch === '[' || ch === '{')
|
|
72
|
+
depth++;
|
|
73
|
+
else if (ch === ')' || ch === ']')
|
|
74
|
+
depth--;
|
|
75
|
+
else if (ch === '}') {
|
|
76
|
+
if (depth === 0)
|
|
77
|
+
return i + 1;
|
|
78
|
+
depth--;
|
|
79
|
+
}
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
return -1;
|
|
83
|
+
}
|
|
84
|
+
function tokenizePython(src) {
|
|
85
|
+
const tokens = [];
|
|
86
|
+
let i = 0;
|
|
87
|
+
let line = 1;
|
|
88
|
+
const len = src.length;
|
|
89
|
+
const push = (type, start, end, str) => {
|
|
90
|
+
tokens.push({ type, value: src.slice(start, end), start, end, line, str });
|
|
91
|
+
for (let k = start; k < end; k++)
|
|
92
|
+
if (src.charCodeAt(k) === 10)
|
|
93
|
+
line++;
|
|
94
|
+
};
|
|
95
|
+
while (i < len) {
|
|
96
|
+
const ch = src[i];
|
|
97
|
+
if (ch === '\n') {
|
|
98
|
+
line++;
|
|
99
|
+
i++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (ch === ' ' || ch === '\t' || ch === '\r' || ch === '\f') {
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (ch === '\\' && (src[i + 1] === '\n' || (src[i + 1] === '\r' && src[i + 2] === '\n'))) {
|
|
107
|
+
i += src[i + 1] === '\r' ? 3 : 2;
|
|
108
|
+
line++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (ch === '#') {
|
|
112
|
+
let end = src.indexOf('\n', i);
|
|
113
|
+
if (end === -1)
|
|
114
|
+
end = len;
|
|
115
|
+
push('comment', i, end);
|
|
116
|
+
i = end;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (/[A-Za-z_\u0080-]/.test(ch)) {
|
|
120
|
+
let j = i + 1;
|
|
121
|
+
while (j < len && /[\w\u0080-]/.test(src[j]))
|
|
122
|
+
j++;
|
|
123
|
+
const word = src.slice(i, j);
|
|
124
|
+
if ((src[j] === '"' || src[j] === "'") && STRING_PREFIX.test(word)) {
|
|
125
|
+
const prefix = word.toLowerCase();
|
|
126
|
+
const f = prefix.includes('f');
|
|
127
|
+
const r = scanStringEnd(src, j, f);
|
|
128
|
+
if (r) {
|
|
129
|
+
push('string', i, r.end, {
|
|
130
|
+
prefix,
|
|
131
|
+
quote: r.quote,
|
|
132
|
+
raw: prefix.includes('r'),
|
|
133
|
+
f,
|
|
134
|
+
bytes: prefix.includes('b'),
|
|
135
|
+
bodyStart: j + r.quote.length,
|
|
136
|
+
bodyEnd: r.end - r.quote.length,
|
|
137
|
+
});
|
|
138
|
+
i = r.end;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
push('name', i, j);
|
|
143
|
+
i = j;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (ch === '"' || ch === "'") {
|
|
147
|
+
const r = scanStringEnd(src, i, false);
|
|
148
|
+
if (r) {
|
|
149
|
+
push('string', i, r.end, {
|
|
150
|
+
prefix: '', quote: r.quote, raw: false, f: false, bytes: false,
|
|
151
|
+
bodyStart: i + r.quote.length, bodyEnd: r.end - r.quote.length,
|
|
152
|
+
});
|
|
153
|
+
i = r.end;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
// Unterminated: treat the quote as an operator so scanning continues.
|
|
157
|
+
push('op', i, i + 1);
|
|
158
|
+
i++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(src[i + 1] ?? ''))) {
|
|
162
|
+
const m = /^(?:0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d+)?[jJ]?)/.exec(src.slice(i, i + 64));
|
|
163
|
+
const end = i + (m ? m[0].length : 1);
|
|
164
|
+
push('number', i, end);
|
|
165
|
+
i = end;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const op = OPERATORS.find((o) => src.startsWith(o, i));
|
|
169
|
+
const end = i + (op ? op.length : 1);
|
|
170
|
+
push('op', i, end);
|
|
171
|
+
i = end;
|
|
172
|
+
}
|
|
173
|
+
return tokens;
|
|
174
|
+
}
|
|
175
|
+
/** Column of `offset` within its physical line (tabs count as 8, like Python). */
|
|
176
|
+
function columnOf(src, offset) {
|
|
177
|
+
let col = 0;
|
|
178
|
+
for (let k = src.lastIndexOf('\n', offset - 1) + 1; k < offset; k++) {
|
|
179
|
+
col = src[k] === '\t' ? col + 8 - (col % 8) : col + 1;
|
|
180
|
+
}
|
|
181
|
+
return col;
|
|
182
|
+
}
|
|
183
|
+
function lastTokenLine(line) {
|
|
184
|
+
const t = line.tokens[line.tokens.length - 1];
|
|
185
|
+
return t.line + (t.value.match(/\n/g)?.length ?? 0);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Group tokens into logical lines: a newline ends a statement unless it is
|
|
189
|
+
* inside brackets (or a string, or after a backslash continuation).
|
|
190
|
+
*/
|
|
191
|
+
function logicalLines(src, tokens) {
|
|
192
|
+
const lines = [];
|
|
193
|
+
let current = null;
|
|
194
|
+
let depth = 0;
|
|
195
|
+
let pendingComments = [];
|
|
196
|
+
for (const tok of tokens) {
|
|
197
|
+
if (tok.type === 'comment') {
|
|
198
|
+
if (current && depth > 0)
|
|
199
|
+
current.comments.push(tok);
|
|
200
|
+
else if (current && current.tokens.length && tok.line === lastTokenLine(current))
|
|
201
|
+
current.comments.push(tok);
|
|
202
|
+
else
|
|
203
|
+
pendingComments.push(tok);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// A token on a new physical line, outside brackets, starts a new statement —
|
|
207
|
+
// unless the previous token ended with a backslash continuation (no newline
|
|
208
|
+
// between them in tokens terms, detected by the source between them).
|
|
209
|
+
const prev = current?.tokens[current.tokens.length - 1];
|
|
210
|
+
const between = prev ? src.slice(prev.end, tok.start) : '';
|
|
211
|
+
const continued = /\\\r?\n/.test(between);
|
|
212
|
+
if (!current || (prev && depth === 0 && !continued && /\n/.test(between))) {
|
|
213
|
+
current = { indent: columnOf(src, tok.start), tokens: [], comments: pendingComments };
|
|
214
|
+
pendingComments = [];
|
|
215
|
+
lines.push(current);
|
|
216
|
+
}
|
|
217
|
+
if (tok.type === 'op') {
|
|
218
|
+
if (tok.value === '(' || tok.value === '[' || tok.value === '{')
|
|
219
|
+
depth++;
|
|
220
|
+
else if ((tok.value === ')' || tok.value === ']' || tok.value === '}') && depth > 0)
|
|
221
|
+
depth--;
|
|
222
|
+
}
|
|
223
|
+
if (depth === 0 && tok.type === 'op' && tok.value === ';') {
|
|
224
|
+
// `a = 1; b = 2` — split into separate statements at the same indent.
|
|
225
|
+
const indent = current.indent;
|
|
226
|
+
current = { indent, tokens: [], comments: [] };
|
|
227
|
+
lines.push(current);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
current.tokens.push(tok);
|
|
231
|
+
}
|
|
232
|
+
return lines.filter((l) => l.tokens.length > 0);
|
|
233
|
+
}
|
|
234
|
+
// ── String literal values ─────────────────────────────────────
|
|
235
|
+
const SIMPLE_ESCAPES = {
|
|
236
|
+
'\\': '\\', "'": "'", '"': '"', a: '\x07', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t', v: '\v',
|
|
237
|
+
};
|
|
238
|
+
/** The runtime value of a (non-f) string literal's body. */
|
|
239
|
+
function decodePythonString(body, raw) {
|
|
240
|
+
if (raw)
|
|
241
|
+
return body.replace(/\r\n/g, '\n');
|
|
242
|
+
let out = '';
|
|
243
|
+
for (let i = 0; i < body.length; i++) {
|
|
244
|
+
const ch = body[i];
|
|
245
|
+
if (ch === '\r' && body[i + 1] === '\n')
|
|
246
|
+
continue;
|
|
247
|
+
if (ch !== '\\') {
|
|
248
|
+
out += ch;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const next = body[i + 1];
|
|
252
|
+
if (next === undefined) {
|
|
253
|
+
out += '\\';
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
if (next === '\n') {
|
|
257
|
+
i++;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (next === '\r' && body[i + 2] === '\n') {
|
|
261
|
+
i += 2;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (next in SIMPLE_ESCAPES) {
|
|
265
|
+
out += SIMPLE_ESCAPES[next];
|
|
266
|
+
i++;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (/[0-7]/.test(next)) {
|
|
270
|
+
const m = /^[0-7]{1,3}/.exec(body.slice(i + 1))[0];
|
|
271
|
+
out += String.fromCharCode(parseInt(m, 8));
|
|
272
|
+
i += m.length;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const hex = next === 'x' ? 2 : next === 'u' ? 4 : next === 'U' ? 8 : 0;
|
|
276
|
+
if (hex) {
|
|
277
|
+
const digits = body.slice(i + 2, i + 2 + hex);
|
|
278
|
+
if (digits.length === hex && /^[0-9a-fA-F]+$/.test(digits)) {
|
|
279
|
+
out += String.fromCodePoint(parseInt(digits, 16));
|
|
280
|
+
i += 1 + hex;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Unknown escape (or \N{NAME}): Python keeps it as written.
|
|
285
|
+
out += ch;
|
|
286
|
+
}
|
|
287
|
+
return out;
|
|
288
|
+
}
|
|
289
|
+
/** A double-quoted Python string literal whose value is `value`. */
|
|
290
|
+
function pythonStringLiteral(value) {
|
|
291
|
+
let out = '"';
|
|
292
|
+
for (const ch of value) {
|
|
293
|
+
const code = ch.codePointAt(0);
|
|
294
|
+
if (ch === '\\')
|
|
295
|
+
out += '\\\\';
|
|
296
|
+
else if (ch === '"')
|
|
297
|
+
out += '\\"';
|
|
298
|
+
else if (ch === '\n')
|
|
299
|
+
out += '\\n';
|
|
300
|
+
else if (ch === '\t')
|
|
301
|
+
out += '\\t';
|
|
302
|
+
else if (ch === '\r')
|
|
303
|
+
out += '\\r';
|
|
304
|
+
else if (code < 0x20 || code === 0x7f)
|
|
305
|
+
out += `\\x${code.toString(16).padStart(2, '0')}`;
|
|
306
|
+
else
|
|
307
|
+
out += ch;
|
|
308
|
+
}
|
|
309
|
+
return out + '"';
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Split an f-string body into literal text and replacement fields. Returns
|
|
313
|
+
* null for fields this tool won't rewrite (nested fields in format specs).
|
|
314
|
+
*/
|
|
315
|
+
function parseFString(body, raw) {
|
|
316
|
+
const parts = [];
|
|
317
|
+
let text = '';
|
|
318
|
+
let i = 0;
|
|
319
|
+
const flush = () => {
|
|
320
|
+
if (text)
|
|
321
|
+
parts.push({ kind: 'text', value: decodePythonString(text, raw) });
|
|
322
|
+
text = '';
|
|
323
|
+
};
|
|
324
|
+
while (i < body.length) {
|
|
325
|
+
const ch = body[i];
|
|
326
|
+
if (ch === '\\' && !raw) {
|
|
327
|
+
text += body.slice(i, i + 2);
|
|
328
|
+
i += 2;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (ch === '{' && body[i + 1] === '{') {
|
|
332
|
+
text += '{';
|
|
333
|
+
i += 2;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (ch === '}' && body[i + 1] === '}') {
|
|
337
|
+
text += '}';
|
|
338
|
+
i += 2;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (ch === '{') {
|
|
342
|
+
flush();
|
|
343
|
+
const end = scanFieldEnd(body, i + 1);
|
|
344
|
+
if (end === -1)
|
|
345
|
+
return null;
|
|
346
|
+
const inner = body.slice(i + 1, end - 1);
|
|
347
|
+
// Split off !conversion and :format_spec at depth 0, outside strings.
|
|
348
|
+
let exprEnd = inner.length;
|
|
349
|
+
let conversion;
|
|
350
|
+
let formatSpec;
|
|
351
|
+
let depth = 0;
|
|
352
|
+
for (let k = 0; k < inner.length; k++) {
|
|
353
|
+
const c = inner[k];
|
|
354
|
+
if (c === '\'' || c === '"') {
|
|
355
|
+
const q = inner.startsWith(c.repeat(3), k) ? c.repeat(3) : c;
|
|
356
|
+
const close = inner.indexOf(q, k + q.length);
|
|
357
|
+
if (close === -1)
|
|
358
|
+
return null;
|
|
359
|
+
k = close + q.length - 1;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (c === '(' || c === '[' || c === '{')
|
|
363
|
+
depth++;
|
|
364
|
+
else if (c === ')' || c === ']' || c === '}')
|
|
365
|
+
depth--;
|
|
366
|
+
else if (depth === 0 && c === '!' && inner[k + 1] !== '=') {
|
|
367
|
+
exprEnd = k;
|
|
368
|
+
const rest = inner.slice(k + 1);
|
|
369
|
+
const colon = rest.indexOf(':');
|
|
370
|
+
conversion = colon === -1 ? rest : rest.slice(0, colon);
|
|
371
|
+
if (colon !== -1)
|
|
372
|
+
formatSpec = rest.slice(colon + 1);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
else if (depth === 0 && c === ':') {
|
|
376
|
+
exprEnd = k;
|
|
377
|
+
formatSpec = inner.slice(k + 1);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
let expr = inner.slice(0, exprEnd);
|
|
382
|
+
let debug = false;
|
|
383
|
+
if (/=\s*$/.test(expr) && !/[=!<>]=\s*$/.test(expr)) {
|
|
384
|
+
debug = true;
|
|
385
|
+
expr = expr.replace(/=\s*$/, '');
|
|
386
|
+
}
|
|
387
|
+
if (formatSpec !== undefined && formatSpec.includes('{'))
|
|
388
|
+
return null;
|
|
389
|
+
parts.push({ kind: 'field', value: expr.trim(), conversion, formatSpec, debug });
|
|
390
|
+
i = end;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
text += ch;
|
|
394
|
+
i++;
|
|
395
|
+
}
|
|
396
|
+
flush();
|
|
397
|
+
return parts;
|
|
398
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Django Template Language extraction.
|
|
3
|
+
*
|
|
4
|
+
* Finds strings already marked in templates — `{% translate %}` / `{% trans %}`,
|
|
5
|
+
* `{% blocktranslate %}` / `{% blocktrans %}` (with count/plural, context,
|
|
6
|
+
* with/trimmed/asvar) and `_("…")` in filter and tag arguments — and
|
|
7
|
+
* hardcoded user-facing text the injector can mark:
|
|
8
|
+
*
|
|
9
|
+
* - text in the page, as `{% translate "…" %}`, or as one
|
|
10
|
+
* `{% blocktranslate %}` when the sentence carries `{{ variables }}` or
|
|
11
|
+
* inline markup (`<a>`, `<strong>`, …). Variables with lookups or
|
|
12
|
+
* filters are bound with `with name=expr` because blocktranslate only
|
|
13
|
+
* accepts plain names;
|
|
14
|
+
* - title / alt / placeholder / aria-label attributes and button values;
|
|
15
|
+
* - string arguments of the `default` / `default_if_none` filters.
|
|
16
|
+
*
|
|
17
|
+
* Every edit is an exact replacement of the original span, so nothing else
|
|
18
|
+
* in the template (whitespace, attribute order, other tags) changes.
|
|
19
|
+
* Text inside <script>, <style>, <pre>, <code>, <textarea>, <svg>,
|
|
20
|
+
* {% comment %} and {% verbatim %} is never touched.
|
|
21
|
+
*/
|
|
22
|
+
import { type DjangoFileAnalysis, type TemplateEdit } from './types';
|
|
23
|
+
/** Split DTL tag contents into bits, keeping quoted strings (and _("…")) whole. */
|
|
24
|
+
export declare function smartSplit(text: string): string[];
|
|
25
|
+
/** Django's trim_whitespace() for {% blocktranslate trimmed %}. */
|
|
26
|
+
export declare function trimWhitespace(s: string): string;
|
|
27
|
+
export interface TemplateExtractOptions {
|
|
28
|
+
filePath: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function extractDjangoTemplate(src: string, _options: TemplateExtractOptions): DjangoFileAnalysis & {
|
|
31
|
+
extraEdits: TemplateEdit[];
|
|
32
|
+
};
|