locize-cli 7.10.1 → 7.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  Project versioning adheres to [Semantic Versioning](http://semver.org/).
6
6
  Change log format is based on [Keep a Changelog](http://keepachangelog.com/).
7
7
 
8
+ ## [7.12.1](https://github.com/locize/locize-cli/compare/v7.12.0...v7.12.1) - 2022-07-12
9
+
10
+ - limit parallel downloads
11
+
12
+ ## [7.12.0](https://github.com/locize/locize-cli/compare/v7.11.0...v7.12.0) - 2022-06-16
13
+
14
+ - sync and download: introduce --unpublished flag
15
+
16
+ ## [7.11.0](https://github.com/locize/locize-cli/compare/v7.10.1...v7.11.0) - 2022-06-13
17
+
18
+ - alpine build
19
+ - update dependencies
20
+
8
21
  ## [7.10.1](https://github.com/locize/locize-cli/compare/v7.10.0...v7.10.1) - 2022-04-07
9
22
 
10
23
  - cache dns lookups
package/README.md CHANGED
@@ -91,7 +91,7 @@ locize download --project-id my-project-id-93e1-442a-ab35-24331fa294ba --ver lat
91
91
  By using the sync command, you can keep your existing code setup and synchronize the translations with locize.
92
92
  An example on how this could look like can be seen in [this tutorial](https://github.com/locize/react-tutorial#step-1---keep-existing-code-setup-but-synchronize-with-locize).
93
93
 
94
- **⚠️ Since the remote source are the published translations, make sure the desired version is set to standard/auto publish mode, or alternatively [publish](#publish-version) that version before you execute the cli command. ⚠️**
94
+ **⚠️ Since the remote source are the published translations, make sure the desired version is set to auto publish mode. Alternatively use the `--unpublished true` argument (this will generate [private downloads costs](https://docs.locize.com/integration/api#fetch-filter-the-unpublished-namespace-resources)). ⚠️**
95
95
 
96
96
  ### Step 1: Go near to your translation files
97
97
 
package/bin/locize CHANGED
@@ -238,6 +238,7 @@ program
238
238
  .option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
239
239
  .option('-m, --path-mask <mask>', 'This will define the folder and file structure; do not add a file extension (default: {{language}}/{{namespace}})', `{{language}}${path.sep}{{namespace}}`)
240
240
  .option('-c, --clean <true|false>', 'Removes all local files by removing the whole folder (default: false)', 'false')
241
+ .option('-up, --unpublished <true|false>', 'Downloads the current (unpublished) translations. This will generate private download costs (default: false)', 'false')
241
242
  .option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${workingDirectory} or ${home})`)
242
243
  .action((options) => {
243
244
  try {
@@ -271,6 +272,8 @@ program
271
272
 
272
273
  const clean = options.clean === 'true';
273
274
 
275
+ const unpublished = options.unpublished === 'true';
276
+
274
277
  const languageFolderPrefix = options.languageFolderPrefix || '';
275
278
 
276
279
  const pathMask = options.pathMask;
@@ -287,7 +290,8 @@ program
287
290
  skipEmpty: skipEmpty,
288
291
  clean: clean,
289
292
  languageFolderPrefix: languageFolderPrefix,
290
- pathMask: pathMask
293
+ pathMask: pathMask,
294
+ unpublished: unpublished
291
295
  });
292
296
  })
293
297
  .on('--help', () => {
@@ -377,6 +381,7 @@ program
377
381
  .option('-l, --language <lng>', 'The language that should be targeted')
378
382
  .option('-n, --namespace <ns>', 'The namespace that should be targeted')
379
383
  .option('-g, --get-path <url>', `Specify the get-path url that should be used (default: ${getPathUrl})`)
384
+ .option('-up, --unpublished <true|false>', 'Downloads the current (unpublished) translations. This will generate private download costs (default: false)', 'false')
380
385
  .option('-C, --config-path <configPath>', `Specify the path to the optional locize config file (default: ${workingDirectory} or ${home})`)
381
386
  .action((options) => {
382
387
  try {
@@ -424,6 +429,7 @@ program
424
429
  const referenceLanguageOnly = options.referenceLanguageOnly === 'false' ? false : true;
425
430
  const compareModificationTime = options.compareModificationTime === 'true';
426
431
  const pathMask = options.pathMask;
432
+ const unpublished = options.unpublished === 'true';
427
433
 
428
434
  sync({
429
435
  apiPath: url.parse(getPath).protocol + '//' + url.parse(getPath).host,
@@ -444,7 +450,8 @@ program
444
450
  language: language,
445
451
  namespace: namespace,
446
452
  dry: dry,
447
- pathMask: pathMask
453
+ pathMask: pathMask,
454
+ unpublished: unpublished
448
455
  });
449
456
  })
450
457
  .on('--help', () => {
package/download.js CHANGED
@@ -10,6 +10,7 @@ const getRemoteNamespace = require('./getRemoteNamespace');
10
10
  const getRemoteLanguages = require('./getRemoteLanguages');
11
11
  const convertToDesiredFormat = require('./convertToDesiredFormat');
12
12
  const formats = require('./formats');
13
+ const getProjectStats = require('./getProjectStats');
13
14
  const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
14
15
 
15
16
  function handleDownload(opt, url, err, res, downloads, cb) {
@@ -33,7 +34,7 @@ function handleDownload(opt, url, err, res, downloads, cb) {
33
34
  return;
34
35
  }
35
36
 
36
- async.each(downloads, (download, clb) => {
37
+ async.eachLimit(downloads, 30, (download, clb) => {
37
38
  const splitted = download.key.split('/');
38
39
  const version = splitted[download.isPrivate ? 2 : 1];
39
40
  const lng = splitted[download.isPrivate ? 3 : 2];
@@ -92,6 +93,56 @@ function handleDownload(opt, url, err, res, downloads, cb) {
92
93
  });
93
94
  }
94
95
 
96
+ function handlePull(opt, toDownload, cb) {
97
+ const url = opt.apiPath + '/pull/' + opt.projectId + '/' + opt.version;
98
+ async.eachLimit(toDownload, 30, (download, clb) => {
99
+ const lng = download.language;
100
+ const namespace = download.namespace;
101
+
102
+ getRemoteNamespace(opt, lng, namespace, (err, ns, lastModified) => {
103
+ if (err) return clb(err);
104
+
105
+ if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
106
+ return clb(null);
107
+ }
108
+
109
+ convertToDesiredFormat(opt, namespace, lng, ns, lastModified, (err, converted) => {
110
+ if (err) {
111
+ err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
112
+ return clb(err);
113
+ }
114
+ var filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, lng).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
115
+ var mkdirPath;
116
+ if (filledMask.lastIndexOf(path.sep) > 0) {
117
+ mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
118
+ }
119
+ if (!opt.language) {
120
+ if (mkdirPath) mkdirp.sync(path.join(opt.path, mkdirPath));
121
+ fs.writeFile(path.join(opt.path, filledMask), converted, clb);
122
+ return;
123
+ }
124
+
125
+ if (filledMask.indexOf(path.sep) > 0) filledMask = filledMask.replace(opt.languageFolderPrefix + lng, '');
126
+ const parentDir = path.dirname(path.join(opt.path, filledMask));
127
+ mkdirp.sync(parentDir);
128
+ fs.writeFile(path.join(opt.path, filledMask), converted, clb);
129
+ });
130
+ });
131
+ }, (err) => {
132
+ if (err) {
133
+ if (!cb) {
134
+ console.error(colors.red(err.message));
135
+ process.exit(1);
136
+ }
137
+ if (cb) cb(err);
138
+ return;
139
+ }
140
+
141
+ if (!cb) console.log(colors.green(`downloaded ${url} to ${opt.path}...`));
142
+ if (cb) cb(null);
143
+ });
144
+ }
145
+
95
146
  const handleError = (err, cb) => {
96
147
  if (!cb && err) {
97
148
  console.error(colors.red(err.message));
@@ -103,7 +154,7 @@ const handleError = (err, cb) => {
103
154
  const download = (opt, cb) => {
104
155
  opt.format = opt.format || 'json';
105
156
  if (!reversedFileExtensionsMap[opt.format]) {
106
- return handleError(new Error(`${opt.format} is not a valid format!`));
157
+ return handleError(new Error(`${opt.format} is not a valid format!`), cb);
107
158
  }
108
159
 
109
160
  if (opt.skipEmpty === undefined) opt.skipEmpty = true;
@@ -115,6 +166,9 @@ const download = (opt, cb) => {
115
166
  opt.pathMaskInterpolationSuffix = opt.pathMaskInterpolationSuffix || '}}';
116
167
  opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
117
168
  opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
169
+ if (opt.unpublished && !opt.apiKey) {
170
+ return handleError(new Error('Please provide also an api-key!'), cb);
171
+ }
118
172
 
119
173
  var url = opt.apiPath + '/download/' + opt.projectId;
120
174
 
@@ -140,27 +194,57 @@ const download = (opt, cb) => {
140
194
  if (!cb) console.log(colors.yellow(`downloading ${url} to ${opt.path}...`));
141
195
 
142
196
  getRemoteLanguages(opt, (err) => {
143
- if (err) return handleError(err);
144
-
145
- request(url, {
146
- method: 'get',
147
- headers: opt.apiKey ? {
148
- 'Authorization': opt.apiKey
149
- } : undefined
150
- }, (err, res, obj) => {
151
- if (res && res.status === 401) {
152
- opt.apiKey = null;
153
- request(url, {
154
- method: 'get',
155
- }, (err, res, obj) => {
156
- if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
157
- handleDownload(opt, url, err, res, obj, cb);
158
- });
159
- return;
160
- }
197
+ if (err) return handleError(err, cb);
198
+
199
+ if (!opt.unpublished) {
200
+ request(url, {
201
+ method: 'get',
202
+ headers: opt.apiKey ? {
203
+ 'Authorization': opt.apiKey
204
+ } : undefined
205
+ }, (err, res, obj) => {
206
+ if (res && res.status === 401) {
207
+ opt.apiKey = null;
208
+ request(url, {
209
+ method: 'get',
210
+ }, (err, res, obj) => {
211
+ if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
212
+ handleDownload(opt, url, err, res, obj, cb);
213
+ });
214
+ return;
215
+ }
161
216
 
162
- if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
163
- handleDownload(opt, url, err, res, obj, cb);
217
+ if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
218
+ handleDownload(opt, url, err, res, obj, cb);
219
+ });
220
+ return;
221
+ }
222
+
223
+ getProjectStats(opt, (err, res) => {
224
+ if (err) return handleError(err, cb);
225
+ if (!res || !res[opt.version]) return handleError(new Error('Nothing found!'), cb);
226
+
227
+ const toDownload = [];
228
+ const lngsToCheck = opt.language ? [opt.language] : Object.keys(res[opt.version]);
229
+ lngsToCheck.forEach((l) => {
230
+ if (opt.namespaces) {
231
+ opt.namespaces.forEach((n) => {
232
+ if (!res[opt.version][l][n]) return;
233
+ if (opt.skipEmpty && res[opt.version][l][n].segmentsTranslated === 0) return;
234
+ toDownload.push({ language: l, namespace: n });
235
+ });
236
+ } else if (opt.namespace) {
237
+ if (!res[opt.version][l][opt.namespace]) return;
238
+ if (opt.skipEmpty && res[opt.version][l][opt.namespace].segmentsTranslated === 0) return;
239
+ toDownload.push({ language: l, namespace: opt.namespace });
240
+ } else {
241
+ Object.keys(res[opt.version][l]).forEach((n) => {
242
+ if (opt.skipEmpty && res[opt.version][l][n].segmentsTranslated === 0) return;
243
+ toDownload.push({ language: l, namespace: n });
244
+ });
245
+ }
246
+ });
247
+ handlePull(opt, toDownload, cb);
164
248
  });
165
249
  });
166
250
  };
package/format.js CHANGED
@@ -150,7 +150,7 @@ function processFiles(opt, filePaths, clb) {
150
150
  files.forEach((f) => {
151
151
  if (f.content) {
152
152
  Object.keys(f.content).forEach((k) => {
153
- if (f.content[k] && typeof f.content[k] === 'object' && typeof f.content[k].value !== undefined) {
153
+ if (f.content[k] && typeof f.content[k] === 'object' && f.content[k].value !== undefined) {
154
154
  f.content[k] = f.content[k].value;
155
155
  }
156
156
  });
@@ -0,0 +1,40 @@
1
+ const colors = require('colors');
2
+ const request = require('./request');
3
+
4
+ const getProjectStats = (opt, cb) => {
5
+ request(opt.apiPath + '/stats/project/' + opt.projectId, {
6
+ method: 'get',
7
+ headers: {
8
+ 'Authorization': opt.apiKey
9
+ }
10
+ }, (err, res, obj) => {
11
+ if (err || (obj && (obj.errorMessage || obj.message))) {
12
+ if (!cb) console.log(colors.red('getting job failed...'));
13
+
14
+ if (err) {
15
+ if (!cb) { console.error(colors.red(err.message)); process.exit(1); }
16
+ if (cb) cb(err);
17
+ return;
18
+ }
19
+ if (obj && (obj.errorMessage || obj.message)) {
20
+ if (!cb) { console.error(colors.red((obj.errorMessage || obj.message))); process.exit(1); }
21
+ if (cb) cb(new Error((obj.errorMessage || obj.message)));
22
+ return;
23
+ }
24
+ }
25
+ if (res.status === 404) {
26
+ if (!cb) { console.error(colors.yellow(res.statusText + ' (' + res.status + ')')); process.exit(1); }
27
+ if (cb) cb(null, null);
28
+ return;
29
+ }
30
+ if (res.status >= 300) {
31
+ if (!cb) { console.error(colors.red(res.statusText + ' (' + res.status + ')')); process.exit(1); }
32
+ if (cb) cb(new Error(res.statusText + ' (' + res.status + ')'));
33
+ return;
34
+ }
35
+ if (!cb) console.log(colors.green('getting project stats succesfull'));
36
+ if (cb) cb(null, obj);
37
+ });
38
+ };
39
+
40
+ module.exports = getProjectStats;
@@ -3,9 +3,9 @@ const flatten = require('flat');
3
3
  const sortFlatResources = require('./sortFlatResources');
4
4
 
5
5
  const getRemoteNamespace = (opt, lng, ns, cb) => {
6
- request(opt.apiPath + (opt.isPrivate ? '/private' : '') + '/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns + '?ts=' + Date.now(), {
6
+ request(opt.apiPath + (opt.isPrivate ? '/private' : opt.unpublished ? '/pull' : '') + '/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns + '?ts=' + Date.now(), {
7
7
  method: 'get',
8
- headers: opt.isPrivate ? {
8
+ headers: (opt.isPrivate || opt.unpublished) ? {
9
9
  'Authorization': opt.apiKey
10
10
  } : undefined
11
11
  }, (err, res, obj) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locize-cli",
3
- "version": "7.10.1",
3
+ "version": "7.12.1",
4
4
  "description": "locize cli to import locales",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,17 +9,17 @@
9
9
  "dependencies": {
10
10
  "@js.properties/properties": "0.5.4",
11
11
  "android-string-resource": "2.3.4",
12
- "async": "3.2.3",
12
+ "async": "3.2.4",
13
13
  "cacheable-lookup": "6.0.4",
14
14
  "colors": "1.4.0",
15
- "commander": "9.0.0",
15
+ "commander": "9.3.0",
16
16
  "csvjson": "5.1.0",
17
- "diff": "5.0.0",
17
+ "diff": "5.1.0",
18
18
  "flat": "5.0.2",
19
19
  "fluent_conv": "3.1.0",
20
20
  "gettext-converter": "1.2.2",
21
- "https-proxy-agent": "5.0.0",
22
- "ini": "2.0.0",
21
+ "https-proxy-agent": "5.0.1",
22
+ "ini": "3.0.0",
23
23
  "js-yaml": "4.1.0",
24
24
  "laravelphp": "2.0.3",
25
25
  "lodash.clonedeep": "4.5.0",
@@ -30,18 +30,18 @@
30
30
  "strings-file": "0.0.5",
31
31
  "tmexchange": "2.0.4",
32
32
  "xliff": "6.0.3",
33
- "xlsx": "0.18.3"
33
+ "xlsx": "0.18.5"
34
34
  },
35
35
  "devDependencies": {
36
- "eslint": "7.32.0",
37
- "gh-release": "6.0.2",
38
- "pkg": "5.5.2"
36
+ "eslint": "8.17.0",
37
+ "gh-release": "6.0.4",
38
+ "pkg": "5.7.0"
39
39
  },
40
40
  "scripts": {
41
41
  "lint": "eslint .",
42
42
  "test": "npm run lint",
43
- "pkg": "mkdir -p ./bins && pkg ./bin/locize --out-path ./bins --targets node16-linux-x64,node16-macos-x64,node16-win-x64",
44
- "release": "gh-release --assets ./bins/locize-linux,./bins/locize-macos,./bins/locize-win.exe",
43
+ "pkg": "mkdir -p ./bins && pkg ./bin/locize --out-path ./bins --targets node16-linux-x64,node16-macos-x64,node16-win-x64,node16-alpine-x64",
44
+ "release": "gh-release --assets ./bins/locize-linux,./bins/locize-macos,./bins/locize-win.exe,./bins/locize-alpine",
45
45
  "version": "npm run pkg",
46
46
  "postversion": "git push && npm run release"
47
47
  },
package/sync.js CHANGED
@@ -15,6 +15,7 @@ const parseLocalReference = require('./parseLocalReference');
15
15
  const formats = require('./formats');
16
16
  const lngCodes = require('./lngs.json');
17
17
  const deleteNamespace = require('./deleteNamespace');
18
+ const getProjectStats = require('./getProjectStats');
18
19
  const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
19
20
 
20
21
  const getDirectories = (srcpath) => {
@@ -24,22 +25,66 @@ const getDirectories = (srcpath) => {
24
25
  };
25
26
 
26
27
  const getDownloads = (opt, cb) => {
27
- request(opt.apiPath + '/download/' + opt.projectId + '/' + opt.version, {
28
- method: 'get',
29
- headers: opt.apiKey ? {
30
- 'Authorization': opt.apiKey
31
- } : undefined
32
- }, (err, res, obj) => {
33
- if (err) return cb(err);
34
- if (res.status >= 300) {
35
- if (obj && (obj.errorMessage || obj.message)) {
36
- return cb(new Error((obj.errorMessage || obj.message)));
28
+ if (!opt.unpublished) {
29
+ request(opt.apiPath + '/download/' + opt.projectId + '/' + opt.version, {
30
+ method: 'get',
31
+ headers: opt.apiKey ? {
32
+ 'Authorization': opt.apiKey
33
+ } : undefined
34
+ }, (err, res, obj) => {
35
+ if (err) return cb(err);
36
+ if (res.status >= 300) {
37
+ if (obj && (obj.errorMessage || obj.message)) {
38
+ return cb(new Error((obj.errorMessage || obj.message)));
39
+ }
40
+ return cb(new Error(res.statusText + ' (' + res.status + ')'));
37
41
  }
38
- return cb(new Error(res.statusText + ' (' + res.status + ')'));
39
- }
40
- if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
41
- cb(null, obj);
42
- });
42
+ if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
43
+ cb(null, obj);
44
+ });
45
+ } else {
46
+ getProjectStats(opt, (err, res) => {
47
+ if (err) return handleError(err, cb);
48
+ if (!res || !res[opt.version]) return handleError(new Error('Nothing found!'), cb);
49
+
50
+ const toDownload = [];
51
+ const lngsToCheck = opt.language ? [opt.language] : Object.keys(res[opt.version]);
52
+ lngsToCheck.forEach((l) => {
53
+ if (opt.namespaces) {
54
+ opt.namespaces.forEach((n) => {
55
+ if (!res[opt.version][l][n]) return;
56
+ if (opt.skipEmpty && res[opt.version][l][n].segmentsTranslated === 0) return;
57
+ toDownload.push({
58
+ url: `${opt.apiPath}/${opt.projectId}/${opt.version}/${l}/${n}`,
59
+ key: `${opt.projectId}/${opt.version}/${l}/${n}`,
60
+ lastModified: '1960-01-01T00:00:00.000Z',
61
+ size: 0
62
+ });
63
+ });
64
+ } else if (opt.namespace) {
65
+ if (!res[opt.version][l][opt.namespace]) return;
66
+ if (opt.skipEmpty && res[opt.version][l][opt.namespace].segmentsTranslated === 0) return;
67
+ toDownload.push({
68
+ url: `${opt.apiPath}/${opt.projectId}/${opt.version}/${l}/${opt.namespace}`,
69
+ key: `${opt.projectId}/${opt.version}/${l}/${opt.namespace}`,
70
+ lastModified: '1960-01-01T00:00:00.000Z',
71
+ size: 0
72
+ });
73
+ } else {
74
+ Object.keys(res[opt.version][l]).forEach((n) => {
75
+ if (opt.skipEmpty && res[opt.version][l][n].segmentsTranslated === 0) return;
76
+ toDownload.push({
77
+ url: `${opt.apiPath}/${opt.projectId}/${opt.version}/${l}/${n}`,
78
+ key: `${opt.projectId}/${opt.version}/${l}/${n}`,
79
+ lastModified: '1960-01-01T00:00:00.000Z',
80
+ size: 0
81
+ });
82
+ });
83
+ }
84
+ });
85
+ cb(null, toDownload);
86
+ });
87
+ }
43
88
  };
44
89
 
45
90
  const compareNamespace = (local, remote, lastModifiedLocal, lastModifiedRemote) => {
@@ -160,7 +205,7 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
160
205
  return lng !== opt.referenceLanguage;
161
206
  });
162
207
  }
163
- async.each(downloads, (download, clb) => {
208
+ async.eachLimit(downloads, 30, (download, clb) => {
164
209
  const splitted = download.key.split('/');
165
210
  const lng = splitted[download.isPrivate ? 3 : 2];
166
211
  const namespace = splitted[download.isPrivate ? 4 : 3];
@@ -327,7 +372,7 @@ const checkForMissingLocalNamespaces = (downloads, localNamespaces, opt) => {
327
372
  const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
328
373
  if (!localNamespaces || localNamespaces.length === 0) {
329
374
  downloadAll(opt, remoteLanguages, (err) => {
330
- if (err) return handleError(err);
375
+ if (err) return handleError(err, cb);
331
376
  if (!cb) console.log(colors.green('FINISHED'));
332
377
  if (cb) cb(null);
333
378
  });
@@ -335,14 +380,14 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
335
380
  }
336
381
 
337
382
  getDownloads(opt, (err, downloads) => {
338
- if (err) return handleError(err);
383
+ if (err) return handleError(err, cb);
339
384
 
340
385
  opt.isPrivate = downloads.length > 0 && downloads[0].isPrivate;
341
386
 
342
387
  const localMissingNamespaces = checkForMissingLocalNamespaces(downloads, localNamespaces, opt);
343
388
 
344
389
  compareNamespaces(opt, localNamespaces, (err, compared) => {
345
- if (err) return handleError(err);
390
+ if (err) return handleError(err, cb);
346
391
 
347
392
  const onlyToUpdate = compared.filter((ns) => ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove).concat(ns.diff.toUpdate).length > 0);
348
393
 
@@ -412,7 +457,7 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
412
457
  async.eachLimit(remoteLanguages, Math.round(require('os').cpus().length / 2), (lng, clb) => update(opt, lng, nsOnlyRemove, shouldOmit, clb), clb);
413
458
  });
414
459
  }, (err) => {
415
- if (err) return handleError(err);
460
+ if (err) return handleError(err, cb);
416
461
  if (!cb) console.log(colors.grey('syncing...'));
417
462
 
418
463
  function down() {
@@ -432,7 +477,7 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
432
477
  }
433
478
  }
434
479
  } : undefined, (err) => {
435
- if (err) return handleError(err);
480
+ if (err) return handleError(err, cb);
436
481
  if (!cb) console.log(colors.green('FINISHED'));
437
482
  if (cb) cb(null);
438
483
  });
@@ -450,12 +495,12 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
450
495
  'Authorization': opt.apiKey
451
496
  }
452
497
  }, (err, res, obj) => {
453
- if (err) return handleError(err);
498
+ if (err) return handleError(err, cb);
454
499
  if (res.status >= 300 && res.status !== 412) {
455
500
  if (obj && (obj.errorMessage || obj.message)) {
456
- return handleError(new Error((obj.errorMessage || obj.message)));
501
+ return handleError(new Error((obj.errorMessage || obj.message)), cb);
457
502
  }
458
- return handleError(new Error(res.statusText + ' (' + res.status + ')'));
503
+ return handleError(new Error(res.statusText + ' (' + res.status + ')'), cb);
459
504
  }
460
505
  down();
461
506
  });
@@ -464,7 +509,7 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
464
509
 
465
510
  if (opt.deleteRemoteNamespace && localMissingNamespaces.length > 0) {
466
511
  wasThereSomethingToUpdate = true;
467
- async.each(localMissingNamespaces, (n, clb) => {
512
+ async.eachLimit(localMissingNamespaces, 30, (n, clb) => {
468
513
  if (opt.dry) {
469
514
  console.log(colors.red(`would delete complete namespace ${n.namespace}...`));
470
515
  return clb();
@@ -478,7 +523,7 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
478
523
  namespace: n.namespace
479
524
  }, clb);
480
525
  }, (err) => {
481
- if (err) return handleError(err);
526
+ if (err) return handleError(err, cb);
482
527
  updateComparedNamespaces();
483
528
  });
484
529
  return;
@@ -491,7 +536,7 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
491
536
  const sync = (opt, cb) => {
492
537
  opt.format = opt.format || 'json';
493
538
  if (!reversedFileExtensionsMap[opt.format]) {
494
- return handleError(new Error(`${opt.format} is not a valid format!`));
539
+ return handleError(new Error(`${opt.format} is not a valid format!`), cb);
495
540
  }
496
541
 
497
542
  opt.version = opt.version || 'latest';
@@ -510,9 +555,12 @@ const sync = (opt, cb) => {
510
555
  opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
511
556
  opt.languageFolderPrefix = opt.languageFolderPrefix || '';
512
557
  opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
558
+ if (opt.unpublished && !opt.apiKey) {
559
+ return handleError(new Error('Please provide also an api-key!'), cb);
560
+ }
513
561
 
514
562
  getRemoteLanguages(opt, (err, remoteLanguages) => {
515
- if (err) return handleError(err);
563
+ if (err) return handleError(err, cb);
516
564
 
517
565
  if (opt.referenceLanguageOnly && opt.language && opt.referenceLanguage !== opt.language) {
518
566
  opt.referenceLanguage = opt.language;
@@ -520,7 +568,7 @@ const sync = (opt, cb) => {
520
568
 
521
569
  if (opt.referenceLanguageOnly) {
522
570
  parseLocalReference(opt, (err, localNamespaces) => {
523
- if (err) return handleError(err);
571
+ if (err) return handleError(err, cb);
524
572
 
525
573
  if (!opt.dry && opt.cleanLocalFiles) {
526
574
  localNamespaces.forEach((ln) => fs.unlinkSync(ln.path));
@@ -533,7 +581,7 @@ const sync = (opt, cb) => {
533
581
  }
534
582
 
535
583
  parseLocalLanguages(opt, remoteLanguages, (err, localNamespaces) => {
536
- if (err) return handleError(err);
584
+ if (err) return handleError(err, cb);
537
585
 
538
586
  if (!opt.dry && opt.cleanLocalFiles) {
539
587
  localNamespaces.forEach((ln) => fs.unlinkSync(ln.path));