/
azure-translator.ts
117 lines (97 loc) · 3.13 KB
/
azure-translator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import fetch from 'node-fetch';
import { chunk, flatten } from 'lodash';
import { TranslationService, TranslationResult, TString } from '.';
import {
Matcher,
reInsertInterpolations,
replaceInterpolations,
} from '../matchers';
interface TranslationResponse {
translations: [
{
text: string;
to: string;
},
];
}
interface SupportedLanguagesResponse {
translation: {
[code: string]: {
name: string;
nativeName: string;
direction: 'ltr' | 'rtl';
};
};
}
const LANGUAGE_ENDPOINT =
'https://api.cognitive.microsofttranslator.com/languages?api-version=3.0';
const TRANSLATE_ENDPOINT =
'https://api.cognitive.microsofttranslator.com/translate?api-version=3.0';
export class AzureTranslator implements TranslationService {
public name = 'Azure';
private apiKey: string;
private interpolationMatcher: Matcher;
private supportedLanguages: Set<string>;
async initialize(apiKey?: string, interpolationMatcher?: Matcher) {
if (!apiKey) throw new Error(`Please provide an API key for Azure.`);
this.apiKey = apiKey;
this.interpolationMatcher = interpolationMatcher;
this.supportedLanguages = await this.getAvailableLanguages();
}
async getAvailableLanguages() {
const response = await fetch(LANGUAGE_ENDPOINT);
const supported = (await response.json()) as SupportedLanguagesResponse;
const keys = Object.keys(supported.translation).map((k) => k.toLowerCase());
// Some language codes can be simplified by using only the part before the dash
const simplified = keys
.filter((k) => k.includes('-'))
.map((l) => l.split('-')[0]);
return new Set(keys.concat(simplified));
}
supportsLanguage(language: string) {
return this.supportedLanguages.has(language.toLowerCase());
}
async translateBatch(batch: TString[], from: string, to: string) {
const toTranslate = batch.map(({ key, value }) => {
const { clean, replacements } = replaceInterpolations(
value,
this.interpolationMatcher,
);
return { key, value, clean, replacements };
});
const response = await fetch(
`${TRANSLATE_ENDPOINT}&from=${from}&to=${to}&textType=html`,
{
method: 'POST',
headers: {
'Ocp-Apim-Subscription-Key': this.apiKey,
'Content-Type': 'application/json; charset=UTF-8',
},
body: JSON.stringify(toTranslate.map((c) => ({ Text: c.clean }))),
},
);
if (!response.ok) {
throw new Error('Azure Translation failed: ' + (await response.text()));
}
const data = (await response.json()) as TranslationResponse[];
return data.map((res, i) => ({
key: toTranslate[i].key,
value: toTranslate[i].value,
translated: reInsertInterpolations(
res.translations[0].text,
toTranslate[i].replacements,
),
}));
}
async translateStrings(
strings: TString[],
from: string,
to: string,
): Promise<TranslationResult[]> {
const batches = chunk(strings, 50);
const results = await Promise.all(
batches.map((batch) => this.translateBatch(batch, from, to)),
);
return flatten(results);
}
}