Skip to content

Commit e709713

Browse files
feat: Add ability to text translate with a translation memory
1 parent 5367710 commit e709713

6 files changed

Lines changed: 261 additions & 7 deletions

File tree

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ arguments are:
178178
use, options are `'v1'` and `'v2'`.
179179
- `style_rule`: specifies a style rule to use with translation, either as a string
180180
containing the ID of the style rule, or a `StyleRuleInfo` object.
181+
- `translation_memory`: specifies a translation memory to use with translation,
182+
either as a string containing the ID of the translation memory, or a
183+
`TranslationMemoryInfo` object.
184+
- `translation_memory_threshold`: the minimum matching percentage for fuzzy
185+
matches from the translation memory (0-100). We recommend a minimum threshold
186+
of 75%.
181187
- `custom_instructions`: an array of instructions to customize the text
182188
translation behavior. Up to 10 custom instructions can be specified, each with
183189
a maximum of 300 characters.
@@ -756,6 +762,68 @@ Style rules can also be used with the command line interface for text translatio
756762
python3 -m deepl --auth-key=YOUR_AUTH_KEY text --to=DE --style-id=YOUR_STYLE_ID "Text to translate"
757763
```
758764

765+
### Translation Memories
766+
767+
Translation memories allow you to store and reuse previously created translations.
768+
They can be used in text translation requests to improve consistency by matching
769+
against stored segments. Multiple translation memories can be stored with your
770+
account, each with a source language and one or more target languages.
771+
772+
#### Uploading and managing translation memories
773+
774+
Currently translation memories must be uploaded and managed in the DeepL UI via
775+
https://www.deepl.com/translation-memory. Full CRUD functionality via the APIs will
776+
come shortly.
777+
778+
#### Listing translation memories
779+
780+
`list_translation_memories()` returns a list of `TranslationMemoryInfo` objects
781+
for your stored translation memories. The number of translation memories
782+
returned is controlled by `page_size` (max 25). The method accepts
783+
optional parameters: `page` (page number for pagination, 0-indexed)
784+
and `page_size` (number of items per page).
785+
786+
```python
787+
# List translation memories
788+
translation_memories = deepl_client.list_translation_memories()
789+
for tm in translation_memories:
790+
print(f"{tm.name} ({tm.translation_memory_id})")
791+
print(f" Source: {tm.source_language}, Targets: {tm.target_languages}")
792+
print(f" Segments: {tm.segment_count}")
793+
```
794+
795+
#### Using a translation memory in translations
796+
797+
Pass the `translation_memory` parameter to `translate_text()` to use a
798+
translation memory. You can pass either a string containing the translation
799+
memory ID, or a `TranslationMemoryInfo` object. Use
800+
`translation_memory_threshold` to control the minimum matching percentage for
801+
fuzzy matches (0-100, recommended minimum of 75%).
802+
803+
```python
804+
# Translate with a translation memory ID
805+
result = deepl_client.translate_text(
806+
"Hello, world!",
807+
target_lang="DE",
808+
translation_memory="YOUR_TM_ID",
809+
translation_memory_threshold=80,
810+
)
811+
812+
# Or use a TranslationMemoryInfo object
813+
translation_memories = deepl_client.list_translation_memories()
814+
result = deepl_client.translate_text(
815+
"Hello, world!",
816+
target_lang="DE",
817+
translation_memory=translation_memories[0],
818+
)
819+
```
820+
821+
Translation memories can also be used with the command line interface:
822+
823+
```bash
824+
python3 -m deepl --auth-key=YOUR_AUTH_KEY text --to=DE --translation-memory-id=YOUR_TM_ID --translation-memory-threshold=75 "Text to translate"
825+
```
826+
759827
### Writing a Plugin
760828

761829
If you use this library in an application, please identify the application with

deepl/__main__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,19 @@ def add_common_arguments(subparser: argparse.ArgumentParser):
385385
type=str,
386386
help="ID of style rule to use for translation",
387387
)
388+
parser_text.add_argument(
389+
"--translation-memory-id",
390+
dest="translation_memory",
391+
type=str,
392+
help="ID of translation memory to use for translation",
393+
)
394+
parser_text.add_argument(
395+
"--translation-memory-threshold",
396+
dest="translation_memory_threshold",
397+
type=int,
398+
help="minimum matching percentage (0-100) for translation memory "
399+
"fuzzy matches, recommended minimum is 75",
400+
)
388401
parser_text.add_argument(
389402
"--custom-instructions",
390403
dest="custom_instructions",

deepl/api_data.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,3 +1005,70 @@ def configured_rules(self) -> Optional[ConfiguredRules]:
10051005
def custom_instructions(self) -> List[CustomInstruction]:
10061006
"""Returns the list of custom instructions."""
10071007
return self._custom_instructions or []
1008+
1009+
1010+
class TranslationMemoryInfo:
1011+
"""Information about a translation memory.
1012+
1013+
:param translation_memory_id: Unique ID assigned to the translation memory.
1014+
:param name: User-defined name assigned to the translation memory.
1015+
:param source_language: Source language code for the translation memory.
1016+
:param target_languages: List of target language codes available.
1017+
:param segment_count: Number of segments stored in the translation memory.
1018+
"""
1019+
1020+
def __init__(
1021+
self,
1022+
translation_memory_id: str,
1023+
name: str,
1024+
source_language: str,
1025+
target_languages: List[str],
1026+
segment_count: int,
1027+
):
1028+
self._translation_memory_id = translation_memory_id
1029+
self._name = name
1030+
self._source_language = source_language
1031+
self._target_languages = target_languages
1032+
self._segment_count = segment_count
1033+
1034+
def __str__(self) -> str:
1035+
return (
1036+
f'TranslationMemory "{self.name}" '
1037+
f"({self.translation_memory_id})"
1038+
)
1039+
1040+
@staticmethod
1041+
def from_json(json) -> "TranslationMemoryInfo":
1042+
"""Create TranslationMemoryInfo from the given API JSON object."""
1043+
return TranslationMemoryInfo(
1044+
translation_memory_id=json["translation_memory_id"],
1045+
name=json["name"],
1046+
source_language=json["source_language"],
1047+
target_languages=json.get("target_languages", []),
1048+
segment_count=json.get("segment_count", 0),
1049+
)
1050+
1051+
@property
1052+
def translation_memory_id(self) -> str:
1053+
"""Returns the unique ID of the translation memory."""
1054+
return self._translation_memory_id
1055+
1056+
@property
1057+
def name(self) -> str:
1058+
"""Returns the name of the translation memory."""
1059+
return self._name
1060+
1061+
@property
1062+
def source_language(self) -> str:
1063+
"""Returns the source language code."""
1064+
return self._source_language
1065+
1066+
@property
1067+
def target_languages(self) -> List[str]:
1068+
"""Returns the list of target language codes."""
1069+
return self._target_languages
1070+
1071+
@property
1072+
def segment_count(self) -> int:
1073+
"""Returns the number of segments stored."""
1074+
return self._segment_count

deepl/deepl_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Language,
1212
WriteResult,
1313
StyleRuleInfo,
14+
TranslationMemoryInfo,
1415
)
1516
from deepl.translator import Translator
1617
from deepl import util
@@ -907,3 +908,39 @@ def delete_style_rule_custom_instruction(
907908
method="DELETE",
908909
)
909910
self._raise_for_status(status, content, json)
911+
912+
def list_translation_memories(
913+
self,
914+
page: Optional[int] = None,
915+
page_size: Optional[int] = None,
916+
) -> List[TranslationMemoryInfo]:
917+
"""Retrieves a list of TranslationMemoryInfo for available
918+
translation memories. The maximum number of translation memories
919+
returned is controlled by page_size (max 25).
920+
921+
:param page: Page number for pagination, 0-indexed (optional).
922+
:param page_size: Number of items per page (optional).
923+
:return: List of TranslationMemoryInfo objects.
924+
"""
925+
params = {}
926+
if page is not None:
927+
params["page"] = str(page)
928+
if page_size is not None:
929+
params["page_size"] = str(page_size)
930+
931+
endpoint = "v3/translation_memories"
932+
if params:
933+
query_string = urllib.parse.urlencode(params)
934+
endpoint += f"?{query_string}"
935+
936+
status, content, json = self._api_call(endpoint, method="GET")
937+
self._raise_for_status(status, content, json)
938+
939+
translation_memories = (
940+
json.get("translation_memories", [])
941+
if (json and isinstance(json, dict))
942+
else []
943+
)
944+
return [
945+
TranslationMemoryInfo.from_json(tm) for tm in translation_memories
946+
]

deepl/translator.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
SplitSentences,
1515
StyleRuleInfo,
1616
TextResult,
17+
TranslationMemoryInfo,
1718
Usage,
1819
)
1920
from . import http_client, util
@@ -264,6 +265,8 @@ def _check_language_and_formality(
264265
str, GlossaryInfo, MultilingualGlossaryInfo, None
265266
] = None,
266267
style_rule: Union[str, StyleRuleInfo, None] = None,
268+
translation_memory: Union[str, TranslationMemoryInfo, None] = None,
269+
translation_memory_threshold: Optional[int] = None,
267270
) -> dict:
268271
# target_lang and source_lang are case insensitive
269272
target_lang = str(target_lang).upper()
@@ -304,7 +307,7 @@ def _check_language_and_formality(
304307

305308
self._check_valid_languages(source_lang, target_lang)
306309

307-
request_data = {"target_lang": target_lang}
310+
request_data: dict = {"target_lang": target_lang}
308311
if source_lang is not None:
309312
request_data["source_lang"] = source_lang
310313
if formality is not None:
@@ -319,6 +322,25 @@ def _check_language_and_formality(
319322
request_data["style_id"] = style_rule.style_id
320323
elif style_rule is not None:
321324
request_data["style_id"] = style_rule
325+
if isinstance(translation_memory, TranslationMemoryInfo):
326+
request_data["translation_memory_id"] = (
327+
translation_memory.translation_memory_id
328+
)
329+
elif translation_memory is not None:
330+
request_data["translation_memory_id"] = translation_memory
331+
if translation_memory_threshold is not None:
332+
if translation_memory is None:
333+
raise ValueError(
334+
"translation_memory_threshold requires"
335+
" translation_memory"
336+
)
337+
if not (0 <= translation_memory_threshold <= 100):
338+
raise ValueError(
339+
"translation_memory_threshold must be between 0 and 100"
340+
)
341+
request_data["translation_memory_threshold"] = (
342+
translation_memory_threshold
343+
)
322344
return request_data
323345

324346
def _create_glossary(
@@ -383,6 +405,8 @@ def translate_text(
383405
ignore_tags: Union[str, List[str], None] = None,
384406
model_type: Union[str, ModelType, None] = None,
385407
style_rule: Union[str, StyleRuleInfo, None] = None,
408+
translation_memory: Union[str, TranslationMemoryInfo, None] = None,
409+
translation_memory_threshold: Optional[int] = None,
386410
custom_instructions: Optional[List[str]] = None,
387411
extra_body_parameters: Optional[dict] = None,
388412
) -> Union[TextResult, List[TextResult]]:
@@ -413,6 +437,11 @@ def translate_text(
413437
translation. Must match specified source_lang and target_lang.
414438
:param style_rule: (Optional) style rule or style rule ID to use for
415439
translation.
440+
:param translation_memory: (Optional) translation memory or translation
441+
memory ID to use for translation.
442+
:param translation_memory_threshold: (Optional) minimum matching
443+
percentage for fuzzy matches from the translation memory (0-100).
444+
Recommended minimum is 75%.
416445
:param tag_handling: (Optional) Type of tags to parse before
417446
translation, only "xml" and "html" are currently available.
418447
:param tag_handling_version: (Optional) Version of tag handling
@@ -456,7 +485,13 @@ def translate_text(
456485
)
457486

458487
request_data = self._check_language_and_formality(
459-
source_lang, target_lang, formality, glossary, style_rule
488+
source_lang,
489+
target_lang,
490+
formality,
491+
glossary,
492+
style_rule,
493+
translation_memory,
494+
translation_memory_threshold,
460495
)
461496
request_data["text"] = text
462497

@@ -478,11 +513,6 @@ def translate_text(
478513
request_data["outline_detection"] = bool(outline_detection)
479514
if model_type is not None:
480515
request_data["model_type"] = str(model_type)
481-
if style_rule is not None:
482-
if isinstance(style_rule, StyleRuleInfo):
483-
request_data["style_id"] = style_rule.style_id
484-
else:
485-
request_data["style_id"] = style_rule
486516

487517
def join_tags(tag_argument: Union[str, Iterable[str]]) -> List[str]:
488518
if isinstance(tag_argument, str):

tests/test_translation_memories.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2022 DeepL SE (https://www.deepl.com)
2+
# Use of this source code is governed by an MIT
3+
# license that can be found in the LICENSE file.
4+
5+
from .conftest import example_text, needs_mock_server
6+
7+
DEFAULT_TM_ID = "a74d88fb-ed2a-4943-a664-a4512398b994"
8+
9+
10+
@needs_mock_server
11+
def test_list_translation_memories(deepl_client):
12+
translation_memories = deepl_client.list_translation_memories()
13+
14+
assert isinstance(translation_memories, list)
15+
assert len(translation_memories) > 0
16+
assert translation_memories[0].translation_memory_id is not None
17+
assert translation_memories[0].name is not None
18+
assert translation_memories[0].source_language is not None
19+
assert isinstance(translation_memories[0].target_languages, list)
20+
assert isinstance(translation_memories[0].segment_count, int)
21+
22+
23+
@needs_mock_server
24+
def test_translate_text_with_translation_memory(deepl_client):
25+
_ = deepl_client.translate_text(
26+
example_text["DE"],
27+
target_lang="EN-US",
28+
translation_memory=DEFAULT_TM_ID,
29+
)
30+
31+
32+
@needs_mock_server
33+
def test_translate_text_with_translation_memory_and_threshold(deepl_client):
34+
_ = deepl_client.translate_text(
35+
example_text["DE"],
36+
target_lang="EN-US",
37+
translation_memory=DEFAULT_TM_ID,
38+
translation_memory_threshold=80,
39+
)

0 commit comments

Comments
 (0)