transduck 0.6.9 → 0.6.10

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/cli.js CHANGED
@@ -637,7 +637,7 @@ export async function runStats(opts) {
637
637
  }
638
638
  // CLI entry point
639
639
  const program = new Command();
640
- program.name('transduck').description('AI-native translation tool').version('0.6.9');
640
+ program.name('transduck').description('AI-native translation tool').version('0.6.10');
641
641
  program.command('init')
642
642
  .description('Initialize a new transduck project')
643
643
  .action(async () => {
package/dist/handler.d.ts CHANGED
@@ -12,6 +12,7 @@ interface TranslationRequestPlural {
12
12
  }
13
13
  export interface TranslationRequest {
14
14
  language: string;
15
+ sourceLang?: string;
15
16
  strings: TranslationRequestString[];
16
17
  plurals: TranslationRequestPlural[];
17
18
  }
package/dist/handler.js CHANGED
@@ -46,12 +46,13 @@ export async function handleTranslationRequest(body, configPath, opts) {
46
46
  const store = await getStore(configPath);
47
47
  const shared = await getSharedStore(configPath);
48
48
  const targetLang = body.language.toUpperCase();
49
+ const bodySourceLang = body.sourceLang?.toUpperCase();
49
50
  const projectContextHash = hash(cfg.projectContext);
50
51
  const translations = {};
51
52
  const plurals = {};
52
53
  // Translate regular strings
53
54
  for (const item of body.strings ?? []) {
54
- const sourceLang = item.sourceLang?.toUpperCase() ?? cfg.sourceLang;
55
+ const sourceLang = item.sourceLang?.toUpperCase() ?? bodySourceLang ?? cfg.sourceLang;
55
56
  const stringContextHash = hash(item.context ?? '');
56
57
  const key = `${item.text}||${item.context ?? ''}`;
57
58
  const lookupParams = {
@@ -109,7 +110,7 @@ export async function handleTranslationRequest(body, configPath, opts) {
109
110
  }
110
111
  // Translate plurals
111
112
  for (const item of body.plurals ?? []) {
112
- const sourceLang = item.sourceLang?.toUpperCase() ?? cfg.sourceLang;
113
+ const sourceLang = item.sourceLang?.toUpperCase() ?? bodySourceLang ?? cfg.sourceLang;
113
114
  const stringContextHash = hash(item.context ?? '');
114
115
  const sourceKey = item.one + '\x00' + item.other;
115
116
  const responseKey = `${sourceKey}||${item.context ?? ''}`;
@@ -282,6 +282,7 @@ export function TransDuckProvider({ language, sourceLang = 'EN', endpoint = '/ap
282
282
  headers: { 'Content-Type': 'application/json' },
283
283
  body: JSON.stringify({
284
284
  language: _state.language,
285
+ sourceLang: _state.sourceLang,
285
286
  strings,
286
287
  plurals,
287
288
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transduck",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "description": "AI-native translation tool using source text as keys",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/cli.ts CHANGED
@@ -743,7 +743,7 @@ export async function runStats(opts: StatsOptions): Promise<string> {
743
743
  // CLI entry point
744
744
  const program = new Command();
745
745
 
746
- program.name('transduck').description('AI-native translation tool').version('0.6.9');
746
+ program.name('transduck').description('AI-native translation tool').version('0.6.10');
747
747
 
748
748
  program.command('init')
749
749
  .description('Initialize a new transduck project')
package/src/handler.ts CHANGED
@@ -25,6 +25,7 @@ interface TranslationRequestPlural {
25
25
 
26
26
  export interface TranslationRequest {
27
27
  language: string;
28
+ sourceLang?: string;
28
29
  strings: TranslationRequestString[];
29
30
  plurals: TranslationRequestPlural[];
30
31
  }
@@ -77,6 +78,7 @@ export async function handleTranslationRequest(
77
78
  const store = await getStore(configPath);
78
79
  const shared = await getSharedStore(configPath);
79
80
  const targetLang = body.language.toUpperCase();
81
+ const bodySourceLang = body.sourceLang?.toUpperCase();
80
82
 
81
83
  const projectContextHash = hash(cfg.projectContext);
82
84
 
@@ -85,7 +87,7 @@ export async function handleTranslationRequest(
85
87
 
86
88
  // Translate regular strings
87
89
  for (const item of body.strings ?? []) {
88
- const sourceLang = item.sourceLang?.toUpperCase() ?? cfg.sourceLang;
90
+ const sourceLang = item.sourceLang?.toUpperCase() ?? bodySourceLang ?? cfg.sourceLang;
89
91
  const stringContextHash = hash(item.context ?? '');
90
92
  const key = `${item.text}||${item.context ?? ''}`;
91
93
  const lookupParams = {
@@ -148,7 +150,7 @@ export async function handleTranslationRequest(
148
150
 
149
151
  // Translate plurals
150
152
  for (const item of body.plurals ?? []) {
151
- const sourceLang = item.sourceLang?.toUpperCase() ?? cfg.sourceLang;
153
+ const sourceLang = item.sourceLang?.toUpperCase() ?? bodySourceLang ?? cfg.sourceLang;
152
154
  const stringContextHash = hash(item.context ?? '');
153
155
  const sourceKey = item.one + '\x00' + item.other;
154
156
  const responseKey = `${sourceKey}||${item.context ?? ''}`;
@@ -382,6 +382,7 @@ export function TransDuckProvider({
382
382
  headers: { 'Content-Type': 'application/json' },
383
383
  body: JSON.stringify({
384
384
  language: _state.language,
385
+ sourceLang: _state.sourceLang,
385
386
  strings,
386
387
  plurals,
387
388
  }),
@@ -133,4 +133,54 @@ describe('handleTranslationRequest', () => {
133
133
  expect(result.plurals[pluralKey]).toBeDefined();
134
134
  expect(result.plurals[pluralKey].one).toBe('{count} Artikel');
135
135
  });
136
+
137
+ it('body-level sourceLang overrides config sourceLang', async () => {
138
+ const { TranslationStore } = await import('../src/storage.js');
139
+ const { createHash } = await import('crypto');
140
+ const hash = (t: string) => createHash('sha256').update(t).digest('hex');
141
+
142
+ // Cache entry stored under source FR (not config's EN)
143
+ const store = new TranslationStore(join(tmpDir, 'translations.db'));
144
+ await store.initialize();
145
+ await store.insert({
146
+ sourceText: 'Bonjour', sourceLang: 'FR', targetLang: 'DE',
147
+ projectContextHash: hash('A test site'), stringContextHash: hash(''),
148
+ translatedText: 'Hallo', model: 'gpt-4.1-mini', status: 'translated', stringContext: '',
149
+ });
150
+ store.close();
151
+
152
+ // With body-level sourceLang: FR, the handler should find the cache entry
153
+ const result = await handleTranslationRequest({
154
+ language: 'DE',
155
+ sourceLang: 'FR',
156
+ strings: [{ text: 'Bonjour' }],
157
+ plurals: [],
158
+ }, configPath);
159
+
160
+ expect(result.translations['Bonjour||']).toBe('Hallo');
161
+ });
162
+
163
+ it('per-item sourceLang overrides body-level sourceLang', async () => {
164
+ const { TranslationStore } = await import('../src/storage.js');
165
+ const { createHash } = await import('crypto');
166
+ const hash = (t: string) => createHash('sha256').update(t).digest('hex');
167
+
168
+ const store = new TranslationStore(join(tmpDir, 'translations.db'));
169
+ await store.initialize();
170
+ await store.insert({
171
+ sourceText: 'Hola', sourceLang: 'ES', targetLang: 'DE',
172
+ projectContextHash: hash('A test site'), stringContextHash: hash(''),
173
+ translatedText: 'Hallo', model: 'gpt-4.1-mini', status: 'translated', stringContext: '',
174
+ });
175
+ store.close();
176
+
177
+ const result = await handleTranslationRequest({
178
+ language: 'DE',
179
+ sourceLang: 'FR',
180
+ strings: [{ text: 'Hola', sourceLang: 'ES' }],
181
+ plurals: [],
182
+ }, configPath);
183
+
184
+ expect(result.translations['Hola||']).toBe('Hallo');
185
+ });
136
186
  });
@@ -630,6 +630,32 @@ describe('TransDuckProvider + t()', () => {
630
630
  expect(captured!.source).toBe('cache');
631
631
  });
632
632
 
633
+ it('fetch body includes sourceLang from provider prop', async () => {
634
+ mockFetch.mockResolvedValue({
635
+ ok: true,
636
+ json: async () => ({ translations: { 'Hello||': 'Hallo' }, plurals: {} }),
637
+ });
638
+
639
+ function TestComp() {
640
+ useTransDuck();
641
+ return <span data-testid="text">{t('Hello')}</span>;
642
+ }
643
+
644
+ render(
645
+ <TransDuckProvider language="DE" sourceLang="FR">
646
+ <TestComp />
647
+ </TransDuckProvider>
648
+ );
649
+
650
+ await waitFor(() => {
651
+ expect(mockFetch).toHaveBeenCalled();
652
+ });
653
+
654
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
655
+ expect(body.sourceLang).toBe('FR');
656
+ expect(body.language).toBe('DE');
657
+ });
658
+
633
659
  it('useTransDuck() still works without destructuring (backward compat)', async () => {
634
660
  mockFetch.mockResolvedValue({
635
661
  ok: true,