Skip to content

Commit 548735b

Browse files
feat: Add ability to text translate with a translation memory
1 parent 1c115c8 commit 548735b

6 files changed

Lines changed: 224 additions & 0 deletions

File tree

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ console.log(await deeplClient.translateText('How are you?', null, 'de', { formal
177177
`getMultilingualGlossary()`/`getGlossary()`.
178178
- `styleRule`: specifies a style rule to use with translation, either as a string
179179
containing the style rule ID, or a `StyleRuleInfo` as returned by `getAllStyleRules()`.
180+
- `translationMemory`: specifies a translation memory to use with translation,
181+
either as a string containing the translation memory ID, or a
182+
`TranslationMemoryInfo` as returned by `listTranslationMemories()`.
183+
- `translationMemoryThreshold`: a number from 0 to 100 that specifies the
184+
minimum matching percentage for translation memory matches. We recommend
185+
a minimum threshold of 75%.
180186
- `context`: specifies additional context to influence translations, that is not
181187
translated itself. Characters in the `context` parameter are not counted toward billing.
182188
See the [API documentation][api-docs-context-param] for more information and
@@ -592,6 +598,59 @@ Use `deleteStyleRule()` to delete a style rule by ID.
592598
await deeplClient.deleteStyleRule('YOUR_STYLE_ID');
593599
```
594600

601+
### Translation Memories
602+
603+
Translation memories store and reuse previously created translations, helping to
604+
maintain consistency and reduce translation costs by leveraging past work.
605+
606+
#### Uploading and managing translation memories
607+
608+
Currently translation memories must be uploaded and managed in the DeepL UI via
609+
https://www.deepl.com/translation-memory. Full CRUD functionality via the APIs will
610+
come shortly.
611+
612+
#### Listing translation memories
613+
614+
Use `listTranslationMemories()` to list available translation memories
615+
associated with your account.
616+
617+
```javascript
618+
const translationMemories = await deeplClient.listTranslationMemories();
619+
for (const tm of translationMemories) {
620+
console.log(`${tm.name} (${tm.translationMemoryId})`);
621+
}
622+
```
623+
624+
#### Using a translation memory in translations
625+
626+
Pass the `translationMemory` option to `translateText()` to apply a translation
627+
memory during translation. You can provide either the translation memory ID as a
628+
string, or a `TranslationMemoryInfo` object returned by
629+
`listTranslationMemories()`. Optionally, use `translationMemoryThreshold` to
630+
control the minimum similarity for matches.
631+
632+
```typescript
633+
// Using a translation memory ID
634+
const result = await deeplClient.translateText(
635+
'Hello, world!',
636+
'en',
637+
'de',
638+
{ translationMemory: 'YOUR_TM_ID' },
639+
);
640+
641+
// Using a TranslationMemoryInfo object with a similarity threshold
642+
const translationMemories = await deeplClient.listTranslationMemories();
643+
const result = await deeplClient.translateText(
644+
'Hello, world!',
645+
'en',
646+
'de',
647+
{
648+
translationMemory: translationMemories[0],
649+
translationMemoryThreshold: 80,
650+
},
651+
);
652+
```
653+
595654
### Checking account usage
596655

597656
To check account usage, use the `getUsage()` function.

src/deeplClient.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
StyleRuleInfoApiResponse,
1818
StyleId,
1919
CustomInstruction,
20+
TranslationMemoryInfo,
2021
} from './types';
2122
import {
2223
parseMultilingualGlossaryDictionaryInfo,
@@ -27,6 +28,7 @@ import {
2728
parseStyleRuleInfoList,
2829
parseStyleRuleInfo,
2930
parseCustomInstruction,
31+
parseTranslationMemoryInfoList,
3032
} from './parsing';
3133
import {
3234
appendCsvDictionaryEntries,
@@ -598,6 +600,38 @@ export class DeepLClient extends Translator {
598600
return parseStyleRuleInfoList(content);
599601
}
600602

603+
/**
604+
* Retrieves a list of available translation memories. The maximum number of translation
605+
* memories returned is controlled by pageSize (max 25).
606+
*
607+
* @param page: Page number for pagination, 0-indexed (optional).
608+
* @param pageSize: Number of items per page (optional).
609+
* @returns {Promise<TranslationMemoryInfo[]>} An array of objects containing details about each translation memory.
610+
*
611+
* @throws {DeepLError} If any error occurs while communicating with the DeepL API.
612+
*/
613+
async listTranslationMemories(
614+
page?: number,
615+
pageSize?: number,
616+
): Promise<TranslationMemoryInfo[]> {
617+
const queryParams = new URLSearchParams();
618+
if (page !== undefined) {
619+
queryParams.append('page', String(page));
620+
}
621+
if (pageSize !== undefined) {
622+
queryParams.append('page_size', String(pageSize));
623+
}
624+
625+
const { statusCode, content } = await this.httpClient.sendRequestWithBackoff<string>(
626+
'GET',
627+
'/v3/translation_memories',
628+
{ data: queryParams },
629+
);
630+
631+
await checkStatusCode(statusCode, content);
632+
return parseTranslationMemoryInfoList(content);
633+
}
634+
601635
/**
602636
* Creates a new style rule.
603637
*

src/parsing.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import {
2929
ListStyleRuleApiResponse,
3030
ConfiguredRules,
3131
CustomInstruction,
32+
TranslationMemoryInfo,
33+
TranslationMemoryInfoApiResponse,
34+
ListTranslationMemoryApiResponse,
3235
} from './types';
3336
import { standardizeLanguageCode } from './utils';
3437

@@ -611,3 +614,32 @@ export function parseStyleRuleInfoList(json: string): StyleRuleInfo[] {
611614
throw new DeepLError(`Error parsing response JSON: ${error}`);
612615
}
613616
}
617+
618+
/**
619+
* Parses the given translation memory API response to a TranslationMemoryInfo object.
620+
* @private
621+
*/
622+
export function parseTranslationMemoryInfo(
623+
tm: TranslationMemoryInfoApiResponse,
624+
): TranslationMemoryInfo {
625+
return {
626+
translationMemoryId: tm.translation_memory_id,
627+
name: tm.name,
628+
sourceLanguage: tm.source_language,
629+
targetLanguages: tm.target_languages,
630+
segmentCount: tm.segment_count,
631+
};
632+
}
633+
634+
/**
635+
* Parses the given JSON string to an array of TranslationMemoryInfo objects.
636+
* @private
637+
*/
638+
export function parseTranslationMemoryInfoList(json: string): TranslationMemoryInfo[] {
639+
try {
640+
const obj = JSON.parse(json) as ListTranslationMemoryApiResponse;
641+
return obj.translation_memories.map(parseTranslationMemoryInfo);
642+
} catch (error) {
643+
throw new DeepLError(`Error parsing response JSON: ${error}`);
644+
}
645+
}

src/types.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export type TagHandlingVersion = 'v1' | 'v2';
8888
export type ModelType = 'quality_optimized' | 'latency_optimized' | 'prefer_quality_optimized';
8989
export type GlossaryId = string;
9090
export type StyleId = string;
91+
export type TranslationMemoryId = string;
9192
export type TagList = string | string[];
9293

9394
/**
@@ -161,6 +162,14 @@ export interface TranslateTextOptions extends BaseRequestOptions {
161162
*/
162163
styleRule?: StyleId | StyleRuleInfo;
163164

165+
/** Specifies the ID of a translation memory to use with translation, or
166+
* a TranslationMemoryInfo object as returned by listTranslationMemories().
167+
*/
168+
translationMemory?: TranslationMemoryId | TranslationMemoryInfo;
169+
170+
/** Specifies the minimum similarity threshold (0 to 100) for translation memory matches. */
171+
translationMemoryThreshold?: number;
172+
164173
/** Type of tags to parse before translation, options are 'html' and 'xml'. */
165174
tagHandling?: TagHandlingMode;
166175

@@ -764,3 +773,34 @@ export interface StyleRuleInfoApiResponse {
764773
export interface ListStyleRuleApiResponse {
765774
style_rules: StyleRuleInfoApiResponse[];
766775
}
776+
777+
/**
778+
* Information about a translation memory.
779+
*/
780+
export interface TranslationMemoryInfo {
781+
readonly translationMemoryId: TranslationMemoryId;
782+
readonly name: string;
783+
readonly sourceLanguage: string;
784+
readonly targetLanguages: string[];
785+
readonly segmentCount: number;
786+
}
787+
788+
/**
789+
* Type used during JSON parsing of API response for translation memory info.
790+
* @private
791+
*/
792+
export interface TranslationMemoryInfoApiResponse {
793+
translation_memory_id: string;
794+
name: string;
795+
source_language: string;
796+
target_languages: string[];
797+
segment_count: number;
798+
}
799+
800+
/**
801+
* Type used during JSON parsing of API response for listing translation memories.
802+
* @private
803+
*/
804+
export interface ListTranslationMemoryApiResponse {
805+
translation_memories: TranslationMemoryInfoApiResponse[];
806+
}

src/utils.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,27 @@ export function validateAndAppendTextOptions(
288288
data.append('style_id', options.styleRule.styleId);
289289
}
290290
}
291+
if (options.translationMemory !== undefined) {
292+
if (!isString(options.translationMemory)) {
293+
if (options.translationMemory.translationMemoryId === undefined) {
294+
throw new DeepLError(
295+
'translationMemory option should be a TranslationMemoryId (string) or a TranslationMemoryInfo object.',
296+
);
297+
}
298+
data.append('translation_memory_id', options.translationMemory.translationMemoryId);
299+
} else {
300+
data.append('translation_memory_id', options.translationMemory);
301+
}
302+
}
303+
if (options.translationMemoryThreshold !== undefined) {
304+
if (options.translationMemory === undefined) {
305+
throw new DeepLError('translationMemoryThreshold requires translationMemory');
306+
}
307+
if (options.translationMemoryThreshold < 0 || options.translationMemoryThreshold > 100) {
308+
throw new DeepLError('translationMemoryThreshold must be a number between 0 and 100.');
309+
}
310+
data.append('translation_memory_threshold', options.translationMemoryThreshold.toString());
311+
}
291312
if (options.customInstructions !== undefined) {
292313
for (const instruction of options.customInstructions) {
293314
data.append('custom_instructions', instruction);

tests/translationMemories.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2025 DeepL SE (https://www.deepl.com)
2+
// Use of this source code is governed by an MIT
3+
// license that can be found in the LICENSE file.
4+
import { makeDeeplClient, withMockServer } from './core';
5+
6+
describe('Translation Memories Tests', () => {
7+
const DEFAULT_TM_ID = 'a74d88fb-ed2a-4943-a664-a4512398b994';
8+
9+
withMockServer('test listTranslationMemories', async () => {
10+
const deeplClient = makeDeeplClient();
11+
const translationMemories = await deeplClient.listTranslationMemories(0, 10);
12+
13+
expect(Array.isArray(translationMemories)).toBe(true);
14+
expect(translationMemories.length).toBeGreaterThan(0);
15+
expect(translationMemories[0].translationMemoryId).toBeDefined();
16+
expect(translationMemories[0].name).toBeDefined();
17+
expect(translationMemories[0].sourceLanguage).toBeDefined();
18+
expect(translationMemories[0].targetLanguages).toBeDefined();
19+
expect(translationMemories[0].segmentCount).toBeDefined();
20+
});
21+
22+
withMockServer('test translateText with translationMemory string ID', async () => {
23+
const deeplClient = makeDeeplClient();
24+
const exampleText = 'Hallo, Welt!';
25+
await deeplClient.translateText(exampleText, 'de', 'en-US', {
26+
translationMemory: DEFAULT_TM_ID,
27+
});
28+
});
29+
30+
withMockServer('test translateText with translationMemory and threshold', async () => {
31+
const deeplClient = makeDeeplClient();
32+
const exampleText = 'Hallo, Welt!';
33+
await deeplClient.translateText(exampleText, 'de', 'en-US', {
34+
translationMemory: DEFAULT_TM_ID,
35+
translationMemoryThreshold: 80,
36+
});
37+
});
38+
});

0 commit comments

Comments
 (0)