transduck 0.5.2 → 0.5.3
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/dist/cli.js +1 -1
- package/dist/scanner.js +14 -0
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/scanner.ts +18 -0
- package/tests/scanner.test.ts +38 -0
package/dist/cli.js
CHANGED
|
@@ -493,7 +493,7 @@ export async function runStats(opts) {
|
|
|
493
493
|
}
|
|
494
494
|
// CLI entry point
|
|
495
495
|
const program = new Command();
|
|
496
|
-
program.name('transduck').description('AI-native translation tool').version('0.5.
|
|
496
|
+
program.name('transduck').description('AI-native translation tool').version('0.5.3');
|
|
497
497
|
program.command('init')
|
|
498
498
|
.description('Initialize a new transduck project')
|
|
499
499
|
.action(async () => {
|
package/dist/scanner.js
CHANGED
|
@@ -32,10 +32,24 @@ function shouldSkipDir(dirname) {
|
|
|
32
32
|
return true;
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Join adjacent string literals so the regex can match multi-line strings.
|
|
37
|
+
*
|
|
38
|
+
* Handles:
|
|
39
|
+
* - Python implicit concatenation: "foo" \n "bar" -> "foobar"
|
|
40
|
+
* - JS + concatenation: "foo" + \n "bar" -> "foobar"
|
|
41
|
+
* - Both single and double quotes
|
|
42
|
+
*/
|
|
43
|
+
function normalizeMultilineStrings(content) {
|
|
44
|
+
// Join adjacent string literals: "..." \s+ "..." or '...' \s+ '...'
|
|
45
|
+
// Also handles "..." + \s+ "..." for JS
|
|
46
|
+
return content.replace(/(['"])\s*\+?\s*\n\s*\1/g, '');
|
|
47
|
+
}
|
|
35
48
|
/**
|
|
36
49
|
* Extract translatable strings from file content.
|
|
37
50
|
*/
|
|
38
51
|
export function extractStrings(content, filename) {
|
|
52
|
+
content = normalizeMultilineStrings(content);
|
|
39
53
|
const results = [];
|
|
40
54
|
const ext = extname(filename).toLowerCase();
|
|
41
55
|
const isJs = JS_EXTENSIONS.has(ext);
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -611,7 +611,7 @@ export async function runStats(opts: StatsOptions): Promise<string> {
|
|
|
611
611
|
// CLI entry point
|
|
612
612
|
const program = new Command();
|
|
613
613
|
|
|
614
|
-
program.name('transduck').description('AI-native translation tool').version('0.5.
|
|
614
|
+
program.name('transduck').description('AI-native translation tool').version('0.5.3');
|
|
615
615
|
|
|
616
616
|
program.command('init')
|
|
617
617
|
.description('Initialize a new transduck project')
|
package/src/scanner.ts
CHANGED
|
@@ -57,10 +57,28 @@ function shouldSkipDir(dirname: string): boolean {
|
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Join adjacent string literals so the regex can match multi-line strings.
|
|
62
|
+
*
|
|
63
|
+
* Handles:
|
|
64
|
+
* - Python implicit concatenation: "foo" \n "bar" -> "foobar"
|
|
65
|
+
* - JS + concatenation: "foo" + \n "bar" -> "foobar"
|
|
66
|
+
* - Both single and double quotes
|
|
67
|
+
*/
|
|
68
|
+
function normalizeMultilineStrings(content: string): string {
|
|
69
|
+
// Join adjacent string literals: "..." \s+ "..." or '...' \s+ '...'
|
|
70
|
+
// Also handles "..." + \s+ "..." for JS
|
|
71
|
+
return content.replace(
|
|
72
|
+
/(['"])\s*\+?\s*\n\s*\1/g,
|
|
73
|
+
'',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
60
77
|
/**
|
|
61
78
|
* Extract translatable strings from file content.
|
|
62
79
|
*/
|
|
63
80
|
export function extractStrings(content: string, filename: string): ScanEntry[] {
|
|
81
|
+
content = normalizeMultilineStrings(content);
|
|
64
82
|
const results: ScanEntry[] = [];
|
|
65
83
|
const ext = extname(filename).toLowerCase();
|
|
66
84
|
const isJs = JS_EXTENSIONS.has(ext);
|
package/tests/scanner.test.ts
CHANGED
|
@@ -124,6 +124,44 @@ ait_plural("{count} item", "{count} items", count=n)
|
|
|
124
124
|
expect(result[0].one).toBe('{count} item');
|
|
125
125
|
expect(result[0].other).toBe('{count} items');
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
it('extracts multi-line Python implicit concatenation', () => {
|
|
129
|
+
const code = `ait(\n "This is a long "\n "paragraph that spans "\n "multiple lines"\n)`;
|
|
130
|
+
const result = extractStrings(code, 'test.py');
|
|
131
|
+
expect(result).toHaveLength(1);
|
|
132
|
+
expect(result[0].text).toBe('This is a long paragraph that spans multiple lines');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('extracts multi-line with context', () => {
|
|
136
|
+
const code = `ait(\n "This is a long "\n "paragraph",\n context="Some context"\n)`;
|
|
137
|
+
const result = extractStrings(code, 'test.py');
|
|
138
|
+
expect(result).toHaveLength(1);
|
|
139
|
+
expect(result[0].text).toBe('This is a long paragraph');
|
|
140
|
+
expect(result[0].context).toBe('Some context');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('extracts multi-line JS + concatenation', () => {
|
|
144
|
+
const code = `ait(\n "This is a long " +\n "paragraph that spans " +\n "multiple lines"\n)`;
|
|
145
|
+
const result = extractStrings(code, 'test.js');
|
|
146
|
+
expect(result).toHaveLength(1);
|
|
147
|
+
expect(result[0].text).toBe('This is a long paragraph that spans multiple lines');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('extracts multi-line plural strings', () => {
|
|
151
|
+
const code = `ait_plural(\n "{count} long item "\n "description here",\n "{count} long items "\n "description here",\n count=n\n)`;
|
|
152
|
+
const result = extractStrings(code, 'test.py');
|
|
153
|
+
expect(result).toHaveLength(1);
|
|
154
|
+
expect(result[0].plural).toBe(true);
|
|
155
|
+
expect(result[0].one).toBe('{count} long item description here');
|
|
156
|
+
expect(result[0].other).toBe('{count} long items description here');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('extracts multi-line single-quoted strings', () => {
|
|
160
|
+
const code = `ait(\n 'This is a long '\n 'paragraph'\n)`;
|
|
161
|
+
const result = extractStrings(code, 'test.py');
|
|
162
|
+
expect(result).toHaveLength(1);
|
|
163
|
+
expect(result[0].text).toBe('This is a long paragraph');
|
|
164
|
+
});
|
|
127
165
|
});
|
|
128
166
|
|
|
129
167
|
describe('scanDirectory', () => {
|