Skip to content

Commit 5992499

Browse files
feat: Add support for style_id and get all style rules endpoint
1 parent b99c077 commit 5992499

File tree

6 files changed

+321
-0
lines changed

6 files changed

+321
-0
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ console.log(await deeplClient.translateText('How are you?', null, 'de', { formal
175175
- `glossary`: specifies a glossary to use with translation, either as a string
176176
containing the glossary ID, or a `MultilingualGlossaryInfo`/`GlossaryInfo` as returned by
177177
`getMultilingualGlossary()`/`getGlossary()`.
178+
- `styleRule`: specifies a style rule to use with translation, either as a string
179+
containing the style rule ID, or a `StyleRuleInfo` as returned by `getAllStyleRules()`.
178180
- `context`: specifies additional context to influence translations, that is not
179181
translated itself. Characters in the `context` parameter are not counted toward billing.
180182
See the [API documentation][api-docs-context-param] for more information and
@@ -261,6 +263,7 @@ directly:
261263

262264
- `formality`: same as in [Text translation options](#text-translation-options).
263265
- `glossary`: same as in [Text translation options](#text-translation-options).
266+
- `styleRule`: same as in [Text translation options](#text-translation-options).
264267
- `filename`: if the input file is not provided as file path, this option is
265268
needed to specify the file extension.
266269
- `extraRequestParameters`: same as in [Text translation options](#text-translation-options).
@@ -482,6 +485,42 @@ console.log(entriesObj.entries.toTsv()); // {'artist': 'Maler', 'hello': 'hallo'
482485

483486
For examples for the other methods please see [this migration guide](upgrade_to_multilingual_glossaries.md)
484487

488+
### Style Rules
489+
490+
Style rules allow you to customize your translations using a managed, shared list
491+
of rules for style, formatting, and more. Multiple style rules can be stored with
492+
your account, each with a user-specified name and a uniquely-assigned ID.
493+
494+
#### Creating and managing style rules
495+
496+
Currently style rules must be created and managed in the DeepL UI via
497+
https://www.deepl.com/en/custom-rules. Full CRUD functionality via the APIs will
498+
come shortly.
499+
500+
#### Listing all style rules
501+
502+
`getAllStyleRules()` returns a list of `StyleRuleInfo` objects
503+
corresponding to all of your stored style rules. The method accepts optional
504+
parameters: `page` (page number for pagination, 0-indexed), `pageSize` (number
505+
of items per page), and `detailed` (whether to include detailed configuration
506+
rules in the `configuredRules` property).
507+
508+
```javascript
509+
// Get all style rules
510+
const styleRules = await deeplClient.getAllStyleRules();
511+
for (const rule of styleRules) {
512+
console.log(`${rule.name} (${rule.styleId})`);
513+
}
514+
515+
// Get style rules with detailed configuration
516+
const styleRules = await deeplClient.getAllStyleRules({ detailed: true });
517+
for (const rule of styleRules) {
518+
if (rule.configuredRules) {
519+
console.log(` Number formatting: ${rule.configuredRules.numbers}`);
520+
}
521+
}
522+
```
523+
485524
### Checking account usage
486525

487526
To check account usage, use the `getUsage()` function.

src/deeplClient.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import {
1313
MultilingualGlossaryDictionaryEntriesApiResponse,
1414
GlossaryId,
1515
ListMultilingualGlossaryApiResponse,
16+
StyleRuleInfo,
1617
} from './types';
1718
import {
1819
parseMultilingualGlossaryDictionaryInfo,
1920
parseMultilingualGlossaryInfo,
2021
parseWriteResultArray,
2122
parseMultilingualGlossaryDictionaryEntries,
2223
parseListMultilingualGlossaries,
24+
parseStyleRuleInfoList,
2325
} from './parsing';
2426
import {
2527
appendCsvDictionaryEntries,
@@ -542,4 +544,40 @@ export class DeepLClient extends Translator {
542544
await checkStatusCode(statusCode, content, true /* usingGlossary */);
543545
return parseMultilingualGlossaryInfo(content);
544546
}
547+
548+
/**
549+
* Retrieves a list of all style rules available for the authenticated user.
550+
*
551+
* @param page: Page number for pagination, 0-indexed (optional).
552+
* @param pageSize: Number of items per page (optional).
553+
* @param detailed: Whether to include detailed configuration rules in the `configuredRules` property (optional).
554+
* @returns {Promise<StyleRuleInfo[]>} An array of objects containing details about each style rule.
555+
*
556+
* @throws {DeepLError} If any error occurs while communicating with the DeepL API.
557+
*/
558+
async getAllStyleRules(
559+
page?: number,
560+
pageSize?: number,
561+
detailed?: boolean,
562+
): Promise<StyleRuleInfo[]> {
563+
const queryParams = new URLSearchParams();
564+
if (page !== undefined) {
565+
queryParams.append('page', String(page));
566+
}
567+
if (pageSize !== undefined) {
568+
queryParams.append('page_size', String(pageSize));
569+
}
570+
if (detailed !== undefined) {
571+
queryParams.append('detailed', String(detailed).toLowerCase());
572+
}
573+
574+
const { statusCode, content } = await this.httpClient.sendRequestWithBackoff<string>(
575+
'GET',
576+
'/v3/style_rules',
577+
{ data: queryParams },
578+
);
579+
580+
await checkStatusCode(statusCode, content);
581+
return parseStyleRuleInfoList(content);
582+
}
545583
}

src/parsing.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import {
2424
MultilingualGlossaryDictionaryApiResponse,
2525
MultilingualGlossaryDictionaryEntriesApiResponse,
2626
ListMultilingualGlossaryApiResponse,
27+
StyleRuleInfo,
28+
StyleRuleInfoApiResponse,
29+
ListStyleRuleApiResponse,
30+
ConfiguredRules,
31+
CustomInstruction,
2732
} from './types';
2833
import { standardizeLanguageCode } from './utils';
2934

@@ -527,3 +532,80 @@ export function parseDocumentHandle(json: string): DocumentHandle {
527532
throw new DeepLError(`Error parsing response JSON: ${error}`);
528533
}
529534
}
535+
536+
/**
537+
* Parses the given style rule API response to a ConfiguredRules object.
538+
* @private
539+
*/
540+
function parseConfiguredRules(
541+
configuredRulesData?: StyleRuleInfoApiResponse['configured_rules'],
542+
): ConfiguredRules | undefined {
543+
if (!configuredRulesData) {
544+
return undefined;
545+
}
546+
return {
547+
datesAndTimes: configuredRulesData.dates_and_times,
548+
formatting: configuredRulesData.formatting,
549+
numbers: configuredRulesData.numbers,
550+
punctuation: configuredRulesData.punctuation,
551+
spellingAndGrammar: configuredRulesData.spelling_and_grammar,
552+
styleAndTone: configuredRulesData.style_and_tone,
553+
vocabulary: configuredRulesData.vocabulary,
554+
};
555+
}
556+
557+
/**
558+
* Parses the given custom instruction API response to a CustomInstruction object.
559+
* @private
560+
*/
561+
function parseCustomInstruction(instruction: {
562+
label: string;
563+
prompt: string;
564+
source_language?: string;
565+
}): CustomInstruction {
566+
return {
567+
label: instruction.label,
568+
prompt: instruction.prompt,
569+
sourceLanguage: instruction.source_language,
570+
};
571+
}
572+
573+
/**
574+
* Parses the given style rule API response to a StyleRuleInfo object.
575+
* @private
576+
*/
577+
function parseStyleRuleInfo(styleRule: StyleRuleInfoApiResponse): StyleRuleInfo {
578+
try {
579+
const customInstructions = styleRule.custom_instructions
580+
? styleRule.custom_instructions.map(parseCustomInstruction)
581+
: undefined;
582+
583+
return {
584+
styleId: styleRule.style_id,
585+
name: styleRule.name,
586+
creationTime: new Date(styleRule.creation_time),
587+
updatedTime: new Date(styleRule.updated_time),
588+
language: styleRule.language,
589+
version: styleRule.version,
590+
configuredRules: parseConfiguredRules(styleRule.configured_rules),
591+
customInstructions,
592+
};
593+
} catch (error) {
594+
throw new DeepLError(`Error parsing response JSON: ${error}`);
595+
}
596+
}
597+
598+
/**
599+
* Parses the given JSON string to an array of StyleRuleInfo objects.
600+
* @private
601+
*/
602+
export function parseStyleRuleInfoList(json: string): StyleRuleInfo[] {
603+
try {
604+
const obj = JSON.parse(json) as ListStyleRuleApiResponse;
605+
return obj.style_rules.map((styleRule: StyleRuleInfoApiResponse) =>
606+
parseStyleRuleInfo(styleRule),
607+
);
608+
} catch (error) {
609+
throw new DeepLError(`Error parsing response JSON: ${error}`);
610+
}
611+
}

src/types.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export type SentenceSplittingMode = 'off' | 'on' | 'nonewlines' | 'default';
8686
export type TagHandlingMode = 'html' | 'xml';
8787
export type ModelType = 'quality_optimized' | 'latency_optimized' | 'prefer_quality_optimized';
8888
export type GlossaryId = string;
89+
export type StyleId = string;
8990
export type TagList = string | string[];
9091

9192
/**
@@ -154,6 +155,11 @@ export interface TranslateTextOptions extends BaseRequestOptions {
154155
*/
155156
glossary?: GlossaryId | GlossaryInfo | MultilingualGlossaryInfo;
156157

158+
/** Specifies the ID of a style rule to use with translation, or
159+
* a StyleRuleInfo object as returned by getAllStyleRules().
160+
*/
161+
styleRule?: StyleId | StyleRuleInfo;
162+
157163
/** Type of tags to parse before translation, options are 'html' and 'xml'. */
158164
tagHandling?: TagHandlingMode;
159165

@@ -194,6 +200,11 @@ export interface DocumentTranslateOptions extends BaseRequestOptions {
194200
*/
195201
glossary?: GlossaryId | GlossaryInfo | MultilingualGlossaryInfo;
196202

203+
/** Specifies the ID of a style rule to use with translation, or
204+
* a StyleRuleInfo object as returned by getAllStyleRules().
205+
*/
206+
styleRule?: StyleId | StyleRuleInfo;
207+
197208
/** Filename including extension, only required when translating documents as streams. */
198209
filename?: string;
199210

@@ -569,3 +580,92 @@ export interface ListMultilingualGlossaryApiResponse {
569580
},
570581
];
571582
}
583+
584+
/**
585+
* Custom instruction for a style rule.
586+
*/
587+
export interface CustomInstruction {
588+
/** Label for the custom instruction. */
589+
readonly label: string;
590+
/** Prompt text for the custom instruction. */
591+
readonly prompt: string;
592+
/** Optional source language code for the custom instruction. */
593+
readonly sourceLanguage?: string;
594+
}
595+
596+
/**
597+
* Configuration rules for a style rule list.
598+
*/
599+
export interface ConfiguredRules {
600+
/** Date and time formatting rules. */
601+
readonly datesAndTimes?: Record<string, string>;
602+
/** Text formatting rules. */
603+
readonly formatting?: Record<string, string>;
604+
/** Number formatting rules. */
605+
readonly numbers?: Record<string, string>;
606+
/** Punctuation rules. */
607+
readonly punctuation?: Record<string, string>;
608+
/** Spelling and grammar rules. */
609+
readonly spellingAndGrammar?: Record<string, string>;
610+
/** Style and tone rules. */
611+
readonly styleAndTone?: Record<string, string>;
612+
/** Vocabulary rules. */
613+
readonly vocabulary?: Record<string, string>;
614+
}
615+
616+
/**
617+
* Information about a style rule list.
618+
*/
619+
export interface StyleRuleInfo {
620+
/** Unique ID assigned to the style rule list. */
621+
readonly styleId: StyleId;
622+
/** User-defined name assigned to the style rule list. */
623+
readonly name: string;
624+
/** Time when the style rule list was created. */
625+
readonly creationTime: Date;
626+
/** Time when the style rule list was last updated. */
627+
readonly updatedTime: Date;
628+
/** Language code for the style rule list. */
629+
readonly language: string;
630+
/** Version number of the style rule list. */
631+
readonly version: number;
632+
/** The predefined rules that have been enabled. */
633+
readonly configuredRules?: ConfiguredRules;
634+
/** Optional list of custom instructions. */
635+
readonly customInstructions?: CustomInstruction[];
636+
}
637+
638+
/**
639+
* Type used during JSON parsing of API response for style rule info.
640+
* @private
641+
*/
642+
export interface StyleRuleInfoApiResponse {
643+
style_id: string;
644+
name: string;
645+
creation_time: string;
646+
updated_time: string;
647+
language: string;
648+
version: number;
649+
configured_rules?: {
650+
dates_and_times?: Record<string, string>;
651+
formatting?: Record<string, string>;
652+
numbers?: Record<string, string>;
653+
punctuation?: Record<string, string>;
654+
spelling_and_grammar?: Record<string, string>;
655+
style_and_tone?: Record<string, string>;
656+
vocabulary?: Record<string, string>;
657+
};
658+
custom_instructions?: Array<{
659+
label: string;
660+
prompt: string;
661+
source_language?: string;
662+
}>;
663+
}
664+
665+
/**
666+
* Type used during JSON parsing of API response for listing style rules.
667+
* @private
668+
*/
669+
export interface ListStyleRuleApiResponse {
670+
style_rules: StyleRuleInfoApiResponse[];
671+
}

src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
NonRegionalLanguageCode,
1313
RequestParameters,
1414
SentenceSplittingMode,
15+
StyleId,
1516
TagList,
1617
TranslateTextOptions,
1718
MultilingualGlossaryInfo,
1819
MultilingualGlossaryDictionaryEntries,
20+
StyleRuleInfo,
1921
} from './types';
2022

2123
const logger = loglevel.getLogger('deepl');
@@ -273,6 +275,16 @@ export function validateAndAppendTextOptions(
273275
if (options.ignoreTags !== undefined) {
274276
data.append('ignore_tags', joinTagList(options.ignoreTags));
275277
}
278+
if (options.styleRule !== undefined) {
279+
if (!isString(options.styleRule)) {
280+
if (options.styleRule.styleId === undefined) {
281+
throw new DeepLError(
282+
'styleRule option should be a StyleId (string) containing the Style Rule ID or a StyleRuleInfo object.',
283+
);
284+
}
285+
data.append('style_id', options.styleRule.styleId);
286+
}
287+
}
276288
}
277289

278290
/**

0 commit comments

Comments
 (0)