@tandem-language-exchange/content-store 1.2.0 → 1.2.2
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/README.md +81 -13
- package/dist/chunk-4DE47ZJD.js +19 -0
- package/dist/chunk-4DE47ZJD.js.map +1 -0
- package/dist/{chunk-YZSLCPN6.js → chunk-6FXNAJSI.js} +160 -14
- package/dist/chunk-6FXNAJSI.js.map +1 -0
- package/dist/{chunk-UCBZUEUP.js → chunk-BAJT7RMQ.js} +31 -69
- package/dist/chunk-BAJT7RMQ.js.map +1 -0
- package/dist/{chunk-XP3USUQC.js → chunk-EQ3DSPTJ.js} +6 -2
- package/dist/{chunk-XP3USUQC.js.map → chunk-EQ3DSPTJ.js.map} +1 -1
- package/dist/chunk-UPIQFNCR.js +17 -0
- package/dist/chunk-UPIQFNCR.js.map +1 -0
- package/dist/{chunk-VRWRAFDK.js → chunk-VKXN2SE6.js} +79 -24
- package/dist/chunk-VKXN2SE6.js.map +1 -0
- package/dist/client/fetch-content-bundles.js +7 -4
- package/dist/client/fetch-content-bundles.js.map +1 -1
- package/dist/client/fetch-merged-translation-bundles.js +46 -0
- package/dist/client/fetch-merged-translation-bundles.js.map +1 -0
- package/dist/client/fetch-translation-bundles.js +22 -11
- package/dist/client/fetch-translation-bundles.js.map +1 -1
- package/dist/client/query-cms.js +33 -0
- package/dist/client/query-cms.js.map +1 -0
- package/dist/{index-Db97SUTy.d.ts → index-PQ7XN47c.d.ts} +36 -7
- package/dist/index.d.ts +1 -1
- package/dist/node.browser.js +7 -0
- package/dist/node.browser.js.map +1 -1
- package/dist/node.d.ts +9 -3
- package/dist/node.js +237 -15
- package/dist/node.js.map +1 -1
- package/package.json +8 -5
- package/dist/chunk-UCBZUEUP.js.map +0 -1
- package/dist/chunk-VRWRAFDK.js.map +0 -1
- package/dist/chunk-YZSLCPN6.js.map +0 -1
- package/dist/client/cli.js +0 -82
- package/dist/client/cli.js.map +0 -1
package/dist/node.js
CHANGED
|
@@ -28,6 +28,18 @@ var ContentStore = class {
|
|
|
28
28
|
);
|
|
29
29
|
return key;
|
|
30
30
|
}
|
|
31
|
+
/** Raw UTF-8 body (e.g. Lingohub file bytes as text). */
|
|
32
|
+
async uploadRaw(key, body, contentType) {
|
|
33
|
+
await this.client.send(
|
|
34
|
+
new PutObjectCommand({
|
|
35
|
+
Bucket: this.bucket,
|
|
36
|
+
Key: key,
|
|
37
|
+
Body: body,
|
|
38
|
+
ContentType: contentType
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
return key;
|
|
42
|
+
}
|
|
31
43
|
async download(key) {
|
|
32
44
|
const response = await this.client.send(
|
|
33
45
|
new GetObjectCommand({ Bucket: this.bucket, Key: key })
|
|
@@ -36,6 +48,15 @@ var ContentStore = class {
|
|
|
36
48
|
if (!body) throw new Error(`Empty response for key: ${key}`);
|
|
37
49
|
return JSON.parse(body);
|
|
38
50
|
}
|
|
51
|
+
/** Raw UTF-8 body from S3 (no JSON.parse). */
|
|
52
|
+
async downloadRaw(key) {
|
|
53
|
+
const response = await this.client.send(
|
|
54
|
+
new GetObjectCommand({ Bucket: this.bucket, Key: key })
|
|
55
|
+
);
|
|
56
|
+
const body = await response.Body?.transformToString();
|
|
57
|
+
if (!body) throw new Error(`Empty response for key: ${key}`);
|
|
58
|
+
return body;
|
|
59
|
+
}
|
|
39
60
|
};
|
|
40
61
|
var buildCmsObjectKey = (cms, contentType) => {
|
|
41
62
|
return `${cms}-${contentType}.json`;
|
|
@@ -46,7 +67,7 @@ var buildTranslationObjectKey = (project, fileName, locale) => {
|
|
|
46
67
|
|
|
47
68
|
// src/shared/bundles.ts
|
|
48
69
|
import fs from "fs/promises";
|
|
49
|
-
import
|
|
70
|
+
import path2 from "path";
|
|
50
71
|
|
|
51
72
|
// src/shared/s3-retry.ts
|
|
52
73
|
function computeDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
@@ -122,6 +143,9 @@ async function withS3Retry(fn, { maxRetries, baseDelayMs, maxDelayMs }) {
|
|
|
122
143
|
async function downloadWithRetry(store, key, cfg) {
|
|
123
144
|
return withS3Retry(() => store.download(key), cfg);
|
|
124
145
|
}
|
|
146
|
+
async function downloadRawWithRetry(store, key, cfg) {
|
|
147
|
+
return withS3Retry(() => store.downloadRaw(key), cfg);
|
|
148
|
+
}
|
|
125
149
|
|
|
126
150
|
// src/shared/trimDepth.ts
|
|
127
151
|
function trimDepth(value, remaining) {
|
|
@@ -169,41 +193,49 @@ var localeMapping = {
|
|
|
169
193
|
var allProjects = {
|
|
170
194
|
"tandem-(new-website)": [
|
|
171
195
|
{
|
|
196
|
+
resource: "main",
|
|
172
197
|
fileName: "Website.[locale].json",
|
|
173
198
|
type: "json"
|
|
174
199
|
}
|
|
175
200
|
],
|
|
176
201
|
"tandem-(website)": [
|
|
177
202
|
{
|
|
203
|
+
resource: "main",
|
|
178
204
|
fileName: "[locale].json",
|
|
179
205
|
type: "json"
|
|
180
206
|
},
|
|
181
207
|
{
|
|
208
|
+
resource: "ai",
|
|
182
209
|
fileName: "AI.[locale].json",
|
|
183
210
|
type: "json"
|
|
184
211
|
},
|
|
185
212
|
{
|
|
213
|
+
resource: "languages",
|
|
186
214
|
fileName: "languages.[locale].json",
|
|
187
215
|
type: "json"
|
|
188
216
|
}
|
|
189
217
|
],
|
|
190
218
|
"tandem": [
|
|
191
219
|
{
|
|
220
|
+
resource: "infoplist",
|
|
192
221
|
fileName: "InfoPlist.[locale].strings",
|
|
193
222
|
type: "strings",
|
|
194
223
|
localeMapping: localeMapping.ios
|
|
195
224
|
},
|
|
196
225
|
{
|
|
226
|
+
resource: "localizable",
|
|
197
227
|
fileName: "Localizable.[locale].strings",
|
|
198
228
|
type: "strings",
|
|
199
229
|
localeMapping: localeMapping.ios
|
|
200
230
|
},
|
|
201
231
|
{
|
|
232
|
+
resource: "ipad",
|
|
202
233
|
fileName: "MainiPad.[locale].strings",
|
|
203
234
|
type: "strings",
|
|
204
235
|
localeMapping: localeMapping.ios
|
|
205
236
|
},
|
|
206
237
|
{
|
|
238
|
+
resource: "main",
|
|
207
239
|
fileName: "Main.[locale].strings",
|
|
208
240
|
type: "strings",
|
|
209
241
|
localeMapping: localeMapping.ios
|
|
@@ -211,136 +243,163 @@ var allProjects = {
|
|
|
211
243
|
],
|
|
212
244
|
"tandem-(android)": [
|
|
213
245
|
{
|
|
246
|
+
resource: "accessibility",
|
|
214
247
|
fileName: "accessibility_localizable.[locale].xml",
|
|
215
248
|
type: "xml",
|
|
216
249
|
localeMapping: localeMapping.android
|
|
217
250
|
},
|
|
218
251
|
{
|
|
252
|
+
resource: "call",
|
|
219
253
|
fileName: "call_localizable.[locale].xml",
|
|
220
254
|
type: "xml",
|
|
221
255
|
localeMapping: localeMapping.android
|
|
222
256
|
},
|
|
223
257
|
{
|
|
258
|
+
resource: "cert",
|
|
224
259
|
fileName: "cert_localizable.[locale].xml",
|
|
225
260
|
type: "xml",
|
|
226
261
|
localeMapping: localeMapping.android
|
|
227
262
|
},
|
|
228
263
|
{
|
|
264
|
+
resource: "chat",
|
|
229
265
|
fileName: "chat_localizable.[locale].xml",
|
|
230
266
|
type: "xml",
|
|
231
267
|
localeMapping: localeMapping.android
|
|
232
268
|
},
|
|
233
269
|
{
|
|
270
|
+
resource: "checklist",
|
|
234
271
|
fileName: "checklist_localizable.[locale].xml",
|
|
235
272
|
type: "xml",
|
|
236
273
|
localeMapping: localeMapping.android
|
|
237
274
|
},
|
|
238
275
|
{
|
|
276
|
+
resource: "clubs",
|
|
239
277
|
fileName: "clubs_localizable.[locale].xml",
|
|
240
278
|
type: "xml",
|
|
241
279
|
localeMapping: localeMapping.android
|
|
242
280
|
},
|
|
243
281
|
{
|
|
282
|
+
resource: "common",
|
|
244
283
|
fileName: "common_localizable.[locale].xml",
|
|
245
284
|
type: "xml",
|
|
246
285
|
localeMapping: localeMapping.android
|
|
247
286
|
},
|
|
248
287
|
{
|
|
288
|
+
resource: "community",
|
|
249
289
|
fileName: "community_localizable.[locale].xml",
|
|
250
290
|
type: "xml",
|
|
251
291
|
localeMapping: localeMapping.android
|
|
252
292
|
},
|
|
253
293
|
{
|
|
294
|
+
resource: "correction",
|
|
254
295
|
fileName: "correction_localizable.[locale].xml",
|
|
255
296
|
type: "xml",
|
|
256
297
|
localeMapping: localeMapping.android
|
|
257
298
|
},
|
|
258
299
|
{
|
|
300
|
+
resource: "country_names",
|
|
259
301
|
fileName: "country_names.[locale].xml",
|
|
260
302
|
type: "xml",
|
|
261
303
|
localeMapping: localeMapping.android
|
|
262
304
|
},
|
|
263
305
|
{
|
|
306
|
+
resource: "emoji",
|
|
264
307
|
fileName: "emoji_localizable.[locale].xml",
|
|
265
308
|
type: "xml",
|
|
266
309
|
localeMapping: localeMapping.android
|
|
267
310
|
},
|
|
268
311
|
{
|
|
312
|
+
resource: "errors",
|
|
269
313
|
fileName: "errors_localizable.[locale].xml",
|
|
270
314
|
type: "xml",
|
|
271
315
|
localeMapping: localeMapping.android
|
|
272
316
|
},
|
|
273
317
|
{
|
|
318
|
+
resource: "expressions",
|
|
274
319
|
fileName: "expressions_localizable.[locale].xml",
|
|
275
320
|
type: "xml",
|
|
276
321
|
localeMapping: localeMapping.android
|
|
277
322
|
},
|
|
278
323
|
{
|
|
324
|
+
resource: "gif",
|
|
279
325
|
fileName: "gif_localizable.[locale].xml",
|
|
280
326
|
type: "xml",
|
|
281
327
|
localeMapping: localeMapping.android
|
|
282
328
|
},
|
|
283
329
|
{
|
|
330
|
+
resource: "guidelines",
|
|
284
331
|
fileName: "guidelines_localizable.[locale].xml",
|
|
285
332
|
type: "xml",
|
|
286
333
|
localeMapping: localeMapping.android
|
|
287
334
|
},
|
|
288
335
|
{
|
|
336
|
+
resource: "lanuguages",
|
|
289
337
|
fileName: "languages_localizable.[locale].xml",
|
|
290
338
|
type: "xml",
|
|
291
339
|
localeMapping: localeMapping.android
|
|
292
340
|
},
|
|
293
341
|
{
|
|
342
|
+
resource: "localizable",
|
|
294
343
|
fileName: "localizable.[locale].xml",
|
|
295
344
|
type: "xml",
|
|
296
345
|
localeMapping: localeMapping.android
|
|
297
346
|
},
|
|
298
347
|
{
|
|
348
|
+
resource: "localizable2",
|
|
299
349
|
fileName: "localizable2.[locale].xml",
|
|
300
350
|
type: "xml",
|
|
301
351
|
localeMapping: localeMapping.android
|
|
302
352
|
},
|
|
303
353
|
{
|
|
354
|
+
resource: "login",
|
|
304
355
|
fileName: "login_localizable.[locale].xml",
|
|
305
356
|
type: "xml",
|
|
306
357
|
localeMapping: localeMapping.android
|
|
307
358
|
},
|
|
308
359
|
{
|
|
360
|
+
resource: "myprofile",
|
|
309
361
|
fileName: "myprofile_localizable.[locale].xml",
|
|
310
362
|
type: "xml",
|
|
311
363
|
localeMapping: localeMapping.android
|
|
312
364
|
},
|
|
313
365
|
{
|
|
366
|
+
resource: "onb",
|
|
314
367
|
fileName: "onb_localizable.[locale].xml",
|
|
315
368
|
type: "xml",
|
|
316
369
|
localeMapping: localeMapping.android
|
|
317
370
|
},
|
|
318
371
|
{
|
|
372
|
+
resource: "parties",
|
|
319
373
|
fileName: "parties_localizable.[locale].xml",
|
|
320
374
|
type: "xml",
|
|
321
375
|
localeMapping: localeMapping.android
|
|
322
376
|
},
|
|
323
377
|
{
|
|
378
|
+
resource: "pro",
|
|
324
379
|
fileName: "pro_localizable.[locale].xml",
|
|
325
380
|
type: "xml",
|
|
326
381
|
localeMapping: localeMapping.android
|
|
327
382
|
},
|
|
328
383
|
{
|
|
384
|
+
resource: "pro_screen",
|
|
329
385
|
fileName: "pro_screen_localizable.[locale].xml",
|
|
330
386
|
type: "xml",
|
|
331
387
|
localeMapping: localeMapping.android
|
|
332
388
|
},
|
|
333
389
|
{
|
|
390
|
+
resource: "push_notification",
|
|
334
391
|
fileName: "push_notification_localizable.[locale].xml",
|
|
335
392
|
type: "xml",
|
|
336
393
|
localeMapping: localeMapping.android
|
|
337
394
|
},
|
|
338
395
|
{
|
|
396
|
+
resource: "reporting",
|
|
339
397
|
fileName: "reporting_localizable.[locale].xml",
|
|
340
398
|
type: "xml",
|
|
341
399
|
localeMapping: localeMapping.android
|
|
342
400
|
},
|
|
343
401
|
{
|
|
402
|
+
resource: "translation",
|
|
344
403
|
fileName: "translation_localizable.[locale].xml",
|
|
345
404
|
type: "xml",
|
|
346
405
|
localeMapping: localeMapping.android
|
|
@@ -348,12 +407,106 @@ var allProjects = {
|
|
|
348
407
|
],
|
|
349
408
|
"tandem-(web-invites)": [
|
|
350
409
|
{
|
|
410
|
+
resource: "main",
|
|
351
411
|
fileName: "[locale].json",
|
|
352
412
|
type: "json"
|
|
353
413
|
}
|
|
354
414
|
]
|
|
355
415
|
};
|
|
356
416
|
|
|
417
|
+
// src/shared/translationResource.ts
|
|
418
|
+
import path from "path";
|
|
419
|
+
|
|
420
|
+
// src/shared/utils.ts
|
|
421
|
+
import convert from "xml-js";
|
|
422
|
+
import set from "lodash.set";
|
|
423
|
+
import merge from "lodash.merge";
|
|
424
|
+
var transformObjectToFlat = (data) => {
|
|
425
|
+
const result = {};
|
|
426
|
+
const flatten = (obj, path3 = []) => {
|
|
427
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
428
|
+
if (typeof value === "object") {
|
|
429
|
+
flatten(value, path3.concat(key));
|
|
430
|
+
} else {
|
|
431
|
+
result[path3.concat(key).join(".")] = value;
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
flatten(data);
|
|
436
|
+
return result;
|
|
437
|
+
};
|
|
438
|
+
var convertXMLToJS = (xml) => {
|
|
439
|
+
const converted = convert.xml2js(xml, {
|
|
440
|
+
ignoreComment: true,
|
|
441
|
+
ignoreDeclaration: true,
|
|
442
|
+
ignoreInstruction: true,
|
|
443
|
+
compact: true
|
|
444
|
+
});
|
|
445
|
+
let mapped = {};
|
|
446
|
+
converted.resources.string.forEach((item) => {
|
|
447
|
+
mapped = {
|
|
448
|
+
...mapped,
|
|
449
|
+
[item._attributes.name]: item._text
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
return mapped;
|
|
453
|
+
};
|
|
454
|
+
var parseIOSStrings = (strings) => {
|
|
455
|
+
const parsedObj = {};
|
|
456
|
+
strings.split("\n").filter((line) => line.startsWith('"') && line.endsWith(";")).map((line) => line.trim().slice(0, -1)).forEach((line) => {
|
|
457
|
+
let [key, value] = line.split(" = ");
|
|
458
|
+
if (!key || !value) return;
|
|
459
|
+
key = key.slice(1, -1);
|
|
460
|
+
value = value.slice(1, -1);
|
|
461
|
+
parsedObj[key] = value;
|
|
462
|
+
});
|
|
463
|
+
return parsedObj;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// src/shared/translationResource.ts
|
|
467
|
+
function parseTranslationResourceRaw(raw, resource) {
|
|
468
|
+
if (resource.type === "json") {
|
|
469
|
+
return JSON.parse(raw);
|
|
470
|
+
}
|
|
471
|
+
if (resource.type === "strings") {
|
|
472
|
+
return parseIOSStrings(raw);
|
|
473
|
+
}
|
|
474
|
+
if (resource.type === "xml") {
|
|
475
|
+
return convertXMLToJS(raw);
|
|
476
|
+
}
|
|
477
|
+
throw new Error(`Unsupported resource type: ${resource.type}`);
|
|
478
|
+
}
|
|
479
|
+
function toFlatStringMap(parsed) {
|
|
480
|
+
if (parsed === null || parsed === void 0) return {};
|
|
481
|
+
if (typeof parsed === "string") return { value: parsed };
|
|
482
|
+
if (typeof parsed !== "object") return { value: String(parsed) };
|
|
483
|
+
if (Array.isArray(parsed)) {
|
|
484
|
+
const out2 = {};
|
|
485
|
+
parsed.forEach((v, i) => {
|
|
486
|
+
out2[String(i)] = typeof v === "object" && v !== null ? JSON.stringify(v) : String(v);
|
|
487
|
+
});
|
|
488
|
+
return out2;
|
|
489
|
+
}
|
|
490
|
+
const flat = transformObjectToFlat(parsed);
|
|
491
|
+
const out = {};
|
|
492
|
+
for (const [k, v] of Object.entries(flat)) {
|
|
493
|
+
if (v === null || v === void 0) {
|
|
494
|
+
out[k] = "";
|
|
495
|
+
} else if (typeof v === "object") {
|
|
496
|
+
out[k] = JSON.stringify(v);
|
|
497
|
+
} else {
|
|
498
|
+
out[k] = String(v);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
function translationJsonOutputPath(outputDir, objectKey) {
|
|
504
|
+
if (objectKey.endsWith(".json")) {
|
|
505
|
+
return path.resolve(outputDir, objectKey);
|
|
506
|
+
}
|
|
507
|
+
return path.resolve(outputDir, `${objectKey}.json`);
|
|
508
|
+
}
|
|
509
|
+
|
|
357
510
|
// src/shared/bundles.ts
|
|
358
511
|
function getAtPath(obj, dottedPath) {
|
|
359
512
|
const parts = dottedPath.split(".").filter((p) => p.length > 0);
|
|
@@ -384,8 +537,8 @@ function isEmptyValue(value) {
|
|
|
384
537
|
return typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === 0;
|
|
385
538
|
}
|
|
386
539
|
function matchesFieldFilter(item, key, expected) {
|
|
387
|
-
const { path:
|
|
388
|
-
const at = getAtPath(item,
|
|
540
|
+
const { path: path3, operator } = parseFieldKey(key);
|
|
541
|
+
const at = getAtPath(item, path3);
|
|
389
542
|
if (operator === "exists") {
|
|
390
543
|
if (expected !== true && expected !== false) return false;
|
|
391
544
|
const empty = !at.found || isEmptyValue(at.value);
|
|
@@ -420,13 +573,17 @@ async function fetchCmsBundles(store, outputDir, options) {
|
|
|
420
573
|
contentTypes.map(async (contentType) => {
|
|
421
574
|
const key = buildCmsObjectKey(cms, contentType);
|
|
422
575
|
const data = await downloadWithRetry(store, key, retry);
|
|
423
|
-
const filePath =
|
|
576
|
+
const filePath = path2.resolve(outputDir, key);
|
|
424
577
|
await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
425
578
|
result[contentType] = filePath;
|
|
426
579
|
})
|
|
427
580
|
);
|
|
428
581
|
return result;
|
|
429
582
|
}
|
|
583
|
+
function filterResources(projectResources, resourceFilter) {
|
|
584
|
+
if (resourceFilter.length === 0) return projectResources;
|
|
585
|
+
return projectResources.filter((r) => resourceFilter.includes(r.resource));
|
|
586
|
+
}
|
|
430
587
|
async function fetchTranslationBundles(store, outputDir, options) {
|
|
431
588
|
const { projects, locales } = options;
|
|
432
589
|
const retry = options.retry ?? getDefaultS3RetryConfig();
|
|
@@ -434,11 +591,11 @@ async function fetchTranslationBundles(store, outputDir, options) {
|
|
|
434
591
|
const result = {};
|
|
435
592
|
const localesToSync = locales && locales.length ? locales : defaultLocales;
|
|
436
593
|
await Promise.all(
|
|
437
|
-
projects.map(async (project) => {
|
|
438
|
-
const
|
|
439
|
-
if (!
|
|
440
|
-
|
|
441
|
-
|
|
594
|
+
Object.entries(projects).map(async ([project, resourceFilter]) => {
|
|
595
|
+
const allResources = allProjects[project];
|
|
596
|
+
if (!allResources?.length) return;
|
|
597
|
+
const resources = filterResources(allResources, resourceFilter);
|
|
598
|
+
if (!resources.length) return;
|
|
442
599
|
const resourceTasks = [];
|
|
443
600
|
for (const resource of resources) {
|
|
444
601
|
for (const loc of localesToSync) {
|
|
@@ -448,14 +605,20 @@ async function fetchTranslationBundles(store, outputDir, options) {
|
|
|
448
605
|
resource.fileName,
|
|
449
606
|
locale
|
|
450
607
|
);
|
|
451
|
-
resourceTasks.push({ objectKey });
|
|
608
|
+
resourceTasks.push({ objectKey, resource });
|
|
452
609
|
}
|
|
453
610
|
}
|
|
454
611
|
await Promise.all(
|
|
455
|
-
resourceTasks.map(async ({ objectKey }) => {
|
|
456
|
-
const
|
|
457
|
-
const
|
|
458
|
-
|
|
612
|
+
resourceTasks.map(async ({ objectKey, resource }) => {
|
|
613
|
+
const raw = await downloadRawWithRetry(store, objectKey, retry);
|
|
614
|
+
const parsed = parseTranslationResourceRaw(raw, resource);
|
|
615
|
+
const filePath = translationJsonOutputPath(outputDir, objectKey);
|
|
616
|
+
await fs.mkdir(path2.dirname(filePath), { recursive: true });
|
|
617
|
+
await fs.writeFile(
|
|
618
|
+
filePath,
|
|
619
|
+
JSON.stringify(parsed, null, 2),
|
|
620
|
+
"utf-8"
|
|
621
|
+
);
|
|
459
622
|
if (!result[project]) {
|
|
460
623
|
result[project] = {};
|
|
461
624
|
}
|
|
@@ -466,8 +629,58 @@ async function fetchTranslationBundles(store, outputDir, options) {
|
|
|
466
629
|
);
|
|
467
630
|
return result;
|
|
468
631
|
}
|
|
632
|
+
async function fetchMergedTranslationBundles(store, outputDir, options) {
|
|
633
|
+
const { projects, locales } = options;
|
|
634
|
+
const retry = options.retry ?? getDefaultS3RetryConfig();
|
|
635
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
636
|
+
const localesToSync = locales && locales.length ? locales : defaultLocales;
|
|
637
|
+
const tasks = [];
|
|
638
|
+
for (const [project, resourceFilter] of Object.entries(projects)) {
|
|
639
|
+
const allResources = allProjects[project];
|
|
640
|
+
if (!allResources?.length) continue;
|
|
641
|
+
const resources = filterResources(allResources, resourceFilter);
|
|
642
|
+
if (!resources.length) continue;
|
|
643
|
+
for (const resource of resources) {
|
|
644
|
+
for (const loc of localesToSync) {
|
|
645
|
+
const mappedLocale = resource.localeMapping && resource.localeMapping[loc] ? resource.localeMapping[loc] : loc;
|
|
646
|
+
const objectKey = buildTranslationObjectKey(
|
|
647
|
+
project,
|
|
648
|
+
resource.fileName,
|
|
649
|
+
mappedLocale
|
|
650
|
+
);
|
|
651
|
+
tasks.push({ catalogLocale: loc, objectKey, resource });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
const results = await Promise.all(
|
|
656
|
+
tasks.map(async ({ catalogLocale, objectKey, resource }) => {
|
|
657
|
+
const raw = await downloadRawWithRetry(store, objectKey, retry);
|
|
658
|
+
const parsed = parseTranslationResourceRaw(raw, resource);
|
|
659
|
+
const stringMap = toFlatStringMap(parsed);
|
|
660
|
+
return { catalogLocale, stringMap };
|
|
661
|
+
})
|
|
662
|
+
);
|
|
663
|
+
const merged = {};
|
|
664
|
+
for (const loc of localesToSync) {
|
|
665
|
+
merged[loc] = {};
|
|
666
|
+
}
|
|
667
|
+
for (const { catalogLocale, stringMap } of results) {
|
|
668
|
+
Object.assign(merged[catalogLocale], stringMap);
|
|
669
|
+
}
|
|
670
|
+
const out = {};
|
|
671
|
+
for (const loc of localesToSync) {
|
|
672
|
+
const filePath = path2.resolve(outputDir, `${loc}.json`);
|
|
673
|
+
await fs.writeFile(
|
|
674
|
+
filePath,
|
|
675
|
+
JSON.stringify(merged[loc], null, 2),
|
|
676
|
+
"utf-8"
|
|
677
|
+
);
|
|
678
|
+
out[loc] = filePath;
|
|
679
|
+
}
|
|
680
|
+
return out;
|
|
681
|
+
}
|
|
469
682
|
async function queryCmsBundle(outputDir, cms, contentType, options = {}) {
|
|
470
|
-
const filePath =
|
|
683
|
+
const filePath = path2.resolve(
|
|
471
684
|
outputDir,
|
|
472
685
|
`${cms}-${contentType}.json`
|
|
473
686
|
);
|
|
@@ -529,6 +742,14 @@ var ContentStoreSDK = class {
|
|
|
529
742
|
async fetchTranslationBundles(options) {
|
|
530
743
|
return fetchTranslationBundles(this.store, this.outputDir, options);
|
|
531
744
|
}
|
|
745
|
+
/**
|
|
746
|
+
* Downloads all translation resources for the given projects/locales, parses them,
|
|
747
|
+
* and writes one merged `{locale}.json` per locale (string key/value map; duplicate keys:
|
|
748
|
+
* last wins).
|
|
749
|
+
*/
|
|
750
|
+
async fetchMergedTranslationBundles(options) {
|
|
751
|
+
return fetchMergedTranslationBundles(this.store, this.outputDir, options);
|
|
752
|
+
}
|
|
532
753
|
/**
|
|
533
754
|
* Queries a previously fetched bundle from the local filesystem.
|
|
534
755
|
*/
|
|
@@ -540,6 +761,7 @@ export {
|
|
|
540
761
|
ContentStore,
|
|
541
762
|
ContentStoreSDK,
|
|
542
763
|
fetchCmsBundles,
|
|
764
|
+
fetchMergedTranslationBundles,
|
|
543
765
|
fetchTranslationBundles,
|
|
544
766
|
getDefaultS3RetryConfig,
|
|
545
767
|
queryCmsBundle,
|