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