transduck 0.1.5 → 0.2.1
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/backend.d.ts +7 -40
- package/dist/backend.js +13 -88
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +49 -45
- package/dist/config.d.ts +2 -0
- package/dist/config.js +18 -5
- package/dist/handler.js +2 -24
- package/dist/index.js +2 -15
- package/dist/providers/claude-api.d.ts +7 -0
- package/dist/providers/claude-api.js +50 -0
- package/dist/providers/claude-code.d.ts +8 -0
- package/dist/providers/claude-code.js +47 -0
- package/dist/providers/index.d.ts +12 -0
- package/dist/providers/index.js +20 -0
- package/dist/providers/openai-provider.d.ts +6 -0
- package/dist/providers/openai-provider.js +40 -0
- package/dist/providers/prompts.d.ts +42 -0
- package/dist/providers/prompts.js +89 -0
- package/package.json +7 -3
- package/src/backend.ts +35 -141
- package/src/cli.ts +79 -46
- package/src/config.ts +22 -5
- package/src/handler.ts +15 -22
- package/src/index.ts +8 -14
- package/src/providers/claude-api.ts +77 -0
- package/src/providers/claude-code.ts +72 -0
- package/src/providers/index.ts +45 -0
- package/src/providers/openai-provider.ts +67 -0
- package/src/providers/prompts.ts +124 -0
- package/tests/ait.test.ts +3 -0
- package/tests/backend.test.ts +61 -59
- package/tests/providers.test.ts +289 -0
package/dist/backend.d.ts
CHANGED
|
@@ -1,40 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
interface TranslateParams extends BuildMessagesParams {
|
|
9
|
-
apiKey: string;
|
|
10
|
-
model: string;
|
|
11
|
-
timeout: number;
|
|
12
|
-
maxRetries: number;
|
|
13
|
-
_clientOverride?: any;
|
|
14
|
-
}
|
|
15
|
-
interface BuildPluralMessagesParams {
|
|
16
|
-
one: string;
|
|
17
|
-
other: string;
|
|
18
|
-
sourceLang: string;
|
|
19
|
-
targetLang: string;
|
|
20
|
-
projectContext: string;
|
|
21
|
-
stringContext: string | null;
|
|
22
|
-
}
|
|
23
|
-
interface TranslatePluralParams extends BuildPluralMessagesParams {
|
|
24
|
-
apiKey: string;
|
|
25
|
-
model: string;
|
|
26
|
-
timeout: number;
|
|
27
|
-
maxRetries: number;
|
|
28
|
-
_clientOverride?: any;
|
|
29
|
-
}
|
|
30
|
-
export declare function buildMessages(params: BuildMessagesParams): Array<{
|
|
31
|
-
role: string;
|
|
32
|
-
content: string;
|
|
33
|
-
}>;
|
|
34
|
-
export declare function translate(params: TranslateParams): Promise<string>;
|
|
35
|
-
export declare function buildPluralMessages(params: BuildPluralMessagesParams): Array<{
|
|
36
|
-
role: string;
|
|
37
|
-
content: string;
|
|
38
|
-
}>;
|
|
39
|
-
export declare function translatePlural(params: TranslatePluralParams): Promise<Record<string, string>>;
|
|
40
|
-
export {};
|
|
1
|
+
/**
|
|
2
|
+
* Translation backend router -- delegates to the configured provider.
|
|
3
|
+
*/
|
|
4
|
+
import type { TransduckConfig } from './config.js';
|
|
5
|
+
export { buildMessages, buildPluralMessages } from './providers/prompts.js';
|
|
6
|
+
export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<string>;
|
|
7
|
+
export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<Record<string, string>>;
|
package/dist/backend.js
CHANGED
|
@@ -1,89 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'that will be replaced by code at runtime. You MUST include them exactly as written ' +
|
|
15
|
-
'in EVERY plural form, even for zero, one, and two categories where the language ' +
|
|
16
|
-
'would not normally use a numeral. For example, for Arabic zero: use "{count} ..." ' +
|
|
17
|
-
'not "لا توجد ...". For Arabic one: use "{count} ..." not "واحدة ...".\n\n' +
|
|
18
|
-
'Preserve brand names. Match the tone and formality of the original.\n\n' +
|
|
19
|
-
'CLDR plural categories are: zero, one, two, few, many, other.\n' +
|
|
20
|
-
'Only include categories that {target_lang} actually uses.\n\n' +
|
|
21
|
-
'Project context: {project_context}';
|
|
22
|
-
const PLURAL_USER_TEMPLATE = 'Source one form: "{one}"\n' +
|
|
23
|
-
'Source other form: "{other}"\n' +
|
|
24
|
-
'String context: {string_context}';
|
|
25
|
-
function safeRender(template, vars) {
|
|
26
|
-
let result = template;
|
|
27
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
28
|
-
result = result.replaceAll(`{${key}}`, value);
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
export function buildMessages(params) {
|
|
33
|
-
const systemMsg = SYSTEM_TEMPLATE
|
|
34
|
-
.replace('{source_lang}', params.sourceLang)
|
|
35
|
-
.replace('{target_lang}', params.targetLang)
|
|
36
|
-
.replace('{project_context}', params.projectContext);
|
|
37
|
-
const userMsg = USER_TEMPLATE
|
|
38
|
-
.replace('{source_text}', params.sourceText)
|
|
39
|
-
.replace('{string_context}', params.stringContext || 'none');
|
|
40
|
-
return [
|
|
41
|
-
{ role: 'system', content: systemMsg },
|
|
42
|
-
{ role: 'user', content: userMsg },
|
|
43
|
-
];
|
|
44
|
-
}
|
|
45
|
-
export async function translate(params) {
|
|
46
|
-
const client = params._clientOverride ?? new OpenAI({
|
|
47
|
-
apiKey: params.apiKey,
|
|
48
|
-
timeout: params.timeout * 1000,
|
|
49
|
-
maxRetries: params.maxRetries,
|
|
50
|
-
});
|
|
51
|
-
const messages = buildMessages(params);
|
|
52
|
-
const response = await client.chat.completions.create({
|
|
53
|
-
model: params.model,
|
|
54
|
-
messages,
|
|
55
|
-
temperature: 0.3,
|
|
56
|
-
});
|
|
57
|
-
return response.choices[0].message.content.trim();
|
|
58
|
-
}
|
|
59
|
-
export function buildPluralMessages(params) {
|
|
60
|
-
const systemMsg = safeRender(PLURAL_SYSTEM_TEMPLATE, {
|
|
61
|
-
source_lang: params.sourceLang,
|
|
62
|
-
target_lang: params.targetLang,
|
|
63
|
-
project_context: params.projectContext,
|
|
64
|
-
});
|
|
65
|
-
const userMsg = safeRender(PLURAL_USER_TEMPLATE, {
|
|
66
|
-
one: params.one,
|
|
67
|
-
other: params.other,
|
|
68
|
-
string_context: params.stringContext || 'none',
|
|
69
|
-
});
|
|
70
|
-
return [
|
|
71
|
-
{ role: 'system', content: systemMsg },
|
|
72
|
-
{ role: 'user', content: userMsg },
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
export async function translatePlural(params) {
|
|
76
|
-
const client = params._clientOverride ?? new OpenAI({
|
|
77
|
-
apiKey: params.apiKey,
|
|
78
|
-
timeout: params.timeout * 1000,
|
|
79
|
-
maxRetries: params.maxRetries,
|
|
80
|
-
});
|
|
81
|
-
const messages = buildPluralMessages(params);
|
|
82
|
-
const response = await client.chat.completions.create({
|
|
83
|
-
model: params.model,
|
|
84
|
-
messages,
|
|
85
|
-
temperature: 0.3,
|
|
86
|
-
});
|
|
87
|
-
const raw = response.choices[0].message.content.trim();
|
|
88
|
-
return JSON.parse(raw);
|
|
1
|
+
/**
|
|
2
|
+
* Translation backend router -- delegates to the configured provider.
|
|
3
|
+
*/
|
|
4
|
+
import { getProvider } from './providers/index.js';
|
|
5
|
+
// Re-export prompts for backward compat (tests import buildMessages from backend)
|
|
6
|
+
export { buildMessages, buildPluralMessages } from './providers/prompts.js';
|
|
7
|
+
export async function translate(sourceText, sourceLang, targetLang, projectContext, stringContext, config, _clientOverride) {
|
|
8
|
+
const provider = await getProvider(config);
|
|
9
|
+
return provider.translate(sourceText, sourceLang, targetLang, projectContext, stringContext, config, _clientOverride);
|
|
10
|
+
}
|
|
11
|
+
export async function translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config, _clientOverride) {
|
|
12
|
+
const provider = await getProvider(config);
|
|
13
|
+
return provider.translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config, _clientOverride);
|
|
89
14
|
}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -14,19 +14,53 @@ function hash(text) {
|
|
|
14
14
|
return createHash('sha256').update(text).digest('hex');
|
|
15
15
|
}
|
|
16
16
|
export async function runInit(opts) {
|
|
17
|
+
const providerChoice = opts.provider ?? 1;
|
|
17
18
|
const config = {
|
|
18
19
|
project: { name: opts.name, context: opts.context },
|
|
19
20
|
languages: { source: opts.sourceLang.toUpperCase(), targets: opts.targetLangs.map(l => l.toUpperCase()) },
|
|
20
21
|
storage: { path: './translations.duckdb' },
|
|
21
|
-
backend: { api_key_env: 'OPENAI_API_KEY', model: 'gpt-4.1-mini', timeout_seconds: 10, max_retries: 2 },
|
|
22
22
|
};
|
|
23
|
+
if (providerChoice === 2) {
|
|
24
|
+
config.backend = {
|
|
25
|
+
provider: 'claude_api',
|
|
26
|
+
api_key_env: 'ANTHROPIC_API_KEY',
|
|
27
|
+
model: 'claude-haiku-4-5-20251001',
|
|
28
|
+
timeout_seconds: 10,
|
|
29
|
+
max_retries: 2,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
else if (providerChoice === 3) {
|
|
33
|
+
config.backend = {
|
|
34
|
+
provider: 'claude_code',
|
|
35
|
+
token_env: 'CLAUDE_CODE_OAUTH_TOKEN',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
config.backend = {
|
|
40
|
+
provider: 'openai',
|
|
41
|
+
api_key_env: 'OPENAI_API_KEY',
|
|
42
|
+
model: 'gpt-4.1-mini',
|
|
43
|
+
timeout_seconds: 10,
|
|
44
|
+
max_retries: 2,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
23
47
|
const configPath = join(opts.dir, 'transduck.yaml');
|
|
24
48
|
writeFileSync(configPath, yamlStringify(config));
|
|
25
49
|
const dbPath = join(opts.dir, 'translations.duckdb');
|
|
26
50
|
const store = new TranslationStore(dbPath);
|
|
27
51
|
await store.initialize();
|
|
28
52
|
store.close();
|
|
29
|
-
|
|
53
|
+
const lines = [`Created ${configPath}`, `Created ${dbPath}`];
|
|
54
|
+
if (providerChoice === 2) {
|
|
55
|
+
lines.push('', 'Add to your .env file: ANTHROPIC_API_KEY=your-key-here');
|
|
56
|
+
}
|
|
57
|
+
else if (providerChoice === 3) {
|
|
58
|
+
lines.push('', "Run 'claude setup-token' to get your OAuth token, then add to your .env file:", ' CLAUDE_CODE_OAUTH_TOKEN=your-token-here', '', 'Note: claude_code works for CLI warming only. Your app will run in read-only mode.');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
lines.push('', 'Add to your .env file: OPENAI_API_KEY=your-key-here');
|
|
62
|
+
}
|
|
63
|
+
return lines.join('\n');
|
|
30
64
|
}
|
|
31
65
|
export async function runTranslate(opts) {
|
|
32
66
|
const cfg = loadConfig(opts.configPath);
|
|
@@ -43,13 +77,7 @@ export async function runTranslate(opts) {
|
|
|
43
77
|
store.close();
|
|
44
78
|
return `[cached] ${interpolateVars(cached, opts.vars)}`;
|
|
45
79
|
}
|
|
46
|
-
const
|
|
47
|
-
const translated = await backendTranslate({
|
|
48
|
-
sourceText: opts.text, sourceLang: cfg.sourceLang, targetLang,
|
|
49
|
-
projectContext: cfg.projectContext, stringContext: opts.stringContext ?? null,
|
|
50
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
51
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
52
|
-
});
|
|
80
|
+
const translated = await backendTranslate(opts.text, cfg.sourceLang, targetLang, cfg.projectContext, opts.stringContext ?? null, cfg);
|
|
53
81
|
if (!validateTranslation(opts.text, translated)) {
|
|
54
82
|
await store.insert({
|
|
55
83
|
sourceText: opts.text, sourceLang: cfg.sourceLang, targetLang,
|
|
@@ -93,14 +121,7 @@ export async function runTranslatePlural(opts) {
|
|
|
93
121
|
}
|
|
94
122
|
// Cache miss — call backend
|
|
95
123
|
try {
|
|
96
|
-
const
|
|
97
|
-
const forms = await backendTranslatePlural({
|
|
98
|
-
one: opts.one, other: opts.other,
|
|
99
|
-
sourceLang: cfg.sourceLang, targetLang,
|
|
100
|
-
projectContext: cfg.projectContext, stringContext: opts.stringContext ?? null,
|
|
101
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
102
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
103
|
-
});
|
|
124
|
+
const forms = await backendTranslatePlural(opts.one, opts.other, cfg.sourceLang, targetLang, cfg.projectContext, opts.stringContext ?? null, cfg);
|
|
104
125
|
// Validate each form
|
|
105
126
|
const sourcePlaceholders = new Set([
|
|
106
127
|
...extractPlaceholders(opts.one),
|
|
@@ -181,7 +202,6 @@ export async function runWarm(opts) {
|
|
|
181
202
|
else {
|
|
182
203
|
entries = content.split('\n').filter(l => l.trim()).map(text => ({ text: text.trim() }));
|
|
183
204
|
}
|
|
184
|
-
const apiKey = process.env[cfg.apiKeyEnv];
|
|
185
205
|
const projectContextHash = hash(cfg.projectContext);
|
|
186
206
|
let translated = 0, skipped = 0, failed = 0;
|
|
187
207
|
for (const entry of entries) {
|
|
@@ -199,13 +219,7 @@ export async function runWarm(opts) {
|
|
|
199
219
|
continue;
|
|
200
220
|
}
|
|
201
221
|
try {
|
|
202
|
-
const forms = await backendTranslatePlural(
|
|
203
|
-
one: entry.one, other: entry.other,
|
|
204
|
-
sourceLang: cfg.sourceLang, targetLang: lang,
|
|
205
|
-
projectContext: cfg.projectContext, stringContext: entry.context ?? null,
|
|
206
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
207
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
208
|
-
});
|
|
222
|
+
const forms = await backendTranslatePlural(entry.one, entry.other, cfg.sourceLang, lang, cfg.projectContext, entry.context ?? null, cfg);
|
|
209
223
|
const sourcePlaceholders = new Set([
|
|
210
224
|
...extractPlaceholders(entry.one),
|
|
211
225
|
...extractPlaceholders(entry.other),
|
|
@@ -254,12 +268,7 @@ export async function runWarm(opts) {
|
|
|
254
268
|
continue;
|
|
255
269
|
}
|
|
256
270
|
try {
|
|
257
|
-
const result = await backendTranslate(
|
|
258
|
-
sourceText: entry.text, sourceLang: cfg.sourceLang, targetLang: lang,
|
|
259
|
-
projectContext: cfg.projectContext, stringContext: entry.context ?? null,
|
|
260
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
261
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
262
|
-
});
|
|
271
|
+
const result = await backendTranslate(entry.text, cfg.sourceLang, lang, cfg.projectContext, entry.context ?? null, cfg);
|
|
263
272
|
if (validateTranslation(entry.text, result)) {
|
|
264
273
|
await store.insert({
|
|
265
274
|
sourceText: entry.text, sourceLang: cfg.sourceLang, targetLang: lang,
|
|
@@ -333,7 +342,6 @@ export async function runScan(opts) {
|
|
|
333
342
|
: cfg.targetLangs;
|
|
334
343
|
const store = new TranslationStore(cfg.storagePath);
|
|
335
344
|
await store.initialize();
|
|
336
|
-
const apiKey = process.env[cfg.apiKeyEnv];
|
|
337
345
|
const projectContextHash = hash(cfg.projectContext);
|
|
338
346
|
let translated = 0;
|
|
339
347
|
let skipped = 0;
|
|
@@ -352,13 +360,7 @@ export async function runScan(opts) {
|
|
|
352
360
|
continue;
|
|
353
361
|
}
|
|
354
362
|
try {
|
|
355
|
-
const forms = await backendTranslatePlural(
|
|
356
|
-
one: entry.one, other: entry.other,
|
|
357
|
-
sourceLang: cfg.sourceLang, targetLang: lang,
|
|
358
|
-
projectContext: cfg.projectContext, stringContext: entry.context ?? null,
|
|
359
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
360
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
361
|
-
});
|
|
363
|
+
const forms = await backendTranslatePlural(entry.one, entry.other, cfg.sourceLang, lang, cfg.projectContext, entry.context ?? null, cfg);
|
|
362
364
|
for (const [cat, translatedText] of Object.entries(forms)) {
|
|
363
365
|
await store.insertPlural({
|
|
364
366
|
sourceText: sourceKey, sourceLang: cfg.sourceLang, targetLang: lang,
|
|
@@ -386,12 +388,7 @@ export async function runScan(opts) {
|
|
|
386
388
|
continue;
|
|
387
389
|
}
|
|
388
390
|
try {
|
|
389
|
-
const result = await backendTranslate(
|
|
390
|
-
sourceText: entry.text, sourceLang: cfg.sourceLang, targetLang: lang,
|
|
391
|
-
projectContext: cfg.projectContext, stringContext: entry.context ?? null,
|
|
392
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
393
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
394
|
-
});
|
|
391
|
+
const result = await backendTranslate(entry.text, cfg.sourceLang, lang, cfg.projectContext, entry.context ?? null, cfg);
|
|
395
392
|
if (validateTranslation(entry.text, result)) {
|
|
396
393
|
await store.insert({
|
|
397
394
|
sourceText: entry.text, sourceLang: cfg.sourceLang, targetLang: lang,
|
|
@@ -450,10 +447,17 @@ program.command('init')
|
|
|
450
447
|
const context = await ask('Project context: ');
|
|
451
448
|
const sourceLang = await ask('Source language (e.g. EN): ');
|
|
452
449
|
const targetsRaw = await ask('Target languages (comma-separated): ');
|
|
450
|
+
console.log('\nTranslation provider:');
|
|
451
|
+
console.log(' 1. OpenAI (requires API key)');
|
|
452
|
+
console.log(' 2. Claude API (requires API key)');
|
|
453
|
+
console.log(' 3. Claude Code (uses your Claude Code subscription, warming only)');
|
|
454
|
+
const providerRaw = await ask('Select provider [1]: ');
|
|
455
|
+
const providerChoice = parseInt(providerRaw, 10) || 1;
|
|
453
456
|
rl.close();
|
|
454
457
|
const output = await runInit({
|
|
455
458
|
dir, name, context, sourceLang,
|
|
456
459
|
targetLangs: targetsRaw.split(',').map(s => s.trim()),
|
|
460
|
+
provider: providerChoice,
|
|
457
461
|
});
|
|
458
462
|
console.log(output);
|
|
459
463
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -29,16 +29,29 @@ export function loadConfig(path) {
|
|
|
29
29
|
const raw = parseYaml(readFileSync(configPath, 'utf-8'));
|
|
30
30
|
const configDir = dirname(configPath);
|
|
31
31
|
const storagePath = resolve(configDir, raw.storage.path);
|
|
32
|
+
const backend = raw.backend ?? {};
|
|
33
|
+
const provider = backend.provider ?? 'openai';
|
|
34
|
+
const apiKeyEnv = backend.api_key_env ?? 'OPENAI_API_KEY';
|
|
35
|
+
const tokenEnv = backend.token_env ?? 'CLAUDE_CODE_OAUTH_TOKEN';
|
|
36
|
+
const backendModel = backend.model ?? 'gpt-4.1-mini';
|
|
37
|
+
const backendTimeout = backend.timeout_seconds ?? 10;
|
|
38
|
+
const backendMaxRetries = backend.max_retries ?? 2;
|
|
39
|
+
let readOnly = raw.runtime?.read_only ?? false;
|
|
40
|
+
if (provider === 'claude_code') {
|
|
41
|
+
readOnly = true;
|
|
42
|
+
}
|
|
32
43
|
return {
|
|
33
44
|
projectName: raw.project.name,
|
|
34
45
|
projectContext: raw.project.context,
|
|
35
46
|
sourceLang: String(raw.languages.source).toUpperCase(),
|
|
36
47
|
targetLangs: raw.languages.targets.map((l) => String(l).toUpperCase()),
|
|
37
48
|
storagePath,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
provider,
|
|
50
|
+
apiKeyEnv,
|
|
51
|
+
tokenEnv,
|
|
52
|
+
backendModel,
|
|
53
|
+
backendTimeout,
|
|
54
|
+
backendMaxRetries,
|
|
55
|
+
readOnly,
|
|
43
56
|
};
|
|
44
57
|
}
|
package/dist/handler.js
CHANGED
|
@@ -27,7 +27,6 @@ export async function handleTranslationRequest(body, configPath) {
|
|
|
27
27
|
const store = await getStore(configPath);
|
|
28
28
|
const targetLang = body.language.toUpperCase();
|
|
29
29
|
const projectContextHash = hash(cfg.projectContext);
|
|
30
|
-
const apiKey = process.env[cfg.apiKeyEnv];
|
|
31
30
|
const translations = {};
|
|
32
31
|
const plurals = {};
|
|
33
32
|
// Translate regular strings
|
|
@@ -48,17 +47,7 @@ export async function handleTranslationRequest(body, configPath) {
|
|
|
48
47
|
}
|
|
49
48
|
// Backend call
|
|
50
49
|
try {
|
|
51
|
-
const translated = await backendTranslate(
|
|
52
|
-
sourceText: item.text,
|
|
53
|
-
sourceLang: cfg.sourceLang,
|
|
54
|
-
targetLang,
|
|
55
|
-
projectContext: cfg.projectContext,
|
|
56
|
-
stringContext: item.context ?? null,
|
|
57
|
-
apiKey: apiKey,
|
|
58
|
-
model: cfg.backendModel,
|
|
59
|
-
timeout: cfg.backendTimeout,
|
|
60
|
-
maxRetries: cfg.backendMaxRetries,
|
|
61
|
-
});
|
|
50
|
+
const translated = await backendTranslate(item.text, cfg.sourceLang, targetLang, cfg.projectContext, item.context ?? null, cfg);
|
|
62
51
|
if (validateTranslation(item.text, translated)) {
|
|
63
52
|
await store.insert({
|
|
64
53
|
sourceText: item.text,
|
|
@@ -99,18 +88,7 @@ export async function handleTranslationRequest(body, configPath) {
|
|
|
99
88
|
}
|
|
100
89
|
// Backend call
|
|
101
90
|
try {
|
|
102
|
-
const forms = await backendTranslatePlural(
|
|
103
|
-
one: item.one,
|
|
104
|
-
other: item.other,
|
|
105
|
-
sourceLang: cfg.sourceLang,
|
|
106
|
-
targetLang,
|
|
107
|
-
projectContext: cfg.projectContext,
|
|
108
|
-
stringContext: item.context ?? null,
|
|
109
|
-
apiKey: apiKey,
|
|
110
|
-
model: cfg.backendModel,
|
|
111
|
-
timeout: cfg.backendTimeout,
|
|
112
|
-
maxRetries: cfg.backendMaxRetries,
|
|
113
|
-
});
|
|
91
|
+
const forms = await backendTranslatePlural(item.one, item.other, cfg.sourceLang, targetLang, cfg.projectContext, item.context ?? null, cfg);
|
|
114
92
|
for (const [cat, translatedText] of Object.entries(forms)) {
|
|
115
93
|
await store.insertPlural({
|
|
116
94
|
sourceText: sourceKey,
|
package/dist/index.js
CHANGED
|
@@ -67,14 +67,8 @@ export async function ait(sourceText, context, vars) {
|
|
|
67
67
|
});
|
|
68
68
|
if (rechecked !== null)
|
|
69
69
|
return rechecked;
|
|
70
|
-
const apiKey = process.env[cfg.apiKeyEnv];
|
|
71
70
|
try {
|
|
72
|
-
const translated = await backendTranslate(
|
|
73
|
-
sourceText, sourceLang: cfg.sourceLang, targetLang: state.targetLang,
|
|
74
|
-
projectContext: cfg.projectContext, stringContext: context ?? null,
|
|
75
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
76
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
77
|
-
});
|
|
71
|
+
const translated = await backendTranslate(sourceText, cfg.sourceLang, state.targetLang, cfg.projectContext, context ?? null, cfg);
|
|
78
72
|
if (!validateTranslation(sourceText, translated)) {
|
|
79
73
|
console.warn(`[transduck] Validation failed for: ${sourceText} -> ${translated}`);
|
|
80
74
|
await state.store.insert({
|
|
@@ -153,15 +147,8 @@ export async function aitPlural(one, other, count, opts) {
|
|
|
153
147
|
return interpolateVars(fallback, vars);
|
|
154
148
|
}
|
|
155
149
|
// Cache miss — call backend
|
|
156
|
-
const apiKey = process.env[cfg.apiKeyEnv];
|
|
157
150
|
try {
|
|
158
|
-
const forms = await backendTranslatePlural(
|
|
159
|
-
one, other,
|
|
160
|
-
sourceLang: cfg.sourceLang, targetLang: state.targetLang,
|
|
161
|
-
projectContext: cfg.projectContext, stringContext: context ?? null,
|
|
162
|
-
apiKey: apiKey, model: cfg.backendModel,
|
|
163
|
-
timeout: cfg.backendTimeout, maxRetries: cfg.backendMaxRetries,
|
|
164
|
-
});
|
|
151
|
+
const forms = await backendTranslatePlural(one, other, cfg.sourceLang, state.targetLang, cfg.projectContext, context ?? null, cfg);
|
|
165
152
|
// Validate and store each form
|
|
166
153
|
const validCategories = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);
|
|
167
154
|
const sourcePlaceholders = new Set([
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude API translation provider.
|
|
3
|
+
* Uses the @anthropic-ai/sdk package (optional peer dependency, lazy imported).
|
|
4
|
+
*/
|
|
5
|
+
import type { TransduckConfig } from '../config.js';
|
|
6
|
+
export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<string>;
|
|
7
|
+
export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude API translation provider.
|
|
3
|
+
* Uses the @anthropic-ai/sdk package (optional peer dependency, lazy imported).
|
|
4
|
+
*/
|
|
5
|
+
import { buildMessages, buildPluralMessages } from './prompts.js';
|
|
6
|
+
async function getClient(config) {
|
|
7
|
+
let Anthropic;
|
|
8
|
+
try {
|
|
9
|
+
// @ts-ignore — optional peer dependency, may not be installed
|
|
10
|
+
const mod = await import('@anthropic-ai/sdk');
|
|
11
|
+
Anthropic = mod.default ?? mod.Anthropic;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
throw new Error('Install the required package: npm install @anthropic-ai/sdk');
|
|
15
|
+
}
|
|
16
|
+
const apiKey = process.env[config.apiKeyEnv];
|
|
17
|
+
if (!apiKey) {
|
|
18
|
+
throw new Error(`Set ${config.apiKeyEnv} environment variable`);
|
|
19
|
+
}
|
|
20
|
+
return new Anthropic({ apiKey });
|
|
21
|
+
}
|
|
22
|
+
export async function translate(sourceText, sourceLang, targetLang, projectContext, stringContext, config) {
|
|
23
|
+
const client = await getClient(config);
|
|
24
|
+
const messages = buildMessages({
|
|
25
|
+
sourceText, sourceLang, targetLang, projectContext, stringContext,
|
|
26
|
+
});
|
|
27
|
+
const response = await client.messages.create({
|
|
28
|
+
model: config.backendModel,
|
|
29
|
+
max_tokens: 1024,
|
|
30
|
+
temperature: 0.3,
|
|
31
|
+
system: messages[0].content,
|
|
32
|
+
messages: [{ role: 'user', content: messages[1].content }],
|
|
33
|
+
});
|
|
34
|
+
return response.content[0].text.trim();
|
|
35
|
+
}
|
|
36
|
+
export async function translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config) {
|
|
37
|
+
const client = await getClient(config);
|
|
38
|
+
const messages = buildPluralMessages({
|
|
39
|
+
one, other, sourceLang, targetLang, projectContext, stringContext,
|
|
40
|
+
});
|
|
41
|
+
const response = await client.messages.create({
|
|
42
|
+
model: config.backendModel,
|
|
43
|
+
max_tokens: 2048,
|
|
44
|
+
temperature: 0.3,
|
|
45
|
+
system: messages[0].content,
|
|
46
|
+
messages: [{ role: 'user', content: messages[1].content }],
|
|
47
|
+
});
|
|
48
|
+
const raw = response.content[0].text.trim();
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code translation provider (uses Claude Agent SDK with subscription auth).
|
|
3
|
+
* Uses the @anthropic-ai/claude-agent-sdk package (optional peer dependency, lazy imported).
|
|
4
|
+
* Already async in JS — no asyncio bridge needed like Python.
|
|
5
|
+
*/
|
|
6
|
+
import type { TransduckConfig } from '../config.js';
|
|
7
|
+
export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<string>;
|
|
8
|
+
export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig): Promise<Record<string, string>>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code translation provider (uses Claude Agent SDK with subscription auth).
|
|
3
|
+
* Uses the @anthropic-ai/claude-agent-sdk package (optional peer dependency, lazy imported).
|
|
4
|
+
* Already async in JS — no asyncio bridge needed like Python.
|
|
5
|
+
*/
|
|
6
|
+
import { buildSinglePrompt, buildPluralSinglePrompt } from './prompts.js';
|
|
7
|
+
function ensureToken(config) {
|
|
8
|
+
const token = process.env[config.tokenEnv];
|
|
9
|
+
if (!token) {
|
|
10
|
+
throw new Error(`Set ${config.tokenEnv} \u2014 run 'claude setup-token' to get your OAuth token`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function translateWithSdk(prompt) {
|
|
14
|
+
let query;
|
|
15
|
+
try {
|
|
16
|
+
// @ts-ignore — optional peer dependency, may not be installed
|
|
17
|
+
const mod = await import('@anthropic-ai/claude-agent-sdk');
|
|
18
|
+
query = mod.query;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error('Install the required package: npm install @anthropic-ai/claude-agent-sdk');
|
|
22
|
+
}
|
|
23
|
+
for await (const message of query({
|
|
24
|
+
prompt,
|
|
25
|
+
options: { allowedTools: [] },
|
|
26
|
+
})) {
|
|
27
|
+
if ('result' in message) {
|
|
28
|
+
return message.result.trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
throw new Error('No result from Claude Agent SDK');
|
|
32
|
+
}
|
|
33
|
+
export async function translate(sourceText, sourceLang, targetLang, projectContext, stringContext, config) {
|
|
34
|
+
ensureToken(config);
|
|
35
|
+
const prompt = buildSinglePrompt({
|
|
36
|
+
sourceText, sourceLang, targetLang, projectContext, stringContext,
|
|
37
|
+
});
|
|
38
|
+
return translateWithSdk(prompt);
|
|
39
|
+
}
|
|
40
|
+
export async function translatePlural(one, other, sourceLang, targetLang, projectContext, stringContext, config) {
|
|
41
|
+
ensureToken(config);
|
|
42
|
+
const prompt = buildPluralSinglePrompt({
|
|
43
|
+
one, other, sourceLang, targetLang, projectContext, stringContext,
|
|
44
|
+
});
|
|
45
|
+
const raw = await translateWithSdk(prompt);
|
|
46
|
+
return JSON.parse(raw);
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation provider abstraction.
|
|
3
|
+
*/
|
|
4
|
+
import type { TransduckConfig } from '../config.js';
|
|
5
|
+
export interface TranslationProvider {
|
|
6
|
+
translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<string>;
|
|
7
|
+
translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<Record<string, string>>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Return the provider module for the configured provider.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getProvider(config: TransduckConfig): Promise<TranslationProvider>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translation provider abstraction.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Return the provider module for the configured provider.
|
|
6
|
+
*/
|
|
7
|
+
export async function getProvider(config) {
|
|
8
|
+
if (config.provider === 'openai') {
|
|
9
|
+
return await import('./openai-provider.js');
|
|
10
|
+
}
|
|
11
|
+
else if (config.provider === 'claude_api') {
|
|
12
|
+
return await import('./claude-api.js');
|
|
13
|
+
}
|
|
14
|
+
else if (config.provider === 'claude_code') {
|
|
15
|
+
return await import('./claude-code.js');
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
throw new Error(`Unknown provider: ${config.provider}. Valid: openai, claude_api, claude_code`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI translation provider.
|
|
3
|
+
*/
|
|
4
|
+
import type { TransduckConfig } from '../config.js';
|
|
5
|
+
export declare function translate(sourceText: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<string>;
|
|
6
|
+
export declare function translatePlural(one: string, other: string, sourceLang: string, targetLang: string, projectContext: string, stringContext: string | null, config: TransduckConfig, _clientOverride?: any): Promise<Record<string, string>>;
|