Skip to content

Commit bad2dcd

Browse files
hive-botclaude
andcommitted
feat: align the style profile options with the other request options
Rename StyleProfileOptions to CreateStyleProfileOptions and StyleProfileUpdateOptions to UpdateStyleProfileOptions. Both accept extraBodyParameters, merged into the JSON body last, and take the linked glossaries, style rule lists and translation memories as IDs or objects. The options of updateStyleProfile() are optional; without them it sends {}. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 39b8043 commit bad2dcd

5 files changed

Lines changed: 153 additions & 31 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4141
- Added style profiles (`/v3/style_profiles`) to `DeepLClient`:
4242
`listStyleProfiles()`, `createStyleProfile()`, `getStyleProfile()`,
4343
`updateStyleProfile()` and `deleteStyleProfile()`. Added the `StyleProfile`,
44-
`StyleProfileOptions` and `StyleProfileUpdateOptions` types.
44+
`CreateStyleProfileOptions` and `UpdateStyleProfileOptions` types. The
45+
options accept `extraBodyParameters`, and the linked glossaries, style rule
46+
lists and translation memories as IDs or objects; the options of
47+
`updateStyleProfile()` are optional.
4548
- Added spoken terms collections (`/v3/spoken-terms`) to `DeepLClient`:
4649
`createSpokenTerms()`, `listSpokenTerms()`, `getSpokenTerms()`,
4750
`updateSpokenTerms()`, `getSpokenTermsEntries()`, `replaceSpokenTermsList()`,

‎README.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,19 @@ A style profile is a named set of linked glossaries, style rule lists and transl
747747
memories, with an optional description and industry. Every method that takes a style
748748
profile accepts its ID or a `StyleProfile` object.
749749

750+
`createStyleProfile()` takes `CreateStyleProfileOptions` and `updateStyleProfile()`
751+
takes `UpdateStyleProfileOptions` (optional; the same options plus `name`):
752+
753+
- `description`, `industry`: free-text description and industry.
754+
- `glossaryIds`, `styleRuleIds`, `translationMemoryIds`: the components to link, each
755+
given as its ID or as the object returned by the SDK (`GlossaryInfo` or
756+
`MultilingualGlossaryInfo`, `StyleRuleInfo`, `TranslationMemoryInfo`). Glossaries
757+
are listed highest priority first.
758+
- `glossaryPriorityOrderEnabled`: whether the order of `glossaryIds` sets the
759+
priority of the glossaries.
760+
- `extraBodyParameters`: extra fields added to the JSON request body, replacing
761+
fields with the same key.
762+
750763
Options left `undefined` are not sent. When updating, they are not changed; an empty
751764
string `''` clears `description` or `industry`, and an ID array replaces the linked
752765
components of that kind in full, so an empty array `[]` unlinks all of them.
@@ -759,7 +772,7 @@ const minimal = await deeplClient.createStyleProfile('My profile');
759772
const styleProfile = await deeplClient.createStyleProfile('Legal, German', {
760773
description: 'Contract wording for the German market.',
761774
industry: 'Legal',
762-
glossaryIds: ['YOUR_GLOSSARY_ID'],
775+
glossaryIds: ['YOUR_GLOSSARY_ID', myGlossaryInfo],
763776
styleRuleIds: ['YOUR_STYLE_ID'],
764777
translationMemoryIds: ['YOUR_TRANSLATION_MEMORY_ID'],
765778
glossaryPriorityOrderEnabled: true,

‎src/deeplClient.ts‎

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { Translator, checkStatusCode } from './translator';
66
import {
7+
BaseRequestOptions,
78
DeepLClientOptions,
89
WriteResult,
910
MultilingualGlossaryDictionaryEntries,
@@ -17,8 +18,8 @@ import {
1718
SpokenTermsList,
1819
UpdateSpokenTermsOptions,
1920
StyleProfile,
20-
StyleProfileOptions,
21-
StyleProfileUpdateOptions,
21+
CreateStyleProfileOptions,
22+
UpdateStyleProfileOptions,
2223
StyleRuleInfo,
2324
StyleId,
2425
CustomInstruction,
@@ -70,6 +71,7 @@ import {
7071
extractSpokenTermsId,
7172
extractStyleProfileId,
7273
extractTranslationMemoryId,
74+
isString,
7375
logInfo,
7476
resolveExtraBodyParameters,
7577
setExtraBodyParameters,
@@ -92,25 +94,31 @@ function spokenTermsEntriesToJson(entries: SpokenTermsEntries): Record<string, u
9294

9395
function addExtraBodyParametersToJson(
9496
body: Record<string, unknown>,
95-
options: CreateSpokenTermsOptions | undefined,
97+
options: BaseRequestOptions | undefined,
9698
): Record<string, unknown> {
9799
// Added last, so they replace library-set fields with the same key
98100
const extraBodyParameters = resolveExtraBodyParameters(options);
99101
return extraBodyParameters === undefined ? body : { ...body, ...extraBodyParameters };
100102
}
101103

102104
function styleProfileOptionsToJson(
103-
options: StyleProfileOptions | undefined,
105+
options: CreateStyleProfileOptions | undefined,
104106
): Record<string, unknown> {
105107
const body: Record<string, unknown> = {};
106108
if (options === undefined) return body;
107109
// Unset fields are not sent; '' and [] are sent, as they clear or unlink in an update
108110
if (options.description !== undefined) body.description = options.description;
109111
if (options.industry !== undefined) body.industry = options.industry;
110-
if (options.glossaryIds !== undefined) body.glossary_ids = options.glossaryIds;
111-
if (options.styleRuleIds !== undefined) body.style_rule_ids = options.styleRuleIds;
112+
if (options.glossaryIds !== undefined) {
113+
body.glossary_ids = options.glossaryIds.map(extractGlossaryId);
114+
}
115+
if (options.styleRuleIds !== undefined) {
116+
body.style_rule_ids = options.styleRuleIds.map((styleRule) =>
117+
isString(styleRule) ? styleRule : styleRule.styleId,
118+
);
119+
}
112120
if (options.translationMemoryIds !== undefined) {
113-
body.translation_memory_ids = options.translationMemoryIds;
121+
body.translation_memory_ids = options.translationMemoryIds.map(extractTranslationMemoryId);
114122
}
115123
if (options.glossaryPriorityOrderEnabled !== undefined) {
116124
body.glossary_priority_order_enabled = options.glossaryPriorityOrderEnabled;
@@ -1825,13 +1833,19 @@ export class DeepLClient extends Translator {
18251833
*
18261834
* @param name: Name of the style profile.
18271835
* @param options: Description, industry and the IDs of the glossaries, style rule lists and
1828-
* translation memories to link, see {@link StyleProfileOptions}.
1836+
* translation memories to link, see {@link CreateStyleProfileOptions}.
18291837
* @returns {Promise<StyleProfile>} The created style profile.
18301838
*
18311839
* @throws {DeepLError} If any error occurs while communicating with the DeepL API.
18321840
*/
1833-
async createStyleProfile(name: string, options?: StyleProfileOptions): Promise<StyleProfile> {
1834-
const body = { name, ...styleProfileOptionsToJson(options) };
1841+
async createStyleProfile(
1842+
name: string,
1843+
options?: CreateStyleProfileOptions,
1844+
): Promise<StyleProfile> {
1845+
const body = addExtraBodyParametersToJson(
1846+
{ name, ...styleProfileOptionsToJson(options) },
1847+
options,
1848+
);
18351849
const { statusCode, content } = await this.httpClient.sendRequestWithBackoff<string>(
18361850
'POST',
18371851
'/v3/style_profiles',
@@ -1862,23 +1876,25 @@ export class DeepLClient extends Translator {
18621876
/**
18631877
* Updates a style profile. Options left undefined are not changed. An empty string clears
18641878
* the description or industry; an ID array replaces the linked components of that kind in
1865-
* full, and an empty array unlinks all of them. With no options set, nothing changes.
1879+
* full, and an empty array unlinks all of them. With no options set, `{}` is sent and nothing
1880+
* changes.
18661881
*
18671882
* @param styleProfile: ID of the style profile, or a StyleProfile object.
1868-
* @param options: The fields to change, see {@link StyleProfileUpdateOptions}.
1883+
* @param options: The fields to change (optional), see {@link UpdateStyleProfileOptions}.
18691884
* @returns {Promise<StyleProfile>} The updated style profile.
18701885
*
18711886
* @throws {DeepLError} If any error occurs while communicating with the DeepL API.
18721887
*/
18731888
async updateStyleProfile(
18741889
styleProfile: string | StyleProfile,
1875-
options: StyleProfileUpdateOptions,
1890+
options?: UpdateStyleProfileOptions,
18761891
): Promise<StyleProfile> {
18771892
const styleProfileId = extractStyleProfileId(styleProfile);
1878-
const body = styleProfileOptionsToJson(options);
1879-
if (options.name !== undefined) {
1880-
body.name = options.name;
1893+
const fields = styleProfileOptionsToJson(options);
1894+
if (options?.name !== undefined) {
1895+
fields.name = options.name;
18811896
}
1897+
const body = addExtraBodyParametersToJson(fields, options);
18821898
const { statusCode, content } = await this.httpClient.sendRequestWithBackoff<string>(
18831899
'PATCH',
18841900
`/v3/style_profiles/${encodeURIComponent(styleProfileId)}`,

‎src/types.ts‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,34 +1073,35 @@ export interface StyleProfile {
10731073
* Options for createStyleProfile(). An option left undefined is not sent. An empty string or
10741074
* empty array is sent as given.
10751075
*/
1076-
export interface StyleProfileOptions {
1076+
export interface CreateStyleProfileOptions extends BaseRequestOptions {
10771077
/** Free-text description of the style profile. In an update, '' clears it. */
10781078
description?: string;
10791079
/** The industry the style profile is written for. In an update, '' clears it. */
10801080
industry?: string;
10811081
/**
1082-
* IDs of the glossaries to link, highest priority first. In an update, the array replaces the
1083-
* linked glossaries in full; [] unlinks all of them.
1082+
* Glossaries to link, as IDs or GlossaryInfo / MultilingualGlossaryInfo objects, highest
1083+
* priority first. In an update, the array replaces the linked glossaries in full; [] unlinks
1084+
* all of them.
10841085
*/
1085-
glossaryIds?: string[];
1086+
glossaryIds?: (GlossaryId | GlossaryInfo | MultilingualGlossaryInfo)[];
10861087
/**
1087-
* IDs of the style rule lists to link. In an update, the array replaces the linked style rule
1088-
* lists in full; [] unlinks all of them.
1088+
* Style rule lists to link, as IDs or StyleRuleInfo objects. In an update, the array replaces
1089+
* the linked style rule lists in full; [] unlinks all of them.
10891090
*/
1090-
styleRuleIds?: string[];
1091+
styleRuleIds?: (StyleId | StyleRuleInfo)[];
10911092
/**
1092-
* IDs of the translation memories to link. In an update, the array replaces the linked
1093-
* translation memories in full; [] unlinks all of them.
1093+
* Translation memories to link, as IDs or TranslationMemoryInfo objects. In an update, the
1094+
* array replaces the linked translation memories in full; [] unlinks all of them.
10941095
*/
1095-
translationMemoryIds?: string[];
1096+
translationMemoryIds?: (TranslationMemoryId | TranslationMemoryInfo)[];
10961097
/** Whether the order of glossaryIds sets the priority of the glossaries. */
10971098
glossaryPriorityOrderEnabled?: boolean;
10981099
}
10991100

11001101
/**
11011102
* Options for updateStyleProfile(). Options left undefined are not changed.
11021103
*/
1103-
export interface StyleProfileUpdateOptions extends StyleProfileOptions {
1104+
export interface UpdateStyleProfileOptions extends CreateStyleProfileOptions {
11041105
/** New name of the style profile. */
11051106
name?: string;
11061107
}

‎tests/offline/styleProfiles.test.ts‎

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,70 @@ describe('DeepLClient.createStyleProfile', () => {
127127
},
128128
]);
129129
});
130+
131+
it('sends the IDs of linked objects given instead of IDs', async () => {
132+
const bodies: unknown[] = [];
133+
nock(serverUrl).post('/v3/style_profiles', capture(bodies)).reply(201, profileReply);
134+
const created = new Date('2026-01-01T00:00:00Z');
135+
const glossary: deepl.GlossaryInfo = {
136+
glossaryId: 'g-1',
137+
name: 'v2',
138+
ready: true,
139+
sourceLang: 'en',
140+
targetLang: 'de',
141+
creationTime: created,
142+
entryCount: 1,
143+
};
144+
const multilingualGlossary: deepl.MultilingualGlossaryInfo = {
145+
glossaryId: 'g-2',
146+
name: 'v3',
147+
dictionaries: [],
148+
creationTime: created,
149+
};
150+
const styleRule: deepl.StyleRuleInfo = {
151+
styleId: 's-1',
152+
name: 'rules',
153+
creationTime: created,
154+
updatedTime: created,
155+
language: 'de',
156+
version: 1,
157+
};
158+
const translationMemory: deepl.TranslationMemoryInfo = {
159+
translationMemoryId: 't-1',
160+
name: 'tm',
161+
sourceLanguage: 'en',
162+
targetLanguages: ['de'],
163+
segmentCount: 0,
164+
};
165+
const options: deepl.CreateStyleProfileOptions = {
166+
glossaryIds: [multilingualGlossary, 'g-3', glossary],
167+
styleRuleIds: [styleRule, 's-2'],
168+
translationMemoryIds: ['t-2', translationMemory],
169+
};
170+
171+
await makeDeeplClient().createStyleProfile('Refs', options);
172+
173+
expect(bodies).toStrictEqual([
174+
{
175+
name: 'Refs',
176+
glossary_ids: ['g-2', 'g-3', 'g-1'],
177+
style_rule_ids: ['s-1', 's-2'],
178+
translation_memory_ids: ['t-2', 't-1'],
179+
},
180+
]);
181+
});
182+
183+
it('merges extraBodyParameters into the JSON body last', async () => {
184+
const bodies: unknown[] = [];
185+
nock(serverUrl).post('/v3/style_profiles', capture(bodies)).reply(201, profileReply);
186+
187+
await makeDeeplClient().createStyleProfile('Legal', {
188+
industry: 'Legal',
189+
extraBodyParameters: { industry: 'Finance', beta_flag: 'on' },
190+
});
191+
192+
expect(bodies).toStrictEqual([{ name: 'Legal', industry: 'Finance', beta_flag: 'on' }]);
193+
});
130194
});
131195

132196
describe('DeepLClient.getStyleProfile', () => {
@@ -199,12 +263,13 @@ describe('DeepLClient.updateStyleProfile', () => {
199263
.patch(`/v3/style_profiles/${profileId}`, capture(bodies))
200264
.reply(200, profileReply);
201265

202-
await makeDeeplClient().updateStyleProfile(profileId, {
266+
const options: deepl.UpdateStyleProfileOptions = {
203267
name: 'Renamed',
204268
description: '',
205269
styleRuleIds: [],
206270
glossaryPriorityOrderEnabled: false,
207-
});
271+
};
272+
await makeDeeplClient().updateStyleProfile(profileId, options);
208273

209274
expect(bodies).toStrictEqual([
210275
{
@@ -231,6 +296,30 @@ describe('DeepLClient.updateStyleProfile', () => {
231296

232297
expect(bodies).toStrictEqual([{}, {}]);
233298
});
299+
300+
it('sends an empty object when options are omitted', async () => {
301+
const bodies: unknown[] = [];
302+
nock(serverUrl)
303+
.patch(`/v3/style_profiles/${profileId}`, capture(bodies))
304+
.reply(200, profileReply);
305+
306+
expect(await makeDeeplClient().updateStyleProfile(profileId)).toStrictEqual(profile);
307+
expect(bodies).toStrictEqual([{}]);
308+
});
309+
310+
it('merges extraBodyParameters into the JSON body last', async () => {
311+
const bodies: unknown[] = [];
312+
nock(serverUrl)
313+
.patch(`/v3/style_profiles/${profileId}`, capture(bodies))
314+
.reply(200, profileReply);
315+
316+
await makeDeeplClient().updateStyleProfile(profile, {
317+
name: 'Renamed',
318+
extraBodyParameters: { name: 'Override', beta_flag: 'on' },
319+
});
320+
321+
expect(bodies).toStrictEqual([{ name: 'Override', beta_flag: 'on' }]);
322+
});
234323
});
235324

236325
describe('DeepLClient.deleteStyleProfile', () => {

0 commit comments

Comments
 (0)