locize-cli 7.6.17 → 7.8.0

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,27 @@ 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.8.0](https://github.com/locize/locize-cli/compare/v7.7.2...v7.8.0) - 2022-01-13
9
+
10
+ - sync: introduce --delete-remote-namespace option
11
+
12
+
13
+ ## [7.7.2](https://github.com/locize/locize-cli/compare/v7.7.1...v7.7.2) - 2021-12-10
14
+
15
+ - update dependencies
16
+
17
+
18
+ ## [7.7.1](https://github.com/locize/locize-cli/compare/v7.7.0...v7.7.1) - 2021-12-10
19
+
20
+ - new binary build (x64)
21
+
22
+
23
+ ## [7.7.0](https://github.com/locize/locize-cli/compare/v7.6.17...v7.7.0) - 2021-12-08
24
+
25
+ - sync: optimize api requests on bigger projects
26
+ - retry on very short dns resolution errors
27
+
28
+
8
29
  ## [7.6.17](https://github.com/locize/locize-cli/compare/v7.6.16...v7.6.17) - 2021-09-21
9
30
 
10
31
  - check skipEmpty flag when downloading translations
package/README.md CHANGED
@@ -91,6 +91,8 @@ 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 publish mode, or alternatively [publish](#publish-version) that version before you execute the cli command. ⚠️**
95
+
94
96
  ### Step 1: Go near to your translation files
95
97
 
96
98
  ```sh
package/bin/locize CHANGED
@@ -368,6 +368,7 @@ program
368
368
  .option('-cf, --clean-local-files <true|false>', 'Removes all local files without removing any folder (default: false)', 'false')
369
369
  .option('-u, --update-values <true|false>', 'This will update values of existing translations. (default: false)', 'false')
370
370
  .option('-S, --skip-delete <true|false>', 'This will skip the removal of keys on locize. (default: false)', 'false')
371
+ .option('-D, --delete-remote-namespace <true|false>', 'This will delete a complete namespace on locize, if a local file in reference language was deleted. (default: false)', 'false')
371
372
  .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}}`)
372
373
  .option('-P, --language-folder-prefix <prefix>', 'This will be added as a local folder name prefix in front of the language.', '')
373
374
  .option('-d, --dry <true|false>', 'Dry run (default: false)', 'false')
@@ -417,6 +418,7 @@ program
417
418
  const dry = options.dry === 'true';
418
419
  const updateValues = options.updateValues === 'true';
419
420
  const skipDelete = options.skipDelete === 'true';
421
+ const deleteRemoteNamespace = options.deleteRemoteNamespace === 'true';
420
422
  const languageFolderPrefix = options.languageFolderPrefix || '';
421
423
  const skipEmpty = options.skipEmpty === 'true';
422
424
  const referenceLanguageOnly = options.referenceLanguageOnly === 'false' ? false : true;
@@ -432,6 +434,7 @@ program
432
434
  format: options.format,
433
435
  updateValues: updateValues,
434
436
  skipDelete: skipDelete,
437
+ deleteRemoteNamespace: deleteRemoteNamespace,
435
438
  languageFolderPrefix: languageFolderPrefix,
436
439
  clean: clean,
437
440
  cleanLocalFiles: cleanLocalFiles,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locize-cli",
3
- "version": "7.6.17",
3
+ "version": "7.8.0",
4
4
  "description": "locize cli to import locales",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -9,37 +9,37 @@
9
9
  "dependencies": {
10
10
  "@js.properties/properties": "0.5.4",
11
11
  "android-string-resource": "2.3.4",
12
- "async": "3.2.1",
12
+ "async": "3.2.2",
13
13
  "colors": "1.4.0",
14
14
  "commander": "7.2.0",
15
15
  "csvjson": "5.1.0",
16
16
  "diff": "5.0.0",
17
17
  "flat": "5.0.2",
18
18
  "fluent_conv": "3.1.0",
19
- "gettext-converter": "1.1.0",
19
+ "gettext-converter": "1.2.0",
20
20
  "https-proxy-agent": "5.0.0",
21
21
  "ini": "2.0.0",
22
22
  "js-yaml": "4.1.0",
23
23
  "laravelphp": "2.0.3",
24
24
  "lodash.clonedeep": "4.5.0",
25
25
  "mkdirp": "1.0.4",
26
- "node-fetch": "2.6.2",
26
+ "node-fetch": "2.6.6",
27
27
  "resx": "2.0.3",
28
28
  "rimraf": "3.0.2",
29
29
  "strings-file": "0.0.5",
30
30
  "tmexchange": "2.0.4",
31
- "xliff": "5.6.2",
32
- "xlsx": "0.17.2"
31
+ "xliff": "5.7.2",
32
+ "xlsx": "0.17.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "eslint": "7.32.0",
36
- "gh-release": "6.0.0",
37
- "pkg": "5.3.2"
36
+ "gh-release": "6.0.1",
37
+ "pkg": "5.5.1"
38
38
  },
39
39
  "scripts": {
40
40
  "lint": "eslint .",
41
41
  "test": "npm run lint",
42
- "pkg": "mkdir -p ./bins && pkg ./bin/locize --out-path ./bins",
42
+ "pkg": "mkdir -p ./bins && pkg ./bin/locize --out-path ./bins --targets node16-linux-x64,node16-macos-x64,node16-win-x64",
43
43
  "release": "gh-release --assets ./bins/locize-linux,./bins/locize-macos,./bins/locize-win.exe",
44
44
  "version": "npm run pkg",
45
45
  "postversion": "git push && npm run release"
package/request.js CHANGED
@@ -22,5 +22,19 @@ module.exports = (url, options, callback) => {
22
22
  } else {
23
23
  return { res };
24
24
  }
25
- }).then((ret) => callback(null, ret.res, ret.obj)).catch(callback);
25
+ }).then((ret) => callback(null, ret.res, ret.obj)).catch((err) => {
26
+ if (err && err.message && err.message.indexOf('ENOTFOUND') > -1) {
27
+ setTimeout(() => {
28
+ fetch(url, options).then((res) => {
29
+ if (res.headers.get('content-type') && res.headers.get('content-type').indexOf('json') > 0) {
30
+ return new Promise((resolve, reject) => res.json().then((obj) => resolve({ res, obj })).catch(reject));
31
+ } else {
32
+ return { res };
33
+ }
34
+ }).then((ret) => callback(null, ret.res, ret.obj)).catch(callback);
35
+ }, 3000);
36
+ return;
37
+ }
38
+ callback(err);
39
+ });
26
40
  };
package/sync.js CHANGED
@@ -14,6 +14,7 @@ const parseLocalLanguages = require('./parseLocalLanguages');
14
14
  const parseLocalReference = require('./parseLocalReference');
15
15
  const formats = require('./formats');
16
16
  const lngCodes = require('./lngs.json');
17
+ const deleteNamespace = require('./deleteNamespace');
17
18
  const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
18
19
 
19
20
  const getDirectories = (srcpath) => {
@@ -198,7 +199,7 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
198
199
  });
199
200
  };
200
201
 
201
- const update = (opt, lng, ns, cb) => {
202
+ const update = (opt, lng, ns, shouldOmit, cb) => {
202
203
  var data = {};
203
204
  if (!opt.skipDelete) {
204
205
  ns.diff.toRemove.forEach((k) => data[k] = null);
@@ -215,7 +216,7 @@ const update = (opt, lng, ns, cb) => {
215
216
  var payloadKeysLimit = 1000;
216
217
 
217
218
  function send(d, clb, isRetrying) {
218
- request(opt.apiPath + '/update/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns.namespace, {
219
+ request(opt.apiPath + '/update/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns.namespace + (shouldOmit ? '?omitstatsgeneration=true' : ''), {
219
220
  method: 'post',
220
221
  body: d,
221
222
  headers: {
@@ -303,6 +304,26 @@ const handleError = (err, cb) => {
303
304
  if (cb) cb(err);
304
305
  };
305
306
 
307
+ const checkForMissingLocalNamespaces = (downloads, localNamespaces, opt) => {
308
+ const localMissingNamespaces = [];
309
+ downloads.forEach((d) => {
310
+ const splitted = d.url.split('/');
311
+ const namespace = splitted.pop();
312
+ const language = splitted.pop();
313
+ // if (!opt.referenceLanguageOnly || (opt.referenceLanguageOnly && language === opt.referenceLanguage)) {
314
+ if (language === opt.referenceLanguage) {
315
+ const foundLocalNamespace = localNamespaces.find((n) => n.namespace === namespace && n.language === language);
316
+ if (!foundLocalNamespace) {
317
+ localMissingNamespaces.push({
318
+ language,
319
+ namespace
320
+ });
321
+ }
322
+ }
323
+ });
324
+ return localMissingNamespaces;
325
+ };
326
+
306
327
  const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
307
328
  if (!localNamespaces || localNamespaces.length === 0) {
308
329
  downloadAll(opt, remoteLanguages, (err) => {
@@ -318,86 +339,151 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
318
339
 
319
340
  opt.isPrivate = downloads.length > 0 && downloads[0].isPrivate;
320
341
 
342
+ const localMissingNamespaces = checkForMissingLocalNamespaces(downloads, localNamespaces, opt);
343
+
321
344
  compareNamespaces(opt, localNamespaces, (err, compared) => {
322
345
  if (err) return handleError(err);
323
346
 
347
+ const onlyToUpdate = compared.filter((ns) => ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove).concat(ns.diff.toUpdate).length > 0);
348
+
349
+ const lngsInReqs = [];
350
+ const nsInReqs = [];
351
+ onlyToUpdate.forEach((n) => {
352
+ if (lngsInReqs.indexOf(n.language) < 0) {
353
+ lngsInReqs.push(n.language);
354
+ }
355
+ if (nsInReqs.indexOf(n.namespace) < 0) {
356
+ nsInReqs.push(n.namespace);
357
+ }
358
+ });
359
+ const shouldOmit = lngsInReqs.length > 5 || nsInReqs.length > 5;
360
+
324
361
  var wasThereSomethingToUpdate = false;
325
- async.eachLimit(compared, Math.round(require('os').cpus().length / 2), (ns, clb) => {
326
- if (!cb) {
327
- if (ns.diff.toRemove.length > 0) {
328
- if (opt.skipDelete) {
329
- console.log(colors.bgRed(`skipping the removal of ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
330
- if (opt.dry) console.log(colors.bgRed(`skipped to remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
331
- } else {
332
- console.log(colors.red(`removing ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
333
- if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
362
+
363
+ function updateComparedNamespaces() {
364
+ async.eachLimit(compared, Math.round(require('os').cpus().length / 2), (ns, clb) => {
365
+ if (!cb) {
366
+ if (ns.diff.toRemove.length > 0) {
367
+ if (opt.skipDelete) {
368
+ console.log(colors.bgRed(`skipping the removal of ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
369
+ if (opt.dry) console.log(colors.bgRed(`skipped to remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
370
+ } else {
371
+ console.log(colors.red(`removing ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
372
+ if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
373
+ }
334
374
  }
335
- }
336
- if (ns.diff.toRemoveLocally.length > 0) {
337
- console.log(colors.red(`removing ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
338
- if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemoveLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
339
- }
340
- if (ns.diff.toAdd.length > 0) {
341
- console.log(colors.green(`adding ${ns.diff.toAdd.length} keys in ${ns.language}/${ns.namespace}...`));
342
- if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAdd.join(', ')} in ${ns.language}/${ns.namespace}...`));
343
- }
344
- if (ns.diff.toAddLocally.length > 0) {
345
- if (opt.skipDelete) {
346
- console.log(colors.bgGreen(`skipping the addition of ${ns.diff.toAddLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
347
- if (opt.dry) console.log(colors.bgGreen(`skipped the addition of ${ns.diff.toAddLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
348
- } else {
349
- console.log(colors.green(`adding ${ns.diff.toAddLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
350
- if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAddLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
375
+ if (ns.diff.toRemoveLocally.length > 0) {
376
+ console.log(colors.red(`removing ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
377
+ if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemoveLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
351
378
  }
352
- }
353
- if (opt.updateValues) {
354
- if (ns.diff.toUpdate.length > 0) {
355
- console.log(colors.yellow(`updating ${ns.diff.toUpdate.length} keys in ${ns.language}/${ns.namespace}...`));
356
- if (opt.dry) console.log(colors.yellow(`would update ${ns.diff.toUpdate.join(', ')} in ${ns.language}/${ns.namespace}...`));
379
+ if (ns.diff.toAdd.length > 0) {
380
+ console.log(colors.green(`adding ${ns.diff.toAdd.length} keys in ${ns.language}/${ns.namespace}...`));
381
+ if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAdd.join(', ')} in ${ns.language}/${ns.namespace}...`));
357
382
  }
358
- if (ns.diff.toUpdateLocally.length > 0) {
359
- console.log(colors.yellow(`updating ${ns.diff.toUpdateLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
360
- if (opt.dry) console.log(colors.yellow(`would update ${ns.diff.toUpdateLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
383
+ if (ns.diff.toAddLocally.length > 0) {
384
+ if (opt.skipDelete) {
385
+ console.log(colors.bgGreen(`skipping the addition of ${ns.diff.toAddLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
386
+ if (opt.dry) console.log(colors.bgGreen(`skipped the addition of ${ns.diff.toAddLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
387
+ } else {
388
+ console.log(colors.green(`adding ${ns.diff.toAddLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
389
+ if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAddLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
390
+ }
361
391
  }
362
- }
363
- const somethingToUpdate = ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove)/*.concat(ns.diff.toUpdate)*/.length > 0;
364
- if (!somethingToUpdate) console.log(colors.grey(`nothing to update for ${ns.language}/${ns.namespace}`));
365
- if (!wasThereSomethingToUpdate && somethingToUpdate) wasThereSomethingToUpdate = true;
366
- }
367
- update(opt, ns.language, ns, (err) => {
368
- if (err) return clb(err);
369
- if (ns.diff.toRemove.length === 0 || ns.language !== opt.referenceLanguage) return clb();
370
- const nsOnlyRemove = cloneDeep(ns);
371
- nsOnlyRemove.diff.toAdd = [];
372
- nsOnlyRemove.diff.toUpdate = [];
373
- async.eachLimit(remoteLanguages, Math.round(require('os').cpus().length / 2), (lng, clb) => update(opt, lng, nsOnlyRemove, clb), clb);
374
- });
375
- }, (err) => {
376
- if (err) return handleError(err);
377
-
378
- if (!cb) console.log(colors.grey('syncing...'));
379
- setTimeout(() => {
380
- downloadAll(opt, remoteLanguages, false, opt.skipDelete ? (lng, namespace, ns) => {
381
- const found = compared.find((n) => n.namespace === namespace && n.language === lng);
382
- if (found && found.diff) {
383
- if (found.diff.toAddLocally && found.diff.toAddLocally.length > 0) {
384
- found.diff.toAddLocally.forEach((k) => {
385
- delete ns[k];
386
- });
392
+ if (opt.updateValues) {
393
+ if (ns.diff.toUpdate.length > 0) {
394
+ console.log(colors.yellow(`updating ${ns.diff.toUpdate.length} keys in ${ns.language}/${ns.namespace}...`));
395
+ if (opt.dry) console.log(colors.yellow(`would update ${ns.diff.toUpdate.join(', ')} in ${ns.language}/${ns.namespace}...`));
387
396
  }
388
- if (found.diff.toRemove && found.diff.toRemove.length > 0) {
389
- found.diff.toRemove.forEach((k) => {
390
- delete ns[k];
391
- });
397
+ if (ns.diff.toUpdateLocally.length > 0) {
398
+ console.log(colors.yellow(`updating ${ns.diff.toUpdateLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
399
+ if (opt.dry) console.log(colors.yellow(`would update ${ns.diff.toUpdateLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
392
400
  }
393
401
  }
394
- } : undefined, (err) => {
402
+ const somethingToUpdate = ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove)/*.concat(ns.diff.toUpdate)*/.length > 0;
403
+ if (!somethingToUpdate) console.log(colors.grey(`nothing to update for ${ns.language}/${ns.namespace}`));
404
+ if (!wasThereSomethingToUpdate && somethingToUpdate) wasThereSomethingToUpdate = true;
405
+ }
406
+ update(opt, ns.language, ns, shouldOmit, (err) => {
407
+ if (err) return clb(err);
408
+ if (ns.diff.toRemove.length === 0 || ns.language !== opt.referenceLanguage) return clb();
409
+ const nsOnlyRemove = cloneDeep(ns);
410
+ nsOnlyRemove.diff.toAdd = [];
411
+ nsOnlyRemove.diff.toUpdate = [];
412
+ async.eachLimit(remoteLanguages, Math.round(require('os').cpus().length / 2), (lng, clb) => update(opt, lng, nsOnlyRemove, shouldOmit, clb), clb);
413
+ });
414
+ }, (err) => {
415
+ if (err) return handleError(err);
416
+ if (!cb) console.log(colors.grey('syncing...'));
417
+
418
+ function down() {
419
+ setTimeout(() => { // wait a bit before downloading... just to have a chance to get the newly published files
420
+ downloadAll(opt, remoteLanguages, false, opt.skipDelete ? (lng, namespace, ns) => {
421
+ const found = compared.find((n) => n.namespace === namespace && n.language === lng);
422
+ if (found && found.diff) {
423
+ if (found.diff.toAddLocally && found.diff.toAddLocally.length > 0) {
424
+ found.diff.toAddLocally.forEach((k) => {
425
+ delete ns[k];
426
+ });
427
+ }
428
+ if (found.diff.toRemove && found.diff.toRemove.length > 0) {
429
+ found.diff.toRemove.forEach((k) => {
430
+ delete ns[k];
431
+ });
432
+ }
433
+ }
434
+ } : undefined, (err) => {
435
+ if (err) return handleError(err);
436
+ if (!cb) console.log(colors.green('FINISHED'));
437
+ if (cb) cb(null);
438
+ });
439
+ }, wasThereSomethingToUpdate && !opt.dry ? 5000 : 0);
440
+ }
441
+
442
+ if (!shouldOmit) return down();
443
+ if (opt.dry) return down();
444
+
445
+ // optimize stats generation...
446
+ request(opt.apiPath + '/stats/project/regenerate/' + opt.projectId + '/' + opt.version + (lngsInReqs.length === 1 ? `/${lngsInReqs[0]}` : '') + (nsInReqs.length === 1 ? `?namespace=${nsInReqs[0]}` : ''), {
447
+ method: 'post',
448
+ body: {},
449
+ headers: {
450
+ 'Authorization': opt.apiKey
451
+ }
452
+ }, (err, res, obj) => {
395
453
  if (err) return handleError(err);
396
- if (!cb) console.log(colors.green('FINISHED'));
397
- if (cb) cb(null);
454
+ if (res.status >= 300 && res.status !== 412) {
455
+ if (obj && (obj.errorMessage || obj.message)) {
456
+ return handleError(new Error((obj.errorMessage || obj.message)));
457
+ }
458
+ return handleError(new Error(res.statusText + ' (' + res.status + ')'));
459
+ }
460
+ down();
398
461
  });
399
- }, wasThereSomethingToUpdate && !opt.dry ? 5000 : 0);
400
- }); // wait a bit before downloading... just to have a chance to get the newly published files
462
+ });
463
+ }
464
+
465
+ if (opt.deleteRemoteNamespace && localMissingNamespaces.length > 0) {
466
+ wasThereSomethingToUpdate = true;
467
+ async.each(localMissingNamespaces, (n, clb) => {
468
+ if (opt.dry) {
469
+ console.log(colors.red(`would delete complete namespace ${n.namespace}...`));
470
+ return clb();
471
+ }
472
+ console.log(colors.red(`deleting complete namespace ${n.namespace}...`));
473
+ deleteNamespace({
474
+ apiPath: opt.apiPath,
475
+ apiKey: opt.apiKey,
476
+ projectId: opt.projectId,
477
+ version: opt.version,
478
+ namespace: n.namespace
479
+ }, clb);
480
+ }, (err) => {
481
+ if (err) return handleError(err);
482
+ updateComparedNamespaces();
483
+ });
484
+ return;
485
+ }
486
+ updateComparedNamespaces();
401
487
  });
402
488
  });
403
489
  };