getVocabulary() {
+ return vocabulary;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/CustomInstruction.java b/deepl-java/src/main/java/com/deepl/api/CustomInstruction.java
new file mode 100644
index 0000000..552a857
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/CustomInstruction.java
@@ -0,0 +1,73 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import org.jetbrains.annotations.*;
+
+/** Custom instruction for a style rule. */
+public class CustomInstruction {
+ @SerializedName(value = "id")
+ @Nullable
+ private final String id;
+
+ @SerializedName(value = "label")
+ private final String label;
+
+ @SerializedName(value = "prompt")
+ private final String prompt;
+
+ @SerializedName(value = "source_language")
+ @Nullable
+ private final String sourceLanguage;
+
+ /**
+ * Initializes a new {@link CustomInstruction} containing a custom instruction for a style rule.
+ *
+ * @param id Optional unique identifier for the custom instruction.
+ * @param label Label for the custom instruction.
+ * @param prompt Prompt text for the custom instruction.
+ * @param sourceLanguage Optional source language code for the custom instruction.
+ */
+ public CustomInstruction(
+ @Nullable String id, String label, String prompt, @Nullable String sourceLanguage) {
+ this.id = id;
+ this.label = label;
+ this.prompt = prompt;
+ this.sourceLanguage = sourceLanguage;
+ }
+
+ /**
+ * Initializes a new {@link CustomInstruction} containing a custom instruction for a style rule.
+ *
+ * @param label Label for the custom instruction.
+ * @param prompt Prompt text for the custom instruction.
+ * @param sourceLanguage Optional source language code for the custom instruction.
+ */
+ public CustomInstruction(String label, String prompt, @Nullable String sourceLanguage) {
+ this(null, label, prompt, sourceLanguage);
+ }
+
+ /** @return Optional unique identifier for the custom instruction, or {@code null} if not set. */
+ @Nullable
+ public String getId() {
+ return id;
+ }
+
+ /** @return Label for the custom instruction. */
+ public String getLabel() {
+ return label;
+ }
+
+ /** @return Prompt text for the custom instruction. */
+ public String getPrompt() {
+ return prompt;
+ }
+
+ /** @return Optional source language code for the custom instruction. */
+ @Nullable
+ public String getSourceLanguage() {
+ return sourceLanguage;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/DeepLApiVersion.java b/deepl-java/src/main/java/com/deepl/api/DeepLApiVersion.java
new file mode 100644
index 0000000..19a5768
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/DeepLApiVersion.java
@@ -0,0 +1,23 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+public enum DeepLApiVersion {
+ VERSION_1("v1"),
+ VERSION_2("v2");
+
+ /**
+ * How the version is represented in the URL string. Does not include any slashes (/). Example:
+ * "v2"
+ */
+ private final String urlRepresentation;
+
+ private DeepLApiVersion(String urlRepresentation) {
+ this.urlRepresentation = urlRepresentation;
+ }
+
+ public String toString() {
+ return this.urlRepresentation;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/DeepLClient.java b/deepl-java/src/main/java/com/deepl/api/DeepLClient.java
new file mode 100644
index 0000000..9333746
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/DeepLClient.java
@@ -0,0 +1,1806 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+
+package com.deepl.api;
+
+import com.deepl.api.http.HttpResponse;
+import com.deepl.api.http.HttpResponseStream;
+import com.deepl.api.utils.*;
+import java.io.*;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.jetbrains.annotations.Nullable;
+
+public class DeepLClient extends Translator {
+
+ /** Default MIME type of translation memory (TMX) files. */
+ public static final String TRANSLATION_MEMORY_FILE_CONTENT_TYPE = "application/xml";
+
+ /** Time to wait between polls of the status of a translation memory job, in milliseconds. */
+ private static final long TRANSLATION_MEMORY_JOB_POLL_INTERVAL_MILLIS = 5000;
+
+ /**
+ * Initializes a new DeepLClient object using your Authentication Key.
+ *
+ * Note: This function does not establish a connection to the DeepL API. To check connectivity,
+ * use {@link DeepLClient#getUsage()}.
+ *
+ * @param authKey DeepL Authentication Key as found in your DeepL account.
+ * @throws IllegalArgumentException If authKey is invalid.
+ */
+ public DeepLClient(String authKey) throws IllegalArgumentException {
+ this(authKey, new DeepLClientOptions());
+ }
+
+ /**
+ * Initializes a new DeepLClient object using your Authentication Key.
+ *
+ *
Note: This function does not establish a connection to the DeepL API. To check connectivity,
+ * use {@link DeepLClient#getUsage()}.
+ *
+ * @param authKey DeepL Authentication Key as found in your DeepL account.
+ * @param options Additional options controlling Client behaviour.
+ * @throws IllegalArgumentException If authKey is invalid.
+ * @deprecated Use the constructor that takes {@link DeepLClientOptions} instead of {@link
+ * TranslatorOptions}
+ */
+ @Deprecated
+ public DeepLClient(String authKey, TranslatorOptions options) throws IllegalArgumentException {
+ super(authKey, options);
+ }
+
+ /**
+ * Initializes a new DeepLClient object using your Authentication Key.
+ *
+ *
Note: This function does not establish a connection to the DeepL API. To check connectivity,
+ * use {@link DeepLClient#getUsage()}.
+ *
+ * @param authKey DeepL Authentication Key as found in your DeepL account.
+ * @param options Additional options controlling Client behaviour.
+ * @throws IllegalArgumentException If authKey is invalid.
+ */
+ @SuppressWarnings("deprecation")
+ public DeepLClient(String authKey, DeepLClientOptions options) throws IllegalArgumentException {
+ super(authKey, options);
+ }
+
+ public WriteResult rephraseText(
+ String text, @Nullable String targetLang, @Nullable TextRephraseOptions options)
+ throws InterruptedException, DeepLException {
+ ArrayList texts = new ArrayList<>();
+ texts.add(text);
+ return this.rephraseText(texts, targetLang, options).get(0);
+ }
+
+ public List rephraseText(
+ List texts, @Nullable String targetLang, @Nullable TextRephraseOptions options)
+ throws InterruptedException, DeepLException {
+ Iterable> params =
+ createWriteHttpParams(texts, targetLang, options);
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/write/rephrase", apiVersion), params);
+ checkResponse(response, false, false);
+ return jsonParser.parseWriteResult(response.getBody());
+ }
+
+ /**
+ * Creates a glossary in your DeepL account with the specified details and returns a {@link
+ * MultilingualGlossaryInfo} object with details about the newly created glossary. The glossary
+ * will contain the glossary dictionaries specified in each with
+ * their own source language, target language and entries.The glossary can be used in translations
+ * to override translations for specific terms (words). The glossary must contain a glossary
+ * dictionary that matches the languages of translations for which it will be used.
+ *
+ * @param name User-defined name to assign to the glossary; must not be empty.
+ * @param glossaryDicts {@link MultilingualGlossaryDictionaryInfo} The dictionaries of the
+ * glossary
+ * @return {@link MultilingualGlossaryInfo} object with details about the newly created glossary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo createMultilingualGlossary(
+ String name, List glossaryDicts)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ validateParameter("name", name);
+ if (glossaryDicts.isEmpty()) {
+ throw new IllegalArgumentException("Parameter dictionaries must not be empty");
+ }
+ ArrayList> bodyParams =
+ createGlossaryHttpParams(name, glossaryDicts);
+ HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v3/glossaries", bodyParams);
+ checkResponse(response, false, false);
+ return jsonParser.parseMultilingualGlossaryInfo(response.getBody());
+ }
+
+ /**
+ * Creates a glossary in your DeepL account with the specified details and returns a {@link
+ * MultilingualGlossaryInfo} object with details about the newly created glossary. The glossary
+ * will contain a glossary dictionary with the source and target language codes specified and
+ * entries created from the . The glossary can be used in translations
+ * to override translations for specific terms (words). The glossary must contain a glossary
+ * dictionary that matches the languages of translations for which it will be used.
+ *
+ * @param name User-defined name to assign to the glossary; must not be empty.
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param csvFile String containing CSV content.
+ * @return {@link MultilingualGlossaryInfo} object with details about the newly created glossary.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo createMultilingualGlossaryFromCsv(
+ String name, String sourceLanguageCode, String targetLanguageCode, String csvFile)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return createGlossaryFromCsvInternal(name, sourceLanguageCode, targetLanguageCode, csvFile);
+ }
+
+ /**
+ * Creates a glossary in your DeepL account with the specified details and returns a {@link
+ * MultilingualGlossaryInfo} object with details about the newly created glossary. The glossary
+ * will contain a glossary dictionary with the source and target language codes specified and
+ * entries created from the . The glossary can be used in translations
+ * to override translations for specific terms (words). The glossary must contain a glossary
+ * dictionary that matches the languages of translations for which it will be used.
+ *
+ * @param name User-defined name to assign to the glossary; must not be empty.
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param csvFile String containing CSV content.
+ * @return {@link MultilingualGlossaryInfo} object with details about the newly created glossary.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ * @throws IOException If an I/O error occurs.
+ */
+ public MultilingualGlossaryInfo createMultilingualGlossaryFromCsv(
+ String name, String sourceLanguageCode, String targetLanguageCode, File csvFile)
+ throws DeepLException, IllegalArgumentException, InterruptedException, IOException {
+ try (FileInputStream stream = new FileInputStream(csvFile)) {
+ String csvContent = StreamUtil.readStream(stream);
+ return createGlossaryFromCsvInternal(
+ name, sourceLanguageCode, targetLanguageCode, csvContent);
+ }
+ }
+
+ /**
+ * Retrieves information about the glossary with the specified ID and returns a {@link
+ * MultilingualGlossaryInfo} object containing details. This does not retrieve the glossary
+ * entries; to retrieve entries use {@link
+ * DeepLClient#getMultilingualGlossaryDictionaryEntries(String, String, String)}
+ *
+ * @param glossaryId ID of glossary to retrieve.
+ * @return {@link MultilingualGlossaryInfo} object with details about the specified glossary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public MultilingualGlossaryInfo getMultilingualGlossary(String glossaryId)
+ throws DeepLException, InterruptedException {
+ String relativeUrl = String.format("/v3/glossaries/%s", glossaryId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, true);
+ return jsonParser.parseMultilingualGlossaryInfo(response.getBody());
+ }
+
+ /**
+ * Retrieves information about all glossaries and returns an array of {@link
+ * MultilingualGlossaryInfo} objects containing details. This does not retrieve the glossary
+ * entries; to retrieve entries use {@link
+ * DeepLClient#getMultilingualGlossaryDictionaryEntries(String, String, String)}
+ *
+ * @return List of {@link MultilingualGlossaryInfo} objects with details about each glossary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public List listMultilingualGlossaries()
+ throws DeepLException, InterruptedException {
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff("/v3/glossaries");
+ checkResponse(response, false, false);
+ return jsonParser.parseMultilingualGlossaryInfoList(response.getBody());
+ }
+
+ /**
+ * For the glossary with the specified ID, retrieves the glossary dictionary with its entries for
+ * the given source and target language code pair.
+ *
+ * @param glossaryId ID of glossary for which to retrieve entries.
+ * @param sourceLanguageCode Source language code for the requested glossary dictionary.
+ * @param targetLanguageCode Target language code of the requested glossary dictionary.
+ * @return {@link MultilingualGlossaryDictionaryEntries} object containing a glossary dictionary
+ * with entries.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryEntries getMultilingualGlossaryDictionaryEntries(
+ String glossaryId, String sourceLanguageCode, String targetLanguageCode)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ validateParameter("glossaryId", glossaryId);
+ String queryString = createLanguageQueryParams(sourceLanguageCode, targetLanguageCode);
+ String relativeUrl = String.format("/v3/glossaries/%s/entries%s", glossaryId, queryString);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, true);
+ return jsonParser
+ .parseMultilingualGlossaryDictionaryListResponse(response.getBody())
+ .getDictionaries()
+ .get(0)
+ .getDictionaryEntries();
+ }
+
+ /**
+ * For the glossary with the specified ID, retrieves the glossary dictionary with its entries for
+ * the given {@link MultilingualGlossaryDictionaryInfo} glossary dictionary.
+ *
+ * @param glossaryId ID of glossary for which to retrieve entries.
+ * @param glossaryDict The requested glossary dictionary.
+ * @return {@link MultilingualGlossaryDictionaryEntries} object containing a glossary dictionary
+ * with entries.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryEntries getMultilingualGlossaryDictionaryEntries(
+ String glossaryId, MultilingualGlossaryDictionaryInfo glossaryDict)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return getMultilingualGlossaryDictionaryEntries(
+ glossaryId, glossaryDict.getSourceLanguageCode(), glossaryDict.getTargetLanguageCode());
+ }
+
+ /**
+ * For the specified glossary, retrieves the glossary dictionary with its entries for the given
+ * source and target language code pair.
+ *
+ * @param glossary The glossary for which to retrieve entries.
+ * @param sourceLanguageCode Source language code for the requested glossary dictionary.
+ * @param targetLanguageCode Target language code of the requested glossary dictionary.
+ * @return {@link MultilingualGlossaryDictionaryEntries} object containing a glossary dictionary
+ * with entries.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryEntries getMultilingualGlossaryDictionaryEntries(
+ MultilingualGlossaryInfo glossary, String sourceLanguageCode, String targetLanguageCode)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return getMultilingualGlossaryDictionaryEntries(
+ glossary.getGlossaryId(), sourceLanguageCode, targetLanguageCode);
+ }
+
+ /**
+ * For the specified glossary, retrieves the glossary dictionary with its entries for the given
+ * {@link MultilingualGlossaryDictionaryInfo} glossary dictionary.
+ *
+ * @param glossary The glossary for which to retrieve entries.
+ * @param glossaryDict The requested glossary dictionary.
+ * @return {@link MultilingualGlossaryDictionaryEntries} object containing a glossary dictionary
+ * with entries.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryEntries getMultilingualGlossaryDictionaryEntries(
+ MultilingualGlossaryInfo glossary, MultilingualGlossaryDictionaryInfo glossaryDict)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return getMultilingualGlossaryDictionaryEntries(
+ glossary.getGlossaryId(),
+ glossaryDict.getSourceLanguageCode(),
+ glossaryDict.getTargetLanguageCode());
+ }
+
+ /**
+ * Replaces a glossary dictionary with given entries for the source and target language codes. If
+ * no such glossary dictionary exists for that language pair, a new glossary dictionary will be
+ * created for that language pair and entries.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * replaced/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param entries The source-target entry pairs in the new glossary dictionary.
+ * @return {@link MultilingualGlossaryDictionaryInfo} object with details about the newly replaced
+ * glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(
+ String glossaryId,
+ String sourceLanguageCode,
+ String targetLanguageCode,
+ GlossaryEntries entries)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return replaceGlossaryDictionaryInternal(
+ glossaryId, sourceLanguageCode, targetLanguageCode, entries.toTsv(), "tsv");
+ }
+
+ /**
+ * Replaces a glossary dictionary with given entries for the source and target language codes. If
+ * no such glossary dictionary exists for that language pair, a new glossary dictionary will be
+ * created for that language pair and entries.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * replaced/created
+ * @param glossaryDict The glossary dictionary to replace the existing glossary dictionary for
+ * that source/target language code pair or to be newly created if no such glossary dictionary
+ * exists.
+ * @return {@link MultilingualGlossaryDictionaryInfo} object with details about the newly replaced
+ * glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(
+ String glossaryId, MultilingualGlossaryDictionaryEntries glossaryDict)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return replaceGlossaryDictionaryInternal(
+ glossaryId,
+ glossaryDict.getSourceLanguageCode(),
+ glossaryDict.getTargetLanguageCode(),
+ glossaryDict.getEntries().toTsv(),
+ "tsv");
+ }
+
+ /**
+ * Replaces a glossary dictionary with given entries for given glossary dictionary. If no such
+ * glossary dictionary exists for that language pair, a new glossary dictionary will be created
+ * for that language pair and entries.
+ *
+ * @param glossary The specified glossary that contains the dictionary to be replaced/created
+ * @param glossaryDict The glossary dictionary to replace the existing glossary dictionary for
+ * that source/target language code pair or to be newly created if no such glossary dictionary
+ * exists.
+ * @return {@link MultilingualGlossaryDictionaryInfo} object with details about the newly replaced
+ * glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(
+ MultilingualGlossaryInfo glossary, MultilingualGlossaryDictionaryEntries glossaryDict)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return replaceGlossaryDictionaryInternal(
+ glossary.getGlossaryId(),
+ glossaryDict.getSourceLanguageCode(),
+ glossaryDict.getTargetLanguageCode(),
+ glossaryDict.getEntries().toTsv(),
+ "tsv");
+ }
+
+ /**
+ * Replaces a glossary dictionary with given entries for the source and target language codes. If
+ * no such glossary dictionary exists for that language pair, a new glossary dictionary will be
+ * created for that language pair and entries.
+ *
+ * @param glossary The specified glossary that contains the dictionary to be replaced/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param entries The source-target entry pairs in the new glossary dictionary.
+ * @return {@link MultilingualGlossaryDictionaryInfo} object with details about the newly replaced
+ * glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(
+ MultilingualGlossaryInfo glossary,
+ String sourceLanguageCode,
+ String targetLanguageCode,
+ GlossaryEntries entries)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return replaceGlossaryDictionaryInternal(
+ glossary.getGlossaryId(), sourceLanguageCode, targetLanguageCode, entries.toTsv(), "tsv");
+ }
+
+ /**
+ * Replaces a glossary dictionary with given entries for the source and target language codes. If
+ * no such glossary dictionary exists for that language pair, a new glossary dictionary will be
+ * created for that language pair and entries specified in the {@code csvFile}.
+ *
+ * @param glossaryId The specified Id of the glossary that contains the dictionary to be
+ * replaced/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param csvFile File containing CSV content.
+ * @return {@link MultilingualGlossaryDictionaryInfo} object with details about the newly replaced
+ * glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ * @throws IOException If an I/O error occurs.
+ */
+ public MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionaryFromCsv(
+ String glossaryId, String sourceLanguageCode, String targetLanguageCode, File csvFile)
+ throws DeepLException, IllegalArgumentException, InterruptedException, IOException {
+ try (FileInputStream stream = new FileInputStream(csvFile)) {
+ String csvContent = StreamUtil.readStream(stream);
+ return replaceGlossaryDictionaryInternal(
+ glossaryId, sourceLanguageCode, targetLanguageCode, csvContent, "csv");
+ }
+ }
+
+ /**
+ * Replaces a glossary dictionary with given entries for the source and target language codes. If
+ * no such glossary dictionary exists for that language pair, a new glossary dictionary will be
+ * created for that language pair and entries specified in the {@code csvContent}.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * replaced/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param csvContent String containing CSV content.
+ * @return {@link MultilingualGlossaryDictionaryInfo} object with details about the newly replaced
+ * glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionaryFromCsv(
+ String glossaryId, String sourceLanguageCode, String targetLanguageCode, String csvContent)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return replaceGlossaryDictionaryInternal(
+ glossaryId, sourceLanguageCode, targetLanguageCode, csvContent, "csv");
+ }
+
+ /**
+ * Updates a glossary dictionary with given entries for the source and target language codes. The
+ * glossary dictionary must belong to the glossary with the ID specified in . If a dictionary for the provided language pair already exists, the
+ * dictionary entries are merged.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * updated/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param entries The source-target entry pairs in the new glossary dictionary.
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(
+ String glossaryId,
+ String sourceLanguageCode,
+ String targetLanguageCode,
+ GlossaryEntries entries)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return updateGlossaryDictionaryInternal(
+ glossaryId, sourceLanguageCode, targetLanguageCode, entries.toTsv(), "tsv");
+ }
+
+ /**
+ * Updates a glossary dictionary with given entries for the source and target language codes. The
+ * glossary dictionary must belong to the glossary specified in . If a
+ * dictionary for the provided language pair already exists, the dictionary entries are merged.
+ *
+ * @param glossary The specified ID for the glossary that contains the dictionary to be
+ * updated/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param entries The source-target entry pairs in the new glossary dictionary.
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(
+ MultilingualGlossaryInfo glossary,
+ String sourceLanguageCode,
+ String targetLanguageCode,
+ GlossaryEntries entries)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return updateGlossaryDictionaryInternal(
+ glossary.getGlossaryId(), sourceLanguageCode, targetLanguageCode, entries.toTsv(), "tsv");
+ }
+
+ /**
+ * Updates a glossary dictionary with given glossary dictionary specified in . The glossary dictionary must belong to the glossary with the ID
+ * specified in . If a dictionary for the provided language pair
+ * already exists, the dictionary entries are merged.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * updated/created
+ * @param glossaryDict The glossary dictionary to be created/updated
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(
+ String glossaryId, MultilingualGlossaryDictionaryEntries glossaryDict)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return updateGlossaryDictionaryInternal(
+ glossaryId,
+ glossaryDict.getSourceLanguageCode(),
+ glossaryDict.getTargetLanguageCode(),
+ glossaryDict.getEntries().toTsv(),
+ "tsv");
+ }
+
+ /**
+ * Updates a glossary dictionary with given entries for the source and target language codes. If a
+ * dictionary for the provided language pair already exists, the dictionary entries are merged.
+ *
+ * @param glossary The specified glossary that contains the dictionary to be updated/created
+ * @param glossaryDict The glossary dictionary to be created/updated
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(
+ MultilingualGlossaryInfo glossary, MultilingualGlossaryDictionaryEntries glossaryDict)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return updateGlossaryDictionaryInternal(
+ glossary.getGlossaryId(),
+ glossaryDict.getSourceLanguageCode(),
+ glossaryDict.getTargetLanguageCode(),
+ glossaryDict.getEntries().toTsv(),
+ "tsv");
+ }
+
+ /**
+ * Updates a glossary's name with the provided parameter
+ *
+ * @param glossaryId The specified ID of the glossary whose name will be updated
+ * @param name The new name of the glossary
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryName(String glossaryId, String name)
+ throws DeepLException, IllegalArgumentException {
+
+ ArrayList> bodyParams = new ArrayList<>();
+ bodyParams.add(new KeyValuePair<>("name", name));
+ String relativeUrl = String.format("/v3/glossaries/%s", glossaryId);
+ try {
+ HttpResponse response =
+ httpClientWrapper.sendPatchRequestWithBackoff(relativeUrl, bodyParams);
+ checkResponse(response, false, true);
+ return jsonParser.parseMultilingualGlossaryInfo(response.getBody());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new DeepLException("Request was interrupted", e);
+ }
+ }
+
+ /**
+ * Updates a glossary dictionary correlating to the specified ID with given entries in the {@code
+ * csvFile} for the source and target language codes. If a dictionary for the provided language
+ * pair already exists, the dictionary entries are merged.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * updated/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param csvFile {@link File} containing CSV content.
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ * @throws IOException If an I/O error occurs.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryDictionaryFromCsv(
+ String glossaryId, String sourceLanguageCode, String targetLanguageCode, File csvFile)
+ throws DeepLException, IllegalArgumentException, InterruptedException, IOException {
+ try (FileInputStream stream = new FileInputStream(csvFile)) {
+ String csvContent = StreamUtil.readStream(stream);
+ return updateGlossaryDictionaryInternal(
+ glossaryId, sourceLanguageCode, targetLanguageCode, csvContent, "csv");
+ }
+ }
+
+ /**
+ * Updates a glossary dictionary with given entries in the {@code csvFile} for the source and
+ * target language codes. If a dictionary for the provided language pair already exists, the
+ * dictionary entries are merged.
+ *
+ * @param glossaryId The specified ID of the glossary that contains the dictionary to be
+ * updated/created
+ * @param sourceLanguageCode Language code of the source terms language.
+ * @param targetLanguageCode Language code of the target terms language.
+ * @param csvContent String containing CSV content.
+ * @return {@link MultilingualGlossaryInfo} object with details about the glossary with the newly
+ * updated glossary dictionary.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public MultilingualGlossaryInfo updateMultilingualGlossaryDictionaryFromCsv(
+ String glossaryId, String sourceLanguageCode, String targetLanguageCode, String csvContent)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ return updateGlossaryDictionaryInternal(
+ glossaryId, sourceLanguageCode, targetLanguageCode, csvContent, "csv");
+ }
+
+ /**
+ * Deletes the glossary with the specified ID.
+ *
+ * @param glossaryId ID of glossary to delete.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public void deleteMultilingualGlossary(String glossaryId)
+ throws DeepLException, InterruptedException {
+ String relativeUrl = String.format("/v3/glossaries/%s", glossaryId);
+ HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
+ this.checkResponse(response, false, true);
+ }
+
+ /**
+ * Deletes the specified glossary.
+ *
+ * @param glossary {@link MultilingualGlossaryInfo} object corresponding to glossary to delete.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public void deleteMultilingualGlossary(MultilingualGlossaryInfo glossary)
+ throws DeepLException, InterruptedException {
+ this.deleteMultilingualGlossary(glossary.getGlossaryId());
+ }
+
+ /**
+ * Deletes the glossary dictionary with the source and target language codes specified in the
+ * glossary with the specified ID.
+ *
+ * @param glossaryId ID of glossary that contains the glossary dictionary to delete.
+ * @param sourceLanguageCode Source language code of the glossary dictionary to be deleted.
+ * @param targetLanguageCode Target language code of the glossary dictionary to be deleted.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws IllegalArgumentException If any argument is invalid.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public void deleteMultilingualGlossaryDictionary(
+ String glossaryId, String sourceLanguageCode, String targetLanguageCode)
+ throws DeepLException, InterruptedException, IllegalArgumentException {
+ String queryString = createLanguageQueryParams(sourceLanguageCode, targetLanguageCode);
+ String relativeUrl = String.format("/v3/glossaries/%s/dictionaries%s", glossaryId, queryString);
+ HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
+ this.checkResponse(response, false, true);
+ }
+
+ /**
+ * Deletes the specified glossary dictionary in the glossary with the specified ID.
+ *
+ * @param glossaryId ID of glossary that contains the glossary dictionary to delete.
+ * @param glossaryDict {@link MultilingualGlossaryDictionaryInfo} object corresponding to glossary
+ * dictionary to delete.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public void deleteMultilingualGlossaryDictionary(
+ String glossaryId, MultilingualGlossaryDictionaryInfo glossaryDict)
+ throws DeepLException, InterruptedException, IllegalArgumentException {
+ deleteMultilingualGlossaryDictionary(
+ glossaryId, glossaryDict.getSourceLanguageCode(), glossaryDict.getTargetLanguageCode());
+ }
+
+ /**
+ * Deletes the specified glossary dictionary in the glossary in the specified glossary.
+ *
+ * @param glossary The glossary that contains the glossary dictionary to delete.
+ * @param sourceLanguageCode Source language code of the glossary dictionary to be deleted.
+ * @param targetLanguageCode Target language code of the glossary dictionary to be deleted.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public void deleteMultilingualGlossaryDictionary(
+ MultilingualGlossaryInfo glossary, String sourceLanguageCode, String targetLanguageCode)
+ throws DeepLException, InterruptedException, IllegalArgumentException {
+ deleteMultilingualGlossaryDictionary(
+ glossary.getGlossaryId(), sourceLanguageCode, targetLanguageCode);
+ }
+
+ /**
+ * Deletes the specified glossary dictionary in the glossary in the specified glossary.
+ *
+ * @param glossary The glossary that contains the glossary dictionary to delete.
+ * @param glossaryDict {@link MultilingualGlossaryDictionaryInfo} object corresponding to glossary
+ * dictionary to delete.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public void deleteMultilingualGlossaryDictionary(
+ MultilingualGlossaryInfo glossary, MultilingualGlossaryDictionaryInfo glossaryDict)
+ throws DeepLException, InterruptedException, IllegalArgumentException {
+ deleteMultilingualGlossaryDictionary(
+ glossary.getGlossaryId(),
+ glossaryDict.getSourceLanguageCode(),
+ glossaryDict.getTargetLanguageCode());
+ }
+
+ /**
+ * Retrieves the list of all available style rules and returns a list of {@link StyleRuleInfo}
+ * objects corresponding to all of your stored style rules.
+ *
+ * @param page Optional page number for pagination, 0-indexed.
+ * @param pageSize Optional number of items per page.
+ * @param detailed Optional flag indicating whether to include detailed configuration rules
+ * including the configuredRules and customInstructions properties.
+ * @return List of {@link StyleRuleInfo} objects for all available style rules.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API, a {@link
+ * DeepLException} or a derived class will be thrown.
+ */
+ public List getAllStyleRules(
+ @Nullable Integer page, @Nullable Integer pageSize, @Nullable Boolean detailed)
+ throws DeepLException, InterruptedException {
+ ArrayList> queryParams = new ArrayList<>();
+ if (page != null) {
+ queryParams.add(new KeyValuePair<>("page", page.toString()));
+ }
+ if (pageSize != null) {
+ queryParams.add(new KeyValuePair<>("page_size", pageSize.toString()));
+ }
+ if (detailed != null) {
+ queryParams.add(new KeyValuePair<>("detailed", detailed.toString().toLowerCase()));
+ }
+
+ String relativeUrl = "/v3/style_rules" + createQueryString(queryParams);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseStyleRuleInfoList(response.getBody());
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#getAllStyleRules(Integer, Integer, Boolean)} but with
+ * default parameters (all null).
+ *
+ * @see DeepLClient#getAllStyleRules(Integer, Integer, Boolean)
+ */
+ public List getAllStyleRules() throws DeepLException, InterruptedException {
+ return getAllStyleRules(null, null, null);
+ }
+
+ /**
+ * Retrieves a list of translation memories available for the account associated with the DeepL
+ * API auth key. The maximum number of translation memories returned is controlled by pageSize
+ * (max 25).
+ *
+ * @param page Page number to retrieve (starting from 0), or null.
+ * @param pageSize Number of items per page, or null.
+ * @return List of {@link TranslationMemoryInfo} objects.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public List listTranslationMemories(
+ @Nullable Integer page, @Nullable Integer pageSize)
+ throws DeepLException, InterruptedException {
+ ArrayList> queryParams = new ArrayList<>();
+ if (page != null) {
+ queryParams.add(new KeyValuePair<>("page", page.toString()));
+ }
+ if (pageSize != null) {
+ queryParams.add(new KeyValuePair<>("page_size", pageSize.toString()));
+ }
+
+ String relativeUrl = "/v3/translation_memories" + createQueryString(queryParams);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseTranslationMemoryInfoList(response.getBody());
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#listTranslationMemories(Integer, Integer)} but with
+ * default parameters (all null).
+ *
+ * @see DeepLClient#listTranslationMemories(Integer, Integer)
+ */
+ public List listTranslationMemories()
+ throws DeepLException, InterruptedException {
+ return listTranslationMemories(null, null);
+ }
+
+ /**
+ * Retrieves information about the translation memory with the specified ID and returns a {@link
+ * TranslationMemoryInfo} object containing details.
+ *
+ * @param translationMemoryId ID of the translation memory to retrieve.
+ * @return {@link TranslationMemoryInfo} object with details about the translation memory.
+ * @throws NotFoundException If no translation memory with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public TranslationMemoryInfo getTranslationMemory(String translationMemoryId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("translationMemoryId", translationMemoryId);
+ String relativeUrl = String.format("/v3/translation_memories/%s", translationMemoryId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseTranslationMemoryInfo(response.getBody());
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#getTranslationMemory(String)} but accepts a {@link
+ * TranslationMemoryInfo} object.
+ *
+ * @see DeepLClient#getTranslationMemory(String)
+ */
+ public TranslationMemoryInfo getTranslationMemory(TranslationMemoryInfo translationMemory)
+ throws DeepLException, InterruptedException, NotFoundException {
+ return getTranslationMemory(translationMemoryId(translationMemory));
+ }
+
+ /**
+ * Retrieves one page of the segments of the translation memory with the specified ID.
+ *
+ * Pagination is cursor-based: omit the page cursor on the first call, then pass the previous
+ * response's {@link TranslationMemorySegments#getNextPageCursor()} to fetch the next page. An
+ * absent next page cursor means the last page has been returned.
+ *
+ * @param translationMemoryId ID of the translation memory to retrieve the segments of.
+ * @param options Options influencing the returned page, or null.
+ * @return {@link TranslationMemorySegments} object containing the requested page.
+ * @throws NotFoundException If no translation memory with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public TranslationMemorySegments listTranslationMemorySegments(
+ String translationMemoryId, @Nullable TranslationMemorySegmentsOptions options)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("translationMemoryId", translationMemoryId);
+ ArrayList> queryParams = new ArrayList<>();
+ if (options != null) {
+ if (options.getPageSize() != null) {
+ queryParams.add(new KeyValuePair<>("page_size", options.getPageSize().toString()));
+ }
+ if (options.getPageCursor() != null) {
+ queryParams.add(new KeyValuePair<>("page_cursor", options.getPageCursor()));
+ }
+ if (options.getFilterText() != null) {
+ queryParams.add(new KeyValuePair<>("filter_text", options.getFilterText()));
+ }
+ if (options.getFilterCaseSensitive() != null) {
+ queryParams.add(
+ new KeyValuePair<>(
+ "filter_case_sensitive", options.getFilterCaseSensitive().toString()));
+ }
+ }
+
+ String relativeUrl =
+ String.format("/v3/translation_memories/%s/segments", translationMemoryId)
+ + createQueryString(queryParams);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseTranslationMemorySegments(response.getBody());
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#listTranslationMemorySegments(String,
+ * TranslationMemorySegmentsOptions)} but with default options.
+ *
+ * @see DeepLClient#listTranslationMemorySegments(String, TranslationMemorySegmentsOptions)
+ */
+ public TranslationMemorySegments listTranslationMemorySegments(String translationMemoryId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ return listTranslationMemorySegments(translationMemoryId, null);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#listTranslationMemorySegments(String,
+ * TranslationMemorySegmentsOptions)} but accepts a {@link TranslationMemoryInfo} object.
+ *
+ * @see DeepLClient#listTranslationMemorySegments(String, TranslationMemorySegmentsOptions)
+ */
+ public TranslationMemorySegments listTranslationMemorySegments(
+ TranslationMemoryInfo translationMemory, @Nullable TranslationMemorySegmentsOptions options)
+ throws DeepLException, InterruptedException, NotFoundException {
+ return listTranslationMemorySegments(translationMemoryId(translationMemory), options);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#listTranslationMemorySegments(String,
+ * TranslationMemorySegmentsOptions)} but accepts a {@link TranslationMemoryInfo} object and uses
+ * default options.
+ *
+ * @see DeepLClient#listTranslationMemorySegments(String, TranslationMemorySegmentsOptions)
+ */
+ public TranslationMemorySegments listTranslationMemorySegments(
+ TranslationMemoryInfo translationMemory)
+ throws DeepLException, InterruptedException, NotFoundException {
+ return listTranslationMemorySegments(translationMemoryId(translationMemory), null);
+ }
+
+ /**
+ * Deletes the translation memory with the specified ID.
+ *
+ * @param translationMemoryId ID of the translation memory to delete.
+ * @throws NotFoundException If no translation memory with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public void deleteTranslationMemory(String translationMemoryId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("translationMemoryId", translationMemoryId);
+ String relativeUrl = String.format("/v3/translation_memories/%s", translationMemoryId);
+ HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#deleteTranslationMemory(String)} but accepts a {@link
+ * TranslationMemoryInfo} object.
+ *
+ * @see DeepLClient#deleteTranslationMemory(String)
+ */
+ public void deleteTranslationMemory(TranslationMemoryInfo translationMemory)
+ throws DeepLException, InterruptedException, NotFoundException {
+ deleteTranslationMemory(translationMemoryId(translationMemory));
+ }
+
+ /**
+ * Creates an import job for a new translation memory.
+ *
+ * The job only declares the file; upload the TMX file itself to the returned upload URL with
+ * {@link DeepLClient#uploadTranslationMemoryFile(TranslationMemoryImport, byte[])}, then poll
+ * {@link DeepLClient#getTranslationMemoryJob(String)} for the outcome. Use {@link
+ * DeepLClient#importTranslationMemoryFromFilepath(File, String)} to do all three steps at once.
+ *
+ * @param fileName Name of the TMX file to import, for example "legal.tmx".
+ * @param contentLength Size of the TMX file in bytes.
+ * @param contentType Optional MIME type of the file, defaults to "application/xml".
+ * @param displayName Optional name for the resulting translation memory, defaults to the file
+ * name.
+ * @return {@link TranslationMemoryImport} object with the job ID and upload URL.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public TranslationMemoryImport createTranslationMemoryImport(
+ String fileName,
+ long contentLength,
+ @Nullable String contentType,
+ @Nullable String displayName)
+ throws DeepLException, InterruptedException {
+ validateParameter("fileName", fileName);
+ if (contentLength <= 0) {
+ throw new IllegalArgumentException("contentLength must be greater than 0");
+ }
+
+ Map sourceFile = new HashMap<>();
+ sourceFile.put("file_name", fileName);
+ sourceFile.put("content_length", contentLength);
+ if (contentType != null) {
+ sourceFile.put("content_type", contentType);
+ }
+ Map requestData = new HashMap<>();
+ requestData.put("source_file", sourceFile);
+ if (displayName != null) {
+ Map parameters = new HashMap<>();
+ parameters.put("display_name", displayName);
+ requestData.put("parameters", parameters);
+ }
+
+ String jsonBody = jsonParser.getGson().toJson(requestData);
+ HttpResponse response =
+ httpClientWrapper.sendJsonRequestWithBackoff("/v3/translation_memories/import", jsonBody);
+ checkResponse(response, false, false);
+ return jsonParser.parseTranslationMemoryImport(response.getBody());
+ }
+
+ /**
+ * Uploads a TMX file to the upload URL of an import job, which starts processing.
+ *
+ * The upload URL is a pre-signed storage URL outside of the DeepL API, so the authentication
+ * key is not sent with this request.
+ *
+ * @param uploadUrl Upload URL returned by {@link
+ * DeepLClient#createTranslationMemoryImport(String, long, String, String)}.
+ * @param fileContent Content of the TMX file to upload.
+ * @param contentType MIME type of the file, which must match the content type declared when the
+ * import job was created.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while uploading the file.
+ */
+ public void uploadTranslationMemoryFile(String uploadUrl, byte[] fileContent, String contentType)
+ throws DeepLException, InterruptedException {
+ validateParameter("uploadUrl", uploadUrl);
+ validateParameter("contentType", contentType);
+ if (fileContent == null) {
+ throw new IllegalArgumentException("fileContent must not be null");
+ }
+ HttpResponse response =
+ httpClientWrapper.sendAssetPutRequestWithBackoff(uploadUrl, fileContent, contentType);
+ if (response.getCode() < 200 || response.getCode() >= 300) {
+ throw new DeepLException(
+ String.format(
+ "Error uploading translation memory file, HTTP status: %d", response.getCode()));
+ }
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#uploadTranslationMemoryFile(String, byte[], String)}
+ * but accepts the {@link TranslationMemoryImport} object holding the upload URL.
+ *
+ * @see DeepLClient#uploadTranslationMemoryFile(String, byte[], String)
+ */
+ public void uploadTranslationMemoryFile(
+ TranslationMemoryImport translationMemoryImport, byte[] fileContent, String contentType)
+ throws DeepLException, InterruptedException {
+ if (translationMemoryImport == null) {
+ throw new IllegalArgumentException("translationMemoryImport must not be null");
+ }
+ uploadTranslationMemoryFile(translationMemoryImport.getUploadUrl(), fileContent, contentType);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#uploadTranslationMemoryFile(TranslationMemoryImport,
+ * byte[], String)} but uses the default content type "application/xml".
+ *
+ * @see DeepLClient#uploadTranslationMemoryFile(TranslationMemoryImport, byte[], String)
+ */
+ public void uploadTranslationMemoryFile(
+ TranslationMemoryImport translationMemoryImport, byte[] fileContent)
+ throws DeepLException, InterruptedException {
+ uploadTranslationMemoryFile(
+ translationMemoryImport, fileContent, TRANSLATION_MEMORY_FILE_CONTENT_TYPE);
+ }
+
+ /**
+ * Creates an export job for the translation memory with the specified ID.
+ *
+ *
Poll {@link DeepLClient#getTranslationMemoryJob(String)} for the download URL of the
+ * exported TMX file. Use {@link DeepLClient#exportTranslationMemoryToFilepath(String, File)} to
+ * do both steps and write the file at once.
+ *
+ * @param translationMemoryId ID of the translation memory to export.
+ * @return {@link TranslationMemoryExport} object with the job ID, and whether the API reused a
+ * previously completed export.
+ * @throws NotFoundException If no translation memory with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public TranslationMemoryExport createTranslationMemoryExport(String translationMemoryId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("translationMemoryId", translationMemoryId);
+ String relativeUrl = String.format("/v3/translation_memories/%s/export", translationMemoryId);
+ HttpResponse response = httpClientWrapper.sendRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ // 200 means the API reused a previously completed export, 202 that it started a new one.
+ return jsonParser.parseTranslationMemoryExport(response.getBody(), response.getCode() == 200);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#createTranslationMemoryExport(String)} but accepts a
+ * {@link TranslationMemoryInfo} object.
+ *
+ * @see DeepLClient#createTranslationMemoryExport(String)
+ */
+ public TranslationMemoryExport createTranslationMemoryExport(
+ TranslationMemoryInfo translationMemory)
+ throws DeepLException, InterruptedException, NotFoundException {
+ return createTranslationMemoryExport(translationMemoryId(translationMemory));
+ }
+
+ /**
+ * Retrieves the status of the translation memory import or export job with the specified ID.
+ *
+ * @param jobId ID of the job to query.
+ * @return {@link TranslationMemoryJob} object with the current status of the job.
+ * @throws NotFoundException If no job with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public TranslationMemoryJob getTranslationMemoryJob(String jobId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("jobId", jobId);
+ String relativeUrl = String.format("/v3/translation_memories/jobs/%s", jobId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseTranslationMemoryJob(response.getBody());
+ }
+
+ /**
+ * Polls the translation memory job with the specified ID until it finishes, sleeping between
+ * requests, and returns the final status.
+ *
+ *
Note that an import job keeps reporting {@link
+ * TranslationMemoryJobResult.Status#AwaitingInput} for a while after its file has been uploaded,
+ * because the API detects the upload asynchronously. That status is therefore polled through like
+ * any other non-terminal one. A job whose file is never uploaded does not finish on its own, so
+ * use {@link DeepLClient#waitUntilTranslationMemoryJobDone(String, Duration)} to pass a timeout
+ * when that is a possibility.
+ *
+ * @param jobId ID of the job to wait for.
+ * @return {@link TranslationMemoryJob} object with the status of the finished job.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the job failed or expired, or any error occurs while communicating
+ * with the DeepL API.
+ */
+ public TranslationMemoryJob waitUntilTranslationMemoryJobDone(String jobId)
+ throws DeepLException, InterruptedException {
+ return waitUntilTranslationMemoryJobDone(jobId, null);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#waitUntilTranslationMemoryJobDone(String)} but gives
+ * up after the specified timeout.
+ *
+ * @param jobId ID of the job to wait for.
+ * @param timeout Maximum time to wait before throwing, or null to wait indefinitely.
+ * Note that this is not accurate to the millisecond, as the job status is only polled every 5
+ * seconds.
+ * @return {@link TranslationMemoryJob} object with the status of the finished job.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the timeout is exceeded, the job failed or expired, or any error
+ * occurs while communicating with the DeepL API.
+ * @see DeepLClient#waitUntilTranslationMemoryJobDone(String)
+ */
+ public TranslationMemoryJob waitUntilTranslationMemoryJobDone(
+ String jobId, @Nullable Duration timeout) throws DeepLException, InterruptedException {
+ long startTimeMillis = System.currentTimeMillis();
+ TranslationMemoryJob job = getTranslationMemoryJob(jobId);
+ while (!job.done()) {
+ // The API always returns exactly one result; an empty list would never reach a terminal
+ // status and the no-timeout overload would poll forever.
+ if (job.getResults().isEmpty()) {
+ throw new DeepLException("Translation memory job " + jobId + " returned no results");
+ }
+ if (timeout != null && System.currentTimeMillis() - startTimeMillis > timeout.toMillis()) {
+ throw new DeepLException(
+ String.format(
+ "Manual timeout of %ds exceeded for translation memory job", timeout.getSeconds()));
+ }
+ Thread.sleep(TRANSLATION_MEMORY_JOB_POLL_INTERVAL_MILLIS);
+ job = getTranslationMemoryJob(jobId);
+ }
+ if (!job.ok()) {
+ TranslationMemoryJobResult result = job.getResult();
+ String message =
+ (result != null && result.getErrorMessage() != null)
+ ? result.getErrorMessage()
+ : "Unknown error";
+ throw new DeepLException(message);
+ }
+ return job;
+ }
+
+ /**
+ * Downloads the TMX file of a completed export job to the specified output file.
+ *
+ *
The download URL is a pre-signed storage URL outside of the DeepL API, so the authentication
+ * key is not sent with this request.
+ *
+ * @param job Completed export job carrying the download URL.
+ * @param outputFile File to download the exported translation memory to.
+ * @throws IOException If the output path is occupied.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the job has no download URL, or any error occurs while downloading.
+ */
+ public void downloadTranslationMemoryExport(TranslationMemoryJob job, File outputFile)
+ throws DeepLException, IOException, InterruptedException {
+ // Checked before the try so the cleanup below only ever deletes a file this call created:
+ // otherwise the guard protecting an existing file would be what destroys it.
+ if (outputFile.exists()) {
+ throw new IOException("File already exists at output path");
+ }
+ try {
+ try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
+ downloadTranslationMemoryExport(job, outputStream);
+ }
+ } catch (Exception exception) {
+ outputFile.delete();
+ throw exception;
+ }
+ }
+
+ /**
+ * Downloads the TMX file of a completed export job to the specified output stream. The output
+ * stream is not closed.
+ *
+ * @param job Completed export job carrying the download URL.
+ * @param outputStream Stream to download the exported translation memory to.
+ * @throws IOException If an I/O error occurs.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the job has no download URL, or any error occurs while downloading.
+ * @see DeepLClient#downloadTranslationMemoryExport(TranslationMemoryJob, File)
+ */
+ public void downloadTranslationMemoryExport(TranslationMemoryJob job, OutputStream outputStream)
+ throws DeepLException, IOException, InterruptedException {
+ if (job == null) {
+ throw new IllegalArgumentException("job must not be null");
+ }
+ TranslationMemoryJobResult result = job.getResult();
+ String downloadUrl = (result == null) ? null : result.getDownloadUrl();
+ if (downloadUrl == null || downloadUrl.isEmpty()) {
+ throw new DeepLException(
+ "Translation memory export job has no download URL, it may not have completed yet");
+ }
+
+ try (HttpResponseStream response = httpClientWrapper.downloadAssetWithBackoff(downloadUrl)) {
+ if (response.getCode() < 200 || response.getCode() >= 300) {
+ throw new DeepLException(
+ String.format(
+ "Error downloading translation memory export, HTTP status: %d",
+ response.getCode()));
+ }
+ assert response.getBody() != null;
+ StreamUtil.transferTo(response.getBody(), outputStream);
+ }
+ }
+
+ /**
+ * Imports a TMX file as a new translation memory: creates the import job, uploads the file, and
+ * waits for processing to finish.
+ *
+ * @param inputFile TMX file to import.
+ * @param displayName Optional name for the resulting translation memory, defaults to the file
+ * name.
+ * @return {@link TranslationMemoryJob} object for the completed import; its result carries the ID
+ * of the new translation memory.
+ * @throws IOException If the input file cannot be read.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the import fails, or any error occurs while communicating with the
+ * DeepL API.
+ */
+ public TranslationMemoryJob importTranslationMemoryFromFilepath(
+ File inputFile, @Nullable String displayName)
+ throws DeepLException, IOException, InterruptedException {
+ return importTranslationMemoryFromFilepath(inputFile, displayName, null);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#importTranslationMemoryFromFilepath(File, String)} but
+ * gives up waiting for the import after the specified timeout.
+ *
+ * @param inputFile TMX file to import.
+ * @param displayName Optional name for the resulting translation memory, defaults to the file
+ * name.
+ * @param timeout Maximum time to wait for the import to finish, or null to wait
+ * indefinitely. Note that this is not accurate to the millisecond, as the job status is only
+ * polled every 5 seconds.
+ * @return {@link TranslationMemoryJob} object for the completed import; its result carries the ID
+ * of the new translation memory.
+ * @throws IOException If the input file cannot be read.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the timeout is exceeded, the import fails, or any error occurs while
+ * communicating with the DeepL API.
+ * @see DeepLClient#importTranslationMemoryFromFilepath(File, String)
+ */
+ public TranslationMemoryJob importTranslationMemoryFromFilepath(
+ File inputFile, @Nullable String displayName, @Nullable Duration timeout)
+ throws DeepLException, IOException, InterruptedException {
+ if (inputFile == null || !inputFile.exists()) {
+ throw new IllegalArgumentException("inputFile must be an existing file");
+ }
+ byte[] fileContent = Files.readAllBytes(inputFile.toPath());
+ TranslationMemoryImport translationMemoryImport =
+ createTranslationMemoryImport(inputFile.getName(), fileContent.length, null, displayName);
+ uploadTranslationMemoryFile(translationMemoryImport, fileContent);
+ return waitUntilTranslationMemoryJobDone(translationMemoryImport.getJobId(), timeout);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#importTranslationMemoryFromFilepath(File, String)} but
+ * lets the API name the translation memory after the file.
+ *
+ * @see DeepLClient#importTranslationMemoryFromFilepath(File, String)
+ */
+ public TranslationMemoryJob importTranslationMemoryFromFilepath(File inputFile)
+ throws DeepLException, IOException, InterruptedException {
+ return importTranslationMemoryFromFilepath(inputFile, null);
+ }
+
+ /**
+ * Exports a translation memory to a TMX file: creates the export job, waits for it to finish, and
+ * writes the result to the specified output file.
+ *
+ * @param translationMemoryId ID of the translation memory to export.
+ * @param outputFile File to write the exported translation memory to.
+ * @return {@link TranslationMemoryJob} object for the completed export.
+ * @throws IOException If the output path is occupied.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the export fails, or any error occurs while communicating with the
+ * DeepL API.
+ */
+ public TranslationMemoryJob exportTranslationMemoryToFilepath(
+ String translationMemoryId, File outputFile)
+ throws DeepLException, IOException, InterruptedException {
+ return exportTranslationMemoryToFilepath(translationMemoryId, outputFile, null);
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#exportTranslationMemoryToFilepath(String, File)} but
+ * gives up waiting for the export after the specified timeout.
+ *
+ * @param translationMemoryId ID of the translation memory to export.
+ * @param outputFile File to write the exported translation memory to.
+ * @param timeout Maximum time to wait for the export to finish, or null to wait
+ * indefinitely. Note that this is not accurate to the millisecond, as the job status is only
+ * polled every 5 seconds.
+ * @return {@link TranslationMemoryJob} object for the completed export.
+ * @throws IOException If the output path is occupied.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If the timeout is exceeded, the export fails, or any error occurs while
+ * communicating with the DeepL API.
+ * @see DeepLClient#exportTranslationMemoryToFilepath(String, File)
+ */
+ public TranslationMemoryJob exportTranslationMemoryToFilepath(
+ String translationMemoryId, File outputFile, @Nullable Duration timeout)
+ throws DeepLException, IOException, InterruptedException {
+ TranslationMemoryExport translationMemoryExport =
+ createTranslationMemoryExport(translationMemoryId);
+ TranslationMemoryJob job =
+ waitUntilTranslationMemoryJobDone(translationMemoryExport.getJobId(), timeout);
+ downloadTranslationMemoryExport(job, outputFile);
+ return job;
+ }
+
+ /**
+ * Functions the same as {@link DeepLClient#exportTranslationMemoryToFilepath(String, File)} but
+ * accepts a {@link TranslationMemoryInfo} object.
+ *
+ * @see DeepLClient#exportTranslationMemoryToFilepath(String, File)
+ */
+ public TranslationMemoryJob exportTranslationMemoryToFilepath(
+ TranslationMemoryInfo translationMemory, File outputFile)
+ throws DeepLException, IOException, InterruptedException {
+ return exportTranslationMemoryToFilepath(translationMemoryId(translationMemory), outputFile);
+ }
+
+ /** Extracts the ID of the given translation memory. */
+ private static String translationMemoryId(TranslationMemoryInfo translationMemory)
+ throws IllegalArgumentException {
+ if (translationMemory == null) {
+ throw new IllegalArgumentException("translationMemory must not be null");
+ }
+ return translationMemory.getTranslationMemoryId();
+ }
+
+ /**
+ * Creates a new style rule with the specified details and returns a {@link StyleRuleInfo} object
+ * with details about the newly created style rule.
+ *
+ * @param name User-defined name for the style rule.
+ * @param language Language code for the style rule (e.g. "en", "de").
+ * @param configuredRules Optional configured rules for the style rule.
+ * @param customInstructions Optional list of custom instructions for the style rule.
+ * @return {@link StyleRuleInfo} object with details about the newly created style rule.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public StyleRuleInfo createStyleRule(
+ String name,
+ String language,
+ @Nullable ConfiguredRules configuredRules,
+ @Nullable List customInstructions)
+ throws DeepLException, InterruptedException {
+ validateParameter("name", name);
+ validateParameter("language", language);
+ Map requestData = new HashMap<>();
+ requestData.put("name", name);
+ requestData.put("language", language);
+ if (configuredRules != null) {
+ requestData.put("configured_rules", configuredRules);
+ }
+ if (customInstructions != null) {
+ requestData.put("custom_instructions", customInstructions);
+ }
+ String jsonBody = jsonParser.getGson().toJson(requestData);
+ HttpResponse response =
+ httpClientWrapper.sendJsonRequestWithBackoff("/v3/style_rules", jsonBody);
+ checkResponse(response, false, false);
+ return jsonParser.parseStyleRuleInfo(response.getBody());
+ }
+
+ /**
+ * Retrieves information about the style rule with the specified ID and returns a {@link
+ * StyleRuleInfo} object containing details.
+ *
+ * @param styleId ID of the style rule to retrieve.
+ * @return {@link StyleRuleInfo} object with details about the specified style rule.
+ * @throws NotFoundException If no style rule with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public StyleRuleInfo getStyleRule(String styleId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ String relativeUrl = String.format("/v3/style_rules/%s", styleId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseStyleRuleInfo(response.getBody());
+ }
+
+ /**
+ * Updates the name of the style rule with the specified ID and returns the updated {@link
+ * StyleRuleInfo} object.
+ *
+ * @param styleId ID of the style rule to update.
+ * @param name New name for the style rule.
+ * @return {@link StyleRuleInfo} object with updated details about the style rule.
+ * @throws NotFoundException If no style rule with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public StyleRuleInfo updateStyleRuleName(String styleId, String name)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ validateParameter("name", name);
+ String relativeUrl = String.format("/v3/style_rules/%s", styleId);
+ Map requestData = new HashMap<>();
+ requestData.put("name", name);
+ String jsonBody = jsonParser.getGson().toJson(requestData);
+ HttpResponse response =
+ httpClientWrapper.sendJsonPatchRequestWithBackoff(relativeUrl, jsonBody);
+ checkResponse(response, false, false);
+ return jsonParser.parseStyleRuleInfo(response.getBody());
+ }
+
+ /**
+ * Deletes the style rule with the specified ID.
+ *
+ * @param styleId ID of the style rule to delete.
+ * @throws NotFoundException If no style rule with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public void deleteStyleRule(String styleId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ String relativeUrl = String.format("/v3/style_rules/%s", styleId);
+ HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ }
+
+ /**
+ * Replaces the configured rules of the style rule with the specified ID and returns the updated
+ * {@link StyleRuleInfo} object.
+ *
+ * @param styleId ID of the style rule to update.
+ * @param configuredRules The new configured rules to set for the style rule.
+ * @return {@link StyleRuleInfo} object with updated details about the style rule.
+ * @throws NotFoundException If no style rule with the given ID is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public StyleRuleInfo updateStyleRuleConfiguredRules(
+ String styleId, ConfiguredRules configuredRules)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ if (configuredRules == null) {
+ throw new IllegalArgumentException("configuredRules must not be null");
+ }
+ String relativeUrl = String.format("/v3/style_rules/%s/configured_rules", styleId);
+ String jsonBody = jsonParser.getGson().toJson(configuredRules);
+ HttpResponse response =
+ httpClientWrapper.sendJsonRequestWithBackoff("PUT", relativeUrl, jsonBody);
+ checkResponse(response, false, false);
+ return jsonParser.parseStyleRuleInfo(response.getBody());
+ }
+
+ /**
+ * Creates a new custom instruction for the style rule with the specified ID and returns the
+ * created {@link CustomInstruction} object.
+ *
+ * @param styleId ID of the style rule to add the custom instruction to.
+ * @param label Label for the custom instruction.
+ * @param prompt Prompt text for the custom instruction.
+ * @param sourceLanguage Optional source language code for the custom instruction.
+ * @return {@link CustomInstruction} object with details about the newly created custom
+ * instruction.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public CustomInstruction createStyleRuleCustomInstruction(
+ String styleId, String label, String prompt, @Nullable String sourceLanguage)
+ throws DeepLException, InterruptedException {
+ validateParameter("styleId", styleId);
+ validateParameter("label", label);
+ validateParameter("prompt", prompt);
+ String relativeUrl = String.format("/v3/style_rules/%s/custom_instructions", styleId);
+ Map requestData = new HashMap<>();
+ requestData.put("label", label);
+ requestData.put("prompt", prompt);
+ if (sourceLanguage != null) {
+ requestData.put("source_language", sourceLanguage);
+ }
+ String jsonBody = jsonParser.getGson().toJson(requestData);
+ HttpResponse response = httpClientWrapper.sendJsonRequestWithBackoff(relativeUrl, jsonBody);
+ checkResponse(response, false, false);
+ return jsonParser.parseCustomInstruction(response.getBody());
+ }
+
+ /**
+ * Retrieves information about the custom instruction with the specified ID within the style rule
+ * with the specified ID and returns a {@link CustomInstruction} object containing details.
+ *
+ * @param styleId ID of the style rule containing the custom instruction.
+ * @param instructionId ID of the custom instruction to retrieve.
+ * @return {@link CustomInstruction} object with details about the specified custom instruction.
+ * @throws NotFoundException If no style rule or custom instruction with the given IDs is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public CustomInstruction getStyleRuleCustomInstruction(String styleId, String instructionId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ validateParameter("instructionId", instructionId);
+ String relativeUrl =
+ String.format("/v3/style_rules/%s/custom_instructions/%s", styleId, instructionId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ return jsonParser.parseCustomInstruction(response.getBody());
+ }
+
+ /**
+ * Updates the custom instruction with the specified ID within the style rule with the specified
+ * ID and returns the updated {@link CustomInstruction} object.
+ *
+ * @param styleId ID of the style rule containing the custom instruction.
+ * @param instructionId ID of the custom instruction to update.
+ * @param label New label for the custom instruction.
+ * @param prompt New prompt text for the custom instruction.
+ * @param sourceLanguage Optional new source language code for the custom instruction.
+ * @return {@link CustomInstruction} object with updated details about the custom instruction.
+ * @throws NotFoundException If no style rule or custom instruction with the given IDs is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public CustomInstruction updateStyleRuleCustomInstruction(
+ String styleId,
+ String instructionId,
+ String label,
+ String prompt,
+ @Nullable String sourceLanguage)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ validateParameter("instructionId", instructionId);
+ validateParameter("label", label);
+ validateParameter("prompt", prompt);
+ String relativeUrl =
+ String.format("/v3/style_rules/%s/custom_instructions/%s", styleId, instructionId);
+ Map requestData = new HashMap<>();
+ requestData.put("label", label);
+ requestData.put("prompt", prompt);
+ if (sourceLanguage != null) {
+ requestData.put("source_language", sourceLanguage);
+ }
+ String jsonBody = jsonParser.getGson().toJson(requestData);
+ HttpResponse response =
+ httpClientWrapper.sendJsonRequestWithBackoff("PUT", relativeUrl, jsonBody);
+ checkResponse(response, false, false);
+ return jsonParser.parseCustomInstruction(response.getBody());
+ }
+
+ /**
+ * Deletes the custom instruction with the specified ID from the style rule with the specified ID.
+ *
+ * @param styleId ID of the style rule containing the custom instruction.
+ * @param instructionId ID of the custom instruction to delete.
+ * @throws NotFoundException If no style rule or custom instruction with the given IDs is found.
+ * @throws InterruptedException If the thread is interrupted during execution of this function.
+ * @throws DeepLException If any error occurs while communicating with the DeepL API.
+ */
+ public void deleteStyleRuleCustomInstruction(String styleId, String instructionId)
+ throws DeepLException, InterruptedException, NotFoundException {
+ validateParameter("styleId", styleId);
+ validateParameter("instructionId", instructionId);
+ String relativeUrl =
+ String.format("/v3/style_rules/%s/custom_instructions/%s", styleId, instructionId);
+ HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, false);
+ }
+
+ /** Creates a glossary with given details. */
+ private MultilingualGlossaryInfo createGlossaryFromCsvInternal(
+ String name, String sourceLanguageCode, String targetLanguageCode, String entries)
+ throws DeepLException, InterruptedException {
+ ArrayList> params =
+ createGlossaryDictionariesHttpParams(
+ sourceLanguageCode, targetLanguageCode, entries, "csv");
+ params.add(new KeyValuePair<>("name", name));
+ HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v3/glossaries", params);
+ checkResponse(response, false, false);
+ return jsonParser.parseMultilingualGlossaryInfo(response.getBody());
+ }
+
+ /**
+ * Gets the entries in the glossary with the specified ID for the given source and target
+ * languages
+ */
+ private MultilingualGlossaryDictionaryInfo replaceGlossaryDictionaryInternal(
+ String glossaryId,
+ String sourceLanguageCode,
+ String targetLanguageCode,
+ String entries,
+ String entriesFormat)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ validateParameter("glossaryId", glossaryId);
+ validateParameter("sourceLanguageCode", sourceLanguageCode);
+ validateParameter("targetLanguageCode", targetLanguageCode);
+ validateParameter("entries", entries);
+
+ ArrayList> bodyParams = new ArrayList<>();
+ bodyParams.add(new KeyValuePair<>("source_lang", sourceLanguageCode));
+ bodyParams.add(new KeyValuePair<>("target_lang", targetLanguageCode));
+ bodyParams.add(new KeyValuePair<>("entries", entries));
+ bodyParams.add(new KeyValuePair<>("entries_format", entriesFormat));
+
+ String relativeUrl = String.format("/v3/glossaries/%s/dictionaries", glossaryId);
+ HttpResponse response = httpClientWrapper.sendPutRequestWithBackoff(relativeUrl, bodyParams);
+ checkResponse(response, false, true);
+ return jsonParser.parseMultilingualGlossaryDictionaryInfo(response.getBody());
+ }
+
+ /**
+ * Gets the entries in the glossary with the specified ID for the given source and target
+ * languages
+ */
+ private MultilingualGlossaryInfo updateGlossaryDictionaryInternal(
+ String glossaryId,
+ String sourceLanguageCode,
+ String targetLanguageCode,
+ String entries,
+ String entriesFormat)
+ throws DeepLException, IllegalArgumentException, InterruptedException {
+ validateParameter("glossaryId", glossaryId);
+ validateParameter("sourceLanguageCode", sourceLanguageCode);
+ validateParameter("targetLanguageCode", targetLanguageCode);
+ validateParameter("entries", entries);
+
+ ArrayList> bodyParams =
+ createGlossaryDictionariesHttpParams(
+ sourceLanguageCode, targetLanguageCode, entries, entriesFormat);
+ String relativeUrl = String.format("/v3/glossaries/%s", glossaryId);
+ HttpResponse response = httpClientWrapper.sendPatchRequestWithBackoff(relativeUrl, bodyParams);
+ checkResponse(response, false, true);
+ return jsonParser.parseMultilingualGlossaryInfo(response.getBody());
+ }
+
+ /** Creates query string for the source and target languages */
+ private String createLanguageQueryParams(String sourceLanguageCode, String targetLanguageCode)
+ throws IllegalArgumentException, DeepLException {
+ validateParameter("sourceLanguageCode", sourceLanguageCode);
+ validateParameter("targetLanguageCode", targetLanguageCode);
+ try {
+ return "?"
+ + String.join(
+ "&",
+ String.format(
+ "source_lang=%s",
+ URLEncoder.encode(sourceLanguageCode, StandardCharsets.UTF_8.name())),
+ String.format(
+ "target_lang=%s",
+ URLEncoder.encode(targetLanguageCode, StandardCharsets.UTF_8.name())));
+ } catch (UnsupportedEncodingException exception) {
+ throw new DeepLException("Error while URL-encoding request", exception);
+ }
+ }
+
+ /** Creates the query string for the given parameters, including the leading "?" if non-empty. */
+ private static String createQueryString(List> queryParams) {
+ if (queryParams.isEmpty()) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder("?");
+ for (int i = 0; i < queryParams.size(); i++) {
+ if (i > 0) {
+ sb.append("&");
+ }
+ KeyValuePair param = queryParams.get(i);
+ try {
+ sb.append(encodeQueryComponent(param.getKey()))
+ .append("=")
+ .append(encodeQueryComponent(param.getValue()));
+ } catch (java.io.UnsupportedEncodingException e) {
+ throw new RuntimeException("UTF-8 encoding not supported", e);
+ }
+ }
+ return sb.toString();
+ }
+
+ // URLEncoder encodes a space as "+", which is correct for a form body but not for a URI query
+ // string. Any literal "+" is already escaped as %2B by URLEncoder, so every remaining "+" is a
+ // space.
+ private static String encodeQueryComponent(String value)
+ throws java.io.UnsupportedEncodingException {
+ return URLEncoder.encode(value, StandardCharsets.UTF_8.name()).replace("+", "%20");
+ }
+
+ private void validateParameter(String paramName, String value) throws IllegalArgumentException {
+ if (value == null || value.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format("Parameter %s must not be empty", paramName));
+ }
+ }
+
+ protected static ArrayList> createWriteHttpParams(
+ List texts, @Nullable String targetLang, @Nullable TextRephraseOptions options) {
+ targetLang = LanguageCode.standardize(targetLang);
+ checkValidLanguages(null, targetLang);
+
+ ArrayList> params = new ArrayList<>();
+ if (targetLang != null) {
+ params.add(new KeyValuePair<>("target_lang", targetLang));
+ }
+ if (options != null && options.getWritingStyle() != null) {
+ params.add(new KeyValuePair<>("writing_style", options.getWritingStyle()));
+ }
+ if (options != null && options.getTone() != null) {
+ params.add(new KeyValuePair<>("tone", options.getTone()));
+ }
+
+ texts.forEach(
+ (text) -> {
+ if (text.isEmpty()) throw new IllegalArgumentException("text must not be empty");
+ params.add(new KeyValuePair<>("text", text));
+ });
+
+ return params;
+ }
+
+ protected static ArrayList> createGlossaryHttpParams(
+ String name, List glossaryDicts) {
+ ArrayList> bodyParams = new ArrayList<>();
+ bodyParams.add(new KeyValuePair<>("name", name));
+ for (int i = 0; i < glossaryDicts.size(); i++) {
+ bodyParams.add(
+ new KeyValuePair<>(
+ String.format("dictionaries[%d].source_lang", i),
+ glossaryDicts.get(i).getSourceLanguageCode()));
+ bodyParams.add(
+ new KeyValuePair<>(
+ String.format("dictionaries[%d].target_lang", i),
+ glossaryDicts.get(i).getTargetLanguageCode()));
+ bodyParams.add(
+ new KeyValuePair<>(
+ String.format("dictionaries[%d].entries", i),
+ glossaryDicts.get(i).getEntries().toTsv()));
+ bodyParams.add(
+ new KeyValuePair<>(String.format("dictionaries[%d].entries_format", i), "tsv"));
+ }
+ return bodyParams;
+ }
+
+ protected static ArrayList> createGlossaryDictionariesHttpParams(
+ String sourceLanguageCode, String targetLanguageCode, String entries, String entriesFormat) {
+ ArrayList> bodyParams = new ArrayList<>();
+ bodyParams.add(new KeyValuePair<>("dictionaries[0].source_lang", sourceLanguageCode));
+ bodyParams.add(new KeyValuePair<>("dictionaries[0].target_lang", targetLanguageCode));
+ bodyParams.add(new KeyValuePair<>("dictionaries[0].entries", entries));
+ bodyParams.add(new KeyValuePair<>("dictionaries[0].entries_format", entriesFormat));
+
+ return bodyParams;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/DeepLClientOptions.java b/deepl-java/src/main/java/com/deepl/api/DeepLClientOptions.java
new file mode 100644
index 0000000..1f2a6b3
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/DeepLClientOptions.java
@@ -0,0 +1,28 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import org.jetbrains.annotations.Nullable;
+
+/** {@inheritDoc} */
+@SuppressWarnings("deprecation")
+public class DeepLClientOptions extends TranslatorOptions {
+ /**
+ * Set the version of the DeepL API to use. By default, this value is
+ * DeepLApiVersion.VERSION_2 and the most recent DeepL API version is used. Note that older
+ * API versions like DeepLApiVersion.VERSION_1 might not support all features of the
+ * more modern API (eg. document translation is v2-only), and that not all API subscriptions have
+ * access to one or the other API version. If in doubt, always use the most recent API version you
+ * have access to.
+ */
+ public DeepLClientOptions setApiVersion(DeepLApiVersion apiVersion) {
+ this.apiVersion = apiVersion;
+ return this;
+ }
+
+ /** Gets the current API version. */
+ public @Nullable DeepLApiVersion getApiVersion() {
+ return apiVersion;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/DocumentTranslationOptions.java b/deepl-java/src/main/java/com/deepl/api/DocumentTranslationOptions.java
index c143a8a..b95e5d1 100644
--- a/deepl-java/src/main/java/com/deepl/api/DocumentTranslationOptions.java
+++ b/deepl-java/src/main/java/com/deepl/api/DocumentTranslationOptions.java
@@ -3,6 +3,10 @@
// license that can be found in the LICENSE file.
package com.deepl.api;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Options to control document translation behaviour. These options may be provided to {@link
* Translator#translateDocument} overloads.
@@ -13,9 +17,13 @@
* .setFormality(Formality.Less).setGlossaryId("f63c02c5-f056-..");
*
*/
-public class DocumentTranslationOptions {
+public class DocumentTranslationOptions extends BaseRequestOptions {
private Formality formality;
private String glossaryId;
+ private List glossaryIds;
+ private String styleId;
+ private String translationMemoryId;
+ private Integer translationMemoryThreshold;
/**
* Sets whether translations should lean toward formal or informal language. This option is only
@@ -43,7 +51,7 @@ public DocumentTranslationOptions setGlossaryId(String glossaryId) {
* Sets the glossary to use with the translation. By default, this value is null and
* no glossary is used.
*/
- public DocumentTranslationOptions setGlossary(GlossaryInfo glossary) {
+ public DocumentTranslationOptions setGlossary(IGlossary glossary) {
return setGlossary(glossary.getGlossaryId());
}
@@ -56,6 +64,102 @@ public DocumentTranslationOptions setGlossary(String glossaryId) {
return this;
}
+ /**
+ * Sets the list of glossary IDs to use with the translation, up to a maximum of 5. Glossaries are
+ * applied in the order provided (first match wins). By default, this value is null
+ * and no glossaries are used. This option requires a source language to be set and cannot be
+ * combined with {@link #setGlossaryId} or {@link #setGlossary}.
+ */
+ public DocumentTranslationOptions setGlossaryIds(List glossaryIds) {
+ this.glossaryIds = glossaryIds;
+ return this;
+ }
+
+ /**
+ * Sets the list of glossary IDs to use with the translation, up to a maximum of 5. Glossaries are
+ * applied in the order provided (first match wins). By default, this value is null
+ * and no glossaries are used. This option requires a source language to be set and cannot be
+ * combined with {@link #setGlossaryId} or {@link #setGlossary}.
+ */
+ public DocumentTranslationOptions setGlossaryIds(String... glossaryIds) {
+ this.glossaryIds = Arrays.asList(glossaryIds);
+ return this;
+ }
+
+ /**
+ * Sets the list of glossaries to use with the translation, up to a maximum of 5. Glossaries are
+ * applied in the order provided (first match wins). By default, this value is null
+ * and no glossaries are used. This option requires a source language to be set and cannot be
+ * combined with {@link #setGlossaryId} or {@link #setGlossary}.
+ */
+ public DocumentTranslationOptions setGlossaries(IGlossary... glossaries) {
+ List ids = new ArrayList<>();
+ for (IGlossary glossary : glossaries) {
+ if (glossary == null) {
+ throw new IllegalArgumentException("glossaries must not contain null");
+ }
+ ids.add(glossary.getGlossaryId());
+ }
+ this.glossaryIds = ids;
+ return this;
+ }
+
+ /**
+ * Sets the ID of a style rule to use with the translation. By default, this value is
+ * null and no style rule is used.
+ */
+ public DocumentTranslationOptions setStyleId(String styleId) {
+ this.styleId = styleId;
+ return this;
+ }
+
+ /**
+ * Sets the style rule to use with the translation. By default, this value is null
+ * and no style rule is used.
+ */
+ public DocumentTranslationOptions setStyleRule(StyleRuleInfo styleRule) {
+ if (styleRule == null) {
+ throw new IllegalArgumentException("styleRule must not be null");
+ }
+ return setStyleId(styleRule.getStyleId());
+ }
+
+ /**
+ * Sets the ID of a translation memory to use with the translation. By default, this value is
+ * null and no translation memory is used.
+ */
+ public DocumentTranslationOptions setTranslationMemoryId(String translationMemoryId) {
+ this.translationMemoryId = translationMemoryId;
+ return this;
+ }
+
+ /**
+ * Sets the translation memory to use with the translation. By default, this value is null
+ * and no translation memory is used.
+ */
+ public DocumentTranslationOptions setTranslationMemory(TranslationMemoryInfo translationMemory) {
+ if (translationMemory == null) {
+ throw new IllegalArgumentException("translationMemory must not be null");
+ }
+ return setTranslationMemoryId(translationMemory.getTranslationMemoryId());
+ }
+
+ /**
+ * Sets the threshold for translation memory matches. By default, this value is null
+ * and the API default threshold is used. Note: a translation memory ID must also be set via
+ * {@link #setTranslationMemoryId} or {@link #setTranslationMemory}, otherwise an error will be
+ * thrown at translation time.
+ */
+ public DocumentTranslationOptions setTranslationMemoryThreshold(
+ Integer translationMemoryThreshold) {
+ if (translationMemoryThreshold != null
+ && (translationMemoryThreshold < 0 || translationMemoryThreshold > 100)) {
+ throw new IllegalArgumentException("translationMemoryThreshold must be between 0 and 100");
+ }
+ this.translationMemoryThreshold = translationMemoryThreshold;
+ return this;
+ }
+
/** Gets the current formality setting. */
public Formality getFormality() {
return formality;
@@ -65,4 +169,24 @@ public Formality getFormality() {
public String getGlossaryId() {
return glossaryId;
}
+
+ /** Gets the current list of glossary IDs. */
+ public List getGlossaryIds() {
+ return glossaryIds;
+ }
+
+ /** Gets the current style rule ID. */
+ public String getStyleId() {
+ return styleId;
+ }
+
+ /** Gets the current translation memory ID. */
+ public String getTranslationMemoryId() {
+ return translationMemoryId;
+ }
+
+ /** Gets the current translation memory threshold. */
+ public Integer getTranslationMemoryThreshold() {
+ return translationMemoryThreshold;
+ }
}
diff --git a/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java b/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java
index 15022b9..34c0b07 100644
--- a/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java
+++ b/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java
@@ -8,7 +8,7 @@
import org.jetbrains.annotations.*;
/** Information about a glossary, excluding the entry list. */
-public class GlossaryInfo {
+public class GlossaryInfo implements IGlossary {
@SerializedName(value = "glossary_id")
private final String glossaryId;
diff --git a/deepl-java/src/main/java/com/deepl/api/HttpClientWrapper.java b/deepl-java/src/main/java/com/deepl/api/HttpClientWrapper.java
index b589be0..d67cd9d 100644
--- a/deepl-java/src/main/java/com/deepl/api/HttpClientWrapper.java
+++ b/deepl-java/src/main/java/com/deepl/api/HttpClientWrapper.java
@@ -9,6 +9,14 @@
import java.net.*;
import java.time.*;
import java.util.*;
+import org.apache.http.HttpHost;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPatch;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.*;
/**
@@ -21,6 +29,7 @@ class HttpClientWrapper {
private static final String GET = "GET";
private static final String POST = "POST";
private static final String DELETE = "DELETE";
+ private static final String PUT = "PUT";
private final String serverUrl;
private final Map headers;
private final Duration minTimeout;
@@ -45,9 +54,16 @@ public HttpResponse sendGetRequestWithBackoff(String relativeUrl)
return sendRequestWithBackoff(GET, relativeUrl, null).toStringResponse();
}
+ public HttpResponse sendDeleteRequestWithBackoff(
+ String relativeUrl, @Nullable Iterable> params)
+ throws InterruptedException, DeepLException {
+ HttpContent content = HttpContent.buildFormURLEncodedContent(params);
+ return sendRequestWithBackoff(DELETE, relativeUrl, content).toStringResponse();
+ }
+
public HttpResponse sendDeleteRequestWithBackoff(String relativeUrl)
throws InterruptedException, DeepLException {
- return sendRequestWithBackoff(DELETE, relativeUrl, null).toStringResponse();
+ return sendDeleteRequestWithBackoff(relativeUrl, null);
}
public HttpResponse sendRequestWithBackoff(String relativeUrl)
@@ -62,6 +78,102 @@ public HttpResponse sendRequestWithBackoff(
return sendRequestWithBackoff(POST, relativeUrl, content).toStringResponse();
}
+ public HttpResponse sendPutRequestWithBackoff(
+ String relativeUrl, @Nullable Iterable> params)
+ throws InterruptedException, DeepLException {
+ HttpContent content = HttpContent.buildFormURLEncodedContent(params);
+ return sendRequestWithBackoff(PUT, relativeUrl, content).toStringResponse();
+ }
+
+ public HttpResponse sendJsonRequestWithBackoff(String relativeUrl, String jsonBody)
+ throws InterruptedException, DeepLException {
+ HttpContent content = HttpContent.buildJsonContent(jsonBody);
+ return sendRequestWithBackoff(POST, relativeUrl, content).toStringResponse();
+ }
+
+ public HttpResponse sendJsonRequestWithBackoff(String method, String relativeUrl, String jsonBody)
+ throws InterruptedException, DeepLException {
+ HttpContent content = HttpContent.buildJsonContent(jsonBody);
+ return sendRequestWithBackoff(method, relativeUrl, content).toStringResponse();
+ }
+
+ public HttpResponse sendJsonPatchRequestWithBackoff(String relativeUrl, String jsonBody)
+ throws InterruptedException, DeepLException {
+ return sendPatchRequestWithBackoff(relativeUrl, HttpContent.buildJsonContent(jsonBody));
+ }
+
+ public HttpResponse sendPatchRequestWithBackoff(
+ String relativeUrl, @Nullable Iterable> params)
+ throws InterruptedException, DeepLException {
+ HttpContent content = HttpContent.buildFormURLEncodedContent(params);
+ return sendPatchRequestWithBackoff(relativeUrl, content);
+ }
+
+ private HttpResponse sendPatchRequestWithBackoff(String relativeUrl, HttpContent content)
+ throws InterruptedException, DeepLException {
+ BackoffTimer backoffTimer = new BackoffTimer(this.minTimeout);
+ while (true) {
+ try {
+ HttpResponse response = sendPatchRequest(relativeUrl, content, backoffTimer);
+ if (backoffTimer.getNumRetries() >= this.maxRetries) {
+ return response;
+ } else if (response.getCode() != 429 && response.getCode() < 500) {
+ return response;
+ }
+ } catch (ConnectionException exception) {
+ if (!exception.getShouldRetry() || backoffTimer.getNumRetries() >= this.maxRetries) {
+ throw exception;
+ }
+ }
+ backoffTimer.sleepUntilRetry();
+ }
+ }
+
+ private HttpResponse sendPatchRequest(
+ String relativeUrl, HttpContent content, BackoffTimer backoffTimer) throws DeepLException {
+ HttpClientBuilder builder = HttpClients.custom();
+ if (proxy != null) {
+ InetSocketAddress addr = (InetSocketAddress) proxy.address();
+ builder.setProxy(new HttpHost(addr.getHostName(), addr.getPort()));
+ }
+ try (CloseableHttpClient httpClient = builder.build()) {
+ HttpPatch request = new HttpPatch(serverUrl + relativeUrl);
+
+ // Set timeouts
+ RequestConfig requestConfig =
+ RequestConfig.custom()
+ .setConnectTimeout((int) backoffTimer.getTimeoutMillis())
+ .setSocketTimeout((int) backoffTimer.getTimeoutMillis())
+ .build();
+ request.setConfig(requestConfig);
+
+ // Set headers
+ for (Map.Entry entry : this.headers.entrySet()) {
+ request.setHeader(entry.getKey(), entry.getValue());
+ }
+
+ request.setHeader("Content-Type", content.getContentType());
+ request.setEntity(new ByteArrayEntity(content.getContent()));
+
+ // Execute the request
+ org.apache.http.HttpResponse response = httpClient.execute(request);
+
+ // Get the response stream
+ InputStream responseStream =
+ (response.getStatusLine().getStatusCode() >= 200
+ && response.getStatusLine().getStatusCode() < 400)
+ ? response.getEntity().getContent()
+ : new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
+
+ return new HttpResponseStream(response.getStatusLine().getStatusCode(), responseStream)
+ .toStringResponse();
+ } catch (SocketTimeoutException e) {
+ throw new ConnectionException(e.getMessage(), true, e);
+ } catch (RuntimeException | IOException e) {
+ throw new ConnectionException(e.getMessage(), false, e);
+ }
+ }
+
public HttpResponseStream downloadWithBackoff(
String relativeUrl, @Nullable Iterable> params)
throws InterruptedException, DeepLException {
@@ -92,15 +204,45 @@ public HttpResponse uploadWithBackoff(
return sendRequestWithBackoff(POST, relativeUrl, content).toStringResponse();
}
+ /**
+ * Uploads content to an absolute URL outside of the DeepL API, for example a pre-signed storage
+ * URL returned by a translation memory import job. The DeepL headers, in particular the
+ * Authorization header, are deliberately not sent to third-party storage.
+ */
+ public HttpResponse sendAssetPutRequestWithBackoff(
+ String url, byte[] fileContent, String contentType)
+ throws InterruptedException, DeepLException {
+ HttpContent content = HttpContent.buildRawContent(contentType, fileContent);
+ return sendRequestToUrlWithBackoff(PUT, url, content, Collections.emptyMap())
+ .toStringResponse();
+ }
+
+ /**
+ * Downloads content from an absolute URL outside of the DeepL API, for example a pre-signed
+ * storage URL returned by a completed translation memory export job. The DeepL headers, in
+ * particular the Authorization header, are deliberately not sent to third-party storage.
+ */
+ public HttpResponseStream downloadAssetWithBackoff(String url)
+ throws InterruptedException, DeepLException {
+ return sendRequestToUrlWithBackoff(GET, url, null, Collections.emptyMap());
+ }
+
// Sends a request with exponential backoff
private HttpResponseStream sendRequestWithBackoff(
String method, String relativeUrl, HttpContent content)
throws InterruptedException, DeepLException {
+ return sendRequestToUrlWithBackoff(method, serverUrl + relativeUrl, content, this.headers);
+ }
+
+ // Sends a request to an absolute URL with exponential backoff
+ private HttpResponseStream sendRequestToUrlWithBackoff(
+ String method, String url, @Nullable HttpContent content, Map requestHeaders)
+ throws InterruptedException, DeepLException {
BackoffTimer backoffTimer = new BackoffTimer(this.minTimeout);
while (true) {
try {
HttpResponseStream response =
- sendRequest(method, serverUrl + relativeUrl, backoffTimer.getTimeoutMillis(), content);
+ sendRequest(method, url, backoffTimer.getTimeoutMillis(), content, requestHeaders);
if (backoffTimer.getNumRetries() >= this.maxRetries) {
return response;
} else if (response.getCode() != 429 && response.getCode() < 500) {
@@ -117,7 +259,11 @@ private HttpResponseStream sendRequestWithBackoff(
}
private HttpResponseStream sendRequest(
- String method, String urlString, long timeoutMs, HttpContent content)
+ String method,
+ String urlString,
+ long timeoutMs,
+ @Nullable HttpContent content,
+ Map requestHeaders)
throws ConnectionException {
try {
URL url = new URL(urlString);
@@ -129,7 +275,7 @@ private HttpResponseStream sendRequest(
connection.setReadTimeout((int) timeoutMs);
connection.setUseCaches(false);
- for (Map.Entry entry : this.headers.entrySet()) {
+ for (Map.Entry entry : requestHeaders.entrySet()) {
connection.setRequestProperty(entry.getKey(), entry.getValue());
}
diff --git a/deepl-java/src/main/java/com/deepl/api/IGlossary.java b/deepl-java/src/main/java/com/deepl/api/IGlossary.java
new file mode 100644
index 0000000..a94d7fe
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/IGlossary.java
@@ -0,0 +1,10 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+/** Interface representing a glossary. */
+public interface IGlossary {
+ /** @return Unique ID assigned to the glossary. */
+ String getGlossaryId();
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/LanguageCode.java b/deepl-java/src/main/java/com/deepl/api/LanguageCode.java
index 6fa2028..b4c9448 100644
--- a/deepl-java/src/main/java/com/deepl/api/LanguageCode.java
+++ b/deepl-java/src/main/java/com/deepl/api/LanguageCode.java
@@ -113,6 +113,9 @@ public class LanguageCode {
/** Turkish language code, may be used as source or target language. */
public static final String Turkish = "tr";
+ /** Ukrainian language code, may be used as source or target language. */
+ public static final String Ukrainian = "uk";
+
/** Chinese language code, may be used as source or target language. */
public static final String Chinese = "zh";
diff --git a/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryDictionaryEntries.java b/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryDictionaryEntries.java
new file mode 100644
index 0000000..14925d2
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryDictionaryEntries.java
@@ -0,0 +1,41 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+/** Stores the entries of a glossary. */
+public class MultilingualGlossaryDictionaryEntries {
+ private final String sourceLanguageCode;
+ private final String targetLanguageCode;
+ private final GlossaryEntries entries;
+
+ /**
+ * Initializes a new {@link MultilingualGlossaryDictionaryInfo} containing information about a
+ * glossary dictionary.
+ *
+ * @param sourceLanguageCode the source language for this dictionary
+ * @param targetLanguageCode the target language for this dictionary
+ * @param entries the entries in this dictionary
+ */
+ public MultilingualGlossaryDictionaryEntries(
+ String sourceLanguageCode, String targetLanguageCode, GlossaryEntries entries) {
+ this.sourceLanguageCode = sourceLanguageCode;
+ this.targetLanguageCode = targetLanguageCode;
+ this.entries = entries;
+ }
+
+ /** @return the source language code */
+ public String getSourceLanguageCode() {
+ return this.sourceLanguageCode;
+ }
+
+ /** @return the target language code */
+ public String getTargetLanguageCode() {
+ return this.targetLanguageCode;
+ }
+
+ /** @return the entry count */
+ public GlossaryEntries getEntries() {
+ return this.entries;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryDictionaryInfo.java b/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryDictionaryInfo.java
new file mode 100644
index 0000000..4c04aff
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryDictionaryInfo.java
@@ -0,0 +1,48 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.SerializedName;
+
+/** Stores the entries of a glossary. */
+public class MultilingualGlossaryDictionaryInfo {
+ @SerializedName(value = "source_lang")
+ private final String sourceLanguageCode;
+
+ @SerializedName(value = "target_lang")
+ private final String targetLanguageCode;
+
+ @SerializedName(value = "entry_count")
+ private final long entryCount;
+
+ /**
+ * Initializes a new {@link MultilingualGlossaryDictionaryInfo} containing information about a
+ * glossary dictionary.
+ *
+ * @param sourceLanguageCode the source language for this dictionary
+ * @param targetLanguageCode the target language for this dictionary
+ * @param entryCount the number of entries in this dictionary
+ */
+ public MultilingualGlossaryDictionaryInfo(
+ String sourceLanguageCode, String targetLanguageCode, long entryCount) {
+ this.sourceLanguageCode = sourceLanguageCode;
+ this.targetLanguageCode = targetLanguageCode;
+ this.entryCount = entryCount;
+ }
+
+ /** @return the source language code */
+ public String getSourceLanguageCode() {
+ return this.sourceLanguageCode;
+ }
+
+ /** @return the target language code */
+ public String getTargetLanguageCode() {
+ return this.targetLanguageCode;
+ }
+
+ /** @return the entry count */
+ public long getEntryCount() {
+ return this.entryCount;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryInfo.java b/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryInfo.java
new file mode 100644
index 0000000..528c5bc
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/MultilingualGlossaryInfo.java
@@ -0,0 +1,61 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+
+/** Information about a glossary, excluding the entry list. */
+public class MultilingualGlossaryInfo implements IGlossary {
+ @SerializedName(value = "glossary_id")
+ private final String glossaryId;
+
+ @SerializedName(value = "name")
+ private final String name;
+
+ @SerializedName(value = "creation_time")
+ private final Date creationTime;
+
+ @SerializedName(value = "dictionaries")
+ private final List dictionaries;
+
+ /**
+ * Initializes a new {@link MultilingualGlossaryInfo} containing information about a glossary.
+ *
+ * @param glossaryId ID of the associated glossary.
+ * @param name Name of the glossary chosen during creation.
+ * @param creationTime Time when the glossary was created.
+ * @param dictionaries A list of dictionaries that are in this glossary
+ */
+ public MultilingualGlossaryInfo(
+ String glossaryId,
+ String name,
+ Date creationTime,
+ List dictionaries) {
+ this.glossaryId = glossaryId;
+ this.name = name;
+ this.creationTime = creationTime;
+ this.dictionaries = dictionaries;
+ }
+
+ /** @return Unique ID assigned to the glossary. */
+ public String getGlossaryId() {
+ return glossaryId;
+ }
+
+ /** @return User-defined name assigned to the glossary. */
+ public String getName() {
+ return name;
+ }
+
+ /** @return Timestamp when the glossary was created. */
+ public Date getCreationTime() {
+ return creationTime;
+ }
+
+ /** @return the list of dictionaries in this glossary */
+ public List getDictionaries() {
+ return dictionaries;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/StyleRuleInfo.java b/deepl-java/src/main/java/com/deepl/api/StyleRuleInfo.java
new file mode 100644
index 0000000..e712d01
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/StyleRuleInfo.java
@@ -0,0 +1,110 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/** Information about a style rule list. */
+public class StyleRuleInfo {
+ @SerializedName(value = "style_id")
+ private final String styleId;
+
+ @SerializedName(value = "name")
+ private final String name;
+
+ @SerializedName(value = "creation_time")
+ private final Date creationTime;
+
+ @SerializedName(value = "updated_time")
+ private final Date updatedTime;
+
+ @SerializedName(value = "language")
+ private final String language;
+
+ @SerializedName(value = "version")
+ private final int version;
+
+ @SerializedName(value = "configured_rules")
+ @Nullable
+ private final ConfiguredRules configuredRules;
+
+ @SerializedName(value = "custom_instructions")
+ @Nullable
+ private final List customInstructions;
+
+ /**
+ * Initializes a new {@link StyleRuleInfo} containing information about a style rule list.
+ *
+ * @param styleId Unique ID assigned to the style rule list.
+ * @param name User-defined name assigned to the style rule list.
+ * @param creationTime Timestamp when the style rule list was created.
+ * @param updatedTime Timestamp when the style rule list was last updated.
+ * @param language Language code for the style rule list.
+ * @param version Version number of the style rule list.
+ * @param configuredRules The predefined rules that have been enabled.
+ * @param customInstructions Optional list of custom instructions.
+ */
+ public StyleRuleInfo(
+ String styleId,
+ String name,
+ Date creationTime,
+ Date updatedTime,
+ String language,
+ int version,
+ @Nullable ConfiguredRules configuredRules,
+ @Nullable List customInstructions) {
+ this.styleId = styleId;
+ this.name = name;
+ this.creationTime = creationTime;
+ this.updatedTime = updatedTime;
+ this.language = language;
+ this.version = version;
+ this.configuredRules = configuredRules;
+ this.customInstructions = customInstructions;
+ }
+
+ /** @return Unique ID assigned to the style rule list. */
+ public String getStyleId() {
+ return styleId;
+ }
+
+ /** @return User-defined name assigned to the style rule list. */
+ public String getName() {
+ return name;
+ }
+
+ /** @return Timestamp when the style rule list was created. */
+ public Date getCreationTime() {
+ return creationTime;
+ }
+
+ /** @return Timestamp when the style rule list was last updated. */
+ public Date getUpdatedTime() {
+ return updatedTime;
+ }
+
+ /** @return Language code for the style rule list. */
+ public String getLanguage() {
+ return language;
+ }
+
+ /** @return Version number of the style rule list. */
+ public int getVersion() {
+ return version;
+ }
+
+ /** @return The predefined rules that have been enabled. */
+ @Nullable
+ public ConfiguredRules getConfiguredRules() {
+ return configuredRules;
+ }
+
+ /** @return Optional list of custom instructions. */
+ @Nullable
+ public List getCustomInstructions() {
+ return customInstructions;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TextRephraseOptions.java b/deepl-java/src/main/java/com/deepl/api/TextRephraseOptions.java
new file mode 100644
index 0000000..2cd37c4
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TextRephraseOptions.java
@@ -0,0 +1,49 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+/**
+ * Options to control text rephrasing behaviour. These options may be provided to {@link
+ * DeepLClient#rephraseText} overloads.
+ *
+ * All properties have corresponding setters in fluent-style, so the following is possible:
+ *
+ * TextRephraseOptions options = new TextRephraseOptions()
+ * .WritingStyle(WritingStyle.Business.getValue());
+ *
+ */
+public class TextRephraseOptions {
+ private String writingStyle;
+ private String tone;
+
+ /**
+ * Sets a style the improved text should be in. Note that only style OR tone can be set.
+ *
+ * @see WritingStyle
+ */
+ public TextRephraseOptions setWritingStyle(String style) {
+ this.writingStyle = style;
+ return this;
+ }
+
+ /**
+ * Sets a tone the improved text should be in. Note that only style OR tone can be set.
+ *
+ * @see WritingTone
+ */
+ public TextRephraseOptions setTone(String tone) {
+ this.tone = tone;
+ return this;
+ }
+
+ /** Gets the current style setting. */
+ public String getWritingStyle() {
+ return writingStyle;
+ }
+
+ /** Gets the current tone setting. */
+ public String getTone() {
+ return tone;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TextResult.java b/deepl-java/src/main/java/com/deepl/api/TextResult.java
index d7c8b6b..dee38f3 100644
--- a/deepl-java/src/main/java/com/deepl/api/TextResult.java
+++ b/deepl-java/src/main/java/com/deepl/api/TextResult.java
@@ -3,15 +3,25 @@
// license that can be found in the LICENSE file.
package com.deepl.api;
+import org.jetbrains.annotations.Nullable;
+
/** The result of a text translation. */
public class TextResult {
private final String text;
private final String detectedSourceLanguage;
+ private final int billedCharacters;
+ private final @Nullable String modelTypeUsed;
/** Constructs a new instance. */
- public TextResult(String text, String detectedSourceLanguage) {
+ public TextResult(
+ String text,
+ String detectedSourceLanguage,
+ int billedCharacters,
+ @Nullable String modelTypeUsed) {
this.text = text;
this.detectedSourceLanguage = LanguageCode.standardize(detectedSourceLanguage);
+ this.billedCharacters = billedCharacters;
+ this.modelTypeUsed = modelTypeUsed;
}
/** The translated text. */
@@ -23,4 +33,14 @@ public String getText() {
public String getDetectedSourceLanguage() {
return detectedSourceLanguage;
}
+
+ /** Number of characters billed for this text. */
+ public int getBilledCharacters() {
+ return billedCharacters;
+ }
+
+ /** Model type used for the translation of this text. */
+ public @Nullable String getModelTypeUsed() {
+ return modelTypeUsed;
+ }
}
diff --git a/deepl-java/src/main/java/com/deepl/api/TextTranslationOptions.java b/deepl-java/src/main/java/com/deepl/api/TextTranslationOptions.java
index 1baebe1..e70ef07 100644
--- a/deepl-java/src/main/java/com/deepl/api/TextTranslationOptions.java
+++ b/deepl-java/src/main/java/com/deepl/api/TextTranslationOptions.java
@@ -3,6 +3,10 @@
// license that can be found in the LICENSE file.
package com.deepl.api;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
/**
* Options to control text translation behaviour. These options may be provided to {@link
* Translator#translateText} overloads.
@@ -13,17 +17,24 @@
* .setFormality(Formality.Less).setGlossaryId("f63c02c5-f056-..");
*
*/
-public class TextTranslationOptions {
+public class TextTranslationOptions extends BaseRequestOptions {
private Formality formality;
private String glossaryId;
+ private List glossaryIds;
+ private String styleId;
+ private String translationMemoryId;
+ private Integer translationMemoryThreshold;
private SentenceSplittingMode sentenceSplittingMode;
private boolean preserveFormatting = false;
private String context;
private String tagHandling;
+ private String tagHandlingVersion;
+ private String modelType;
private boolean outlineDetection = true;
private Iterable ignoreTags;
private Iterable nonSplittingTags;
private Iterable splittingTags;
+ private Iterable customInstructions;
/**
* Sets whether translations should lean toward formal or informal language. This option is only
@@ -51,7 +62,7 @@ public TextTranslationOptions setGlossaryId(String glossaryId) {
* Sets the glossary to use with the translation. By default, this value is null and
* no glossary is used.
*/
- public TextTranslationOptions setGlossary(GlossaryInfo glossary) {
+ public TextTranslationOptions setGlossary(IGlossary glossary) {
return setGlossary(glossary.getGlossaryId());
}
@@ -64,6 +75,101 @@ public TextTranslationOptions setGlossary(String glossaryId) {
return this;
}
+ /**
+ * Sets the list of glossary IDs to use with the translation, up to a maximum of 5. Glossaries are
+ * applied in the order provided (first match wins). By default, this value is null
+ * and no glossaries are used. This option requires a source language to be set and cannot be
+ * combined with {@link #setGlossaryId} or {@link #setGlossary}.
+ */
+ public TextTranslationOptions setGlossaryIds(List glossaryIds) {
+ this.glossaryIds = glossaryIds;
+ return this;
+ }
+
+ /**
+ * Sets the list of glossary IDs to use with the translation, up to a maximum of 5. Glossaries are
+ * applied in the order provided (first match wins). By default, this value is null
+ * and no glossaries are used. This option requires a source language to be set and cannot be
+ * combined with {@link #setGlossaryId} or {@link #setGlossary}.
+ */
+ public TextTranslationOptions setGlossaryIds(String... glossaryIds) {
+ this.glossaryIds = Arrays.asList(glossaryIds);
+ return this;
+ }
+
+ /**
+ * Sets the list of glossaries to use with the translation, up to a maximum of 5. Glossaries are
+ * applied in the order provided (first match wins). By default, this value is null
+ * and no glossaries are used. This option requires a source language to be set and cannot be
+ * combined with {@link #setGlossaryId} or {@link #setGlossary}.
+ */
+ public TextTranslationOptions setGlossaries(IGlossary... glossaries) {
+ List ids = new ArrayList<>();
+ for (IGlossary glossary : glossaries) {
+ if (glossary == null) {
+ throw new IllegalArgumentException("glossaries must not contain null");
+ }
+ ids.add(glossary.getGlossaryId());
+ }
+ this.glossaryIds = ids;
+ return this;
+ }
+
+ /**
+ * Sets the ID of a style rule to use with the translation. By default, this value is
+ * null and no style rule is used.
+ */
+ public TextTranslationOptions setStyleId(String styleId) {
+ this.styleId = styleId;
+ return this;
+ }
+
+ /**
+ * Sets the style rule to use with the translation. By default, this value is null
+ * and no style rule is used.
+ */
+ public TextTranslationOptions setStyleRule(StyleRuleInfo styleRule) {
+ if (styleRule == null) {
+ throw new IllegalArgumentException("styleRule must not be null");
+ }
+ return setStyleId(styleRule.getStyleId());
+ }
+
+ /**
+ * Sets the ID of a translation memory to use with the translation. By default, this value is
+ * null and no translation memory is used.
+ */
+ public TextTranslationOptions setTranslationMemoryId(String translationMemoryId) {
+ this.translationMemoryId = translationMemoryId;
+ return this;
+ }
+
+ /**
+ * Sets the translation memory to use with the translation. By default, this value is null
+ * and no translation memory is used.
+ */
+ public TextTranslationOptions setTranslationMemory(TranslationMemoryInfo translationMemory) {
+ if (translationMemory == null) {
+ throw new IllegalArgumentException("translationMemory must not be null");
+ }
+ return setTranslationMemoryId(translationMemory.getTranslationMemoryId());
+ }
+
+ /**
+ * Sets the threshold for translation memory matches. By default, this value is null
+ * and the API default threshold is used. Note: a translation memory ID must also be set via
+ * {@link #setTranslationMemoryId} or {@link #setTranslationMemory}, otherwise an error will be
+ * thrown at translation time.
+ */
+ public TextTranslationOptions setTranslationMemoryThreshold(Integer translationMemoryThreshold) {
+ if (translationMemoryThreshold != null
+ && (translationMemoryThreshold < 0 || translationMemoryThreshold > 100)) {
+ throw new IllegalArgumentException("translationMemoryThreshold must be between 0 and 100");
+ }
+ this.translationMemoryThreshold = translationMemoryThreshold;
+ return this;
+ }
+
/**
* Specifies additional context to influence translations, that is not translated itself.
* Characters in the `context` parameter are not counted toward billing. See the API documentation
@@ -106,6 +212,29 @@ public TextTranslationOptions setTagHandling(String tagHandling) {
return this;
}
+ /**
+ * Sets the version of tag handling algorithm to use. By default, this value is null
+ * and the API default is used.
+ */
+ public TextTranslationOptions setTagHandlingVersion(String tagHandlingVersion) {
+ this.tagHandlingVersion = tagHandlingVersion;
+ return this;
+ }
+
+ /**
+ * Set the type of model to use for a text translation. Currently supported values:
+ * "quality_optimized" use a translation model that maximizes translation quality, at the
+ * cost of response time. This option may be unavailable for some language pairs and the API would
+ * respond with an error in this case; "prefer_quality_optimized" use the
+ * highest-quality translation model for the given language pair; "latency_optimized"
+ * use a translation model that minimizes response time, at the cost of translation
+ * quality.
+ */
+ public TextTranslationOptions setModelType(String modelType) {
+ this.modelType = modelType;
+ return this;
+ }
+
/**
* Sets whether outline detection is used; set to false to disable automatic tag
* detection, default is true.
@@ -142,6 +271,15 @@ public TextTranslationOptions setSplittingTags(Iterable splittingTags) {
return this;
}
+ /**
+ * Sets custom instructions to influence translations. By default, this value is null
+ * and no custom instructions are used.
+ */
+ public TextTranslationOptions setCustomInstructions(Iterable customInstructions) {
+ this.customInstructions = customInstructions;
+ return this;
+ }
+
/** Gets the current formality setting. */
public Formality getFormality() {
return formality;
@@ -152,6 +290,26 @@ public String getGlossaryId() {
return glossaryId;
}
+ /** Gets the current list of glossary IDs. */
+ public List getGlossaryIds() {
+ return glossaryIds;
+ }
+
+ /** Gets the current style rule ID. */
+ public String getStyleId() {
+ return styleId;
+ }
+
+ /** Gets the current translation memory ID. */
+ public String getTranslationMemoryId() {
+ return translationMemoryId;
+ }
+
+ /** Gets the current translation memory threshold. */
+ public Integer getTranslationMemoryThreshold() {
+ return translationMemoryThreshold;
+ }
+
/** Gets the current sentence splitting mode. */
public SentenceSplittingMode getSentenceSplittingMode() {
return sentenceSplittingMode;
@@ -167,11 +325,21 @@ public String getContext() {
return context;
}
+ /** Gets the current model type. */
+ public String getModelType() {
+ return modelType;
+ }
+
/** Gets the current tag handling setting. */
public String getTagHandling() {
return tagHandling;
}
+ /** Gets the current tag handling version. */
+ public String getTagHandlingVersion() {
+ return tagHandlingVersion;
+ }
+
/** Gets the current outline detection setting. */
public boolean isOutlineDetection() {
return outlineDetection;
@@ -191,4 +359,9 @@ public Iterable getNonSplittingTags() {
public Iterable getSplittingTags() {
return splittingTags;
}
+
+ /** Gets the current custom instructions list. */
+ public Iterable getCustomInstructions() {
+ return customInstructions;
+ }
}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemoryExport.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryExport.java
new file mode 100644
index 0000000..d1f47e6
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryExport.java
@@ -0,0 +1,49 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import org.jetbrains.annotations.*;
+
+/** A translation memory export job. */
+public class TranslationMemoryExport {
+ private final String jobId;
+ private final @Nullable String translationMemoryId;
+ private final boolean reusedExisting;
+
+ /**
+ * Initializes a new {@link TranslationMemoryExport} describing a created export job.
+ *
+ * @param jobId Unique ID assigned to the export job.
+ * @param translationMemoryId ID of the translation memory being exported, if provided by the API.
+ * @param reusedExisting true if the API reused a previously completed export instead
+ * of starting a new one.
+ */
+ public TranslationMemoryExport(
+ String jobId, @Nullable String translationMemoryId, boolean reusedExisting) {
+ this.jobId = jobId;
+ this.translationMemoryId = translationMemoryId;
+ this.reusedExisting = reusedExisting;
+ }
+
+ /** @return Unique ID assigned to the export job. */
+ public String getJobId() {
+ return jobId;
+ }
+
+ /**
+ * @return ID of the translation memory being exported, or null if not provided by
+ * the API.
+ */
+ public @Nullable String getTranslationMemoryId() {
+ return translationMemoryId;
+ }
+
+ /**
+ * @return true if the API answered with a previously completed export of the same
+ * translation memory (HTTP 200), or false if it started a new one (HTTP 202).
+ */
+ public boolean isReusedExisting() {
+ return reusedExisting;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemoryImport.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryImport.java
new file mode 100644
index 0000000..972e0ef
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryImport.java
@@ -0,0 +1,57 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/**
+ * A newly created translation memory import job.
+ *
+ * The TMX file must be uploaded to the upload URL before it expires; processing starts
+ * automatically once the upload is detected.
+ */
+public class TranslationMemoryImport {
+ @SerializedName(value = "job_id")
+ private final String jobId;
+
+ @SerializedName(value = "upload_url")
+ private final String uploadUrl;
+
+ @SerializedName(value = "expires_at")
+ private final @Nullable Date expiresAt;
+
+ /**
+ * Initializes a new {@link TranslationMemoryImport} describing a created import job.
+ *
+ * @param jobId Unique ID assigned to the import job.
+ * @param uploadUrl URL to upload the TMX file to.
+ * @param expiresAt Timestamp after which the upload URL is no longer valid, if provided by the
+ * API.
+ */
+ public TranslationMemoryImport(String jobId, String uploadUrl, @Nullable Date expiresAt) {
+ this.jobId = jobId;
+ this.uploadUrl = uploadUrl;
+ this.expiresAt = expiresAt;
+ }
+
+ /** @return Unique ID assigned to the import job. */
+ public String getJobId() {
+ return jobId;
+ }
+
+ /** @return URL to upload the TMX file to. */
+ public String getUploadUrl() {
+ return uploadUrl;
+ }
+
+ /**
+ * @return Timestamp after which the upload URL is no longer valid, or null if not
+ * provided by the API.
+ */
+ public @Nullable Date getExpiresAt() {
+ return expiresAt;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemoryInfo.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryInfo.java
new file mode 100644
index 0000000..b77fbbc
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryInfo.java
@@ -0,0 +1,141 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/** Information about a translation memory. */
+public class TranslationMemoryInfo {
+ @SerializedName(value = "translation_memory_id")
+ private final String translationMemoryId;
+
+ @SerializedName(value = "name")
+ private final String name;
+
+ @SerializedName(value = "source_language")
+ private final String sourceLanguage;
+
+ @SerializedName(value = "target_languages")
+ private final List targetLanguages;
+
+ @SerializedName(value = "segment_count")
+ private final int segmentCount;
+
+ @SerializedName(value = "creation_time")
+ private final @Nullable Date creationTime;
+
+ @SerializedName(value = "updated_time")
+ private final @Nullable Date updatedTime;
+
+ /**
+ * Initializes a new {@link TranslationMemoryInfo} containing information about a translation
+ * memory.
+ *
+ * @param translationMemoryId Unique ID assigned to the translation memory.
+ * @param name User-defined name assigned to the translation memory.
+ * @param sourceLanguage Source language code for the translation memory.
+ * @param targetLanguages List of target language codes for the translation memory.
+ * @param segmentCount Number of segments in the translation memory.
+ */
+ public TranslationMemoryInfo(
+ String translationMemoryId,
+ String name,
+ String sourceLanguage,
+ List targetLanguages,
+ int segmentCount) {
+ this(translationMemoryId, name, sourceLanguage, targetLanguages, segmentCount, null, null);
+ }
+
+ /**
+ * Initializes a new {@link TranslationMemoryInfo} containing information about a translation
+ * memory.
+ *
+ * @param translationMemoryId Unique ID assigned to the translation memory.
+ * @param name User-defined name assigned to the translation memory.
+ * @param sourceLanguage Source language code for the translation memory.
+ * @param targetLanguages List of target language codes for the translation memory.
+ * @param segmentCount Number of segments in the translation memory.
+ * @param creationTime Timestamp when the translation memory was created, if provided by the API.
+ * @param updatedTime Timestamp when the translation memory was last updated, if provided by the
+ * API.
+ */
+ public TranslationMemoryInfo(
+ String translationMemoryId,
+ String name,
+ String sourceLanguage,
+ List targetLanguages,
+ int segmentCount,
+ @Nullable Date creationTime,
+ @Nullable Date updatedTime) {
+ this.translationMemoryId = translationMemoryId;
+ this.name = name;
+ this.sourceLanguage = sourceLanguage;
+ this.targetLanguages = targetLanguages;
+ this.segmentCount = segmentCount;
+ this.creationTime = creationTime;
+ this.updatedTime = updatedTime;
+ }
+
+ /** @return Unique ID assigned to the translation memory. */
+ public String getTranslationMemoryId() {
+ return translationMemoryId;
+ }
+
+ /** @return User-defined name assigned to the translation memory. */
+ public String getName() {
+ return name;
+ }
+
+ /** @return Source language code for the translation memory. */
+ public String getSourceLanguage() {
+ return sourceLanguage;
+ }
+
+ /** @return List of target language codes for the translation memory. */
+ public List getTargetLanguages() {
+ return targetLanguages;
+ }
+
+ /** @return Number of segments in the translation memory. */
+ public int getSegmentCount() {
+ return segmentCount;
+ }
+
+ /**
+ * @return Timestamp when the translation memory was created, or null if not provided
+ * by the API.
+ */
+ public @Nullable Date getCreationTime() {
+ return creationTime;
+ }
+
+ /**
+ * @return Timestamp when the translation memory was last updated, or null if not
+ * provided by the API.
+ */
+ public @Nullable Date getUpdatedTime() {
+ return updatedTime;
+ }
+
+ @Override
+ public String toString() {
+ return "TranslationMemoryInfo{"
+ + "translationMemoryId='"
+ + translationMemoryId
+ + '\''
+ + ", name='"
+ + name
+ + '\''
+ + ", sourceLanguage='"
+ + sourceLanguage
+ + '\''
+ + ", targetLanguages="
+ + targetLanguages
+ + ", segmentCount="
+ + segmentCount
+ + '}';
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemoryJob.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryJob.java
new file mode 100644
index 0000000..3fb322b
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryJob.java
@@ -0,0 +1,163 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Date;
+import java.util.List;
+import org.jetbrains.annotations.Nullable;
+
+/** Status of a translation memory import or export job. */
+public class TranslationMemoryJob {
+ private final String jobId;
+ private final @Nullable String product;
+ private final @Nullable Operation operation;
+ private final List results;
+ private final @Nullable Date creationTime;
+ private final @Nullable Date updatedTime;
+ private final @Nullable String translationMemoryId;
+ private final @Nullable String displayName;
+ private final @Nullable String sourceContentType;
+ private final @Nullable Long sourceContentLength;
+
+ /** Operation a translation memory job performs. */
+ public enum Operation {
+ /** The job imports a TMX file into a new translation memory. */
+ @SerializedName("import")
+ Import,
+ /** The job exports an existing translation memory to a TMX file. */
+ @SerializedName("export")
+ Export,
+ }
+
+ /**
+ * Initializes a new {@link TranslationMemoryJob} describing an import or export job.
+ *
+ * @param jobId Unique ID assigned to the job.
+ * @param product Product the job belongs to, always "translation_memory".
+ * @param operation Operation the job performs.
+ * @param results Results of the job; the API returns exactly one.
+ * @param creationTime Timestamp when the job was created.
+ * @param updatedTime Timestamp when the job was last updated.
+ * @param translationMemoryId ID of the translation memory an export job reads from.
+ * @param displayName Display name an import job assigns to the new translation memory.
+ * @param sourceContentType MIME type declared for the file of an import job.
+ * @param sourceContentLength Size in bytes declared for the file of an import job.
+ */
+ public TranslationMemoryJob(
+ String jobId,
+ @Nullable String product,
+ @Nullable Operation operation,
+ List results,
+ @Nullable Date creationTime,
+ @Nullable Date updatedTime,
+ @Nullable String translationMemoryId,
+ @Nullable String displayName,
+ @Nullable String sourceContentType,
+ @Nullable Long sourceContentLength) {
+ this.jobId = jobId;
+ this.product = product;
+ this.operation = operation;
+ this.results = results;
+ this.creationTime = creationTime;
+ this.updatedTime = updatedTime;
+ this.translationMemoryId = translationMemoryId;
+ this.displayName = displayName;
+ this.sourceContentType = sourceContentType;
+ this.sourceContentLength = sourceContentLength;
+ }
+
+ /** @return Unique ID assigned to the job. */
+ public String getJobId() {
+ return jobId;
+ }
+
+ /** @return Product the job belongs to, or null if not provided by the API. */
+ public @Nullable String getProduct() {
+ return product;
+ }
+
+ /** @return Operation the job performs, or null if not provided by the API. */
+ public @Nullable Operation getOperation() {
+ return operation;
+ }
+
+ /** @return Results of the job; the API returns exactly one. */
+ public List getResults() {
+ return results;
+ }
+
+ /** @return The single result of the job, or null if the API returned none. */
+ public @Nullable TranslationMemoryJobResult getResult() {
+ return getResults().isEmpty() ? null : getResults().get(0);
+ }
+
+ /** @return Status of the result of the job, or null if there is no result. */
+ public @Nullable TranslationMemoryJobResult.Status getStatus() {
+ TranslationMemoryJobResult result = getResult();
+ return result == null ? null : result.getStatus();
+ }
+
+ /**
+ * @return true if the job has finished, successfully or not, otherwise false
+ * .
+ */
+ public boolean done() {
+ TranslationMemoryJobResult result = getResult();
+ return result != null && result.done();
+ }
+
+ /** @return false if the job failed or expired, otherwise true. */
+ public boolean ok() {
+ TranslationMemoryJobResult result = getResult();
+ return result == null || result.ok();
+ }
+
+ /**
+ * @return Timestamp when the job was created, or null if not provided by the API.
+ */
+ public @Nullable Date getCreationTime() {
+ return creationTime;
+ }
+
+ /**
+ * @return Timestamp when the job was last updated, or null if not provided by the
+ * API.
+ */
+ public @Nullable Date getUpdatedTime() {
+ return updatedTime;
+ }
+
+ /**
+ * @return ID of the translation memory an export job reads from, or null for an
+ * import job.
+ */
+ public @Nullable String getTranslationMemoryId() {
+ return translationMemoryId;
+ }
+
+ /**
+ * @return Display name an import job assigns to the new translation memory, or null
+ * for an export job.
+ */
+ public @Nullable String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * @return MIME type declared for the file of an import job, or null for an export
+ * job.
+ */
+ public @Nullable String getSourceContentType() {
+ return sourceContentType;
+ }
+
+ /**
+ * @return Size in bytes declared for the file of an import job, or null for an
+ * export job.
+ */
+ public @Nullable Long getSourceContentLength() {
+ return sourceContentLength;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemoryJobResult.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryJobResult.java
new file mode 100644
index 0000000..180c45a
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryJobResult.java
@@ -0,0 +1,134 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.SerializedName;
+import java.util.Date;
+import org.jetbrains.annotations.Nullable;
+
+/** The outcome of a translation memory import or export job. */
+public class TranslationMemoryJobResult {
+ private final @Nullable Status status;
+ private final @Nullable String requiredAction;
+ private final @Nullable String downloadUrl;
+ private final @Nullable Date expiresAt;
+ private final @Nullable String errorMessage;
+ private final @Nullable String translationMemoryId;
+ private final @Nullable Integer skippedSegmentCount;
+
+ /** Status of a translation memory import or export job. */
+ public enum Status {
+ /** The job is waiting for the caller, for example to upload the TMX file of an import. */
+ @SerializedName("awaiting_input")
+ AwaitingInput,
+ /** The job is being processed. */
+ @SerializedName("processing")
+ Processing,
+ /** The job completed successfully. */
+ @SerializedName("completed")
+ Completed,
+ /** The exported file of a completed export job has been downloaded. */
+ @SerializedName("downloaded")
+ Downloaded,
+ /** An error occurred while processing the job. */
+ @SerializedName("failed")
+ Failed,
+ /** The job expired before it finished. */
+ @SerializedName("expired")
+ Expired,
+ }
+
+ /**
+ * Initializes a new {@link TranslationMemoryJobResult} describing the outcome of a job.
+ *
+ * @param status Status of the job.
+ * @param requiredAction Action the caller must take, set while the job is waiting on the caller.
+ * @param downloadUrl Download URL of the exported TMX file, set once an export completed.
+ * @param expiresAt Timestamp after which the download URL is no longer valid.
+ * @param errorMessage Error description, set when the job failed.
+ * @param translationMemoryId ID of the translation memory created by a completed import.
+ * @param skippedSegmentCount Number of segments an import skipped.
+ */
+ public TranslationMemoryJobResult(
+ @Nullable Status status,
+ @Nullable String requiredAction,
+ @Nullable String downloadUrl,
+ @Nullable Date expiresAt,
+ @Nullable String errorMessage,
+ @Nullable String translationMemoryId,
+ @Nullable Integer skippedSegmentCount) {
+ this.status = status;
+ this.requiredAction = requiredAction;
+ this.downloadUrl = downloadUrl;
+ this.expiresAt = expiresAt;
+ this.errorMessage = errorMessage;
+ this.translationMemoryId = translationMemoryId;
+ this.skippedSegmentCount = skippedSegmentCount;
+ }
+
+ /** @return Status of the job, or null if not provided by the API. */
+ public @Nullable Status getStatus() {
+ return status;
+ }
+
+ /**
+ * @return true if the job has finished, successfully or not, otherwise false
+ * .
+ */
+ public boolean done() {
+ return status == Status.Completed
+ || status == Status.Downloaded
+ || status == Status.Failed
+ || status == Status.Expired;
+ }
+
+ /** @return false if the job failed or expired, otherwise true. */
+ public boolean ok() {
+ return status != Status.Failed && status != Status.Expired;
+ }
+
+ /**
+ * @return Action the caller must take, or null if the job is not waiting on the
+ * caller.
+ */
+ public @Nullable String getRequiredAction() {
+ return requiredAction;
+ }
+
+ /**
+ * @return Download URL of the exported TMX file, or null if the export has not
+ * completed.
+ */
+ public @Nullable String getDownloadUrl() {
+ return downloadUrl;
+ }
+
+ /**
+ * @return Timestamp after which the download URL is no longer valid, or null if not
+ * provided by the API.
+ */
+ public @Nullable Date getExpiresAt() {
+ return expiresAt;
+ }
+
+ /** @return Error description if the job failed, otherwise null. */
+ public @Nullable String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * @return ID of the translation memory created by a completed import, otherwise null
+ * .
+ */
+ public @Nullable String getTranslationMemoryId() {
+ return translationMemoryId;
+ }
+
+ /**
+ * @return Number of segments an import skipped, or null if not provided by the API.
+ */
+ public @Nullable Integer getSkippedSegmentCount() {
+ return skippedSegmentCount;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegment.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegment.java
new file mode 100644
index 0000000..2473c10
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegment.java
@@ -0,0 +1,107 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/** A source segment of a translation memory and its translations. */
+public class TranslationMemorySegment {
+ @SerializedName(value = "source_segment_id")
+ private final String sourceSegmentId;
+
+ @SerializedName(value = "source_text")
+ private final String sourceText;
+
+ @SerializedName(value = "targets")
+ private final List targets;
+
+ @SerializedName(value = "creation_time")
+ private final @Nullable Date creationTime;
+
+ @SerializedName(value = "updated_time")
+ private final @Nullable Date updatedTime;
+
+ @SerializedName(value = "last_used_time")
+ private final @Nullable Date lastUsedTime;
+
+ /**
+ * Initializes a new {@link TranslationMemorySegment} holding a source text and its translations.
+ *
+ * @param sourceSegmentId Unique ID assigned to the source segment.
+ * @param sourceText The source text.
+ * @param targets Translations of the source text, one per target language.
+ * @param creationTime Timestamp when the source segment was created, if provided by the API.
+ * @param updatedTime Timestamp when the source segment was last updated, if provided by the API.
+ * @param lastUsedTime Timestamp when the source segment was last used, if provided by the API.
+ */
+ public TranslationMemorySegment(
+ String sourceSegmentId,
+ String sourceText,
+ List targets,
+ @Nullable Date creationTime,
+ @Nullable Date updatedTime,
+ @Nullable Date lastUsedTime) {
+ this.sourceSegmentId = sourceSegmentId;
+ this.sourceText = sourceText;
+ this.targets = targets;
+ this.creationTime = creationTime;
+ this.updatedTime = updatedTime;
+ this.lastUsedTime = lastUsedTime;
+ }
+
+ /** @return Unique ID assigned to the source segment. */
+ public String getSourceSegmentId() {
+ return sourceSegmentId;
+ }
+
+ /** @return The source text. */
+ public String getSourceText() {
+ return sourceText;
+ }
+
+ /** @return Translations of the source text, one per target language. */
+ public List getTargets() {
+ return targets;
+ }
+
+ /**
+ * @return Timestamp when the source segment was created, or null if not provided by
+ * the API.
+ */
+ public @Nullable Date getCreationTime() {
+ return creationTime;
+ }
+
+ /**
+ * @return Timestamp when the source segment was last updated, or null if not
+ * provided by the API.
+ */
+ public @Nullable Date getUpdatedTime() {
+ return updatedTime;
+ }
+
+ /**
+ * @return Timestamp when the source segment was last used, or null if not provided
+ * by the API.
+ */
+ public @Nullable Date getLastUsedTime() {
+ return lastUsedTime;
+ }
+
+ @Override
+ public String toString() {
+ return "TranslationMemorySegment{"
+ + "sourceSegmentId='"
+ + sourceSegmentId
+ + '\''
+ + ", sourceText='"
+ + sourceText
+ + '\''
+ + ", targets="
+ + targets
+ + '}';
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegments.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegments.java
new file mode 100644
index 0000000..d365fe2
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegments.java
@@ -0,0 +1,55 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/** One page of the segments of a translation memory. */
+public class TranslationMemorySegments {
+ @SerializedName(value = "segments")
+ private final List segments;
+
+ @SerializedName(value = "segment_count")
+ private final int segmentCount;
+
+ @SerializedName(value = "next_page_cursor")
+ private final @Nullable String nextPageCursor;
+
+ /**
+ * Initializes a new {@link TranslationMemorySegments} holding one page of segments.
+ *
+ * @param segments The segments in this page.
+ * @param segmentCount Total number of segments stored in the translation memory.
+ * @param nextPageCursor Cursor to fetch the next page, or null on the last page.
+ */
+ public TranslationMemorySegments(
+ List segments, int segmentCount, @Nullable String nextPageCursor) {
+ this.segments = segments;
+ this.segmentCount = segmentCount;
+ this.nextPageCursor = nextPageCursor;
+ }
+
+ /** @return The segments in this page. */
+ public List getSegments() {
+ return segments;
+ }
+
+ /**
+ * @return Total number of segments stored in the translation memory. This is
+ * translation-memory-level metadata, so it is not reduced by a text filter.
+ */
+ public int getSegmentCount() {
+ return segmentCount;
+ }
+
+ /**
+ * @return Opaque cursor to pass as the page cursor to fetch the next page, or null
+ * if this is the last page.
+ */
+ public @Nullable String getNextPageCursor() {
+ return nextPageCursor;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegmentsOptions.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegmentsOptions.java
new file mode 100644
index 0000000..fe064b3
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemorySegmentsOptions.java
@@ -0,0 +1,95 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import org.jetbrains.annotations.*;
+
+/**
+ * Options influencing the page of segments returned by {@link
+ * DeepLClient#listTranslationMemorySegments(String, TranslationMemorySegmentsOptions)}.
+ */
+public class TranslationMemorySegmentsOptions {
+ private @Nullable Integer pageSize;
+ private @Nullable String pageCursor;
+ private @Nullable String filterText;
+ private @Nullable Boolean filterCaseSensitive;
+
+ /**
+ * Sets the maximum number of segments to return in one page.
+ *
+ * @param pageSize Number of segments per page, between 1 and 100, or null to use the
+ * API default.
+ * @return This object, for convenience when chaining setters.
+ * @throws IllegalArgumentException If the page size is outside the range 1 to 100.
+ */
+ public TranslationMemorySegmentsOptions setPageSize(@Nullable Integer pageSize) {
+ if (pageSize != null && (pageSize < 1 || pageSize > 100)) {
+ throw new IllegalArgumentException("pageSize must be between 1 and 100");
+ }
+ this.pageSize = pageSize;
+ return this;
+ }
+
+ /**
+ * Sets the cursor of the page to return.
+ *
+ * @param pageCursor Cursor returned as {@link TranslationMemorySegments#getNextPageCursor()} by
+ * the previous page, or null to return the first page.
+ * @return This object, for convenience when chaining setters.
+ */
+ public TranslationMemorySegmentsOptions setPageCursor(@Nullable String pageCursor) {
+ this.pageCursor = pageCursor;
+ return this;
+ }
+
+ /**
+ * Sets the text that returned segments must contain, in either their source or one of their
+ * target texts.
+ *
+ * @param filterText Text to filter by, at least 2 characters, or null for no
+ * filtering.
+ * @return This object, for convenience when chaining setters.
+ * @throws IllegalArgumentException If the filter text is shorter than 2 characters.
+ */
+ public TranslationMemorySegmentsOptions setFilterText(@Nullable String filterText) {
+ if (filterText != null && filterText.length() < 2) {
+ throw new IllegalArgumentException("filterText must be at least 2 characters");
+ }
+ this.filterText = filterText;
+ return this;
+ }
+
+ /**
+ * Sets whether the text filter is case-sensitive.
+ *
+ * @param filterCaseSensitive true to match the filter text case-sensitively, or
+ * null to use the API default.
+ * @return This object, for convenience when chaining setters.
+ */
+ public TranslationMemorySegmentsOptions setFilterCaseSensitive(
+ @Nullable Boolean filterCaseSensitive) {
+ this.filterCaseSensitive = filterCaseSensitive;
+ return this;
+ }
+
+ /** @return Number of segments per page, or null if unset. */
+ public @Nullable Integer getPageSize() {
+ return pageSize;
+ }
+
+ /** @return Cursor of the page to return, or null if unset. */
+ public @Nullable String getPageCursor() {
+ return pageCursor;
+ }
+
+ /** @return Text that returned segments must contain, or null if unset. */
+ public @Nullable String getFilterText() {
+ return filterText;
+ }
+
+ /** @return Whether the text filter is case-sensitive, or null if unset. */
+ public @Nullable Boolean getFilterCaseSensitive() {
+ return filterCaseSensitive;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslationMemoryTargetSegment.java b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryTargetSegment.java
new file mode 100644
index 0000000..0f9581c
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/TranslationMemoryTargetSegment.java
@@ -0,0 +1,99 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import com.google.gson.annotations.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/** A target-language translation attached to a source segment of a translation memory. */
+public class TranslationMemoryTargetSegment {
+ @SerializedName(value = "target_segment_id")
+ private final String targetSegmentId;
+
+ @SerializedName(value = "target_language")
+ private final String targetLanguage;
+
+ @SerializedName(value = "target_text")
+ private final String targetText;
+
+ @SerializedName(value = "creation_time")
+ private final @Nullable Date creationTime;
+
+ @SerializedName(value = "updated_time")
+ private final @Nullable Date updatedTime;
+
+ @SerializedName(value = "last_used_time")
+ private final @Nullable Date lastUsedTime;
+
+ /**
+ * Initializes a new {@link TranslationMemoryTargetSegment} holding one translation of a source
+ * segment.
+ *
+ * @param targetSegmentId Unique ID assigned to the target segment.
+ * @param targetLanguage Target language code of the translation.
+ * @param targetText The translated text.
+ * @param creationTime Timestamp when the target segment was created, if provided by the API.
+ * @param updatedTime Timestamp when the target segment was last updated, if provided by the API.
+ * @param lastUsedTime Timestamp when the target segment was last used, if provided by the API.
+ */
+ public TranslationMemoryTargetSegment(
+ String targetSegmentId,
+ String targetLanguage,
+ String targetText,
+ @Nullable Date creationTime,
+ @Nullable Date updatedTime,
+ @Nullable Date lastUsedTime) {
+ this.targetSegmentId = targetSegmentId;
+ this.targetLanguage = targetLanguage;
+ this.targetText = targetText;
+ this.creationTime = creationTime;
+ this.updatedTime = updatedTime;
+ this.lastUsedTime = lastUsedTime;
+ }
+
+ /** @return Unique ID assigned to the target segment. */
+ public String getTargetSegmentId() {
+ return targetSegmentId;
+ }
+
+ /** @return Target language code of the translation. */
+ public String getTargetLanguage() {
+ return targetLanguage;
+ }
+
+ /** @return The translated text. */
+ public String getTargetText() {
+ return targetText;
+ }
+
+ /**
+ * @return Timestamp when the target segment was created, or null if not provided by
+ * the API.
+ */
+ public @Nullable Date getCreationTime() {
+ return creationTime;
+ }
+
+ /**
+ * @return Timestamp when the target segment was last updated, or null if not
+ * provided by the API.
+ */
+ public @Nullable Date getUpdatedTime() {
+ return updatedTime;
+ }
+
+ /**
+ * @return Timestamp when the target segment was last used, or null if not provided
+ * by the API.
+ */
+ public @Nullable Date getLastUsedTime() {
+ return lastUsedTime;
+ }
+
+ @Override
+ public String toString() {
+ return targetLanguage + ": " + targetText;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/Translator.java b/deepl-java/src/main/java/com/deepl/api/Translator.java
index 0ddd919..c44350d 100644
--- a/deepl-java/src/main/java/com/deepl/api/Translator.java
+++ b/deepl-java/src/main/java/com/deepl/api/Translator.java
@@ -24,8 +24,9 @@ public class Translator {
/** Base URL for DeepL API Pro accounts */
private static final String DEEPL_SERVER_URL_PRO = "https://api.deepl.com";
- private final Parser jsonParser = new Parser();
- private final HttpClientWrapper httpClientWrapper;
+ protected final Parser jsonParser = new Parser();
+ protected final HttpClientWrapper httpClientWrapper;
+ protected final DeepLApiVersion apiVersion;
/**
* Initializes a new Translator object using your Authentication Key.
@@ -37,21 +38,28 @@ public class Translator {
* href="https://www.deepl.com/pro-account/">DeepL account.
* @param options Additional options controlling Translator behaviour.
* @throws IllegalArgumentException If authKey is invalid.
+ * @deprecated Use {@link DeepLClient} instead.
*/
+ @Deprecated
public Translator(String authKey, TranslatorOptions options) throws IllegalArgumentException {
- if (authKey == null || authKey.length() == 0) {
- throw new IllegalArgumentException("authKey must be a non-empty string");
+ if (authKey == null || authKey.isEmpty()) {
+ throw new IllegalArgumentException("authKey cannot be null or empty");
}
+
+ String sanitizedAuthKey = authKey.trim();
+ this.apiVersion = options.apiVersion;
String serverUrl =
(options.getServerUrl() != null)
? options.getServerUrl()
- : (isFreeAccountAuthKey(authKey) ? DEEPL_SERVER_URL_FREE : DEEPL_SERVER_URL_PRO);
+ : (isFreeAccountAuthKey(sanitizedAuthKey)
+ ? DEEPL_SERVER_URL_FREE
+ : DEEPL_SERVER_URL_PRO);
Map headers = new HashMap<>();
if (options.getHeaders() != null) {
headers.putAll(options.getHeaders());
}
- headers.putIfAbsent("Authorization", "DeepL-Auth-Key " + authKey);
+ headers.putIfAbsent("Authorization", "DeepL-Auth-Key " + sanitizedAuthKey);
headers.putIfAbsent(
"User-Agent",
constructUserAgentString(options.getSendPlatformInfo(), options.getAppInfo()));
@@ -70,7 +78,9 @@ public Translator(String authKey, TranslatorOptions options) throws IllegalArgum
* @param authKey DeepL Authentication Key as found in your DeepL account.
* @throws IllegalArgumentException If authKey is invalid.
+ * @deprecated Use {@link DeepLClient} instead.
*/
+ @Deprecated
public Translator(String authKey) throws IllegalArgumentException {
this(authKey, new TranslatorOptions());
}
@@ -82,7 +92,7 @@ public Translator(String authKey) throws IllegalArgumentException {
*/
private String constructUserAgentString(boolean sendPlatformInfo, AppInfo appInfo) {
StringBuilder sb = new StringBuilder();
- sb.append("deepl-java/1.5.0");
+ sb.append("deepl-java/1.17.0");
if (sendPlatformInfo) {
sb.append(" (");
Properties props = System.getProperties();
@@ -191,7 +201,9 @@ public List translateText(
throws DeepLException, InterruptedException {
Iterable> params =
createHttpParams(texts, sourceLang, targetLang, options);
- HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v2/translate", params);
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/translate", this.apiVersion), params);
checkResponse(response, false, false);
return jsonParser.parseTextResult(response.getBody());
}
@@ -247,7 +259,8 @@ public List translateText(
* @throws DeepLException If any error occurs while communicating with the DeepL API.
*/
public Usage getUsage() throws DeepLException, InterruptedException {
- HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff("/v2/usage");
+ HttpResponse response =
+ httpClientWrapper.sendGetRequestWithBackoff(String.format("/%s/usage", apiVersion));
checkResponse(response, false, false);
return jsonParser.parseUsage(response.getBody());
}
@@ -291,7 +304,9 @@ public List getLanguages(LanguageType languageType)
if (languageType == LanguageType.Target) {
params.add(new KeyValuePair<>("type", "target"));
}
- HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v2/languages", params);
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/languages", apiVersion), params);
checkResponse(response, false, false);
return jsonParser.parseLanguages(response.getBody());
}
@@ -308,7 +323,8 @@ public List getLanguages(LanguageType languageType)
public List getGlossaryLanguages()
throws DeepLException, InterruptedException {
HttpResponse response =
- httpClientWrapper.sendGetRequestWithBackoff("/v2/glossary-language-pairs");
+ httpClientWrapper.sendGetRequestWithBackoff(
+ String.format("/%s/glossary-language-pairs", apiVersion));
checkResponse(response, false, false);
return jsonParser.parseGlossaryLanguageList(response.getBody());
}
@@ -447,7 +463,7 @@ public DocumentHandle translateDocumentUpload(
try (FileInputStream inputStream = new FileInputStream(inputFile)) {
HttpResponse response =
httpClientWrapper.uploadWithBackoff(
- "/v2/document", params, inputFile.getName(), inputStream);
+ String.format("/%s/document", apiVersion), params, inputFile.getName(), inputStream);
checkResponse(response, false, false);
return jsonParser.parseDocumentHandle(response.getBody());
}
@@ -491,7 +507,8 @@ public DocumentHandle translateDocumentUpload(
Iterable> params =
createHttpParams(sourceLang, targetLang, options);
HttpResponse response =
- httpClientWrapper.uploadWithBackoff("/v2/document/", params, fileName, inputStream);
+ httpClientWrapper.uploadWithBackoff(
+ String.format("/%s/document/", apiVersion), params, fileName, inputStream);
checkResponse(response, false, false);
return jsonParser.parseDocumentHandle(response.getBody());
}
@@ -521,7 +538,7 @@ public DocumentStatus translateDocumentStatus(DocumentHandle handle)
throws DeepLException, InterruptedException {
ArrayList> params = new ArrayList<>();
params.add(new KeyValuePair<>("document_key", handle.getDocumentKey()));
- String relativeUrl = String.format("/v2/document/%s", handle.getDocumentId());
+ String relativeUrl = String.format("/%s/document/%s", apiVersion, handle.getDocumentId());
HttpResponse response = httpClientWrapper.sendRequestWithBackoff(relativeUrl, params);
checkResponse(response, false, false);
return jsonParser.parseDocumentStatus(response.getBody());
@@ -595,7 +612,8 @@ public void translateDocumentDownload(DocumentHandle handle, OutputStream output
throws DeepLException, IOException, InterruptedException {
ArrayList> params = new ArrayList<>();
params.add(new KeyValuePair<>("document_key", handle.getDocumentKey()));
- String relativeUrl = String.format("/v2/document/%s/result", handle.getDocumentId());
+ String relativeUrl =
+ String.format("/%s/document/%s/result", apiVersion, handle.getDocumentId());
try (HttpResponseStream response = httpClientWrapper.downloadWithBackoff(relativeUrl, params)) {
checkResponse(response);
assert response.getBody() != null;
@@ -678,7 +696,7 @@ public GlossaryInfo createGlossaryFromCsv(
* @throws DeepLException If any error occurs while communicating with the DeepL API.
*/
public GlossaryInfo getGlossary(String glossaryId) throws DeepLException, InterruptedException {
- String relativeUrl = String.format("/v2/glossaries/%s", glossaryId);
+ String relativeUrl = String.format("/%s/glossaries/%s", apiVersion, glossaryId);
HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
checkResponse(response, false, true);
return jsonParser.parseGlossaryInfo(response.getBody());
@@ -694,7 +712,8 @@ public GlossaryInfo getGlossary(String glossaryId) throws DeepLException, Interr
* @throws DeepLException If any error occurs while communicating with the DeepL API.
*/
public List listGlossaries() throws DeepLException, InterruptedException {
- HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff("/v2/glossaries");
+ HttpResponse response =
+ httpClientWrapper.sendGetRequestWithBackoff(String.format("/%s/glossaries", apiVersion));
checkResponse(response, false, false);
return jsonParser.parseGlossaryInfoList(response.getBody());
}
@@ -725,7 +744,7 @@ public GlossaryEntries getGlossaryEntries(GlossaryInfo glossary)
*/
public GlossaryEntries getGlossaryEntries(String glossaryId)
throws DeepLException, InterruptedException {
- String relativeUrl = String.format("/v2/glossaries/%s/entries", glossaryId);
+ String relativeUrl = String.format("/%s/glossaries/%s/entries", apiVersion, glossaryId);
HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
checkResponse(response, false, true);
return GlossaryEntries.fromTsv(response.getBody());
@@ -750,7 +769,7 @@ public void deleteGlossary(GlossaryInfo glossary) throws DeepLException, Interru
* @throws DeepLException If any error occurs while communicating with the DeepL API.
*/
public void deleteGlossary(String glossaryId) throws DeepLException, InterruptedException {
- String relativeUrl = String.format("/v2/glossaries/%s", glossaryId);
+ String relativeUrl = String.format("/%s/glossaries/%s", apiVersion, glossaryId);
HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
checkResponse(response, false, true);
}
@@ -776,12 +795,15 @@ private static ArrayList> createHttpParams(
sourceLang,
targetLang,
options != null ? options.getFormality() : null,
- options != null ? options.getGlossaryId() : null);
+ options != null ? options.getGlossaryId() : null,
+ options != null ? options.getGlossaryIds() : null);
texts.forEach(
(text) -> {
if (text.isEmpty()) throw new IllegalArgumentException("text must not be empty");
params.add(new KeyValuePair<>("text", text));
});
+ // Always send show_billed_characters=1, remove when the API default is changed to true
+ params.add(new KeyValuePair<>("show_billed_characters", "1"));
if (options != null) {
// Note: formality and glossaryId are added above
@@ -806,9 +828,15 @@ private static ArrayList> createHttpParams(
if (options.getContext() != null) {
params.add(new KeyValuePair<>("context", options.getContext()));
}
+ if (options.getModelType() != null) {
+ params.add(new KeyValuePair<>("model_type", options.getModelType()));
+ }
if (options.getTagHandling() != null) {
params.add(new KeyValuePair<>("tag_handling", options.getTagHandling()));
}
+ if (options.getTagHandlingVersion() != null) {
+ params.add(new KeyValuePair<>("tag_handling_version", options.getTagHandlingVersion()));
+ }
if (!options.isOutlineDetection()) {
params.add(new KeyValuePair<>("outline_detection", "0"));
}
@@ -822,6 +850,28 @@ private static ArrayList> createHttpParams(
if (options.getIgnoreTags() != null) {
params.add(new KeyValuePair<>("ignore_tags", joinTags(options.getIgnoreTags())));
}
+ if (options.getStyleId() != null) {
+ params.add(new KeyValuePair<>("style_id", options.getStyleId()));
+ }
+ if (options.getTranslationMemoryId() != null) {
+ params.add(new KeyValuePair<>("translation_memory_id", options.getTranslationMemoryId()));
+ }
+ if (options.getTranslationMemoryThreshold() != null) {
+ if (options.getTranslationMemoryId() == null) {
+ throw new IllegalArgumentException(
+ "translationMemoryThreshold requires translationMemoryId");
+ }
+ params.add(
+ new KeyValuePair<>(
+ "translation_memory_threshold",
+ options.getTranslationMemoryThreshold().toString()));
+ }
+ if (options.getCustomInstructions() != null) {
+ for (String instruction : options.getCustomInstructions()) {
+ params.add(new KeyValuePair<>("custom_instructions", instruction));
+ }
+ }
+ addExtraBodyParameters(params, options.getExtraBodyParameters());
}
return params;
}
@@ -836,13 +886,38 @@ private static ArrayList> createHttpParams(
* @param options Options influencing translation.
* @return Iterable of parameters for HTTP request.
*/
- private static ArrayList> createHttpParams(
+ protected static ArrayList> createHttpParams(
String sourceLang, String targetLang, DocumentTranslationOptions options) {
- return createHttpParamsCommon(
- sourceLang,
- targetLang,
- options != null ? options.getFormality() : null,
- options != null ? options.getGlossaryId() : null);
+ ArrayList> params =
+ createHttpParamsCommon(
+ sourceLang,
+ targetLang,
+ options != null ? options.getFormality() : null,
+ options != null ? options.getGlossaryId() : null,
+ options != null ? options.getGlossaryIds() : null);
+
+ if (options != null) {
+ if (options.getStyleId() != null) {
+ params.add(new KeyValuePair<>("style_id", options.getStyleId()));
+ }
+ if (options.getTranslationMemoryId() != null) {
+ params.add(new KeyValuePair<>("translation_memory_id", options.getTranslationMemoryId()));
+ }
+ if (options.getTranslationMemoryThreshold() != null) {
+ if (options.getTranslationMemoryId() == null) {
+ throw new IllegalArgumentException(
+ "translationMemoryThreshold requires translationMemoryId");
+ }
+ params.add(
+ new KeyValuePair<>(
+ "translation_memory_threshold",
+ options.getTranslationMemoryThreshold().toString()));
+ }
+ }
+
+ addExtraBodyParameters(params, options != null ? options.getExtraBodyParameters() : null);
+
+ return params;
}
/**
@@ -854,13 +929,15 @@ private static ArrayList> createHttpParams(
* @param targetLang Language code of the desired output language.
* @param formality Formality option for translation.
* @param glossaryId ID of glossary to use for translation.
+ * @param glossaryIds List of glossary IDs to use for translation.
* @return Iterable of parameters for HTTP request.
*/
- private static ArrayList> createHttpParamsCommon(
+ protected static ArrayList> createHttpParamsCommon(
@Nullable String sourceLang,
String targetLang,
@Nullable Formality formality,
- @Nullable String glossaryId) {
+ @Nullable String glossaryId,
+ @Nullable List glossaryIds) {
targetLang = LanguageCode.standardize(targetLang);
sourceLang = sourceLang == null ? null : LanguageCode.standardize(sourceLang);
checkValidLanguages(sourceLang, targetLang);
@@ -899,6 +976,20 @@ private static ArrayList> createHttpParamsCommon(
params.add(new KeyValuePair<>("glossary_id", glossaryId));
}
+ if (glossaryIds != null && !glossaryIds.isEmpty()) {
+ if (glossaryId != null) {
+ throw new IllegalArgumentException(
+ "glossaryIds cannot be used together with glossaryId; use one or the other");
+ }
+ if (sourceLang == null) {
+ throw new IllegalArgumentException("sourceLang is required if using glossaries");
+ }
+ if (glossaryIds.size() > 5) {
+ throw new IllegalArgumentException("glossaryIds cannot contain more than 5 glossary IDs");
+ }
+ params.add(new KeyValuePair<>("glossary_ids", String.join(",", glossaryIds)));
+ }
+
return params;
}
@@ -907,6 +998,23 @@ private static String joinTags(Iterable tags) {
return String.join(",", tags);
}
+ /**
+ * Adds extra body parameters to the HTTP request parameters. Extra parameters can override
+ * existing parameters.
+ *
+ * @param params List of HTTP parameters to add to.
+ * @param extraBodyParameters Map of extra parameters to add (can be null).
+ */
+ private static void addExtraBodyParameters(
+ ArrayList> params, Map extraBodyParameters) {
+ if (extraBodyParameters != null) {
+ params.removeIf(pair -> extraBodyParameters.containsKey(pair.getKey()));
+ for (Map.Entry entry : extraBodyParameters.entrySet()) {
+ params.add(new KeyValuePair<>(entry.getKey(), entry.getValue()));
+ }
+ }
+ }
+
/**
* Checks the specified source and target language are valid.
*
@@ -915,7 +1023,7 @@ private static String joinTags(Iterable tags) {
* @param targetLang Language code of the desired output language.
* @throws IllegalArgumentException If either language code is invalid.
*/
- private static void checkValidLanguages(@Nullable String sourceLang, String targetLang)
+ protected static void checkValidLanguages(@Nullable String sourceLang, String targetLang)
throws IllegalArgumentException {
if (sourceLang != null && sourceLang.isEmpty()) {
throw new IllegalArgumentException("sourceLang must be null or non-empty");
@@ -945,7 +1053,9 @@ private GlossaryInfo createGlossaryInternal(
params.add(new KeyValuePair<>("target_lang", targetLang));
params.add(new KeyValuePair<>("entries_format", entriesFormat));
params.add(new KeyValuePair<>("entries", entries));
- HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v2/glossaries", params);
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/glossaries", apiVersion), params);
checkResponse(response, false, false);
return jsonParser.parseGlossaryInfo(response.getBody());
}
@@ -977,7 +1087,7 @@ private void checkResponse(HttpResponseStream response) throws DeepLException {
* @throws DeepLException Throws {@link DeepLException} or a derived exception depending on the
* type of error.
*/
- private void checkResponse(
+ protected void checkResponse(
HttpResponse response, boolean inDocumentDownload, boolean usingGlossary)
throws DeepLException {
if (response.getCode() >= 200 && response.getCode() < 300) {
@@ -1003,7 +1113,7 @@ private void checkResponse(
if (usingGlossary) {
throw new GlossaryNotFoundException("Glossary not found" + messageSuffix);
} else {
- throw new NotFoundException("Not found, check serverUrl" + messageSuffix);
+ throw new NotFoundException("Not found" + messageSuffix);
}
case 429:
throw new TooManyRequestsException(
diff --git a/deepl-java/src/main/java/com/deepl/api/TranslatorOptions.java b/deepl-java/src/main/java/com/deepl/api/TranslatorOptions.java
index 7fb2773..42f4634 100644
--- a/deepl-java/src/main/java/com/deepl/api/TranslatorOptions.java
+++ b/deepl-java/src/main/java/com/deepl/api/TranslatorOptions.java
@@ -26,6 +26,13 @@ public class TranslatorOptions {
@Nullable private String serverUrl = null;
private boolean sendPlatformInfo = true;
@Nullable private AppInfo appInfo = null;
+ @Nullable protected DeepLApiVersion apiVersion = null;
+
+ /** @deprecated Use {@link DeepLClient} instead. */
+ @Deprecated
+ public TranslatorOptions() {
+ apiVersion = DeepLApiVersion.VERSION_2;
+ }
/**
* Set the maximum number of failed attempts that {@link Translator} will retry, per request. By
diff --git a/deepl-java/src/main/java/com/deepl/api/WriteResult.java b/deepl-java/src/main/java/com/deepl/api/WriteResult.java
new file mode 100644
index 0000000..bdb19d4
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/WriteResult.java
@@ -0,0 +1,33 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+/** The result of a text translation. */
+public class WriteResult {
+ private final String text;
+ private final String detectedSourceLanguage;
+ private final String targetLanguage;
+
+ /** Constructs a new instance. */
+ public WriteResult(String text, String detectedSourceLanguage, String targetLanguage) {
+ this.text = text;
+ this.detectedSourceLanguage = LanguageCode.standardize(detectedSourceLanguage);
+ this.targetLanguage = targetLanguage;
+ }
+
+ /** The translated text. */
+ public String getText() {
+ return text;
+ }
+
+ /** The language code of the source text detected by DeepL. */
+ public String getDetectedSourceLanguage() {
+ return detectedSourceLanguage;
+ }
+
+ /** The language code of the target language set by the request. */
+ public String getTargetLanguage() {
+ return targetLanguage;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/WritingStyle.java b/deepl-java/src/main/java/com/deepl/api/WritingStyle.java
new file mode 100644
index 0000000..37ba128
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/WritingStyle.java
@@ -0,0 +1,27 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+/** Represents the style the improved text should be in in a rephrase request. */
+public enum WritingStyle {
+ Academic("academic"),
+ Business("business"),
+ Casual("casual"),
+ Default("default"),
+ PreferAcademic("prefer_academic"),
+ PreferBusiness("prefer_business"),
+ PreferCasual("prefer_casual"),
+ PreferSimple("prefer_simple"),
+ Simple("simple");
+
+ private final String value;
+
+ WritingStyle(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/WritingTone.java b/deepl-java/src/main/java/com/deepl/api/WritingTone.java
new file mode 100644
index 0000000..d507fbb
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/WritingTone.java
@@ -0,0 +1,27 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+/** Represents the tone the improved text should be in in a rephrase request. */
+public enum WritingTone {
+ Confident("confident"),
+ Default("default"),
+ Diplomatic("diplomatic"),
+ Enthusiastic("enthusiastic"),
+ Friendly("friendly"),
+ PreferConfident("prefer_confident"),
+ PreferDiplomatic("prefer_diplomatic"),
+ PreferEnthusiastic("prefer_enthusiastic"),
+ PreferFriendly("prefer_friendly");
+
+ private final String value;
+
+ WritingTone(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/http/HttpContent.java b/deepl-java/src/main/java/com/deepl/api/http/HttpContent.java
index 992e5e7..7b2a229 100644
--- a/deepl-java/src/main/java/com/deepl/api/http/HttpContent.java
+++ b/deepl-java/src/main/java/com/deepl/api/http/HttpContent.java
@@ -52,6 +52,27 @@ private static String urlEncode(String value) throws DeepLException {
}
}
+ /**
+ * Builds content from the given bytes without any encoding, for example the contents of a file to
+ * upload.
+ */
+ public static HttpContent buildRawContent(String contentType, byte[] content) {
+ if (contentType == null) {
+ throw new IllegalArgumentException("contentType must not be null");
+ }
+ if (content == null) {
+ throw new IllegalArgumentException("content must not be null");
+ }
+ return new HttpContent(contentType, content);
+ }
+
+ public static HttpContent buildJsonContent(String jsonBody) {
+ if (jsonBody == null) {
+ throw new IllegalArgumentException("jsonBody must not be null");
+ }
+ return new HttpContent("application/json", jsonBody.getBytes(StandardCharsets.UTF_8));
+ }
+
public static HttpContent buildMultipartFormDataContent(
Iterable> params) throws Exception {
String boundary = UUID.randomUUID().toString();
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryDictionaryEntriesResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryDictionaryEntriesResponse.java
new file mode 100644
index 0000000..823e492
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryDictionaryEntriesResponse.java
@@ -0,0 +1,52 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.GlossaryEntries;
+import com.deepl.api.MultilingualGlossaryDictionaryEntries;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Class representing v3 list-glossaries response by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+public class MultilingualGlossaryDictionaryEntriesResponse {
+
+ @SerializedName(value = "source_lang")
+ private final String sourceLanguageCode;
+
+ @SerializedName(value = "target_lang")
+ private final String targetLanguageCode;
+
+ @SerializedName(value = "entries")
+ private final String entries;
+
+ @SerializedName(value = "entries_format")
+ private final String entriesFormat;
+
+ /**
+ * Initializes a new {@link MultilingualGlossaryDictionaryEntriesResponse} containing information
+ * about a glossary dictionary.
+ *
+ * @param sourceLanguageCode the source language for this dictionary
+ * @param targetLanguageCode the target language for this dictionary
+ * @param entries the entries in this dictionary
+ * @param entriesFormat the format of the entries in this dictionary
+ */
+ public MultilingualGlossaryDictionaryEntriesResponse(
+ String sourceLanguageCode, String targetLanguageCode, String entries, String entriesFormat) {
+ this.sourceLanguageCode = sourceLanguageCode;
+ this.targetLanguageCode = targetLanguageCode;
+ this.entries = entries;
+ this.entriesFormat = entriesFormat;
+ }
+
+ public MultilingualGlossaryDictionaryEntries getDictionaryEntries() {
+ return new MultilingualGlossaryDictionaryEntries(
+ this.sourceLanguageCode,
+ this.targetLanguageCode,
+ new GlossaryEntries(GlossaryEntries.fromTsv(this.entries)));
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryDictionaryListResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryDictionaryListResponse.java
new file mode 100644
index 0000000..d573155
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryDictionaryListResponse.java
@@ -0,0 +1,19 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import java.util.List;
+
+/**
+ * Class representing v3 list-glossaries response by the DeepL API.
+ *
+ *
This class is internal; you should not use this class directly.
+ */
+public class MultilingualGlossaryDictionaryListResponse {
+ private List dictionaries;
+
+ public List getDictionaries() {
+ return dictionaries;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryListResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryListResponse.java
new file mode 100644
index 0000000..fe1da36
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/MultilingualGlossaryListResponse.java
@@ -0,0 +1,20 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.MultilingualGlossaryInfo;
+import java.util.List;
+
+/**
+ * Class representing v3 list-glossaries response by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class MultilingualGlossaryListResponse {
+ private List glossaries;
+
+ public List getGlossaries() {
+ return glossaries;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/Parser.java b/deepl-java/src/main/java/com/deepl/api/parsing/Parser.java
index ffd7814..fb13bc2 100644
--- a/deepl-java/src/main/java/com/deepl/api/parsing/Parser.java
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/Parser.java
@@ -18,11 +18,18 @@
public class Parser {
private final Gson gson;
+ public Gson getGson() {
+ return gson;
+ }
+
public Parser() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(TextResult.class, new TextResultDeserializer());
+ gsonBuilder.registerTypeAdapter(WriteResult.class, new WriteResultDeserializer());
gsonBuilder.registerTypeAdapter(Language.class, new LanguageDeserializer());
gsonBuilder.registerTypeAdapter(Usage.class, new UsageDeserializer());
+ gsonBuilder.registerTypeAdapter(
+ TranslationMemoryJob.class, new TranslationMemoryJobDeserializer());
gson = gsonBuilder.create();
}
@@ -31,6 +38,11 @@ public List parseTextResult(String json) {
return result.translations;
}
+ public List parseWriteResult(String json) {
+ WriteResponse result = gson.fromJson(json, WriteResponse.class);
+ return result.improvements;
+ }
+
public Usage parseUsage(String json) {
return gson.fromJson(json, Usage.class);
}
@@ -56,11 +68,71 @@ public GlossaryInfo parseGlossaryInfo(String json) {
return gson.fromJson(json, GlossaryInfo.class);
}
+ public MultilingualGlossaryInfo parseMultilingualGlossaryInfo(String json) {
+ return gson.fromJson(json, MultilingualGlossaryInfo.class);
+ }
+
+ public MultilingualGlossaryDictionaryListResponse parseMultilingualGlossaryDictionaryListResponse(
+ String json) {
+ return gson.fromJson(json, MultilingualGlossaryDictionaryListResponse.class);
+ }
+
public List parseGlossaryInfoList(String json) {
GlossaryListResponse result = gson.fromJson(json, GlossaryListResponse.class);
return result.getGlossaries();
}
+ public List parseMultilingualGlossaryInfoList(String json) {
+ MultilingualGlossaryListResponse result =
+ gson.fromJson(json, MultilingualGlossaryListResponse.class);
+ return result.getGlossaries();
+ }
+
+ public MultilingualGlossaryDictionaryInfo parseMultilingualGlossaryDictionaryInfo(String json) {
+ return gson.fromJson(json, MultilingualGlossaryDictionaryInfo.class);
+ }
+
+ public List parseStyleRuleInfoList(String json) {
+ StyleRuleListResponse result = gson.fromJson(json, StyleRuleListResponse.class);
+ return result.getStyleRules();
+ }
+
+ public StyleRuleInfo parseStyleRuleInfo(String json) {
+ return gson.fromJson(json, StyleRuleInfo.class);
+ }
+
+ public List parseTranslationMemoryInfoList(String json) {
+ TranslationMemoryListResponse result = gson.fromJson(json, TranslationMemoryListResponse.class);
+ return result.getTranslationMemories();
+ }
+
+ public TranslationMemoryInfo parseTranslationMemoryInfo(String json) {
+ return gson.fromJson(json, TranslationMemoryInfo.class);
+ }
+
+ public TranslationMemorySegments parseTranslationMemorySegments(String json) {
+ return gson.fromJson(json, TranslationMemorySegments.class);
+ }
+
+ public TranslationMemoryImport parseTranslationMemoryImport(String json) {
+ return gson.fromJson(json, TranslationMemoryImport.class);
+ }
+
+ public TranslationMemoryExport parseTranslationMemoryExport(String json, boolean reusedExisting) {
+ TranslationMemoryExportResponse response =
+ gson.fromJson(json, TranslationMemoryExportResponse.class);
+ return new TranslationMemoryExport(
+ response.getJobId(), response.getTranslationMemoryId(), reusedExisting);
+ }
+
+ public TranslationMemoryJob parseTranslationMemoryJob(String json) {
+ return gson.fromJson(json, TranslationMemoryJob.class);
+ }
+
+ public CustomInstruction parseCustomInstruction(String json) {
+ return gson.fromJson(json, CustomInstruction.class);
+ }
+
public String parseErrorMessage(String json) {
ErrorResponse response = gson.fromJson(json, ErrorResponse.class);
@@ -72,12 +144,20 @@ public String parseErrorMessage(String json) {
}
static @Nullable Integer getAsIntOrNull(JsonObject jsonObject, String parameterName) {
- if (!jsonObject.has(parameterName)) return null;
+ // An explicit JSON null must be treated as absent: getAsX() throws on JsonNull.
+ if (!jsonObject.has(parameterName) || jsonObject.get(parameterName).isJsonNull()) return null;
return jsonObject.get(parameterName).getAsInt();
}
+ static @Nullable Long getAsLongOrNull(JsonObject jsonObject, String parameterName) {
+ // An explicit JSON null must be treated as absent: getAsX() throws on JsonNull.
+ if (!jsonObject.has(parameterName) || jsonObject.get(parameterName).isJsonNull()) return null;
+ return jsonObject.get(parameterName).getAsLong();
+ }
+
static @Nullable String getAsStringOrNull(JsonObject jsonObject, String parameterName) {
- if (!jsonObject.has(parameterName)) return null;
+ // An explicit JSON null must be treated as absent: getAsX() throws on JsonNull.
+ if (!jsonObject.has(parameterName) || jsonObject.get(parameterName).isJsonNull()) return null;
return jsonObject.get(parameterName).getAsString();
}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/StyleRuleListResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/StyleRuleListResponse.java
new file mode 100644
index 0000000..2b5bd06
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/StyleRuleListResponse.java
@@ -0,0 +1,20 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.StyleRuleInfo;
+import java.util.List;
+
+/**
+ * Class representing v3 style_rules list response by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class StyleRuleListResponse {
+ private List style_rules;
+
+ public List getStyleRules() {
+ return style_rules;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/TextResultDeserializer.java b/deepl-java/src/main/java/com/deepl/api/parsing/TextResultDeserializer.java
index 15d6180..22cd42d 100644
--- a/deepl-java/src/main/java/com/deepl/api/parsing/TextResultDeserializer.java
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/TextResultDeserializer.java
@@ -16,8 +16,11 @@ class TextResultDeserializer implements JsonDeserializer {
public TextResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
+ JsonElement modelType = jsonObject.get("model_type_used");
return new TextResult(
jsonObject.get("text").getAsString(),
- jsonObject.get("detected_source_language").getAsString());
+ jsonObject.get("detected_source_language").getAsString(),
+ jsonObject.get("billed_characters").getAsInt(),
+ modelType != null ? (modelType.getAsString()) : null);
}
}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryExportResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryExportResponse.java
new file mode 100644
index 0000000..e0d3f23
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryExportResponse.java
@@ -0,0 +1,26 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+/**
+ * Class representing v3 translation memory export response by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class TranslationMemoryExportResponse {
+ private String job_id;
+ private Parameters parameters;
+
+ static class Parameters {
+ private String translation_memory_id;
+ }
+
+ public String getJobId() {
+ return job_id;
+ }
+
+ public String getTranslationMemoryId() {
+ return parameters == null ? null : parameters.translation_memory_id;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryJobDeserializer.java b/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryJobDeserializer.java
new file mode 100644
index 0000000..1d46b0e
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryJobDeserializer.java
@@ -0,0 +1,76 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.*;
+import com.google.gson.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * Deserializer for {@link TranslationMemoryJob} objects, flattening the nested parameters
+ * , source_file, status_metadata and error objects of
+ * the API response onto the job and its results.
+ *
+ *
This class is internal; you should not use this class directly.
+ */
+class TranslationMemoryJobDeserializer implements JsonDeserializer {
+ @Override
+ public TranslationMemoryJob deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject jsonObject = json.getAsJsonObject();
+ JsonObject parameters = getAsObjectOrNull(jsonObject, "parameters");
+ JsonObject sourceFile = getAsObjectOrNull(jsonObject, "source_file");
+
+ List results = new ArrayList<>();
+ if (jsonObject.has("results") && jsonObject.get("results").isJsonArray()) {
+ for (JsonElement result : jsonObject.get("results").getAsJsonArray()) {
+ results.add(deserializeResult(result.getAsJsonObject(), context));
+ }
+ }
+
+ return new TranslationMemoryJob(
+ Parser.getAsStringOrNull(jsonObject, "job_id"),
+ Parser.getAsStringOrNull(jsonObject, "product"),
+ context.deserialize(jsonObject.get("operation"), TranslationMemoryJob.Operation.class),
+ results,
+ deserializeDate(jsonObject, "creation_time", context),
+ deserializeDate(jsonObject, "updated_time", context),
+ parameters == null ? null : Parser.getAsStringOrNull(parameters, "translation_memory_id"),
+ parameters == null ? null : Parser.getAsStringOrNull(parameters, "display_name"),
+ sourceFile == null ? null : Parser.getAsStringOrNull(sourceFile, "content_type"),
+ sourceFile == null ? null : Parser.getAsLongOrNull(sourceFile, "content_length"));
+ }
+
+ private static TranslationMemoryJobResult deserializeResult(
+ JsonObject jsonObject, JsonDeserializationContext context) {
+ JsonObject statusMetadata = getAsObjectOrNull(jsonObject, "status_metadata");
+ JsonObject error = getAsObjectOrNull(jsonObject, "error");
+
+ return new TranslationMemoryJobResult(
+ context.deserialize(jsonObject.get("status"), TranslationMemoryJobResult.Status.class),
+ statusMetadata == null ? null : Parser.getAsStringOrNull(statusMetadata, "required_action"),
+ Parser.getAsStringOrNull(jsonObject, "download_url"),
+ deserializeDate(jsonObject, "expires_at", context),
+ error == null ? null : Parser.getAsStringOrNull(error, "message"),
+ Parser.getAsStringOrNull(jsonObject, "translation_memory_id"),
+ Parser.getAsIntOrNull(jsonObject, "skipped_segment_count"));
+ }
+
+ private static Date deserializeDate(
+ JsonObject jsonObject, String parameterName, JsonDeserializationContext context) {
+ if (!jsonObject.has(parameterName) || jsonObject.get(parameterName).isJsonNull()) {
+ return null;
+ }
+ return context.deserialize(jsonObject.get(parameterName), Date.class);
+ }
+
+ private static JsonObject getAsObjectOrNull(JsonObject jsonObject, String parameterName) {
+ if (!jsonObject.has(parameterName) || !jsonObject.get(parameterName).isJsonObject()) {
+ return null;
+ }
+ return jsonObject.get(parameterName).getAsJsonObject();
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryListResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryListResponse.java
new file mode 100644
index 0000000..bbb9dd8
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/TranslationMemoryListResponse.java
@@ -0,0 +1,20 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.TranslationMemoryInfo;
+import java.util.List;
+
+/**
+ * Class representing v3 translation_memories list response by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class TranslationMemoryListResponse {
+ private List translation_memories;
+
+ public List getTranslationMemories() {
+ return translation_memories;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/UsageDeserializer.java b/deepl-java/src/main/java/com/deepl/api/parsing/UsageDeserializer.java
index 1f1db7d..f28d119 100644
--- a/deepl-java/src/main/java/com/deepl/api/parsing/UsageDeserializer.java
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/UsageDeserializer.java
@@ -25,8 +25,8 @@ public Usage deserialize(JsonElement json, Type typeOfT, JsonDeserializationCont
}
public static @Nullable Usage.Detail createDetail(JsonObject jsonObject, String prefix) {
- Integer count = Parser.getAsIntOrNull(jsonObject, prefix + "count");
- Integer limit = Parser.getAsIntOrNull(jsonObject, prefix + "limit");
+ Long count = Parser.getAsLongOrNull(jsonObject, prefix + "count");
+ Long limit = Parser.getAsLongOrNull(jsonObject, prefix + "limit");
if (count == null || limit == null) return null;
return new Usage.Detail(count, limit);
}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/WriteResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/WriteResponse.java
new file mode 100644
index 0000000..d7074bf
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/WriteResponse.java
@@ -0,0 +1,16 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.WriteResult;
+import java.util.List;
+
+/**
+ * Class representing text rephrase responses from the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class WriteResponse {
+ public List improvements;
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/WriteResultDeserializer.java b/deepl-java/src/main/java/com/deepl/api/parsing/WriteResultDeserializer.java
new file mode 100644
index 0000000..e692803
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/WriteResultDeserializer.java
@@ -0,0 +1,24 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api.parsing;
+
+import com.deepl.api.WriteResult;
+import com.google.gson.*;
+import java.lang.reflect.Type;
+
+/**
+ * Utility class for deserializing text rephrase results returned by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class WriteResultDeserializer implements JsonDeserializer {
+ public WriteResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject jsonObject = json.getAsJsonObject();
+ return new WriteResult(
+ jsonObject.get("text").getAsString(),
+ jsonObject.get("detected_source_language").getAsString(),
+ jsonObject.get("target_language").getAsString());
+ }
+}
diff --git a/deepl-java/src/test/java/com/deepl/api/GeneralTest.java b/deepl-java/src/test/java/com/deepl/api/GeneralTest.java
index d587a0f..833e715 100644
--- a/deepl-java/src/test/java/com/deepl/api/GeneralTest.java
+++ b/deepl-java/src/test/java/com/deepl/api/GeneralTest.java
@@ -3,17 +3,17 @@
// license that can be found in the LICENSE file.
package com.deepl.api;
-import static org.mockito.Mockito.*;
-
import java.io.*;
import java.net.*;
import java.time.*;
import java.util.*;
import java.util.stream.Stream;
import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.MockedConstruction;
import org.mockito.Mockito;
@@ -29,6 +29,16 @@ void testEmptyAuthKey() {
});
}
+ @Test
+ void testNullAuthKey() {
+ IllegalArgumentException thrown =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ Translator translator = new Translator(null);
+ });
+ }
+
@Test
void testInvalidAuthKey() {
String authKey = "invalid";
@@ -45,9 +55,24 @@ void testExampleTranslation() throws DeepLException, InterruptedException {
String sourceLang = LanguageCode.removeRegionalVariant(entry.getKey());
TextResult result = translator.translateText(inputText, sourceLang, "en-US");
Assertions.assertTrue(result.getText().toLowerCase(Locale.ENGLISH).contains("proton"));
+ Assertions.assertEquals(inputText.length(), result.getBilledCharacters());
}
}
+ @ParameterizedTest
+ @ValueSource(strings = {"quality_optimized", "prefer_quality_optimized", "latency_optimized"})
+ void testModelType(String modelTypeArg) throws DeepLException, InterruptedException {
+ Translator translator = createTranslator();
+ String sourceLang = "de";
+ TextResult result =
+ translator.translateText(
+ exampleText.get(sourceLang),
+ sourceLang,
+ "en-US",
+ new TextTranslationOptions().setModelType(modelTypeArg));
+ Assertions.assertNotNull(result.getModelTypeUsed());
+ }
+
@Test
void testInvalidServerUrl() {
Assertions.assertThrows(
@@ -67,7 +92,9 @@ void testMixedDirectionText() throws DeepLException, InterruptedException {
new TextTranslationOptions().setTagHandling("xml").setIgnoreTags(Arrays.asList("xml"));
String arIgnorePart = "يجب تجاهل هذا الجزء.";
String enSentenceWithArIgnorePart =
- "This is a short sentence. " + arIgnorePart + " This is another sentence.";
+ "
This is a short sentence.
"
+ + arIgnorePart
+ + " This is another sentence.";
String enIgnorePart = "This part should be ignored.";
String arSentenceWithEnIgnorePart =
"هذه جملة قصيرة. " + enIgnorePart + "هذه جملة أخرى.
";
@@ -86,6 +113,21 @@ void testUsage() throws DeepLException, InterruptedException {
Assertions.assertTrue(usage.toString().contains("Usage this billing period"));
}
+ @Test
+ void testUsageLarge() throws DeepLException, InterruptedException {
+ Assumptions.assumeTrue(isMockServer);
+ SessionOptions sessionOptions = new SessionOptions();
+ sessionOptions.initCharacterLimit = 1000000000000L;
+ Map headers = sessionOptions.createSessionHeaders();
+
+ TranslatorOptions options = new TranslatorOptions().setHeaders(headers).setServerUrl(serverUrl);
+ String authKeyWithUuid = authKey + "/" + UUID.randomUUID().toString();
+ Translator translator = new Translator(authKeyWithUuid, options);
+ Usage usage = translator.getUsage();
+ Assertions.assertNotNull(usage.getCharacter());
+ Assertions.assertEquals(sessionOptions.initCharacterLimit, usage.getCharacter().getLimit());
+ }
+
@Test
void testGetSourceAndTargetLanguages() throws DeepLException, InterruptedException {
Translator translator = createTranslator();
@@ -310,6 +352,31 @@ void testUserAgent(
}
}
+ @Test
+ @EnabledIf("runV1ApiTests")
+ void testV1Api() throws DeepLException, InterruptedException {
+ SessionOptions sessionOptions = new SessionOptions();
+ DeepLClientOptions clientOptions =
+ (new DeepLClientOptions()).setApiVersion(DeepLApiVersion.VERSION_1);
+ DeepLClient client = createDeepLClient(sessionOptions, clientOptions);
+
+ for (Map.Entry entry : exampleText.entrySet()) {
+ String inputText = entry.getValue();
+ String sourceLang = LanguageCode.removeRegionalVariant(entry.getKey());
+ TextResult result = client.translateText(inputText, sourceLang, "en-US");
+ Assertions.assertTrue(result.getText().toLowerCase(Locale.ENGLISH).contains("proton"));
+ Assertions.assertEquals(inputText.length(), result.getBilledCharacters());
+ }
+ Usage usage = client.getUsage();
+ Assertions.assertTrue(usage.toString().contains("Usage this billing period"));
+
+ List sourceLanguages = client.getSourceLanguages();
+ List targetLanguages = client.getTargetLanguages();
+ Assertions.assertTrue(sourceLanguages.size() > 20);
+ Assertions.assertTrue(targetLanguages.size() > 20);
+ Assertions.assertTrue(targetLanguages.size() >= sourceLanguages.size());
+ }
+
// Session options & Translator options: Used to construct the `Translator`
// Next arg: List of Strings that must be contained in the user agent header
// Last arg: List of Strings that must not be contained in the user agent header
@@ -357,4 +424,8 @@ private static Stream extends Arguments> provideUserAgentTestData() {
customUserAgent,
detailedPlatformInfoWithAppInfo));
}
+
+ boolean runV1ApiTests() {
+ return Boolean.getBoolean("runV1ApiTests");
+ }
}
diff --git a/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java b/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java
index 906399b..11a1fc1 100644
--- a/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java
+++ b/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java
@@ -355,4 +355,135 @@ void testGlossaryTranslateTextInvalid() throws Exception {
Assertions.assertTrue(exception.getMessage().contains("targetLang=\"en\" is not allowed"));
}
}
+
+ @Test
+ void testGlossaryIdsTranslateTextBasic() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanupEnDe1 = new GlossaryCleanupUtility(translator, "EnDe1");
+ GlossaryCleanupUtility cleanupEnDe2 = new GlossaryCleanupUtility(translator, "EnDe2")) {
+ String glossaryName1 = cleanupEnDe1.getGlossaryName();
+ String glossaryName2 = cleanupEnDe2.getGlossaryName();
+
+ GlossaryEntries entries1 = GlossaryEntries.fromTsv("Apple\tApfel");
+ GlossaryEntries entries2 = GlossaryEntries.fromTsv("Banana\tBanane");
+
+ GlossaryInfo glossary1 = translator.createGlossary(glossaryName1, "en", "de", entries1);
+ GlossaryInfo glossary2 = translator.createGlossary(glossaryName2, "en", "de", entries2);
+
+ List textsEn =
+ new ArrayList() {
+ {
+ add("Apple");
+ add("Banana");
+ }
+ };
+ List textsDe =
+ new ArrayList() {
+ {
+ add("Apfel");
+ add("Banane");
+ }
+ };
+
+ // Using a list of glossary IDs
+ List result =
+ translator.translateText(
+ textsEn,
+ "en",
+ "de",
+ new TextTranslationOptions()
+ .setGlossaryIds(glossary1.getGlossaryId(), glossary2.getGlossaryId()));
+ Assertions.assertArrayEquals(
+ textsDe.toArray(), result.stream().map(TextResult::getText).toArray());
+
+ // Using GlossaryInfo objects
+ result =
+ translator.translateText(
+ textsEn,
+ "en",
+ "de",
+ new TextTranslationOptions().setGlossaries(glossary1, glossary2));
+ Assertions.assertArrayEquals(
+ textsDe.toArray(), result.stream().map(TextResult::getText).toArray());
+ }
+ }
+
+ @Test
+ void testGlossaryIdsTranslateDocument() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup1 = new GlossaryCleanupUtility(translator, "1");
+ GlossaryCleanupUtility cleanup2 = new GlossaryCleanupUtility(translator, "2")) {
+ String glossaryName1 = cleanup1.getGlossaryName();
+ String glossaryName2 = cleanup2.getGlossaryName();
+ File inputFile = createInputFile("artist\nprize");
+ File outputFile = createOutputFile();
+ String expectedOutput = "Maler\nGewinn";
+
+ GlossaryEntries entries1 = GlossaryEntries.fromTsv("artist\tMaler");
+ GlossaryEntries entries2 = GlossaryEntries.fromTsv("prize\tGewinn");
+
+ GlossaryInfo glossary1 =
+ translator.createGlossary(glossaryName1, sourceLang, targetLang, entries1);
+ GlossaryInfo glossary2 =
+ translator.createGlossary(glossaryName2, sourceLang, targetLang, entries2);
+
+ translator.translateDocument(
+ inputFile,
+ outputFile,
+ sourceLang,
+ targetLang,
+ new DocumentTranslationOptions()
+ .setGlossaryIds(glossary1.getGlossaryId(), glossary2.getGlossaryId()));
+ Assertions.assertEquals(expectedOutput, readFromFile(outputFile));
+ }
+ }
+
+ @Test
+ void testGlossaryIdsAndGlossaryIdMutuallyExclusive() throws Exception {
+ Translator translator = createTranslator();
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ translator.translateText(
+ "test",
+ "en",
+ "de",
+ new TextTranslationOptions()
+ .setGlossaryId(nonexistentGlossaryId)
+ .setGlossaryIds(nonexistentGlossaryId)));
+ Assertions.assertTrue(
+ exception.getMessage().contains("cannot be used together with glossaryId"));
+ }
+
+ @Test
+ void testGlossaryIdsTooMany() throws Exception {
+ Translator translator = createTranslator();
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ translator.translateText(
+ "test",
+ "en",
+ "de",
+ new TextTranslationOptions()
+ .setGlossaryIds("id1", "id2", "id3", "id4", "id5", "id6")));
+ Assertions.assertTrue(exception.getMessage().contains("more than 5"));
+ }
+
+ @Test
+ void testGlossaryIdsMissingSourceLang() throws Exception {
+ Translator translator = createTranslator();
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ translator.translateText(
+ "test",
+ null,
+ "de",
+ new TextTranslationOptions().setGlossaryIds(nonexistentGlossaryId)));
+ Assertions.assertTrue(exception.getMessage().contains("sourceLang is required"));
+ }
}
diff --git a/deepl-java/src/test/java/com/deepl/api/MultilingualGlossaryCleanupUtility.java b/deepl-java/src/test/java/com/deepl/api/MultilingualGlossaryCleanupUtility.java
new file mode 100644
index 0000000..a2825f4
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/MultilingualGlossaryCleanupUtility.java
@@ -0,0 +1,63 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+public class MultilingualGlossaryCleanupUtility implements AutoCloseable {
+ private final String glossaryName;
+ private final DeepLClient deepLClient;
+
+ public MultilingualGlossaryCleanupUtility(DeepLClient deepLClient) {
+ this(deepLClient, "");
+ }
+
+ public MultilingualGlossaryCleanupUtility(DeepLClient deepLClient, String testNameSuffix) {
+ String callingFunc = getCallerFunction();
+ String uuid = UUID.randomUUID().toString();
+
+ this.glossaryName =
+ String.format("deepl-java-test-glossary: %s%s %s", callingFunc, testNameSuffix, uuid);
+ this.deepLClient = deepLClient;
+ }
+
+ public String getGlossaryName() {
+ return glossaryName;
+ }
+
+ @Override
+ public void close() throws Exception {
+ List glossaries = deepLClient.listMultilingualGlossaries();
+ for (MultilingualGlossaryInfo glossary : glossaries) {
+ if (Objects.equals(glossary.getName(), glossaryName)) {
+ try {
+ deepLClient.deleteMultilingualGlossary(glossary.getGlossaryId());
+ } catch (Exception exception) {
+ // Ignore
+ System.out.println(
+ "Failed to delete glossary: "
+ + glossaryName
+ + "\nException: "
+ + exception.getMessage());
+ }
+ }
+ }
+ }
+
+ private static String getCallerFunction() {
+ StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
+ // Find the first function outside this class following functions in this class
+ for (int i = 1; i < stacktrace.length; i++) {
+ if (!stacktrace[i].getClassName().equals(MultilingualGlossaryCleanupUtility.class.getName())
+ && stacktrace[i - 1]
+ .getClassName()
+ .equals(MultilingualGlossaryCleanupUtility.class.getName())) {
+ return stacktrace[i].getMethodName();
+ }
+ }
+ return "unknown";
+ }
+}
diff --git a/deepl-java/src/test/java/com/deepl/api/MultilingualGlossaryTest.java b/deepl-java/src/test/java/com/deepl/api/MultilingualGlossaryTest.java
new file mode 100644
index 0000000..45524e6
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/MultilingualGlossaryTest.java
@@ -0,0 +1,718 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import java.io.File;
+import java.util.*;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class MultilingualGlossaryTest extends TestBase {
+ private final String invalidGlossaryId = "invalid_glossary_id";
+ private final String nonexistentGlossaryId = "96ab91fd-e715-41a1-adeb-5d701f84a483";
+ private final String sourceLang = "en";
+ private final String targetLang = "de";
+
+ private final GlossaryEntries testEntries = GlossaryEntries.fromTsv("Hello\tHallo");
+ private final MultilingualGlossaryDictionaryEntries testGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, testEntries);
+
+ @Test
+ void testGlossaryCreate() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ GlossaryEntries entries = new GlossaryEntries(Collections.singletonMap("Hello", "Hallo"));
+ System.out.println(entries);
+ for (Map.Entry entry : entries.entrySet()) {
+ System.out.println(entry.getKey() + ":" + entry.getValue());
+ }
+ String glossaryName = cleanup.getGlossaryName();
+ List glossaryDicts =
+ Arrays.asList(
+ testGlossaryDict,
+ new MultilingualGlossaryDictionaryEntries(targetLang, sourceLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ Assertions.assertEquals(glossaryName, glossary.getName());
+ AssertGlossaryDictionariesEquivalent(glossaryDicts, glossary.getDictionaries());
+
+ MultilingualGlossaryInfo getResult =
+ deepLClient.getMultilingualGlossary(glossary.getGlossaryId());
+ AssertGlossaryDictionariesEquivalent(glossaryDicts, getResult.getDictionaries());
+ }
+ }
+
+ @Test
+ void testGlossaryCreateLarge() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+
+ Map entryPairs = new HashMap<>();
+ for (int i = 0; i < 10000; i++) {
+ entryPairs.put(String.format("Source-%d", i), String.format("Target-%d", i));
+ }
+ GlossaryEntries entries = new GlossaryEntries(entryPairs);
+ Assertions.assertTrue(entries.toTsv().length() > 100000);
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ Assertions.assertEquals(glossaryName, glossary.getName());
+ AssertGlossaryDictionariesEquivalent(glossaryDicts, glossary.getDictionaries());
+ }
+ }
+
+ @Test
+ void testGlossaryCreateCsv() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ Map expectedEntries = new HashMap<>();
+ expectedEntries.put("sourceEntry1", "targetEntry1");
+ expectedEntries.put("source\"Entry", "target,Entry");
+
+ String csvContent =
+ "sourceEntry1,targetEntry1,en,de\n\"source\"\"Entry\",\"target,Entry\",en,de";
+
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossaryFromCsv(
+ glossaryName, sourceLang, targetLang, csvContent);
+
+ MultilingualGlossaryDictionaryEntries createdGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(glossary, sourceLang, targetLang);
+ Assertions.assertEquals(expectedEntries, createdGlossaryDict.getEntries());
+ }
+ }
+
+ @Test
+ void testGlossaryCreateInvalid() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> deepLClient.createMultilingualGlossary("", Arrays.asList(testGlossaryDict)));
+ Assertions.assertThrows(
+ Exception.class,
+ () ->
+ deepLClient.createMultilingualGlossary(
+ glossaryName,
+ Arrays.asList(
+ new MultilingualGlossaryDictionaryEntries("en", "xx", testEntries))));
+ }
+ }
+
+ @Test
+ void testGlossaryGet() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ List glossaryDicts = Arrays.asList(testGlossaryDict);
+ MultilingualGlossaryInfo createdGlossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ MultilingualGlossaryInfo glossary =
+ deepLClient.getMultilingualGlossary(createdGlossary.getGlossaryId());
+ Assertions.assertEquals(createdGlossary.getGlossaryId(), glossary.getGlossaryId());
+ Assertions.assertEquals(glossaryName, glossary.getName());
+ AssertGlossaryDictionariesEquivalent(glossaryDicts, glossary.getDictionaries());
+ }
+ Assertions.assertThrows(
+ DeepLException.class, () -> deepLClient.getMultilingualGlossary(invalidGlossaryId));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class,
+ () -> deepLClient.getMultilingualGlossary(nonexistentGlossaryId));
+ }
+
+ @Test
+ void testGlossaryGetEntries() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ GlossaryEntries entries = new GlossaryEntries();
+ entries.put("Apple", "Apfel");
+ entries.put("Banana", "Banane");
+ entries.put("A%=&", "B&=%");
+ entries.put("\u0394\u3041", "\u6DF1");
+ entries.put("\uD83E\uDEA8", "\uD83E\uDEB5");
+
+ MultilingualGlossaryDictionaryEntries glossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ MultilingualGlossaryInfo createdGlossary =
+ deepLClient.createMultilingualGlossary(glossaryName, Arrays.asList(glossaryDict));
+ Assertions.assertEquals(1, createdGlossary.getDictionaries().size());
+ MultilingualGlossaryDictionaryInfo createdGlossaryDict =
+ createdGlossary.getDictionaries().get(0);
+
+ MultilingualGlossaryDictionaryEntries updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ createdGlossary, sourceLang, targetLang);
+ Assertions.assertEquals(entries, updatedGlossaryDict.getEntries());
+ updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ createdGlossary, sourceLang, targetLang);
+ Assertions.assertEquals(entries, updatedGlossaryDict.getEntries());
+ updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ createdGlossary, createdGlossaryDict);
+ Assertions.assertEquals(entries, updatedGlossaryDict.getEntries());
+ updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ createdGlossary.getGlossaryId(), createdGlossaryDict);
+ Assertions.assertEquals(entries, updatedGlossaryDict.getEntries());
+
+ Assertions.assertThrows(
+ DeepLException.class,
+ () ->
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ invalidGlossaryId, sourceLang, targetLang));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class,
+ () ->
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ nonexistentGlossaryId, sourceLang, targetLang));
+ Assertions.assertThrows(
+ Exception.class,
+ () -> deepLClient.getMultilingualGlossaryDictionaryEntries(createdGlossary, "en", "xx"));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ deepLClient.getMultilingualGlossaryDictionaryEntries(
+ createdGlossary, "", targetLang));
+ }
+ }
+
+ @Test
+ void testGlossaryList() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ deepLClient.createMultilingualGlossary(glossaryName, Arrays.asList(testGlossaryDict));
+
+ List glossaries = deepLClient.listMultilingualGlossaries();
+ Assertions.assertTrue(
+ glossaries.stream()
+ .anyMatch((glossaryInfo -> Objects.equals(glossaryInfo.getName(), glossaryName))));
+ }
+ }
+
+ @Test
+ void testGlossaryDelete() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, Arrays.asList(testGlossaryDict));
+
+ deepLClient.deleteMultilingualGlossary(glossary);
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class, () -> deepLClient.deleteMultilingualGlossary(glossary));
+
+ Assertions.assertThrows(
+ DeepLException.class, () -> deepLClient.deleteMultilingualGlossary(invalidGlossaryId));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class,
+ () -> deepLClient.deleteMultilingualGlossary(nonexistentGlossaryId));
+ }
+ }
+
+ @Test
+ void testGlossaryDictionaryDelete() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, Arrays.asList(testGlossaryDict));
+
+ deepLClient.deleteMultilingualGlossaryDictionary(glossary, sourceLang, targetLang);
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class,
+ () -> deepLClient.deleteMultilingualGlossaryDictionary(glossary, sourceLang, targetLang));
+
+ Assertions.assertThrows(
+ DeepLException.class,
+ () ->
+ deepLClient.deleteMultilingualGlossaryDictionary(
+ invalidGlossaryId, sourceLang, targetLang));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class,
+ () ->
+ deepLClient.deleteMultilingualGlossaryDictionary(
+ nonexistentGlossaryId, sourceLang, targetLang));
+ }
+ }
+
+ @Test
+ void testGlossaryReplaceDictionary() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ List glossaryDicts = Arrays.asList(testGlossaryDict);
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ GlossaryEntries newEntries = new GlossaryEntries();
+
+ newEntries.put("key1", "value1");
+ MultilingualGlossaryDictionaryEntries newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, newEntries);
+ MultilingualGlossaryDictionaryInfo updatedGlossary =
+ deepLClient.replaceMultilingualGlossaryDictionary(glossary, newGlossaryDict);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), Arrays.asList(updatedGlossary));
+
+ newEntries.put("key2", "value2");
+ newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, newEntries);
+ updatedGlossary =
+ deepLClient.replaceMultilingualGlossaryDictionary(
+ glossary.getGlossaryId(), newGlossaryDict);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), Arrays.asList(updatedGlossary));
+
+ newEntries.put("key3", "value3");
+ newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, newEntries);
+ updatedGlossary =
+ deepLClient.replaceMultilingualGlossaryDictionary(
+ glossary.getGlossaryId(), sourceLang, targetLang, newEntries);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), Arrays.asList(updatedGlossary));
+
+ newEntries.put("key4", "value4");
+ newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, newEntries);
+ updatedGlossary =
+ deepLClient.replaceMultilingualGlossaryDictionary(
+ glossary, sourceLang, targetLang, newEntries);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), Arrays.asList(updatedGlossary));
+ }
+ }
+
+ @Test
+ void testGlossaryReplaceDictionaryReplacesExistingEntries() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ List glossaryDicts = Arrays.asList(testGlossaryDict);
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ GlossaryEntries newEntries = new GlossaryEntries();
+ newEntries.put("key1", "value1");
+ newEntries.put("key2", "value2");
+ MultilingualGlossaryDictionaryEntries newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, newEntries);
+ deepLClient.replaceMultilingualGlossaryDictionary(glossary, newGlossaryDict);
+
+ MultilingualGlossaryDictionaryEntries updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(glossary, sourceLang, targetLang);
+ Assertions.assertEquals(newEntries, updatedGlossaryDict.getEntries());
+ }
+ }
+
+ @Test
+ void testGlossaryReplaceDictionaryFromCsvReplacesExistingEntries() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ List glossaryDicts = Arrays.asList(testGlossaryDict);
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ GlossaryEntries newEntries = new GlossaryEntries();
+ newEntries.put("key1", "value1");
+ newEntries.put("key2", "value2");
+ String csvContent = "key1,value1\nkey2,value2";
+ deepLClient.replaceMultilingualGlossaryDictionaryFromCsv(
+ glossary.getGlossaryId(), sourceLang, targetLang, csvContent);
+
+ MultilingualGlossaryDictionaryEntries updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(glossary, sourceLang, targetLang);
+ Assertions.assertEquals(newEntries, updatedGlossaryDict.getEntries());
+ }
+ }
+
+ @Test
+ void testGlossaryUpdateDictionary() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ GlossaryEntries entries = new GlossaryEntries(Collections.singletonMap("key1", "value1"));
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ entries = new GlossaryEntries(Collections.singletonMap("key1", "value2"));
+ MultilingualGlossaryDictionaryEntries newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ MultilingualGlossaryInfo updatedGlossary =
+ deepLClient.updateMultilingualGlossaryDictionary(glossary, newGlossaryDict);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), updatedGlossary.getDictionaries());
+
+ entries = new GlossaryEntries(Collections.singletonMap("key1", "value3"));
+ newGlossaryDict = new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ updatedGlossary =
+ deepLClient.updateMultilingualGlossaryDictionary(
+ glossary.getGlossaryId(), newGlossaryDict);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), updatedGlossary.getDictionaries());
+
+ entries = new GlossaryEntries(Collections.singletonMap("key1", "value4"));
+ newGlossaryDict = new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ updatedGlossary =
+ deepLClient.updateMultilingualGlossaryDictionary(
+ glossary.getGlossaryId(), sourceLang, targetLang, entries);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), updatedGlossary.getDictionaries());
+
+ entries.put("key1", "value5");
+ newGlossaryDict = new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ updatedGlossary =
+ deepLClient.updateMultilingualGlossaryDictionary(
+ glossary, sourceLang, targetLang, entries);
+ AssertGlossaryDictionariesEquivalent(
+ Arrays.asList(newGlossaryDict), updatedGlossary.getDictionaries());
+ }
+ }
+
+ @Test
+ void testGlossaryUpdateDictionaryUpdatesExistingEntries() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+
+ GlossaryEntries entries = new GlossaryEntries();
+ entries.put("key1", "value1");
+ entries.put("key2", "value2");
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ GlossaryEntries newEntries = new GlossaryEntries();
+ newEntries.put("key1", "updatedValue1");
+ newEntries.put("newKey", "newValue");
+ MultilingualGlossaryDictionaryEntries newGlossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, newEntries);
+ deepLClient.updateMultilingualGlossaryDictionary(glossary, newGlossaryDict);
+
+ /* We expect the entries to be the newly updated entries plus the old key2/value2 entry that was unchanged */
+ GlossaryEntries expectedEntries = newEntries;
+ expectedEntries.put("key2", "value2");
+
+ MultilingualGlossaryDictionaryEntries updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(glossary, sourceLang, targetLang);
+ Assertions.assertEquals(expectedEntries, updatedGlossaryDict.getEntries());
+ }
+ }
+
+ @Test
+ void testGlossaryUpdateDictionaryFromCsvUpdatesExistingEntries() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+
+ GlossaryEntries entries = new GlossaryEntries();
+ entries.put("key1", "value1");
+ entries.put("key2", "value2");
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, glossaryDicts);
+
+ GlossaryEntries csvEntries = new GlossaryEntries();
+ csvEntries.put("key1", "updatedValue1");
+ csvEntries.put("newKey", "newValue");
+ String csvContent = "key1,updatedValue1\nnewKey,newValue";
+ deepLClient.updateMultilingualGlossaryDictionaryFromCsv(
+ glossary.getGlossaryId(), sourceLang, targetLang, csvContent);
+
+ /* We expect the entries to be the newly updated entries plus the old key2/value2 entry that was unchanged */
+ GlossaryEntries expectedEntries = csvEntries;
+ expectedEntries.put("key2", "value2");
+
+ MultilingualGlossaryDictionaryEntries updatedGlossaryDict =
+ deepLClient.getMultilingualGlossaryDictionaryEntries(glossary, sourceLang, targetLang);
+ Assertions.assertEquals(expectedEntries, updatedGlossaryDict.getEntries());
+ }
+ }
+
+ @Test
+ void testGlossaryUpdateName() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String originalGlossaryName = "original glossary name";
+
+ GlossaryEntries entries = new GlossaryEntries();
+ entries.put("key1", "value1");
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(originalGlossaryName, glossaryDicts);
+
+ String glossaryName = cleanup.getGlossaryName();
+ MultilingualGlossaryInfo updatedGlossary =
+ deepLClient.updateMultilingualGlossaryName(glossary.getGlossaryId(), glossaryName);
+
+ Assertions.assertEquals(glossaryName, updatedGlossary.getName());
+ }
+ }
+
+ @Test
+ void testGlossaryUpdateNameWithSpecialCharsIsProperlyEncoded() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ GlossaryEntries entries = new GlossaryEntries();
+ entries.put("key1", "value1");
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries));
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(cleanup.getGlossaryName(), glossaryDicts);
+
+ String nameWithSpecialChars = "Name with special chars: é &foo=bar \"quoted\"";
+ MultilingualGlossaryInfo updatedGlossary =
+ deepLClient.updateMultilingualGlossaryName(
+ glossary.getGlossaryId(), nameWithSpecialChars);
+
+ Assertions.assertEquals(nameWithSpecialChars, updatedGlossary.getName());
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateTextSentence() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ GlossaryEntries entries =
+ new GlossaryEntries() {
+ {
+ put("artist", "Maler");
+ put("prize", "Gewinn");
+ }
+ };
+ String inputText = "The artist was awarded a prize.";
+ MultilingualGlossaryDictionaryEntries glossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, Arrays.asList(glossaryDict));
+
+ TextResult result =
+ deepLClient.translateText(
+ inputText,
+ sourceLang,
+ targetLang,
+ new TextTranslationOptions().setGlossary(glossary.getGlossaryId()));
+ if (!isMockServer) {
+ Assertions.assertTrue(result.getText().contains("Maler"));
+ Assertions.assertTrue(result.getText().contains("Gewinn"));
+ }
+
+ // It is also possible to specify GlossaryInfo
+ result =
+ deepLClient.translateText(
+ inputText,
+ sourceLang,
+ targetLang,
+ new TextTranslationOptions().setGlossary(glossary));
+ if (!isMockServer) {
+ Assertions.assertTrue(result.getText().contains("Maler"));
+ Assertions.assertTrue(result.getText().contains("Gewinn"));
+ }
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateTextBasic() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ List textsEn =
+ new ArrayList() {
+ {
+ add("Apple");
+ add("Banana");
+ }
+ };
+ List textsDe =
+ new ArrayList() {
+ {
+ add("Apfel");
+ add("Banane");
+ }
+ };
+ GlossaryEntries glossaryEntriesEnDe = new GlossaryEntries();
+ GlossaryEntries glossaryEntriesDeEn = new GlossaryEntries();
+ for (int i = 0; i < textsEn.size(); i++) {
+ glossaryEntriesEnDe.put(textsEn.get(i), textsDe.get(i));
+ glossaryEntriesDeEn.put(textsDe.get(i), textsEn.get(i));
+ }
+
+ MultilingualGlossaryDictionaryEntries glossaryDictEnDe =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, glossaryEntriesEnDe);
+ MultilingualGlossaryDictionaryEntries glossaryDictDeEn =
+ new MultilingualGlossaryDictionaryEntries(targetLang, sourceLang, glossaryEntriesDeEn);
+
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(
+ glossaryName, Arrays.asList(glossaryDictEnDe, glossaryDictDeEn));
+
+ List result =
+ deepLClient.translateText(
+ textsEn, "en", "de", new TextTranslationOptions().setGlossary(glossary));
+ Assertions.assertArrayEquals(
+ textsDe.toArray(), result.stream().map(TextResult::getText).toArray());
+
+ result =
+ deepLClient.translateText(
+ textsDe,
+ "de",
+ "en-US",
+ new TextTranslationOptions().setGlossary(glossary.getGlossaryId()));
+ Assertions.assertArrayEquals(
+ textsEn.toArray(), result.stream().map(TextResult::getText).toArray());
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateDocument() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+ File inputFile = createInputFile("artist\nprize");
+ File outputFile = createOutputFile();
+ String expectedOutput = "Maler\nGewinn";
+ GlossaryEntries entries =
+ new GlossaryEntries() {
+ {
+ put("artist", "Maler");
+ put("prize", "Gewinn");
+ }
+ };
+ MultilingualGlossaryDictionaryEntries glossaryDict =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, entries);
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(glossaryName, Arrays.asList(glossaryDict));
+
+ deepLClient.translateDocument(
+ inputFile,
+ outputFile,
+ sourceLang,
+ targetLang,
+ new DocumentTranslationOptions().setGlossary(glossary));
+ Assertions.assertEquals(expectedOutput, readFromFile(outputFile));
+ boolean ignored = outputFile.delete();
+
+ deepLClient.translateDocument(
+ inputFile,
+ outputFile,
+ sourceLang,
+ targetLang,
+ new DocumentTranslationOptions().setGlossary(glossary.getGlossaryId()));
+ Assertions.assertEquals(expectedOutput, readFromFile(outputFile));
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateTextInvalid() throws Exception {
+ DeepLClient deepLClient = createDeepLClient();
+ try (MultilingualGlossaryCleanupUtility cleanup =
+ new MultilingualGlossaryCleanupUtility(deepLClient)) {
+ String glossaryName = cleanup.getGlossaryName();
+
+ MultilingualGlossaryDictionaryEntries glossaryDictEnDe =
+ new MultilingualGlossaryDictionaryEntries(sourceLang, targetLang, testEntries);
+ MultilingualGlossaryDictionaryEntries glossaryDictDeEn =
+ new MultilingualGlossaryDictionaryEntries(targetLang, sourceLang, testEntries);
+ MultilingualGlossaryInfo glossary =
+ deepLClient.createMultilingualGlossary(
+ glossaryName, Arrays.asList(glossaryDictEnDe, glossaryDictDeEn));
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ deepLClient.translateText(
+ "test", null, "de", new TextTranslationOptions().setGlossary(glossary)));
+ Assertions.assertTrue(exception.getMessage().contains("sourceLang is required"));
+
+ exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ deepLClient.translateText(
+ "test", "de", "en", new TextTranslationOptions().setGlossary(glossary)));
+ Assertions.assertTrue(exception.getMessage().contains("targetLang=\"en\" is not allowed"));
+ }
+ }
+
+ /**
+ * Utility function for determining if a list of MultilingualGlossaryDictionaryEntries objects
+ * (that have entries) matches a list of MultilingualGlossaryDictionaryInfo (that do not contain
+ * entries, but just a count of the number of entries for that glossary dictionary
+ */
+ private void AssertGlossaryDictionariesEquivalent(
+ List expectedDicts,
+ List actualDicts) {
+ Assertions.assertEquals(expectedDicts.size(), actualDicts.size());
+ for (MultilingualGlossaryDictionaryEntries expectedDict : expectedDicts) {
+ MultilingualGlossaryDictionaryInfo actualDict =
+ findMatchingDictionary(
+ actualDicts,
+ expectedDict.getSourceLanguageCode(),
+ expectedDict.getTargetLanguageCode());
+
+ Assertions.assertEquals(
+ expectedDict.getSourceLanguageCode().toLowerCase(),
+ actualDict.getSourceLanguageCode().toLowerCase());
+ Assertions.assertEquals(
+ expectedDict.getTargetLanguageCode().toLowerCase(),
+ actualDict.getTargetLanguageCode().toLowerCase());
+ Assertions.assertEquals(
+ expectedDict.getEntries().entrySet().size(), actualDict.getEntryCount());
+ }
+ }
+
+ private MultilingualGlossaryDictionaryInfo findMatchingDictionary(
+ List glossaryDicts,
+ String sourceLang,
+ String targetLang) {
+ String lowerCaseSourceLang = sourceLang.toLowerCase();
+ String lowerCaseTargetLang = targetLang.toLowerCase();
+ for (MultilingualGlossaryDictionaryInfo glossaryDict : glossaryDicts) {
+ if (glossaryDict.getSourceLanguageCode().equals(lowerCaseSourceLang)
+ && glossaryDict.getTargetLanguageCode().equals(lowerCaseTargetLang)) {
+ return glossaryDict;
+ }
+ }
+ Assertions.fail("glossary did not contain expected language pair $sourceLang->$targetLang");
+ return null;
+ }
+}
diff --git a/deepl-java/src/test/java/com/deepl/api/RephraseTextTest.java b/deepl-java/src/test/java/com/deepl/api/RephraseTextTest.java
new file mode 100644
index 0000000..210afbd
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/RephraseTextTest.java
@@ -0,0 +1,66 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import java.util.*;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class RephraseTextTest extends TestBase {
+
+ @Test
+ void testSingleText() throws DeepLException, InterruptedException {
+ String inputText = exampleText.get("en");
+ DeepLClient client = createDeepLClient();
+ WriteResult result = client.rephraseText(inputText, "EN-GB", null);
+ this.checkSanityOfImprovements(inputText, result, "EN", "EN-GB", 0.2f);
+ }
+
+ @Test
+ void testTextArray() throws DeepLException, InterruptedException {
+ DeepLClient client = createDeepLClient();
+ List texts = new ArrayList<>();
+ texts.add(exampleText.get("en"));
+ texts.add(exampleText.get("en"));
+ List results = client.rephraseText(texts, "EN-GB", null);
+ for (int i = 0; i < texts.size(); i++) {
+ this.checkSanityOfImprovements(texts.get(i), results.get(i), "EN", "EN-GB", 0.2f);
+ }
+ }
+
+ @Test
+ void testBusinessStyle() throws DeepLException, InterruptedException {
+ String inputText =
+ "As Gregor Samsa awoke one morning from uneasy dreams he found himself transformed in his bed into a gigantic insect.";
+ DeepLClient client = createDeepLClient();
+ TextRephraseOptions options =
+ (new TextRephraseOptions()).setWritingStyle(WritingStyle.Business.getValue());
+ WriteResult result = client.rephraseText(inputText, "EN-GB", options);
+ if (!isMockServer) {
+ this.checkSanityOfImprovements(inputText, result, "EN", "EN-GB", 0.2f);
+ }
+ }
+
+ protected void checkSanityOfImprovements(
+ String inputText,
+ WriteResult result,
+ String expectedSourceLanguageUppercase,
+ String expectedTargetLanguageUppercase,
+ float epsilon) {
+ Assertions.assertEquals(
+ expectedSourceLanguageUppercase, result.getDetectedSourceLanguage().toUpperCase());
+ Assertions.assertEquals(
+ expectedTargetLanguageUppercase, result.getTargetLanguage().toUpperCase());
+ int nImproved = result.getText().length();
+ int nOriginal = inputText.length();
+ Assertions.assertTrue(
+ 1 / (1 + epsilon) <= ((float) nImproved) / nOriginal,
+ "Improved text is too short compared to original, improved:\n"
+ + result.getText()
+ + "\n, original:\n"
+ + inputText);
+ Assertions.assertTrue(
+ nImproved / nOriginal <= (1 + epsilon), "Improved text is too long compared to original");
+ }
+}
diff --git a/deepl-java/src/test/java/com/deepl/api/SessionOptions.java b/deepl-java/src/test/java/com/deepl/api/SessionOptions.java
index 83d0116..1b45722 100644
--- a/deepl-java/src/test/java/com/deepl/api/SessionOptions.java
+++ b/deepl-java/src/test/java/com/deepl/api/SessionOptions.java
@@ -12,12 +12,13 @@ public class SessionOptions {
// Mock server session options
public Integer noResponse;
public Integer respondWith429;
- public Integer initCharacterLimit;
- public Integer initDocumentLimit;
- public Integer initTeamDocumentLimit;
+ public Long initCharacterLimit;
+ public Long initDocumentLimit;
+ public Long initTeamDocumentLimit;
public Integer documentFailure;
public Duration documentQueueTime;
public Duration documentTranslateTime;
+ public Integer translationMemoryJobProcessingPolls;
public Boolean expectProxy;
public boolean randomAuthKey;
@@ -59,6 +60,11 @@ public Map createSessionHeaders() {
"mock-server-session-doc-translate-time",
Long.toString(documentTranslateTime.toMillis()));
}
+ if (translationMemoryJobProcessingPolls != null) {
+ headers.put(
+ "mock-server-session-tm-job-processing-polls",
+ translationMemoryJobProcessingPolls.toString());
+ }
if (expectProxy != null) {
headers.put("mock-server-session-expect-proxy", expectProxy ? "1" : "0");
}
@@ -76,17 +82,17 @@ public SessionOptions setRespondWith429(int respondWith429) {
return this;
}
- public SessionOptions setInitCharacterLimit(int initCharacterLimit) {
+ public SessionOptions setInitCharacterLimit(long initCharacterLimit) {
this.initCharacterLimit = initCharacterLimit;
return this;
}
- public SessionOptions setInitDocumentLimit(int initDocumentLimit) {
+ public SessionOptions setInitDocumentLimit(long initDocumentLimit) {
this.initDocumentLimit = initDocumentLimit;
return this;
}
- public SessionOptions setInitTeamDocumentLimit(int initTeamDocumentLimit) {
+ public SessionOptions setInitTeamDocumentLimit(long initTeamDocumentLimit) {
this.initTeamDocumentLimit = initTeamDocumentLimit;
return this;
}
@@ -106,6 +112,16 @@ public SessionOptions setDocumentTranslateTime(Duration documentTranslateTime) {
return this;
}
+ /**
+ * Makes translation memory import and export jobs report their non-terminal status the given
+ * number of times before completing, so that polling loops are exercised.
+ */
+ public SessionOptions setTranslationMemoryJobProcessingPolls(
+ int translationMemoryJobProcessingPolls) {
+ this.translationMemoryJobProcessingPolls = translationMemoryJobProcessingPolls;
+ return this;
+ }
+
public SessionOptions setExpectProxy(boolean expectProxy) {
this.expectProxy = expectProxy;
return this;
diff --git a/deepl-java/src/test/java/com/deepl/api/StyleRuleTest.java b/deepl-java/src/test/java/com/deepl/api/StyleRuleTest.java
new file mode 100644
index 0000000..5f8d371
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/StyleRuleTest.java
@@ -0,0 +1,233 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.jupiter.api.*;
+
+public class StyleRuleTest extends TestBase {
+ private static final String DEFAULT_STYLE_ID = "dca2e053-8ae5-45e6-a0d2-881156e7f4e4";
+
+ @Test
+ void testGetAllStyleRules() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ List styleRules = client.getAllStyleRules(0, 10, true);
+
+ Assertions.assertNotNull(styleRules);
+ Assertions.assertFalse(styleRules.isEmpty());
+ Assertions.assertEquals(DEFAULT_STYLE_ID, styleRules.get(0).getStyleId());
+ Assertions.assertEquals("Default Style Rule", styleRules.get(0).getName());
+ Assertions.assertNotNull(styleRules.get(0).getCreationTime());
+ Assertions.assertNotNull(styleRules.get(0).getUpdatedTime());
+ Assertions.assertEquals("en", styleRules.get(0).getLanguage());
+ Assertions.assertEquals(1, styleRules.get(0).getVersion());
+ Assertions.assertNotNull(styleRules.get(0).getConfiguredRules());
+ Assertions.assertNotNull(styleRules.get(0).getCustomInstructions());
+ }
+
+ @Test
+ void testGetAllStyleRulesWithoutDetailed() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ List styleRules = client.getAllStyleRules();
+
+ Assertions.assertNotNull(styleRules);
+ Assertions.assertFalse(styleRules.isEmpty());
+ Assertions.assertEquals(DEFAULT_STYLE_ID, styleRules.get(0).getStyleId());
+ Assertions.assertNull(styleRules.get(0).getConfiguredRules());
+ Assertions.assertNull(styleRules.get(0).getCustomInstructions());
+ }
+
+ @Test
+ void testStyleRuleCrud() throws Exception {
+ DeepLClient client = createDeepLClient();
+
+ // Create
+ StyleRuleInfo rule = client.createStyleRule("Test Rule", "en", null, null);
+ Assertions.assertNotNull(rule.getStyleId());
+ Assertions.assertEquals("Test Rule", rule.getName());
+
+ String styleId = rule.getStyleId();
+
+ // Get
+ StyleRuleInfo retrieved = client.getStyleRule(styleId);
+ Assertions.assertEquals(styleId, retrieved.getStyleId());
+
+ // Update name
+ StyleRuleInfo updated = client.updateStyleRuleName(styleId, "Updated Name");
+ Assertions.assertEquals("Updated Name", updated.getName());
+
+ // Update configured rules
+ Map datesAndTimes = new HashMap<>();
+ datesAndTimes.put("calendar_era", "use_bc_and_ad");
+ StyleRuleInfo configuredResult =
+ client.updateStyleRuleConfiguredRules(
+ styleId, new ConfiguredRules(datesAndTimes, null, null, null, null, null, null));
+ Assertions.assertEquals(styleId, configuredResult.getStyleId());
+
+ // Create custom instruction
+ CustomInstruction instruction =
+ client.createStyleRuleCustomInstruction(styleId, "Test Label", "Test prompt", null);
+ Assertions.assertNotNull(instruction.getId());
+ Assertions.assertEquals("Test Label", instruction.getLabel());
+
+ String instructionId = instruction.getId();
+
+ // Get custom instruction
+ CustomInstruction retrievedInstruction =
+ client.getStyleRuleCustomInstruction(styleId, instructionId);
+ Assertions.assertEquals("Test Label", retrievedInstruction.getLabel());
+
+ // Update custom instruction
+ CustomInstruction updatedInstruction =
+ client.updateStyleRuleCustomInstruction(
+ styleId, instructionId, "Updated Label", "Updated prompt", null);
+ Assertions.assertEquals("Updated Label", updatedInstruction.getLabel());
+
+ // Delete custom instruction
+ client.deleteStyleRuleCustomInstruction(styleId, instructionId);
+
+ // Delete style rule
+ client.deleteStyleRule(styleId);
+ }
+
+ @Test
+ void testStyleRuleValidation() throws Exception {
+ DeepLClient client = createDeepLClient();
+
+ // createStyleRule
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () -> client.createStyleRule("", "en", null, null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () -> client.createStyleRule("Test", "", null, null));
+
+ // getStyleRule
+ Assertions.assertThrows(IllegalArgumentException.class, () -> client.getStyleRule(""));
+
+ // updateStyleRuleName
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () -> client.updateStyleRuleName("", "New Name"));
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () -> client.updateStyleRuleName("some-id", ""));
+
+ // deleteStyleRule
+ Assertions.assertThrows(IllegalArgumentException.class, () -> client.deleteStyleRule(""));
+
+ // updateStyleRuleConfiguredRules
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ client.updateStyleRuleConfiguredRules(
+ "", new ConfiguredRules(null, null, null, null, null, null, null)));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.updateStyleRuleConfiguredRules("some-id", null));
+
+ // createStyleRuleCustomInstruction
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.createStyleRuleCustomInstruction("", "L", "P", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.createStyleRuleCustomInstruction("some-id", "", "P", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.createStyleRuleCustomInstruction("some-id", "L", "", null));
+
+ // getStyleRuleCustomInstruction
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () -> client.getStyleRuleCustomInstruction("", "instr-id"));
+ Assertions.assertThrows(
+ IllegalArgumentException.class, () -> client.getStyleRuleCustomInstruction("some-id", ""));
+
+ // updateStyleRuleCustomInstruction
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.updateStyleRuleCustomInstruction("", "instr-id", "L", "P", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.updateStyleRuleCustomInstruction("some-id", "", "L", "P", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.updateStyleRuleCustomInstruction("some-id", "instr-id", "", "P", null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.updateStyleRuleCustomInstruction("some-id", "instr-id", "L", "", null));
+
+ // deleteStyleRuleCustomInstruction
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.deleteStyleRuleCustomInstruction("", "instr-id"));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.deleteStyleRuleCustomInstruction("some-id", ""));
+ }
+
+ @Test
+ void testTranslateTextWithStyleId() throws Exception {
+ // Note: this test may use the mock server that will not translate the text
+ // with a style rule, therefore we do not check the translated result.
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ String text = "Hallo, Welt!";
+
+ TextResult result =
+ client.translateText(
+ text, "de", "en-US", new TextTranslationOptions().setStyleId(DEFAULT_STYLE_ID));
+
+ Assertions.assertNotNull(result);
+ }
+
+ @Test
+ void testTranslateTextWithStyleRuleInfo() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ List styleRules = client.getAllStyleRules();
+ StyleRuleInfo rule = styleRules.get(0);
+ String text = "Hallo, Welt!";
+
+ TextResult result =
+ client.translateText(text, "de", "en-US", new TextTranslationOptions().setStyleRule(rule));
+
+ Assertions.assertNotNull(result);
+ }
+
+ @Test
+ void testTranslateDocumentWithStyleId() throws Exception {
+ // Note: this test may use the mock server that will not translate the document
+ // with a style rule, therefore we do not check the translated result.
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ File inputFile = createInputFile("Hallo, Welt!");
+ File outputFile = createOutputFile();
+
+ client.translateDocument(
+ inputFile,
+ outputFile,
+ "de",
+ "en-US",
+ new DocumentTranslationOptions().setStyleId(DEFAULT_STYLE_ID));
+
+ Assertions.assertNotNull(readFromFile(outputFile));
+ }
+
+ @Test
+ void testTranslateDocumentWithStyleRuleInfo() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ List styleRules = client.getAllStyleRules();
+ StyleRuleInfo rule = styleRules.get(0);
+ File inputFile = createInputFile("Hallo, Welt!");
+ File outputFile = createOutputFile();
+
+ client.translateDocument(
+ inputFile, outputFile, "de", "en-US", new DocumentTranslationOptions().setStyleRule(rule));
+
+ Assertions.assertNotNull(readFromFile(outputFile));
+ }
+}
diff --git a/deepl-java/src/test/java/com/deepl/api/TestBase.java b/deepl-java/src/test/java/com/deepl/api/TestBase.java
index 9cbe60f..1ea1032 100644
--- a/deepl-java/src/test/java/com/deepl/api/TestBase.java
+++ b/deepl-java/src/test/java/com/deepl/api/TestBase.java
@@ -51,7 +51,7 @@ public class TestBase {
exampleText.put("ar", "شعاع البروتون");
exampleText.put("bg", "протонен лъч");
exampleText.put("cs", "protonový paprsek");
- exampleText.put("da", "protonstråle");
+ exampleText.put("da", "Protonstråle");
exampleText.put("de", "Protonenstrahl");
exampleText.put("el", "δέσμη πρωτονίων");
exampleText.put("en", "proton beam");
@@ -62,9 +62,9 @@ public class TestBase {
exampleText.put("fi", "protonisäde");
exampleText.put("fr", "faisceau de protons");
exampleText.put("hu", "protonnyaláb");
- exampleText.put("id", "berkas proton");
+ exampleText.put("id", "sinar proton");
exampleText.put("it", "fascio di protoni");
- exampleText.put("ja", "陽子ビーム");
+ exampleText.put("ja", "陽子線");
exampleText.put("ko", "양성자 빔");
exampleText.put("lt", "protonų spindulys");
exampleText.put("lv", "protonu staru kūlis");
@@ -73,13 +73,14 @@ public class TestBase {
exampleText.put("pl", "wiązka protonów");
exampleText.put("pt", "feixe de prótons");
exampleText.put("pt-BR", "feixe de prótons");
- exampleText.put("pt-PT", "feixe de prótons");
+ exampleText.put("pt-PT", "feixe de protões");
exampleText.put("ro", "fascicul de protoni");
- exampleText.put("ru", "протонный луч");
+ exampleText.put("ru", "протонный пучок");
exampleText.put("sk", "protónový lúč");
exampleText.put("sl", "protonski žarek");
exampleText.put("sv", "protonstråle");
exampleText.put("tr", "proton ışını");
+ exampleText.put("uk", "Протонний промінь");
exampleText.put("zh", "质子束");
String tmpdir = System.getProperty("java.io.tmpdir");
@@ -90,6 +91,7 @@ protected TestBase() {
tempDir = createTempDir();
}
+ // TODO: Delete `createTranslator` methods, replace with `createDeepLClient`
protected Translator createTranslator() {
SessionOptions sessionOptions = new SessionOptions();
return createTranslator(sessionOptions);
@@ -124,6 +126,40 @@ protected Translator createTranslator(
}
}
+ protected DeepLClient createDeepLClient() {
+ SessionOptions sessionOptions = new SessionOptions();
+ return createDeepLClient(sessionOptions);
+ }
+
+ protected DeepLClient createDeepLClient(SessionOptions sessionOptions) {
+ TranslatorOptions translatorOptions = new TranslatorOptions();
+ return createDeepLClient(sessionOptions, translatorOptions);
+ }
+
+ protected DeepLClient createDeepLClient(
+ SessionOptions sessionOptions, TranslatorOptions translatorOptions) {
+ Map headers = sessionOptions.createSessionHeaders();
+
+ if (translatorOptions.getServerUrl() == null) {
+ translatorOptions.setServerUrl(serverUrl);
+ }
+
+ if (translatorOptions.getHeaders() != null) {
+ headers.putAll(translatorOptions.getHeaders());
+ }
+ translatorOptions.setHeaders(headers);
+
+ String authKey = sessionOptions.randomAuthKey ? UUID.randomUUID().toString() : TestBase.authKey;
+
+ try {
+ return new DeepLClient(authKey, translatorOptions);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ System.exit(1);
+ return null;
+ }
+ }
+
protected String createTempDir() {
String newTempDir = tempDirBase + UUID.randomUUID();
boolean created = new File(newTempDir).mkdirs();
diff --git a/deepl-java/src/test/java/com/deepl/api/TranslateDocumentTest.java b/deepl-java/src/test/java/com/deepl/api/TranslateDocumentTest.java
index 54e789c..fd674d6 100644
--- a/deepl-java/src/test/java/com/deepl/api/TranslateDocumentTest.java
+++ b/deepl-java/src/test/java/com/deepl/api/TranslateDocumentTest.java
@@ -94,7 +94,7 @@ void testTranslateDocumentFormality() throws Exception {
"de",
new DocumentTranslationOptions().setFormality(Formality.More));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es Ihnen?", readFromFile(outputFile));
+ Assertions.assertTrue(readFromFile(outputFile).contains("Ihnen"));
}
outputFile.delete();
@@ -106,7 +106,7 @@ void testTranslateDocumentFormality() throws Exception {
"de",
new DocumentTranslationOptions().setFormality(Formality.Less));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es dir?", readFromFile(outputFile));
+ Assertions.assertTrue(readFromFile(outputFile).contains("dir"));
}
}
diff --git a/deepl-java/src/test/java/com/deepl/api/TranslateTextTest.java b/deepl-java/src/test/java/com/deepl/api/TranslateTextTest.java
index 666a4cf..3719e61 100644
--- a/deepl-java/src/test/java/com/deepl/api/TranslateTextTest.java
+++ b/deepl-java/src/test/java/com/deepl/api/TranslateTextTest.java
@@ -17,6 +17,7 @@ void testSingleText() throws DeepLException, InterruptedException {
TextResult result = translator.translateText(exampleText.get("en"), null, LanguageCode.German);
Assertions.assertEquals(exampleText.get("de"), result.getText());
Assertions.assertEquals("en", result.getDetectedSourceLanguage());
+ Assertions.assertEquals(exampleText.get("en").length(), result.getBilledCharacters());
}
@Test
@@ -145,9 +146,10 @@ void testFormality() throws DeepLException, InterruptedException {
translator.translateText(
"How are you?", null, "de", new TextTranslationOptions().setFormality(Formality.Less));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es dir?", result.getText());
+ Assertions.assertTrue(result.getText().contains("dir"));
}
+ // Default formality is automatic, so the output may be either formal or informal
result =
translator.translateText(
"How are you?",
@@ -155,14 +157,14 @@ void testFormality() throws DeepLException, InterruptedException {
"de",
new TextTranslationOptions().setFormality(Formality.Default));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es Ihnen?", result.getText());
+ Assertions.assertTrue(result.getText().contains("Ihnen") || result.getText().contains("dir"));
}
result =
translator.translateText(
"How are you?", null, "de", new TextTranslationOptions().setFormality(Formality.More));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es Ihnen?", result.getText());
+ Assertions.assertTrue(result.getText().contains("Ihnen"));
}
result =
@@ -172,7 +174,7 @@ void testFormality() throws DeepLException, InterruptedException {
"de",
new TextTranslationOptions().setFormality(Formality.PreferLess));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es dir?", result.getText());
+ Assertions.assertTrue(result.getText().contains("dir"));
}
result =
@@ -182,7 +184,7 @@ void testFormality() throws DeepLException, InterruptedException {
"de",
new TextTranslationOptions().setFormality(Formality.PreferMore));
if (!isMockServer) {
- Assertions.assertEquals("Wie geht es Ihnen?", result.getText());
+ Assertions.assertTrue(result.getText().contains("Ihnen"));
}
}
@@ -298,6 +300,38 @@ void testTagHandlingHTML() throws DeepLException, InterruptedException {
}
}
+ @Test
+ void testTagHandlingVersionV1() throws DeepLException, InterruptedException {
+ Translator translator = createTranslator();
+ String text = "Hello world
";
+
+ TextResult result =
+ translator.translateText(
+ text,
+ null,
+ "de",
+ new TextTranslationOptions().setTagHandling("html").setTagHandlingVersion("v1"));
+ Assertions.assertNotNull(result);
+ Assertions.assertNotNull(result.getText());
+ Assertions.assertFalse(result.getText().isEmpty());
+ }
+
+ @Test
+ void testTagHandlingVersionV2() throws DeepLException, InterruptedException {
+ Translator translator = createTranslator();
+ String text = "Hello world
";
+
+ TextResult result =
+ translator.translateText(
+ text,
+ null,
+ "de",
+ new TextTranslationOptions().setTagHandling("html").setTagHandlingVersion("v2"));
+ Assertions.assertNotNull(result);
+ Assertions.assertNotNull(result.getText());
+ Assertions.assertFalse(result.getText().isEmpty());
+ }
+
@Test
void testEmptyText() {
Translator translator = createTranslator();
@@ -329,4 +363,51 @@ void testMixedCaseLanguages() throws DeepLException, InterruptedException {
Assertions.assertEquals(exampleText.get("en-US"), result.getText().toLowerCase(Locale.ENGLISH));
Assertions.assertEquals("de", result.getDetectedSourceLanguage());
}
+
+ @Test
+ void testExtraBodyParams() throws DeepLException, InterruptedException {
+ Translator translator = createTranslator();
+
+ // Verifies that extra_body_parameters can override standard parameters like target_lang
+ Map extraParams = new HashMap<>();
+ extraParams.put("target_lang", "FR");
+ extraParams.put("debug", "1");
+
+ TextTranslationOptions options = new TextTranslationOptions();
+ options.setExtraBodyParameters(extraParams);
+
+ TextResult result = translator.translateText(exampleText.get("en"), null, "DE", options);
+
+ Assertions.assertEquals(exampleText.get("fr"), result.getText());
+ Assertions.assertEquals("en", result.getDetectedSourceLanguage());
+ Assertions.assertEquals(exampleText.get("en").length(), result.getBilledCharacters());
+ }
+
+ @Test
+ void testCustomInstructions() throws DeepLException, InterruptedException {
+ Translator translator = createTranslator();
+ String text = "Hello world. I am testing if custom instructions are working correctly.";
+
+ TextResult resultWithCustomInstructions =
+ translator.translateText(
+ text,
+ null,
+ "de",
+ new TextTranslationOptions()
+ .setCustomInstructions(
+ Collections.singletonList("Render the whole text in ALL CAPS")));
+
+ Assertions.assertNotNull(resultWithCustomInstructions.getText());
+ Assertions.assertEquals("en", resultWithCustomInstructions.getDetectedSourceLanguage());
+ if (!isMockServer) {
+ // Assert the instruction was actually applied, rather than that the output merely differs
+ // from an unconstrained translation. The previous version compared against a translation
+ // made without instructions and used "Use informal language" / "Be concise", but the
+ // default translation of this sentence is already informal and concise, so the API
+ // legitimately returned identical text and the comparison drifted with the model.
+ Assertions.assertEquals(
+ resultWithCustomInstructions.getText().toUpperCase(Locale.ROOT),
+ resultWithCustomInstructions.getText());
+ }
+ }
}
diff --git a/deepl-java/src/test/java/com/deepl/api/TranslationMemoryTest.java b/deepl-java/src/test/java/com/deepl/api/TranslationMemoryTest.java
new file mode 100644
index 0000000..0c0bd03
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/TranslationMemoryTest.java
@@ -0,0 +1,382 @@
+// Copyright 2025 DeepL SE (https://www.deepl.com)
+// Use of this source code is governed by an MIT
+// license that can be found in the LICENSE file.
+package com.deepl.api;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.time.Duration;
+import java.util.List;
+import org.junit.jupiter.api.*;
+
+public class TranslationMemoryTest extends TestBase {
+ private static final String DEFAULT_TM_ID = "a74d88fb-ed2a-4943-a664-a4512398b994";
+ private static final String UNKNOWN_ID = "00000000-0000-0000-0000-000000000000";
+ private static final String EXAMPLE_TMX =
+ "\n"
+ + ""
+ + "Hallo"
+ + "Hello"
+ + "\n";
+
+ @Test
+ void testListTranslationMemories() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ List translationMemories = client.listTranslationMemories(0, 10);
+
+ Assertions.assertNotNull(translationMemories);
+ Assertions.assertFalse(translationMemories.isEmpty());
+ Assertions.assertNotNull(translationMemories.get(0).getTranslationMemoryId());
+ Assertions.assertNotNull(translationMemories.get(0).getName());
+ Assertions.assertNotNull(translationMemories.get(0).getSourceLanguage());
+ Assertions.assertNotNull(translationMemories.get(0).getTargetLanguages());
+ }
+
+ @Test
+ void testTranslateTextWithTranslationMemoryId() throws Exception {
+ // Note: this test may use the mock server that will not translate the text
+ // with a translation memory, therefore we do not check the translated result.
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ String text = "Hallo, Welt!";
+
+ TextResult result =
+ client.translateText(
+ text,
+ "de",
+ "en-US",
+ new TextTranslationOptions().setTranslationMemoryId(DEFAULT_TM_ID));
+
+ Assertions.assertNotNull(result);
+ }
+
+ @Test
+ void testTranslateTextWithTranslationMemoryIdAndThreshold() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ String text = "Hallo, Welt!";
+
+ TextResult result =
+ client.translateText(
+ text,
+ "de",
+ "en-US",
+ new TextTranslationOptions()
+ .setTranslationMemoryId(DEFAULT_TM_ID)
+ .setTranslationMemoryThreshold(80));
+
+ Assertions.assertNotNull(result);
+ }
+
+ @Test
+ void testTranslateDocumentWithTranslationMemoryId() throws Exception {
+ // Note: this test may use the mock server that will not translate the document
+ // with a translation memory, therefore we do not check the translated result.
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ File inputFile = createInputFile("Hallo, Welt!");
+ File outputFile = createOutputFile();
+
+ client.translateDocument(
+ inputFile,
+ outputFile,
+ "de",
+ "en-US",
+ new DocumentTranslationOptions().setTranslationMemoryId(DEFAULT_TM_ID));
+
+ Assertions.assertNotNull(readFromFile(outputFile));
+ }
+
+ @Test
+ void testTranslateDocumentWithTranslationMemoryIdAndThreshold() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ File inputFile = createInputFile("Hallo, Welt!");
+ File outputFile = createOutputFile();
+
+ client.translateDocument(
+ inputFile,
+ outputFile,
+ "de",
+ "en-US",
+ new DocumentTranslationOptions()
+ .setTranslationMemoryId(DEFAULT_TM_ID)
+ .setTranslationMemoryThreshold(80));
+
+ Assertions.assertNotNull(readFromFile(outputFile));
+ }
+
+ @Test
+ void testTranslateDocumentWithTranslationMemoryInfo() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ List translationMemories = client.listTranslationMemories(0, 10);
+ TranslationMemoryInfo translationMemory = translationMemories.get(0);
+ File inputFile = createInputFile("Hallo, Welt!");
+ File outputFile = createOutputFile();
+
+ client.translateDocument(
+ inputFile,
+ outputFile,
+ "de",
+ "en-US",
+ new DocumentTranslationOptions().setTranslationMemory(translationMemory));
+
+ Assertions.assertNotNull(readFromFile(outputFile));
+ }
+
+ @Test
+ void testGetTranslationMemory() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ TranslationMemoryInfo translationMemory = client.getTranslationMemory(DEFAULT_TM_ID);
+
+ Assertions.assertEquals(DEFAULT_TM_ID, translationMemory.getTranslationMemoryId());
+ Assertions.assertNotNull(translationMemory.getName());
+ Assertions.assertEquals("de", translationMemory.getSourceLanguage());
+ Assertions.assertFalse(translationMemory.getTargetLanguages().isEmpty());
+ Assertions.assertTrue(translationMemory.getSegmentCount() > 0);
+ Assertions.assertNotNull(translationMemory.getCreationTime());
+ Assertions.assertNotNull(translationMemory.getUpdatedTime());
+ }
+
+ @Test
+ void testGetTranslationMemoryWithTranslationMemoryInfo() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ TranslationMemoryInfo listed = client.listTranslationMemories().get(0);
+
+ TranslationMemoryInfo translationMemory = client.getTranslationMemory(listed);
+
+ Assertions.assertEquals(
+ listed.getTranslationMemoryId(), translationMemory.getTranslationMemoryId());
+ }
+
+ @Test
+ void testGetTranslationMemoryWithUnknownId() {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ Assertions.assertThrows(NotFoundException.class, () -> client.getTranslationMemory(UNKNOWN_ID));
+ }
+
+ @Test
+ void testListTranslationMemorySegments() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ TranslationMemorySegments page = client.listTranslationMemorySegments(DEFAULT_TM_ID);
+
+ Assertions.assertFalse(page.getSegments().isEmpty());
+ Assertions.assertTrue(page.getSegmentCount() > 0);
+ TranslationMemorySegment segment = page.getSegments().get(0);
+ Assertions.assertNotNull(segment.getSourceSegmentId());
+ Assertions.assertNotNull(segment.getSourceText());
+ Assertions.assertFalse(segment.getTargets().isEmpty());
+ TranslationMemoryTargetSegment target = segment.getTargets().get(0);
+ Assertions.assertNotNull(target.getTargetSegmentId());
+ Assertions.assertNotNull(target.getTargetLanguage());
+ Assertions.assertNotNull(target.getTargetText());
+ }
+
+ @Test
+ void testListTranslationMemorySegmentsPagination() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ TranslationMemorySegments firstPage =
+ client.listTranslationMemorySegments(
+ DEFAULT_TM_ID, new TranslationMemorySegmentsOptions().setPageSize(5));
+
+ Assertions.assertEquals(5, firstPage.getSegments().size());
+ Assertions.assertNotNull(firstPage.getNextPageCursor());
+
+ TranslationMemorySegments secondPage =
+ client.listTranslationMemorySegments(
+ DEFAULT_TM_ID,
+ new TranslationMemorySegmentsOptions()
+ .setPageSize(5)
+ .setPageCursor(firstPage.getNextPageCursor()));
+
+ Assertions.assertFalse(secondPage.getSegments().isEmpty());
+ for (TranslationMemorySegment segment : secondPage.getSegments()) {
+ for (TranslationMemorySegment firstPageSegment : firstPage.getSegments()) {
+ Assertions.assertNotEquals(
+ firstPageSegment.getSourceSegmentId(), segment.getSourceSegmentId());
+ }
+ }
+ }
+
+ @Test
+ void testListTranslationMemorySegmentsFilter() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ TranslationMemorySegments unfiltered = client.listTranslationMemorySegments(DEFAULT_TM_ID);
+ TranslationMemorySegments filtered =
+ client.listTranslationMemorySegments(
+ DEFAULT_TM_ID, new TranslationMemorySegmentsOptions().setFilterText("Nummer 7"));
+
+ Assertions.assertTrue(filtered.getSegments().size() < unfiltered.getSegments().size());
+ // segmentCount is translation-memory-level metadata and unaffected by the filter
+ Assertions.assertEquals(unfiltered.getSegmentCount(), filtered.getSegmentCount());
+ }
+
+ @Test
+ void testImportTranslationMemoryFromFilepath() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ TranslationMemoryJob job =
+ client.importTranslationMemoryFromFilepath(createTmxFile(), "Imported TM");
+
+ Assertions.assertEquals(TranslationMemoryJob.Operation.Import, job.getOperation());
+ Assertions.assertEquals("translation_memory", job.getProduct());
+ Assertions.assertEquals(TranslationMemoryJobResult.Status.Completed, job.getStatus());
+ String translationMemoryId = job.getResult().getTranslationMemoryId();
+ Assertions.assertNotNull(translationMemoryId);
+
+ TranslationMemoryInfo imported = client.getTranslationMemory(translationMemoryId);
+ Assertions.assertEquals("Imported TM", imported.getName());
+
+ client.deleteTranslationMemory(imported);
+ }
+
+ @Test
+ void testCreateTranslationMemoryImportAwaitsUpload() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ TranslationMemoryImport translationMemoryImport =
+ client.createTranslationMemoryImport("example.tmx", 1024, null, "Awaiting Upload TM");
+
+ Assertions.assertNotNull(translationMemoryImport.getJobId());
+ Assertions.assertNotNull(translationMemoryImport.getUploadUrl());
+
+ TranslationMemoryJob job = client.getTranslationMemoryJob(translationMemoryImport.getJobId());
+ Assertions.assertEquals(TranslationMemoryJobResult.Status.AwaitingInput, job.getStatus());
+ Assertions.assertNotNull(job.getResult().getRequiredAction());
+ // A job whose file is never uploaded does not finish on its own, so waiting for it only
+ // stops when the caller's timeout is exceeded
+ Assertions.assertThrows(
+ DeepLException.class,
+ () ->
+ client.waitUntilTranslationMemoryJobDone(
+ translationMemoryImport.getJobId(), Duration.ofSeconds(1)));
+ }
+
+ @Test
+ void testWaitUntilTranslationMemoryJobDonePollsThroughAwaitingInput() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ // The job reports its non-terminal status once before completing
+ DeepLClient client =
+ createDeepLClient(new SessionOptions().setTranslationMemoryJobProcessingPolls(1));
+ File tmxFile = createTmxFile();
+ byte[] fileContent = Files.readAllBytes(tmxFile.toPath());
+
+ TranslationMemoryImport translationMemoryImport =
+ client.createTranslationMemoryImport(
+ tmxFile.getName(), fileContent.length, null, "Awaiting Input TM");
+ client.uploadTranslationMemoryFile(translationMemoryImport, fileContent);
+
+ // An uploaded import keeps reporting AwaitingInput for a while, because the API detects
+ // the upload asynchronously. Waiting must poll through that status instead of throwing.
+ long startTimeMillis = System.currentTimeMillis();
+ TranslationMemoryJob job =
+ client.waitUntilTranslationMemoryJobDone(
+ translationMemoryImport.getJobId(), Duration.ofSeconds(60));
+ long elapsedMillis = System.currentTimeMillis() - startTimeMillis;
+
+ Assertions.assertEquals(TranslationMemoryJobResult.Status.Completed, job.getStatus());
+ // The job was polled at least twice, so the first AwaitingInput response was polled through
+ Assertions.assertTrue(elapsedMillis >= 5000);
+ Assertions.assertNotNull(job.getResult().getTranslationMemoryId());
+
+ client.deleteTranslationMemory(job.getResult().getTranslationMemoryId());
+ }
+
+ @Test
+ void testCreateTranslationMemoryImportWithInvalidFile() {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.createTranslationMemoryImport("", 100, null, null));
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () -> client.createTranslationMemoryImport("example.tmx", 0, null, null));
+ }
+
+ @Test
+ void testExportTranslationMemoryToFilepath() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ String translationMemoryId = importTranslationMemory(client);
+ File outputFile = new File(tempDir + "/exported.tmx");
+
+ TranslationMemoryJob job =
+ client.exportTranslationMemoryToFilepath(translationMemoryId, outputFile);
+
+ Assertions.assertEquals(TranslationMemoryJob.Operation.Export, job.getOperation());
+ Assertions.assertEquals(TranslationMemoryJobResult.Status.Completed, job.getStatus());
+ Assertions.assertTrue(readFromFile(outputFile).contains(" client.getTranslationMemoryJob(UNKNOWN_ID));
+ }
+
+ @Test
+ void testDeleteTranslationMemory() throws Exception {
+ Assumptions.assumeTrue(isMockServer);
+ DeepLClient client = createDeepLClient();
+ String translationMemoryId = importTranslationMemory(client);
+
+ client.deleteTranslationMemory(translationMemoryId);
+
+ Assertions.assertThrows(
+ NotFoundException.class, () -> client.getTranslationMemory(translationMemoryId));
+ }
+
+ /** Imports the example TMX file and returns the ID of the resulting translation memory. */
+ private String importTranslationMemory(DeepLClient client) throws Exception {
+ TranslationMemoryJob job = client.importTranslationMemoryFromFilepath(createTmxFile());
+ return job.getResult().getTranslationMemoryId();
+ }
+
+ /** Writes the example TMX file into the temporary directory of this test. */
+ private File createTmxFile() throws Exception {
+ File tmxFile = new File(tempDir + "/example.tmx");
+ boolean ignored = tmxFile.delete();
+ writeToFile(tmxFile, EXAMPLE_TMX);
+ return tmxFile;
+ }
+}
diff --git a/upgrading_to_multilingual_glossaries.md b/upgrading_to_multilingual_glossaries.md
new file mode 100644
index 0000000..6a5acb6
--- /dev/null
+++ b/upgrading_to_multilingual_glossaries.md
@@ -0,0 +1,412 @@
+# Migration Documentation for Newest Glossary Functionality
+
+## 1. Overview of Changes
+
+The newest version of the Glossary APIs is the `/v3` endpoints, which introduce enhanced functionality:
+
+- **Support for Multilingual Glossaries**: The v3 endpoints allow for the creation of glossaries with multiple language
+ pairs, enhancing flexibility and usability.
+- **Editing Capabilities**: Users can now edit existing glossaries.
+
+To support these new v3 APIs, we have created new methods to interact with these new multilingual glossaries. Users are
+encouraged to transition to the new to take full advantage of these new features. However, for those who prefer to
+continue using the existing functionality, the `v2` methods for monolingual glossaries (e.g., `createGlossary()`,
+`getGlossary()`, etc.) remain available.
+
+## 2. Endpoint Changes
+
+| Monolingual glossary methods | Multilingual glossary methods | Changes Summary |
+|--------------------------------|----------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `createGlossary()` | `createMultilingualGlossary()` | Accepts a list of `MultilingualGlossaryDictionaryEntries` for multi-lingual support and now returns a `MultilingualGlossaryInfo` object. |
+| `createGlossaryFromCsv()` | `createMultilingualGlossaryFromCsv()` | Similar functionality, but now returns a `MultilingualGlossaryInfo` object |
+| `getGlossary()` | `getMultilingualGlossary()` | Similar functionality, but now returns `MultilingualGlossaryInfo`. Also can accept a `MultilingualGlossaryInfo` object as the glossary parameter instead of a `GlossaryInfo` object. |
+| `listGlossaries()` | `listMultilingualGlossaries()` | Similar functionality, but now returns a list of `MultilingualGlossaryInfo` objects. |
+| `getGlossaryEntries()` | `getMultilingualGlossaryDictionaryEntries()` | Requires specifying source and target languages. Also returns a `MultilingualGlossaryDictionaryEntriesResponse` object as the response. |
+| `deleteGlossary()` | `deleteMultilingualGlossary()` | Similar functionality, but now can accept a `MultilingualGlossaryInfo` object instead of a `GlossaryInfo` object when specifying the glossary. |
+
+## 3. Model Changes
+
+V2 glossaries are monolingual and the previous glossary objects could only have entries for one language pair (
+`SourceLanguageCode` and `TargetLanguageCode`). Now we introduce the concept of "glossary dictionaries", where a
+glossary dictionary specifies its own `SourceLanguageCode`, `TargetLanguageCode`, and has its own entries.
+
+- **Glossary Information**:
+ - **v2**: `GlossaryInfo` supports only mono-lingual glossaries, containing fields such as `SourceLanguageCode`,
+ `TargetLanguageCode`, and `EntryCount`.
+ - **v3**: `MultilingualGlossaryInfo` supports multi-lingual glossaries and includes a list of
+ `MultilingualGlossaryDictionaryInfo`, which provides details about each glossary dictionary, each with its own
+ `SourceLanguageCode`, `TargetLanguageCode`, and `EntryCount`.
+
+- **Glossary Entries**:
+ - **v3**: Introduces `MultilingualGlossaryDictionaryEntries`, which encapsulates a glossary dictionary with source and
+ target languages along with its entries.
+
+## 4. Code Examples
+
+### Create a glossary
+
+```java
+class Example {
+ // monolingual glossary example
+ public void createGlossaryExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ }};
+ GlossaryInfo glossaryInfo = client.createGlossary("My Glossary", "EN", "DE", entries);
+ }
+
+ // multilingual glossary example
+ public void createMultilingualGlossaryExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ }};
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries("EN", "DE", entries));
+ MultilingualGlossaryInfo glossaryInfo = client.createMultilingualGlossary("My Glossary", glossaryDicts);
+ }
+}
+```
+
+### Get a glossary
+
+```java
+class Example {
+ // monolingual glossary example
+ public void getGlossaryExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ }};
+ GlossaryInfo createdGlossary = client.createGlossary("My Glossary", "EN", "DE", entries);
+ GlossaryInfo glossaryInfo = client.getGlossary(createdGlossary); // GlossaryInfo object
+ }
+
+ // multilingual glossary example
+ public void getMultilingualGlossaryExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ }};
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries("EN", "DE", entries));
+ MultilingualGlossaryInfo createdGlossary = client.createMultilingualGlossary("My Glossary", glossaryDicts);
+ GlossaryInfo glossaryInfo = client.getGlossary(createdGlossary); // MultilingualGlossaryInfo object
+ }
+}
+```
+
+### Get glossary entries
+
+```java
+class Example {
+ // monolingual glossary example
+ public void getGlossaryEntriesExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ }};
+ GlossaryInfo createdGlossary = client.createGlossary("My Glossary", "EN", "DE", entries);
+ GlossaryEntries entries = client.getGlossaryEntries(createdGlossary);
+ System.out.println(entries.toTsv()); // 'hello\thallo'
+ }
+
+ // mutlilingual glossary example
+ public void getMultilingualGlossaryEntriesExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ }};
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries("EN", "DE", entries));
+ MultilingualGlossaryInfo createdGlossary = client.createMultilingualGlossary("My Glossary", glossaryDicts);
+ MultilingualGlossaryInfo dictEntries = client.getMultilingualGlossaryDictionaryEntries(createdGlossary, "EN", "DE");
+ System.out.println(dictEntries.getDictionaries()[0].getEntries().ToTsv()); // 'hello\thallo'
+ }
+}
+```
+
+### List and delete glossaries
+
+```java
+class Example {
+ // monolingual glossary example
+ public void getListDeleteGlossaryExamples() throws Exception {
+ List glossaries = client.listGlossaries();
+ for (GlossaryInfo glossary : glossaries) {
+ if (glossary.getName() == "Old glossary") {
+ client.deleteGlossary(glossary);
+ }
+ }
+ }
+
+ // multilingual glossary example
+ public void getListDeleteMultilingualGlossaryExamples() throws Exception {
+ List glossaries = client.listMultilingualGlossaries();
+ for (MultilingualGlossaryInfo glossary : glossaries) {
+ if (glossary.getName() == "Old glossary") {
+ client.deleteMultilingualGlossary(glossary);
+ }
+ }
+ }
+}
+```
+
+## 5. New Multilingual Glossary Methods
+
+In addition to introducing multilingual glossaries, we introduce several new methods that enhance the functionality for
+managing glossaries. Below are the details for each new method:
+
+### Update Multilingual Glossary Dictionary
+
+- **Method Overloads**:
+ - `MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(String glossaryId, String
+ sourceLanguageCode, String targetLanguageCode, GlossaryEntries entries)`
+ - `MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(MultilingualGlossaryInfo glossary, String
+ sourceLanguageCode, String targetLanguageCode, GlossaryEntries entries)`
+ - `MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(String glossaryId,
+ MultilingualGlossaryDictionaryEntries glossaryDict)`
+ - `MultilingualGlossaryInfo updateMultilingualGlossaryDictionary(MultilingualGlossaryInfo glossary,
+ MultilingualGlossaryDictionaryEntries glossaryDict)`
+- **Description**: Updates a glossary dictionary with new entries.
+- **Parameters:**
+ - `String glossaryId`: ID of the glossary to update.
+ - `String sourceLanguageCode`: Source language code for the glossary dictionary.
+ - `String targetLanguageCode`: Target language code for the glossary dictionary.
+ - `GlossaryEntries entries`: The source-target entry pairs.
+ - `MultilingualGlossaryDictionaryEntries glossaryDict`: The glossary dictionary to update.
+- **Returns**: `MultilingualGlossaryInfo` containing details about the updated glossary.
+- **Exceptions**:
+ - `IllegalArgumentException`: Thrown if any argument is invalid.
+ - `DeepLException`: Thrown if any error occurs while communicating with the DeepL API.
+ - `InterruptedException`: If the thread is interrupted during execution of this function.
+- **Example**:
+
+```java
+class Example {
+ public void updateGlossaryEntriesExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("artist", "Maler");
+ put("hello", "guten tag");
+ }};
+ List dictionaries =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries("EN", "DE", entries));
+ MultilingualGlossaryInfo myGlossary = client.createMultilingualGlossary(
+ "My glossary",
+ dictionaries
+ );
+
+ GlossaryEntries newEntries = new GlossaryEntries() {{
+ put("hello", "hallo");
+ put("prize", "Gewinn");
+ }};
+
+ MultilingualGlossaryDictionaryEntries glossaryDict =
+ new MultilingualGlossaryDictionaryEntries("EN", "DE", newEntries);
+
+ MultilingualGlossaryInfo updatedGlossary =
+ client.updateMultilingualGlossaryDictionary(myGlossary, glossaryDict);
+
+ MultilingualGlossaryInfo entriesResponse =
+ client.getMultilingualGlossaryDictionaryEntries(myGlossary, "EN", "DE");
+
+ for (Map.Entry entry : entriesResponse.getDictionaries()[0].getEntries().entrySet()) {
+ System.out.println(entry.getKey() + ":" + entry.getValue());
+ }
+
+ // prints:
+ // artist:Maler
+ // hello:hallo
+ // prize:Gewinn
+ }
+}
+```
+
+### Update Multilingual Glossary Dictionary from CSV
+
+- **Method**:
+ - `MultilingualGlossaryInfo updateMultilingualGlossaryDictionaryFromCsv(String glossaryId, String
+ sourceLanguageCode, String targetLanguageCode, File csvFile)`
+ - `MultilingualGlossaryInfo updateMultilingualGlossaryDictionaryFromCsv(MultilingualGlossaryInfo glossary,
+ String sourceLanguageCode, String targetLanguageCode, File csvFile)`
+- **Description**: This method allows you to update or create a glossary dictionary using entries in CSV format.
+- **Parameters**:
+ - `String glossaryId`: The ID of the glossary to update.
+ - `MultilingualGlossaryInfo glossary`: The `MultilingualGlossaryInfo` object representing the glossary to update
+ - `String sourceLanguageCode`: Language of source entries.
+ - `String targetLanguageCode`: Language of target entries.
+ - `File csvFile`: The CSV data containing glossary entries as a stream.
+- **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary.
+- **Exceptions**:
+ - `IllegalArgumentException`: Thrown if any argument is invalid.
+ - `DeepLException`: Thrown if any error occurs while communicating with the DeepL API.
+ - `InterruptedException`: If the thread is interrupted during execution of this function.
+ - `IOException`: If an I/O error occurs.
+- **Example**:
+ ```java
+ class Example {
+ public void updateGlossaryEntriesFromCsvExample() throws Exception {
+ File csvFile = new File("/path/to/glossary_file.csv");
+ String glossaryId = "559192ed-8e23-...";
+ MultilingualGlossaryInfo myGlossary =
+ client.updateMultilingualGlossaryDictionaryFromCsv(glossaryId, "en", "de", csvFile);
+ }
+ }
+ ```
+
+### Update Multilingual Glossary Name
+
+- **Method**:
+ `MultilingualGlossaryInfo updateMultilingualGlossaryName(String glossaryId, String name)`
+- **Description**: This method allows you to update the name of an existing glossary.
+- **Parameters**:
+ - `String glossary`: The ID of the glossary to update.
+ - `String name`: The new name for the glossary.
+- **Returns**: `MultilingualGlossaryInfo` containing information about the updated glossary.
+- **Exceptions**:
+ - `IllegalArgumentException`: Thrown if any argument is invalid.
+ - `DeepLException`: Thrown if any error occurs while communicating with the DeepL API.
+- **Example**:
+ ```java
+ class Example {
+ public void updateGlossaryNameExample() throws Exception {
+ String glossaryId = "559192ed-8e23-...";
+ MultilingualGlossaryInfo myGlossary = client.updateMultilingualGlossaryName(glossaryId, "My new glossary name");
+ System.out.println(myGlossary.getName()); // 'My new glossary name'
+ }
+ }
+ ```
+
+### Replace a Multilingual Glossary Dictionary
+
+- **Method**:
+ - `MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(String glossaryId, String
+ sourceLanguageCode, String targetLanguageCode, GlossaryEntries entries)`
+ - `MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(MultilingualGlossaryInfo
+ glossary, String sourceLanguageCode, String targetLanguageCode, GlossaryEntries entries)`
+ - `MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(String glossaryId,
+ MultilingualGlossaryDictionaryEntries glossaryDict)`
+ - `MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionary(MultilingualGlossaryInfo
+ glossary, MultilingualGlossaryDictionaryEntries glossaryDict)`
+- **Description**: This method replaces the existing glossary dictionary with a new set of entries.
+- **Parameters**:
+ - `String glossaryId`: ID of the glossary whose dictionary will be replaced.
+ - `String sourceLanguageCode`: Source language code for the glossary dictionary.
+ - `String targetLanguageCode`: Target language code for the glossary dictionary.
+ - `GlossaryEntries entries`: The source-target entries that will replace any existing ones for that language pair.
+ - `MultilingualGlossaryDictionaryEntries glossaryDict`: The glossary dictionary to update.
+- **Returns**: `MultilingualGlossaryDictionaryInfo` containing information about the replaced glossary dictionary.
+- **Exceptions**:
+ - `IllegalArgumentException`: Thrown if any argument is invalid.
+ - `DeepLException`: Thrown if any error occurs while communicating with the DeepL API.
+ - `InterruptedException`: If the thread is interrupted during execution of this function.
+- **Note**: Ensure that the new dictionary entries are complete and valid, as this method will completely overwrite the
+ existing entries. It will also create a new glossary dictionary if one did not exist for the given language pair.
+- **Example**:
+ ```java
+ class Example {
+ public void replaceGlossaryEntriesExample() throws Exception {
+ GlossaryEntries entries = new GlossaryEntries() {{
+ put("artist", "Maler");
+ put("hello", "guten tag");
+ }};
+ List dictionaries =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries("EN", "DE", entries));
+ MultilingualGlossaryInfo myGlossary = client.createMultilingualGlossary(
+ "My glossary",
+ dictionaries
+ );
+
+ GlossaryEntries newEntries = new GlossaryEntries() {{put("goodbye", "Auf Weidersehen");}};
+ MultilingualGlossaryDictionaryEntries glossaryDict =
+ new MultilingualGlossaryDictionaryEntries("EN", "DE", newEntries);
+
+ MultilingualGlossaryInfo updatedGlossary =
+ client.replaceMultilingualGlossaryDictionary(myGlossary, glossaryDict);
+
+ MultilingualGlossaryInfo entriesResponse =
+ client.getMultilingualGlossaryDictionaryEntries(myGlossary, "EN", "DE");
+
+ for (Map.Entry entry : glossaryDicts.getDictionaries()[0].getEntries().entrySet()) {
+ System.out.println(entry.getKey() + ":" + entry.getValue());
+ }
+ // prints:
+ // goodbye:Auf Weidersehen
+ }
+ }
+ ```
+
+### Replace Multilingual Glossary Dictionary from CSV
+
+- **Method**:
+ - `MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionaryFromCsv(String glossaryId, String
+ sourceLanguageCode, String targetLanguageCode, Stream csvFile)`
+ - `MultilingualGlossaryDictionaryInfo replaceMultilingualGlossaryDictionaryFromCsv(MultilingualGlossaryInfo
+ glossary, String sourceLanguageCode, String targetLanguageCode, Stream csvFile)`
+- **Description**: This method allows you to replace or create a glossary dictionary using entries in CSV format.
+- **Parameters**:
+ - `String glossaryId`: The ID of the glossary whose dictionary will be replaced.
+ - `MultilingualGlossaryInfo glossary`: The `MultilingualGlossaryInfo` object representing the glossary whose
+ dictionary will be replaced.
+ - `String sourceLanguageCode`: Language of source entries.
+ - `String targetLanguageCode`: Language of target entries.
+ - `Stream csvFile`: The CSV data containing glossary entries as a stream.
+- **Returns**: `MultilingualGlossaryDictionaryInfo` containing information about the replaced glossary dictionary.
+- **Exceptions**:
+ - `IllegalArgumentException`: Thrown if any argument is invalid.
+ - `DeepLException`: Thrown if any error occurs while communicating with the DeepL API.
+ - `InterruptedException`: If the thread is interrupted during execution of this function.
+ - `IOException`: If an I/O error occurs.
+- **Example**:
+ ```java
+ class Example {
+ public void replaceGlossaryEntriesFromCsvExample() throws Exception {
+ File csvFile = new File("/path/to/glossary_file.csv");
+ String glossaryId = "559192ed-8e23-...";
+ MultilingualGlossaryInfo myGlossary =
+ client.replaceMultilingualGlossaryDictionaryFromCsv(glossaryId, "en", "de", csvFile);
+ MultilingualGlossaryInfo entriesResponse =
+ client.getMultilingualGlossaryDictionaryEntries(myGlossary, "EN", "DE");
+ }
+ }
+ ```
+
+### Delete a Multilingual Glossary Dictionary
+
+- **Method**:
+ - `Task deleteMultilingualGlossaryDictionary(MultilingualGlossaryInfo glossary, String sourceLanguageCode, String
+ targetLanguageCode)`
+ - `Task deleteMultilingualGlossaryDictionary(String glossaryId, String sourceLanguageCode, String
+ targetLanguageCode)`
+- **Description**: This method deletes a specified glossary dictionary from a given glossary.
+- **Parameters**:
+ - `String glossaryId`: The ID of the glossary containing the dictionary to delete.
+ - `MultilingualGlossaryInfo glossary`: The `MultilingualGlossaryInfo` object of the glossary containing the dictionary
+ to delete.
+ - `MultilingualGlossaryDictionaryInfo dictionary`: The `MultilingualGlossaryDictionaryInfo` object that specifies the
+ - dictionary to delete.
+ - `String sourceLanguageCode`: The source language of the glossary dictionary.
+ - `String targetLanguageCode`: The target language of the glossary dictionary.
+- **Returns**: A Task
+
+- **Migration Note**: Ensure that your application logic correctly identifies the dictionary to delete. If using
+ `sourceLanguageCode` and `targetLanguageCode`, both must be provided to specify the dictionary.
+
+- **Example**:
+ ```java
+ class Example {
+ public void deleteGlossaryDictionaryExample() throws Exception {
+ GlossaryEntries entriesEnde = new GlossaryEntries() {{put("hello", "hallo");}};
+ GlossaryEntries entriesDeen = new GlossaryEntries() {{put("hallo", "hello");}};
+ MultilingualGlossaryDictionaryEntries glossaryDictEnde =
+ new MultilingualGlossaryDictionaryEntries("EN", "DE", entriesEnde);
+ MultilingualGlossaryDictionaryEntries glossaryDictDeen =
+ new MultilingualGlossaryDictionaryEntries("EN", "DE", entriesDeen);
+ List glossaryDicts =
+ Arrays.asList(new MultilingualGlossaryDictionaryEntries("EN", "DE", entriesEnde),
+ new MultilingualGlossaryDictionaryEntries("DE", "EN", entriesDeen));
+ MultilingualGlossaryInfo glossaryInfo = client.createMultilingualGlossary("My Glossary", glossaryDicts);
+
+ // Delete via specifying the glossary dictionary
+ client.deleteMultilingualGlossaryDictionary(glossaryInfo, glossaryDictEnde);
+
+ // Delete via specifying the language pair
+ client.deleteMultilingualGlossaryDictionary(glossaryInfo, "DE", "EN");
+ }
+ }
+ ```