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