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/skills/gptrans/SKILL.md
CHANGED
|
@@ -81,11 +81,13 @@ You can manually edit translation files to override specific entries.
|
|
|
81
81
|
|
|
82
82
|
```javascript
|
|
83
83
|
import GPTrans from 'gptrans';
|
|
84
|
+
import path from 'node:path';
|
|
84
85
|
|
|
86
|
+
const dbPath = path.join(import.meta.dirname, 'db');
|
|
85
87
|
const gptrans = new GPTrans({
|
|
88
|
+
path: dbPath,
|
|
86
89
|
from: 'en-US',
|
|
87
|
-
target: 'es-AR'
|
|
88
|
-
model: 'sonnet45'
|
|
90
|
+
target: 'es-AR'
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
// Translate text — returns original on first call, cached translation after
|
|
@@ -104,7 +106,7 @@ console.log(gptrans.setContext().t('Welcome back'));
|
|
|
104
106
|
| --- | --- | --- | --- |
|
|
105
107
|
| `from` | `string` | `'en-US'` | Source language (BCP 47) |
|
|
106
108
|
| `target` | `string` | `'es'` | Target language (BCP 47) |
|
|
107
|
-
| `model` | `string \| string[]` | `'
|
|
109
|
+
| `model` | `string \| string[]` | `['sonnet5@50', 'gpt56luna@100']` | ModelMix shortcut or ordered fallback chain; supports `@<effort>` from `0` to `100` |
|
|
108
110
|
| `batchThreshold` | `number` | `1500` | Max characters before triggering batch |
|
|
109
111
|
| `debounceTimeout` | `number` | `500` | Milliseconds to wait before processing |
|
|
110
112
|
| `freeze` | `boolean` | `false` | Prevent new translations from being queued |
|
|
@@ -119,7 +121,7 @@ console.log(gptrans.setContext().t('Welcome back'));
|
|
|
119
121
|
### Translate text with parameter substitution
|
|
120
122
|
|
|
121
123
|
```javascript
|
|
122
|
-
const gptrans = new GPTrans({ from: 'en', target: 'es-AR' });
|
|
124
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'en', target: 'es-AR' });
|
|
123
125
|
console.log(gptrans.t('Hello, {name}!', { name: 'Martin' }));
|
|
124
126
|
// First call: "Hello, Martin!" (original)
|
|
125
127
|
// After caching: "Hola, Martin!" (translated)
|
|
@@ -129,16 +131,19 @@ console.log(gptrans.t('Hello, {name}!', { name: 'Martin' }));
|
|
|
129
131
|
|
|
130
132
|
```javascript
|
|
131
133
|
const gptrans = new GPTrans({
|
|
134
|
+
path: dbPath,
|
|
132
135
|
from: 'en',
|
|
133
136
|
target: 'fr',
|
|
134
|
-
model: ['
|
|
137
|
+
model: ['sonnet5@50', 'gpt56luna@100']
|
|
135
138
|
});
|
|
136
139
|
```
|
|
137
140
|
|
|
141
|
+
Use `model@<effort>` with an integer from `0` to `100` to override the unified effort for one model. The default chain uses `sonnet5` at effort `50`, then falls back to `gpt56luna` at effort `100`.
|
|
142
|
+
|
|
138
143
|
### Pre-translate all pending texts
|
|
139
144
|
|
|
140
145
|
```javascript
|
|
141
|
-
const gptrans = new GPTrans({ from: 'en', target: 'es' });
|
|
146
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'en', target: 'es' });
|
|
142
147
|
|
|
143
148
|
// Register texts
|
|
144
149
|
gptrans.t('Welcome');
|
|
@@ -157,7 +162,7 @@ console.log(gptrans.t('Welcome')); // "Bienvenido"
|
|
|
157
162
|
Use existing translations in other languages as context for better accuracy:
|
|
158
163
|
|
|
159
164
|
```javascript
|
|
160
|
-
const gptrans = new GPTrans({ from: 'es', target: 'fr' });
|
|
165
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'es', target: 'fr' });
|
|
161
166
|
await gptrans.preload({
|
|
162
167
|
references: ['en', 'pt'] // AI sees English and Portuguese as reference
|
|
163
168
|
});
|
|
@@ -168,7 +173,7 @@ await gptrans.preload({
|
|
|
168
173
|
Translate from an intermediate language instead of the original (useful for gender-neutral intermediaries):
|
|
169
174
|
|
|
170
175
|
```javascript
|
|
171
|
-
const gptrans = new GPTrans({ from: 'es', target: 'pt' });
|
|
176
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'es', target: 'pt' });
|
|
172
177
|
await gptrans.preload({
|
|
173
178
|
baseLanguage: 'en', // translate FROM English instead of Spanish
|
|
174
179
|
references: ['es'] // show original Spanish for context
|
|
@@ -178,7 +183,7 @@ await gptrans.preload({
|
|
|
178
183
|
### Set context for gender-aware translations
|
|
179
184
|
|
|
180
185
|
```javascript
|
|
181
|
-
const gptrans = new GPTrans({ from: 'en', target: 'es-AR' });
|
|
186
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'en', target: 'es-AR' });
|
|
182
187
|
|
|
183
188
|
// Context applies to next translation(s) in the batch
|
|
184
189
|
console.log(gptrans.setContext('The user is female').t('You are welcome'));
|
|
@@ -191,6 +196,7 @@ console.log(gptrans.setContext().t('Thank you'));
|
|
|
191
196
|
|
|
192
197
|
```javascript
|
|
193
198
|
const gptrans = new GPTrans({
|
|
199
|
+
path: dbPath,
|
|
194
200
|
from: 'en', target: 'es-AR',
|
|
195
201
|
instruction: 'Use a formal and professional tone'
|
|
196
202
|
});
|
|
@@ -203,7 +209,7 @@ console.log(gptrans.t('Welcome to our platform'));
|
|
|
203
209
|
Improve cached translations with specific instructions:
|
|
204
210
|
|
|
205
211
|
```javascript
|
|
206
|
-
const gptrans = new GPTrans({ from: 'en', target: 'es-AR' });
|
|
212
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'en', target: 'es-AR' });
|
|
207
213
|
|
|
208
214
|
// Single instruction
|
|
209
215
|
await gptrans.refine('Use a more colloquial tone');
|
|
@@ -224,7 +230,7 @@ await gptrans.refine('More formal', { promptFile: './my-refine-prompt.md' });
|
|
|
224
230
|
Requires `GEMINI_API_KEY`. Auto-detects language folders for output path:
|
|
225
231
|
|
|
226
232
|
```javascript
|
|
227
|
-
const gptrans = new GPTrans({ from: 'en', target: 'es' });
|
|
233
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'en', target: 'es' });
|
|
228
234
|
|
|
229
235
|
// en/banner.jpg → es/banner.jpg (auto sibling folder)
|
|
230
236
|
const translatedPath = await gptrans.img('en/banner.jpg');
|
|
@@ -240,7 +246,7 @@ const result = await gptrans.img('en/hero.jpg', {
|
|
|
240
246
|
### Freeze mode (prevent new translations)
|
|
241
247
|
|
|
242
248
|
```javascript
|
|
243
|
-
const gptrans = new GPTrans({ from: 'en', target: 'es', freeze: true });
|
|
249
|
+
const gptrans = new GPTrans({ path: dbPath, from: 'en', target: 'es', freeze: true });
|
|
244
250
|
|
|
245
251
|
// Returns original text, logs "[key] text" — nothing queued
|
|
246
252
|
console.log(gptrans.t('New text'));
|
|
@@ -262,9 +268,9 @@ await gptrans.purge();
|
|
|
262
268
|
```javascript
|
|
263
269
|
// Spain Spanish → Argentina Spanish
|
|
264
270
|
const es2ar = new GPTrans({
|
|
271
|
+
path: dbPath,
|
|
265
272
|
from: 'es-ES',
|
|
266
|
-
target: 'es-AR'
|
|
267
|
-
model: 'sonnet45'
|
|
273
|
+
target: 'es-AR'
|
|
268
274
|
});
|
|
269
275
|
|
|
270
276
|
console.log(es2ar.t('Eres muy bueno'));
|
|
@@ -290,7 +296,7 @@ if (GPTrans.isLanguageAvailable('pt-BR')) {
|
|
|
290
296
|
- Use `setContext()` for gender-aware or domain-specific translations. Context is captured per-batch and auto-resets when changed.
|
|
291
297
|
- Use the `instruction` constructor option for style/tone guidance (e.g., "Use a more natural tone"). Unlike `context`, `instruction` does NOT affect the cache key — different instructions for the same text overwrite the same translation entry.
|
|
292
298
|
- Prefer passing an array of instructions to `refine()` over multiple calls — it processes everything in a single API pass.
|
|
293
|
-
- Use model arrays (`model: ['
|
|
299
|
+
- Use model arrays (`model: ['sonnet5@50', 'gpt56luna@100']`) for production resilience with automatic fallback. The optional `@<effort>` suffix accepts integers from `0` to `100` and applies only to that model.
|
|
294
300
|
- Translation caches live in `db/gptrans_<locale>.json`. These files can be manually edited to override specific translations.
|
|
295
301
|
- The `name` constructor option isolates cache files (`db/gptrans_<name>_<locale>.json`), useful for multiple independent translation contexts in the same project.
|
|
296
302
|
- When translating images, ensure `GEMINI_API_KEY` is set. The `img()` method auto-creates sibling language folders.
|
|
@@ -301,7 +307,7 @@ if (GPTrans.isLanguageAvailable('pt-BR')) {
|
|
|
301
307
|
|
|
302
308
|
| Method | Returns | Description |
|
|
303
309
|
| --- | --- | --- |
|
|
304
|
-
| `new GPTrans(options)` | `GPTrans` | Create instance
|
|
310
|
+
| `new GPTrans(options)` | `GPTrans` | Create an instance. `options.path` must be an absolute directory path. |
|
|
305
311
|
| `.t(text, params?)` | `string` | Translate text (sync). Returns cached or original. |
|
|
306
312
|
| `.get(key, text)` | `string \| undefined` | Get translation by key, queue if missing. |
|
|
307
313
|
| `.setContext(context?)` | `this` | Set context for next batch (gender, tone, etc.). |
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
3
4
|
import GPTrans from '../index.js';
|
|
4
5
|
|
|
5
6
|
function createMemoryDb() {
|
|
6
7
|
const store = new Map();
|
|
7
8
|
return {
|
|
8
|
-
|
|
9
|
+
getSync(...keys) {
|
|
10
|
+
if (keys.length === 0) {
|
|
11
|
+
return Object.fromEntries(Array.from(store.entries()).map(([context, pairs]) => [
|
|
12
|
+
context,
|
|
13
|
+
Object.fromEntries(pairs.entries())
|
|
14
|
+
]));
|
|
15
|
+
}
|
|
16
|
+
const [context, key] = keys;
|
|
17
|
+
if (keys.length === 1) {
|
|
18
|
+
return Object.fromEntries(store.get(context)?.entries() ?? []);
|
|
19
|
+
}
|
|
9
20
|
return store.get(context)?.get(key);
|
|
10
21
|
},
|
|
11
22
|
set(context, key, value) {
|
|
@@ -35,6 +46,7 @@ function createTestInstance() {
|
|
|
35
46
|
target: 'en-US',
|
|
36
47
|
debounceTimeout: 10_000,
|
|
37
48
|
batchThreshold: 50_000,
|
|
49
|
+
path: tmpdir(),
|
|
38
50
|
name: `unit_${Date.now()}_${Math.random().toString(36).slice(2)}`
|
|
39
51
|
});
|
|
40
52
|
|
|
@@ -63,9 +75,9 @@ test('_processBatch splits correctly with \\n------\\n separators', async (t) =>
|
|
|
63
75
|
await gp._processBatch('');
|
|
64
76
|
|
|
65
77
|
const h = gp._hash('');
|
|
66
|
-
assert.equal(gp.dbTarget.
|
|
67
|
-
assert.equal(gp.dbTarget.
|
|
68
|
-
assert.equal(gp.dbTarget.
|
|
78
|
+
assert.equal(gp.dbTarget.getSync(h, 'k1'), 'Hello');
|
|
79
|
+
assert.equal(gp.dbTarget.getSync(h, 'k2'), 'Goodbye');
|
|
80
|
+
assert.equal(gp.dbTarget.getSync(h, 'k3'), 'Thank you');
|
|
69
81
|
|
|
70
82
|
GPTrans.mmix = originalMmix;
|
|
71
83
|
});
|
|
@@ -86,9 +98,9 @@ test('_processBatch falls back to split by divider without newlines', async (t)
|
|
|
86
98
|
await gp._processBatch('');
|
|
87
99
|
|
|
88
100
|
const h = gp._hash('');
|
|
89
|
-
assert.equal(gp.dbTarget.
|
|
90
|
-
assert.equal(gp.dbTarget.
|
|
91
|
-
assert.equal(gp.dbTarget.
|
|
101
|
+
assert.equal(gp.dbTarget.getSync(h, 'k1'), 'Hello');
|
|
102
|
+
assert.equal(gp.dbTarget.getSync(h, 'k2'), 'Goodbye');
|
|
103
|
+
assert.equal(gp.dbTarget.getSync(h, 'k3'), 'Thank you');
|
|
92
104
|
|
|
93
105
|
GPTrans.mmix = originalMmix;
|
|
94
106
|
});
|
|
@@ -149,10 +161,10 @@ test('_processBatch discards batch when all translations are identical (3+)', as
|
|
|
149
161
|
|
|
150
162
|
const h = gp._hash('');
|
|
151
163
|
// Nothing should be saved
|
|
152
|
-
assert.equal(gp.dbTarget.
|
|
153
|
-
assert.equal(gp.dbTarget.
|
|
154
|
-
assert.equal(gp.dbTarget.
|
|
155
|
-
assert.equal(gp.dbTarget.
|
|
164
|
+
assert.equal(gp.dbTarget.getSync(h, 'k1'), undefined);
|
|
165
|
+
assert.equal(gp.dbTarget.getSync(h, 'k2'), undefined);
|
|
166
|
+
assert.equal(gp.dbTarget.getSync(h, 'k3'), undefined);
|
|
167
|
+
assert.equal(gp.dbTarget.getSync(h, 'k4'), undefined);
|
|
156
168
|
|
|
157
169
|
assert.ok(errors.some(e => e.includes('translations are identical')));
|
|
158
170
|
|
|
@@ -174,8 +186,8 @@ test('_processBatch allows batch of 2 with identical translations (edge case)',
|
|
|
174
186
|
await gp._processBatch('');
|
|
175
187
|
|
|
176
188
|
const h = gp._hash('');
|
|
177
|
-
assert.equal(gp.dbTarget.
|
|
178
|
-
assert.equal(gp.dbTarget.
|
|
189
|
+
assert.equal(gp.dbTarget.getSync(h, 'k1'), 'Yes');
|
|
190
|
+
assert.equal(gp.dbTarget.getSync(h, 'k2'), 'Yes');
|
|
179
191
|
|
|
180
192
|
GPTrans.mmix = originalMmix;
|
|
181
193
|
});
|
|
@@ -196,9 +208,9 @@ test('_processBatch saves partial duplicates normally', async (t) => {
|
|
|
196
208
|
await gp._processBatch('');
|
|
197
209
|
|
|
198
210
|
const h = gp._hash('');
|
|
199
|
-
assert.equal(gp.dbTarget.
|
|
200
|
-
assert.equal(gp.dbTarget.
|
|
201
|
-
assert.equal(gp.dbTarget.
|
|
211
|
+
assert.equal(gp.dbTarget.getSync(h, 'k1'), 'Hello');
|
|
212
|
+
assert.equal(gp.dbTarget.getSync(h, 'k2'), 'Hello');
|
|
213
|
+
assert.equal(gp.dbTarget.getSync(h, 'k3'), 'Goodbye');
|
|
202
214
|
|
|
203
215
|
GPTrans.mmix = originalMmix;
|
|
204
216
|
});
|
|
@@ -227,9 +239,9 @@ test('_processBatch saves partial results on count mismatch', async (t) => {
|
|
|
227
239
|
console.error = origError;
|
|
228
240
|
|
|
229
241
|
const h = gp._hash('');
|
|
230
|
-
assert.equal(gp.dbTarget.
|
|
231
|
-
assert.equal(gp.dbTarget.
|
|
232
|
-
assert.equal(gp.dbTarget.
|
|
242
|
+
assert.equal(gp.dbTarget.getSync(h, 'k1'), 'Hello');
|
|
243
|
+
assert.equal(gp.dbTarget.getSync(h, 'k2'), 'Goodbye');
|
|
244
|
+
assert.equal(gp.dbTarget.getSync(h, 'k3'), undefined); // Not saved
|
|
233
245
|
|
|
234
246
|
assert.ok(errors.some(e => e.includes('Translation count mismatch')));
|
|
235
247
|
|
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import { access, mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
3
6
|
import GPTrans from '../index.js';
|
|
4
7
|
|
|
5
8
|
function createMemoryDb() {
|
|
6
9
|
const store = new Map();
|
|
7
10
|
return {
|
|
8
|
-
|
|
11
|
+
getSync(...keys) {
|
|
12
|
+
if (keys.length === 0) {
|
|
13
|
+
return Object.fromEntries(Array.from(store.entries()).map(([context, pairs]) => [
|
|
14
|
+
context,
|
|
15
|
+
Object.fromEntries(pairs.entries())
|
|
16
|
+
]));
|
|
17
|
+
}
|
|
18
|
+
const [context, key] = keys;
|
|
19
|
+
if (keys.length === 1) {
|
|
20
|
+
return Object.fromEntries(store.get(context)?.entries() ?? []);
|
|
21
|
+
}
|
|
9
22
|
return store.get(context)?.get(key);
|
|
10
23
|
},
|
|
11
24
|
set(context, key, value) {
|
|
@@ -39,6 +52,7 @@ function createTestInstance() {
|
|
|
39
52
|
target: 'es',
|
|
40
53
|
debounceTimeout: 10_000,
|
|
41
54
|
batchThreshold: 5000,
|
|
55
|
+
path: tmpdir(),
|
|
42
56
|
name: `unit_${Date.now()}_${Math.random().toString(36).slice(2)}`
|
|
43
57
|
});
|
|
44
58
|
|
|
@@ -131,7 +145,7 @@ test('preload translates missing keys from dbFrom into dbTarget', async () => {
|
|
|
131
145
|
await gp.preload();
|
|
132
146
|
|
|
133
147
|
assert.equal(translateCalls, 1);
|
|
134
|
-
assert.equal(gp.dbTarget.
|
|
148
|
+
assert.equal(gp.dbTarget.getSync(contextHash, key), 'Comprar ahora');
|
|
135
149
|
assert.deepEqual(gp.preloadReferences, []);
|
|
136
150
|
assert.equal(gp.preloadBaseLanguage, null);
|
|
137
151
|
} finally {
|
|
@@ -141,3 +155,143 @@ test('preload translates missing keys from dbFrom into dbTarget', async () => {
|
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
157
|
});
|
|
158
|
+
|
|
159
|
+
test('constructor requires an absolute database path', () => {
|
|
160
|
+
assert.throws(() => new GPTrans(), /requires an absolute "path" option/);
|
|
161
|
+
assert.throws(() => new GPTrans({ path: './db' }), /requires an absolute "path" option/);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('the default model chain uses the configured efforts and renders the EJS prompt', async () => {
|
|
165
|
+
const previousAnthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
166
|
+
const previousOpenAiKey = process.env.OPENAI_API_KEY;
|
|
167
|
+
process.env.ANTHROPIC_API_KEY = 'test-anthropic-key';
|
|
168
|
+
process.env.OPENAI_API_KEY = 'test-openai-key';
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
const model = GPTrans.mmix();
|
|
172
|
+
assert.deepEqual(
|
|
173
|
+
model.models.map(({ key }) => key),
|
|
174
|
+
['claude-sonnet-5', 'gpt-5.6-luna']
|
|
175
|
+
);
|
|
176
|
+
assert.equal(model.models[0].provider.config.effort, 50);
|
|
177
|
+
assert.equal(model.models[1].provider.config.effort, 100);
|
|
178
|
+
|
|
179
|
+
const gp = new GPTrans({ path: tmpdir(), freeze: true, name: 'modelmix-render' });
|
|
180
|
+
assert.deepEqual(gp.modelKey, ['sonnet5@50', 'gpt56luna@100']);
|
|
181
|
+
model.addTextFromFile(gp.promptFile);
|
|
182
|
+
model.assign({
|
|
183
|
+
INPUT: 'Hello',
|
|
184
|
+
CONTEXT: 'Greeting',
|
|
185
|
+
INSTRUCTION: 'Be friendly',
|
|
186
|
+
REFERENCES: '',
|
|
187
|
+
TARGET_ISO: 'es',
|
|
188
|
+
TARGET_LANG: 'Spanish',
|
|
189
|
+
TARGET_COUNTRY: 'Spain',
|
|
190
|
+
TARGET_DENONYM: 'Spanish',
|
|
191
|
+
FROM_ISO: 'en-US',
|
|
192
|
+
FROM_LANG: 'English',
|
|
193
|
+
FROM_COUNTRY: 'United States',
|
|
194
|
+
FROM_DENONYM: 'American'
|
|
195
|
+
});
|
|
196
|
+
const messages = await model.prepareMessages();
|
|
197
|
+
const renderedPrompt = messages[0].content[0].text;
|
|
198
|
+
assert.match(renderedPrompt, /Translation from en-US to es/);
|
|
199
|
+
assert.match(renderedPrompt, /```\nHello\n```/);
|
|
200
|
+
assert.doesNotMatch(renderedPrompt, /<%-\s*[A-Z_]+\s*%>/);
|
|
201
|
+
} finally {
|
|
202
|
+
if (previousAnthropicKey === undefined) delete process.env.ANTHROPIC_API_KEY;
|
|
203
|
+
else process.env.ANTHROPIC_API_KEY = previousAnthropicKey;
|
|
204
|
+
if (previousOpenAiKey === undefined) delete process.env.OPENAI_API_KEY;
|
|
205
|
+
else process.env.OPENAI_API_KEY = previousOpenAiKey;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('_translate uses assign() with the absolute built-in prompt path', async () => {
|
|
210
|
+
const gp = createTestInstance();
|
|
211
|
+
const originalMmix = GPTrans.mmix;
|
|
212
|
+
const calls = {};
|
|
213
|
+
const model = {
|
|
214
|
+
setSystem(value) {
|
|
215
|
+
calls.system = value;
|
|
216
|
+
},
|
|
217
|
+
addTextFromFile(value) {
|
|
218
|
+
calls.promptFile = value;
|
|
219
|
+
},
|
|
220
|
+
assign(value) {
|
|
221
|
+
calls.templateData = value;
|
|
222
|
+
},
|
|
223
|
+
async block(options) {
|
|
224
|
+
calls.blockOptions = options;
|
|
225
|
+
return 'Hola';
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
GPTrans.mmix = () => model;
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
assert.equal(await gp._translate('Hello'), 'Hola');
|
|
232
|
+
assert.equal(path.isAbsolute(calls.promptFile), true);
|
|
233
|
+
assert.equal(path.basename(calls.promptFile), 'translate.md');
|
|
234
|
+
await access(calls.promptFile);
|
|
235
|
+
assert.equal(calls.templateData.INPUT, 'Hello');
|
|
236
|
+
assert.equal(calls.templateData.FROM_ISO, 'en-US');
|
|
237
|
+
assert.equal(calls.templateData.TARGET_ISO, 'es');
|
|
238
|
+
assert.match(calls.system, /<%- FROM_LANG %>/);
|
|
239
|
+
assert.deepEqual(calls.blockOptions, { addSystemExtra: false });
|
|
240
|
+
} finally {
|
|
241
|
+
GPTrans.mmix = originalMmix;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('DeepBase 3.9 cache is available to synchronous translations', async () => {
|
|
246
|
+
const directory = await mkdtemp(path.join(tmpdir(), 'gptrans-deepbase-'));
|
|
247
|
+
const originalMmix = GPTrans.mmix;
|
|
248
|
+
const gp = new GPTrans({
|
|
249
|
+
from: 'en',
|
|
250
|
+
target: 'es',
|
|
251
|
+
freeze: true,
|
|
252
|
+
debounceTimeout: 1,
|
|
253
|
+
path: directory,
|
|
254
|
+
name: 'sync-cache'
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const text = 'Hello';
|
|
259
|
+
const key = gp._textToKey(text);
|
|
260
|
+
const contextHash = gp._hash('');
|
|
261
|
+
await gp.dbTarget.set(contextHash, key, 'Hola');
|
|
262
|
+
|
|
263
|
+
assert.equal(gp.t(text), 'Hola');
|
|
264
|
+
assert.equal(gp.dbTarget.getSync(contextHash, key), 'Hola');
|
|
265
|
+
|
|
266
|
+
gp.setFreeze(false);
|
|
267
|
+
const sourceText = 'New source text';
|
|
268
|
+
const sourceKey = gp._textToKey(sourceText);
|
|
269
|
+
gp._translate = async () => 'Nuevo texto fuente';
|
|
270
|
+
GPTrans.mmix = () => ({ limiter: { updateSettings() {} } });
|
|
271
|
+
|
|
272
|
+
gp.t(sourceText);
|
|
273
|
+
await gp.preload();
|
|
274
|
+
|
|
275
|
+
assert.equal(gp.dbFrom.getSync('', sourceKey), sourceText);
|
|
276
|
+
assert.equal(gp.dbTarget.getSync(contextHash, sourceKey), 'Nuevo texto fuente');
|
|
277
|
+
|
|
278
|
+
let refinePromptFile;
|
|
279
|
+
gp._processRefineBatch = async (entries, instruction, promptFile) => {
|
|
280
|
+
assert.equal(entries.length, 2);
|
|
281
|
+
assert.equal(instruction, 'Use a formal tone');
|
|
282
|
+
refinePromptFile = promptFile;
|
|
283
|
+
};
|
|
284
|
+
await gp.refine('Use a formal tone');
|
|
285
|
+
assert.equal(path.isAbsolute(refinePromptFile), true);
|
|
286
|
+
assert.equal(path.basename(refinePromptFile), 'refine.md');
|
|
287
|
+
await access(refinePromptFile);
|
|
288
|
+
} finally {
|
|
289
|
+
GPTrans.mmix = originalMmix;
|
|
290
|
+
if (gp.debounceTimer) {
|
|
291
|
+
clearTimeout(gp.debounceTimer);
|
|
292
|
+
}
|
|
293
|
+
await gp.dbTarget.dispose({ clearMemory: true, releaseInstance: true });
|
|
294
|
+
await gp.dbFrom.dispose({ clearMemory: true, releaseInstance: true });
|
|
295
|
+
await rm(directory, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import GPTrans from './index.js';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
4
|
|
|
9
5
|
async function testImageTranslation() {
|
|
10
6
|
try {
|
|
@@ -12,13 +8,14 @@ async function testImageTranslation() {
|
|
|
12
8
|
|
|
13
9
|
// Initialize GPTrans with Portuguese as target language
|
|
14
10
|
const gptrans = new GPTrans({
|
|
11
|
+
path: path.join(import.meta.dirname, 'db'),
|
|
15
12
|
from: 'en-US',
|
|
16
13
|
target: 'pt-BR',
|
|
17
14
|
model: 'sonnet45'
|
|
18
15
|
});
|
|
19
16
|
|
|
20
17
|
// Test image path (you'll need to provide an actual image)
|
|
21
|
-
const testImagePath = path.join(
|
|
18
|
+
const testImagePath = path.join(import.meta.dirname, 'test-image.jpg');
|
|
22
19
|
|
|
23
20
|
// Check if test image exists
|
|
24
21
|
if (!fs.existsSync(testImagePath)) {
|
|
@@ -67,4 +64,3 @@ async function testImageTranslation() {
|
|
|
67
64
|
|
|
68
65
|
// Run the test
|
|
69
66
|
testImageTranslation();
|
|
70
|
-
|