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,215 @@
1
+ 'use strict';
2
+
3
+ const lngCodes = [
4
+ 'dev',
5
+ 'ach',
6
+ 'arn',
7
+ 'ast',
8
+ 'cgg',
9
+ 'csb',
10
+ 'dz',
11
+ 'es_ar',
12
+ 'es-ar',
13
+ 'fil',
14
+ 'fur',
15
+ 'gun',
16
+ 'jbo',
17
+ 'mai',
18
+ 'mfe',
19
+ 'mnk',
20
+ 'nah',
21
+ 'nap',
22
+ 'nso',
23
+ 'pap',
24
+ 'pms',
25
+ 'pt_br',
26
+ 'pt-br',
27
+ 'sah',
28
+ 'sco',
29
+ 'son',
30
+ 'ab',
31
+ 'aa',
32
+ 'af',
33
+ 'ak',
34
+ 'sq',
35
+ 'am',
36
+ 'ar',
37
+ 'an',
38
+ 'hy',
39
+ 'as',
40
+ 'av',
41
+ 'ae',
42
+ 'ay',
43
+ 'az',
44
+ 'bm',
45
+ 'ba',
46
+ 'eu',
47
+ 'be',
48
+ 'bn',
49
+ 'bh',
50
+ 'bi',
51
+ 'bs',
52
+ 'br',
53
+ 'bg',
54
+ 'my',
55
+ 'ca',
56
+ 'ch',
57
+ 'ce',
58
+ 'ny',
59
+ 'zh',
60
+ 'cv',
61
+ 'kw',
62
+ 'co',
63
+ 'cr',
64
+ 'hr',
65
+ 'cs',
66
+ 'da',
67
+ 'dv',
68
+ 'nl',
69
+ 'en',
70
+ 'eo',
71
+ 'et',
72
+ 'ee',
73
+ 'fo',
74
+ 'fj',
75
+ 'fi',
76
+ 'fr',
77
+ 'ff',
78
+ 'gl',
79
+ 'ka',
80
+ 'de',
81
+ 'el',
82
+ 'gn',
83
+ 'gu',
84
+ 'ht',
85
+ 'ha',
86
+ 'he',
87
+ 'hz',
88
+ 'hi',
89
+ 'ho',
90
+ 'hu',
91
+ 'ia',
92
+ 'id',
93
+ 'ie',
94
+ 'ga',
95
+ 'ig',
96
+ 'ik',
97
+ 'io',
98
+ 'is',
99
+ 'it',
100
+ 'iu',
101
+ 'ja',
102
+ 'jv',
103
+ 'kl',
104
+ 'kn',
105
+ 'kr',
106
+ 'ks',
107
+ 'kk',
108
+ 'km',
109
+ 'ki',
110
+ 'rw',
111
+ 'ky',
112
+ 'kv',
113
+ 'kg',
114
+ 'ko',
115
+ 'ku',
116
+ 'ckb',
117
+ 'kj',
118
+ 'la',
119
+ 'lb',
120
+ 'lg',
121
+ 'li',
122
+ 'ln',
123
+ 'lo',
124
+ 'lt',
125
+ 'lu',
126
+ 'lv',
127
+ 'gv',
128
+ 'mk',
129
+ 'mg',
130
+ 'ms',
131
+ 'ml',
132
+ 'mt',
133
+ 'mi',
134
+ 'mr',
135
+ 'mh',
136
+ 'mn',
137
+ 'na',
138
+ 'nv',
139
+ 'nb',
140
+ 'nd',
141
+ 'ne',
142
+ 'ng',
143
+ 'nn',
144
+ 'no',
145
+ 'ii',
146
+ 'nr',
147
+ 'oc',
148
+ 'oj',
149
+ 'cu',
150
+ 'om',
151
+ 'or',
152
+ 'os',
153
+ 'pa',
154
+ 'pi',
155
+ 'fa',
156
+ 'pl',
157
+ 'ps',
158
+ 'pt',
159
+ 'qu',
160
+ 'rm',
161
+ 'rn',
162
+ 'ro',
163
+ 'ru',
164
+ 'sa',
165
+ 'sc',
166
+ 'sd',
167
+ 'se',
168
+ 'sm',
169
+ 'sg',
170
+ 'sr',
171
+ 'gd',
172
+ 'sn',
173
+ 'si',
174
+ 'sk',
175
+ 'sl',
176
+ 'so',
177
+ 'st',
178
+ 'es',
179
+ 'su',
180
+ 'sw',
181
+ 'ss',
182
+ 'sv',
183
+ 'ta',
184
+ 'te',
185
+ 'tg',
186
+ 'th',
187
+ 'ti',
188
+ 'bo',
189
+ 'tk',
190
+ 'tl',
191
+ 'tn',
192
+ 'to',
193
+ 'tr',
194
+ 'ts',
195
+ 'tt',
196
+ 'tw',
197
+ 'ty',
198
+ 'ug',
199
+ 'uk',
200
+ 'ur',
201
+ 'uz',
202
+ 've',
203
+ 'vi',
204
+ 'vo',
205
+ 'wa',
206
+ 'cy',
207
+ 'wo',
208
+ 'fy',
209
+ 'xh',
210
+ 'yi',
211
+ 'yo',
212
+ 'za'
213
+ ];
214
+
215
+ module.exports = lngCodes;
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ async function mapLimit (arr, limit, asyncFn) {
4
+ const ret = [];
5
+ let i = 0;
6
+ let active = 0;
7
+ return new Promise((resolve, reject) => {
8
+ function next () {
9
+ if (i === arr.length && active === 0) return resolve(ret)
10
+ while (active < limit && i < arr.length) {
11
+ const cur = i++;
12
+ active++;
13
+ Promise.resolve(asyncFn(arr[cur], cur, arr))
14
+ .then((res) => { ret[cur] = res; active--; next(); })
15
+ .catch(reject);
16
+ }
17
+ }
18
+ next();
19
+ })
20
+ }
21
+
22
+ module.exports = mapLimit;
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ var colors = require('colors');
4
+ var request = require('./request.js');
5
+ var getBranches = require('./getBranches.js');
6
+ var isValidUuid = require('./isValidUuid.js');
7
+ var getJob = require('./getJob.js');
8
+
9
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
10
+
11
+ const merge = async (opt) => {
12
+ const queryParams = new URLSearchParams();
13
+ if (opt.delete) {
14
+ queryParams.append('delete', 'true');
15
+ }
16
+ const queryString = queryParams.size > 0 ? '?' + queryParams.toString() : '';
17
+ const { res, obj, err } = await request(opt.apiEndpoint + '/branch/merge/' + opt.branch + queryString, {
18
+ method: 'post',
19
+ headers: {
20
+ Authorization: opt.apiKey
21
+ }
22
+ });
23
+ if (err || (obj && (obj.errorMessage || obj.message))) {
24
+ console.log(colors.red('merging branch failed...'));
25
+ if (err) {
26
+ console.error(colors.red(err.message));
27
+ throw err
28
+ }
29
+ if (obj && (obj.errorMessage || obj.message)) {
30
+ console.error(colors.red((obj.errorMessage || obj.message)));
31
+ throw new Error((obj.errorMessage || obj.message))
32
+ }
33
+ }
34
+ if (res.status === 404) {
35
+ console.error(colors.yellow(res.statusText + ' (' + res.status + ')'));
36
+ return null
37
+ }
38
+ if (res.status >= 300) {
39
+ console.error(colors.red(res.statusText + ' (' + res.status + ')'));
40
+ throw new Error(res.statusText + ' (' + res.status + ')')
41
+ }
42
+ if (!obj || !obj.jobId) {
43
+ console.error(colors.red('No jobId! Something went wrong!'));
44
+ throw new Error('No jobId! Something went wrong!')
45
+ }
46
+ let job;
47
+ while (true) {
48
+ job = await getJob({
49
+ apiEndpoint: opt.apiEndpoint,
50
+ apiKey: opt.apiKey,
51
+ projectId: opt.branch
52
+ }, obj.jobId);
53
+ if (job && !job.timeouted) {
54
+ await sleep(2000);
55
+ continue
56
+ }
57
+ if (job && job.timeouted) {
58
+ console.error(colors.red('Job timeouted!'));
59
+ throw new Error('Job timeouted!')
60
+ }
61
+ break
62
+ }
63
+ console.log(colors.green('merging branch successful'));
64
+ };
65
+
66
+ // handleError removed (unused)
67
+
68
+ const mergeBranch = async (opt) => {
69
+ const branches = await getBranches(opt);
70
+ let b;
71
+ if (isValidUuid(opt.branch)) b = branches.find((br) => br.id === opt.branch);
72
+ if (!b) b = branches.find((br) => br.name === opt.branch);
73
+ if (!b) {
74
+ throw new Error(`Branch ${opt.branch} not found!`)
75
+ }
76
+ opt.branch = b.id;
77
+ await merge(opt);
78
+ };
79
+
80
+ module.exports = mergeBranch;
@@ -0,0 +1,239 @@
1
+ 'use strict';
2
+
3
+ var fs = require('node:fs');
4
+ var path = require('node:path');
5
+ var flatten = require('flat');
6
+ var colors = require('colors');
7
+ var request = require('./request.js');
8
+ var getRemoteLanguages = require('./getRemoteLanguages.js');
9
+ var os = require('node:os');
10
+ var mapLimit = require('./mapLimit.js');
11
+
12
+ const getDirectories = (srcpath) => {
13
+ return fs.readdirSync(srcpath).filter(function (file) {
14
+ return fs.statSync(path.join(srcpath, file)).isDirectory()
15
+ })
16
+ };
17
+
18
+ const getFiles = (srcpath) => {
19
+ return fs.readdirSync(srcpath).filter(function (file) {
20
+ return !fs.statSync(path.join(srcpath, file)).isDirectory()
21
+ })
22
+ };
23
+
24
+ const load = async (namespaces) => {
25
+ await Promise.all(namespaces.map(async (ns) => {
26
+ try {
27
+ const data = await fs.promises.readFile(ns.path, 'utf8');
28
+ ns.value = flatten(JSON.parse(data));
29
+ } catch (err) {
30
+ console.error(colors.red(err.stack));
31
+ ns.value = {};
32
+ }
33
+ }));
34
+ return namespaces
35
+ };
36
+
37
+ const parseLanguage = async (p) => {
38
+ const dirs = getDirectories(p);
39
+ const namespaces = [];
40
+ dirs.forEach((lng) => {
41
+ const files = getFiles(path.join(p, lng));
42
+ files.forEach((file) => {
43
+ if (path.extname(file) !== '.json') return
44
+ namespaces.push({
45
+ language: lng,
46
+ namespace: path.basename(file, '.json'),
47
+ path: path.join(p, lng, file)
48
+ });
49
+ });
50
+ });
51
+ return await load(namespaces)
52
+ };
53
+
54
+ const transfer = async (opt, ns) => {
55
+ let url = `${opt.apiEndpoint}/update/{{projectId}}/{{version}}/{{lng}}/{{ns}}`
56
+ .replace('{{projectId}}', opt.projectId)
57
+ .replace('{{ver}}', opt.version)
58
+ .replace('{{version}}', opt.version)
59
+ .replace('{{language}}', ns.language)
60
+ .replace('{{lng}}', ns.language)
61
+ .replace('{{ns}}', ns.namespace)
62
+ .replace('{{namespace}}', ns.namespace);
63
+
64
+ console.log(colors.yellow(`transfering ${opt.version}/${ns.language}/${ns.namespace}...`));
65
+
66
+ if (!opt.replace) url = url.replace('/update/', '/missing/');
67
+
68
+ const data = ns.value;
69
+ const keysToSend = Object.keys(data).length;
70
+ if (keysToSend === 0) return
71
+
72
+ const payloadKeysLimit = 1000;
73
+
74
+ async function send (d, so, isFirst, isRetrying = false) {
75
+ const queryParams = new URLSearchParams();
76
+ if (so) {
77
+ queryParams.append('omitstatsgeneration', 'true');
78
+ }
79
+ if (isFirst && opt.replace) {
80
+ queryParams.append('replace', 'true');
81
+ }
82
+ const queryString = queryParams.size > 0 ? '?' + queryParams.toString() : '';
83
+ try {
84
+ const { res, obj } = await request(url + queryString, {
85
+ method: 'post',
86
+ body: d,
87
+ headers: {
88
+ Authorization: opt.apiKey
89
+ }
90
+ });
91
+ if (url.indexOf('/missing/') > -1 && res.status === 412) {
92
+ console.log(colors.green(`transfered ${Object.keys(d).length} keys ${opt.version}/${ns.language}/${ns.namespace} (but all keys already existed)...`));
93
+ return
94
+ }
95
+ if (res.status === 504 && !isRetrying) {
96
+ await new Promise(resolve => setTimeout(resolve, 3000));
97
+ return send(d, so, isFirst, true)
98
+ }
99
+ if (res.status >= 300 && res.status !== 412) {
100
+ if (obj && (obj.errorMessage || obj.message)) {
101
+ throw new Error((obj.errorMessage || obj.message))
102
+ }
103
+ throw new Error(res.statusText + ' (' + res.status + ')')
104
+ }
105
+ console.log(colors.green(`transfered ${Object.keys(d).length} keys ${opt.version}/${ns.language}/${ns.namespace}...`));
106
+ } catch (err) {
107
+ console.log(colors.red(`transfer failed for ${Object.keys(d).length} keys ${opt.version}/${ns.language}/${ns.namespace}...`));
108
+ throw err
109
+ }
110
+ }
111
+
112
+ if (keysToSend > payloadKeysLimit) {
113
+ const keysInObj = Object.keys(data);
114
+ let isFirst = true;
115
+ while (keysInObj.length > payloadKeysLimit) {
116
+ const pagedData = {};
117
+ keysInObj.splice(0, payloadKeysLimit).forEach((k) => { pagedData[k] = data[k]; });
118
+ const hasMoreKeys = keysInObj.length > 0;
119
+ await send(pagedData, hasMoreKeys, isFirst);
120
+ isFirst = false;
121
+ }
122
+ if (keysInObj.length === 0) return
123
+ const finalPagedData = {};
124
+ keysInObj.splice(0, keysInObj.length).forEach((k) => { finalPagedData[k] = data[k]; });
125
+ await send(finalPagedData, false, isFirst);
126
+ return
127
+ }
128
+
129
+ await send(data, false, true);
130
+ };
131
+
132
+ const upload = async (opt, nss) => {
133
+ const concurrency = os.cpus().length;
134
+ if (!opt.referenceLanguage) {
135
+ await mapLimit(nss, concurrency, async (ns) => transfer(opt, ns));
136
+ return
137
+ }
138
+
139
+ const nssRefLng = nss.filter((n) => n.language === opt.referenceLanguage);
140
+ const nssNonRefLng = nss.filter((n) => n.language !== opt.referenceLanguage);
141
+
142
+ // Reference language first, then others, but each group in parallel
143
+ await mapLimit(nssRefLng, concurrency, async (ns) => transfer(opt, ns));
144
+ await mapLimit(nssNonRefLng, concurrency, async (ns) => transfer(opt, ns));
145
+ };
146
+
147
+ const addLanguage = async (opt, l) => {
148
+ const url = opt.apiEndpoint + '/language/' + opt.projectId + '/' + l;
149
+ try {
150
+ const { res } = await request(url, {
151
+ method: 'post',
152
+ headers: {
153
+ Authorization: opt.apiKey
154
+ }
155
+ });
156
+ if (res.status >= 300 && res.status !== 412) throw new Error(res.statusText + ' (' + res.status + ')')
157
+ console.log(colors.green(`added language ${l}...`));
158
+ } catch (err) {
159
+ console.log(colors.red(`failed to add language ${l}...`));
160
+ throw err
161
+ }
162
+ };
163
+
164
+ const migrate = async (opt) => {
165
+ if (opt.format !== 'json') {
166
+ throw new Error(`Format ${opt.format} is not accepted!`)
167
+ }
168
+
169
+ opt.apiEndpoint = opt.apiEndpoint || 'https://api.locize.app';
170
+
171
+ if (opt.language) {
172
+ const files = getFiles(opt.path);
173
+ const namespaces = files.map((file) => ({
174
+ language: opt.language,
175
+ namespace: path.basename(file, '.json'),
176
+ path: path.join(opt.path, file)
177
+ }));
178
+ let nss;
179
+ try {
180
+ nss = await load(namespaces);
181
+ } catch (err) {
182
+ console.error(colors.red(err.stack));
183
+ process.exit(1);
184
+ }
185
+ try {
186
+ await upload(opt, nss);
187
+ console.log(colors.green('FINISHED'));
188
+ } catch (err) {
189
+ console.error(colors.red(err.stack));
190
+ process.exit(1);
191
+ }
192
+ return
193
+ }
194
+
195
+ if (opt.parseLanguage) {
196
+ let nss;
197
+ try {
198
+ nss = await parseLanguage(opt.path);
199
+ } catch (err) {
200
+ console.error(colors.red(err.stack));
201
+ process.exit(1);
202
+ }
203
+ let remoteLanguages;
204
+ try {
205
+ remoteLanguages = await getRemoteLanguages(opt);
206
+ } catch (err) {
207
+ console.error(colors.red(err.stack));
208
+ process.exit(1);
209
+ }
210
+ const localLanguages = [];
211
+ nss.forEach((n) => {
212
+ if (localLanguages.indexOf(n.language) < 0) localLanguages.push(n.language);
213
+ });
214
+ const notExistingLanguages = localLanguages.filter((l) => remoteLanguages.indexOf(l) < 0);
215
+ if (notExistingLanguages.length === 0) {
216
+ try {
217
+ await upload(opt, nss);
218
+ console.log(colors.green('FINISHED'));
219
+ } catch (err) {
220
+ console.error(colors.red(err.stack));
221
+ process.exit(1);
222
+ }
223
+ return
224
+ }
225
+ try {
226
+ for (const l of notExistingLanguages) {
227
+ await addLanguage(opt, l);
228
+ }
229
+ await new Promise(resolve => setTimeout(resolve, 5000));
230
+ await upload(opt, nss);
231
+ console.log(colors.green('FINISHED'));
232
+ } catch (err) {
233
+ console.error(colors.red(err.stack));
234
+ process.exit(1);
235
+ }
236
+ }
237
+ };
238
+
239
+ module.exports = migrate;
@@ -0,0 +1,162 @@
1
+ 'use strict';
2
+
3
+ var colors = require('colors');
4
+ var path = require('node:path');
5
+ var request = require('./request.js');
6
+ var formats = require('./formats.js');
7
+ var getRemoteLanguages = require('./getRemoteLanguages.js');
8
+ var parseLocalReference = require('./parseLocalReference.js');
9
+ var parseLocalLanguages = require('./parseLocalLanguages.js');
10
+ var getRemoteNamespace = require('./getRemoteNamespace.js');
11
+ var os = require('node:os');
12
+ var mapLimit = require('./mapLimit.js');
13
+
14
+ const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
15
+
16
+ const compareNamespace = (local, remote) => {
17
+ const diff = {
18
+ toAdd: []
19
+ };
20
+ local = local || {};
21
+ remote = remote || {};
22
+ Object.keys(local).forEach((k) => {
23
+ if (remote[k] === '' && local[k] === '') return
24
+ if (!remote[k]) {
25
+ diff.toAdd.push(k);
26
+ }
27
+ });
28
+ return diff
29
+ };
30
+
31
+ const compareNamespaces = async (opt, localNamespaces) => {
32
+ return await Promise.all(localNamespaces.map(async (ns) => {
33
+ const { result: remoteNamespace } = await getRemoteNamespace(opt, ns.language, ns.namespace);
34
+ const diff = compareNamespace(ns.content, remoteNamespace);
35
+ ns.diff = diff;
36
+ ns.remoteContent = remoteNamespace;
37
+ return ns
38
+ }))
39
+ };
40
+
41
+ const saveMissing = async (opt, lng, ns) => {
42
+ const data = {};
43
+ ns.diff.toAdd.forEach((k) => { data[k] = ns.content[k]; });
44
+ if (Object.keys(data).length === 0 || opt.dry) return
45
+ const payloadKeysLimit = 1000;
46
+ async function send (d, isRetrying = false) {
47
+ const { res, obj } = await request(opt.apiEndpoint + '/missing/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns.namespace, {
48
+ method: 'post',
49
+ body: d,
50
+ headers: {
51
+ Authorization: opt.apiKey
52
+ }
53
+ });
54
+ if (res.status === 504 && !isRetrying) {
55
+ await new Promise(resolve => setTimeout(resolve, 3000));
56
+ return send(d, true)
57
+ }
58
+ if (res.status >= 300 && res.status !== 412) {
59
+ if (obj && (obj.errorMessage || obj.message)) {
60
+ throw new Error((obj.errorMessage || obj.message))
61
+ }
62
+ throw new Error(res.statusText + ' (' + res.status + ')')
63
+ }
64
+ await new Promise(resolve => setTimeout(resolve, 1000));
65
+ }
66
+ if (Object.keys(data).length > payloadKeysLimit) {
67
+ const keysInObj = Object.keys(data);
68
+ while (keysInObj.length > payloadKeysLimit) {
69
+ const pagedData = {};
70
+ keysInObj.splice(0, payloadKeysLimit).forEach((k) => { pagedData[k] = data[k]; });
71
+ await send(pagedData);
72
+ }
73
+ if (keysInObj.length === 0) return
74
+ const finalPagedData = {};
75
+ keysInObj.splice(0, keysInObj.length).forEach((k) => { finalPagedData[k] = data[k]; });
76
+ await send(finalPagedData);
77
+ return
78
+ }
79
+ await send(data);
80
+ };
81
+
82
+ const handleError = (err) => {
83
+ if (err) {
84
+ console.error(colors.red(err.stack));
85
+ process.exit(1);
86
+ }
87
+ };
88
+
89
+ const handleMissing = async (opt, localNamespaces) => {
90
+ if (!localNamespaces || localNamespaces.length === 0) {
91
+ handleError(new Error('No local namespaces found!'));
92
+ return
93
+ }
94
+ let compared;
95
+ try {
96
+ compared = await compareNamespaces(opt, localNamespaces);
97
+ } catch (err) {
98
+ handleError(err);
99
+ return
100
+ }
101
+ const concurrency = os.cpus().length;
102
+ await mapLimit(compared, concurrency, async (ns) => {
103
+ if (ns.diff.toAdd.length > 0) {
104
+ console.log(colors.green(`adding ${ns.diff.toAdd.length} keys in ${ns.language}/${ns.namespace}...`));
105
+ if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAdd.join(', ')} in ${ns.language}/${ns.namespace}...`));
106
+ }
107
+ try {
108
+ await saveMissing(opt, ns.language, ns);
109
+ } catch (err) {
110
+ handleError(err);
111
+ // Don't return here, continue with others
112
+ }
113
+ });
114
+ console.log(colors.green('FINISHED'));
115
+ };
116
+
117
+ const missing = async (opt) => {
118
+ if (!reversedFileExtensionsMap[opt.format]) {
119
+ handleError(new Error(`${opt.format} is not a valid format!`));
120
+ return
121
+ }
122
+ if (opt.namespace && opt.namespace.indexOf(',') > 0) {
123
+ opt.namespaces = opt.namespace.split(',');
124
+ delete opt.namespace;
125
+ }
126
+ opt.pathMaskInterpolationPrefix = opt.pathMaskInterpolationPrefix || '{{';
127
+ opt.pathMaskInterpolationSuffix = opt.pathMaskInterpolationSuffix || '}}';
128
+ opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
129
+ opt.languageFolderPrefix = opt.languageFolderPrefix || '';
130
+ opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
131
+ let remoteLanguages;
132
+ try {
133
+ remoteLanguages = await getRemoteLanguages(opt);
134
+ } catch (err) {
135
+ handleError(err);
136
+ return
137
+ }
138
+ if (opt.referenceLanguageOnly && opt.language && opt.referenceLanguage !== opt.language) {
139
+ opt.referenceLanguage = opt.language;
140
+ }
141
+ if (opt.referenceLanguageOnly) {
142
+ let localNamespaces;
143
+ try {
144
+ localNamespaces = await parseLocalReference(opt);
145
+ } catch (err) {
146
+ handleError(err);
147
+ return
148
+ }
149
+ await handleMissing(opt, localNamespaces);
150
+ return
151
+ }
152
+ let localNamespaces;
153
+ try {
154
+ localNamespaces = await parseLocalLanguages(opt, remoteLanguages);
155
+ } catch (err) {
156
+ handleError(err);
157
+ return
158
+ }
159
+ await handleMissing(opt, localNamespaces);
160
+ };
161
+
162
+ module.exports = missing;