transduck 0.2.5 → 0.4.0

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/tests/cli.test.ts CHANGED
@@ -27,7 +27,7 @@ languages:
27
27
  targets:
28
28
  - DE
29
29
  storage:
30
- path: ./translations.duckdb
30
+ path: ./translations.lmdb
31
31
  backend:
32
32
  api_key_env: OPENAI_API_KEY
33
33
  model: gpt-4.1-mini
@@ -55,7 +55,7 @@ describe('CLI functions', () => {
55
55
  });
56
56
  const { existsSync } = await import('fs');
57
57
  expect(existsSync(join(tmpDir, 'transduck.yaml'))).toBe(true);
58
- expect(existsSync(join(tmpDir, 'translations.duckdb'))).toBe(true);
58
+ expect(existsSync(join(tmpDir, 'translations.lmdb'))).toBe(true);
59
59
  });
60
60
 
61
61
  it('stats on empty db shows zero', async () => {
@@ -68,7 +68,7 @@ describe('CLI functions', () => {
68
68
 
69
69
  it('translate returns cached with vars interpolated', async () => {
70
70
  const configPath = writeConfig(tmpDir);
71
- const dbPath = join(tmpDir, 'translations.duckdb');
71
+ const dbPath = join(tmpDir, 'translations.lmdb');
72
72
 
73
73
  // Pre-populate the DB
74
74
  const store = new TranslationStore(dbPath);
@@ -91,7 +91,7 @@ describe('CLI functions', () => {
91
91
 
92
92
  it('translate-plural returns cached plural form', async () => {
93
93
  const configPath = writeConfig(tmpDir);
94
- const dbPath = join(tmpDir, 'translations.duckdb');
94
+ const dbPath = join(tmpDir, 'translations.lmdb');
95
95
 
96
96
  // Pre-populate the DB with plural forms
97
97
  const store = new TranslationStore(dbPath);
@@ -132,7 +132,7 @@ describe('CLI functions', () => {
132
132
 
133
133
  it('warm handles plural entries', async () => {
134
134
  const configPath = writeConfig(tmpDir);
135
- const dbPath = join(tmpDir, 'translations.duckdb');
135
+ const dbPath = join(tmpDir, 'translations.lmdb');
136
136
 
137
137
  // Pre-populate some plural forms so warm skips them
138
138
  const store = new TranslationStore(dbPath);
@@ -202,7 +202,7 @@ describe('CLI functions', () => {
202
202
  writeFileSync(join(srcDir, 'app.py'), 'ait("Hello")\n');
203
203
 
204
204
  // Pre-populate the DB so warm skips
205
- const dbPath = join(tmpDir, 'translations.duckdb');
205
+ const dbPath = join(tmpDir, 'translations.lmdb');
206
206
  const store = new TranslationStore(dbPath);
207
207
  await store.initialize();
208
208
  await store.insert({
@@ -19,7 +19,7 @@ languages:
19
19
  - DE
20
20
  - ES
21
21
  storage:
22
- path: ./translations.duckdb
22
+ path: ./translations.lmdb
23
23
  backend:
24
24
  api_key_env: OPENAI_API_KEY
25
25
  model: gpt-4.1-mini
@@ -60,7 +60,7 @@ describe('loadConfig', () => {
60
60
  const configPath = join(tmpDir, 'transduck.yaml');
61
61
  writeFileSync(configPath, VALID_YAML);
62
62
  const cfg = loadConfig(configPath);
63
- expect(cfg.storagePath).toBe(join(tmpDir, 'translations.duckdb'));
63
+ expect(cfg.storagePath).toBe(join(tmpDir, 'translations.lmdb'));
64
64
  });
65
65
 
66
66
  it('discovers config from TRANSDUCK_CONFIG env var', () => {
@@ -21,7 +21,7 @@ languages:
21
21
  targets:
22
22
  - DE
23
23
  storage:
24
- path: ./translations.duckdb
24
+ path: ./translations.lmdb
25
25
  backend:
26
26
  api_key_env: OPENAI_API_KEY
27
27
  model: gpt-4.1-mini
@@ -48,7 +48,7 @@ describe('handleTranslationRequest', () => {
48
48
  const { createHash } = await import('crypto');
49
49
  const hash = (t: string) => createHash('sha256').update(t).digest('hex');
50
50
 
51
- const store = new TranslationStore(join(tmpDir, 'translations.duckdb'));
51
+ const store = new TranslationStore(join(tmpDir, 'translations.lmdb'));
52
52
  await store.initialize();
53
53
  await store.insert({
54
54
  sourceText: 'Hello', sourceLang: 'EN', targetLang: 'DE',
@@ -83,7 +83,7 @@ describe('handleTranslationRequest', () => {
83
83
  const { createHash } = await import('crypto');
84
84
  const hash = (t: string) => createHash('sha256').update(t).digest('hex');
85
85
 
86
- const store = new TranslationStore(join(tmpDir, 'translations.duckdb'));
86
+ const store = new TranslationStore(join(tmpDir, 'translations.lmdb'));
87
87
  await store.initialize();
88
88
  await store.insert({
89
89
  sourceText: 'Book', sourceLang: 'EN', targetLang: 'DE',
@@ -106,7 +106,7 @@ describe('handleTranslationRequest', () => {
106
106
  const { createHash } = await import('crypto');
107
107
  const hash = (t: string) => createHash('sha256').update(t).digest('hex');
108
108
 
109
- const store = new TranslationStore(join(tmpDir, 'translations.duckdb'));
109
+ const store = new TranslationStore(join(tmpDir, 'translations.lmdb'));
110
110
  await store.initialize();
111
111
  const sourceKey = '{count} item\x00{count} items';
112
112
  await store.insertPlural({
@@ -20,7 +20,7 @@ function makeConfig(overrides: Partial<TransduckConfig> = {}): TransduckConfig {
20
20
  projectContext: 'A test site',
21
21
  sourceLang: 'EN',
22
22
  targetLangs: ['DE'],
23
- storagePath: '/tmp/test.duckdb',
23
+ storagePath: '/tmp/test.lmdb',
24
24
  provider: 'openai',
25
25
  apiKeyEnv: 'OPENAI_API_KEY',
26
26
  tokenEnv: 'CLAUDE_CODE_OAUTH_TOKEN',
@@ -207,7 +207,7 @@ languages:
207
207
  source: EN
208
208
  targets: [DE]
209
209
  storage:
210
- path: ./translations.duckdb
210
+ path: ./translations.lmdb
211
211
  backend:
212
212
  api_key_env: OPENAI_API_KEY
213
213
  model: gpt-4.1-mini
@@ -230,7 +230,7 @@ languages:
230
230
  source: EN
231
231
  targets: [DE]
232
232
  storage:
233
- path: ./translations.duckdb
233
+ path: ./translations.lmdb
234
234
  backend:
235
235
  provider: claude_api
236
236
  api_key_env: ANTHROPIC_API_KEY
@@ -255,7 +255,7 @@ languages:
255
255
  source: EN
256
256
  targets: [DE]
257
257
  storage:
258
- path: ./translations.duckdb
258
+ path: ./translations.lmdb
259
259
  backend:
260
260
  provider: claude_code
261
261
  token_env: CLAUDE_CODE_OAUTH_TOKEN
@@ -277,7 +277,7 @@ languages:
277
277
  source: EN
278
278
  targets: [DE]
279
279
  storage:
280
- path: ./translations.duckdb
280
+ path: ./translations.lmdb
281
281
  backend:
282
282
  provider: claude_code
283
283
  `);
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
2
  import React from 'react';
3
- import { render, screen, cleanup, waitFor } from '@testing-library/react';
4
- import { TransDuckProvider, t, ait, tPlural, aitPlural, useTransDuck, _resetReactState } from '../src/react/index.js';
3
+ import { render, screen, cleanup, waitFor, act } from '@testing-library/react';
4
+ import { TransDuckProvider, t, ait, tPlural, aitPlural, useTransDuck, _resetReactState, _getReactState } from '../src/react/index.js';
5
+ import type { UseTransDuckReturn } from '../src/react/index.js';
5
6
 
6
7
  // Mock fetch globally
7
8
  const mockFetch = vi.fn();
@@ -159,4 +160,364 @@ describe('TransDuckProvider + t()', () => {
159
160
  expect(mockFetch).not.toHaveBeenCalled();
160
161
  expect(screen.getByTestId('text').textContent).toBe('Hallo');
161
162
  });
163
+
164
+ it('t() tracks called keys in _state.knownKeys', async () => {
165
+ mockFetch.mockResolvedValue({
166
+ ok: true,
167
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
168
+ });
169
+
170
+ function TestComp() {
171
+ useTransDuck();
172
+ return <span>{t('Hello')}</span>;
173
+ }
174
+
175
+ render(
176
+ <TransDuckProvider language="DE">
177
+ <TestComp />
178
+ </TransDuckProvider>
179
+ );
180
+
181
+ const state = _getReactState();
182
+ expect(state.knownKeys.has('Hello||')).toBe(true);
183
+ });
184
+
185
+ it('useTransDuck() returns t, tPlural, ait, aitPlural, language, setLanguage, isLoading', () => {
186
+ let hookResult: ReturnType<typeof useTransDuck> | null = null;
187
+
188
+ function TestComp() {
189
+ hookResult = useTransDuck();
190
+ return <span>test</span>;
191
+ }
192
+
193
+ render(
194
+ <TransDuckProvider language="DE">
195
+ <TestComp />
196
+ </TransDuckProvider>
197
+ );
198
+
199
+ expect(hookResult).not.toBeNull();
200
+ expect(hookResult!.t).toBe(t);
201
+ expect(hookResult!.tPlural).toBe(tPlural);
202
+ expect(hookResult!.ait).toBe(ait);
203
+ expect(hookResult!.aitPlural).toBe(aitPlural);
204
+ expect(hookResult!.language).toBe('DE');
205
+ expect(typeof hookResult!.setLanguage).toBe('function');
206
+ expect(hookResult!.isLoading).toBe(false);
207
+ });
208
+
209
+ it('setLanguage() switches language, clears cache, and re-fetches', async () => {
210
+ // First render in DE
211
+ mockFetch.mockResolvedValueOnce({
212
+ ok: true,
213
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
214
+ });
215
+ // After switch to ES
216
+ mockFetch.mockResolvedValueOnce({
217
+ ok: true,
218
+ json: async () => ({ translations: { 'Hello||': 'Hola' }, plurals: {} }),
219
+ });
220
+
221
+ let hookRef: UseTransDuckReturn | null = null;
222
+
223
+ function TestComp() {
224
+ const hook = useTransDuck();
225
+ hookRef = hook;
226
+ return <span data-testid="text">{hook.t('Hello')}</span>;
227
+ }
228
+
229
+ render(
230
+ <TransDuckProvider language="DE">
231
+ <TestComp />
232
+ </TransDuckProvider>
233
+ );
234
+
235
+ // Wait for DE translations
236
+ await waitFor(() => {
237
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
238
+ });
239
+
240
+ // Switch to ES
241
+ await act(async () => {
242
+ hookRef!.setLanguage('ES');
243
+ });
244
+
245
+ // Wait for ES translations
246
+ await waitFor(() => {
247
+ expect(screen.getByTestId('text').textContent).toBe('Hola');
248
+ });
249
+
250
+ expect(hookRef!.language).toBe('ES');
251
+ });
252
+
253
+ it('setLanguage() with current language is a no-op', async () => {
254
+ mockFetch.mockResolvedValueOnce({
255
+ ok: true,
256
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
257
+ });
258
+
259
+ let hookRef: UseTransDuckReturn | null = null;
260
+
261
+ function TestComp() {
262
+ const hook = useTransDuck();
263
+ hookRef = hook;
264
+ return <span data-testid="text">{hook.t('Hello')}</span>;
265
+ }
266
+
267
+ render(
268
+ <TransDuckProvider language="DE">
269
+ <TestComp />
270
+ </TransDuckProvider>
271
+ );
272
+
273
+ await waitFor(() => {
274
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
275
+ });
276
+
277
+ mockFetch.mockClear();
278
+
279
+ await act(async () => {
280
+ hookRef!.setLanguage('DE');
281
+ });
282
+
283
+ expect(mockFetch).not.toHaveBeenCalled();
284
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
285
+ });
286
+
287
+ it('setLanguage() to source language skips fetch', async () => {
288
+ mockFetch.mockResolvedValueOnce({
289
+ ok: true,
290
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
291
+ });
292
+
293
+ let hookRef: UseTransDuckReturn | null = null;
294
+
295
+ function TestComp() {
296
+ const hook = useTransDuck();
297
+ hookRef = hook;
298
+ return <span data-testid="text">{hook.t('Hello')}</span>;
299
+ }
300
+
301
+ render(
302
+ <TransDuckProvider language="DE" sourceLang="EN">
303
+ <TestComp />
304
+ </TransDuckProvider>
305
+ );
306
+
307
+ await waitFor(() => {
308
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
309
+ });
310
+
311
+ mockFetch.mockClear();
312
+
313
+ await act(async () => {
314
+ hookRef!.setLanguage('EN');
315
+ });
316
+
317
+ await waitFor(() => {
318
+ expect(screen.getByTestId('text').textContent).toBe('Hello');
319
+ });
320
+ expect(mockFetch).not.toHaveBeenCalled();
321
+ });
322
+
323
+ it('isLoading is true during language switch fetch', async () => {
324
+ mockFetch.mockResolvedValueOnce({
325
+ ok: true,
326
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
327
+ });
328
+
329
+ let resolveSecondFetch: ((value: unknown) => void) | null = null;
330
+ mockFetch.mockImplementationOnce(() => new Promise(resolve => {
331
+ resolveSecondFetch = resolve;
332
+ }));
333
+
334
+ let hookRef: UseTransDuckReturn | null = null;
335
+
336
+ function TestComp() {
337
+ const hook = useTransDuck();
338
+ hookRef = hook;
339
+ return <span data-testid="text">{hook.t('Hello')}</span>;
340
+ }
341
+
342
+ render(
343
+ <TransDuckProvider language="DE">
344
+ <TestComp />
345
+ </TransDuckProvider>
346
+ );
347
+
348
+ await waitFor(() => {
349
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
350
+ });
351
+
352
+ await act(async () => {
353
+ hookRef!.setLanguage('ES');
354
+ });
355
+
356
+ expect(hookRef!.isLoading).toBe(true);
357
+
358
+ await act(async () => {
359
+ resolveSecondFetch!({
360
+ ok: true,
361
+ json: async () => ({ translations: { 'Hello||': 'Hola' }, plurals: {} }),
362
+ });
363
+ });
364
+
365
+ await waitFor(() => {
366
+ expect(hookRef!.isLoading).toBe(false);
367
+ expect(screen.getByTestId('text').textContent).toBe('Hola');
368
+ });
369
+ });
370
+
371
+ it('isLoading clears on fetch error during language switch', async () => {
372
+ mockFetch.mockResolvedValueOnce({
373
+ ok: true,
374
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
375
+ });
376
+ mockFetch.mockRejectedValueOnce(new Error('Network error'));
377
+
378
+ let hookRef: UseTransDuckReturn | null = null;
379
+
380
+ function TestComp() {
381
+ const hook = useTransDuck();
382
+ hookRef = hook;
383
+ return <span data-testid="text">{hook.t('Hello')}</span>;
384
+ }
385
+
386
+ render(
387
+ <TransDuckProvider language="DE">
388
+ <TestComp />
389
+ </TransDuckProvider>
390
+ );
391
+
392
+ await waitFor(() => {
393
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
394
+ });
395
+
396
+ await act(async () => {
397
+ hookRef!.setLanguage('ES');
398
+ });
399
+
400
+ await waitFor(() => {
401
+ expect(hookRef!.isLoading).toBe(false);
402
+ });
403
+ });
404
+
405
+ it('discards stale fetch response when language changes during fetch', async () => {
406
+ mockFetch.mockResolvedValueOnce({
407
+ ok: true,
408
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
409
+ });
410
+
411
+ let resolveFRFetch: ((value: unknown) => void) | null = null;
412
+ mockFetch.mockImplementationOnce(() => new Promise(resolve => {
413
+ resolveFRFetch = resolve;
414
+ }));
415
+
416
+ mockFetch.mockResolvedValueOnce({
417
+ ok: true,
418
+ json: async () => ({ translations: { 'Hello||': 'Hola' }, plurals: {} }),
419
+ });
420
+
421
+ let hookRef: UseTransDuckReturn | null = null;
422
+
423
+ function TestComp() {
424
+ const hook = useTransDuck();
425
+ hookRef = hook;
426
+ return <span data-testid="text">{hook.t('Hello')}</span>;
427
+ }
428
+
429
+ render(
430
+ <TransDuckProvider language="DE">
431
+ <TestComp />
432
+ </TransDuckProvider>
433
+ );
434
+
435
+ await waitFor(() => {
436
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
437
+ });
438
+
439
+ await act(async () => {
440
+ hookRef!.setLanguage('FR');
441
+ });
442
+
443
+ await act(async () => {
444
+ hookRef!.setLanguage('ES');
445
+ });
446
+
447
+ await waitFor(() => {
448
+ expect(screen.getByTestId('text').textContent).toBe('Hola');
449
+ });
450
+
451
+ await act(async () => {
452
+ resolveFRFetch!({
453
+ ok: true,
454
+ json: async () => ({ translations: { 'Hello||': 'Bonjour' }, plurals: {} }),
455
+ });
456
+ });
457
+
458
+ expect(screen.getByTestId('text').textContent).toBe('Hola');
459
+ expect(hookRef!.language).toBe('ES');
460
+ });
461
+
462
+ it('changing language prop triggers switchLanguage with isLoading', async () => {
463
+ mockFetch.mockResolvedValueOnce({
464
+ ok: true,
465
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
466
+ });
467
+ mockFetch.mockResolvedValueOnce({
468
+ ok: true,
469
+ json: async () => ({ translations: { 'Hello||': 'Hola' }, plurals: {} }),
470
+ });
471
+
472
+ let hookRef: UseTransDuckReturn | null = null;
473
+
474
+ function TestComp() {
475
+ const hook = useTransDuck();
476
+ hookRef = hook;
477
+ return <span data-testid="text">{hook.t('Hello')}</span>;
478
+ }
479
+
480
+ const { rerender } = render(
481
+ <TransDuckProvider language="DE">
482
+ <TestComp />
483
+ </TransDuckProvider>
484
+ );
485
+
486
+ await waitFor(() => {
487
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
488
+ });
489
+
490
+ rerender(
491
+ <TransDuckProvider language="ES">
492
+ <TestComp />
493
+ </TransDuckProvider>
494
+ );
495
+
496
+ await waitFor(() => {
497
+ expect(screen.getByTestId('text').textContent).toBe('Hola');
498
+ expect(hookRef!.language).toBe('ES');
499
+ });
500
+ });
501
+
502
+ it('useTransDuck() still works without destructuring (backward compat)', async () => {
503
+ mockFetch.mockResolvedValue({
504
+ ok: true,
505
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
506
+ });
507
+
508
+ function TestComp() {
509
+ useTransDuck();
510
+ return <span data-testid="text">{t('Hello')}</span>;
511
+ }
512
+
513
+ render(
514
+ <TransDuckProvider language="DE">
515
+ <TestComp />
516
+ </TransDuckProvider>
517
+ );
518
+
519
+ await waitFor(() => {
520
+ expect(screen.getByTestId('text').textContent).toBe('Hallo');
521
+ });
522
+ });
162
523
  });
@@ -1,4 +1,4 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import { createHash } from 'crypto';
3
3
  import { join } from 'path';
4
4
  import { mkdtempSync } from 'fs';
@@ -14,13 +14,17 @@ describe('TranslationStore', () => {
14
14
 
15
15
  beforeEach(async () => {
16
16
  const tmpDir = mkdtempSync(join(tmpdir(), 'transduck-test-'));
17
- store = new TranslationStore(join(tmpDir, 'test.duckdb'));
17
+ store = new TranslationStore(join(tmpDir, 'test.lmdb'));
18
18
  await store.initialize();
19
19
  });
20
20
 
21
- it('creates table on initialize', async () => {
22
- const result = await store.query('SELECT count(*) as c FROM translations');
23
- expect(result[0].c).toBe(0);
21
+ afterEach(() => {
22
+ store.close();
23
+ });
24
+
25
+ it('starts empty', async () => {
26
+ const stats = await store.stats();
27
+ expect(stats.totalTranslations).toBe(0);
24
28
  });
25
29
 
26
30
  it('inserts and looks up translation', async () => {
@@ -195,68 +199,32 @@ describe('TranslationStore', () => {
195
199
  await store.insertPlural(entry); // should not throw
196
200
  });
197
201
 
198
- // --- Migration ---
199
-
200
- it('migrates v1 schema to v2', async () => {
201
- // Create a fresh store with v1 schema
202
- const tmpDir = mkdtempSync(join(tmpdir(), 'transduck-migration-'));
203
- const dbPath = join(tmpDir, 'migrate.duckdb');
204
-
205
- // Manually create v1 schema (no plural_category column)
206
- const { DuckDBInstance } = await import('@duckdb/node-api');
207
- const instance = await DuckDBInstance.create(dbPath);
208
- const conn = await instance.connect();
209
- await conn.run(`
210
- CREATE TABLE translations (
211
- source_text TEXT NOT NULL,
212
- source_lang TEXT NOT NULL,
213
- target_lang TEXT NOT NULL,
214
- project_context_hash TEXT NOT NULL,
215
- string_context_hash TEXT NOT NULL,
216
- translated_text TEXT NOT NULL,
217
- model TEXT NOT NULL,
218
- status TEXT NOT NULL DEFAULT 'translated',
219
- created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
220
- PRIMARY KEY(source_text, source_lang, target_lang, project_context_hash, string_context_hash)
221
- )
222
- `);
223
- // Insert a v1 row
224
- await conn.run(`
225
- INSERT INTO translations (source_text, source_lang, target_lang, project_context_hash, string_context_hash, translated_text, model, status)
226
- VALUES ('Hello', 'EN', 'DE', '${hash('ctx')}', '${hash('')}', 'Hallo', 'gpt-4.1-mini', 'translated')
227
- `);
228
-
229
- // Close the raw connection
230
- // (DuckDB node-api handles cleanup via GC, just null out)
202
+ // --- Count and clear ---
231
203
 
232
- // Now open with TranslationStore should migrate
233
- const migratedStore = new TranslationStore(dbPath);
234
- await migratedStore.initialize();
235
-
236
- // Old data should still be accessible
237
- const result = await migratedStore.lookup({
204
+ it('counts and clears entries', async () => {
205
+ await store.insert({
238
206
  sourceText: 'Hello', sourceLang: 'EN', targetLang: 'DE',
239
207
  projectContextHash: hash('ctx'), stringContextHash: hash(''),
208
+ translatedText: 'Hallo', model: 'gpt-4.1-mini', status: 'translated',
240
209
  });
241
- expect(result).toBe('Hallo');
242
-
243
- // Plural methods should work
244
- await migratedStore.insertPlural({
245
- sourceText: 'test\x00tests',
246
- sourceLang: 'EN', targetLang: 'DE',
210
+ await store.insert({
211
+ sourceText: 'Bad', sourceLang: 'EN', targetLang: 'DE',
247
212
  projectContextHash: hash('ctx'), stringContextHash: hash(''),
248
- pluralCategory: 'other',
249
- translatedText: 'Tests',
250
- model: 'gpt-4.1-mini', status: 'translated',
213
+ translatedText: 'schlecht', model: 'gpt-4.1-mini', status: 'failed',
251
214
  });
252
-
253
- const pluralResult = await migratedStore.lookupPlural({
254
- sourceText: 'test\x00tests',
255
- sourceLang: 'EN', targetLang: 'DE',
215
+ await store.insert({
216
+ sourceText: 'Hello', sourceLang: 'EN', targetLang: 'ES',
256
217
  projectContextHash: hash('ctx'), stringContextHash: hash(''),
218
+ translatedText: 'Hola', model: 'gpt-4.1-mini', status: 'translated',
257
219
  });
258
- expect(pluralResult).toEqual({ other: 'Tests' });
259
220
 
260
- migratedStore.close();
221
+ expect(store.count()).toBe(3);
222
+ expect(store.count('DE')).toBe(2);
223
+ expect(store.count(undefined, true)).toBe(1);
224
+ expect(store.count('DE', true)).toBe(1);
225
+
226
+ const deleted = await store.clear('DE', true);
227
+ expect(deleted).toBe(1);
228
+ expect(store.count()).toBe(2);
261
229
  });
262
230
  });