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.
Files changed (108) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +1 -1
  3. package/README.md +1 -0
  4. package/dist/cjs/add.js +90 -0
  5. package/{bin/locize → dist/cjs/cli.js} +390 -670
  6. package/dist/cjs/combineSubkeyPreprocessor.js +155 -0
  7. package/dist/cjs/convertToDesiredFormat.js +205 -0
  8. package/dist/cjs/convertToFlatFormat.js +231 -0
  9. package/dist/cjs/copyVersion.js +60 -0
  10. package/dist/cjs/createBranch.js +59 -0
  11. package/dist/cjs/deleteBranch.js +89 -0
  12. package/dist/cjs/deleteNamespace.js +37 -0
  13. package/dist/cjs/download.js +376 -0
  14. package/dist/cjs/filterNamespaces.js +13 -0
  15. package/dist/cjs/format.js +156 -0
  16. package/dist/cjs/formats.js +33 -0
  17. package/dist/cjs/get.js +66 -0
  18. package/dist/cjs/getBranches.js +37 -0
  19. package/dist/cjs/getJob.js +37 -0
  20. package/dist/cjs/getProjectStats.js +37 -0
  21. package/dist/cjs/getRemoteLanguages.js +38 -0
  22. package/dist/cjs/getRemoteNamespace.js +125 -0
  23. package/dist/cjs/index.js +37 -0
  24. package/dist/cjs/isValidUuid.js +6 -0
  25. package/dist/cjs/lngs.js +215 -0
  26. package/dist/cjs/mapLimit.js +22 -0
  27. package/dist/cjs/mergeBranch.js +80 -0
  28. package/dist/cjs/migrate.js +239 -0
  29. package/dist/cjs/missing.js +162 -0
  30. package/dist/cjs/package.json +5 -0
  31. package/{parseLocalLanguage.js → dist/cjs/parseLocalLanguage.js} +135 -142
  32. package/dist/cjs/parseLocalLanguages.js +18 -0
  33. package/dist/cjs/parseLocalReference.js +11 -0
  34. package/dist/cjs/publishVersion.js +42 -0
  35. package/dist/cjs/removeUndefinedFromArrays.js +19 -0
  36. package/dist/cjs/removeVersion.js +42 -0
  37. package/dist/cjs/request.js +66 -0
  38. package/dist/cjs/shouldUnflatten.js +21 -0
  39. package/dist/cjs/sortFlatResources.js +13 -0
  40. package/dist/cjs/sync.js +772 -0
  41. package/dist/cjs/unflatten.js +81 -0
  42. package/dist/esm/add.js +88 -0
  43. package/dist/esm/cli.js +1020 -0
  44. package/{combineSubkeyPreprocessor.js → dist/esm/combineSubkeyPreprocessor.js} +70 -73
  45. package/dist/esm/convertToDesiredFormat.js +203 -0
  46. package/dist/esm/convertToFlatFormat.js +229 -0
  47. package/dist/esm/copyVersion.js +58 -0
  48. package/dist/esm/createBranch.js +57 -0
  49. package/dist/esm/deleteBranch.js +87 -0
  50. package/dist/esm/deleteNamespace.js +35 -0
  51. package/dist/esm/download.js +374 -0
  52. package/{filterNamespaces.js → dist/esm/filterNamespaces.js} +4 -4
  53. package/dist/esm/format.js +154 -0
  54. package/{formats.js → dist/esm/formats.js} +7 -11
  55. package/dist/esm/get.js +64 -0
  56. package/dist/esm/getBranches.js +35 -0
  57. package/dist/esm/getJob.js +35 -0
  58. package/dist/esm/getProjectStats.js +35 -0
  59. package/dist/esm/getRemoteLanguages.js +36 -0
  60. package/dist/esm/getRemoteNamespace.js +123 -0
  61. package/dist/esm/index.js +16 -0
  62. package/dist/esm/isValidUuid.js +4 -0
  63. package/dist/esm/lngs.js +213 -0
  64. package/dist/esm/mapLimit.js +20 -0
  65. package/dist/esm/mergeBranch.js +78 -0
  66. package/dist/esm/migrate.js +237 -0
  67. package/dist/esm/missing.js +160 -0
  68. package/dist/esm/parseLocalLanguage.js +194 -0
  69. package/dist/esm/parseLocalLanguages.js +16 -0
  70. package/dist/esm/parseLocalReference.js +9 -0
  71. package/dist/esm/publishVersion.js +40 -0
  72. package/{removeUndefinedFromArrays.js → dist/esm/removeUndefinedFromArrays.js} +5 -5
  73. package/dist/esm/removeVersion.js +40 -0
  74. package/dist/esm/request.js +64 -0
  75. package/{shouldUnflatten.js → dist/esm/shouldUnflatten.js} +7 -7
  76. package/dist/esm/sortFlatResources.js +11 -0
  77. package/dist/esm/sync.js +770 -0
  78. package/{unflatten.js → dist/esm/unflatten.js} +36 -34
  79. package/package.json +39 -18
  80. package/rollup.config.js +57 -0
  81. package/add.js +0 -105
  82. package/convertToDesiredFormat.js +0 -268
  83. package/convertToFlatFormat.js +0 -322
  84. package/copyVersion.js +0 -69
  85. package/createBranch.js +0 -61
  86. package/deleteBranch.js +0 -97
  87. package/deleteNamespace.js +0 -39
  88. package/download.js +0 -516
  89. package/format.js +0 -206
  90. package/get.js +0 -81
  91. package/getBranches.js +0 -40
  92. package/getJob.js +0 -40
  93. package/getProjectStats.js +0 -40
  94. package/getRemoteLanguages.js +0 -40
  95. package/getRemoteNamespace.js +0 -122
  96. package/index.js +0 -9
  97. package/isValidUuid.js +0 -2
  98. package/lngs.json +0 -211
  99. package/mergeBranch.js +0 -102
  100. package/migrate.js +0 -314
  101. package/missing.js +0 -169
  102. package/parseLocalLanguages.js +0 -22
  103. package/parseLocalReference.js +0 -10
  104. package/publishVersion.js +0 -64
  105. package/removeVersion.js +0 -64
  106. package/request.js +0 -64
  107. package/sortFlatResources.js +0 -9
  108. 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
- module.exports = filterNamespaces
11
+ export { filterNamespaces as default };