locize-cli 7.6.14 → 7.7.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/.github/stale.yml +23 -0
- package/CHANGELOG.md +21 -0
- package/README.md +12 -0
- package/download.js +5 -1
- package/missing.js +1 -0
- package/package.json +11 -11
- package/request.js +15 -1
- package/sync.js +64 -25
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Number of days of inactivity before an issue becomes stale
|
|
2
|
+
daysUntilStale: 7
|
|
3
|
+
# Number of days of inactivity before a stale issue is closed
|
|
4
|
+
daysUntilClose: 7
|
|
5
|
+
# Issues with these labels will never be considered stale
|
|
6
|
+
exemptLabels:
|
|
7
|
+
- "discussion"
|
|
8
|
+
- "feature request"
|
|
9
|
+
- "bug"
|
|
10
|
+
- "breaking change"
|
|
11
|
+
- "doc"
|
|
12
|
+
- "issue"
|
|
13
|
+
- "help wanted"
|
|
14
|
+
- "good first issue"
|
|
15
|
+
# Label to use when marking an issue as stale
|
|
16
|
+
staleLabel: stale
|
|
17
|
+
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
18
|
+
markComment: >
|
|
19
|
+
This issue has been automatically marked as stale because it has not had
|
|
20
|
+
recent activity. It will be closed if no further activity occurs. Thank you
|
|
21
|
+
for your contributions.
|
|
22
|
+
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
23
|
+
closeComment: false
|
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.7.0](https://github.com/locize/locize-cli/compare/v7.6.17...v7.7.0) - 2021-12-08
|
|
9
|
+
|
|
10
|
+
- sync: optimize api requests on bigger projects
|
|
11
|
+
- retry on very short dns resolution errors
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [7.6.17](https://github.com/locize/locize-cli/compare/v7.6.16...v7.6.17) - 2021-09-21
|
|
15
|
+
|
|
16
|
+
- check skipEmpty flag when downloading translations
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## [7.6.16](https://github.com/locize/locize-cli/compare/v7.6.15...v7.6.16) - 2021-09-20
|
|
20
|
+
|
|
21
|
+
- by default set also languageFolderPrefix to empty string when using programmatically
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [7.6.15](https://github.com/locize/locize-cli/compare/v7.6.14...v7.6.15) - 2021-07-23
|
|
25
|
+
|
|
26
|
+
- update some dependencies
|
|
27
|
+
|
|
28
|
+
|
|
8
29
|
## [7.6.14](https://github.com/locize/locize-cli/compare/v7.6.13...v7.6.14) - 2021-03-13
|
|
9
30
|
|
|
10
31
|
- update all dependencies
|
package/README.md
CHANGED
|
@@ -88,6 +88,11 @@ locize download --project-id my-project-id-93e1-442a-ab35-24331fa294ba --ver lat
|
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
## Synchronize locize with your repository (or any other local directory)
|
|
91
|
+
By using the sync command, you can keep your existing code setup and synchronize the translations with locize.
|
|
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
|
+
|
|
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
|
+
|
|
91
96
|
### Step 1: Go near to your translation files
|
|
92
97
|
|
|
93
98
|
```sh
|
|
@@ -139,6 +144,13 @@ Navigate to your locize project and check the results => [www.locize.app](https:
|
|
|
139
144
|
|
|
140
145
|
|
|
141
146
|
## Push missing keys to locize from your repository (or any other local directory)
|
|
147
|
+
This is useful, when i.e. using [i18next-scanner](https://github.com/i18next/i18next-scanner), like described [here](https://github.com/locize/i18next-locize-backend/issues/315#issuecomment-586967039).
|
|
148
|
+
The save-missing command uses the [missing API](https://docs.locize.com/integration/api#missing-translations) and the sync command uses the [update API](https://docs.locize.com/integration/api#update-remove-translations)
|
|
149
|
+
So, if you want to save new keys (that does not exist in locize), the save-missing command is the better choice.
|
|
150
|
+
Doing so, you can then for example make use of the “created by missing API" filter in the locize UI.
|
|
151
|
+
|
|
152
|
+
But if you need to update existing keys, the sync command is the correct choice.
|
|
153
|
+
|
|
142
154
|
### Step 1: Go near to your translation files
|
|
143
155
|
|
|
144
156
|
```sh
|
package/download.js
CHANGED
|
@@ -152,10 +152,14 @@ const download = (opt, cb) => {
|
|
|
152
152
|
opt.apiKey = null;
|
|
153
153
|
request(url, {
|
|
154
154
|
method: 'get',
|
|
155
|
-
}, (err, res, obj) =>
|
|
155
|
+
}, (err, res, obj) => {
|
|
156
|
+
if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
|
|
157
|
+
handleDownload(opt, url, err, res, obj, cb);
|
|
158
|
+
});
|
|
156
159
|
return;
|
|
157
160
|
}
|
|
158
161
|
|
|
162
|
+
if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
|
|
159
163
|
handleDownload(opt, url, err, res, obj, cb);
|
|
160
164
|
});
|
|
161
165
|
});
|
package/missing.js
CHANGED
|
@@ -139,6 +139,7 @@ const missing = (opt, cb) => {
|
|
|
139
139
|
opt.pathMaskInterpolationPrefix = opt.pathMaskInterpolationPrefix || '{{';
|
|
140
140
|
opt.pathMaskInterpolationSuffix = opt.pathMaskInterpolationSuffix || '}}';
|
|
141
141
|
opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
|
|
142
|
+
opt.languageFolderPrefix = opt.languageFolderPrefix || '';
|
|
142
143
|
opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
|
|
143
144
|
|
|
144
145
|
getRemoteLanguages(opt, (err, remoteLanguages) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locize-cli",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"description": "locize cli to import locales",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,32 +9,32 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@js.properties/properties": "0.5.4",
|
|
11
11
|
"android-string-resource": "2.3.4",
|
|
12
|
-
"async": "3.2.
|
|
12
|
+
"async": "3.2.1",
|
|
13
13
|
"colors": "1.4.0",
|
|
14
|
-
"commander": "7.
|
|
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.0
|
|
19
|
+
"gettext-converter": "1.1.0",
|
|
20
20
|
"https-proxy-agent": "5.0.0",
|
|
21
21
|
"ini": "2.0.0",
|
|
22
|
-
"js-yaml": "4.
|
|
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.
|
|
26
|
+
"node-fetch": "2.6.2",
|
|
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.
|
|
32
|
-
"xlsx": "0.
|
|
31
|
+
"xliff": "5.6.2",
|
|
32
|
+
"xlsx": "0.17.2"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"eslint": "7.
|
|
36
|
-
"gh-release": "
|
|
37
|
-
"pkg": "
|
|
35
|
+
"eslint": "7.32.0",
|
|
36
|
+
"gh-release": "6.0.0",
|
|
37
|
+
"pkg": "5.3.2"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"lint": "eslint .",
|
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(
|
|
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
|
@@ -36,6 +36,7 @@ const getDownloads = (opt, cb) => {
|
|
|
36
36
|
}
|
|
37
37
|
return cb(new Error(res.statusText + ' (' + res.status + ')'));
|
|
38
38
|
}
|
|
39
|
+
if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
|
|
39
40
|
cb(null, obj);
|
|
40
41
|
});
|
|
41
42
|
};
|
|
@@ -197,7 +198,7 @@ const downloadAll = (opt, remoteLanguages, omitRef, manipulate, cb) => {
|
|
|
197
198
|
});
|
|
198
199
|
};
|
|
199
200
|
|
|
200
|
-
const update = (opt, lng, ns, cb) => {
|
|
201
|
+
const update = (opt, lng, ns, shouldOmit, cb) => {
|
|
201
202
|
var data = {};
|
|
202
203
|
if (!opt.skipDelete) {
|
|
203
204
|
ns.diff.toRemove.forEach((k) => data[k] = null);
|
|
@@ -214,7 +215,7 @@ const update = (opt, lng, ns, cb) => {
|
|
|
214
215
|
var payloadKeysLimit = 1000;
|
|
215
216
|
|
|
216
217
|
function send(d, clb, isRetrying) {
|
|
217
|
-
request(opt.apiPath + '/update/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns.namespace, {
|
|
218
|
+
request(opt.apiPath + '/update/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns.namespace + (shouldOmit ? '?omitstatsgeneration=true' : ''), {
|
|
218
219
|
method: 'post',
|
|
219
220
|
body: d,
|
|
220
221
|
headers: {
|
|
@@ -320,6 +321,20 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
|
|
|
320
321
|
compareNamespaces(opt, localNamespaces, (err, compared) => {
|
|
321
322
|
if (err) return handleError(err);
|
|
322
323
|
|
|
324
|
+
const onlyToUpdate = compared.filter((ns) => ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove).concat(ns.diff.toUpdate).length > 0);
|
|
325
|
+
|
|
326
|
+
const lngsInReqs = [];
|
|
327
|
+
const nsInReqs = [];
|
|
328
|
+
onlyToUpdate.forEach((n) => {
|
|
329
|
+
if (lngsInReqs.indexOf(n.language) < 0) {
|
|
330
|
+
lngsInReqs.push(n.language);
|
|
331
|
+
}
|
|
332
|
+
if (nsInReqs.indexOf(n.namespace) < 0) {
|
|
333
|
+
nsInReqs.push(n.namespace);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
const shouldOmit = lngsInReqs.length > 5 || nsInReqs.length > 5;
|
|
337
|
+
|
|
323
338
|
var wasThereSomethingToUpdate = false;
|
|
324
339
|
async.eachLimit(compared, Math.round(require('os').cpus().length / 2), (ns, clb) => {
|
|
325
340
|
if (!cb) {
|
|
@@ -363,40 +378,63 @@ const handleSync = (opt, remoteLanguages, localNamespaces, cb) => {
|
|
|
363
378
|
if (!somethingToUpdate) console.log(colors.grey(`nothing to update for ${ns.language}/${ns.namespace}`));
|
|
364
379
|
if (!wasThereSomethingToUpdate && somethingToUpdate) wasThereSomethingToUpdate = true;
|
|
365
380
|
}
|
|
366
|
-
update(opt, ns.language, ns, (err) => {
|
|
381
|
+
update(opt, ns.language, ns, shouldOmit, (err) => {
|
|
367
382
|
if (err) return clb(err);
|
|
368
383
|
if (ns.diff.toRemove.length === 0 || ns.language !== opt.referenceLanguage) return clb();
|
|
369
384
|
const nsOnlyRemove = cloneDeep(ns);
|
|
370
385
|
nsOnlyRemove.diff.toAdd = [];
|
|
371
386
|
nsOnlyRemove.diff.toUpdate = [];
|
|
372
|
-
async.eachLimit(remoteLanguages, Math.round(require('os').cpus().length / 2), (lng, clb) => update(opt, lng, nsOnlyRemove, clb), clb);
|
|
387
|
+
async.eachLimit(remoteLanguages, Math.round(require('os').cpus().length / 2), (lng, clb) => update(opt, lng, nsOnlyRemove, shouldOmit, clb), clb);
|
|
373
388
|
});
|
|
374
389
|
}, (err) => {
|
|
375
390
|
if (err) return handleError(err);
|
|
376
|
-
|
|
377
391
|
if (!cb) console.log(colors.grey('syncing...'));
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
392
|
+
|
|
393
|
+
function down() {
|
|
394
|
+
setTimeout(() => { // wait a bit before downloading... just to have a chance to get the newly published files
|
|
395
|
+
downloadAll(opt, remoteLanguages, false, opt.skipDelete ? (lng, namespace, ns) => {
|
|
396
|
+
const found = compared.find((n) => n.namespace === namespace && n.language === lng);
|
|
397
|
+
if (found && found.diff) {
|
|
398
|
+
if (found.diff.toAddLocally && found.diff.toAddLocally.length > 0) {
|
|
399
|
+
found.diff.toAddLocally.forEach((k) => {
|
|
400
|
+
delete ns[k];
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (found.diff.toRemove && found.diff.toRemove.length > 0) {
|
|
404
|
+
found.diff.toRemove.forEach((k) => {
|
|
405
|
+
delete ns[k];
|
|
406
|
+
});
|
|
407
|
+
}
|
|
391
408
|
}
|
|
409
|
+
} : undefined, (err) => {
|
|
410
|
+
if (err) return handleError(err);
|
|
411
|
+
if (!cb) console.log(colors.green('FINISHED'));
|
|
412
|
+
if (cb) cb(null);
|
|
413
|
+
});
|
|
414
|
+
}, wasThereSomethingToUpdate && !opt.dry ? 5000 : 0);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!shouldOmit) return down();
|
|
418
|
+
if (opt.dry) return down();
|
|
419
|
+
|
|
420
|
+
// optimize stats generation...
|
|
421
|
+
request(opt.apiPath + '/stats/project/regenerate/' + opt.projectId + '/' + opt.version + (lngsInReqs.length === 1 ? `/${lngsInReqs[0]}` : '') + (nsInReqs.length === 1 ? `?namespace=${nsInReqs[0]}` : ''), {
|
|
422
|
+
method: 'post',
|
|
423
|
+
body: {},
|
|
424
|
+
headers: {
|
|
425
|
+
'Authorization': opt.apiKey
|
|
426
|
+
}
|
|
427
|
+
}, (err, res, obj) => {
|
|
428
|
+
if (err) return handleError(err);
|
|
429
|
+
if (res.status >= 300 && res.status !== 412) {
|
|
430
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
431
|
+
return handleError(new Error((obj.errorMessage || obj.message)));
|
|
392
432
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}, wasThereSomethingToUpdate && !opt.dry ? 5000 : 0);
|
|
399
|
-
}); // wait a bit before downloading... just to have a chance to get the newly published files
|
|
433
|
+
return handleError(new Error(res.statusText + ' (' + res.status + ')'));
|
|
434
|
+
}
|
|
435
|
+
down();
|
|
436
|
+
});
|
|
437
|
+
});
|
|
400
438
|
});
|
|
401
439
|
});
|
|
402
440
|
};
|
|
@@ -421,6 +459,7 @@ const sync = (opt, cb) => {
|
|
|
421
459
|
opt.pathMaskInterpolationPrefix = opt.pathMaskInterpolationPrefix || '{{';
|
|
422
460
|
opt.pathMaskInterpolationSuffix = opt.pathMaskInterpolationSuffix || '}}';
|
|
423
461
|
opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
|
|
462
|
+
opt.languageFolderPrefix = opt.languageFolderPrefix || '';
|
|
424
463
|
opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
|
|
425
464
|
|
|
426
465
|
getRemoteLanguages(opt, (err, remoteLanguages) => {
|