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/sync.js
ADDED
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('node:fs');
|
|
4
|
+
var path = require('node:path');
|
|
5
|
+
var mkdirp = require('mkdirp');
|
|
6
|
+
var rimraf = require('rimraf');
|
|
7
|
+
var colors = require('colors');
|
|
8
|
+
var request = require('./request.js');
|
|
9
|
+
var flatten = require('flat');
|
|
10
|
+
var cloneDeep = require('lodash.clonedeep');
|
|
11
|
+
var getRemoteNamespace = require('./getRemoteNamespace.js');
|
|
12
|
+
var getRemoteLanguages = require('./getRemoteLanguages.js');
|
|
13
|
+
var convertToDesiredFormat = require('./convertToDesiredFormat.js');
|
|
14
|
+
var parseLocalLanguages = require('./parseLocalLanguages.js');
|
|
15
|
+
var parseLocalReference = require('./parseLocalReference.js');
|
|
16
|
+
var formats = require('./formats.js');
|
|
17
|
+
var deleteNamespace = require('./deleteNamespace.js');
|
|
18
|
+
var getProjectStats = require('./getProjectStats.js');
|
|
19
|
+
var locize2xcstrings = require('locize-xcstrings/cjs/locize2xcstrings');
|
|
20
|
+
var getBranches = require('./getBranches.js');
|
|
21
|
+
var isValidUuid = require('./isValidUuid.js');
|
|
22
|
+
var os = require('node:os');
|
|
23
|
+
var lngs = require('./lngs.js');
|
|
24
|
+
|
|
25
|
+
const reversedFileExtensionsMap = formats.reversedFileExtensionsMap;
|
|
26
|
+
|
|
27
|
+
// concurrency-limited map: returns array of results
|
|
28
|
+
async function pMapLimit (items, limit, iterator) {
|
|
29
|
+
if (!Array.isArray(items)) items = Array.from(items || []);
|
|
30
|
+
const results = new Array(items.length);
|
|
31
|
+
let i = 0;
|
|
32
|
+
const workers = new Array(Math.min(limit || 1, items.length)).fill(0).map(async () => {
|
|
33
|
+
while (true) {
|
|
34
|
+
const idx = i++;
|
|
35
|
+
if (idx >= items.length) break
|
|
36
|
+
results[idx] = await iterator(items[idx], idx);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
await Promise.all(workers);
|
|
40
|
+
return results
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// concurrency-limited each (ignore results)
|
|
44
|
+
async function pEachLimit (items, limit, iterator) {
|
|
45
|
+
await pMapLimit(items, limit, async (item, idx) => {
|
|
46
|
+
await iterator(item, idx);
|
|
47
|
+
return null
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// run array of functions returning Promises in series
|
|
52
|
+
async function pSeries (tasks) {
|
|
53
|
+
const results = [];
|
|
54
|
+
for (const t of tasks) {
|
|
55
|
+
// t may be a function that accepts no args and returns a Promise
|
|
56
|
+
results.push(await t());
|
|
57
|
+
}
|
|
58
|
+
return results
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const getDirectories = (srcpath) => {
|
|
62
|
+
return fs.readdirSync(srcpath).filter((file) => {
|
|
63
|
+
return fs.statSync(path.join(srcpath, file)).isDirectory()
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function getInfosInUrl (download) {
|
|
68
|
+
const splitted = download.key.split('/');
|
|
69
|
+
const version = splitted[download.isPrivate ? 2 : 1];
|
|
70
|
+
const language = splitted[download.isPrivate ? 3 : 2];
|
|
71
|
+
const namespace = splitted[download.isPrivate ? 4 : 3];
|
|
72
|
+
return { version, language, namespace }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const compareNamespace = (local, remote, lastModifiedLocal, lastModifiedRemote) => {
|
|
76
|
+
const wasLastChangedRemote = lastModifiedLocal && lastModifiedRemote && lastModifiedLocal.getTime() < lastModifiedRemote.getTime();
|
|
77
|
+
const diff = {
|
|
78
|
+
toAdd: [],
|
|
79
|
+
toAddLocally: [],
|
|
80
|
+
toUpdate: [],
|
|
81
|
+
toUpdateLocally: [],
|
|
82
|
+
toRemove: [],
|
|
83
|
+
toRemoveLocally: []
|
|
84
|
+
};
|
|
85
|
+
local = local || {};
|
|
86
|
+
remote = remote || {};
|
|
87
|
+
Object.keys(local).forEach((k) => {
|
|
88
|
+
if (remote[k] === '' && local[k] === '') return
|
|
89
|
+
if (!remote[k]) {
|
|
90
|
+
if (wasLastChangedRemote) {
|
|
91
|
+
diff.toRemoveLocally.push(k); // will download later
|
|
92
|
+
} else {
|
|
93
|
+
diff.toAdd.push(k);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (
|
|
97
|
+
remote[k] && (
|
|
98
|
+
(typeof local[k] === 'object' && local[k] && local[k].value && remote[k] !== local[k].value) ||
|
|
99
|
+
(typeof local[k] !== 'object' && remote[k] !== local[k])
|
|
100
|
+
)
|
|
101
|
+
) {
|
|
102
|
+
if (wasLastChangedRemote) {
|
|
103
|
+
diff.toUpdateLocally.push(k); // will download later
|
|
104
|
+
} else {
|
|
105
|
+
diff.toUpdate.push(k);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
Object.keys(remote).forEach((k) => {
|
|
110
|
+
if (local[k] === '' && remote[k] === '') return
|
|
111
|
+
if (!local[k]) {
|
|
112
|
+
if (wasLastChangedRemote) {
|
|
113
|
+
diff.toAddLocally.push(k); // will download later
|
|
114
|
+
} else {
|
|
115
|
+
diff.toRemove.push(k);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return diff
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const doesDirectoryExist = (p) => {
|
|
123
|
+
let directoryExists = false;
|
|
124
|
+
try {
|
|
125
|
+
directoryExists = fs.statSync(p).isDirectory();
|
|
126
|
+
} catch (e) {}
|
|
127
|
+
return directoryExists
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const getNamespaceNamesAvailableInReference = (opt, downloads) => {
|
|
131
|
+
const nsNames = [];
|
|
132
|
+
downloads.forEach((d) => {
|
|
133
|
+
const splitted = d.key.split('/');
|
|
134
|
+
const lng = splitted[2];
|
|
135
|
+
const ns = splitted[3];
|
|
136
|
+
if (lng === opt.referenceLanguage) {
|
|
137
|
+
nsNames.push(ns);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return nsNames
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const ensureAllNamespacesInLanguages = (opt, remoteLanguages, downloads) => {
|
|
144
|
+
const namespaces = getNamespaceNamesAvailableInReference(opt, downloads);
|
|
145
|
+
|
|
146
|
+
remoteLanguages.forEach((lng) => {
|
|
147
|
+
namespaces.forEach((n) => {
|
|
148
|
+
const found = downloads.find((d) => d.key === `${opt.projectId}/${opt.version}/${lng}/${n}`);
|
|
149
|
+
if (!found) {
|
|
150
|
+
downloads.push({
|
|
151
|
+
key: `${opt.projectId}/${opt.version}/${lng}/${n}`,
|
|
152
|
+
lastModified: '1960-01-01T00:00:00.000Z',
|
|
153
|
+
size: 0,
|
|
154
|
+
url: `${opt.apiEndpoint}/${opt.projectId}/${opt.version}/${lng}/${n}`
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const cleanupLanguages = (opt, remoteLanguages) => {
|
|
162
|
+
if (opt.pathMask.lastIndexOf(path.sep) < 0) return
|
|
163
|
+
const dirs = getDirectories(opt.path).filter((dir) => dir.indexOf('.') !== 0);
|
|
164
|
+
if (!opt.language && (!opt.languages || opt.languages.length === 0) && !opt.namespace && !opt.namespaces) {
|
|
165
|
+
dirs
|
|
166
|
+
.filter((lng) => {
|
|
167
|
+
const lMask = `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`;
|
|
168
|
+
const startLIndex = opt.pathMask.indexOf(lMask);
|
|
169
|
+
const restLMask = lng.substring((startLIndex || 0) + lMask.length);
|
|
170
|
+
lng = lng.substring(startLIndex || 0, lng.lastIndexOf(restLMask));
|
|
171
|
+
|
|
172
|
+
return lng !== opt.referenceLanguage &&
|
|
173
|
+
!!lngs.find((c) => lng === c || lng.indexOf(c + '-') === 0)
|
|
174
|
+
})
|
|
175
|
+
.forEach((lng) => {
|
|
176
|
+
const filledLngMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, lng);
|
|
177
|
+
let lngPath;
|
|
178
|
+
if (filledLngMask.lastIndexOf(path.sep) > 0) {
|
|
179
|
+
lngPath = filledLngMask.substring(0, filledLngMask.lastIndexOf(path.sep));
|
|
180
|
+
}
|
|
181
|
+
if (doesDirectoryExist(path.join(opt.path, lngPath, 'CVS'))) return // special hack for CVS
|
|
182
|
+
rimraf.rimraf.sync(path.join(opt.path, lngPath));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
remoteLanguages.forEach((lng) => {
|
|
186
|
+
if (opt.language && opt.language !== lng) return
|
|
187
|
+
if (opt.languages && opt.languages.length > 0 && opt.languages.indexOf(lng) < 0) return
|
|
188
|
+
const filledLngMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, lng);
|
|
189
|
+
let lngPath;
|
|
190
|
+
if (filledLngMask.lastIndexOf(path.sep) > 0) {
|
|
191
|
+
lngPath = filledLngMask.substring(0, filledLngMask.lastIndexOf(path.sep));
|
|
192
|
+
}
|
|
193
|
+
if (lngPath && lngPath.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`) < 0) mkdirp.mkdirp.sync(path.join(opt.path, lngPath));
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const backupDeleted = (opt, ns, now) => {
|
|
198
|
+
if (opt.dry || ns.diff.toRemove.length === 0) return
|
|
199
|
+
let m = now.getMonth() + 1;
|
|
200
|
+
if (m < 10) m = `0${m}`;
|
|
201
|
+
let d = now.getDate();
|
|
202
|
+
if (d < 10) d = `0${d}`;
|
|
203
|
+
let h = now.getHours();
|
|
204
|
+
if (h < 10) h = `0${h}`;
|
|
205
|
+
let mi = now.getMinutes();
|
|
206
|
+
if (mi < 10) mi = `0${mi}`;
|
|
207
|
+
let s = now.getSeconds();
|
|
208
|
+
if (s < 10) s = `0${s}`;
|
|
209
|
+
const currentBackupPath = path.join(opt.backupDeletedPath, `${now.getFullYear()}${m}${d}-${h}${mi}${s}`);
|
|
210
|
+
mkdirp.mkdirp.sync(currentBackupPath);
|
|
211
|
+
const removingRemote = ns.diff.toRemove.reduce((prev, k) => {
|
|
212
|
+
prev[k] = ns.remoteContent[k];
|
|
213
|
+
return prev
|
|
214
|
+
}, {});
|
|
215
|
+
mkdirp.mkdirp.sync(path.join(currentBackupPath, ns.language));
|
|
216
|
+
const content = JSON.stringify(removingRemote, null, 2);
|
|
217
|
+
const fileContent = (opt.format !== 'xlsx' && !content.endsWith('\n')) ? (content + '\n') : content;
|
|
218
|
+
fs.writeFileSync(path.join(currentBackupPath, ns.language, `${ns.namespace}.json`), fileContent);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
async function getDownloads (opt) {
|
|
222
|
+
// replicates earlier behavior but returns a Promise that resolves to downloads array
|
|
223
|
+
if (!opt.unpublished) {
|
|
224
|
+
const url = opt.apiEndpoint + '/download/' + opt.projectId + '/' + opt.version;
|
|
225
|
+
const headers = opt.apiKey ? { Authorization: opt.apiKey } : undefined;
|
|
226
|
+
let { res, obj } = await request(url, { method: 'get', headers });
|
|
227
|
+
if (res.status >= 300) {
|
|
228
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
229
|
+
if (res.statusText && res.status) {
|
|
230
|
+
throw new Error(res.statusText + ' (' + res.status + ') | ' + (obj.errorMessage || obj.message))
|
|
231
|
+
}
|
|
232
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
233
|
+
}
|
|
234
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
235
|
+
}
|
|
236
|
+
if (obj.length > 0) {
|
|
237
|
+
if (opt.skipEmpty) obj = obj.filter((d) => d.size > 2);
|
|
238
|
+
return obj
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const resStats = await getProjectStats(opt);
|
|
242
|
+
const stats = resStats;
|
|
243
|
+
if (!stats) throw new Error('Nothing found!')
|
|
244
|
+
if (!stats[opt.version]) throw new Error(`Version "${opt.version}" not found!`)
|
|
245
|
+
return obj
|
|
246
|
+
} else {
|
|
247
|
+
const stats = await getProjectStats(opt);
|
|
248
|
+
if (!stats) throw new Error('Nothing found!')
|
|
249
|
+
if (!stats[opt.version]) throw new Error(`Version "${opt.version}" not found!`)
|
|
250
|
+
|
|
251
|
+
const toDownload = [];
|
|
252
|
+
const lngsToCheck = opt.language ? [opt.language] : (opt.languages && opt.languages.length > 0) ? opt.languages : Object.keys(stats[opt.version]);
|
|
253
|
+
lngsToCheck.forEach((l) => {
|
|
254
|
+
if (opt.namespaces) {
|
|
255
|
+
opt.namespaces.forEach((n) => {
|
|
256
|
+
if (!stats[opt.version][l][n]) return
|
|
257
|
+
if (opt.skipEmpty && stats[opt.version][l][n].segmentsTranslated === 0) return
|
|
258
|
+
toDownload.push({
|
|
259
|
+
url: `${opt.apiEndpoint}/${opt.projectId}/${opt.version}/${l}/${n}`,
|
|
260
|
+
key: `${opt.projectId}/${opt.version}/${l}/${n}`,
|
|
261
|
+
lastModified: '1960-01-01T00:00:00.000Z',
|
|
262
|
+
size: 0
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
} else if (opt.namespace) {
|
|
266
|
+
if (!stats[opt.version][l][opt.namespace]) return
|
|
267
|
+
if (opt.skipEmpty && stats[opt.version][l][opt.namespace].segmentsTranslated === 0) return
|
|
268
|
+
toDownload.push({
|
|
269
|
+
url: `${opt.apiEndpoint}/${opt.projectId}/${opt.version}/${l}/${opt.namespace}`,
|
|
270
|
+
key: `${opt.projectId}/${opt.version}/${l}/${opt.namespace}`,
|
|
271
|
+
lastModified: '1960-01-01T00:00:00.000Z',
|
|
272
|
+
size: 0
|
|
273
|
+
});
|
|
274
|
+
} else {
|
|
275
|
+
Object.keys(stats[opt.version][l]).forEach((n) => {
|
|
276
|
+
if (opt.skipEmpty && stats[opt.version][l][n].segmentsTranslated === 0) return
|
|
277
|
+
toDownload.push({
|
|
278
|
+
url: `${opt.apiEndpoint}/${opt.projectId}/${opt.version}/${l}/${n}`,
|
|
279
|
+
key: `${opt.projectId}/${opt.version}/${l}/${n}`,
|
|
280
|
+
lastModified: '1960-01-01T00:00:00.000Z',
|
|
281
|
+
size: 0
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
return toDownload
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function compareNamespaces (opt, localNamespaces) {
|
|
291
|
+
// previously used async.mapLimit -> pMapLimit
|
|
292
|
+
const limit = 20;
|
|
293
|
+
return pMapLimit(localNamespaces, limit, async (ns) => {
|
|
294
|
+
const { result: remoteNamespace, lastModified } = await getRemoteNamespace(opt, ns.language, ns.namespace);
|
|
295
|
+
const diff = compareNamespace(ns.content, remoteNamespace, opt.compareModificationTime ? ns.mtime : undefined, opt.compareModificationTime ? lastModified : undefined);
|
|
296
|
+
ns.diff = diff;
|
|
297
|
+
ns.remoteContent = remoteNamespace;
|
|
298
|
+
return ns
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function downloadAll (opt, remoteLanguages, omitRef = false, manipulate) {
|
|
303
|
+
if (!opt.dry && opt.format !== 'xcstrings') cleanupLanguages(opt, remoteLanguages);
|
|
304
|
+
|
|
305
|
+
let downloads = await getDownloads(opt);
|
|
306
|
+
|
|
307
|
+
ensureAllNamespacesInLanguages(opt, remoteLanguages, downloads);
|
|
308
|
+
|
|
309
|
+
if (omitRef) {
|
|
310
|
+
downloads = downloads.filter((d) => {
|
|
311
|
+
const splitted = d.key.split('/');
|
|
312
|
+
const lng = splitted[d.isPrivate ? 3 : 2];
|
|
313
|
+
return lng !== opt.referenceLanguage
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (opt.format === 'xcstrings') { // 1 file per namespace including all languages
|
|
318
|
+
const downloadsByNamespace = {};
|
|
319
|
+
downloads.forEach((download) => {
|
|
320
|
+
const { namespace } = getInfosInUrl(download);
|
|
321
|
+
downloadsByNamespace[namespace] = downloadsByNamespace[namespace] || [];
|
|
322
|
+
downloadsByNamespace[namespace].push(download);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const namespaceKeys = Object.keys(downloadsByNamespace);
|
|
326
|
+
const concurrency = opt.unpublished ? 5 : 20;
|
|
327
|
+
|
|
328
|
+
await pEachLimit(namespaceKeys, concurrency, async (namespace) => {
|
|
329
|
+
const locizeData = {
|
|
330
|
+
sourceLng: opt.referenceLanguage,
|
|
331
|
+
resources: {}
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const entries = downloadsByNamespace[namespace];
|
|
335
|
+
await pEachLimit(entries, opt.unpublished ? 5 : 20, async (download) => {
|
|
336
|
+
const { language } = getInfosInUrl(download);
|
|
337
|
+
opt.isPrivate = download.isPrivate;
|
|
338
|
+
|
|
339
|
+
if (opt.language && opt.language !== language && language !== opt.referenceLanguage) return
|
|
340
|
+
if (opt.languages && opt.languages.length > 0 && opt.languages.indexOf(language) < 0 && language !== opt.referenceLanguage) return
|
|
341
|
+
if (opt.namespace && opt.namespace !== namespace) return
|
|
342
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return
|
|
343
|
+
|
|
344
|
+
if (opt.unpublished) opt.raw = true;
|
|
345
|
+
const { result: ns } = await getRemoteNamespace(opt, language, namespace);
|
|
346
|
+
|
|
347
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (manipulate && typeof manipulate === 'function') manipulate(language, namespace, ns);
|
|
352
|
+
|
|
353
|
+
locizeData.resources[language] = ns;
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const converted = locize2xcstrings(locizeData);
|
|
358
|
+
|
|
359
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, '').replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
360
|
+
if (opt.dry) return
|
|
361
|
+
if (opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`) > opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`) && filledMask.lastIndexOf(path.sep) > 0) {
|
|
362
|
+
mkdirp.mkdirp.sync(path.join(opt.path, filledMask.substring(0, filledMask.lastIndexOf(path.sep))));
|
|
363
|
+
}
|
|
364
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
365
|
+
mkdirp.mkdirp.sync(parentDir);
|
|
366
|
+
const fileContent = (opt.format !== 'xlsx' && !converted.endsWith('\n')) ? (converted + '\n') : converted;
|
|
367
|
+
await fs.promises.writeFile(path.join(opt.path, filledMask), fileContent);
|
|
368
|
+
} catch (e) {
|
|
369
|
+
e.message = 'Invalid content for "' + opt.format + '" format!\n' + (e.message || '');
|
|
370
|
+
throw e
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
} else { // 1 file per namespace/lng
|
|
374
|
+
const concurrency = opt.unpublished ? 5 : 20;
|
|
375
|
+
await pEachLimit(downloads, concurrency, async (download) => {
|
|
376
|
+
const { language, namespace } = getInfosInUrl(download);
|
|
377
|
+
opt.isPrivate = download.isPrivate;
|
|
378
|
+
|
|
379
|
+
if (opt.language && opt.language !== language && language !== opt.referenceLanguage) return
|
|
380
|
+
if (opt.languages && opt.languages.length > 0 && opt.languages.indexOf(language) < 0 && language !== opt.referenceLanguage) return
|
|
381
|
+
if (opt.namespace && opt.namespace !== namespace) return
|
|
382
|
+
if (opt.namespaces && opt.namespaces.length > 0 && opt.namespaces.indexOf(namespace) < 0) return
|
|
383
|
+
|
|
384
|
+
const { result: ns, lastModified } = await getRemoteNamespace(opt, language, namespace);
|
|
385
|
+
|
|
386
|
+
if (opt.skipEmpty && Object.keys(flatten(ns)).length === 0) {
|
|
387
|
+
return
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (manipulate && typeof manipulate === 'function') manipulate(language, namespace, ns);
|
|
391
|
+
|
|
392
|
+
const converted = await convertToDesiredFormat(opt, namespace, language, ns, lastModified);
|
|
393
|
+
// convertToDesiredFormatP either resolves converted or throws
|
|
394
|
+
let convertedText = converted;
|
|
395
|
+
if (Array.isArray(converted) && converted.length === 1) convertedText = converted[0];
|
|
396
|
+
|
|
397
|
+
const filledMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, language).replace(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`, namespace) + reversedFileExtensionsMap[opt.format];
|
|
398
|
+
if (opt.dry) return
|
|
399
|
+
if (opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`) > opt.pathMask.indexOf(`${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`) && filledMask.lastIndexOf(path.sep) > 0) {
|
|
400
|
+
mkdirp.mkdirp.sync(path.join(opt.path, filledMask.substring(0, filledMask.lastIndexOf(path.sep))));
|
|
401
|
+
}
|
|
402
|
+
const parentDir = path.dirname(path.join(opt.path, filledMask));
|
|
403
|
+
mkdirp.mkdirp.sync(parentDir);
|
|
404
|
+
const fileContent = (opt.format !== 'xlsx' && !convertedText.endsWith('\n')) ? (convertedText + '\n') : convertedText;
|
|
405
|
+
await fs.promises.writeFile(path.join(opt.path, filledMask), fileContent);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function update (opt, lng, ns, shouldOmit = false) {
|
|
411
|
+
const data = {};
|
|
412
|
+
if (!opt.skipDelete) {
|
|
413
|
+
ns.diff.toRemove.forEach((k) => { data[k] = null; });
|
|
414
|
+
}
|
|
415
|
+
ns.diff.toAdd.forEach((k) => { data[k] = ns.content[k]; });
|
|
416
|
+
if (opt.updateValues) {
|
|
417
|
+
ns.diff.toUpdate.forEach((k) => { data[k] = ns.content[k]; });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const keysToSend = Object.keys(data).length;
|
|
421
|
+
if (keysToSend === 0) return
|
|
422
|
+
|
|
423
|
+
if (opt.dry) return
|
|
424
|
+
|
|
425
|
+
const payloadKeysLimit = 1000;
|
|
426
|
+
|
|
427
|
+
async function send (d, so) {
|
|
428
|
+
const queryParams = new URLSearchParams();
|
|
429
|
+
if (opt.autoTranslate && lng === opt.referenceLanguage) {
|
|
430
|
+
/** @See https://www.locize.com/docs/api#optional-autotranslate */
|
|
431
|
+
queryParams.append('autotranslate', 'true');
|
|
432
|
+
}
|
|
433
|
+
if (so) {
|
|
434
|
+
queryParams.append('omitstatsgeneration', 'true');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const queryString = queryParams.size > 0 ? '?' + queryParams.toString() : '';
|
|
438
|
+
|
|
439
|
+
// retry once on 504
|
|
440
|
+
let isRetrying = false;
|
|
441
|
+
while (true) {
|
|
442
|
+
const { res, obj } = await request(opt.apiEndpoint + '/update/' + opt.projectId + '/' + opt.version + '/' + lng + '/' + ns.namespace + queryString, {
|
|
443
|
+
method: 'post',
|
|
444
|
+
body: d,
|
|
445
|
+
headers: {
|
|
446
|
+
Authorization: opt.apiKey
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
const cliInfo = res.headers.get('x-cli-info');
|
|
450
|
+
if (cliInfo && cliInfo !== opt.lastShownCliInfo) {
|
|
451
|
+
console.log(colors.yellow(cliInfo));
|
|
452
|
+
opt.lastShownCliInfo = cliInfo;
|
|
453
|
+
}
|
|
454
|
+
if (res.status === 504 && !isRetrying) {
|
|
455
|
+
isRetrying = true;
|
|
456
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
457
|
+
continue
|
|
458
|
+
}
|
|
459
|
+
if (res.status >= 300 && res.status !== 412) {
|
|
460
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
461
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
462
|
+
}
|
|
463
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
464
|
+
}
|
|
465
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (keysToSend > payloadKeysLimit) {
|
|
471
|
+
const tasks = [];
|
|
472
|
+
const keysInObj = Object.keys(data);
|
|
473
|
+
|
|
474
|
+
while (keysInObj.length > payloadKeysLimit) {
|
|
475
|
+
const pagedData = {};
|
|
476
|
+
keysInObj.splice(0, payloadKeysLimit).forEach((k) => { pagedData[k] = data[k]; });
|
|
477
|
+
const hasMoreKeys = keysInObj.length > 0;
|
|
478
|
+
tasks.push(async () => send(pagedData, hasMoreKeys ? true : shouldOmit));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (keysInObj.length === 0) return
|
|
482
|
+
|
|
483
|
+
const finalPagedData = {};
|
|
484
|
+
keysInObj.splice(0, keysInObj.length).forEach((k) => { finalPagedData[k] = data[k]; });
|
|
485
|
+
tasks.push(async () => send(finalPagedData, shouldOmit));
|
|
486
|
+
|
|
487
|
+
// run tasks in series (as original async.series)
|
|
488
|
+
await pSeries(tasks);
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
await send(data, shouldOmit);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function handleSync (opt, remoteLanguages, localNamespaces) {
|
|
496
|
+
if (!localNamespaces || localNamespaces.length === 0) {
|
|
497
|
+
await downloadAll(opt, remoteLanguages, false);
|
|
498
|
+
return
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const downloads = await getDownloads(opt);
|
|
502
|
+
opt.isPrivate = downloads.length > 0 && downloads[0].isPrivate;
|
|
503
|
+
|
|
504
|
+
const localMissingNamespaces = checkForMissingLocalNamespaces(downloads, localNamespaces, opt);
|
|
505
|
+
|
|
506
|
+
const compared = await compareNamespaces(opt, localNamespaces);
|
|
507
|
+
|
|
508
|
+
const onlyToUpdate = compared.filter((ns) => ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove).concat(ns.diff.toUpdate).length > 0);
|
|
509
|
+
|
|
510
|
+
const lngsInReqs = [];
|
|
511
|
+
const nsInReqs = [];
|
|
512
|
+
onlyToUpdate.forEach((n) => {
|
|
513
|
+
if (lngsInReqs.indexOf(n.language) < 0) {
|
|
514
|
+
lngsInReqs.push(n.language);
|
|
515
|
+
}
|
|
516
|
+
if (nsInReqs.indexOf(n.namespace) < 0) {
|
|
517
|
+
nsInReqs.push(n.namespace);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
const shouldOmit = lngsInReqs.length > 5 || nsInReqs.length > 5;
|
|
521
|
+
|
|
522
|
+
let wasThereSomethingToUpdate = opt.autoTranslate || false;
|
|
523
|
+
|
|
524
|
+
async function updateComparedNamespaces () {
|
|
525
|
+
const now = new Date();
|
|
526
|
+
const concurrency = Math.max(1, Math.round(os.cpus().length / 2));
|
|
527
|
+
await pEachLimit(compared, concurrency, async (ns) => {
|
|
528
|
+
if (ns.diff.toRemove.length > 0) {
|
|
529
|
+
if (opt.skipDelete) {
|
|
530
|
+
console.log(colors.bgRed(`skipping the removal of ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
531
|
+
if (opt.dry) console.log(colors.bgRed(`skipped to remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
532
|
+
} else {
|
|
533
|
+
console.log(colors.red(`removing ${ns.diff.toRemove.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
534
|
+
if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemove.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
535
|
+
if (!opt.dry && opt.backupDeletedPath) backupDeleted(opt, ns, now);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (ns.diff.toRemoveLocally.length > 0) {
|
|
539
|
+
console.log(colors.red(`removing ${ns.diff.toRemoveLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
|
|
540
|
+
if (opt.dry) console.log(colors.red(`would remove ${ns.diff.toRemoveLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
|
|
541
|
+
}
|
|
542
|
+
if (ns.diff.toAdd.length > 0) {
|
|
543
|
+
console.log(colors.green(`adding ${ns.diff.toAdd.length} keys in ${ns.language}/${ns.namespace}...`));
|
|
544
|
+
if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAdd.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
545
|
+
}
|
|
546
|
+
if (ns.diff.toAddLocally.length > 0) {
|
|
547
|
+
if (opt.skipDelete) {
|
|
548
|
+
console.log(colors.bgGreen(`skipping the addition of ${ns.diff.toAddLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
|
|
549
|
+
if (opt.dry) console.log(colors.bgGreen(`skipped the addition of ${ns.diff.toAddLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
|
|
550
|
+
} else {
|
|
551
|
+
console.log(colors.green(`adding ${ns.diff.toAddLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
|
|
552
|
+
if (opt.dry) console.log(colors.green(`would add ${ns.diff.toAddLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (opt.updateValues) {
|
|
556
|
+
if (ns.diff.toUpdate.length > 0) {
|
|
557
|
+
console.log(colors.yellow(`updating ${ns.diff.toUpdate.length} keys in ${ns.language}/${ns.namespace}${opt.autoTranslate ? ' with automatic translation' : ''}...`));
|
|
558
|
+
if (opt.dry) console.log(colors.yellow(`would update ${ns.diff.toUpdate.join(', ')} in ${ns.language}/${ns.namespace}...`));
|
|
559
|
+
}
|
|
560
|
+
if (ns.diff.toUpdateLocally.length > 0) {
|
|
561
|
+
console.log(colors.yellow(`updating ${ns.diff.toUpdateLocally.length} keys in ${ns.language}/${ns.namespace} locally...`));
|
|
562
|
+
if (opt.dry) console.log(colors.yellow(`would update ${ns.diff.toUpdateLocally.join(', ')} in ${ns.language}/${ns.namespace} locally...`));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const somethingToUpdate = ns.diff.toAdd.concat(opt.skipDelete ? [] : ns.diff.toRemove)/* .concat(ns.diff.toUpdate) */.length > 0;
|
|
566
|
+
if (!somethingToUpdate) console.log(colors.grey(`nothing to update for ${ns.language}/${ns.namespace}`));
|
|
567
|
+
if (!wasThereSomethingToUpdate && somethingToUpdate) wasThereSomethingToUpdate = true;
|
|
568
|
+
|
|
569
|
+
await update(opt, ns.language, ns, shouldOmit);
|
|
570
|
+
if (ns.diff.toRemove.length === 0 || ns.language !== opt.referenceLanguage) return
|
|
571
|
+
const nsOnlyRemove = cloneDeep(ns);
|
|
572
|
+
nsOnlyRemove.diff.toAdd = [];
|
|
573
|
+
nsOnlyRemove.diff.toUpdate = [];
|
|
574
|
+
await pEachLimit(remoteLanguages, Math.max(1, Math.round(os.cpus().length / 2)), async (lng) => {
|
|
575
|
+
await update(opt, lng, nsOnlyRemove, shouldOmit);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
console.log(colors.grey('syncing...'));
|
|
580
|
+
|
|
581
|
+
async function down () {
|
|
582
|
+
await new Promise((resolve) => setTimeout(resolve, wasThereSomethingToUpdate && !opt.dry ? (opt.autoTranslate ? 10000 : 5000) : 0));
|
|
583
|
+
await downloadAll(opt, remoteLanguages, false,
|
|
584
|
+
opt.skipDelete
|
|
585
|
+
? (lng, namespace, ns) => {
|
|
586
|
+
const found = compared.find((n) => n.namespace === namespace && n.language === lng);
|
|
587
|
+
if (found && found.diff) {
|
|
588
|
+
if (found.diff.toAddLocally && found.diff.toAddLocally.length > 0) {
|
|
589
|
+
found.diff.toAddLocally.forEach((k) => {
|
|
590
|
+
delete ns[k];
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
if (found.diff.toRemove && found.diff.toRemove.length > 0) {
|
|
594
|
+
found.diff.toRemove.forEach((k) => {
|
|
595
|
+
delete ns[k];
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
: undefined
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!shouldOmit) return down()
|
|
605
|
+
if (opt.dry) return down()
|
|
606
|
+
|
|
607
|
+
// optimize stats generation...
|
|
608
|
+
const url = opt.apiEndpoint + '/stats/project/regenerate/' + opt.projectId + '/' + opt.version + (lngsInReqs.length === 1 ? `/${lngsInReqs[0]}` : '') + (nsInReqs.length === 1 ? `?namespace=${nsInReqs[0]}` : '');
|
|
609
|
+
const { res, obj } = await request(url, {
|
|
610
|
+
method: 'post',
|
|
611
|
+
body: {},
|
|
612
|
+
headers: {
|
|
613
|
+
Authorization: opt.apiKey
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
if (res.status >= 300 && res.status !== 412) {
|
|
617
|
+
if (obj && (obj.errorMessage || obj.message)) {
|
|
618
|
+
throw new Error((obj.errorMessage || obj.message))
|
|
619
|
+
}
|
|
620
|
+
throw new Error(res.statusText + ' (' + res.status + ')')
|
|
621
|
+
}
|
|
622
|
+
return down()
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (opt.deleteRemoteNamespace && localMissingNamespaces.length > 0) {
|
|
626
|
+
wasThereSomethingToUpdate = true;
|
|
627
|
+
await pEachLimit(localMissingNamespaces, 20, async (n) => {
|
|
628
|
+
if (opt.dry) {
|
|
629
|
+
console.log(colors.red(`would delete complete namespace ${n.namespace}...`));
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
console.log(colors.red(`deleting complete namespace ${n.namespace}...`));
|
|
633
|
+
await deleteNamespace({
|
|
634
|
+
apiEndpoint: opt.apiEndpoint,
|
|
635
|
+
apiKey: opt.apiKey,
|
|
636
|
+
projectId: opt.projectId,
|
|
637
|
+
version: opt.version,
|
|
638
|
+
namespace: n.namespace
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
return updateComparedNamespaces()
|
|
642
|
+
}
|
|
643
|
+
return updateComparedNamespaces()
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const checkForMissingLocalNamespaces = (downloads, localNamespaces, opt) => {
|
|
647
|
+
const localMissingNamespaces = [];
|
|
648
|
+
downloads.forEach((d) => {
|
|
649
|
+
const splitted = d.url.split('/');
|
|
650
|
+
const namespace = splitted.pop();
|
|
651
|
+
const language = splitted.pop();
|
|
652
|
+
if (language === opt.referenceLanguage) {
|
|
653
|
+
const foundLocalNamespace = localNamespaces.find((n) => n.namespace === namespace && n.language === language);
|
|
654
|
+
if (!foundLocalNamespace) {
|
|
655
|
+
localMissingNamespaces.push({
|
|
656
|
+
language,
|
|
657
|
+
namespace
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
return localMissingNamespaces
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
async function continueToSync (opt) {
|
|
666
|
+
console.log(colors.grey('checking remote (locize)...'));
|
|
667
|
+
const remoteLanguages = await getRemoteLanguages(opt);
|
|
668
|
+
|
|
669
|
+
if (opt.referenceLanguageOnly && opt.language && opt.referenceLanguage !== opt.language) {
|
|
670
|
+
opt.referenceLanguage = opt.language;
|
|
671
|
+
}
|
|
672
|
+
if (opt.referenceLanguageOnly && !opt.language && opt.languages && opt.languages.length > 0 && opt.languages.indexOf(opt.referenceLanguage) < 0) {
|
|
673
|
+
opt.referenceLanguage = opt.languages[0];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (opt.referenceLanguageOnly) {
|
|
677
|
+
console.log(colors.grey(`checking local${opt.path !== process.cwd() ? ` (${opt.path})` : ''} only reference language...`));
|
|
678
|
+
let localNamespaces = await parseLocalReference(opt);
|
|
679
|
+
if (!opt.dry && opt.cleanLocalFiles) {
|
|
680
|
+
localNamespaces.forEach((ln) => fs.unlinkSync(ln.path));
|
|
681
|
+
localNamespaces = [];
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
console.log(colors.grey('calculate diffs...'));
|
|
685
|
+
await handleSync(opt, remoteLanguages, localNamespaces);
|
|
686
|
+
return
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
console.log(colors.grey(`checking local${opt.path !== process.cwd() ? ` (${opt.path})` : ''}...`));
|
|
690
|
+
let localNamespaces = await parseLocalLanguages(opt, remoteLanguages);
|
|
691
|
+
if (!opt.dry && opt.cleanLocalFiles) {
|
|
692
|
+
localNamespaces.forEach((ln) => fs.unlinkSync(ln.path));
|
|
693
|
+
localNamespaces = [];
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
console.log(colors.grey('calculate diffs...'));
|
|
697
|
+
await handleSync(opt, remoteLanguages, localNamespaces);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async function syncInternal (opt) {
|
|
701
|
+
opt.format = opt.format || 'json';
|
|
702
|
+
if (!reversedFileExtensionsMap[opt.format]) {
|
|
703
|
+
throw new Error(`${opt.format} is not a valid format!`)
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (opt.autoTranslate && !opt.referenceLanguageOnly) {
|
|
707
|
+
console.log(colors.yellow('Using the "--auto-translate true" option together with the "--reference-language-only false" option might result in inconsistent target language translations (automatic translation vs. what is sent direcly to locize).'));
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
opt.version = opt.version || 'latest';
|
|
711
|
+
opt.apiEndpoint = opt.apiEndpoint || 'https://api.locize.app';
|
|
712
|
+
|
|
713
|
+
if (!opt.dry && opt.clean) rimraf.rimraf.sync(path.join(opt.path, '*'));
|
|
714
|
+
|
|
715
|
+
if (opt.autoCreatePath === false) {
|
|
716
|
+
if (!doesDirectoryExist(opt.path)) {
|
|
717
|
+
throw new Error(`${opt.path} does not exist!`)
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (!opt.dry) mkdirp.mkdirp.sync(opt.path);
|
|
721
|
+
|
|
722
|
+
if (opt.namespace && opt.namespace.indexOf(',') > 0 && opt.namespace.indexOf(' ') < 0) {
|
|
723
|
+
opt.namespaces = opt.namespace.split(',');
|
|
724
|
+
delete opt.namespace;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
opt.pathMaskInterpolationPrefix = opt.pathMaskInterpolationPrefix || '{{';
|
|
728
|
+
opt.pathMaskInterpolationSuffix = opt.pathMaskInterpolationSuffix || '}}';
|
|
729
|
+
opt.pathMask = opt.pathMask || `${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}${path.sep}${opt.pathMaskInterpolationPrefix}namespace${opt.pathMaskInterpolationSuffix}`;
|
|
730
|
+
opt.languageFolderPrefix = opt.languageFolderPrefix || '';
|
|
731
|
+
opt.pathMask = opt.pathMask.replace(`${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`, `${opt.languageFolderPrefix}${opt.pathMaskInterpolationPrefix}language${opt.pathMaskInterpolationSuffix}`);
|
|
732
|
+
if (opt.overriddenOnly) {
|
|
733
|
+
opt.unpublished = true;
|
|
734
|
+
}
|
|
735
|
+
if (opt.unpublished && !opt.apiKey) {
|
|
736
|
+
throw new Error('Please provide also an api-key!')
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (opt.branch === '') {
|
|
740
|
+
throw new Error('The branch name seems invalid!')
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
if (opt.branch) {
|
|
744
|
+
const branches = await getBranches(opt);
|
|
745
|
+
let b;
|
|
746
|
+
if (isValidUuid(opt.branch)) b = branches.find((br) => br.id === opt.branch);
|
|
747
|
+
if (!b) b = branches.find((br) => br.name === opt.branch);
|
|
748
|
+
if (!b) {
|
|
749
|
+
throw new Error(`Branch ${opt.branch} not found!`)
|
|
750
|
+
}
|
|
751
|
+
opt.projectId = b.id;
|
|
752
|
+
opt.version = b.version;
|
|
753
|
+
|
|
754
|
+
return continueToSync(opt)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return continueToSync(opt)
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
async function sync (opt) {
|
|
761
|
+
opt = opt || {};
|
|
762
|
+
|
|
763
|
+
try {
|
|
764
|
+
await syncInternal(opt);
|
|
765
|
+
console.log(colors.green('FINISHED'));
|
|
766
|
+
} catch (err) {
|
|
767
|
+
console.error(colors.red(err.stack));
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
module.exports = sync;
|