transduck 0.6.0 → 0.6.2

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.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import 'dotenv/config';
2
3
  import { Command } from 'commander';
3
4
  export interface InitOptions {
4
5
  dir: string;
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import 'dotenv/config';
2
3
  import { createHash } from 'crypto';
3
4
  import { readFileSync, writeFileSync, mkdirSync } from 'fs';
4
5
  import { join, dirname } from 'path';
@@ -543,7 +544,7 @@ export async function runStats(opts) {
543
544
  }
544
545
  // CLI entry point
545
546
  const program = new Command();
546
- program.name('transduck').description('AI-native translation tool').version('0.6.0');
547
+ program.name('transduck').description('AI-native translation tool').version('0.6.2');
547
548
  program.command('init')
548
549
  .description('Initialize a new transduck project')
549
550
  .action(async () => {
package/dist/config.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import 'dotenv/config';
1
2
  export interface TransduckConfig {
2
3
  projectName: string;
3
4
  projectContext: string;
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ import 'dotenv/config';
1
2
  import { readFileSync, existsSync } from 'fs';
2
3
  import { resolve, dirname, join } from 'path';
3
4
  import { parse as parseYaml } from 'yaml';
@@ -51,7 +51,14 @@ export async function translatePlural(one, other, sourceLang, targetLang, projec
51
51
  model: config.backendModel,
52
52
  messages,
53
53
  temperature: 0.3,
54
+ response_format: { type: 'json_object' },
54
55
  });
55
- const raw = response.choices[0].message.content.trim();
56
+ let raw = (response.choices[0].message.content || '').trim();
57
+ if (raw.startsWith('```')) {
58
+ raw = raw.split('\n').slice(1).join('\n').split('```')[0].trim();
59
+ }
60
+ if (!raw) {
61
+ throw new Error(`Empty response from OpenAI for plural translation: '${one}' / '${other}' → ${targetLang}`);
62
+ }
56
63
  return JSON.parse(raw);
57
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transduck",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "AI-native translation tool using source text as keys",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,8 +24,9 @@
24
24
  "test:watch": "vitest"
25
25
  },
26
26
  "dependencies": {
27
- "commander": "^12.0.0",
28
27
  "better-sqlite3": "^11.0.0",
28
+ "commander": "^12.0.0",
29
+ "dotenv": "^16.6.1",
29
30
  "openai": "^4.0.0",
30
31
  "yaml": "^2.0.0"
31
32
  },
@@ -33,6 +34,7 @@
33
34
  "@testing-library/react": "^16.0.0",
34
35
  "@types/better-sqlite3": "^7.0.0",
35
36
  "@types/node": "^22.0.0",
37
+ "@types/pg": "^8.18.0",
36
38
  "@types/react": "^19.0.0",
37
39
  "@types/react-dom": "^19.0.0",
38
40
  "jsdom": "^25.0.0",
package/src/cli.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import 'dotenv/config';
3
4
  import { createHash } from 'crypto';
4
5
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
5
6
  import { resolve, join, dirname } from 'path';
@@ -660,7 +661,7 @@ export async function runStats(opts: StatsOptions): Promise<string> {
660
661
  // CLI entry point
661
662
  const program = new Command();
662
663
 
663
- program.name('transduck').description('AI-native translation tool').version('0.6.0');
664
+ program.name('transduck').description('AI-native translation tool').version('0.6.2');
664
665
 
665
666
  program.command('init')
666
667
  .description('Initialize a new transduck project')
package/src/config.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import 'dotenv/config';
1
2
  import { readFileSync, existsSync } from 'fs';
2
3
  import { resolve, dirname, join } from 'path';
3
4
  import { parse as parseYaml } from 'yaml';
@@ -84,8 +84,15 @@ export async function translatePlural(
84
84
  model: config.backendModel,
85
85
  messages,
86
86
  temperature: 0.3,
87
+ response_format: { type: 'json_object' },
87
88
  });
88
89
 
89
- const raw = response.choices[0].message.content.trim();
90
+ let raw = (response.choices[0].message.content || '').trim();
91
+ if (raw.startsWith('```')) {
92
+ raw = raw.split('\n').slice(1).join('\n').split('```')[0].trim();
93
+ }
94
+ if (!raw) {
95
+ throw new Error(`Empty response from OpenAI for plural translation: '${one}' / '${other}' → ${targetLang}`);
96
+ }
90
97
  return JSON.parse(raw);
91
98
  }
@@ -116,4 +116,18 @@ describe('loadConfig', () => {
116
116
  delete process.env.TRANSDUCK_CONFIG;
117
117
  expect(() => loadConfig(join(tmpDir, 'nonexistent.yaml'))).toThrow();
118
118
  });
119
+
120
+ it('loads env vars from .env file via dotenv', async () => {
121
+ // Write a .env file in a temp dir
122
+ const envDir = makeTmpDir();
123
+ const envPath = join(envDir, '.env');
124
+ writeFileSync(envPath, 'TRANSDUCK_TEST_DOTENV=loaded_from_dotenv\n');
125
+
126
+ // dotenv/config reads from cwd, so we use dotenv.config with explicit path
127
+ const dotenv = await import('dotenv');
128
+ dotenv.config({ path: envPath });
129
+
130
+ expect(process.env.TRANSDUCK_TEST_DOTENV).toBe('loaded_from_dotenv');
131
+ delete process.env.TRANSDUCK_TEST_DOTENV;
132
+ });
119
133
  });