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.
@@ -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[]` | `'sonnet45'` | Model key or array for fallback chain |
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: ['sonnet45', 'gpt41'] // falls back to gpt41 if sonnet45 fails
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: ['sonnet45', 'gpt41']`) for production resilience with automatic fallback.
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 with `from`, `target`, `model`, etc. |
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
- get(context, key) {
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.get(h, 'k1'), 'Hello');
67
- assert.equal(gp.dbTarget.get(h, 'k2'), 'Goodbye');
68
- assert.equal(gp.dbTarget.get(h, 'k3'), 'Thank you');
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.get(h, 'k1'), 'Hello');
90
- assert.equal(gp.dbTarget.get(h, 'k2'), 'Goodbye');
91
- assert.equal(gp.dbTarget.get(h, 'k3'), 'Thank you');
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.get(h, 'k1'), undefined);
153
- assert.equal(gp.dbTarget.get(h, 'k2'), undefined);
154
- assert.equal(gp.dbTarget.get(h, 'k3'), undefined);
155
- assert.equal(gp.dbTarget.get(h, 'k4'), undefined);
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.get(h, 'k1'), 'Yes');
178
- assert.equal(gp.dbTarget.get(h, 'k2'), 'Yes');
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.get(h, 'k1'), 'Hello');
200
- assert.equal(gp.dbTarget.get(h, 'k2'), 'Hello');
201
- assert.equal(gp.dbTarget.get(h, 'k3'), 'Goodbye');
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.get(h, 'k1'), 'Hello');
231
- assert.equal(gp.dbTarget.get(h, 'k2'), 'Goodbye');
232
- assert.equal(gp.dbTarget.get(h, 'k3'), undefined); // Not saved
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
- get(context, key) {
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.get(contextHash, key), 'Comprar ahora');
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(__dirname, 'test-image.jpg');
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
-