Skip to content

Commit a69e8dc

Browse files
authored
Merge pull request #121 from Smartling/DEVORTEX-5129-Extend-FTS-downloads-with-file-names
DEVORTEX-5129 Extend FTS download responses with file names
2 parents bf3e443 + 0a975ac commit a69e8dc

File tree

7 files changed

+199
-3
lines changed

7 files changed

+199
-3
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export enum ResponseBodyType {
22
TEXT = "TEXT",
33
ARRAY_BUFFER = "ARRAY_BUFFER",
4+
RAW_RESPONSE = "RAW_RESPONSE",
45
}

api/base/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ export class SmartlingBaseApi {
144144
return response.arrayBuffer();
145145
}
146146

147+
// Special case for download file with metadata - return raw response.
148+
if (returnRawResponseBody === ResponseBodyType.RAW_RESPONSE) {
149+
return response;
150+
}
151+
147152
try {
148153
const textResponse = await response.text();
149154
const jsonResponse = JSON.parse(textResponse, (key, value) => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface TranslatedFileDto {
2+
fileContent: ArrayBuffer;
3+
fileName?: string;
4+
contentType?: string;
5+
}
6+
7+
export { TranslatedFileDto };

api/file-translations/index.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TranslateFileParameters } from "./params/translate-file-parameters";
99
import { TranslationStatusDto } from "./dto/translation-status-dto";
1010
import { LanguageDetectionDto } from "./dto/language-detection-dto";
1111
import { LanguageDetectionStatusDto } from "./dto/language-detection-status-dto";
12+
import { TranslatedFileDto } from "./dto/translated-file-dto";
1213
import { ResponseBodyType } from "../base/enum/response-body-type";
1314

1415
export class SmartlingFileTranslationsApi extends SmartlingBaseApi {
@@ -76,6 +77,19 @@ export class SmartlingFileTranslationsApi extends SmartlingBaseApi {
7677
);
7778
}
7879

80+
async downloadTranslatedFileWithMetadata(
81+
accountUid: string, fileUid: string, mtUid: string, localeId: string
82+
): Promise<TranslatedFileDto> {
83+
return await SmartlingFileTranslationsApi.downloadResponseToTranslatedFileDto(
84+
await this.makeRequest(
85+
"get",
86+
`${this.entrypoint}/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/${localeId}/file`,
87+
null,
88+
ResponseBodyType.RAW_RESPONSE
89+
)
90+
);
91+
}
92+
7993
async downloadTranslatedFiles(
8094
accountUid: string, fileUid: string, mtUid: string
8195
): Promise<ArrayBuffer> {
@@ -87,6 +101,19 @@ export class SmartlingFileTranslationsApi extends SmartlingBaseApi {
87101
);
88102
}
89103

104+
async downloadTranslatedFilesWithMetadata(
105+
accountUid: string, fileUid: string, mtUid: string
106+
): Promise<TranslatedFileDto> {
107+
return await SmartlingFileTranslationsApi.downloadResponseToTranslatedFileDto(
108+
await this.makeRequest(
109+
"get",
110+
`${this.entrypoint}/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/all/file/zip`,
111+
null,
112+
ResponseBodyType.RAW_RESPONSE
113+
)
114+
);
115+
}
116+
90117
async cancelFileTranslation(
91118
accountUid: string, fileUid: string, mtUid: string
92119
): Promise<void> {
@@ -120,4 +147,23 @@ export class SmartlingFileTranslationsApi extends SmartlingBaseApi {
120147
delete headers["content-type"];
121148
return headers;
122149
}
150+
151+
private static async downloadResponseToTranslatedFileDto(
152+
response: Response
153+
): Promise<TranslatedFileDto> {
154+
const contentType = response.headers.get("content-type") ?? undefined;
155+
const contentDisposition = response.headers.get("content-disposition");
156+
let fileName;
157+
if (contentDisposition) {
158+
const fileNameMatch = contentDisposition.match(/filename="((?:[^"\\]|\\.)*)"/);
159+
if (fileNameMatch) {
160+
fileName = fileNameMatch[1].replace(/\\"/g, "\"");
161+
}
162+
}
163+
return {
164+
fileContent: await response.arrayBuffer(),
165+
fileName,
166+
contentType
167+
};
168+
}
123169
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "smartling-api-sdk-nodejs",
3-
"version": "2.16.1",
3+
"version": "2.17.0",
44
"description": "Package for Smartling API",
55
"main": "built/index.js",
66
"engines": {

test/file-translations.spec.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ describe("SmartlingFileTranslationsApi class tests.", () => {
2121
let fileTranslationsApiFetchStub;
2222
let fileTranslationsApiUaStub;
2323
let responseMockJsonStub;
24+
let getHeaderStub;
2425

2526
beforeEach(() => {
2627
fileTranslationsApi = new SmartlingFileTranslationsApi(
@@ -37,12 +38,15 @@ describe("SmartlingFileTranslationsApi class tests.", () => {
3738
responseMockJsonStub.returns({
3839
response: {}
3940
});
41+
getHeaderStub = sinon.stub(responseMock.headers, "get");
42+
getHeaderStub.returns(null);
4043
});
4144

4245
afterEach(() => {
4346
fileTranslationsApiFetchStub.restore();
4447
fileTranslationsApiUaStub.restore();
4548
responseMockJsonStub.restore();
49+
getHeaderStub.restore();
4650
});
4751

4852
describe("Methods", () => {
@@ -359,6 +363,89 @@ describe("SmartlingFileTranslationsApi class tests.", () => {
359363
);
360364
});
361365

366+
it("Download translated file with metadata when no headers", async () => {
367+
const localeId = "de-DE";
368+
369+
const fileWithMetadata = await fileTranslationsApi.downloadTranslatedFileWithMetadata(
370+
accountUid, fileUid, mtUid, localeId
371+
);
372+
373+
sinon.assert.calledOnceWithExactly(
374+
fileTranslationsApiFetchStub,
375+
`https://test.com/file-translations-api/v2/accounts/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/${localeId}/file`,
376+
{
377+
method: "get",
378+
headers: {
379+
Authorization: "test_token_type test_access_token",
380+
"Content-Type": "application/json",
381+
"User-Agent": userAgent
382+
}
383+
}
384+
);
385+
386+
assert.ok(fileWithMetadata.contentType === undefined);
387+
assert.ok(fileWithMetadata.fileName === undefined);
388+
assert.ok(fileWithMetadata.fileContent.byteLength === 1);
389+
});
390+
391+
it("Download translated file with metadata", async () => {
392+
const localeId = "de-DE";
393+
394+
getHeaderStub.onCall(0).returns("application/xml");
395+
getHeaderStub.onCall(1).returns("attachment; filename=\"test.xml\"");
396+
getHeaderStub.returns(null);
397+
398+
const fileWithMetadata = await fileTranslationsApi.downloadTranslatedFileWithMetadata(
399+
accountUid, fileUid, mtUid, localeId
400+
);
401+
402+
sinon.assert.calledOnceWithExactly(
403+
fileTranslationsApiFetchStub,
404+
`https://test.com/file-translations-api/v2/accounts/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/${localeId}/file`,
405+
{
406+
method: "get",
407+
headers: {
408+
Authorization: "test_token_type test_access_token",
409+
"Content-Type": "application/json",
410+
"User-Agent": userAgent
411+
}
412+
}
413+
);
414+
415+
assert.ok(fileWithMetadata.contentType === "application/xml");
416+
assert.ok(fileWithMetadata.fileName === "test.xml");
417+
assert.ok(fileWithMetadata.fileContent.byteLength === 1);
418+
});
419+
420+
it("Download translated file with escaped quotes in the file name", async () => {
421+
const localeId = "de-DE";
422+
423+
getHeaderStub.onCall(0).returns("application/xml");
424+
getHeaderStub.onCall(1).returns("attachment; filename=\"test - \\\"phase 1\\\".xml\"");
425+
getHeaderStub.returns(null);
426+
427+
const fileWithMetadata = await fileTranslationsApi.downloadTranslatedFileWithMetadata(
428+
accountUid, fileUid, mtUid, localeId
429+
);
430+
431+
sinon.assert.calledOnceWithExactly(
432+
fileTranslationsApiFetchStub,
433+
`https://test.com/file-translations-api/v2/accounts/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/${localeId}/file`,
434+
{
435+
method: "get",
436+
headers: {
437+
Authorization: "test_token_type test_access_token",
438+
"Content-Type": "application/json",
439+
"User-Agent": userAgent
440+
}
441+
}
442+
);
443+
444+
assert.ok(fileWithMetadata.contentType === "application/xml");
445+
assert.ok(fileWithMetadata.fileName === "test - \"phase 1\".xml");
446+
assert.ok(fileWithMetadata.fileContent.byteLength === 1);
447+
});
448+
362449
it("Download translated files", async () => {
363450
await fileTranslationsApi.downloadTranslatedFiles(accountUid, fileUid, mtUid);
364451

@@ -376,6 +463,56 @@ describe("SmartlingFileTranslationsApi class tests.", () => {
376463
);
377464
});
378465

466+
it("Download translated files with metadata when no headers", async () => {
467+
const fileWithMetadata = await fileTranslationsApi.downloadTranslatedFilesWithMetadata(
468+
accountUid, fileUid, mtUid
469+
);
470+
471+
sinon.assert.calledOnceWithExactly(
472+
fileTranslationsApiFetchStub,
473+
`https://test.com/file-translations-api/v2/accounts/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/all/file/zip`,
474+
{
475+
method: "get",
476+
headers: {
477+
Authorization: "test_token_type test_access_token",
478+
"Content-Type": "application/json",
479+
"User-Agent": userAgent
480+
}
481+
}
482+
);
483+
484+
assert.ok(fileWithMetadata.contentType === undefined);
485+
assert.ok(fileWithMetadata.fileName === undefined);
486+
assert.ok(fileWithMetadata.fileContent.byteLength === 1);
487+
});
488+
489+
it("Download translated files with metadata", async () => {
490+
getHeaderStub.onCall(0).returns("application/zip");
491+
getHeaderStub.onCall(1).returns("attachment; filename=\"test.zip\"");
492+
getHeaderStub.returns(null);
493+
494+
const fileWithMetadata = await fileTranslationsApi.downloadTranslatedFilesWithMetadata(
495+
accountUid, fileUid, mtUid
496+
);
497+
498+
sinon.assert.calledOnceWithExactly(
499+
fileTranslationsApiFetchStub,
500+
`https://test.com/file-translations-api/v2/accounts/${accountUid}/files/${fileUid}/mt/${mtUid}/locales/all/file/zip`,
501+
{
502+
method: "get",
503+
headers: {
504+
Authorization: "test_token_type test_access_token",
505+
"Content-Type": "application/json",
506+
"User-Agent": userAgent
507+
}
508+
}
509+
);
510+
511+
assert.ok(fileWithMetadata.contentType === "application/zip");
512+
assert.ok(fileWithMetadata.fileName === "test.zip");
513+
assert.ok(fileWithMetadata.fileContent.byteLength === 1);
514+
});
515+
379516
it("Cancel file translation", async () => {
380517
await fileTranslationsApi.cancelFileTranslation(accountUid, fileUid, mtUid);
381518

0 commit comments

Comments
 (0)