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
package/dist/cjs/lngs.js
ADDED
|
@@ -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;
|