Skip to content

Commit 06e87b0

Browse files
feat: Add support for style_id and get all style rules endpoint
1 parent fd3323c commit 06e87b0

File tree

7 files changed

+345
-5
lines changed

7 files changed

+345
-5
lines changed

DeepL/DeepLClient.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Task<WriteResult> RephraseTextAsync(
5353
/// Client for the DeepL API. To use the DeepL API, initialize an instance of this class using your DeepL
5454
/// Authentication Key. All functions are thread-safe, aside from <see cref="DeepLClient.Dispose" />.
5555
/// </summary>
56-
public sealed class DeepLClient : Translator, IWriter, IGlossaryManager {
56+
public sealed class DeepLClient : Translator, IWriter, IGlossaryManager, IStyleRuleManager {
5757
/// <summary>Initializes a new instance of the <see cref="AuthorizationException" /> class.</summary>
5858
/// <param name="message">The message that describes the error.</param>
5959
public DeepLClient(string authKey, DeepLClientOptions? options = null) : base(authKey, options) { }
@@ -104,6 +104,35 @@ public async Task<WriteResult[]> RephraseTextAsync(
104104
return rephrasedTexts.Improvements;
105105
}
106106

107+
/// <inheritdoc />
108+
public async Task<StyleRuleInfo[]> GetAllStyleRulesAsync(
109+
int? page = null,
110+
int? pageSize = null,
111+
bool? detailed = null,
112+
CancellationToken cancellationToken = default) {
113+
var queryParams = new List<(string Key, string Value)>();
114+
115+
if (page != null) {
116+
queryParams.Add(("page", page.Value.ToString()));
117+
}
118+
119+
if (pageSize != null) {
120+
queryParams.Add(("page_size", pageSize.Value.ToString()));
121+
}
122+
123+
if (detailed != null) {
124+
queryParams.Add(("detailed", detailed.Value.ToString().ToLower()));
125+
}
126+
127+
using var responseMessage = await _client
128+
.ApiGetAsync("/v3/style_rules", cancellationToken, queryParams.ToArray()).ConfigureAwait(false);
129+
130+
await DeepLHttpClient.CheckStatusCodeAsync(responseMessage).ConfigureAwait(false);
131+
var styleRuleList = await JsonUtils.DeserializeAsync<StyleRuleListResult>(responseMessage)
132+
.ConfigureAwait(false);
133+
return styleRuleList.StyleRules;
134+
}
135+
107136
/// <inheritdoc />
108137
public async Task<MultilingualGlossaryInfo> CreateMultilingualGlossaryAsync(
109138
string name,
@@ -694,5 +723,18 @@ private static (string Key, string Value)[] CreateLanguageQueryParams(
694723

695724
return bodyParams;
696725
}
726+
727+
/// <summary>Class used for JSON-deserialization of style rule list results.</summary>
728+
private readonly struct StyleRuleListResult {
729+
/// <summary>Initializes a new instance of <see cref="StyleRuleListResult" />, used for JSON deserialization.</summary>
730+
[JsonConstructor]
731+
public StyleRuleListResult(StyleRuleInfo[] styleRules) {
732+
StyleRules = styleRules;
733+
}
734+
735+
/// <summary>Array of <see cref="StyleRuleInfo" /> objects holding style rule information.</summary>
736+
[JsonPropertyName("style_rules")]
737+
public StyleRuleInfo[] StyleRules { get; }
738+
}
697739
}
698740
}

DeepL/IStyleRuleManager.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2025 DeepL SE (https://www.deepl.com)
2+
// Use of this source code is governed by an MIT
3+
// license that can be found in the LICENSE file.
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using DeepL.Model;
7+
8+
namespace DeepL {
9+
public interface IStyleRuleManager {
10+
/// <summary>Retrieves the list of all available style rules.</summary>
11+
/// <param name="page">Optional page number for pagination, 0-indexed.</param>
12+
/// <param name="pageSize">Optional number of items per page.</param>
13+
/// <param name="detailed">
14+
/// Optional flag indicating whether to include detailed configuration rules in the configuredRules property.
15+
/// </param>
16+
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
17+
/// <returns>Array of <see cref="StyleRuleInfo" /> objects representing the available style rules.</returns>
18+
/// <exception cref="DeepLException">
19+
/// If any error occurs while communicating with the DeepL API, a
20+
/// <see cref="DeepLException" /> or a derived class will be thrown.
21+
/// </exception>
22+
Task<StyleRuleInfo[]> GetAllStyleRulesAsync(
23+
int? page = null,
24+
int? pageSize = null,
25+
bool? detailed = null,
26+
CancellationToken cancellationToken = default);
27+
}
28+
}

DeepL/Model/StyleRuleInfo.cs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright 2025 DeepL SE (https://www.deepl.com)
2+
// Use of this source code is governed by an MIT
3+
// license that can be found in the LICENSE file.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Text.Json.Serialization;
8+
9+
namespace DeepL.Model {
10+
/// <summary>Configuration rules for a style rule list.</summary>
11+
public sealed class ConfiguredRules {
12+
/// <summary>Initializes a new instance of <see cref="ConfiguredRules" />.</summary>
13+
[JsonConstructor]
14+
public ConfiguredRules(
15+
Dictionary<string, string>? datesAndTimes,
16+
Dictionary<string, string>? formatting,
17+
Dictionary<string, string>? numbers,
18+
Dictionary<string, string>? punctuation,
19+
Dictionary<string, string>? spellingAndGrammar,
20+
Dictionary<string, string>? styleAndTone,
21+
Dictionary<string, string>? vocabulary) {
22+
DatesAndTimes = datesAndTimes;
23+
Formatting = formatting;
24+
Numbers = numbers;
25+
Punctuation = punctuation;
26+
SpellingAndGrammar = spellingAndGrammar;
27+
StyleAndTone = styleAndTone;
28+
Vocabulary = vocabulary;
29+
}
30+
31+
/// <summary>Date and time formatting rules.</summary>
32+
[JsonPropertyName("dates_and_times")]
33+
public Dictionary<string, string>? DatesAndTimes { get; }
34+
35+
/// <summary>Text formatting rules.</summary>
36+
[JsonPropertyName("formatting")]
37+
public Dictionary<string, string>? Formatting { get; }
38+
39+
/// <summary>Number formatting rules.</summary>
40+
[JsonPropertyName("numbers")]
41+
public Dictionary<string, string>? Numbers { get; }
42+
43+
/// <summary>Punctuation rules.</summary>
44+
[JsonPropertyName("punctuation")]
45+
public Dictionary<string, string>? Punctuation { get; }
46+
47+
/// <summary>Spelling and grammar rules.</summary>
48+
[JsonPropertyName("spelling_and_grammar")]
49+
public Dictionary<string, string>? SpellingAndGrammar { get; }
50+
51+
/// <summary>Style and tone rules.</summary>
52+
[JsonPropertyName("style_and_tone")]
53+
public Dictionary<string, string>? StyleAndTone { get; }
54+
55+
/// <summary>Vocabulary rules.</summary>
56+
[JsonPropertyName("vocabulary")]
57+
public Dictionary<string, string>? Vocabulary { get; }
58+
}
59+
60+
/// <summary>Custom instruction for a style rule.</summary>
61+
public sealed class CustomInstruction {
62+
/// <summary>Initializes a new instance of <see cref="CustomInstruction" />.</summary>
63+
[JsonConstructor]
64+
public CustomInstruction(string label, string prompt, string? sourceLanguage) {
65+
Label = label;
66+
Prompt = prompt;
67+
SourceLanguage = sourceLanguage;
68+
}
69+
70+
/// <summary>Label for the custom instruction.</summary>
71+
[JsonPropertyName("label")]
72+
public string Label { get; }
73+
74+
/// <summary>Prompt text for the custom instruction.</summary>
75+
[JsonPropertyName("prompt")]
76+
public string Prompt { get; }
77+
78+
/// <summary>Optional source language code for the custom instruction.</summary>
79+
[JsonPropertyName("source_language")]
80+
public string? SourceLanguage { get; }
81+
}
82+
83+
/// <summary>Information about a style rule list.</summary>
84+
public sealed class StyleRuleInfo {
85+
/// <summary>Initializes a new instance of <see cref="StyleRuleInfo" />.</summary>
86+
[JsonConstructor]
87+
public StyleRuleInfo(
88+
string styleId,
89+
string name,
90+
DateTime creationTime,
91+
DateTime updatedTime,
92+
string language,
93+
int version,
94+
ConfiguredRules? configuredRules,
95+
CustomInstruction[]? customInstructions) {
96+
StyleId = styleId;
97+
Name = name;
98+
CreationTime = creationTime;
99+
UpdatedTime = updatedTime;
100+
Language = language;
101+
Version = version;
102+
ConfiguredRules = configuredRules;
103+
CustomInstructions = customInstructions;
104+
}
105+
106+
/// <summary>Unique ID assigned to the style rule list.</summary>
107+
[JsonPropertyName("style_id")]
108+
public string StyleId { get; }
109+
110+
/// <summary>User-defined name assigned to the style rule list.</summary>
111+
[JsonPropertyName("name")]
112+
public string Name { get; }
113+
114+
/// <summary>Time when the style rule list was created.</summary>
115+
[JsonPropertyName("creation_time")]
116+
public DateTime CreationTime { get; }
117+
118+
/// <summary>Time when the style rule list was last updated.</summary>
119+
[JsonPropertyName("updated_time")]
120+
public DateTime UpdatedTime { get; }
121+
122+
/// <summary>Language code for the style rule list.</summary>
123+
[JsonPropertyName("language")]
124+
public string Language { get; }
125+
126+
/// <summary>Version number of the style rule list.</summary>
127+
[JsonPropertyName("version")]
128+
public int Version { get; }
129+
130+
/// <summary>The predefined rules that have been enabled.</summary>
131+
[JsonPropertyName("configured_rules")]
132+
public ConfiguredRules? ConfiguredRules { get; }
133+
134+
/// <summary>Optional list of custom instructions.</summary>
135+
[JsonPropertyName("custom_instructions")]
136+
public CustomInstruction[]? CustomInstructions { get; }
137+
138+
/// <summary>Returns a string describing the style rule.</summary>
139+
public override string ToString() => $"StyleRule \"{Name}\" ({StyleId})";
140+
}
141+
}

DeepL/TextTranslateOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ public TextTranslateOptions(MultilingualGlossaryInfo glossary) : this() {
2626
GlossaryId = glossary.GlossaryId;
2727
}
2828

29+
/// <summary>Initializes a new <see cref="TextTranslateOptions" /> object including the given style rule.</summary>
30+
/// <param name="styleRule">Style rule to use in translation.</param>
31+
public TextTranslateOptions(StyleRuleInfo styleRule) : this() {
32+
StyleId = styleRule.StyleId;
33+
}
34+
2935
/// <summary>
3036
/// Specifies additional context to influence translations, that is not translated itself.
3137
/// Characters in the `context` parameter are not counted toward billing.
@@ -41,6 +47,9 @@ public TextTranslateOptions(MultilingualGlossaryInfo glossary) : this() {
4147
/// <summary>Specifies the ID of a glossary to use with the translation.</summary>
4248
public string? GlossaryId { get; set; }
4349

50+
/// <summary>Specifies the ID of a style rule to use with the translation.</summary>
51+
public string? StyleId { get; set; }
52+
4453
/// <summary>Specifies a list of XML tags containing content that should not be translated.</summary>
4554
public List<string> IgnoreTags { get; } = new List<string>();
4655

DeepL/Translator.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,8 @@ private String ConstructUserAgentString(bool sendPlatformInfo = true, AppInfo? a
909909
sourceLanguageCode,
910910
targetLanguageCode,
911911
options?.Formality,
912-
options?.GlossaryId);
912+
options?.GlossaryId,
913+
options?.StyleId);
913914

914915
// Always send show_billed_characters=1, remove when the API default is changed to true
915916
bodyParams.Add(("show_billed_characters", "1"));
@@ -971,13 +972,15 @@ private String ConstructUserAgentString(bool sendPlatformInfo = true, AppInfo? a
971972
/// <param name="targetLanguageCode">Language code of translation target language.</param>
972973
/// <param name="formality">Formality option for translation.</param>
973974
/// <param name="glossaryId">Optional ID of glossary to use for translation.</param>
975+
/// <param name="styleId">Optional ID of style rule to use for translation.</param>
974976
/// <returns>List of tuples containing the parameters to include in HTTP request.</returns>
975977
/// <exception cref="ArgumentException">If the specified languages or options are invalid.</exception>
976978
private static List<(string Key, string Value)> CreateCommonHttpParams(
977979
string? sourceLanguageCode,
978980
string targetLanguageCode,
979981
Formality? formality,
980-
string? glossaryId) {
982+
string? glossaryId,
983+
string? styleId = null) {
981984
targetLanguageCode = LanguageCode.Standardize(targetLanguageCode);
982985
sourceLanguageCode = sourceLanguageCode == null ? null : LanguageCode.Standardize(sourceLanguageCode);
983986

@@ -996,6 +999,10 @@ private String ConstructUserAgentString(bool sendPlatformInfo = true, AppInfo? a
996999
bodyParams.Add(("glossary_id", glossaryId));
9971000
}
9981001

1002+
if (styleId != null) {
1003+
bodyParams.Add(("style_id", styleId));
1004+
}
1005+
9991006
switch (formality) {
10001007
case null:
10011008
case Formality.Default:

DeepLTests/StyleRuleTest.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 DeepL SE (https://www.deepl.com)
2+
// Use of this source code is governed by an MIT
3+
// license that can be found in the LICENSE file.
4+
5+
using System.Threading.Tasks;
6+
using DeepL;
7+
using Xunit;
8+
9+
namespace DeepLTests {
10+
public sealed class StyleRuleTest : BaseDeepLTest {
11+
private const string DefaultStyleId = "dca2e053-8ae5-45e6-a0d2-881156e7f4e4";
12+
13+
[MockServerOnlyFact]
14+
public async Task TestGetAllStyleRules() {
15+
var client = CreateTestClient();
16+
var styleRules = await client.GetAllStyleRulesAsync(0, 10, true);
17+
Assert.NotNull(styleRules);
18+
Assert.True(styleRules.Length > 0);
19+
Assert.Equal(DefaultStyleId, styleRules[0].StyleId);
20+
Assert.Equal("Default Style Rule", styleRules[0].Name);
21+
Assert.Equal("en", styleRules[0].Language);
22+
Assert.Equal(1, styleRules[0].Version);
23+
Assert.NotNull(styleRules[0].ConfiguredRules);
24+
Assert.NotNull(styleRules[0].CustomInstructions);
25+
}
26+
27+
[MockServerOnlyFact]
28+
public async Task TestGetAllStyleRulesWithoutDetailed() {
29+
var client = CreateTestClient();
30+
var styleRules = await client.GetAllStyleRulesAsync();
31+
32+
Assert.NotNull(styleRules);
33+
Assert.True(styleRules.Length > 0);
34+
Assert.Equal(DefaultStyleId, styleRules[0].StyleId);
35+
Assert.Null(styleRules[0].ConfiguredRules);
36+
Assert.Null(styleRules[0].CustomInstructions);
37+
}
38+
39+
[MockServerOnlyFact]
40+
public async Task TestTranslateTextWithStyleId() {
41+
// Note: this test may use the mock server that will not translate the text,
42+
// therefore we do not check the translated result.
43+
var client = CreateTestClient();
44+
const string exampleText = "Hallo, Welt!";
45+
46+
var result = await client.TranslateTextAsync(
47+
exampleText,
48+
"de",
49+
"en-US",
50+
new TextTranslateOptions { StyleId = DefaultStyleId });
51+
52+
Assert.NotNull(result);
53+
}
54+
55+
[MockServerOnlyFact]
56+
public async Task TestTranslateTextWithStyleRuleInfo() {
57+
var client = CreateTestClient();
58+
var styleRules = await client.GetAllStyleRulesAsync();
59+
var styleRule = styleRules[0];
60+
const string exampleText = "Hallo, Welt!";
61+
62+
var result = await client.TranslateTextAsync(
63+
exampleText,
64+
"de",
65+
"en-US",
66+
new TextTranslateOptions(styleRule));
67+
68+
Assert.NotNull(result);
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)