-
Notifications
You must be signed in to change notification settings - Fork 6
/
smartling.js
456 lines (396 loc) · 15 KB
/
smartling.js
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
/**
* smartling-sdk
* https://github.com/hightail/smartling-sdk
*
* Javascript SDK for Smartling. All functions are promise based using 'q' npm package
*
* Copyright (c) 2014 Hightail
* Author: Justin Fiedler
*
* Date: 1/15/14
*/
var fs = require('fs'),
path = require('path'),
querystring = require('querystring'),
mkdirp = require('mkdirp'),
request = require('request'),
Q = require('q'),
_ = require('lodash');
/**
* Returns a search (GET var) string based on the @jsonObject
* Handles nested objects using dot notation
*
* ex:
* {
* myParam: 'something',
* myOtherParam: {
* somethingElse: someValue
* }
* }
*
* returns:
* myParam=something&myOtherParam.somethingElse=someValue
*
*
* @param jsonObject
* @returns {string}
*/
var jsonToSearchParameterString = function(jsonObject) {
var getParams = [];
function _jsonToSearchParameterString(_jsonObject, prefix) {
//loop over all keys in the object
_.each(_jsonObject, function(value, key) {
if (_.isObject(value)) {
//if the value is an object recurse
_jsonToSearchParameterString(value, key + '.');
} else {
//if the value is not an object then add it to the GET params
getParams.push(prefix + key + '=' + encodeURIComponent(value));
}
});
}
_jsonToSearchParameterString(jsonObject, '');
return getParams.join('&');
};
function handleSmartlingResponse(response, deferred) {
var smartlingResponse = response.response;
//console.log('smartlingResponse', smartlingResponse);
if (smartlingResponse && smartlingResponse.code && smartlingResponse.code === 'SUCCESS') {
deferred.resolve(smartlingResponse.data);
} else {
deferred.reject(response);
}
}
function getStandardSmartlingRequestHandler(deferred) {
return function (error, response, body) {
if (!error && response.statusCode == 200) {
var data = body;
if (_.isString(data)) {
try {
data = JSON.parse(body);
} catch (err) {};
}
handleSmartlingResponse(data, deferred);
} else {
var errorObject = {
message: "An unknown error occurred"
};
if (body && body.response) {
errorObject = body.response;
} else if (error) {
errorObject = error;
}
deferred.reject(errorObject);
}
};
}
/**
* Initializes Smartling with the given params
*
* @param baseUrl
* @param apiKey
* @param projectId
*/
var SmartlingSdk = function (apiBaseUrl, apiKey, projectId) {
this.config = {
apiBaseUrl: apiBaseUrl,
apiKey: apiKey,
projectId: projectId
};
};
/**
* Smartling API Base URL constants
*/
SmartlingSdk.API_BASE_URLS = {
LIVE: 'https://api.smartling.com/v1',
SANDBOX: 'https://sandbox-api.smartling.com/v1'
};
/**
* Hash of available Smartling operations
*/
SmartlingSdk.OPERATIONS = {
UPLOAD: '/file/upload',
GET: '/file/get',
LIST: '/file/list',
STATUS: '/file/status',
RENAME: '/file/rename',
DELETE: '/file/delete'
};
/**
* Returns a URL for a Smartling API Operation
*
* @param operation A SmartlingSdk.OPERATIONS value
* @param smartlingParams JSON object containing any Smartling parameters
* @returns {String}
*/
SmartlingSdk.prototype.getSmartlingRequestPath = function(operation, smartlingParams) {
// The API key and projectId are always required so
// provide default settings here
var params = {
apiKey: this.config.apiKey,
projectId: this.config.projectId
};
_.extend(params, smartlingParams);
//assemble the request URL
var requestUrl = this.config.apiBaseUrl + operation;
requestUrl += '?' + jsonToSearchParameterString(params);
//console.log('requestUrl', requestUrl);
return requestUrl;
};
/**
* Uploads original source content to Smartling (20MB limit for docx and pptx, 10MB limit for all others).
*
* https://docs.smartling.com/display/docs/Files+API#FilesAPI-/file/upload(POST)
*
* @param file (required) The file path or file contents to upload.
* @param fileUri (required) Value that uniquely identifies the uploaded file. This ID can be used to request the file back.
* We recommend you use file path + file name, similar to how version control systems identify the file.
* Example: /myproject/i18n/ui.properties.
* @param fileType (required)
* Identifiers: android, ios, gettext, html, javaProperties, yaml, xliff, xml, json, docx, pptx, xlsx, idml
* @param options (optional)
* @param options.approved (optional)
* This value, either true or false (default), determines whether content in the file is 'approved' (available for translation)
* upon submitting the file via the Smartling Dashboard. An error message will return if there are insufficient translation
* funds and approved is set to true.
* Note: Setting this parameter to true both approves all new content and overrides any locale-specific or global exclusions.
* If your workflow includes content exclusions, use this parameter with caution.
* @param options.smartling.[command] (optional) Provides custom parser configuration for supported file types. See Supported File Types for more details.
* @param options.callbackUrl (optional) A GET request that creates a callback to a URL when a file is 100% published for a locale.
* The callback includes these parameters:
* fileUri
* locale
* If you upload another file without a callback URL, it will remove any previous callbackUrl for that file.
*
* @return {promise}
*/
SmartlingSdk.prototype.upload = function (filePath, fileUri, fileType, options) {
//console.log('upload:filePath', filePath);
//create a defered object to return
var deferred = Q.defer();
//setup default request params
var smartlingParams = {
fileUri: fileUri,
fileType: fileType,
approved: false
};
//extend the request params with any options passed in by user
_.extend(smartlingParams, options);
//assemble the request URL
var requestUrl = this.getSmartlingRequestPath(SmartlingSdk.OPERATIONS.UPLOAD, smartlingParams);
fs.stat(filePath, function (err, stat) {
if (err) {
//failed to get file stats
deferred.reject(err);
} else {
var req = request.post({
url: requestUrl
}, getStandardSmartlingRequestHandler(deferred));
var form = req.form();
form.append('file', fs.createReadStream(filePath));
}
});
//return the promise
return deferred.promise;
};
/**
* Downloads the requested file (@fileUri) from Smartling.
*
* https://docs.smartling.com/display/docs/Files+API#FilesAPI-/file/get(GET)
*
* @param fileUri (required) Value that uniquely identifies the downloaded file.
*
* @param options
* @param options.locale (optional) A locale identifier as specified in project setup. If no locale is specified, original content is returned. You can find the list of locales for your project on the Smartling dashboard at https://dashboard.smartling.com/settings/api.
* @param options.retrievalType (optional)
* Allowed values: pending, published, pseudo
*
* pending indicates that Smartling returns any translations (including non-published translations)
* published indicates that Smartling returns only published/pre-published translations
* pseudo indicates that Smartling returns a modified version of the original text with certain characters transformed and the text expanded. For example, the uploaded string "This is a sample string", will return as "T~hís ~ís á s~ámpl~é str~íñg". Pseudo translations enable you to test how a longer string integrates into your application.
* If you do not specify a value, Smartling assumes published.
* @param options.IncludeOriginalStrings (optional) Allowed values: true, false For gettext, xml, or json files only.
*
* @return {promise}
*/
SmartlingSdk.prototype.get = function (fileUri, filepath, options) {
//create a defered object to return
var defered = Q.defer();
//setup default request params
var smartlingParams = {
fileUri: fileUri
};
//extend the request params with any options passed in by user
_.extend(smartlingParams, options);
//assemble the request URL
var requestUrl = this.getSmartlingRequestPath(SmartlingSdk.OPERATIONS.GET, smartlingParams);
var requestParams = {
url: requestUrl,
json: true
};
//Make the request
if (filepath) {
mkdirp(path.dirname(filepath), function(err) {
if (err) {
console.log(err);
defered.reject(err);
} else {
//create a new writestream for the file
var fileStream = fs.createWriteStream(filepath);
//handle any error writing to the stream
fileStream.on('error', function(err) {
defered.reject(error);
});
//create a request and pipe the response to a file
var req = request.get(requestParams).pipe(fileStream);
req.on('close', function(error) {
if (error) {
defered.reject(error);
} else {
defered.resolve();
}
});
}
});
} else {
request.get(requestParams, function (error, response, body) {
if (!error && response.statusCode == 200) {
defered.resolve(body);
} else {
defered.reject(body);
}
});
}
//return the promise
return defered.promise;
};
/**
* Lists recently uploaded files. Returns a maximum of 500 files.
*
* https://docs.smartling.com/display/docs/Files+API#FilesAPI-/file/list(GET)
*
* @param options
* @param options.locale (optional) If not specified, the Smartling Files API will return a listing of the original files matching the specified criteria. When the locale is not specified, completedStringCount will be "0".
* @param options.uriMask (optional) SQL like syntax (ex '%.strings').
* @param options.fileTypes (optional) Identifiers: android, ios, gettext, javaProperties, xliff, yaml. File types are combined using the logical ‘OR’.
* @param options.lastUploadedAfter (optional)
* Return all files uploaded after the specified date. See Date Format for more details.
* lastUploadedBefore (optional) Return all files uploaded before the specified date. See Date Format for more details.
* offset (optional) For result set returns, the offset is a number indicating the distance from the beginning of the list; for example, for a result set of "50" files, you can set the offset at 10 to return files 10 - 50.
* limit (optional) For result set returns, limits the number of files returned; for example, for a result set of 50 files, a limit of "10" would return files 0 - 10.
* conditions (optional) An array of the following conditions: haveAtLeastOneUnapproved, haveAtLeastOneApproved, haveAtLeastOneTranslated, haveAllTranslated, haveAllApproved, haveAllUnapproved. Conditions are combined using the logical ‘OR’.
* @param options.orderBy (optional)
* Choices: names of any return parameters; for example, fileUri, stringCount, wordCount, approvedStringCount, completedStringCount, lastUploaded and fileType. You can specify ascending or descending with each parameter by adding "_asc" or "_desc"; for example, "fileUri_desc". If you do not specify ascending or descending, the default is ascending.
*
* @returns {promise}
*/
SmartlingSdk.prototype.list = function (options) {
//create a defered object to return
var deferred = Q.defer();
//assemble the request URL
var requestUrl = this.getSmartlingRequestPath(SmartlingSdk.OPERATIONS.LIST, options);
var requestParams = {
url: requestUrl,
json: true
};
//Make the request
request.get(requestParams, getStandardSmartlingRequestHandler(deferred));
//return the promise
return deferred.promise;
};
/**
* Gets status of translations for @fileUri in @locale
*
* https://docs.smartling.com/display/docs/Files+API#FilesAPI-/file/status(GET)
*
* @param fileUri (required) Value that uniquely identifies the file.
* @param locale (required) A locale identifier as specified in project setup.
* You can find the list of locales for your project on the Smartling
* dashboard at https://dashboard.smartling.com/settings/api.
*
* @returns {promise}
*/
SmartlingSdk.prototype.status = function (fileUri, locale) {
//create a defered object to return
var deferred = Q.defer();
//setup default request params
var smartlingParams = {
fileUri: fileUri,
locale: locale
};
//assemble the request URL
var requestUrl = this.getSmartlingRequestPath(SmartlingSdk.OPERATIONS.STATUS, smartlingParams);
var requestParams = {
url: requestUrl,
json: true
};
//Make the request
request.get(requestParams, getStandardSmartlingRequestHandler(deferred));
//return the promise
return deferred.promise;
};
/**
* Renames an uploaded file @fileUri to @newFileUri. After renaming the file, the file will only be identified by the @newFileUri you provide.
*
* https://docs.smartling.com/display/docs/Files+API#FilesAPI-/file/rename(POST)
*
* @param fileUri (required) Value that uniquely identifies the file to rename.
* @param newFileUri (required) Value that uniquely identifies the new file. We recommend
* that you use file path + file name, similar to how version control systems identify
* the file. Example: /myproject/i18n/ui.properties.
* This must be a fileUri that does not exist in the Smartling database.
*
* @returns {promise}
*/
SmartlingSdk.prototype.rename = function (fileUri, newFileUri) {
//create a defered object to return
var deferred = Q.defer();
//setup default request params
var smartlingParams = {
fileUri: fileUri,
newFileUri: newFileUri
};
//assemble the request URL
var requestUrl = this.getSmartlingRequestPath(SmartlingSdk.OPERATIONS.RENAME, smartlingParams);
var requestParams = {
url: requestUrl,
body: smartlingParams,
json: true
};
//Make the request
request.post(requestParams, getStandardSmartlingRequestHandler(deferred));
//return the promise
return deferred.promise;
};
/**
* Removes the file from Smartling. The file will no longer be available for download.
* Any complete translations for the file remain available for use within the system.
*
* Note: Smartling deletes files asynchronously and it typically takes a few minutes to complete.
* While deleting a file, you can not upload a file with the same fileUri.
*
* @param fileUri (required) Value that uniquely identifies the file.
* @returns {promise}
*/
SmartlingSdk.prototype.delete = function (fileUri) {
//console.log('_delete:', fileUri);
//create a defered object to return
var deferred = Q.defer();
//setup default request params
var smartlingParams = {
fileUri: fileUri
};
//assemble the request URL
var requestUrl = this.getSmartlingRequestPath(SmartlingSdk.OPERATIONS.DELETE, smartlingParams);
var requestParams = {
url: requestUrl,
body: smartlingParams,
json: true
};
//Make the request
request.del(requestParams, getStandardSmartlingRequestHandler(deferred));
//return the promise
return deferred.promise;
};
//Export the SmartlingSdk Class
module.exports = SmartlingSdk;