Skip to content

Commit 989ed18

Browse files
leoncheng57JanEbbing
authored andcommitted
feat: Add support for v3 glossary endpoints
1 parent e789a05 commit 989ed18

11 files changed

Lines changed: 1833 additions & 71 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
<!-- * add to here... -->
1010
* Include `x-trace-id` response headers in debug logs
11+
* Added support for the /v3 Glossary APIs in the client library while providing backwards
12+
compatibility for the previous /v2 Glossary endpoints. Please refer to the README for
13+
usage instructions.
1114
### Changed
1215
<!-- * add to here... -->
1316

README.md

Lines changed: 148 additions & 50 deletions
Large diffs are not rendered by default.

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ProxyConfig } from './types';
1313
import * as https from 'https';
1414
import * as http from 'http';
1515

16-
type HttpMethod = 'GET' | 'DELETE' | 'POST';
16+
type HttpMethod = 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH';
1717

1818
const axiosInstance = axios.create({
1919
httpAgent: new http.Agent({ keepAlive: true }),

src/deeplClient.ts

Lines changed: 478 additions & 3 deletions
Large diffs are not rendered by default.

src/errors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,5 @@ export class DocumentMinificationError extends DeepLError {}
5353
* @see DocumentMinifier.deminifyDocument
5454
*/
5555
export class DocumentDeminificationError extends DeepLError {}
56+
57+
export class ArgumentError extends DeepLError {}

src/parsing.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by an MIT
33
// license that can be found in the LICENSE file.
44

5+
import { GlossaryEntries } from './glossaryEntries';
56
import { DeepLError } from './errors';
67
import {
78
DocumentHandle,
@@ -17,6 +18,12 @@ import {
1718
GlossaryInfo,
1819
SourceLanguageCode,
1920
WriteResult,
21+
MultilingualGlossaryInfo,
22+
MultilingualGlossaryDictionaryInfo,
23+
MultilingualGlossaryDictionaryEntries,
24+
MultilingualGlossaryDictionaryApiResponse,
25+
MultilingualGlossaryDictionaryEntriesApiResponse,
26+
ListMultilingualGlossaryApiResponse,
2027
} from './types';
2128
import { standardizeLanguageCode } from './utils';
2229

@@ -34,6 +41,17 @@ interface GlossaryInfoApiResponse {
3441
entry_count: number;
3542
}
3643

44+
/**
45+
* Type used during JSON parsing of API response for multilingual glossary info.
46+
* @private
47+
*/
48+
interface MultilingualGlossaryInfoApiResponse {
49+
glossary_id: string;
50+
name: string;
51+
dictionaries: MultilingualGlossaryDictionaryApiResponse[];
52+
creation_time: string;
53+
}
54+
3755
/**
3856
* Type used during JSON parsing of API response for lists of glossary infos.
3957
* @private
@@ -119,6 +137,17 @@ interface GlossaryLanguagePairArrayApiResponse {
119137
supported_languages: GlossaryInfoApiResponse[];
120138
}
121139

140+
/**
141+
* Type used during JSON parsing of API response for multilingual glossary info.
142+
* @private
143+
*/
144+
interface MultilingualGlossaryInfoApiResponse {
145+
glossary_id: string;
146+
name: string;
147+
dictionaries: MultilingualGlossaryDictionaryApiResponse[];
148+
creation_time: string;
149+
}
150+
122151
/**
123152
* Type used during JSON parsing of API response for document translation statuses.
124153
* @private
@@ -242,6 +271,66 @@ function parseRawGlossaryInfo(obj: GlossaryInfoApiResponse): GlossaryInfo {
242271
};
243272
}
244273

274+
/**
275+
* Parses the given multilingual glossary info API response to a GlossaryInfo object.
276+
* @private
277+
*/
278+
export function parseMultilingualGlossaryDictionaryInfo(
279+
obj: MultilingualGlossaryDictionaryApiResponse,
280+
): MultilingualGlossaryDictionaryInfo {
281+
return {
282+
sourceLangCode: obj.source_lang as SourceGlossaryLanguageCode,
283+
targetLangCode: obj.target_lang as TargetGlossaryLanguageCode,
284+
entryCount: obj.entry_count,
285+
};
286+
}
287+
288+
/**
289+
* Parses the given multilingual glossary info API response to a GlossaryInfo object.
290+
* @private
291+
*/
292+
function parseRawMultilingualGlossaryInfo(
293+
obj: MultilingualGlossaryInfoApiResponse,
294+
): MultilingualGlossaryInfo {
295+
return {
296+
glossaryId: obj.glossary_id,
297+
name: obj.name,
298+
creationTime: new Date(obj.creation_time),
299+
dictionaries: obj.dictionaries.map((dict) => parseMultilingualGlossaryDictionaryInfo(dict)),
300+
};
301+
}
302+
303+
/**
304+
* Parses the given multilingual glossary entries API response to a GlossaryDictionaryEntries object.
305+
* @private
306+
*/
307+
export function parseMultilingualGlossaryDictionaryEntries(
308+
obj: MultilingualGlossaryDictionaryEntriesApiResponse,
309+
): MultilingualGlossaryDictionaryEntries[] {
310+
return obj.dictionaries.map((dict) => ({
311+
sourceLangCode: dict.source_lang as SourceGlossaryLanguageCode,
312+
targetLangCode: dict.target_lang as TargetGlossaryLanguageCode,
313+
entries: new GlossaryEntries({ tsv: dict.entries }),
314+
}));
315+
}
316+
317+
/**
318+
* Parses the given list multilingual glossaries API response.
319+
* @private
320+
*/
321+
export function parseListMultilingualGlossaries(
322+
obj: ListMultilingualGlossaryApiResponse,
323+
): MultilingualGlossaryInfo[] {
324+
return obj.glossaries.map((glossary) => ({
325+
glossaryId: glossary.glossary_id,
326+
name: glossary.name,
327+
dictionaries: glossary.dictionaries.map((dict) =>
328+
parseMultilingualGlossaryDictionaryInfo(dict),
329+
),
330+
creationTime: new Date(glossary.creation_time),
331+
}));
332+
}
333+
245334
/**
246335
* Parses the given JSON string to a GlossaryInfo object.
247336
* @private
@@ -255,6 +344,19 @@ export function parseGlossaryInfo(json: string): GlossaryInfo {
255344
}
256345
}
257346

347+
/**
348+
* Parses the given JSON string to a MultilingualGlossaryInfo object.
349+
* @private
350+
*/
351+
export function parseMultilingualGlossaryInfo(json: string): MultilingualGlossaryInfo {
352+
try {
353+
const obj = JSON.parse(json) as MultilingualGlossaryInfoApiResponse;
354+
return parseRawMultilingualGlossaryInfo(obj);
355+
} catch (error) {
356+
throw new DeepLError(`Error parsing response JSON: ${error}`);
357+
}
358+
}
359+
258360
/**
259361
* Parses the given JSON string to an array of GlossaryInfo objects.
260362
* @private

src/types.ts

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by an MIT
33
// license that can be found in the LICENSE file.
44

5+
import { GlossaryEntries } from './glossaryEntries';
6+
57
/**
68
* Optional proxy configuration, may be specified as proxy in TranslatorOptions.
79
* @see TranslatorOptions.proxy
@@ -87,7 +89,9 @@ export type GlossaryId = string;
8789
export type TagList = string | string[];
8890

8991
/**
90-
* Information about a glossary, excluding the entry list.
92+
* Information about a glossary, excluding the entry list. {@link GlossaryInfo} is compatible with the
93+
* /v2 glossary endpoints and can only support mono-lingual glossaries (e.g. a glossary with only one source and
94+
* target language defined).
9195
*/
9296
export interface GlossaryInfo {
9397
/** Unique ID assigned to the glossary. */
@@ -128,8 +132,10 @@ export interface TranslateTextOptions {
128132
/** Controls whether translations should lean toward formal or informal language. */
129133
formality?: Formality;
130134

131-
/** Specifies the ID of a glossary to use with translation. */
132-
glossary?: GlossaryId | GlossaryInfo;
135+
/** Specifies the ID of a glossary to use with translation. Or
136+
* using the given v2 glossary or given multilingual glossary.
137+
*/
138+
glossary?: GlossaryId | GlossaryInfo | MultilingualGlossaryInfo;
133139

134140
/** Type of tags to parse before translation, options are 'html' and 'xml'. */
135141
tagHandling?: TagHandlingMode;
@@ -169,8 +175,10 @@ export interface DocumentTranslateOptions {
169175
/** Controls whether translations should lean toward formal or informal language. */
170176
formality?: Formality;
171177

172-
/** Specifies the ID of a glossary to use with translation. */
173-
glossary?: GlossaryId | GlossaryInfo;
178+
/** Specifies the ID of a glossary to use with translation. Or
179+
* using the given v2 glossary or given multilingual glossary.
180+
*/
181+
glossary?: GlossaryId | GlossaryInfo | MultilingualGlossaryInfo;
174182

175183
/** Filename including extension, only required when translating documents as streams. */
176184
filename?: string;
@@ -341,6 +349,47 @@ export interface Language {
341349
readonly supportsFormality?: boolean;
342350
}
343351

352+
/**
353+
* Information about a glossary dictionary, excluding the entry list. Is compatible with
354+
* the /v3 glossary endpoints and supports multi-lingual glossaries compared to the v2 version.
355+
* Glossaries now have multiple glossary dictionaries each with their own source language, target
356+
* language and entries.
357+
*/
358+
export interface MultilingualGlossaryDictionaryInfo {
359+
/** Language code of the glossary dictionary source terms. */
360+
readonly sourceLangCode: string;
361+
/** Language code of the glossary dictionary target terms. */
362+
readonly targetLangCode: string;
363+
/** The number of entries contained in the glossary dictionary. */
364+
readonly entryCount: number;
365+
}
366+
367+
/**
368+
* Information about a multilingual glossary, excluding the entry list.
369+
*/
370+
export interface MultilingualGlossaryInfo {
371+
/** ID of the associated glossary. */
372+
readonly glossaryId: string;
373+
/** Name of the glossary chosen during creation. */
374+
readonly name: string;
375+
/** The dictionaries of the glossary. */
376+
readonly dictionaries: MultilingualGlossaryDictionaryInfo[];
377+
/** Time when the glossary was created. */
378+
readonly creationTime: Date;
379+
}
380+
381+
/**
382+
* Information about a glossary dictionary, including the entry list
383+
*/
384+
export interface MultilingualGlossaryDictionaryEntries {
385+
/** Language code of the glossary dictionary source terms. */
386+
readonly sourceLangCode: string;
387+
/** Language code of the glossary dictionary target terms. */
388+
readonly targetLangCode: string;
389+
/** The entries in the glossary dictionary. */
390+
readonly entries: GlossaryEntries;
391+
}
392+
344393
/**
345394
* Information about a pair of languages supported for DeepL glossaries.
346395
*/
@@ -455,3 +504,42 @@ export interface WriteResult {
455504
*/
456505
readonly targetLang: string;
457506
}
507+
508+
/**
509+
* Type used during JSON parsing of API response for getting multilingual glossary dictionary entries.
510+
* @private
511+
*/
512+
export interface MultilingualGlossaryDictionaryEntriesApiResponse {
513+
dictionaries: [
514+
{
515+
source_lang: string;
516+
target_lang: string;
517+
entries: string;
518+
},
519+
];
520+
}
521+
522+
/**
523+
* Type used during JSON parsing of API response for multilingual glossary dictionaries.
524+
* @private
525+
*/
526+
export interface MultilingualGlossaryDictionaryApiResponse {
527+
source_lang: string;
528+
target_lang: string;
529+
entry_count: number;
530+
}
531+
532+
/**
533+
* Type used during JSON parsing of API response for listing multilingual glossaries.
534+
* @private
535+
*/
536+
export interface ListMultilingualGlossaryApiResponse {
537+
glossaries: [
538+
{
539+
glossary_id: string;
540+
name: string;
541+
dictionaries: MultilingualGlossaryDictionaryApiResponse[];
542+
creation_time: string;
543+
},
544+
];
545+
}

src/utils.ts

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
SentenceSplittingMode,
1515
TagList,
1616
TranslateTextOptions,
17+
MultilingualGlossaryInfo,
18+
MultilingualGlossaryDictionaryEntries,
1719
} from './types';
1820

1921
const logger = loglevel.getLogger('deepl');
@@ -138,7 +140,7 @@ export function buildURLSearchParams(
138140
sourceLang: LanguageCode | null,
139141
targetLang: LanguageCode,
140142
formality: Formality | undefined,
141-
glossary: GlossaryId | GlossaryInfo | undefined,
143+
glossary: GlossaryId | GlossaryInfo | MultilingualGlossaryInfo | undefined,
142144
extraRequestParameters: RequestParameters | undefined,
143145
): URLSearchParams {
144146
targetLang = standardizeLanguageCode(targetLang);
@@ -150,15 +152,6 @@ export function buildURLSearchParams(
150152
throw new DeepLError('sourceLang is required if using a glossary');
151153
}
152154

153-
if (glossary !== undefined && !isString(glossary)) {
154-
if (
155-
nonRegionalLanguageCode(targetLang) !== glossary.targetLang ||
156-
sourceLang !== glossary.sourceLang
157-
) {
158-
throw new DeepLError('sourceLang and targetLang must match glossary');
159-
}
160-
}
161-
162155
if (targetLang === 'en') {
163156
throw new DeepLError(
164157
"targetLang='en' is deprecated, please use 'en-GB' or 'en-US' instead.",
@@ -281,3 +274,49 @@ export function validateAndAppendTextOptions(
281274
data.append('ignore_tags', joinTagList(options.ignoreTags));
282275
}
283276
}
277+
278+
/**
279+
* Appends glossary dictionaries to HTTP request parameters.
280+
* @param data URL-encoded parameters for a HTTP request.
281+
* @param dictionaries Glossary dictionaries to append.
282+
* @private
283+
*/
284+
export function appendDictionaryEntries(
285+
data: URLSearchParams,
286+
dictionaries: MultilingualGlossaryDictionaryEntries[],
287+
): void {
288+
dictionaries.forEach((dict, index) => {
289+
data.append(`dictionaries[${index}].source_lang`, dict.sourceLangCode);
290+
data.append(`dictionaries[${index}].target_lang`, dict.targetLangCode);
291+
data.append(`dictionaries[${index}].entries`, dict.entries.toTsv());
292+
data.append(`dictionaries[${index}].entries_format`, 'tsv');
293+
});
294+
}
295+
/**
296+
* Appends a glossary dictionary with CSV entries to HTTP request parameters.
297+
* @param data URL-encoded parameters for a HTTP request.
298+
* @param sourceLanguageCode Source language code of the dictionary.
299+
* @param targetLanguageCode Target language code of the dictionary.
300+
* @param csvContent CSV-formatted string containing the dictionary entries.
301+
* @private
302+
*/
303+
export function appendCsvDictionaryEntries(
304+
data: URLSearchParams,
305+
sourceLanguageCode: string,
306+
targetLanguageCode: string,
307+
csvContent: string,
308+
): void {
309+
data.append('dictionaries[0].source_lang', sourceLanguageCode);
310+
data.append('dictionaries[0].target_lang', targetLanguageCode);
311+
data.append('dictionaries[0].entries', csvContent);
312+
data.append('dictionaries[0].entries_format', 'csv');
313+
}
314+
315+
/**
316+
* Extract the glossary ID from the argument.
317+
* @param glossary The glossary as a string, GlossaryInfo, or MultilingualGlossaryInfo.
318+
* @private
319+
*/
320+
export function extractGlossaryId(glossary: GlossaryId | GlossaryInfo | MultilingualGlossaryInfo) {
321+
return typeof glossary === 'string' ? glossary : glossary.glossaryId;
322+
}

0 commit comments

Comments
 (0)