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,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;