Skip to content

Commit 75e04f9

Browse files
feat: Add ability to text translate with a translation memory
1 parent de4baa4 commit 75e04f9

6 files changed

Lines changed: 288 additions & 0 deletions

File tree

README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ using the following keys:
149149
- `glossary`: glossary ID of glossary to use for translation.
150150
- `style_id`: specifies a style rule to use with translation, either as a string
151151
containing the ID of the style rule, or a `StyleRuleInfo` object.
152+
- `translation_memory_id`: specifies a translation memory to use with translation,
153+
either as a string containing the ID of the translation memory, or a
154+
`TranslationMemoryInfo` object.
155+
- `translation_memory_threshold`: an integer from 0 to 100 controlling the
156+
minimum matching percentage for translation memory matches. We recommend
157+
a minimum threshold of 75%.
152158
- `model_type`: specifies the type of translation model to use, options are:
153159
- `'quality_optimized'`: use a translation model that maximizes translation quality, at
154160
the cost of response time. This option may be unavailable for
@@ -764,6 +770,64 @@ $result = $deeplClient->translateText(
764770
);
765771
```
766772

773+
### Translation Memories
774+
775+
Translation memories allow you to leverage previously translated segments to
776+
improve consistency and efficiency. Translation memories are managed on your
777+
account, each with a user-specified name and a uniquely-assigned ID.
778+
779+
#### Uploading and managing translation memories
780+
781+
Currently translation memories must be uploaded and managed in the DeepL UI via
782+
https://www.deepl.com/translation-memory. Full CRUD functionality via the APIs will
783+
come shortly.
784+
785+
#### Listing all translation memories
786+
787+
`listTranslationMemories()` returns a list of `TranslationMemoryInfo` objects
788+
corresponding to all of your stored translation memories. The method accepts
789+
optional parameters: `page` (page number for pagination, 0-indexed) and
790+
`pageSize` (number of items per page).
791+
792+
```php
793+
$translationMemories = $deeplClient->listTranslationMemories();
794+
foreach ($translationMemories as $tm) {
795+
echo "{$tm->name} ({$tm->translationMemoryId})\n";
796+
echo " Source: {$tm->sourceLanguage}\n";
797+
echo " Targets: " . implode(', ', $tm->targetLanguages) . "\n";
798+
echo " Segments: {$tm->segmentCount}\n";
799+
}
800+
```
801+
802+
#### Using a translation memory for translation
803+
804+
You can use a translation memory for text translation by setting the
805+
`translation_memory_id` option to either the translation memory ID or a
806+
`TranslationMemoryInfo` object. Optionally, set `translation_memory_threshold`
807+
to control the minimum similarity score for matches:
808+
809+
```php
810+
// Using a translation memory ID
811+
$result = $deeplClient->translateText(
812+
'Hello, world!',
813+
'en',
814+
'de',
815+
['translation_memory_id' => 'tm-example-id-0001']
816+
);
817+
818+
// Using a TranslationMemoryInfo object with threshold
819+
$translationMemories = $deeplClient->listTranslationMemories();
820+
$result = $deeplClient->translateText(
821+
'Hello, world!',
822+
'en',
823+
'de',
824+
[
825+
'translation_memory_id' => $translationMemories[0],
826+
'translation_memory_threshold' => 80,
827+
]
828+
);
829+
```
830+
767831
### Writing a Plugin
768832

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

src/DeepLClient.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -677,4 +677,31 @@ public function deleteStyleRuleCustomInstruction($styleRule, string $instruction
677677
);
678678
$this->checkStatusCode($response);
679679
}
680+
681+
/**
682+
* Retrieves a list of available translation memories. The maximum number of translation memories
683+
* returned is controlled by pageSize (max 25).
684+
* @param int|null $page Page number for pagination, 0-indexed (optional).
685+
* @param int|null $pageSize Number of items per page (optional).
686+
* @return TranslationMemoryInfo[] List of TranslationMemoryInfo objects for all available translation memories.
687+
* @throws DeepLException
688+
*/
689+
public function listTranslationMemories(
690+
?int $page = null,
691+
?int $pageSize = null
692+
): array {
693+
$queryParams = [];
694+
if ($page !== null) {
695+
$queryParams['page'] = $page;
696+
}
697+
if ($pageSize !== null) {
698+
$queryParams['page_size'] = $pageSize;
699+
}
700+
$queryString = empty($queryParams) ? '' : '?' . http_build_query($queryParams);
701+
702+
$response = $this->client->sendRequestWithBackoff('GET', "/v3/translation_memories$queryString");
703+
$this->checkStatusCode($response);
704+
list(, $content) = $response;
705+
return TranslationMemoryInfo::parseList($content);
706+
}
680707
}

src/TranslateTextOptions.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,15 @@ class TranslateTextOptions
103103
* model type will be rejected.
104104
*/
105105
public const CUSTOM_INSTRUCTIONS = 'custom_instructions';
106+
107+
/** Set to string containing a translation memory ID to use the translation memory for translation.
108+
* Can also be set to a TranslationMemoryInfo as returned by listTranslationMemories.
109+
* @see \DeepL\DeepLClient::listTranslationMemories()
110+
*/
111+
public const TRANSLATION_MEMORY_ID = 'translation_memory_id';
112+
113+
/** Set to an integer between 0 and 100 to control the minimum matching percentage
114+
* for translation memory matches. We recommend a minimum threshold of 75%.
115+
*/
116+
public const TRANSLATION_MEMORY_THRESHOLD = 'translation_memory_threshold';
106117
}

src/TranslationMemoryInfo.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
// Copyright 2025 DeepL SE (https://www.deepl.com)
4+
// Use of this source code is governed by an MIT
5+
// license that can be found in the LICENSE file.
6+
7+
namespace DeepL;
8+
9+
use JsonException;
10+
11+
/**
12+
* Information about a translation memory.
13+
*/
14+
class TranslationMemoryInfo
15+
{
16+
/** @var string Unique ID assigned to the translation memory. */
17+
public $translationMemoryId;
18+
19+
/** @var string User-defined name assigned to the translation memory. */
20+
public $name;
21+
22+
/** @var string Language code for the source language of the translation memory. */
23+
public $sourceLanguage;
24+
25+
/** @var string[] List of target language codes for the translation memory. */
26+
public $targetLanguages;
27+
28+
/** @var int Number of segments in the translation memory. */
29+
public $segmentCount;
30+
31+
public function __construct(
32+
string $translationMemoryId,
33+
string $name,
34+
string $sourceLanguage,
35+
array $targetLanguages,
36+
int $segmentCount
37+
) {
38+
$this->translationMemoryId = $translationMemoryId;
39+
$this->name = $name;
40+
$this->sourceLanguage = $sourceLanguage;
41+
$this->targetLanguages = $targetLanguages;
42+
$this->segmentCount = $segmentCount;
43+
}
44+
45+
/**
46+
* @param string|TranslationMemoryInfo $translationMemory Translation memory ID or TranslationMemoryInfo.
47+
*/
48+
public static function getTranslationMemoryId($translationMemory): string
49+
{
50+
return is_string($translationMemory) ? $translationMemory : $translationMemory->translationMemoryId;
51+
}
52+
53+
/**
54+
* @throws InvalidContentException
55+
*/
56+
public static function fromJson(array $json): TranslationMemoryInfo
57+
{
58+
return new TranslationMemoryInfo(
59+
$json['translation_memory_id'],
60+
$json['name'],
61+
$json['source_language'],
62+
$json['target_languages'] ?? [],
63+
$json['segment_count'] ?? 0
64+
);
65+
}
66+
67+
/**
68+
* @throws InvalidContentException
69+
*/
70+
public static function parseList(string $content): array
71+
{
72+
try {
73+
$decoded = json_decode($content, true, 512, \JSON_THROW_ON_ERROR);
74+
} catch (JsonException $exception) {
75+
throw new InvalidContentException($exception);
76+
}
77+
78+
$result = [];
79+
$translationMemories = $decoded['translation_memories'] ?? [];
80+
foreach ($translationMemories as $object) {
81+
$result[] = self::fromJson($object);
82+
}
83+
return $result;
84+
}
85+
86+
public function __toString(): string
87+
{
88+
return "TranslationMemory \"{$this->name}\" ({$this->translationMemoryId})";
89+
}
90+
}

src/Translator.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,26 @@ private function validateAndAppendTextOptions(array &$params, ?array $options):
709709
if (isset($options[TranslateTextOptions::CUSTOM_INSTRUCTIONS])) {
710710
$params[TranslateTextOptions::CUSTOM_INSTRUCTIONS] = $options[TranslateTextOptions::CUSTOM_INSTRUCTIONS];
711711
}
712+
if (isset($options[TranslateTextOptions::TRANSLATION_MEMORY_ID])) {
713+
$tm = $options[TranslateTextOptions::TRANSLATION_MEMORY_ID];
714+
if (is_string($tm)) {
715+
$params['translation_memory_id'] = $tm;
716+
} elseif ($tm instanceof TranslationMemoryInfo) {
717+
$params['translation_memory_id'] = $tm->translationMemoryId;
718+
} else {
719+
throw new DeepLException('translation_memory_id must be a string or TranslationMemoryInfo object');
720+
}
721+
}
722+
if (isset($options[TranslateTextOptions::TRANSLATION_MEMORY_THRESHOLD])) {
723+
if (!isset($options[TranslateTextOptions::TRANSLATION_MEMORY_ID])) {
724+
throw new DeepLException('translation_memory_threshold requires translation_memory_id');
725+
}
726+
$threshold = $options[TranslateTextOptions::TRANSLATION_MEMORY_THRESHOLD];
727+
if (!is_int($threshold) || $threshold < 0 || $threshold > 100) {
728+
throw new DeepLException('translation_memory_threshold must be an integer between 0 and 100');
729+
}
730+
$params['translation_memory_threshold'] = strval($threshold);
731+
}
712732
$this->applyExtraBodyParameters(
713733
$params,
714734
$options[TranslateTextOptions::EXTRA_BODY_PARAMETERS] ?? null

tests/TranslationMemoryTest.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
// Copyright 2025 DeepL SE (https://www.deepl.com)
4+
// Use of this source code is governed by an MIT
5+
// license that can be found in the LICENSE file.
6+
7+
namespace DeepL;
8+
9+
use Psr\Http\Client\ClientInterface;
10+
11+
class TranslationMemoryTest extends DeepLTestBase
12+
{
13+
private const DEFAULT_TM_ID = 'a74d88fb-ed2a-4943-a664-a4512398b994';
14+
15+
/**
16+
* @dataProvider provideHttpClient
17+
*/
18+
public function testListTranslationMemories(?ClientInterface $httpClient)
19+
{
20+
$this->needsMockServer();
21+
$client = $this->makeDeeplClient([TranslatorOptions::HTTP_CLIENT => $httpClient]);
22+
$translationMemories = $client->listTranslationMemories(0, 10);
23+
$this->assertIsArray($translationMemories);
24+
$this->assertGreaterThan(0, count($translationMemories));
25+
$this->assertNotEmpty($translationMemories[0]->translationMemoryId);
26+
$this->assertNotEmpty($translationMemories[0]->name);
27+
$this->assertNotEmpty($translationMemories[0]->sourceLanguage);
28+
$this->assertIsArray($translationMemories[0]->targetLanguages);
29+
$this->assertIsInt($translationMemories[0]->segmentCount);
30+
}
31+
32+
/**
33+
* @dataProvider provideHttpClient
34+
*/
35+
public function testTranslateTextWithTranslationMemoryId(?ClientInterface $httpClient)
36+
{
37+
$this->needsMockServer();
38+
// Note: this test may use the mock server that will not translate the text,
39+
// therefore we do not check the translated result.
40+
$client = $this->makeDeeplClient([TranslatorOptions::HTTP_CLIENT => $httpClient]);
41+
$exampleText = DeepLTestBase::EXAMPLE_TEXT['de'];
42+
43+
$result = $client->translateText(
44+
$exampleText,
45+
'de',
46+
'en-US',
47+
[TranslateTextOptions::TRANSLATION_MEMORY_ID => self::DEFAULT_TM_ID]
48+
);
49+
50+
$this->assertNotNull($result);
51+
}
52+
53+
/**
54+
* @dataProvider provideHttpClient
55+
*/
56+
public function testTranslateTextWithTranslationMemoryIdAndThreshold(?ClientInterface $httpClient)
57+
{
58+
$this->needsMockServer();
59+
// Note: this test may use the mock server that will not translate the text,
60+
// therefore we do not check the translated result.
61+
$client = $this->makeDeeplClient([TranslatorOptions::HTTP_CLIENT => $httpClient]);
62+
$exampleText = DeepLTestBase::EXAMPLE_TEXT['de'];
63+
64+
$result = $client->translateText(
65+
$exampleText,
66+
'de',
67+
'en-US',
68+
[
69+
TranslateTextOptions::TRANSLATION_MEMORY_ID => self::DEFAULT_TM_ID,
70+
TranslateTextOptions::TRANSLATION_MEMORY_THRESHOLD => 80,
71+
]
72+
);
73+
74+
$this->assertNotNull($result);
75+
}
76+
}

0 commit comments

Comments
 (0)