locize-cli 11.0.0 → 12.0.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 +12 -0
- package/LICENSE +1 -1
- package/README.md +1 -0
- package/dist/cjs/add.js +90 -0
- package/{bin/locize → dist/cjs/cli.js} +390 -670
- package/dist/cjs/combineSubkeyPreprocessor.js +155 -0
- package/dist/cjs/convertToDesiredFormat.js +205 -0
- package/dist/cjs/convertToFlatFormat.js +231 -0
- package/dist/cjs/copyVersion.js +60 -0
- package/dist/cjs/createBranch.js +59 -0
- package/dist/cjs/deleteBranch.js +89 -0
- package/dist/cjs/deleteNamespace.js +37 -0
- package/dist/cjs/download.js +376 -0
- package/dist/cjs/filterNamespaces.js +13 -0
- package/dist/cjs/format.js +156 -0
- package/dist/cjs/formats.js +33 -0
- package/dist/cjs/get.js +66 -0
- package/dist/cjs/getBranches.js +37 -0
- package/dist/cjs/getJob.js +37 -0
- package/dist/cjs/getProjectStats.js +37 -0
- package/dist/cjs/getRemoteLanguages.js +38 -0
- package/dist/cjs/getRemoteNamespace.js +125 -0
- package/dist/cjs/index.js +37 -0
- package/dist/cjs/isValidUuid.js +6 -0
- package/dist/cjs/lngs.js +215 -0
- package/dist/cjs/mapLimit.js +22 -0
- package/dist/cjs/mergeBranch.js +80 -0
- package/dist/cjs/migrate.js +239 -0
- package/dist/cjs/missing.js +162 -0
- package/dist/cjs/package.json +5 -0
- package/{parseLocalLanguage.js → dist/cjs/parseLocalLanguage.js} +135 -142
- package/dist/cjs/parseLocalLanguages.js +18 -0
- package/dist/cjs/parseLocalReference.js +11 -0
- package/dist/cjs/publishVersion.js +42 -0
- package/dist/cjs/removeUndefinedFromArrays.js +19 -0
- package/dist/cjs/removeVersion.js +42 -0
- package/dist/cjs/request.js +66 -0
- package/dist/cjs/shouldUnflatten.js +21 -0
- package/dist/cjs/sortFlatResources.js +13 -0
- package/dist/cjs/sync.js +772 -0
- package/dist/cjs/unflatten.js +81 -0
- package/dist/esm/add.js +88 -0
- package/dist/esm/cli.js +1020 -0
- package/{combineSubkeyPreprocessor.js → dist/esm/combineSubkeyPreprocessor.js} +70 -73
- package/dist/esm/convertToDesiredFormat.js +203 -0
- package/dist/esm/convertToFlatFormat.js +229 -0
- package/dist/esm/copyVersion.js +58 -0
- package/dist/esm/createBranch.js +57 -0
- package/dist/esm/deleteBranch.js +87 -0
- package/dist/esm/deleteNamespace.js +35 -0
- package/dist/esm/download.js +374 -0
- package/{filterNamespaces.js → dist/esm/filterNamespaces.js} +4 -4
- package/dist/esm/format.js +154 -0
- package/{formats.js → dist/esm/formats.js} +7 -11
- package/dist/esm/get.js +64 -0
- package/dist/esm/getBranches.js +35 -0
- package/dist/esm/getJob.js +35 -0
- package/dist/esm/getProjectStats.js +35 -0
- package/dist/esm/getRemoteLanguages.js +36 -0
- package/dist/esm/getRemoteNamespace.js +123 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/isValidUuid.js +4 -0
- package/dist/esm/lngs.js +213 -0
- package/dist/esm/mapLimit.js +20 -0
- package/dist/esm/mergeBranch.js +78 -0
- package/dist/esm/migrate.js +237 -0
- package/dist/esm/missing.js +160 -0
- package/dist/esm/parseLocalLanguage.js +194 -0
- package/dist/esm/parseLocalLanguages.js +16 -0
- package/dist/esm/parseLocalReference.js +9 -0
- package/dist/esm/publishVersion.js +40 -0
- package/{removeUndefinedFromArrays.js → dist/esm/removeUndefinedFromArrays.js} +5 -5
- package/dist/esm/removeVersion.js +40 -0
- package/dist/esm/request.js +64 -0
- package/{shouldUnflatten.js → dist/esm/shouldUnflatten.js} +7 -7
- package/dist/esm/sortFlatResources.js +11 -0
- package/dist/esm/sync.js +770 -0
- package/{unflatten.js → dist/esm/unflatten.js} +36 -34
- package/package.json +39 -18
- package/rollup.config.js +57 -0
- package/add.js +0 -105
- package/convertToDesiredFormat.js +0 -268
- package/convertToFlatFormat.js +0 -322
- package/copyVersion.js +0 -69
- package/createBranch.js +0 -61
- package/deleteBranch.js +0 -97
- package/deleteNamespace.js +0 -39
- package/download.js +0 -516
- package/format.js +0 -206
- package/get.js +0 -81
- package/getBranches.js +0 -40
- package/getJob.js +0 -40
- package/getProjectStats.js +0 -40
- package/getRemoteLanguages.js +0 -40
- package/getRemoteNamespace.js +0 -122
- package/index.js +0 -9
- package/isValidUuid.js +0 -2
- package/lngs.json +0 -211
- package/mergeBranch.js +0 -102
- package/migrate.js +0 -314
- package/missing.js +0 -169
- package/parseLocalLanguages.js +0 -22
- package/parseLocalReference.js +0 -10
- package/publishVersion.js +0 -64
- package/removeVersion.js +0 -64
- package/request.js +0 -64
- package/sortFlatResources.js +0 -9
- package/sync.js +0 -786
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var colors = require('colors');
|
|
4
|
+
var request = require('./request.js');
|
|
5
|
+
var getBranches = require('./getBranches.js');
|
|
6
|
+
|
|
7
|
+
const handleError = (err) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
console.error(colors.red(err.stack));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const createBranch = async (opt) => {
|
|
15
|
+
let branches;
|
|
16
|
+
try {
|
|
17
|
+
branches = await getBranches(opt);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
handleError(err);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const b = branches && branches.find((br) => br.name === opt.branch);
|
|
23
|
+
if (b) {
|
|
24
|
+
console.log(colors.green('creating branch "' + b.name + '" (' + b.id + ') not necessary, because already existing'));
|
|
25
|
+
return b
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { res, obj, err } = await request(opt.apiEndpoint + '/branch/create/' + opt.projectId + '/' + opt.version, {
|
|
29
|
+
method: 'post',
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: opt.apiKey
|
|
32
|
+
},
|
|
33
|
+
body: { name: opt.branch }
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (err || (obj && (obj.errorMessage || obj.message))) {
|
|
37
|
+
console.log(colors.red('creating branch failed...'));
|
|
38
|
+
if (err) {
|
|
39
|
+
console.error(colors.red(err.message));
|
|
40
|
+
throw err
|
|
41
|
+
}
|
|
42
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
43
|
+
console.error(colors.red((obj.errorMessage || obj.message)));
|
|
44
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (res.status === 404) {
|
|
48
|
+
console.error(colors.yellow(res.statusText + ' (' + res.status + ')'));
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
if (res.status >= 300) {
|
|
52
|
+
console.error(colors.red(res.statusText + ' (' + res.status + ')'));
|
|
53
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
54
|
+
}
|
|
55
|
+
console.log(colors.green('creating branch "' + obj.name + '" (' + obj.id + ') successful'));
|
|
56
|
+
return obj
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
module.exports = createBranch;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var colors = require('colors');
|
|
4
|
+
var request = require('./request.js');
|
|
5
|
+
var getBranches = require('./getBranches.js');
|
|
6
|
+
var getJob = require('./getJob.js');
|
|
7
|
+
var isValidUuid = require('./isValidUuid.js');
|
|
8
|
+
|
|
9
|
+
const handleError = (err) => {
|
|
10
|
+
if (err) {
|
|
11
|
+
console.error(colors.red(err.stack));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
17
|
+
|
|
18
|
+
const deleteBranch = async (opt) => {
|
|
19
|
+
const { res, obj, err } = await request(opt.apiEndpoint + '/branch/' + opt.branch, {
|
|
20
|
+
method: 'delete',
|
|
21
|
+
headers: {
|
|
22
|
+
Authorization: opt.apiKey
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (err || (obj && (obj.errorMessage || obj.message))) {
|
|
27
|
+
console.log(colors.red('deleting branch failed...'));
|
|
28
|
+
if (err) {
|
|
29
|
+
console.error(colors.red(err.message));
|
|
30
|
+
throw err
|
|
31
|
+
}
|
|
32
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
33
|
+
console.error(colors.red((obj.errorMessage || obj.message)));
|
|
34
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (res.status === 404) {
|
|
38
|
+
console.error(colors.yellow(res.statusText + ' (' + res.status + ')'));
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
if (res.status >= 300) {
|
|
42
|
+
console.error(colors.red(res.statusText + ' (' + res.status + ')'));
|
|
43
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!obj || !obj.jobId) {
|
|
47
|
+
console.error(colors.red('No jobId! Something went wrong!'));
|
|
48
|
+
throw new Error('No jobId! Something went wrong!')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let job;
|
|
52
|
+
while (true) {
|
|
53
|
+
job = await getJob({
|
|
54
|
+
apiEndpoint: opt.apiEndpoint,
|
|
55
|
+
apiKey: opt.apiKey,
|
|
56
|
+
projectId: opt.branch
|
|
57
|
+
}, obj.jobId);
|
|
58
|
+
if (job && !job.timeouted) {
|
|
59
|
+
await sleep(2000);
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
if (job && job.timeouted) {
|
|
63
|
+
console.error(colors.red('Job timeouted!'));
|
|
64
|
+
throw new Error('Job timeouted!')
|
|
65
|
+
}
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
console.log(colors.green(`deleting branch "${opt.branch}" succesfully requested`));
|
|
69
|
+
// done
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const deleteBranchEntry = async (opt) => {
|
|
73
|
+
let branches;
|
|
74
|
+
try {
|
|
75
|
+
branches = await getBranches(opt);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
handleError(err);
|
|
78
|
+
}
|
|
79
|
+
let b;
|
|
80
|
+
if (isValidUuid(opt.branch)) b = branches.find((br) => br.id === opt.branch);
|
|
81
|
+
if (!b) b = branches.find((br) => br.name === opt.branch);
|
|
82
|
+
if (!b) {
|
|
83
|
+
handleError(new Error(`Branch ${opt.branch} not found!`));
|
|
84
|
+
}
|
|
85
|
+
opt.branch = b.id;
|
|
86
|
+
await deleteBranch(opt);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
module.exports = deleteBranchEntry;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var colors = require('colors');
|
|
4
|
+
var request = require('./request.js');
|
|
5
|
+
|
|
6
|
+
const deleteNamespace = async (opt) => {
|
|
7
|
+
const url = opt.apiEndpoint + '/delete/' + opt.projectId + '/' + opt.version + '/' + opt.namespace;
|
|
8
|
+
|
|
9
|
+
console.log(colors.yellow(`deleting ${opt.namespace} from ${opt.version}...`));
|
|
10
|
+
|
|
11
|
+
const { res, obj, err } = await request(url, {
|
|
12
|
+
method: 'delete',
|
|
13
|
+
headers: {
|
|
14
|
+
Authorization: opt.apiKey
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (err || (obj && (obj.errorMessage || obj.message))) {
|
|
19
|
+
console.log(colors.red(`delete failed for ${opt.namespace} from ${opt.version}...`));
|
|
20
|
+
if (err) {
|
|
21
|
+
console.error(colors.red(err.message));
|
|
22
|
+
throw err
|
|
23
|
+
}
|
|
24
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
25
|
+
console.error(colors.red((obj.errorMessage || obj.message)));
|
|
26
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (res.status >= 300) {
|
|
30
|
+
console.error(colors.red(res.statusText + ' (' + res.status + ')'));
|
|
31
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
32
|
+
}
|
|
33
|
+
console.log(colors.green(`deleted ${opt.namespace} from ${opt.version}...`));
|
|
34
|
+
// done
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
module.exports = deleteNamespace;
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var colors = require('colors');
|
|
4
|
+
var mkdirp = require('mkdirp');
|
|
5
|
+
var rimraf = require('rimraf');
|
|
6
|
+
var request = require('./request.js');
|
|
7
|
+
var fs = require('node:fs');
|
|
8
|
+
var path = require('node:path');
|
|
9
|
+
var flatten = require('flat');
|
|
10
|
+
var getRemoteNamespace = require('./getRemoteNamespace.js');
|
|
11
|
+
var getRemoteLanguages = require('./getRemoteLanguages.js');
|
|
12
|
+
var convertToDesiredFormat = require('./convertToDesiredFormat.js');
|
|
13
|
+
var formats = require('./formats.js');
|
|
14
|
+
var getProjectStats = require('./getProjectStats.js');
|
|
15
|
+
var locize2xcstrings = require('locize-xcstrings/locize2xcstrings');
|
|
16
|
+
var getBranches = require('./getBranches.js');
|
|
17
|
+
var isValidUuid = require('./isValidUuid.js');
|
|
18
|
+
var mapLimit = require('./mapLimit.js');
|
|
19
|
+
|
|
20
|
+
const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
|
|
21
|
+
|
|
22
|
+
function getInfosInUrl (download) {
|
|
23
|
+
const splitted = download.key.split('/');
|
|
24
|
+
const version = splitted[download.isPrivate ? 2 : 1];
|
|
25
|
+
const language = splitted[download.isPrivate ? 3 : 2];
|
|
26
|
+
const namespace = splitted[download.isPrivate ? 4 : 3];
|
|
27
|
+
return { version, language, namespace }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function handleDownload (opt, url, err, res, downloads) {
|
|
31
|
+
if (err || (downloads && (downloads.errorMessage || downloads.message))) {
|
|
32
|
+
console.log(colors.red(`download failed for ${url} to ${opt.path}...`));
|
|
33
|
+
if (err) {
|
|
34
|
+
console.error(colors.red(err.message));
|
|
35
|
+
throw err
|
|
36
|
+
}
|
|
37
|
+
if (downloads && (downloads.errorMessage || downloads.message)) {
|
|
38
|
+
console.error(colors.red((downloads.errorMessage || downloads.message)));
|
|
39
|
+
throw new Error((downloads.errorMessage || downloads.message))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (res.status >= 300) {
|
|
43
|
+
console.error(colors.red(res.statusText + ' (' + res.status + ')'));
|
|
44
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (opt.format === 'xcstrings') {
|
|
48
|
+
const downloadsByNamespace = {};
|
|
49
|
+
downloads.forEach((download) => {
|
|
50
|
+
const { version, namespace } = getInfosInUrl(download);
|
|
51
|
+
opt.isPrivate = download.isPrivate;
|
|
52
|
+
downloadsByNamespace[version] = downloadsByNamespace[version] || {};
|
|
53
|
+
downloadsByNamespace[version][namespace] = downloadsByNamespace[version][namespace] || [];
|
|
54
|
+
downloadsByNamespace[version][namespace].push(download);
|
|
55
|
+
});
|
|
56
|
+
for (const version of Object.keys(downloadsByNamespace)) {
|
|
57
|
+
await mapLimit(Object.keys(downloadsByNamespace[version]), 20, async (ns) => {
|
|
58
|
+
if (opt.namespace && opt.namespace !== ns) return
|
|
59
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(ns) < 0) return
|
|
60
|
+
const locizeData = {
|
|
61
|
+
sourceLng: opt.referenceLanguage,
|
|
62
|
+
resources: {}
|
|
63
|
+
};
|
|
64
|
+
await mapLimit(downloadsByNamespace[version][ns], 20, async (download) => {
|
|
65
|
+
const { language } = getInfosInUrl(download);
|
|
66
|
+
const { result: nsData } = await getRemoteNamespace(opt, language, ns);
|
|
67
|
+
if (opt.skipEmpty && Object.keys(flatten(nsData)).length === 0) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
locizeData.resources[language] = nsData;
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const converted = locize2xcstrings(locizeData);
|
|
74
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, '').replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, ns) + reversedFileExtensionsMap[opt.format];
|
|
75
|
+
let mkdirPath;
|
|
76
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
77
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
78
|
+
}
|
|
79
|
+
const fileContent = (opt.format !== 'xlsx' && !converted.endsWith('\n')) ? (converted + '\n') : converted;
|
|
80
|
+
if (!opt.version) {
|
|
81
|
+
if (mkdirPath) mkdirp.mkdirp.sync(path.join(opt.path, version, mkdirPath));
|
|
82
|
+
fs.writeFileSync(path.join(opt.path, version, filledMask), fileContent);
|
|
83
|
+
} else {
|
|
84
|
+
if (mkdirPath) mkdirp.mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
85
|
+
fs.writeFileSync(path.join(opt.path, filledMask), fileContent);
|
|
86
|
+
}
|
|
87
|
+
console.log(colors.green(`downloaded ${version}/${ns} to ${opt.path}...`));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
90
|
+
throw err
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
await mapLimit(downloads, 20, async (download) => {
|
|
96
|
+
const { version, language, namespace } = getInfosInUrl(download);
|
|
97
|
+
opt.isPrivate = download.isPrivate;
|
|
98
|
+
if (opt.namespace && opt.namespace !== namespace) return
|
|
99
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return
|
|
100
|
+
const { result: nsData, lastModified } = await getRemoteNamespace(opt, language, namespace);
|
|
101
|
+
if (opt.skipEmpty && Object.keys(flatten(nsData)).length === 0) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
let converted;
|
|
105
|
+
try {
|
|
106
|
+
converted = await convertToDesiredFormat(opt, namespace, language, nsData, lastModified);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
109
|
+
throw err
|
|
110
|
+
}
|
|
111
|
+
let filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, language).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
112
|
+
let mkdirPath;
|
|
113
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
114
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
115
|
+
}
|
|
116
|
+
const fileContent = (opt.format !== 'xlsx' && !converted.endsWith('\n')) ? (converted + '\n') : converted;
|
|
117
|
+
if (!opt.version) {
|
|
118
|
+
if (mkdirPath) mkdirp.mkdirp.sync(path.join(opt.path, version, mkdirPath));
|
|
119
|
+
fs.writeFileSync(path.join(opt.path, version, filledMask), fileContent);
|
|
120
|
+
} else if (!opt.language) {
|
|
121
|
+
if (mkdirPath) mkdirp.mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
122
|
+
fs.writeFileSync(path.join(opt.path, filledMask), fileContent);
|
|
123
|
+
} else {
|
|
124
|
+
if (opt.languageFolderPrefix && filledMask.indexOf(path.sep) > 0) filledMask = filledMask.replace(opt.languageFolderPrefix + language, '');
|
|
125
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
126
|
+
mkdirp.mkdirp.sync(parentDir);
|
|
127
|
+
fs.writeFileSync(path.join(opt.path, filledMask), fileContent);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
console.log(colors.green(`downloaded ${url} to ${opt.path}...`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function handlePull (opt, toDownload) {
|
|
135
|
+
const url = opt.apiEndpoint + '/pull/' + opt.projectId + '/' + opt.version;
|
|
136
|
+
|
|
137
|
+
if (opt.format === 'xcstrings') {
|
|
138
|
+
const downloadsByNamespace = {};
|
|
139
|
+
toDownload.forEach((download) => {
|
|
140
|
+
const { namespace } = download;
|
|
141
|
+
downloadsByNamespace[namespace] = downloadsByNamespace[namespace] || [];
|
|
142
|
+
downloadsByNamespace[namespace].push(download);
|
|
143
|
+
});
|
|
144
|
+
await mapLimit(Object.keys(downloadsByNamespace), 5, async (namespace) => {
|
|
145
|
+
if (opt.namespace && opt.namespace !== namespace) return
|
|
146
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return
|
|
147
|
+
const locizeData = {
|
|
148
|
+
sourceLng: opt.referenceLanguage,
|
|
149
|
+
resources: {}
|
|
150
|
+
};
|
|
151
|
+
await mapLimit(downloadsByNamespace[namespace], 5, async (download) => {
|
|
152
|
+
const { language } = download;
|
|
153
|
+
opt.raw = true;
|
|
154
|
+
const { result: nsData } = await getRemoteNamespace(opt, language, namespace);
|
|
155
|
+
if (opt.skipEmpty && Object.keys(flatten(nsData)).length === 0) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
locizeData.resources[language] = nsData;
|
|
159
|
+
});
|
|
160
|
+
try {
|
|
161
|
+
const result = locize2xcstrings(locizeData);
|
|
162
|
+
const converted = JSON.stringify(result, null, 2);
|
|
163
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, '').replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
164
|
+
let mkdirPath;
|
|
165
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
166
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
167
|
+
}
|
|
168
|
+
const fileContent = (opt.format !== 'xlsx' && !converted.endsWith('\n')) ? (converted + '\n') : converted;
|
|
169
|
+
if (mkdirPath) mkdirp.mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
170
|
+
fs.writeFileSync(path.join(opt.path, filledMask), fileContent);
|
|
171
|
+
console.log(colors.green(`downloaded ${opt.version}/${namespace} to ${opt.path}...`));
|
|
172
|
+
} catch (err) {
|
|
173
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
174
|
+
throw err
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
await mapLimit(toDownload, 5, async (download) => {
|
|
179
|
+
const lng = download.language;
|
|
180
|
+
const namespace = download.namespace;
|
|
181
|
+
if (opt.namespace && opt.namespace !== namespace) return
|
|
182
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return
|
|
183
|
+
const { result: nsData, lastModified } = await getRemoteNamespace(opt, lng, namespace);
|
|
184
|
+
if (opt.skipEmpty && Object.keys(flatten(nsData)).length === 0) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
let converted;
|
|
188
|
+
try {
|
|
189
|
+
converted = await convertToDesiredFormat(opt, namespace, lng, nsData, lastModified);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
err.message = 'Invalid content for "' + opt.format + '" format!\n' + (err.message || '');
|
|
192
|
+
throw err
|
|
193
|
+
}
|
|
194
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, lng).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
195
|
+
let mkdirPath;
|
|
196
|
+
if (filledMask.lastIndexOf(path.sep) > 0) {
|
|
197
|
+
mkdirPath = filledMask.substring(0, filledMask.lastIndexOf(path.sep));
|
|
198
|
+
}
|
|
199
|
+
const fileContent = (opt.format !== 'xlsx' && !converted.endsWith('\n')) ? (converted + '\n') : converted;
|
|
200
|
+
if (mkdirPath) mkdirp.mkdirp.sync(path.join(opt.path, mkdirPath));
|
|
201
|
+
fs.writeFileSync(path.join(opt.path, filledMask), fileContent);
|
|
202
|
+
});
|
|
203
|
+
console.log(colors.green(`downloaded ${url} to ${opt.path}...`));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// handleError removed (unused)
|
|
208
|
+
|
|
209
|
+
const filterDownloadsLanguages = (opt, downloads) => {
|
|
210
|
+
if (opt.languages) {
|
|
211
|
+
downloads = downloads.filter((d) => {
|
|
212
|
+
const splitted = d.key.split('/');
|
|
213
|
+
// const p = splitted[d.isPrivate ? 1 : 0];
|
|
214
|
+
// const v = splitted[d.isPrivate ? 2 : 1];
|
|
215
|
+
const l = splitted[d.isPrivate ? 3 : 2];
|
|
216
|
+
const n = splitted[d.isPrivate ? 4 : 3];
|
|
217
|
+
return opt.languages.indexOf(l) > -1 && (!opt.namespace || opt.namespace === n)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return downloads
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const filterDownloads = (opt, downloads) => {
|
|
224
|
+
if (opt.skipEmpty) return filterDownloadsLanguages(opt, downloads.filter((d) => d.size > 2))
|
|
225
|
+
if (downloads.length < 1) return downloads
|
|
226
|
+
|
|
227
|
+
const allNamespaces = [];
|
|
228
|
+
const downloadMap = {};
|
|
229
|
+
downloads.forEach((d) => {
|
|
230
|
+
const splitted = d.key.split('/');
|
|
231
|
+
const p = splitted[d.isPrivate ? 1 : 0];
|
|
232
|
+
const v = splitted[d.isPrivate ? 2 : 1];
|
|
233
|
+
const l = splitted[d.isPrivate ? 3 : 2];
|
|
234
|
+
const n = splitted[d.isPrivate ? 4 : 3];
|
|
235
|
+
downloadMap[p] = downloadMap[p] || {};
|
|
236
|
+
downloadMap[p][v] = downloadMap[p][v] || {};
|
|
237
|
+
downloadMap[p][v][l] = downloadMap[p][v][l] || {};
|
|
238
|
+
downloadMap[p][v][l][n] = d;
|
|
239
|
+
if (allNamespaces.indexOf(n) < 0) allNamespaces.push(n);
|
|
240
|
+
});
|
|
241
|
+
Object.keys(downloadMap).forEach((projectId) => {
|
|
242
|
+
Object.keys(downloadMap[projectId]).forEach((version) => {
|
|
243
|
+
Object.keys(downloadMap[projectId][version]).forEach((language) => {
|
|
244
|
+
allNamespaces.forEach((namespace) => {
|
|
245
|
+
if (!downloadMap[projectId][version][language][namespace]) {
|
|
246
|
+
downloads.push({
|
|
247
|
+
url: `${opt.apiEndpoint}/${projectId}/${version}/${language}/${namespace}`,
|
|
248
|
+
key: `${projectId}/${version}/${language}/${namespace}`,
|
|
249
|
+
lastModified: '1960-01-01T00:00:00.000Z',
|
|
250
|
+
size: 0
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
return filterDownloadsLanguages(opt, downloads)
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
async function continueToDownload (opt) {
|
|
261
|
+
let url = opt.apiEndpoint + '/download/' + opt.projectId;
|
|
262
|
+
|
|
263
|
+
if (opt.namespace && opt.namespace.indexOf(',') > 0 && opt.namespace.indexOf(' ') < 0) {
|
|
264
|
+
opt.namespaces = opt.namespace.split(',');
|
|
265
|
+
delete opt.namespace;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (opt.version) {
|
|
269
|
+
url += '/' + opt.version;
|
|
270
|
+
if (!opt.languages && opt.language) {
|
|
271
|
+
url += '/' + opt.language;
|
|
272
|
+
if (opt.namespace) {
|
|
273
|
+
url += '/' + opt.namespace;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (opt.clean) rimraf.rimraf.sync(path.join(opt.path, '*'));
|
|
279
|
+
mkdirp.mkdirp.sync(opt.path);
|
|
280
|
+
console.log(colors.yellow(`downloading ${url} to ${opt.path}...`));
|
|
281
|
+
await getRemoteLanguages(opt);
|
|
282
|
+
if (!opt.unpublished) {
|
|
283
|
+
const { res, obj, err } = await request(url, {
|
|
284
|
+
method: 'get',
|
|
285
|
+
headers: opt.apiKey
|
|
286
|
+
? {
|
|
287
|
+
Authorization: opt.apiKey
|
|
288
|
+
}
|
|
289
|
+
: undefined
|
|
290
|
+
});
|
|
291
|
+
let downloadsObj = obj;
|
|
292
|
+
if (res && res.status === 401) {
|
|
293
|
+
opt.apiKey = null;
|
|
294
|
+
const { obj: obj2 } = await request(url, {
|
|
295
|
+
method: 'get',
|
|
296
|
+
});
|
|
297
|
+
downloadsObj = obj2;
|
|
298
|
+
}
|
|
299
|
+
downloadsObj = filterDownloads(opt, downloadsObj || []);
|
|
300
|
+
if (downloadsObj.length > 0) {
|
|
301
|
+
await handleDownload(opt, url, err, res, downloadsObj);
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
const stats = await getProjectStats(opt);
|
|
305
|
+
if (!stats) throw new Error('Nothing found!')
|
|
306
|
+
if (!stats[opt.version]) throw new Error(`Version "${opt.version}" not found!`)
|
|
307
|
+
downloadsObj = filterDownloads(opt, downloadsObj || []);
|
|
308
|
+
await handleDownload(opt, url, err, res, downloadsObj);
|
|
309
|
+
return
|
|
310
|
+
}
|
|
311
|
+
const stats = await getProjectStats(opt);
|
|
312
|
+
if (!stats) throw new Error('Nothing found!')
|
|
313
|
+
if (!stats[opt.version]) throw new Error(`Version "${opt.version}" not found!`)
|
|
314
|
+
const toDownload = [];
|
|
315
|
+
const lngsToCheck = opt.language ? [opt.language] : Object.keys(stats[opt.version]);
|
|
316
|
+
lngsToCheck.forEach((l) => {
|
|
317
|
+
if (opt.namespaces) {
|
|
318
|
+
opt.namespaces.forEach((n) => {
|
|
319
|
+
if (!stats[opt.version][l][n]) return
|
|
320
|
+
if (opt.skipEmpty && stats[opt.version][l][n].segmentsTranslated === 0) return
|
|
321
|
+
toDownload.push({ language: l, namespace: n });
|
|
322
|
+
});
|
|
323
|
+
} else if (opt.namespace) {
|
|
324
|
+
if (!stats[opt.version][l][opt.namespace]) return
|
|
325
|
+
if (opt.skipEmpty && stats[opt.version][l][opt.namespace].segmentsTranslated === 0) return
|
|
326
|
+
toDownload.push({ language: l, namespace: opt.namespace });
|
|
327
|
+
} else {
|
|
328
|
+
Object.keys(stats[opt.version][l]).forEach((n) => {
|
|
329
|
+
if (opt.skipEmpty && stats[opt.version][l][n].segmentsTranslated === 0) return
|
|
330
|
+
toDownload.push({ language: l, namespace: n });
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
await handlePull(opt, toDownload);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function download (opt) {
|
|
338
|
+
opt.format = opt.format || 'json';
|
|
339
|
+
if (!reversedFileExtensionsMap[opt.format]) {
|
|
340
|
+
throw new Error(`${opt.format} is not a valid format!`)
|
|
341
|
+
}
|
|
342
|
+
if (opt.skipEmpty === undefined) opt.skipEmpty = true;
|
|
343
|
+
opt.apiEndpoint = opt.apiEndpoint || 'https://api.locize.app';
|
|
344
|
+
opt.version = opt.version || 'latest';
|
|
345
|
+
opt.languageFolderPrefix = opt.languageFolderPrefix || '';
|
|
346
|
+
opt.path = opt.path || opt.target;
|
|
347
|
+
opt.pathMaskInterpolationPrefix = opt.pathMaskInterpolationPrefix || '{{';
|
|
348
|
+
opt.pathMaskInterpolationSuffix = opt.pathMaskInterpolationSuffix || '}}';
|
|
349
|
+
opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
|
|
350
|
+
opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
|
|
351
|
+
if (opt.overriddenOnly) {
|
|
352
|
+
opt.unpublished = true;
|
|
353
|
+
}
|
|
354
|
+
if (opt.unpublished && !opt.apiKey) {
|
|
355
|
+
throw new Error('Please provide also an api-key!')
|
|
356
|
+
}
|
|
357
|
+
if (opt.branch === '') {
|
|
358
|
+
throw new Error('The branch name seems invalid!')
|
|
359
|
+
}
|
|
360
|
+
if (opt.branch) {
|
|
361
|
+
const branches = await getBranches(opt);
|
|
362
|
+
let b;
|
|
363
|
+
if (isValidUuid(opt.branch)) b = branches.find((br) => br.id === opt.branch);
|
|
364
|
+
if (!b) b = branches.find((br) => br.name === opt.branch);
|
|
365
|
+
if (!b) {
|
|
366
|
+
throw new Error(`Branch ${opt.branch} not found!`)
|
|
367
|
+
}
|
|
368
|
+
opt.projectId = b.id;
|
|
369
|
+
opt.version = b.version;
|
|
370
|
+
await continueToDownload(opt);
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
await continueToDownload(opt);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
module.exports = download;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const filterNamespaces = (opt, nss) => {
|
|
4
|
+
if (opt.namespace) {
|
|
5
|
+
nss = nss.filter((ns) => ns.namespace === opt.namespace);
|
|
6
|
+
}
|
|
7
|
+
if (opt.namespaces && opt.namespaces.length > 0) {
|
|
8
|
+
nss = nss.filter((ns) => opt.namespaces.indexOf(ns.namespace) > -1);
|
|
9
|
+
}
|
|
10
|
+
return nss
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = filterNamespaces;
|