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 ac5d6a6..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
@@ -39,6 +47,119 @@ public DocumentTranslationOptions setGlossaryId(String glossaryId) {
return this;
}
+ /**
+ * Sets the glossary to use with the translation. By default, this value is null and
+ * no glossary is used.
+ */
+ public DocumentTranslationOptions setGlossary(IGlossary glossary) {
+ return setGlossary(glossary.getGlossaryId());
+ }
+
+ /**
+ * Sets the glossary to use with the translation. By default, this value is null and
+ * no glossary is used.
+ */
+ public DocumentTranslationOptions setGlossary(String glossaryId) {
+ this.glossaryId = 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;
@@ -48,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/Formality.java b/deepl-java/src/main/java/com/deepl/api/Formality.java
index c3ed4e9..6b19638 100644
--- a/deepl-java/src/main/java/com/deepl/api/Formality.java
+++ b/deepl-java/src/main/java/com/deepl/api/Formality.java
@@ -13,4 +13,13 @@ public enum Formality {
/** Increased formality. */
More,
+
+ /**
+ * Less formality, i.e. more informal, if available for the specified target language, otherwise
+ * default.
+ */
+ PreferLess,
+
+ /** Increased formality, if available for the specified target language, otherwise default. */
+ PreferMore,
}
diff --git a/deepl-java/src/main/java/com/deepl/api/GlossaryEntries.java b/deepl-java/src/main/java/com/deepl/api/GlossaryEntries.java
new file mode 100644
index 0000000..1acae38
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/GlossaryEntries.java
@@ -0,0 +1,217 @@
+// Copyright 2022 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.utils.*;
+import java.util.*;
+import org.jetbrains.annotations.*;
+
+/** Stores the entries of a glossary. */
+public class GlossaryEntries implements Map {
+ private final Map entries = new HashMap<>();
+
+ /** Construct an empty GlossaryEntries. */
+ public GlossaryEntries() {}
+
+ /** Initializes a new GlossaryEntries with the entry pairs in the given map. */
+ public GlossaryEntries(Map entryPairs) {
+ this.putAll(entryPairs);
+ }
+
+ /**
+ * Converts the given tab-separated-value (TSV) string of glossary entries into a new
+ * GlossaryEntries object. Whitespace is trimmed from the start and end of each term.
+ */
+ public static GlossaryEntries fromTsv(String tsv) {
+ GlossaryEntries result = new GlossaryEntries();
+ String[] lines = tsv.split("(\\r\\n|\\n|\\r)");
+ int lineNumber = 0;
+ for (String line : lines) {
+ ++lineNumber;
+ String lineTrimmed = trimWhitespace(line);
+ if (lineTrimmed.isEmpty()) {
+ continue;
+ }
+ String[] splitLine = lineTrimmed.split("\\t");
+ if (splitLine.length < 2) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Entry on line %d does not contain a term separator: %s", lineNumber, lineTrimmed));
+ } else if (splitLine.length > 2) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Entry on line %d contains more than one term separator: %s", lineNumber, line));
+ } else {
+ String sourceTerm = trimWhitespace(splitLine[0]);
+ String targetTerm = trimWhitespace(splitLine[1]);
+ validateGlossaryTerm(sourceTerm);
+ validateGlossaryTerm(targetTerm);
+ if (result.containsKey(sourceTerm)) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Entry on line %d duplicates source term '%s'", lineNumber, sourceTerm));
+ }
+ result.put(sourceTerm, targetTerm);
+ }
+ }
+
+ if (result.entries.isEmpty()) {
+ throw new IllegalArgumentException("TSV string contains no valid entries");
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (getClass() != o.getClass()) return false;
+ GlossaryEntries glossaryEntries = (GlossaryEntries) o;
+ return glossaryEntries.entries.equals(entries);
+ }
+
+ @Override
+ public int size() {
+ return entries.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return entries.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return entries.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return entries.containsValue(value);
+ }
+
+ @Override
+ public String get(Object key) {
+ return entries.get(key);
+ }
+
+ /**
+ * Adds the given source term and target term to the glossary entries.
+ *
+ * @param sourceTerm key with which the specified value is to be associated
+ * @param targetTerm value to be associated with the specified key
+ * @return The previous target term associated with this source term, or null if this source term
+ * was not present.
+ */
+ public String put(String sourceTerm, String targetTerm) throws IllegalArgumentException {
+ validateGlossaryTerm(sourceTerm);
+ validateGlossaryTerm(targetTerm);
+ return entries.put(sourceTerm, targetTerm);
+ }
+
+ @Override
+ public String remove(Object key) {
+ return entries.remove(key);
+ }
+
+ @Override
+ public void putAll(@NotNull Map extends String, ? extends String> m) {
+ for (Map.Entry extends String, ? extends String> entryPair : m.entrySet()) {
+ put(entryPair.getKey(), entryPair.getValue());
+ }
+ }
+
+ @Override
+ public void clear() {
+ entries.clear();
+ }
+
+ @NotNull
+ @Override
+ public Set keySet() {
+ return entries.keySet();
+ }
+
+ @NotNull
+ @Override
+ public Collection values() {
+ return entries.values();
+ }
+
+ @NotNull
+ @Override
+ public Set> entrySet() {
+ return entries.entrySet();
+ }
+
+ /**
+ * Checks the validity of the given glossary term, for example that it contains no invalid
+ * characters. Whitespace at the start and end of the term is ignored. Terms are considered valid
+ * if they comprise at least one non-whitespace character, and contain no invalid characters: C0
+ * and C1 control characters, and Unicode newlines.
+ *
+ * @param term String containing term to check.
+ */
+ public static void validateGlossaryTerm(String term) throws IllegalArgumentException {
+ String termTrimmed = trimWhitespace(term);
+ if (termTrimmed.isEmpty()) {
+ throw new IllegalArgumentException(
+ String.format("Term '%s' contains no non-whitespace characters", term));
+ }
+ for (int i = 0; i < termTrimmed.length(); ++i) {
+ char ch = termTrimmed.charAt(i);
+ if ((ch <= 31) || (128 <= ch && ch <= 159) || ch == '\u2028' || ch == '\u2029') {
+ throw new IllegalArgumentException(
+ String.format(
+ "Term '%s' contains invalid character: '%c' (U+%04d)", term, ch, (int) ch));
+ }
+ }
+ }
+
+ /**
+ * Converts the glossary entries to a string containing the entries in tab-separated-value (TSV)
+ * format.
+ *
+ * @return String containing the entries in TSV format.
+ */
+ public String toTsv() {
+ StringBuilder builder = new StringBuilder();
+ for (Map.Entry entryPair : entries.entrySet()) {
+ if (builder.length() > 0) {
+ builder.append("\n");
+ }
+ builder.append(entryPair.getKey()).append("\t").append(entryPair.getValue());
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Strips whitespace characters from the beginning and end of the given string. Implemented here
+ * because String.strip() is not available in Java 8.
+ *
+ * @param input String to have whitespace trimmed.
+ * @return Input string with whitespace removed from ends.
+ */
+ private static String trimWhitespace(String input) {
+ int left = 0;
+ for (; left < input.length(); left++) {
+ char ch = input.charAt(left);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ }
+ if (left >= input.length()) {
+ return "";
+ }
+ int right = input.length() - 1;
+ for (; left < right; right--) {
+ char ch = input.charAt(right);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ }
+ return input.substring(left, right + 1);
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java b/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java
new file mode 100644
index 0000000..34c0b07
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/GlossaryInfo.java
@@ -0,0 +1,96 @@
+// Copyright 2022 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 glossary, excluding the entry list. */
+public class GlossaryInfo implements IGlossary {
+
+ @SerializedName(value = "glossary_id")
+ private final String glossaryId;
+
+ @SerializedName(value = "name")
+ private final String name;
+
+ @SerializedName(value = "ready")
+ private final boolean ready;
+
+ @SerializedName(value = "source_lang")
+ private final String sourceLang;
+
+ @SerializedName(value = "target_lang")
+ private final String targetLang;
+
+ @SerializedName(value = "creation_time")
+ private final Date creationTime;
+
+ @SerializedName(value = "entry_count")
+ private final long entryCount;
+
+ /**
+ * Initializes a new {@link GlossaryInfo} containing information about a glossary.
+ *
+ * @param glossaryId ID of the associated glossary.
+ * @param name Name of the glossary chosen during creation.
+ * @param ready true if the glossary may be used for translations, otherwise false.
+ * @param sourceLang Language code of the source terms in the glossary.
+ * @param targetLang Language code of the target terms in the glossary.
+ * @param creationTime Time when the glossary was created.
+ * @param entryCount The number of source-target entry pairs in the glossary.
+ */
+ public GlossaryInfo(
+ String glossaryId,
+ String name,
+ boolean ready,
+ String sourceLang,
+ String targetLang,
+ Date creationTime,
+ long entryCount) {
+ this.glossaryId = glossaryId;
+ this.name = name;
+ this.ready = ready;
+ this.sourceLang = sourceLang;
+ this.targetLang = targetLang;
+ this.creationTime = creationTime;
+ this.entryCount = entryCount;
+ }
+
+ /** @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 True if the glossary may be used for translations, otherwise false. */
+ public boolean isReady() {
+ return ready;
+ }
+
+ /** @return Source language code of the glossary. */
+ public String getSourceLang() {
+ return sourceLang;
+ }
+
+ /** @return Target language code of the glossary. */
+ public String getTargetLang() {
+ return targetLang;
+ }
+
+ /** @return Timestamp when the glossary was created. */
+ public Date getCreationTime() {
+ return creationTime;
+ }
+
+ /** @return The number of entries contained in the glossary. */
+ public long getEntryCount() {
+ return entryCount;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/GlossaryLanguagePair.java b/deepl-java/src/main/java/com/deepl/api/GlossaryLanguagePair.java
new file mode 100644
index 0000000..1d818e4
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/GlossaryLanguagePair.java
@@ -0,0 +1,40 @@
+// Copyright 2022 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.*;
+
+/**
+ * Information about a language pair supported for glossaries.
+ *
+ * @see Translator#getGlossaryLanguages()
+ */
+public class GlossaryLanguagePair {
+ @SerializedName("source_lang")
+ private final String sourceLang;
+
+ @SerializedName("target_lang")
+ private final String targetLang;
+
+ /**
+ * Initializes a new GlossaryLanguagePair object.
+ *
+ * @param sourceLang Language code of the source terms in the glossary.
+ * @param targetLang Language code of the target terms in the glossary.
+ */
+ public GlossaryLanguagePair(String sourceLang, String targetLang) {
+ this.sourceLang = LanguageCode.standardize(sourceLang);
+ this.targetLang = LanguageCode.standardize(targetLang);
+ }
+
+ /** @return Language code of the source terms in the glossary. */
+ public String getSourceLanguage() {
+ return sourceLang;
+ }
+
+ /** @return Language code of the target terms in the glossary. */
+ public String getTargetLanguage() {
+ return targetLang;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/GlossaryNotFoundException.java b/deepl-java/src/main/java/com/deepl/api/GlossaryNotFoundException.java
new file mode 100644
index 0000000..6700ec9
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/GlossaryNotFoundException.java
@@ -0,0 +1,11 @@
+// Copyright 2022 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;
+
+/** Exception thrown when the specified glossary could not be found. */
+public class GlossaryNotFoundException extends NotFoundException {
+ public GlossaryNotFoundException(String message) {
+ super(message);
+ }
+}
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 2c33c20..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,12 +9,27 @@
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.*;
-/** Helper class providing functions to make HTTP requests and retry with exponential-backoff. */
+/**
+ * Helper class providing functions to make HTTP requests and retry with exponential-backoff.
+ *
+ * This class is internal; you should not use this class directly.
+ */
class HttpClientWrapper {
private static final String CONTENT_TYPE = "Content-Type";
+ 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;
@@ -34,6 +49,23 @@ public HttpClientWrapper(
this.maxRetries = maxRetries;
}
+ public HttpResponse sendGetRequestWithBackoff(String relativeUrl)
+ throws InterruptedException, DeepLException {
+ 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 sendDeleteRequestWithBackoff(relativeUrl, null);
+ }
+
public HttpResponse sendRequestWithBackoff(String relativeUrl)
throws InterruptedException, DeepLException {
return sendRequestWithBackoff(POST, relativeUrl, null).toStringResponse();
@@ -46,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 {
@@ -76,19 +204,48 @@ 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 || response.getCode() == 503)) {
+ } else if (response.getCode() != 429 && response.getCode() < 500) {
return response;
}
response.close();
@@ -102,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);
@@ -114,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 686f0c4..b4c9448 100644
--- a/deepl-java/src/main/java/com/deepl/api/LanguageCode.java
+++ b/deepl-java/src/main/java/com/deepl/api/LanguageCode.java
@@ -3,12 +3,17 @@
// license that can be found in the LICENSE file.
package com.deepl.api;
+import java.util.*;
+
/**
* Language codes for the languages currently supported by DeepL translation. New languages may be
* added in the future; to retrieve the currently supported languages use {@link
* Translator#getSourceLanguages()} and {@link Translator#getTargetLanguages()}.
*/
public class LanguageCode {
+ /** Arabic (MSA) language code, may be used as source or target language */
+ public static final String Arabic = "ar";
+
/** Bulgarian language code, may be used as source or target language. */
public static final String Bulgarian = "bg";
@@ -60,12 +65,18 @@ public class LanguageCode {
/** Japanese language code, may be used as source or target language. */
public static final String Japanese = "ja";
+ /** Korean language code, may be used as source or target language. */
+ public static final String Korean = "ko";
+
/** Lithuanian language code, may be used as source or target language. */
public static final String Lithuanian = "lt";
/** Latvian language code, may be used as source or target language. */
public static final String Latvian = "lv";
+ /** Norwegian (bokmål) language code, may be used as source or target language. */
+ public static final String Norwegian = "nb";
+
/** Dutch language code, may be used as source or target language. */
public static final String Dutch = "nl";
@@ -102,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";
@@ -113,7 +127,7 @@ public class LanguageCode {
*/
public static String removeRegionalVariant(String langCode) {
String[] parts = langCode.split("-", 2);
- return parts[0].toLowerCase();
+ return parts[0].toLowerCase(Locale.ENGLISH);
}
/**
@@ -126,9 +140,9 @@ public static String removeRegionalVariant(String langCode) {
public static String standardize(String langCode) {
String[] parts = langCode.split("-", 2);
if (parts.length == 1) {
- return parts[0].toLowerCase();
+ return parts[0].toLowerCase(Locale.ENGLISH);
} else {
- return parts[0].toLowerCase() + "-" + parts[1].toUpperCase();
+ return parts[0].toLowerCase(Locale.ENGLISH) + "-" + parts[1].toUpperCase(Locale.ENGLISH);
}
}
}
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 0f721df..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,16 +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
@@ -46,6 +58,128 @@ public TextTranslationOptions setGlossaryId(String glossaryId) {
return this;
}
+ /**
+ * Sets the glossary to use with the translation. By default, this value is null and
+ * no glossary is used.
+ */
+ public TextTranslationOptions setGlossary(IGlossary glossary) {
+ return setGlossary(glossary.getGlossaryId());
+ }
+
+ /**
+ * Sets the glossary to use with the translation. By default, this value is null and
+ * no glossary is used.
+ */
+ public TextTranslationOptions setGlossary(String glossaryId) {
+ this.glossaryId = 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
+ * for more information and example usage.
+ */
+ public TextTranslationOptions setContext(String context) {
+ this.context = context;
+ return this;
+ }
+
/**
* Specifies how input translation text should be split into sentences. By default, this value is
* null and the default sentence splitting mode is used.
@@ -78,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.
@@ -114,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;
@@ -124,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;
@@ -134,11 +320,26 @@ public boolean isPreserveFormatting() {
return preserveFormatting;
}
+ /** Gets the current context. */
+ 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;
@@ -158,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 1de8747..c44350d 100644
--- a/deepl-java/src/main/java/com/deepl/api/Translator.java
+++ b/deepl-java/src/main/java/com/deepl/api/Translator.java
@@ -3,13 +3,11 @@
// license that can be found in the LICENSE file.
package com.deepl.api;
-import static java.lang.Math.max;
-import static java.lang.Math.min;
-
import com.deepl.api.http.HttpResponse;
import com.deepl.api.http.HttpResponseStream;
import com.deepl.api.parsing.Parser;
import com.deepl.api.utils.*;
+import com.google.gson.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.util.*;
@@ -26,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.
@@ -39,22 +38,31 @@ 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("User-Agent", "deepl-java/0.1.3");
+ headers.putIfAbsent("Authorization", "DeepL-Auth-Key " + sanitizedAuthKey);
+ headers.putIfAbsent(
+ "User-Agent",
+ constructUserAgentString(options.getSendPlatformInfo(), options.getAppInfo()));
this.httpClientWrapper =
new HttpClientWrapper(
@@ -70,11 +78,34 @@ 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());
}
+ /**
+ * Builds the user-agent String which contains platform information.
+ *
+ * @return A string containing the client library version, java version and operating system.
+ */
+ private String constructUserAgentString(boolean sendPlatformInfo, AppInfo appInfo) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("deepl-java/1.17.0");
+ if (sendPlatformInfo) {
+ sb.append(" (");
+ Properties props = System.getProperties();
+ sb.append(props.get("os.name") + "-" + props.get("os.version") + "-" + props.get("os.arch"));
+ sb.append(") java/");
+ sb.append(props.get("java.version"));
+ }
+ if (appInfo != null) {
+ sb.append(" " + appInfo.getAppName() + "/" + appInfo.getAppVersion());
+ }
+ return sb.toString();
+ }
+
/**
* Determines if the given DeepL Authentication Key belongs to an API Free account.
*
@@ -170,8 +201,10 @@ public List translateText(
throws DeepLException, InterruptedException {
Iterable> params =
createHttpParams(texts, sourceLang, targetLang, options);
- HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v2/translate", params);
- checkResponse(response, false);
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/translate", this.apiVersion), params);
+ checkResponse(response, false, false);
return jsonParser.parseTextResult(response.getBody());
}
@@ -226,8 +259,9 @@ public List translateText(
* @throws DeepLException If any error occurs while communicating with the DeepL API.
*/
public Usage getUsage() throws DeepLException, InterruptedException {
- HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v2/usage");
- checkResponse(response, false);
+ HttpResponse response =
+ httpClientWrapper.sendGetRequestWithBackoff(String.format("/%s/usage", apiVersion));
+ checkResponse(response, false, false);
return jsonParser.parseUsage(response.getBody());
}
@@ -270,11 +304,31 @@ public List getLanguages(LanguageType languageType)
if (languageType == LanguageType.Target) {
params.add(new KeyValuePair<>("type", "target"));
}
- HttpResponse response = httpClientWrapper.sendRequestWithBackoff("/v2/languages", params);
- checkResponse(response, false);
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/languages", apiVersion), params);
+ checkResponse(response, false, false);
return jsonParser.parseLanguages(response.getBody());
}
+ /**
+ * Retrieves the list of supported glossary language pairs. When creating glossaries, the source
+ * and target language pair must match one of the available language pairs.
+ *
+ * @return List of {@link GlossaryLanguagePair} objects representing the available glossary
+ * language pairs.
+ * @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 getGlossaryLanguages()
+ throws DeepLException, InterruptedException {
+ HttpResponse response =
+ httpClientWrapper.sendGetRequestWithBackoff(
+ String.format("/%s/glossary-language-pairs", apiVersion));
+ checkResponse(response, false, false);
+ return jsonParser.parseGlossaryLanguageList(response.getBody());
+ }
+
/**
* Translate specified document content from source language to target language and store the
* translated document content to specified stream.
@@ -409,8 +463,8 @@ public DocumentHandle translateDocumentUpload(
try (FileInputStream inputStream = new FileInputStream(inputFile)) {
HttpResponse response =
httpClientWrapper.uploadWithBackoff(
- "/v2/document/", params, inputFile.getName(), inputStream);
- checkResponse(response, false);
+ String.format("/%s/document", apiVersion), params, inputFile.getName(), inputStream);
+ checkResponse(response, false, false);
return jsonParser.parseDocumentHandle(response.getBody());
}
}
@@ -453,8 +507,9 @@ public DocumentHandle translateDocumentUpload(
Iterable> params =
createHttpParams(sourceLang, targetLang, options);
HttpResponse response =
- httpClientWrapper.uploadWithBackoff("/v2/document/", params, fileName, inputStream);
- checkResponse(response, false);
+ httpClientWrapper.uploadWithBackoff(
+ String.format("/%s/document/", apiVersion), params, fileName, inputStream);
+ checkResponse(response, false, false);
return jsonParser.parseDocumentHandle(response.getBody());
}
@@ -483,9 +538,9 @@ 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);
+ checkResponse(response, false, false);
return jsonParser.parseDocumentStatus(response.getBody());
}
@@ -557,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;
@@ -565,6 +621,159 @@ public void translateDocumentDownload(DocumentHandle handle, OutputStream output
}
}
+ /**
+ * Creates a glossary in your DeepL account with the specified details and returns a {@link
+ * GlossaryInfo} object with details about the newly created glossary. The glossary can be used in
+ * translations to override translations for specific terms (words). The glossary source and
+ * target languages must match 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 sourceLang Language code of the source terms language.
+ * @param targetLang Language code of the target terms language.
+ * @param entries Glossary entries to add to the glossary.
+ * @return {@link GlossaryInfo} object with details about the newly created 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 GlossaryInfo createGlossary(
+ String name, String sourceLang, String targetLang, GlossaryEntries entries)
+ throws DeepLException, InterruptedException {
+ return createGlossaryInternal(name, sourceLang, targetLang, "tsv", entries.toTsv());
+ }
+
+ /**
+ * Creates a glossary in your DeepL account with the specified details and returns a {@link
+ * GlossaryInfo} object with details about the newly created glossary. The glossary can be used in
+ * translations to override translations for specific terms (words). The glossary source and
+ * target languages must match 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 sourceLang Language code of the source terms language.
+ * @param targetLang Language code of the target terms language.
+ * @param csvFile File containing CSV content for glossary.
+ * @return {@link GlossaryInfo} object with details about the newly created 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.
+ * @throws IOException If an I/O error occurs.
+ */
+ public GlossaryInfo createGlossaryFromCsv(
+ String name, String sourceLang, String targetLang, File csvFile)
+ throws DeepLException, InterruptedException, IOException {
+ try (FileInputStream stream = new FileInputStream(csvFile)) {
+ String csvContent = StreamUtil.readStream(stream);
+ return createGlossaryFromCsv(name, sourceLang, targetLang, csvContent);
+ }
+ }
+
+ /**
+ * Creates a glossary in your DeepL account with the specified details and returns a {@link
+ * GlossaryInfo} object with details about the newly created glossary. The glossary can be used in
+ * translations to override translations for specific terms (words). The glossary source and
+ * target languages must match 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 sourceLang Language code of the source terms language.
+ * @param targetLang Language code of the target terms language.
+ * @param csvContent String containing CSV content.
+ * @return {@link GlossaryInfo} object with details about the newly created 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 GlossaryInfo createGlossaryFromCsv(
+ String name, String sourceLang, String targetLang, String csvContent)
+ throws DeepLException, InterruptedException {
+ return createGlossaryInternal(name, sourceLang, targetLang, "csv", csvContent);
+ }
+
+ /**
+ * Retrieves information about the glossary with the specified ID and returns a {@link
+ * GlossaryInfo} object containing details. This does not retrieve the glossary entries; to
+ * retrieve entries use {@link Translator#getGlossaryEntries(String)}
+ *
+ * @param glossaryId ID of glossary to retrieve.
+ * @return {@link GlossaryInfo} 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 GlossaryInfo getGlossary(String glossaryId) throws DeepLException, InterruptedException {
+ String relativeUrl = String.format("/%s/glossaries/%s", apiVersion, glossaryId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, true);
+ return jsonParser.parseGlossaryInfo(response.getBody());
+ }
+
+ /**
+ * Retrieves information about all glossaries and returns an array of {@link GlossaryInfo} objects
+ * containing details. This does not retrieve the glossary entries; to retrieve entries use {@link
+ * Translator#getGlossaryEntries(String)}
+ *
+ * @return Array of {@link GlossaryInfo} 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 listGlossaries() throws DeepLException, InterruptedException {
+ HttpResponse response =
+ httpClientWrapper.sendGetRequestWithBackoff(String.format("/%s/glossaries", apiVersion));
+ checkResponse(response, false, false);
+ return jsonParser.parseGlossaryInfoList(response.getBody());
+ }
+
+ /**
+ * Retrieves the entries containing within the glossary and returns them as a {@link
+ * GlossaryEntries}.
+ *
+ * @param glossary {@link GlossaryInfo} object corresponding to glossary for which to retrieve
+ * entries.
+ * @return {@link GlossaryEntries} containing entry pairs of the 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 GlossaryEntries getGlossaryEntries(GlossaryInfo glossary)
+ throws DeepLException, InterruptedException {
+ return getGlossaryEntries(glossary.getGlossaryId());
+ }
+
+ /**
+ * Retrieves the entries containing within the glossary with the specified ID and returns them as
+ * a {@link GlossaryEntries}.
+ *
+ * @param glossaryId ID of glossary for which to retrieve entries.
+ * @return {@link GlossaryEntries} containing entry pairs of the 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 GlossaryEntries getGlossaryEntries(String glossaryId)
+ throws DeepLException, InterruptedException {
+ String relativeUrl = String.format("/%s/glossaries/%s/entries", apiVersion, glossaryId);
+ HttpResponse response = httpClientWrapper.sendGetRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, true);
+ return GlossaryEntries.fromTsv(response.getBody());
+ }
+
+ /**
+ * Deletes the specified glossary.
+ *
+ * @param glossary {@link GlossaryInfo} object corresponding to glossary 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.
+ */
+ public void deleteGlossary(GlossaryInfo glossary) throws DeepLException, InterruptedException {
+ deleteGlossary(glossary.getGlossaryId());
+ }
+
+ /**
+ * Deletes the glossary with the specified ID.
+ *
+ * @param glossaryId ID of glossary 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.
+ */
+ public void deleteGlossary(String glossaryId) throws DeepLException, InterruptedException {
+ String relativeUrl = String.format("/%s/glossaries/%s", apiVersion, glossaryId);
+ HttpResponse response = httpClientWrapper.sendDeleteRequestWithBackoff(relativeUrl);
+ checkResponse(response, false, true);
+ }
+
/**
* Checks the specified texts, languages and options are valid, and returns an iterable of
* containing the parameters to include in HTTP request.
@@ -586,17 +795,19 @@ 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
- if (options.getSentenceSplittingMode() != null
- && options.getSentenceSplittingMode() != SentenceSplittingMode.All) {
+ if (options.getSentenceSplittingMode() != null) {
switch (options.getSentenceSplittingMode()) {
case Off:
params.add(new KeyValuePair<>("split_sentences", "0"));
@@ -604,6 +815,9 @@ private static ArrayList> createHttpParams(
case NoNewlines:
params.add(new KeyValuePair<>("split_sentences", "nonewlines"));
break;
+ case All:
+ params.add(new KeyValuePair<>("split_sentences", "1"));
+ break;
default:
break;
}
@@ -611,9 +825,18 @@ private static ArrayList> createHttpParams(
if (options.isPreserveFormatting()) {
params.add(new KeyValuePair<>("preserve_formatting", "1"));
}
+ 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"));
}
@@ -627,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;
}
@@ -641,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;
}
/**
@@ -659,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);
@@ -676,7 +948,7 @@ private static ArrayList> createHttpParamsCommon(
}
params.add(new KeyValuePair<>("target_lang", targetLang));
- if (formality != null && formality != Formality.Default) {
+ if (formality != null) {
switch (formality) {
case More:
params.add(new KeyValuePair<>("formality", "more"));
@@ -684,15 +956,40 @@ private static ArrayList> createHttpParamsCommon(
case Less:
params.add(new KeyValuePair<>("formality", "less"));
break;
+ case PreferMore:
+ params.add(new KeyValuePair<>("formality", "prefer_more"));
+ break;
+ case PreferLess:
+ params.add(new KeyValuePair<>("formality", "prefer_less"));
+ break;
+ case Default:
default:
+ params.add(new KeyValuePair<>("formality", "default"));
break;
}
}
if (glossaryId != null) {
+ if (sourceLang == null) {
+ throw new IllegalArgumentException("sourceLang is required if using a glossary");
+ }
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;
}
@@ -701,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.
*
@@ -709,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");
@@ -729,12 +1043,29 @@ private static void checkValidLanguages(@Nullable String sourceLang, String targ
}
}
+ /** Creates a glossary with given details. */
+ private GlossaryInfo createGlossaryInternal(
+ String name, String sourceLang, String targetLang, String entriesFormat, String entries)
+ throws DeepLException, InterruptedException {
+ ArrayList> params = new ArrayList<>();
+ params.add(new KeyValuePair<>("name", name));
+ params.add(new KeyValuePair<>("source_lang", sourceLang));
+ params.add(new KeyValuePair<>("target_lang", targetLang));
+ params.add(new KeyValuePair<>("entries_format", entriesFormat));
+ params.add(new KeyValuePair<>("entries", entries));
+ HttpResponse response =
+ httpClientWrapper.sendRequestWithBackoff(
+ String.format("/%s/glossaries", apiVersion), params);
+ checkResponse(response, false, false);
+ return jsonParser.parseGlossaryInfo(response.getBody());
+ }
+
/**
- * Functions the same as {@link Translator#checkResponse(HttpResponse, boolean)} but accepts
- * response stream for document downloads. If the HTTP status code represents failure, the
+ * Functions the same as {@link Translator#checkResponse(HttpResponse, boolean, boolean)} but
+ * accepts response stream for document downloads. If the HTTP status code represents failure, the
* response stream is converted to a String response to throw the appropriate exception.
*
- * @see Translator#checkResponse(HttpResponse, boolean)
+ * @see Translator#checkResponse(HttpResponse, boolean, boolean)
*/
private void checkResponse(HttpResponseStream response) throws DeepLException {
if (response.getCode() >= HttpURLConnection.HTTP_OK
@@ -744,33 +1075,46 @@ private void checkResponse(HttpResponseStream response) throws DeepLException {
if (response.getBody() == null) {
throw new DeepLException("response stream is empty");
}
- checkResponse(response.toStringResponse(), true);
+ checkResponse(response.toStringResponse(), true, false);
}
/**
* Checks the response HTTP status is OK, otherwise throws corresponding exception.
*
* @param response Response received from DeepL API.
+ * @param inDocumentDownload True if document download function is used, otherwise false.
+ * @param usingGlossary True if a glossary function is used, otherwise false.
* @throws DeepLException Throws {@link DeepLException} or a derived exception depending on the
* type of error.
*/
- private void checkResponse(HttpResponse response, boolean inDocumentDownload)
+ protected void checkResponse(
+ HttpResponse response, boolean inDocumentDownload, boolean usingGlossary)
throws DeepLException {
if (response.getCode() >= 200 && response.getCode() < 300) {
return;
}
- String messageSuffix = jsonParser.parseErrorMessage(response.getBody());
- if (!messageSuffix.isEmpty()) {
- messageSuffix = ", " + messageSuffix;
+ String messageSuffix = "";
+ String body = response.getBody();
+ if (body != null && !body.isEmpty()) {
+ try {
+ messageSuffix = ", error message: " + jsonParser.parseErrorMessage(body);
+ } catch (JsonSyntaxException ignored) {
+ messageSuffix = ", response: " + body;
+ }
}
+
switch (response.getCode()) {
case HttpURLConnection.HTTP_BAD_REQUEST:
throw new DeepLException("Bad request" + messageSuffix);
case HttpURLConnection.HTTP_FORBIDDEN:
throw new AuthorizationException("Authorization failure, check auth_key" + messageSuffix);
case HttpURLConnection.HTTP_NOT_FOUND:
- throw new NotFoundException("Not found, check serverUrl" + messageSuffix);
+ if (usingGlossary) {
+ throw new GlossaryNotFoundException("Glossary not found" + messageSuffix);
+ } else {
+ throw new NotFoundException("Not found" + messageSuffix);
+ }
case 429:
throw new TooManyRequestsException(
"Too many requests, DeepL servers are currently experiencing high load"
@@ -792,11 +1136,7 @@ private void checkResponse(HttpResponse response, boolean inDocumentDownload)
}
private int calculateDocumentWaitTimeMillis(Long secondsRemaining) {
- if (secondsRemaining != null) {
- double secs = ((double) secondsRemaining) / 2.0 + 1.0;
- secs = max(1.0, min(secs, 60.0));
- return (int) (secs * 1000);
- }
- return 1000;
+ // secondsRemaining is currently unreliable, so just poll equidistantly
+ return 5000;
}
}
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 e080ac9..42f4634 100644
--- a/deepl-java/src/main/java/com/deepl/api/TranslatorOptions.java
+++ b/deepl-java/src/main/java/com/deepl/api/TranslatorOptions.java
@@ -24,6 +24,15 @@ public class TranslatorOptions {
@Nullable private Proxy proxy = null;
@Nullable private Map headers = null;
@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
@@ -69,6 +78,26 @@ public TranslatorOptions setServerUrl(String serverUrl) {
return this;
}
+ /**
+ * Set whether to send basic platform information with each API call to improve DeepL products.
+ * Defaults to `true`, set to `false` to opt out. This option will be overriden if a
+ * `'User-agent'` header is present in this objects `headers`.
+ */
+ public TranslatorOptions setSendPlatformInfo(boolean sendPlatformInfo) {
+ this.sendPlatformInfo = sendPlatformInfo;
+ return this;
+ }
+
+ /**
+ * Set an identifier and a version for the program/plugin that uses this Client Library. Example:
+ * `Translator t = new Translator(myAuthKey, new TranslatorOptions()
+ * .setAppInfo('deepl-hadoop-plugin', '1.2.0'))
+ */
+ public TranslatorOptions setAppInfo(String appName, String appVersion) {
+ this.appInfo = new AppInfo(appName, appVersion);
+ return this;
+ }
+
/** Gets the current maximum number of retries. */
public int getMaxRetries() {
return maxRetries;
@@ -93,4 +122,14 @@ public Duration getTimeout() {
public @Nullable String getServerUrl() {
return serverUrl;
}
+
+ /** Gets the `sendPlatformInfo` option */
+ public boolean getSendPlatformInfo() {
+ return sendPlatformInfo;
+ }
+
+ /** Gets the `appInfo` identifiers */
+ public @Nullable AppInfo getAppInfo() {
+ return appInfo;
+ }
}
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 d38ac38..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();
@@ -61,8 +82,8 @@ public static HttpContent buildMultipartFormDataContent(
private static HttpContent buildMultipartFormDataContent(
Iterable> params, String boundary) throws Exception {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream();
- PrintWriter writer =
- new PrintWriter(new OutputStreamWriter(stream, StandardCharsets.UTF_8))) {
+ OutputStreamWriter osw = new OutputStreamWriter(stream, StandardCharsets.UTF_8);
+ PrintWriter writer = new PrintWriter(osw)) {
if (params != null) {
for (KeyValuePair entry : params) {
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/ErrorResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/ErrorResponse.java
index 31468e4..49c44c0 100644
--- a/deepl-java/src/main/java/com/deepl/api/parsing/ErrorResponse.java
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/ErrorResponse.java
@@ -5,17 +5,31 @@
import org.jetbrains.annotations.Nullable;
+/**
+ * Class representing error messages returned by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
class ErrorResponse {
- @Nullable String message;
- @Nullable String detail;
+ @Nullable private String message;
+ @Nullable private String detail;
+ /** Returns a diagnostic string including the message and detail (if available). */
public String getErrorMessage() {
StringBuilder sb = new StringBuilder();
- if (message != null) sb.append("message: ").append(message);
- if (detail != null) {
+ if (getMessage() != null) sb.append("message: ").append(getMessage());
+ if (getDetail() != null) {
if (sb.length() != 0) sb.append(", ");
- sb.append("detail: ").append(detail);
+ sb.append("detail: ").append(getDetail());
}
return sb.toString();
}
+
+ public @Nullable String getMessage() {
+ return message;
+ }
+
+ public @Nullable String getDetail() {
+ return detail;
+ }
}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/GlossaryLanguagesResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/GlossaryLanguagesResponse.java
new file mode 100644
index 0000000..7a62744
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/GlossaryLanguagesResponse.java
@@ -0,0 +1,22 @@
+// Copyright 2022 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.annotations.*;
+import java.util.List;
+
+/**
+ * Class representing glossary-languages response from the DeepL API.
+ *
+ *
This class is internal; you should not use this class directly.
+ */
+class GlossaryLanguagesResponse {
+ @SerializedName("supported_languages")
+ private List supportedLanguages;
+
+ public List getSupportedLanguages() {
+ return supportedLanguages;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/GlossaryListResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/GlossaryListResponse.java
new file mode 100644
index 0000000..dd1fbc4
--- /dev/null
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/GlossaryListResponse.java
@@ -0,0 +1,20 @@
+// Copyright 2022 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 java.util.List;
+
+/**
+ * Class representing list-glossaries response by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
+class GlossaryListResponse {
+ private List glossaries;
+
+ public List getGlossaries() {
+ return glossaries;
+ }
+}
diff --git a/deepl-java/src/main/java/com/deepl/api/parsing/LanguageDeserializer.java b/deepl-java/src/main/java/com/deepl/api/parsing/LanguageDeserializer.java
index da16b1c..f884e34 100644
--- a/deepl-java/src/main/java/com/deepl/api/parsing/LanguageDeserializer.java
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/LanguageDeserializer.java
@@ -7,6 +7,11 @@
import com.google.gson.*;
import java.lang.reflect.Type;
+/**
+ * Utility class for deserializing language codes returned by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
class LanguageDeserializer implements JsonDeserializer {
public Language deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
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 b761bb3..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
@@ -10,14 +10,26 @@
import java.util.*;
import org.jetbrains.annotations.*;
+/**
+ * Parsing functions for responses from the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
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();
}
@@ -26,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);
}
@@ -35,6 +52,10 @@ public List parseLanguages(String json) {
return gson.fromJson(json, languageListType);
}
+ public List parseGlossaryLanguageList(String json) {
+ return gson.fromJson(json, GlossaryLanguagesResponse.class).getSupportedLanguages();
+ }
+
public DocumentStatus parseDocumentStatus(String json) {
return gson.fromJson(json, DocumentStatus.class);
}
@@ -43,6 +64,75 @@ public DocumentHandle parseDocumentHandle(String json) {
return gson.fromJson(json, DocumentHandle.class);
}
+ 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);
@@ -54,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/TextResponse.java b/deepl-java/src/main/java/com/deepl/api/parsing/TextResponse.java
index c6515d1..0f17434 100644
--- a/deepl-java/src/main/java/com/deepl/api/parsing/TextResponse.java
+++ b/deepl-java/src/main/java/com/deepl/api/parsing/TextResponse.java
@@ -6,6 +6,11 @@
import com.deepl.api.TextResult;
import java.util.List;
+/**
+ * Class representing text translation responses from the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
class TextResponse {
public List translations;
}
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 43a849f..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
@@ -7,12 +7,20 @@
import com.google.gson.*;
import java.lang.reflect.Type;
+/**
+ * Utility class for deserializing text translation results returned by the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
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 ce2d417..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
@@ -8,6 +8,11 @@
import java.lang.reflect.*;
import org.jetbrains.annotations.*;
+/**
+ * Class representing usage responses from the DeepL API.
+ *
+ * This class is internal; you should not use this class directly.
+ */
class UsageDeserializer implements JsonDeserializer {
public Usage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
@@ -20,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/main/java/com/deepl/api/utils/StreamUtil.java b/deepl-java/src/main/java/com/deepl/api/utils/StreamUtil.java
index 662f606..f46b1b3 100644
--- a/deepl-java/src/main/java/com/deepl/api/utils/StreamUtil.java
+++ b/deepl-java/src/main/java/com/deepl/api/utils/StreamUtil.java
@@ -13,10 +13,12 @@ public static String readStream(InputStream inputStream) throws IOException {
Charset charset = StandardCharsets.UTF_8;
final char[] buffer = new char[DEFAULT_BUFFER_SIZE];
final StringBuilder sb = new StringBuilder();
- final Reader in = new BufferedReader(new InputStreamReader(inputStream, charset));
- int charsRead;
- while ((charsRead = in.read(buffer, 0, DEFAULT_BUFFER_SIZE)) > 0) {
- sb.append(buffer, 0, charsRead);
+ try (InputStreamReader isr = new InputStreamReader(inputStream, charset);
+ final Reader in = new BufferedReader(isr)) {
+ int charsRead;
+ while ((charsRead = in.read(buffer, 0, DEFAULT_BUFFER_SIZE)) > 0) {
+ sb.append(buffer, 0, charsRead);
+ }
}
return sb.toString();
}
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 9912753..833e715 100644
--- a/deepl-java/src/test/java/com/deepl/api/GeneralTest.java
+++ b/deepl-java/src/test/java/com/deepl/api/GeneralTest.java
@@ -7,7 +7,15 @@
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;
class GeneralTest extends TestBase {
@@ -21,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";
@@ -36,10 +54,25 @@ void testExampleTranslation() throws DeepLException, InterruptedException {
String inputText = entry.getValue();
String sourceLang = LanguageCode.removeRegionalVariant(entry.getKey());
TextResult result = translator.translateText(inputText, sourceLang, "en-US");
- Assertions.assertTrue(result.getText().toLowerCase().contains("proton"));
+ 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(
@@ -51,6 +84,28 @@ void testInvalidServerUrl() {
});
}
+ @Test
+ void testMixedDirectionText() throws DeepLException, InterruptedException {
+ Assumptions.assumeFalse(isMockServer);
+ Translator translator = createTranslator();
+ TextTranslationOptions options =
+ new TextTranslationOptions().setTagHandling("xml").setIgnoreTags(Arrays.asList("xml"));
+ String arIgnorePart = "يجب تجاهل هذا الجزء.";
+ String enSentenceWithArIgnorePart =
+ "This is a short sentence.
"
+ + arIgnorePart
+ + " This is another sentence.";
+ String enIgnorePart = "This part should be ignored.";
+ String arSentenceWithEnIgnorePart =
+ "هذه جملة قصيرة. " + enIgnorePart + "هذه جملة أخرى.
";
+
+ TextResult enResult =
+ translator.translateText(enSentenceWithArIgnorePart, null, "en-US", options);
+ Assertions.assertTrue(enResult.getText().contains(arIgnorePart));
+ TextResult arResult = translator.translateText(arSentenceWithEnIgnorePart, null, "ar", options);
+ Assertions.assertTrue(arResult.getText().contains(enIgnorePart));
+ }
+
@Test
void testUsage() throws DeepLException, InterruptedException {
Translator translator = createTranslator();
@@ -58,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();
@@ -70,7 +140,7 @@ void testGetSourceAndTargetLanguages() throws DeepLException, InterruptedExcepti
}
Assertions.assertNull(language.getSupportsFormality());
}
- Assertions.assertTrue(sourceLanguages.size() > 20);
+ Assertions.assertTrue(sourceLanguages.size() >= 29);
for (Language language : targetLanguages) {
Assertions.assertNotNull(language.getSupportsFormality());
@@ -79,7 +149,18 @@ void testGetSourceAndTargetLanguages() throws DeepLException, InterruptedExcepti
Assertions.assertEquals("German", language.getName());
}
}
- Assertions.assertTrue(targetLanguages.size() > 20);
+ Assertions.assertTrue(targetLanguages.size() >= 31);
+ }
+
+ @Test
+ void testGetGlossaryLanguages() throws DeepLException, InterruptedException {
+ Translator translator = createTranslator();
+ List glossaryLanguagePairs = translator.getGlossaryLanguages();
+ Assertions.assertTrue(glossaryLanguagePairs.size() > 0);
+ for (GlossaryLanguagePair glossaryLanguagePair : glossaryLanguagePairs) {
+ Assertions.assertTrue(glossaryLanguagePair.getSourceLanguage().length() > 0);
+ Assertions.assertTrue(glossaryLanguagePair.getTargetLanguage().length() > 0);
+ }
}
@Test
@@ -226,4 +307,125 @@ void testUsageTeamDocumentLimit() throws Exception {
Assertions.assertNotNull(usage.getTeamDocument());
Assertions.assertTrue(usage.getTeamDocument().limitReached());
}
+
+ @ParameterizedTest
+ @MethodSource("provideUserAgentTestData")
+ void testUserAgent(
+ SessionOptions sessionOptions,
+ TranslatorOptions translatorOptions,
+ Iterable requiredStrings,
+ Iterable blocklistedStrings)
+ throws Exception {
+ Map headers = new HashMap<>();
+ HttpURLConnection con = Mockito.mock(HttpURLConnection.class);
+ Mockito.doAnswer(
+ invocation -> {
+ String key = (String) invocation.getArgument(0);
+ String value = (String) invocation.getArgument(1);
+ headers.put(key, value);
+ return null;
+ })
+ .when(con)
+ .setRequestProperty(Mockito.any(String.class), Mockito.any(String.class));
+ Mockito.when(con.getResponseCode()).thenReturn(200);
+ try (MockedConstruction mockUrl =
+ Mockito.mockConstruction(
+ URL.class,
+ (mock, context) -> {
+ Mockito.when(mock.openConnection()).thenReturn(con);
+ })) {
+ Translator translator = createTranslator(sessionOptions, translatorOptions);
+ Usage usage = translator.getUsage();
+ String userAgentHeader = headers.get("User-Agent");
+ for (String s : requiredStrings) {
+ Assertions.assertTrue(
+ userAgentHeader.contains(s),
+ String.format(
+ "Expected User-Agent header to contain %s\nActual:\n%s", s, userAgentHeader));
+ }
+ for (String n : blocklistedStrings) {
+ Assertions.assertFalse(
+ userAgentHeader.contains(n),
+ String.format(
+ "Expected User-Agent header not to contain %s\nActual:\n%s", n, userAgentHeader));
+ }
+ }
+ }
+
+ @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
+ private static Stream extends Arguments> provideUserAgentTestData() {
+ Map testHeaders = new HashMap<>();
+ testHeaders.put("User-Agent", "my custom user agent");
+ Iterable lightPlatformInfo = Arrays.asList("deepl-java/");
+ Iterable lightPlatformInfoWithAppInfo =
+ Arrays.asList("deepl", "my-java-translation-plugin/1.2.3");
+ Iterable detailedPlatformInfo = Arrays.asList(" java/", "(");
+ Iterable detailedPlatformInfoWithAppInfo =
+ Arrays.asList(" java/", "(", "my-java-translation-plugin/1.2.3");
+ Iterable customUserAgent = Arrays.asList("my custom user agent");
+ Iterable noStrings = new ArrayList();
+ return Stream.of(
+ Arguments.of(
+ new SessionOptions(), new TranslatorOptions(), detailedPlatformInfo, noStrings),
+ Arguments.of(
+ new SessionOptions(),
+ new TranslatorOptions().setSendPlatformInfo(false),
+ lightPlatformInfo,
+ detailedPlatformInfo),
+ Arguments.of(
+ new SessionOptions(),
+ new TranslatorOptions().setHeaders(testHeaders),
+ customUserAgent,
+ detailedPlatformInfo),
+ Arguments.of(
+ new SessionOptions(),
+ new TranslatorOptions().setAppInfo("my-java-translation-plugin", "1.2.3"),
+ detailedPlatformInfoWithAppInfo,
+ noStrings),
+ Arguments.of(
+ new SessionOptions(),
+ new TranslatorOptions()
+ .setSendPlatformInfo(false)
+ .setAppInfo("my-java-translation-plugin", "1.2.3"),
+ lightPlatformInfoWithAppInfo,
+ detailedPlatformInfo),
+ Arguments.of(
+ new SessionOptions(),
+ new TranslatorOptions()
+ .setHeaders(testHeaders)
+ .setAppInfo("my-java-translation-plugin", "1.2.3"),
+ customUserAgent,
+ detailedPlatformInfoWithAppInfo));
+ }
+
+ boolean runV1ApiTests() {
+ return Boolean.getBoolean("runV1ApiTests");
+ }
}
diff --git a/deepl-java/src/test/java/com/deepl/api/GlossaryCleanupUtility.java b/deepl-java/src/test/java/com/deepl/api/GlossaryCleanupUtility.java
new file mode 100644
index 0000000..160cb89
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/GlossaryCleanupUtility.java
@@ -0,0 +1,54 @@
+// Copyright 2022 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.*;
+
+public class GlossaryCleanupUtility implements AutoCloseable {
+ private final String glossaryName;
+ private final Translator translator;
+
+ public GlossaryCleanupUtility(Translator translator) {
+ this(translator, "");
+ }
+
+ public GlossaryCleanupUtility(Translator translator, 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.translator = translator;
+ }
+
+ public String getGlossaryName() {
+ return glossaryName;
+ }
+
+ @Override
+ public void close() throws Exception {
+ List glossaries = translator.listGlossaries();
+ for (GlossaryInfo glossary : glossaries) {
+ if (Objects.equals(glossary.getName(), glossaryName)) {
+ try {
+ translator.deleteGlossary(glossary);
+ } catch (Exception exception) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ 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(GlossaryCleanupUtility.class.getName())
+ && stacktrace[i - 1].getClassName().equals(GlossaryCleanupUtility.class.getName())) {
+ return stacktrace[i].getMethodName();
+ }
+ }
+ return "unknown";
+ }
+}
diff --git a/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java b/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java
new file mode 100644
index 0000000..11a1fc1
--- /dev/null
+++ b/deepl-java/src/test/java/com/deepl/api/GlossaryTest.java
@@ -0,0 +1,489 @@
+// Copyright 2022 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.utils.*;
+import java.io.*;
+import java.util.*;
+import org.junit.jupiter.api.*;
+
+public class GlossaryTest 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");
+
+ @Test
+ void TestGlossaryEntries() {
+ GlossaryEntries testEntries = new GlossaryEntries();
+ testEntries.put("apple", "Apfel");
+ testEntries.put("crab apple", "Holzapfel");
+ Assertions.assertEquals(
+ testEntries, GlossaryEntries.fromTsv("apple\tApfel\n crab apple \t Holzapfel "));
+ Assertions.assertThrows(Exception.class, () -> GlossaryEntries.fromTsv(""));
+ Assertions.assertThrows(
+ Exception.class, () -> GlossaryEntries.fromTsv("Küche\tKitchen\nKüche\tCuisine"));
+ Assertions.assertThrows(Exception.class, () -> GlossaryEntries.fromTsv("A\tB\tC"));
+ Assertions.assertThrows(Exception.class, () -> GlossaryEntries.fromTsv("A\t "));
+
+ Assertions.assertThrows(
+ Exception.class, () -> new GlossaryEntries(Collections.singletonMap("A", "B\tC")));
+ }
+
+ @Test
+ void testGlossaryCreate() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ 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();
+ GlossaryInfo glossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, entries);
+
+ Assertions.assertEquals(glossaryName, glossary.getName());
+ Assertions.assertEquals(sourceLang, glossary.getSourceLang());
+ Assertions.assertEquals(targetLang, glossary.getTargetLang());
+ Assertions.assertEquals(1, glossary.getEntryCount());
+
+ GlossaryInfo getResult = translator.getGlossary(glossary.getGlossaryId());
+ Assertions.assertEquals(getResult.getName(), glossary.getName());
+ Assertions.assertEquals(getResult.getSourceLang(), glossary.getSourceLang());
+ Assertions.assertEquals(getResult.getTargetLang(), glossary.getTargetLang());
+ Assertions.assertEquals(getResult.getCreationTime(), glossary.getCreationTime());
+ Assertions.assertEquals(getResult.getEntryCount(), glossary.getEntryCount());
+ }
+ }
+
+ @Test
+ void testGlossaryCreateLarge() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ 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);
+ GlossaryInfo glossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, entries);
+
+ Assertions.assertEquals(glossaryName, glossary.getName());
+ Assertions.assertEquals(sourceLang, glossary.getSourceLang());
+ Assertions.assertEquals(targetLang, glossary.getTargetLang());
+ Assertions.assertEquals(entryPairs.size(), glossary.getEntryCount());
+ }
+ }
+
+ @Test
+ void testGlossaryCreateCsv() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ 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";
+
+ GlossaryInfo glossary =
+ translator.createGlossaryFromCsv(glossaryName, sourceLang, targetLang, csvContent);
+
+ GlossaryEntries entries = translator.getGlossaryEntries(glossary);
+ Assertions.assertEquals(expectedEntries, entries);
+ }
+ }
+
+ @Test
+ void testGlossaryCreateInvalid() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ String glossaryName = cleanup.getGlossaryName();
+ Assertions.assertThrows(
+ Exception.class,
+ () -> translator.createGlossary("", sourceLang, targetLang, testEntries));
+ Assertions.assertThrows(
+ Exception.class, () -> translator.createGlossary(glossaryName, "en", "xx", testEntries));
+ }
+ }
+
+ @Test
+ void testGlossaryGet() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ String glossaryName = cleanup.getGlossaryName();
+ GlossaryInfo createdGlossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, testEntries);
+
+ GlossaryInfo glossary = translator.getGlossary(createdGlossary.getGlossaryId());
+ Assertions.assertEquals(createdGlossary.getGlossaryId(), glossary.getGlossaryId());
+ Assertions.assertEquals(glossaryName, glossary.getName());
+ Assertions.assertEquals(sourceLang, glossary.getSourceLang());
+ Assertions.assertEquals(targetLang, glossary.getTargetLang());
+ Assertions.assertEquals(createdGlossary.getCreationTime(), glossary.getCreationTime());
+ Assertions.assertEquals(testEntries.size(), glossary.getEntryCount());
+ }
+ Assertions.assertThrows(DeepLException.class, () -> translator.getGlossary(invalidGlossaryId));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class, () -> translator.getGlossary(nonexistentGlossaryId));
+ }
+
+ @Test
+ void testGlossaryGetEntries() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ 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");
+
+ GlossaryInfo createdGlossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, entries);
+ Assertions.assertEquals(entries, translator.getGlossaryEntries(createdGlossary));
+ Assertions.assertEquals(
+ entries, translator.getGlossaryEntries(createdGlossary.getGlossaryId()));
+ }
+
+ Assertions.assertThrows(
+ DeepLException.class, () -> translator.getGlossaryEntries(invalidGlossaryId));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class,
+ () -> translator.getGlossaryEntries(nonexistentGlossaryId));
+ }
+
+ @Test
+ void testGlossaryList() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ String glossaryName = cleanup.getGlossaryName();
+ translator.createGlossary(glossaryName, sourceLang, targetLang, testEntries);
+
+ List glossaries = translator.listGlossaries();
+ Assertions.assertTrue(
+ glossaries.stream()
+ .anyMatch((glossaryInfo -> Objects.equals(glossaryInfo.getName(), glossaryName))));
+ }
+ }
+
+ @Test
+ void testGlossaryDelete() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ String glossaryName = cleanup.getGlossaryName();
+ GlossaryInfo glossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, testEntries);
+
+ translator.deleteGlossary(glossary);
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class, () -> translator.deleteGlossary(glossary));
+
+ Assertions.assertThrows(
+ DeepLException.class, () -> translator.deleteGlossary(invalidGlossaryId));
+ Assertions.assertThrows(
+ GlossaryNotFoundException.class, () -> translator.deleteGlossary(nonexistentGlossaryId));
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateTextSentence() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ String glossaryName = cleanup.getGlossaryName();
+ GlossaryEntries entries =
+ new GlossaryEntries() {
+ {
+ put("artist", "Maler");
+ put("prize", "Gewinn");
+ }
+ };
+ String inputText = "The artist was awarded a prize.";
+
+ GlossaryInfo glossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, entries);
+
+ TextResult result =
+ translator.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 =
+ translator.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 {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanupEnDe = new GlossaryCleanupUtility(translator, "EnDe");
+ GlossaryCleanupUtility cleanupDeEn = new GlossaryCleanupUtility(translator, "DeEn")) {
+ String glossaryNameEnDe = cleanupEnDe.getGlossaryName();
+ String glossaryNameDeEn = cleanupDeEn.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));
+ }
+
+ GlossaryInfo glossaryEnDe =
+ translator.createGlossary(glossaryNameEnDe, "en", "de", glossaryEntriesEnDe);
+ GlossaryInfo glossaryDeEn =
+ translator.createGlossary(glossaryNameDeEn, "de", "en", glossaryEntriesDeEn);
+
+ List result =
+ translator.translateText(
+ textsEn, "en", "de", new TextTranslationOptions().setGlossary(glossaryEnDe));
+ Assertions.assertArrayEquals(
+ textsDe.toArray(), result.stream().map(TextResult::getText).toArray());
+
+ result =
+ translator.translateText(
+ textsDe,
+ "de",
+ "en-US",
+ new TextTranslationOptions().setGlossary(glossaryDeEn.getGlossaryId()));
+ Assertions.assertArrayEquals(
+ textsEn.toArray(), result.stream().map(TextResult::getText).toArray());
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateDocument() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanup = new GlossaryCleanupUtility(translator)) {
+ 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");
+ }
+ };
+
+ GlossaryInfo glossary =
+ translator.createGlossary(glossaryName, sourceLang, targetLang, entries);
+
+ translator.translateDocument(
+ inputFile,
+ outputFile,
+ sourceLang,
+ targetLang,
+ new DocumentTranslationOptions().setGlossary(glossary));
+ Assertions.assertEquals(expectedOutput, readFromFile(outputFile));
+ boolean ignored = outputFile.delete();
+
+ translator.translateDocument(
+ inputFile,
+ outputFile,
+ sourceLang,
+ targetLang,
+ new DocumentTranslationOptions().setGlossary(glossary.getGlossaryId()));
+ Assertions.assertEquals(expectedOutput, readFromFile(outputFile));
+ }
+ }
+
+ @Test
+ void testGlossaryTranslateTextInvalid() throws Exception {
+ Translator translator = createTranslator();
+ try (GlossaryCleanupUtility cleanupEnDe = new GlossaryCleanupUtility(translator, "EnDe");
+ GlossaryCleanupUtility cleanupDeEn = new GlossaryCleanupUtility(translator, "DeEn")) {
+ String glossaryNameEnDe = cleanupEnDe.getGlossaryName();
+ String glossaryNameDeEn = cleanupDeEn.getGlossaryName();
+
+ GlossaryInfo glossaryEnDe =
+ translator.createGlossary(glossaryNameEnDe, "en", "de", testEntries);
+ GlossaryInfo glossaryDeEn =
+ translator.createGlossary(glossaryNameDeEn, "de", "en", testEntries);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ translator.translateText(
+ "test", null, "de", new TextTranslationOptions().setGlossary(glossaryEnDe)));
+ Assertions.assertTrue(exception.getMessage().contains("sourceLang is required"));
+
+ exception =
+ Assertions.assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ translator.translateText(
+ "test", "de", "en", new TextTranslationOptions().setGlossary(glossaryDeEn)));
+ 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