Skip to content

Commit d35854d

Browse files
hive-botclaude
andcommitted
feat: accept any string for text option enums and parse tagHandlingVersion
Formality, SentenceSplittingMode, TagHandlingMode, TagHandlingVersion and ModelType accept any string besides their listed values, so new API values need no type cast. SentenceSplittingMode lists the API values '1' and '0'; 'on' and 'default' are deprecated in favour of '1'. TextResult gains tagHandlingVersion, parsed from the response field tag_handling_version; undefined when absent or not a string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 38b0429 commit d35854d

5 files changed

Lines changed: 106 additions & 18 deletions

File tree

‎CHANGELOG.md‎

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

77
## [Unreleased]
88
### Added
9+
- Added `tagHandlingVersion` to `TextResult`: the tag handling version used, parsed
10+
from the response; `undefined` when the API does not return it.
11+
- `SentenceSplittingMode` accepts `'1'` and `'0'`, the API values, which are sent
12+
unchanged.
913
- Added the `extraBodyParameters` option to `BaseRequestOptions`, and so to
1014
`TranslateTextOptions`, `DocumentTranslateOptions` and `RephraseTextOptions`.
1115
It replaces `extraRequestParameters` and behaves the same. Passing both
@@ -92,6 +96,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9296
`updateStyleRuleCustomInstruction(styleId, instructionId, label, prompt, sourceLanguage?)`.
9397

9498
### Changed
99+
- `Formality`, `SentenceSplittingMode`, `TagHandlingMode`, `TagHandlingVersion`
100+
and `ModelType` accept any string in addition to their listed values, so values
101+
the API adds later can be passed without a type cast.
95102
- Every HTTP 404 response now throws `NotFoundError`; before, only glossary
96103
requests threw an error class of their own (`GlossaryNotFoundError`, which
97104
is still thrown there) and other requests threw a plain `DeepLError`.
@@ -116,6 +123,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
116123
limit and links to the DeepL API usage limits instead.
117124

118125
### Deprecated
126+
- `SentenceSplittingMode` values `'on'` and `'default'` are deprecated. Use `'1'`
127+
instead.
119128
- `extraRequestParameters` is deprecated; use `extraBodyParameters` instead.
120129
It will be removed in the next major version.
121130
- `Translator` and `TranslatorOptions` are deprecated; use `DeepLClient` and

‎README.md‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ following properties:
139139
- `billedCharacters` is the number of characters billed for the text.
140140
- `modelTypeUsed` indicates the translation model used, but is `undefined`
141141
unless the `modelType` option is specified.
142+
- `tagHandlingVersion` is the tag handling version used (for example `'v2'`), or
143+
`undefined` unless the `tagHandling` option is specified.
142144

143145
```javascript
144146
// Translate text into a target language, in this case, French:
@@ -166,13 +168,14 @@ console.log(await deeplClient.translateText('How are you?', null, 'de', { formal
166168
#### Text translation options
167169

168170
- `splitSentences`: specify how input text should be split into sentences,
169-
default: `'on'`.
170-
- `'on'`: input text will be split into sentences using both newlines and
171+
default: `'1'`.
172+
- `'1'`: input text will be split into sentences using both newlines and
171173
punctuation.
172-
- `'off'`: input text will not be split into sentences. Use this for
174+
- `'0'` or `'off'`: input text will not be split into sentences. Use this for
173175
applications where each input text contains only one sentence.
174176
- `'nonewlines'`: input text will be split into sentences using punctuation
175177
but not newlines.
178+
- `'on'` and `'default'` are deprecated aliases of `'1'`.
176179
- `preserveFormatting`: controls automatic-formatting-correction. Set to `true`
177180
to prevent automatic-correction of formatting, default: `false`.
178181
- `formality`: controls whether translations should lean toward informal or
@@ -231,6 +234,10 @@ console.log(await deeplClient.translateText('How are you?', null, 'de', { formal
231234
- `tagHandlingVersion`: specifies which version of the tag handling algorithm to use.
232235
Options are `v1` and `v2`.
233236

237+
`formality`, `splitSentences`, `modelType`, `tagHandling` and `tagHandlingVersion`
238+
also accept any other string, which is sent to the API unchanged; this allows values
239+
the API adds later to be used without a library update.
240+
234241
The following options are only used if `tagHandling` is `'xml'`:
235242

236243
- `outlineDetection`: specify `false` to disable automatic tag detection,

‎src/parsing.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ interface TextResultApiResponse {
126126
detected_source_language: string;
127127
billed_characters: number;
128128
model_type_used?: string;
129+
tag_handling_version?: unknown;
129130
}
130131

131132
/**
@@ -623,6 +624,10 @@ export function parseUsage(json: string): Usage {
623624
}
624625
}
625626

627+
function parseOptionalString(value: unknown): string | undefined {
628+
return typeof value === 'string' ? value : undefined;
629+
}
630+
626631
/**
627632
* Parses the given JSON string to an array of TextResult objects.
628633
* @private
@@ -638,6 +643,7 @@ export function parseTextResultArray(json: string): TextResult[] {
638643
) as SourceLanguageCode,
639644
billedCharacters: translation.billed_characters,
640645
modelTypeUsed: translation.model_type_used,
646+
tagHandlingVersion: parseOptionalString(translation.tag_handling_version),
641647
};
642648
});
643649
} catch (error) {
@@ -874,10 +880,6 @@ function parseIdList(value: unknown): string[] {
874880
return Array.isArray(value) ? value.filter((id): id is string => typeof id === 'string') : [];
875881
}
876882

877-
function parseOptionalString(value: unknown): string | undefined {
878-
return typeof value === 'string' ? value : undefined;
879-
}
880-
881883
function parseRawStyleProfile(obj: StyleProfileApiResponse): StyleProfile {
882884
return {
883885
styleProfileId: obj.style_profile_id ?? '',

‎src/types.ts‎

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,36 @@ export interface DeepLClientOptions extends HttpClientOptions {
8888
// eslint-disable-next-line @typescript-eslint/no-empty-interface
8989
export interface TranslatorOptions extends DeepLClientOptions {}
9090

91-
export type Formality = 'less' | 'more' | 'default' | 'prefer_less' | 'prefer_more';
92-
export type SentenceSplittingMode = 'off' | 'on' | 'nonewlines' | 'default';
93-
export type TagHandlingMode = 'html' | 'xml';
94-
export type TagHandlingVersion = 'v1' | 'v2';
95-
export type ModelType = 'quality_optimized' | 'latency_optimized' | 'prefer_quality_optimized';
91+
// `string & Record<never, never>` accepts any string, e.g. values the API adds later, while
92+
// keeping editor completion for the listed members.
93+
export type Formality =
94+
| 'less'
95+
| 'more'
96+
| 'default'
97+
| 'prefer_less'
98+
| 'prefer_more'
99+
| (string & Record<never, never>);
100+
/**
101+
* Sentence splitting mode: '1' (split on punctuation and newlines), '0' (no splitting) or
102+
* 'nonewlines' (split on punctuation only). 'off' is sent as '0'.
103+
* 'on' and 'default' are deprecated: use '1' instead. They will be removed in the next major
104+
* version.
105+
*/
106+
export type SentenceSplittingMode =
107+
| '1'
108+
| '0'
109+
| 'off'
110+
| 'nonewlines'
111+
| 'on'
112+
| 'default'
113+
| (string & Record<never, never>);
114+
export type TagHandlingMode = 'html' | 'xml' | (string & Record<never, never>);
115+
export type TagHandlingVersion = 'v1' | 'v2' | (string & Record<never, never>);
116+
export type ModelType =
117+
| 'quality_optimized'
118+
| 'latency_optimized'
119+
| 'prefer_quality_optimized'
120+
| (string & Record<never, never>);
96121
export type GlossaryId = string;
97122
export type StyleId = string;
98123
export type TranslationMemoryId = string;
@@ -151,10 +176,10 @@ export interface GlossaryInfo {
151176
export interface TranslateTextOptions extends BaseRequestOptions {
152177
/**
153178
* Specifies how input translation text should be split into sentences.
154-
* - 'on': Input translation text will be split into sentences using both newlines and
179+
* - '1': Input translation text will be split into sentences using both newlines and
155180
* punctuation, this is the default behaviour.
156-
* - 'off': Input translation text will not be split into sentences. This is advisable for
157-
* applications where each input translation text is only one sentence.
181+
* - '0' or 'off': Input translation text will not be split into sentences. This is advisable
182+
* for applications where each input translation text is only one sentence.
158183
* - 'nonewlines': Input translation text will be split into sentences using only punctuation
159184
* but not newlines.
160185
*/
@@ -801,6 +826,11 @@ export interface TextResult {
801826
* The translation model type used, if available.
802827
*/
803828
readonly modelTypeUsed?: string;
829+
830+
/**
831+
* The tag handling version used, present when tag handling was requested.
832+
*/
833+
readonly tagHandlingVersion?: TagHandlingVersion;
804834
}
805835

806836
/**

‎tests/offline/translateText.test.ts‎

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,27 @@ describe('Translator.translateText', () => {
5656
});
5757
});
5858

59+
it('parses tag_handling_version, and leaves it undefined when it is not a string', async () => {
60+
const reply = (version: unknown) => ({
61+
translations: [
62+
{
63+
detected_source_language: 'EN',
64+
text: 'out',
65+
billed_characters: 3,
66+
tag_handling_version: version,
67+
},
68+
],
69+
});
70+
expectTranslateBody(baseBody, reply('v2'));
71+
expectTranslateBody(baseBody, reply(2));
72+
const translator = makeTranslator();
73+
74+
expect((await translator.translateText('abc', null, 'de')).tagHandlingVersion).toBe('v2');
75+
expect(
76+
(await translator.translateText('abc', null, 'de')).tagHandlingVersion,
77+
).toBeUndefined();
78+
});
79+
5980
it('sends every text of an array and parses one result per text', async () => {
6081
expectTranslateBody(
6182
{ ...baseBody, text: ['a', 'b'] },
@@ -103,6 +124,8 @@ describe('Translator.translateText', () => {
103124
['off', '0'],
104125
['nonewlines', 'nonewlines'],
105126
['OFF', '0'],
127+
['1', '1'],
128+
['0', '0'],
106129
])('sends splitSentences %s as %s', async (splitSentences, sent) => {
107130
expectTranslateBody({ ...baseBody, split_sentences: sent });
108131

@@ -113,15 +136,32 @@ describe('Translator.translateText', () => {
113136

114137
it('does not change the splitSentences value of the caller options', async () => {
115138
expectTranslateBody({ ...baseBody, split_sentences: '0' });
116-
const options: deepl.TranslateTextOptions = {
117-
splitSentences: <deepl.SentenceSplittingMode>'OFF',
118-
};
139+
const options: deepl.TranslateTextOptions = { splitSentences: 'OFF' };
119140

120141
await makeTranslator().translateText('abc', null, 'de', options);
121142

122143
expect(options.splitSentences).toBe('OFF');
123144
});
124145

146+
it('sends enum option values that are not listed in their types', async () => {
147+
expectTranslateBody({
148+
...baseBody,
149+
formality: 'future_formality',
150+
split_sentences: 'future_mode',
151+
tag_handling: 'future_markup',
152+
tag_handling_version: 'v3',
153+
model_type: 'future_model',
154+
});
155+
156+
await makeTranslator().translateText('abc', null, 'de', {
157+
formality: 'future_formality',
158+
splitSentences: 'future_mode',
159+
tagHandling: 'future_markup',
160+
tagHandlingVersion: 'v3',
161+
modelType: 'future_model',
162+
});
163+
});
164+
125165
it.each([
126166
[true, '1'],
127167
[false, '0'],

0 commit comments

Comments
 (0)