gptrans 2.1.10 → 2.2.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/.agents/skills/modelmix/SKILL.md +17 -8
- package/README.md +19 -12
- package/demo/case_1.js +7 -3
- package/demo/case_2.js +6 -1
- package/demo/case_3.js +6 -1
- package/demo/case_4.js +6 -8
- package/demo/case_language_info.js +6 -0
- package/demo/case_parallelism.js +4 -9
- package/demo/case_references.js +8 -9
- package/demo/case_refine.js +5 -0
- package/demo/example-image-translation.js +4 -8
- package/index.js +104 -98
- package/package.json +6 -5
- package/pnpm-workspace.yaml +5 -0
- package/prompt/refine.md +4 -4
- package/prompt/translate.md +7 -7
- package/skills/gptrans/SKILL.md +22 -16
- package/test/gptrans.batch.test.js +31 -19
- package/test/gptrans.tAsync.test.js +156 -2
- package/test-image-translation.js +2 -6
package/index.js
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
import DeepBase from 'deepbase';
|
|
2
|
+
import { JsonDriver } from 'deepbase-json';
|
|
2
3
|
import stringHash from 'string-hash';
|
|
3
4
|
import { ModelMix } from 'modelmix';
|
|
4
5
|
import { isoAssoc, isLanguageAvailable } from './isoAssoc.js';
|
|
5
6
|
import { GeminiGenerator } from 'genmix';
|
|
6
7
|
import fs from 'fs';
|
|
7
|
-
import
|
|
8
|
+
import pathModule from 'path';
|
|
9
|
+
|
|
10
|
+
const TRANSLATE_PROMPT_FILE = pathModule.join(import.meta.dirname, 'prompt', 'translate.md');
|
|
11
|
+
const REFINE_PROMPT_FILE = pathModule.join(import.meta.dirname, 'prompt', 'refine.md');
|
|
12
|
+
const DEFAULT_MODEL_CHAIN = Object.freeze(['sonnet5@50', 'gpt56luna@100']);
|
|
8
13
|
|
|
9
14
|
class GPTrans {
|
|
10
15
|
static #mmixInstances = new Map();
|
|
11
16
|
static #translationLocks = new Map();
|
|
12
17
|
|
|
13
|
-
static mmix(models =
|
|
14
|
-
const
|
|
18
|
+
static mmix(models = DEFAULT_MODEL_CHAIN, { debug = 0 } = {}) {
|
|
19
|
+
const modelChain = Array.isArray(models) ? models : [models];
|
|
20
|
+
const key = modelChain.join(',');
|
|
15
21
|
|
|
16
22
|
if (!this.#mmixInstances.has(key)) {
|
|
17
|
-
|
|
23
|
+
const instance = ModelMix.new({
|
|
18
24
|
config: {
|
|
19
25
|
debug,
|
|
20
26
|
bottleneck: {
|
|
@@ -22,18 +28,7 @@ class GPTrans {
|
|
|
22
28
|
maxConcurrent: 1
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
|
-
});
|
|
26
|
-
const modelArray = Array.isArray(models) ? models : [models];
|
|
27
|
-
|
|
28
|
-
for (const model of modelArray) {
|
|
29
|
-
if (typeof instance[model] !== 'function') {
|
|
30
|
-
throw new Error(
|
|
31
|
-
`Model "${model}" is not available. Please check the model name. ` +
|
|
32
|
-
`Available models include: gpt51, gpt52, sonnet46, sonnet45, opus46, haiku45, etc.`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
instance = instance[model]();
|
|
36
|
-
}
|
|
31
|
+
}).chain(...modelChain);
|
|
37
32
|
|
|
38
33
|
this.#mmixInstances.set(key, instance);
|
|
39
34
|
}
|
|
@@ -62,23 +57,21 @@ class GPTrans {
|
|
|
62
57
|
return isLanguageAvailable(langCode);
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
constructor({ from = 'en-US', target = 'es', model =
|
|
60
|
+
constructor({ from = 'en-US', target = 'es', model = DEFAULT_MODEL_CHAIN, batchThreshold = 1500, debounceTimeout = 500, promptFile = null, name = '', context = '', instruction = '', freeze = false, debug = false, path: dbPath } = {}) {
|
|
61
|
+
|
|
62
|
+
if (typeof dbPath !== 'string' || !pathModule.isAbsolute(dbPath)) {
|
|
63
|
+
throw new TypeError('GPTrans requires an absolute "path" option.');
|
|
64
|
+
}
|
|
66
65
|
|
|
67
66
|
target = this.normalizeBCP47(target);
|
|
68
67
|
from = this.normalizeBCP47(from);
|
|
69
68
|
|
|
70
|
-
try {
|
|
71
|
-
process.loadEnvFile();
|
|
72
|
-
} catch {
|
|
73
|
-
/* optional .env missing or unreadable */
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const path = new URL('../../db', import.meta.url).pathname;
|
|
77
69
|
const namePrefix = name ? '_' + name : '';
|
|
78
|
-
this.dbPath =
|
|
70
|
+
this.dbPath = dbPath;
|
|
79
71
|
this.instanceName = name;
|
|
80
|
-
this.dbTarget =
|
|
81
|
-
this.dbFrom =
|
|
72
|
+
this.dbTarget = this._createDatabase('gptrans' + namePrefix + '_' + target);
|
|
73
|
+
this.dbFrom = this._createDatabase('gptrans' + namePrefix + '_from_' + from);
|
|
74
|
+
this.pendingSourceWrites = Promise.resolve();
|
|
82
75
|
|
|
83
76
|
try {
|
|
84
77
|
this.replaceTarget = isoAssoc(target, 'TARGET_');
|
|
@@ -95,7 +88,7 @@ class GPTrans {
|
|
|
95
88
|
this.isProcessingBatch = false; // Track if a batch is currently being processed
|
|
96
89
|
this.modelMixOptions = { debug };
|
|
97
90
|
this.modelKey = model;
|
|
98
|
-
this.promptFile = promptFile
|
|
91
|
+
this.promptFile = promptFile ? pathModule.resolve(promptFile) : TRANSLATE_PROMPT_FILE;
|
|
99
92
|
this.context = context;
|
|
100
93
|
this.instruction = instruction;
|
|
101
94
|
this.freeze = freeze;
|
|
@@ -116,6 +109,17 @@ class GPTrans {
|
|
|
116
109
|
return iso.toLowerCase();
|
|
117
110
|
}
|
|
118
111
|
|
|
112
|
+
_createDatabase(name) {
|
|
113
|
+
return new DeepBase(new JsonDriver({ name, path: this.dbPath }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_saveSourceText(context, contextHash, key, text) {
|
|
117
|
+
this.pendingSourceWrites = this.pendingSourceWrites.then(async () => {
|
|
118
|
+
await this.dbFrom.set(context, key, text);
|
|
119
|
+
await this.dbFrom.set('_context', contextHash, context);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
119
123
|
setContext(context = '') {
|
|
120
124
|
if (this.context !== context && this.pendingTranslations.size > 0) {
|
|
121
125
|
clearTimeout(this.debounceTimer);
|
|
@@ -143,9 +147,10 @@ class GPTrans {
|
|
|
143
147
|
// If this key was enqueued via t(), avoid duplicate work in a later batch.
|
|
144
148
|
this._dequeuePendingTranslation(key);
|
|
145
149
|
|
|
150
|
+
await this.pendingSourceWrites;
|
|
146
151
|
const translatedText = await this._translate(text, [[key, text]], {}, this.preloadBaseLanguage);
|
|
147
152
|
const immediateTranslation = translatedText.trim();
|
|
148
|
-
this.dbTarget.set(contextHash, key, immediateTranslation);
|
|
153
|
+
await this.dbTarget.set(contextHash, key, immediateTranslation);
|
|
149
154
|
|
|
150
155
|
return this._applyParams(immediateTranslation, params);
|
|
151
156
|
}
|
|
@@ -171,11 +176,10 @@ class GPTrans {
|
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
const contextHash = this._hash(this.context);
|
|
174
|
-
const translation = this.dbTarget.
|
|
179
|
+
const translation = this.dbTarget.getSync(contextHash, key);
|
|
175
180
|
|
|
176
|
-
if (!this.freeze && !this.dbFrom.
|
|
177
|
-
this.
|
|
178
|
-
this.dbFrom.set('_context', contextHash, this.context);
|
|
181
|
+
if (!this.freeze && !this.dbFrom.getSync(this.context, key)) {
|
|
182
|
+
this._saveSourceText(this.context, contextHash, key, text);
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
if (translation) {
|
|
@@ -233,16 +237,17 @@ class GPTrans {
|
|
|
233
237
|
async _processBatch(context) {
|
|
234
238
|
|
|
235
239
|
const batch = Array.from(this.pendingTranslations.entries());
|
|
240
|
+
const batchCharCount = this.pendingCharCount;
|
|
236
241
|
|
|
237
242
|
// Clear pending translations and character count before awaiting translation
|
|
238
243
|
this.pendingTranslations.clear();
|
|
244
|
+
this.pendingCharCount = 0;
|
|
245
|
+
await this.pendingSourceWrites;
|
|
239
246
|
|
|
240
|
-
this.modelConfig.options.max_tokens =
|
|
241
|
-
const minTime = Math.floor((60000 / (8000 /
|
|
247
|
+
this.modelConfig.options.max_tokens = batchCharCount + 1000;
|
|
248
|
+
const minTime = Math.floor((60000 / (8000 / batchCharCount)) * 1.4);
|
|
242
249
|
GPTrans.mmix(this.modelKey, this.modelMixOptions).limiter.updateSettings({ minTime });
|
|
243
250
|
|
|
244
|
-
this.pendingCharCount = 0;
|
|
245
|
-
|
|
246
251
|
// Load references for each text in the batch if preloadReferences is set
|
|
247
252
|
const batchReferences = {};
|
|
248
253
|
if (this.preloadReferences && this.preloadReferences.length > 0) {
|
|
@@ -280,7 +285,7 @@ class GPTrans {
|
|
|
280
285
|
const minLength = Math.min(translatedTexts.length, batch.length);
|
|
281
286
|
for (let i = 0; i < minLength; i++) {
|
|
282
287
|
if (translatedTexts[i] && translatedTexts[i].trim()) {
|
|
283
|
-
this.dbTarget.set(contextHash, batch[i][0], translatedTexts[i].trim());
|
|
288
|
+
await this.dbTarget.set(contextHash, batch[i][0], translatedTexts[i].trim());
|
|
284
289
|
}
|
|
285
290
|
}
|
|
286
291
|
return;
|
|
@@ -295,15 +300,15 @@ class GPTrans {
|
|
|
295
300
|
return;
|
|
296
301
|
}
|
|
297
302
|
|
|
298
|
-
|
|
303
|
+
for (const [index, [key]] of batch.entries()) {
|
|
299
304
|
if (!trimmed[index]) {
|
|
300
305
|
console.error(`❌ No translation found for ${key} at index ${index}`);
|
|
301
306
|
console.error(` Original text: ${batch[index][1]}`);
|
|
302
|
-
|
|
307
|
+
continue;
|
|
303
308
|
}
|
|
304
309
|
|
|
305
|
-
this.dbTarget.set(contextHash, key, trimmed[index]);
|
|
306
|
-
}
|
|
310
|
+
await this.dbTarget.set(contextHash, key, trimmed[index]);
|
|
311
|
+
}
|
|
307
312
|
|
|
308
313
|
} catch (e) {
|
|
309
314
|
console.error('❌ Error in _processBatch:', e.message);
|
|
@@ -318,7 +323,7 @@ class GPTrans {
|
|
|
318
323
|
try {
|
|
319
324
|
const model = GPTrans.mmix(this.modelKey, this.modelMixOptions);
|
|
320
325
|
|
|
321
|
-
model.setSystem("You are an expert translator specialized in literary translation between
|
|
326
|
+
model.setSystem("You are an expert translator specialized in literary translation between <%- FROM_LANG %> and <%- TARGET_DENONYM %> <%- TARGET_LANG %>.");
|
|
322
327
|
|
|
323
328
|
// Build references section (includes header when references exist, empty otherwise)
|
|
324
329
|
let referencesText = '';
|
|
@@ -362,21 +367,21 @@ class GPTrans {
|
|
|
362
367
|
}
|
|
363
368
|
}
|
|
364
369
|
|
|
365
|
-
// Use ModelMix templates: addTextFromFile +
|
|
370
|
+
// Use ModelMix templates: addTextFromFile + assign + block
|
|
366
371
|
model.addTextFromFile(this.promptFile);
|
|
367
|
-
model.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
372
|
+
model.assign({
|
|
373
|
+
INPUT: text,
|
|
374
|
+
CONTEXT: this.context,
|
|
375
|
+
INSTRUCTION: this.instruction,
|
|
376
|
+
REFERENCES: referencesText,
|
|
377
|
+
TARGET_ISO: this.replaceTarget.TARGET_ISO,
|
|
378
|
+
TARGET_LANG: this.replaceTarget.TARGET_LANG,
|
|
379
|
+
TARGET_COUNTRY: this.replaceTarget.TARGET_COUNTRY,
|
|
380
|
+
TARGET_DENONYM: this.replaceTarget.TARGET_DENONYM,
|
|
381
|
+
FROM_ISO: fromReplace.FROM_ISO,
|
|
382
|
+
FROM_LANG: fromReplace.FROM_LANG,
|
|
383
|
+
FROM_COUNTRY: fromReplace.FROM_COUNTRY,
|
|
384
|
+
FROM_DENONYM: fromReplace.FROM_DENONYM,
|
|
380
385
|
});
|
|
381
386
|
|
|
382
387
|
return await model.block({ addSystemExtra: false });
|
|
@@ -411,12 +416,9 @@ class GPTrans {
|
|
|
411
416
|
|
|
412
417
|
for (const lang of referenceLangs) {
|
|
413
418
|
const namePrefix = this.instanceName ? '_' + this.instanceName : '';
|
|
414
|
-
const dbRef =
|
|
415
|
-
name: `gptrans${namePrefix}_${lang}`,
|
|
416
|
-
path: this.dbPath
|
|
417
|
-
});
|
|
419
|
+
const dbRef = this._createDatabase(`gptrans${namePrefix}_${lang}`);
|
|
418
420
|
|
|
419
|
-
const translation = dbRef.
|
|
421
|
+
const translation = dbRef.getSync(contextHash, key);
|
|
420
422
|
if (translation) {
|
|
421
423
|
references[lang] = translation;
|
|
422
424
|
}
|
|
@@ -427,6 +429,8 @@ class GPTrans {
|
|
|
427
429
|
|
|
428
430
|
async preload({ references = [], baseLanguage = null } = {}) {
|
|
429
431
|
|
|
432
|
+
await this.pendingSourceWrites;
|
|
433
|
+
|
|
430
434
|
if (!this.context && this.replaceFrom.FROM_ISO === this.replaceTarget.TARGET_ISO) {
|
|
431
435
|
return this;
|
|
432
436
|
}
|
|
@@ -454,7 +458,7 @@ class GPTrans {
|
|
|
454
458
|
// Track which keys need translation
|
|
455
459
|
const keysNeedingTranslation = [];
|
|
456
460
|
|
|
457
|
-
for (const [context, pairs] of this.dbFrom.
|
|
461
|
+
for (const [context, pairs] of Object.entries(this.dbFrom.getSync() ?? {})) {
|
|
458
462
|
// Skip the _context metadata
|
|
459
463
|
if (context === '_context') continue;
|
|
460
464
|
|
|
@@ -463,7 +467,7 @@ class GPTrans {
|
|
|
463
467
|
|
|
464
468
|
for (const [key, text] of Object.entries(pairs)) {
|
|
465
469
|
// Check if translation already exists
|
|
466
|
-
if (!this.dbTarget.
|
|
470
|
+
if (!this.dbTarget.getSync(contextHash, key)) {
|
|
467
471
|
keysNeedingTranslation.push({ context, contextHash, key });
|
|
468
472
|
// Only call get() if translation doesn't exist
|
|
469
473
|
this.get(key, text);
|
|
@@ -488,7 +492,7 @@ class GPTrans {
|
|
|
488
492
|
// Check if all needed translations are now complete
|
|
489
493
|
let allTranslated = true;
|
|
490
494
|
for (const { contextHash, key } of keysNeedingTranslation) {
|
|
491
|
-
if (!this.dbTarget.
|
|
495
|
+
if (!this.dbTarget.getSync(contextHash, key)) {
|
|
492
496
|
allTranslated = false;
|
|
493
497
|
break;
|
|
494
498
|
}
|
|
@@ -514,12 +518,14 @@ class GPTrans {
|
|
|
514
518
|
}
|
|
515
519
|
|
|
516
520
|
async purge() {
|
|
521
|
+
await this.pendingSourceWrites;
|
|
522
|
+
|
|
517
523
|
// Iterate through dbTarget and remove keys that don't exist in dbFrom
|
|
518
|
-
for (const [contextHash, pairs] of this.dbTarget.
|
|
524
|
+
for (const [contextHash, pairs] of Object.entries(this.dbTarget.getSync() ?? {})) {
|
|
519
525
|
for (const key of Object.keys(pairs)) {
|
|
520
526
|
|
|
521
|
-
const context = this.dbFrom.
|
|
522
|
-
if (!this.dbFrom.
|
|
527
|
+
const context = this.dbFrom.getSync('_context', contextHash);
|
|
528
|
+
if (!this.dbFrom.getSync(context, key)) {
|
|
523
529
|
console.log(contextHash, key);
|
|
524
530
|
await this.dbTarget.del(contextHash, key);
|
|
525
531
|
}
|
|
@@ -539,14 +545,14 @@ class GPTrans {
|
|
|
539
545
|
}
|
|
540
546
|
|
|
541
547
|
const finalInstruction = instructions.length > 1 ? `- ${merged}` : merged;
|
|
542
|
-
const refinePromptFile = promptFile
|
|
548
|
+
const refinePromptFile = promptFile ? pathModule.resolve(promptFile) : REFINE_PROMPT_FILE;
|
|
543
549
|
|
|
544
550
|
// Collect all existing translations grouped by contextHash
|
|
545
551
|
const allBatches = [];
|
|
546
552
|
let currentBatch = [];
|
|
547
553
|
let currentCharCount = 0;
|
|
548
554
|
|
|
549
|
-
for (const [contextHash, pairs] of this.dbTarget.
|
|
555
|
+
for (const [contextHash, pairs] of Object.entries(this.dbTarget.getSync() ?? {})) {
|
|
550
556
|
for (const [key, translation] of Object.entries(pairs)) {
|
|
551
557
|
const entryCharCount = translation.length;
|
|
552
558
|
|
|
@@ -607,20 +613,20 @@ class GPTrans {
|
|
|
607
613
|
const minLength = Math.min(refinedTexts.length, entries.length);
|
|
608
614
|
for (let i = 0; i < minLength; i++) {
|
|
609
615
|
if (refinedTexts[i] && refinedTexts[i].trim()) {
|
|
610
|
-
this.dbTarget.set(entries[i].contextHash, entries[i].key, refinedTexts[i].trim());
|
|
616
|
+
await this.dbTarget.set(entries[i].contextHash, entries[i].key, refinedTexts[i].trim());
|
|
611
617
|
}
|
|
612
618
|
}
|
|
613
619
|
return;
|
|
614
620
|
}
|
|
615
621
|
|
|
616
|
-
entries.
|
|
622
|
+
for (const [index, entry] of entries.entries()) {
|
|
617
623
|
const refinedText = refinedTexts[index]?.trim();
|
|
618
624
|
if (!refinedText) {
|
|
619
625
|
console.error(`❌ No refined text for ${entry.key} at index ${index}`);
|
|
620
|
-
|
|
626
|
+
continue;
|
|
621
627
|
}
|
|
622
|
-
this.dbTarget.set(entry.contextHash, entry.key, refinedText);
|
|
623
|
-
}
|
|
628
|
+
await this.dbTarget.set(entry.contextHash, entry.key, refinedText);
|
|
629
|
+
}
|
|
624
630
|
|
|
625
631
|
} catch (e) {
|
|
626
632
|
console.error('❌ Error in _processRefineBatch:', e.message);
|
|
@@ -634,22 +640,22 @@ class GPTrans {
|
|
|
634
640
|
try {
|
|
635
641
|
const model = GPTrans.mmix(this.modelKey, this.modelMixOptions);
|
|
636
642
|
|
|
637
|
-
model.setSystem("You are an expert translator and editor specialized in refining
|
|
643
|
+
model.setSystem("You are an expert translator and editor specialized in refining <%- TARGET_DENONYM %> <%- TARGET_LANG %> translations.");
|
|
638
644
|
|
|
639
|
-
// Use ModelMix templates: addTextFromFile +
|
|
645
|
+
// Use ModelMix templates: addTextFromFile + assign + block
|
|
640
646
|
model.addTextFromFile(refinePromptFile);
|
|
641
|
-
model.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
647
|
+
model.assign({
|
|
648
|
+
INPUT: text,
|
|
649
|
+
INSTRUCTION: instruction,
|
|
650
|
+
CONTEXT: this.context,
|
|
651
|
+
TARGET_ISO: this.replaceTarget.TARGET_ISO,
|
|
652
|
+
TARGET_LANG: this.replaceTarget.TARGET_LANG,
|
|
653
|
+
TARGET_COUNTRY: this.replaceTarget.TARGET_COUNTRY,
|
|
654
|
+
TARGET_DENONYM: this.replaceTarget.TARGET_DENONYM,
|
|
655
|
+
FROM_ISO: this.replaceFrom.FROM_ISO,
|
|
656
|
+
FROM_LANG: this.replaceFrom.FROM_LANG,
|
|
657
|
+
FROM_COUNTRY: this.replaceFrom.FROM_COUNTRY,
|
|
658
|
+
FROM_DENONYM: this.replaceFrom.FROM_DENONYM,
|
|
653
659
|
});
|
|
654
660
|
|
|
655
661
|
return await model.block({ addSystemExtra: false });
|
|
@@ -668,13 +674,13 @@ class GPTrans {
|
|
|
668
674
|
} = options;
|
|
669
675
|
|
|
670
676
|
// Parse image filename and extension
|
|
671
|
-
const parsedPath =
|
|
677
|
+
const parsedPath = pathModule.parse(imagePath);
|
|
672
678
|
const filename = parsedPath.base;
|
|
673
679
|
const targetLang = this.replaceTarget.TARGET_ISO || 'en';
|
|
674
680
|
|
|
675
681
|
// Check if image is already in a language folder
|
|
676
|
-
const dirName =
|
|
677
|
-
const parentDir =
|
|
682
|
+
const dirName = pathModule.basename(pathModule.dirname(imagePath));
|
|
683
|
+
const parentDir = pathModule.dirname(pathModule.dirname(imagePath));
|
|
678
684
|
|
|
679
685
|
// If the image is in a language folder (e.g., en/image.jpg)
|
|
680
686
|
// create the target at the same level (e.g., es/image.jpg)
|
|
@@ -683,12 +689,12 @@ class GPTrans {
|
|
|
683
689
|
|
|
684
690
|
if (this._isLanguageFolder(dirName)) {
|
|
685
691
|
// Image is in a language folder: create sibling folder
|
|
686
|
-
targetDir =
|
|
687
|
-
targetPath =
|
|
692
|
+
targetDir = pathModule.join(parentDir, targetLang);
|
|
693
|
+
targetPath = pathModule.join(targetDir, filename);
|
|
688
694
|
} else {
|
|
689
695
|
// Image is not in a language folder: create subfolder
|
|
690
|
-
targetDir =
|
|
691
|
-
targetPath =
|
|
696
|
+
targetDir = pathModule.join(pathModule.dirname(imagePath), targetLang);
|
|
697
|
+
targetPath = pathModule.join(targetDir, filename);
|
|
692
698
|
}
|
|
693
699
|
|
|
694
700
|
// Check if translated image already exists
|
|
@@ -736,7 +742,7 @@ class GPTrans {
|
|
|
736
742
|
}
|
|
737
743
|
|
|
738
744
|
// Save translated image - preserve original file format
|
|
739
|
-
const filename =
|
|
745
|
+
const filename = pathModule.basename(targetPath, pathModule.extname(targetPath));
|
|
740
746
|
const formatOptions = generator.getReferenceMetadata();
|
|
741
747
|
|
|
742
748
|
// Apply default quality settings for JPEG images
|
|
@@ -755,4 +761,4 @@ class GPTrans {
|
|
|
755
761
|
}
|
|
756
762
|
}
|
|
757
763
|
|
|
758
|
-
export default GPTrans;
|
|
764
|
+
export default GPTrans;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gptrans",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.4",
|
|
5
5
|
"description": "🚆 GPTrans - The smarter AI-powered way to translate.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"translate",
|
|
@@ -32,12 +32,13 @@
|
|
|
32
32
|
},
|
|
33
33
|
"homepage": "https://github.com/clasen/GPTrans#readme",
|
|
34
34
|
"engines": {
|
|
35
|
-
"node": ">=20.
|
|
35
|
+
"node": ">=20.12.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"deepbase": "^
|
|
39
|
-
"
|
|
40
|
-
"
|
|
38
|
+
"deepbase": "^3.9.0",
|
|
39
|
+
"deepbase-json": "3.9.0",
|
|
40
|
+
"genmix": "^1.2.5",
|
|
41
|
+
"modelmix": "^5.1.6",
|
|
41
42
|
"string-hash": "^1.1.3"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
package/pnpm-workspace.yaml
CHANGED
package/prompt/refine.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Goal
|
|
2
|
-
Refine existing translations in
|
|
2
|
+
Refine existing translations in <%- TARGET_ISO %> (<%- TARGET_DENONYM %> <%- TARGET_LANG %>) based on the following instruction.
|
|
3
3
|
|
|
4
4
|
## Refinement Instruction
|
|
5
|
-
|
|
5
|
+
<%- INSTRUCTION %>
|
|
6
6
|
|
|
7
7
|
## Current Translations to Evaluate
|
|
8
8
|
```
|
|
9
|
-
|
|
9
|
+
<%- INPUT %>
|
|
10
10
|
```
|
|
11
11
|
|
|
12
12
|
# Return Format
|
|
@@ -23,4 +23,4 @@ Refine existing translations in {TARGET_ISO} ({TARGET_DENONYM} {TARGET_LANG}) ba
|
|
|
23
23
|
- **Consistency:** Maintain consistent terminology across all translations in the batch.
|
|
24
24
|
|
|
25
25
|
# Context
|
|
26
|
-
|
|
26
|
+
<%- CONTEXT %>
|
package/prompt/translate.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Goal
|
|
2
|
-
Translation from
|
|
3
|
-
|
|
2
|
+
Translation from <%- FROM_ISO %> to <%- TARGET_ISO %> (<%- TARGET_DENONYM %> <%- TARGET_LANG %>) with cultural adaptations.
|
|
3
|
+
<%- INSTRUCTION %>
|
|
4
4
|
|
|
5
5
|
## Text to translate
|
|
6
6
|
```
|
|
7
|
-
|
|
7
|
+
<%- INPUT %>
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
<%- REFERENCES %>
|
|
11
11
|
|
|
12
12
|
# Return Format
|
|
13
13
|
- The input may contain multiple texts separated by `------`. Translate each one independently and return them in the same order, separated by `------`. The number of segments in your output must exactly match the number of segments in the input.
|
|
@@ -15,13 +15,13 @@ Translation from {FROM_ISO} to {TARGET_ISO} ({TARGET_DENONYM} {TARGET_LANG}) wit
|
|
|
15
15
|
- Do not include alternative translations, only provide the best translation.
|
|
16
16
|
|
|
17
17
|
# Warnings
|
|
18
|
-
- **Context:** I will provide you with a text in
|
|
18
|
+
- **Context:** I will provide you with a text in <%- FROM_DENONYM %> <%- FROM_LANG %>. The goal is to translate it to <%- TARGET_ISO %> (<%- TARGET_DENONYM %> <%- TARGET_LANG %>) while maintaining the essence, style, intention, and tone of the original.
|
|
19
19
|
- **Proper names:** Do not translate proper names (people, places, brands, etc.) unless they have an officially recognized translation in the target language.
|
|
20
|
-
- **Cultural references:** Adapt or explain references that are not familiar in
|
|
20
|
+
- **Cultural references:** Adapt or explain references that are not familiar in <%- TARGET_DENONYM %> culture, whenever necessary.
|
|
21
21
|
- **Wordplay and humor:** When it's impossible to directly translate wordplay, find a resource that recreates the playful effect.
|
|
22
22
|
- **Idioms:** Do not introduce new idioms or expressions that are not present in the original text.
|
|
23
23
|
- **Variables:** Do not translate content between curly braces. These are system variables and must remain exactly the same.
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
# Context
|
|
27
|
-
|
|
27
|
+
<%- CONTEXT %>
|