Skip to content

Commit 83f4f2d

Browse files
sjsyrekclaude
andcommitted
fix(ux): improve "target language required" error with clearer suggestions
Replace the confusing JSON config syntax in the error message with a short message + suggestion pointing users to --to and deepl init. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ee9e4c5 commit 83f4f2d

10 files changed

Lines changed: 20 additions & 16 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Improved
11+
- "No target language specified" error now shows clearer suggestions: `--to <lang>` example and `deepl init` instead of raw JSON config syntax
12+
1013
### Changed
1114
- Updated GitHub Actions (`actions/checkout`, `actions/setup-node`) from v4 to v6 across all CI workflows
1215
- Updated `@types/node` from v20 to v25

src/cli/commands/register-translate.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ Examples:
102102
options.to = targetLangs[0];
103103
} else {
104104
throw new ValidationError(
105-
'Target language is required. Use --to <language> or set a default with: deepl config set defaults.targetLangs \'["es"]\'',
105+
'No target language specified.',
106+
'Use --to <lang>: deepl translate "Hello" --to de\n Set a default: deepl init',
106107
);
107108
}
108109
}

src/cli/commands/register-watch.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ Examples:
6161
options.to = targetLangs.join(',');
6262
} else {
6363
throw new ValidationError(
64-
'Target language is required. Use --to <language> or set a default with: deepl config set defaults.targetLangs \'["es"]\'',
64+
'No target language specified.',
65+
'Use --to <lang>: deepl watch ./docs --to de\n Set a default: deepl init',
6566
);
6667
}
6768
}

src/cli/commands/watch.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ export class WatchCommand {
7878
const targetLangs = options.to.split(',').map(lang => lang.trim()).filter(lang => lang.length > 0) as Language[];
7979

8080
if (targetLangs.length === 0) {
81-
throw new ValidationError('At least one target language is required. Use --to es,fr,de');
81+
throw new ValidationError(
82+
'No target language specified.',
83+
'Use --to <lang>: deepl watch ./docs --to es,fr,de\n Set a default: deepl init',
84+
);
8285
}
8386

8487
// Get git-staged files if requested

tests/e2e/cli-document-translation.e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ describe('Document Translation E2E', () => {
252252
const result = runCLIExpectError(`translate "${testFile}"`, 'test-key:fx');
253253

254254
expect(result.status).toBeGreaterThan(0);
255-
expect(result.output).toMatch(/required option.*--to|target.*language.*required|missing.*--to/i);
255+
expect(result.output).toMatch(/required option.*--to|target.*language|No target language specified|missing.*--to/i);
256256
});
257257

258258
it('should handle authentication errors gracefully', () => {

tests/e2e/cli-stdin-stdout.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ describe('CLI Stdin/Stdout E2E', () => {
102102
const result = runCLIExpectError(`translate "${testFile}"`, { apiKey: 'test-key:fx' });
103103

104104
expect(result.status).toBeGreaterThan(0);
105-
expect(result.output).toMatch(/required option.*--to|missing.*--to|target language is required.*--to/i);
105+
expect(result.output).toMatch(/required option.*--to|missing.*--to|target language.*--to|No target language specified/i);
106106
});
107107
});
108108

@@ -215,7 +215,7 @@ describe('CLI Stdin/Stdout E2E', () => {
215215
const result = runCLIExpectError(`translate "${testFile}" --quiet`, { apiKey: 'test-key:fx' });
216216

217217
expect(result.status).toBeGreaterThan(0);
218-
expect(result.output).toMatch(/required option.*--to|target language is required.*--to/i);
218+
expect(result.output).toMatch(/required option.*--to|target language.*--to|No target language specified/i);
219219
});
220220
});
221221
});

tests/e2e/cli-watch.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ describe('Watch Command E2E', () => {
9999
try {
100100
const { status, output } = runCLIExpectError(`watch ${tmpDir} --dry-run`);
101101
expect(status).toBeGreaterThan(0);
102-
expect(output).toContain('Target language is required');
103-
expect(output).toContain('deepl config set');
102+
expect(output).toContain('No target language specified.');
103+
expect(output).toContain('deepl init');
104104
} finally {
105105
fs.rmSync(tmpDir, { recursive: true, force: true });
106106
}

tests/integration/cli-translate.integration.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ describe('Translate CLI Integration', () => {
108108
} catch (error: any) {
109109
const output = error.stderr ?? error.stdout;
110110
expect(output).toMatch(/--to/);
111-
expect(output).toMatch(/defaults\.targetLangs/);
111+
expect(output).toMatch(/deepl init/);
112112
}
113113
});
114114

tests/unit/translate-default-target.test.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,7 @@ describe('translate command default target language', () => {
163163

164164
expect(handleErrorSpy).toHaveBeenCalled();
165165
const error = capturedError as Error;
166-
expect(error.message).toContain('Target language is required');
167-
expect(error.message).toContain('--to');
168-
expect(error.message).toContain('defaults.targetLangs');
166+
expect(error.message).toContain('No target language specified.');
169167
});
170168

171169
it('should throw error when config has no targetLangs', async () => {
@@ -177,9 +175,7 @@ describe('translate command default target language', () => {
177175

178176
expect(handleErrorSpy).toHaveBeenCalled();
179177
const error = capturedError as Error;
180-
expect(error.message).toContain('Target language is required');
181-
expect(error.message).toContain('--to');
182-
expect(error.message).toContain('defaults.targetLangs');
178+
expect(error.message).toContain('No target language specified.');
183179
});
184180

185181
it('should work with stdin when --to is omitted and config has default', async () => {

tests/unit/watch-command.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ describe('WatchCommand', () => {
387387
watchCommand.watch('/some/file.md', {
388388
to: '',
389389
})
390-
).rejects.toThrow('At least one target language is required');
390+
).rejects.toThrow('No target language specified.');
391391
});
392392

393393
it('should display initial watch message with all options', async () => {

0 commit comments

Comments
 (0)