transduck 0.6.3 → 0.6.4
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 +11 -0
- package/package.json +1 -1
- package/src/cli.ts +1 -1
- package/src/scanner.ts +13 -0
- package/tests/scanner.test.ts +31 -0
package/dist/cli.js
CHANGED
|
@@ -544,7 +544,7 @@ export async function runStats(opts) {
|
|
|
544
544
|
}
|
|
545
545
|
// CLI entry point
|
|
546
546
|
const program = new Command();
|
|
547
|
-
program.name('transduck').description('AI-native translation tool').version('0.6.
|
|
547
|
+
program.name('transduck').description('AI-native translation tool').version('0.6.4');
|
|
548
548
|
program.command('init')
|
|
549
549
|
.description('Initialize a new transduck project')
|
|
550
550
|
.action(async () => {
|
package/dist/scanner.js
CHANGED
|
@@ -12,6 +12,8 @@ const AIT_POSITIONAL_CTX = /ait\s*\(\s*(['"])(.*?)\1(?:\s*,\s*(['"])(.*?)\3)?/g;
|
|
|
12
12
|
const AIT_PLURAL = /ait(?:_p|P)lural\s*\(\s*(['"])(.*?)\1\s*,\s*(['"])(.*?)\3/g;
|
|
13
13
|
// {% ait "text" %} or {% ait "text" context="ctx" %}
|
|
14
14
|
const DJANGO_TAG = /\{%\s*ait\s+(['"])(.*?)\1(?:\s+context=(['"])(.*?)\3)?\s*%\}/g;
|
|
15
|
+
// {% ait_plural "one" "other" %} or {% ait_plural "one" "other" context="ctx" %}
|
|
16
|
+
const DJANGO_PLURAL_TAG = /\{%\s*ait_plural\s+(['"])(.*?)\1\s+(['"])(.*?)\3(?:\s+context=(['"])(.*?)\5)?\s*%\}/g;
|
|
15
17
|
// t("text") or t("text", "ctx") — only matched in files with transduck/react import
|
|
16
18
|
const T_POSITIONAL = /(?<![a-zA-Z_.$])t\s*\(\s*(['"])(.*?)\1(?:\s*,\s*(['"])(.*?)\3)?/g;
|
|
17
19
|
// tPlural("one", "other") — only matched in files with transduck/react import
|
|
@@ -81,6 +83,15 @@ export function extractStrings(content, filename) {
|
|
|
81
83
|
const lineNum = content.slice(0, match.index).split('\n').length;
|
|
82
84
|
results.push({ text, context, line: lineNum });
|
|
83
85
|
}
|
|
86
|
+
const djangoPluralRegex = new RegExp(DJANGO_PLURAL_TAG.source, 'g');
|
|
87
|
+
while ((match = djangoPluralRegex.exec(content)) !== null) {
|
|
88
|
+
const one = match[2];
|
|
89
|
+
const other = match[4];
|
|
90
|
+
const context = match[6] || null;
|
|
91
|
+
const lineNum = content.slice(0, match.index).split('\n').length;
|
|
92
|
+
results.push({ plural: true, one, other, context, line: lineNum });
|
|
93
|
+
pluralSpans.push([match.index, match.index + match[0].length]);
|
|
94
|
+
}
|
|
84
95
|
}
|
|
85
96
|
// 3. Find ait() calls
|
|
86
97
|
const pattern = isJs ? AIT_POSITIONAL_CTX : AIT_KEYWORD_CTX;
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -661,7 +661,7 @@ export async function runStats(opts: StatsOptions): Promise<string> {
|
|
|
661
661
|
// CLI entry point
|
|
662
662
|
const program = new Command();
|
|
663
663
|
|
|
664
|
-
program.name('transduck').description('AI-native translation tool').version('0.6.
|
|
664
|
+
program.name('transduck').description('AI-native translation tool').version('0.6.4');
|
|
665
665
|
|
|
666
666
|
program.command('init')
|
|
667
667
|
.description('Initialize a new transduck project')
|
package/src/scanner.ts
CHANGED
|
@@ -31,6 +31,9 @@ const AIT_PLURAL = /ait(?:_p|P)lural\s*\(\s*(['"])(.*?)\1\s*,\s*(['"])(.*?)\3/g;
|
|
|
31
31
|
// {% ait "text" %} or {% ait "text" context="ctx" %}
|
|
32
32
|
const DJANGO_TAG = /\{%\s*ait\s+(['"])(.*?)\1(?:\s+context=(['"])(.*?)\3)?\s*%\}/g;
|
|
33
33
|
|
|
34
|
+
// {% ait_plural "one" "other" %} or {% ait_plural "one" "other" context="ctx" %}
|
|
35
|
+
const DJANGO_PLURAL_TAG = /\{%\s*ait_plural\s+(['"])(.*?)\1\s+(['"])(.*?)\3(?:\s+context=(['"])(.*?)\5)?\s*%\}/g;
|
|
36
|
+
|
|
34
37
|
// t("text") or t("text", "ctx") — only matched in files with transduck/react import
|
|
35
38
|
const T_POSITIONAL = /(?<![a-zA-Z_.$])t\s*\(\s*(['"])(.*?)\1(?:\s*,\s*(['"])(.*?)\3)?/g;
|
|
36
39
|
|
|
@@ -113,6 +116,16 @@ export function extractStrings(content: string, filename: string): ScanEntry[] {
|
|
|
113
116
|
const lineNum = content.slice(0, match.index).split('\n').length;
|
|
114
117
|
results.push({ text, context, line: lineNum });
|
|
115
118
|
}
|
|
119
|
+
|
|
120
|
+
const djangoPluralRegex = new RegExp(DJANGO_PLURAL_TAG.source, 'g');
|
|
121
|
+
while ((match = djangoPluralRegex.exec(content)) !== null) {
|
|
122
|
+
const one = match[2];
|
|
123
|
+
const other = match[4];
|
|
124
|
+
const context = match[6] || null;
|
|
125
|
+
const lineNum = content.slice(0, match.index).split('\n').length;
|
|
126
|
+
results.push({ plural: true, one, other, context, line: lineNum });
|
|
127
|
+
pluralSpans.push([match.index, match.index + match[0].length]);
|
|
128
|
+
}
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
// 3. Find ait() calls
|
package/tests/scanner.test.ts
CHANGED
|
@@ -156,6 +156,37 @@ ait_plural("{count} item", "{count} items", count=n)
|
|
|
156
156
|
expect(result[0].other).toBe('{count} long items description here');
|
|
157
157
|
});
|
|
158
158
|
|
|
159
|
+
it('extracts Django plural template tag', () => {
|
|
160
|
+
const result = extractStrings('{% ait_plural "{count} night" "{count} nights" %}', 'test.html');
|
|
161
|
+
expect(result).toHaveLength(1);
|
|
162
|
+
expect(result[0].plural).toBe(true);
|
|
163
|
+
expect(result[0].one).toBe('{count} night');
|
|
164
|
+
expect(result[0].other).toBe('{count} nights');
|
|
165
|
+
expect(result[0].context).toBeNull();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('extracts Django plural template tag with context', () => {
|
|
169
|
+
const result = extractStrings('{% ait_plural "{count} night" "{count} nights" context="Hotel stay duration" %}', 'test.html');
|
|
170
|
+
expect(result).toHaveLength(1);
|
|
171
|
+
expect(result[0].plural).toBe(true);
|
|
172
|
+
expect(result[0].one).toBe('{count} night');
|
|
173
|
+
expect(result[0].other).toBe('{count} nights');
|
|
174
|
+
expect(result[0].context).toBe('Hotel stay duration');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('extracts Django plural tag with single quotes', () => {
|
|
178
|
+
const result = extractStrings("{% ait_plural '{count} item' '{count} items' %}", 'test.jinja2');
|
|
179
|
+
expect(result).toHaveLength(1);
|
|
180
|
+
expect(result[0].plural).toBe(true);
|
|
181
|
+
expect(result[0].one).toBe('{count} item');
|
|
182
|
+
expect(result[0].other).toBe('{count} items');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('does not extract Django plural tag in .py files', () => {
|
|
186
|
+
const result = extractStrings('{% ait_plural "{count} night" "{count} nights" %}', 'test.py');
|
|
187
|
+
expect(result).toHaveLength(0);
|
|
188
|
+
});
|
|
189
|
+
|
|
159
190
|
it('extracts multi-line single-quoted strings', () => {
|
|
160
191
|
const code = `ait(\n 'This is a long '\n 'paragraph'\n)`;
|
|
161
192
|
const result = extractStrings(code, 'test.py');
|