@translationstudio/translationstudio-strapi-extension 1.1.3 → 2.0.1

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.
@@ -1,4 +1,23 @@
1
1
  "use strict";
2
+ const crypto = require("crypto");
3
+ function _interopNamespace(e) {
4
+ if (e && e.__esModule) return e;
5
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
6
+ if (e) {
7
+ for (const k in e) {
8
+ if (k !== "default") {
9
+ const d = Object.getOwnPropertyDescriptor(e, k);
10
+ Object.defineProperty(n, k, d.get ? d : {
11
+ enumerable: true,
12
+ get: () => e[k]
13
+ });
14
+ }
15
+ }
16
+ }
17
+ n.default = e;
18
+ return Object.freeze(n);
19
+ }
20
+ const crypto__namespace = /* @__PURE__ */ _interopNamespace(crypto);
2
21
  const bootstrap = ({ strapi: strapi2 }) => {
3
22
  };
4
23
  const destroy = ({ strapi: strapi2 }) => {
@@ -76,10 +95,15 @@ const controller = ({ strapi: strapi2 }) => ({
76
95
  ctx.status = 400;
77
96
  return;
78
97
  }
79
- const payload = JSON.parse(ctx.request.body);
80
- const result = await strapi2.plugin(APP_NAME$1).service("service").exportData(payload);
81
- ctx.status = 200;
82
- ctx.body = [{ fields: result }];
98
+ try {
99
+ const payload = typeof ctx.request.body === "string" ? JSON.parse(ctx.request.body) : ctx.request.body;
100
+ const result = await strapi2.plugin(APP_NAME$1).service("service").exportData(payload);
101
+ ctx.status = 200;
102
+ ctx.body = [{ fields: result }];
103
+ } catch (ex) {
104
+ ctx.status = 500;
105
+ ctx.body = { error: ex.message ?? "Generic error" };
106
+ }
83
107
  },
84
108
  async setDevelopmentUrl(ctx) {
85
109
  const url = ctx.request.body.url;
@@ -107,23 +131,36 @@ const controller = ({ strapi: strapi2 }) => ({
107
131
  ctx.status = 400;
108
132
  return;
109
133
  }
110
- const payload = JSON.parse(ctx.request.body);
111
- const result = await strapi2.plugin(APP_NAME$1).service("service").importData(payload);
112
- ctx.body = result;
134
+ try {
135
+ const payload = typeof ctx.request.body === "object" ? ctx.request.body : JSON.parse(ctx.request.body);
136
+ strapi2.log.info("Importing");
137
+ const result = await strapi2.plugin(APP_NAME$1).service("service").importData(payload);
138
+ ctx.body = { success: result };
139
+ ctx.status = 200;
140
+ return;
141
+ } catch (err) {
142
+ strapi2.log.error(err.message ?? err);
143
+ }
144
+ ctx.body = { message: "Could not perform import" };
145
+ ctx.status = 500;
113
146
  },
114
147
  async ping(ctx) {
115
- const result = await strapi2.plugin(APP_NAME$1).service("service").ping();
148
+ await strapi2.plugin(APP_NAME$1).service("service").ping();
116
149
  ctx.status = 204;
117
- ctx.body = result;
118
150
  },
119
151
  async getLanguages(ctx) {
120
152
  if (!await this.validateToken(ctx)) {
121
153
  ctx.status = 400;
122
154
  return;
123
155
  }
124
- const result = await strapi2.plugin(APP_NAME$1).service("service").getLanguages();
125
- ctx.status = 200;
126
- ctx.body = result;
156
+ try {
157
+ const result = await strapi2.plugin(APP_NAME$1).service("service").getLanguages();
158
+ ctx.status = 200;
159
+ ctx.body = result;
160
+ } catch (err) {
161
+ ctx.status = 400;
162
+ ctx.body = {};
163
+ }
127
164
  },
128
165
  async getEmail(ctx) {
129
166
  const result = await strapi2.plugin(APP_NAME$1).service("service").getEmail(ctx);
@@ -257,6 +294,7 @@ const routes = [
257
294
  policies: []
258
295
  }
259
296
  },
297
+ /* get entry title */
260
298
  {
261
299
  method: "POST",
262
300
  path: "/entrydata",
@@ -266,13 +304,33 @@ const routes = [
266
304
  }
267
305
  }
268
306
  ];
269
- const getContentType = async (contentTypeID) => {
270
- const contentType = await strapi.contentType(contentTypeID);
307
+ function getComponentSchemata(schema) {
308
+ const res = {};
309
+ for (let fieldName in schema.attributes) {
310
+ const field = schema.attributes[fieldName];
311
+ if (!field.components || !Array.isArray(field.components) || field.components.length === 0)
312
+ continue;
313
+ for (let type of field.components) {
314
+ const schema2 = strapi.components[type];
315
+ if (schema2 && schema2.attributes)
316
+ res[type] = schema2;
317
+ }
318
+ }
319
+ return res;
320
+ }
321
+ function getContentType(contentTypeID) {
322
+ const contentType = strapi.contentType(contentTypeID);
271
323
  if (!contentType?.attributes) {
272
- throw new Error(`Content type or schema not found: ${contentTypeID}`);
324
+ strapi.log.error(`Content type or schema not found: ${contentTypeID}`);
325
+ return null;
273
326
  }
274
- return contentType;
275
- };
327
+ const res = {
328
+ entry: contentType,
329
+ components: getComponentSchemata(contentType)
330
+ };
331
+ strapi.log.info("SChema loaded for " + contentTypeID + ". Component schemata loaded: " + Object.keys(res.components).length);
332
+ return res;
333
+ }
276
334
  const parsePayload = (payload) => {
277
335
  const [contentTypeID, entryID] = payload.element.includes("#") ? payload.element.split("#") : [payload.element, void 0];
278
336
  const locale = payload.source.includes("-") ? payload.source.split("-")[0] : payload.source;
@@ -282,7 +340,7 @@ const getComponentSchema = async (componentName) => {
282
340
  try {
283
341
  return await strapi.components[componentName];
284
342
  } catch (error) {
285
- console.error(`Failed to get component schema for ${componentName}:`, error);
343
+ strapi.log.error(`Failed to get component schema for ${componentName}:`, error);
286
344
  return null;
287
345
  }
288
346
  };
@@ -305,27 +363,32 @@ const buildPopulateConfig = async (schema) => {
305
363
  }
306
364
  return populate;
307
365
  };
308
- const getEntry = async (contentTypeID, entryID, locale) => {
366
+ const getEntry = async (contentTypeID, entryID, locale, logError = true) => {
309
367
  try {
310
368
  const contentType = await strapi.contentTypes[contentTypeID];
311
369
  const populateConfig = await buildPopulateConfig(contentType);
312
370
  const query = {
313
371
  locale,
372
+ documentId: entryID,
314
373
  populate: populateConfig
315
374
  };
316
- if (entryID) {
317
- Object.assign(query, { documentId: entryID });
318
- }
319
375
  const entry = await strapi.documents(contentTypeID).findFirst(query);
320
- return entry;
376
+ if (entry) {
377
+ strapi.log.info("Obtained " + contentTypeID + "::" + entryID + " in " + locale);
378
+ return entry;
379
+ }
321
380
  } catch (error) {
322
- console.error("Entry fetch error:", error);
323
- return null;
381
+ strapi.log.error(error.message ?? error);
324
382
  }
383
+ strapi.log.warn("Could not find entry " + entryID + " in locale " + locale);
384
+ return null;
385
+ };
386
+ const transformResponse = function(data) {
387
+ data.fields = data.fields.map(
388
+ (item) => item.realType === "blocks" && Array.isArray(item.translatableValue[0]) ? { ...item, translatableValue: item.translatableValue[0] } : item
389
+ );
390
+ return data;
325
391
  };
326
- const transformResponse = (data) => data.map(
327
- (item) => item.realType === "blocks" && Array.isArray(item.translatableValue[0]) ? { ...item, translatableValue: item.translatableValue[0] } : item
328
- );
329
392
  function jsonToHtml(json) {
330
393
  if (!json || !Array.isArray(json)) {
331
394
  return "";
@@ -374,43 +437,37 @@ function formatText(child) {
374
437
  }
375
438
  return text;
376
439
  }
377
- const processComponent = async (fieldName, componentName, value, schemaName, componentId) => {
440
+ const Logger$4 = {
441
+ log: typeof strapi !== "undefined" ? strapi.log : console,
442
+ info: (val) => Logger$4.log.info(val),
443
+ warn: (val) => Logger$4.log.warn(val),
444
+ error: (val) => Logger$4.log.error(val),
445
+ debug: (val) => Logger$4.log.debug(val)
446
+ };
447
+ async function processComponent(fieldName, value, componentSchema, schemata) {
378
448
  const contentFields = [];
379
- const componentSchema = await strapi.components[componentName];
380
- if (!componentSchema) {
381
- throw new Error(`Component schema not found for ${componentName}`);
382
- }
449
+ Logger$4.info("Processing dynamic field " + fieldName);
450
+ if (!componentSchema || !componentSchema.attributes)
451
+ return [];
383
452
  const schemaAttributes = componentSchema.attributes || {};
384
453
  const dataToProcess = value || {};
385
- if (Array.isArray(dataToProcess)) {
386
- for (const item of dataToProcess) {
387
- const processedFields = await processComponentFields$1(
388
- item,
389
- schemaAttributes,
390
- fieldName,
391
- componentName,
392
- schemaName,
393
- item.id
394
- );
395
- contentFields.push(...processedFields);
396
- }
397
- } else {
398
- const processedFields = await processComponentFields$1(
399
- dataToProcess,
454
+ const candidates = Array.isArray(dataToProcess) ? dataToProcess : [dataToProcess];
455
+ for (const item of candidates) {
456
+ const processedFields = await processComponentFields(
457
+ item,
400
458
  schemaAttributes,
401
459
  fieldName,
402
- componentName,
403
- schemaName,
404
- componentId
460
+ schemata
405
461
  );
406
- contentFields.push(...processedFields);
462
+ if (processedFields.length > 0)
463
+ contentFields.push(...processedFields);
407
464
  }
408
465
  return contentFields;
409
- };
466
+ }
410
467
  const shouldSkipField$1 = (key, fieldSchema) => {
411
468
  return key === "id" || fieldSchema.private;
412
469
  };
413
- const isTranslatableField$1 = (type) => {
470
+ const isTranslatableField = (type) => {
414
471
  return ["string", "text", "blocks", "richtext"].includes(type);
415
472
  };
416
473
  const getTranslatedValue = (type, value) => {
@@ -419,64 +476,61 @@ const getTranslatedValue = (type, value) => {
419
476
  }
420
477
  return value.toString();
421
478
  };
422
- const buildTranslatable = (key, fieldSchema, value, parentPath, componentId, schemaName) => {
479
+ const buildTranslatable = (key, fieldSchema, value, uuid = "") => {
423
480
  return {
424
481
  field: key,
425
482
  type: ["richtext", "blocks"].includes(fieldSchema.type) ? "html" : "text",
426
483
  translatableValue: [value],
427
484
  realType: fieldSchema.type,
428
- componentInfo: {
429
- namePath: parentPath,
430
- id: componentId,
431
- schemaName
432
- }
485
+ uuid
433
486
  };
434
487
  };
435
- const processComponentFields$1 = async (componentData, schema, parentField, componentName, schemaName, componentId) => {
488
+ const processComponentFields = async (componentData, schema, parentField, schemata) => {
436
489
  const contentFields = [];
437
- const parentPath = parentField.split(".");
490
+ const uuid = crypto__namespace.randomUUID();
438
491
  for (const [key, fieldSchema] of Object.entries(schema)) {
439
492
  if (shouldSkipField$1(key, fieldSchema)) continue;
440
493
  const value = componentData?.[key];
441
- const fieldPath = `${parentField}.${key}`;
494
+ if (!value)
495
+ continue;
442
496
  if (fieldSchema.type === "component") {
443
- if (!fieldSchema.component) continue;
497
+ if (!value.__component)
498
+ continue;
499
+ const targetSchema = schemata[value.__component];
500
+ if (!targetSchema)
501
+ continue;
444
502
  const nestedFields = await processComponent(
445
- fieldPath,
446
- fieldSchema.component,
447
- value || {},
448
- fieldSchema.component,
449
- value?.id
503
+ `${parentField}.${key}`,
504
+ value,
505
+ targetSchema,
506
+ schemata
450
507
  );
451
- contentFields.push(...nestedFields);
508
+ if (nestedFields.length > 0)
509
+ contentFields.push(...nestedFields);
452
510
  continue;
453
511
  }
454
- if (!isTranslatableField$1(fieldSchema.type)) continue;
512
+ if (!isTranslatableField(fieldSchema.type)) continue;
455
513
  if (value === null || value === void 0 || value === "") continue;
456
514
  const translatedValue = getTranslatedValue(fieldSchema.type, value);
457
515
  const translatable = buildTranslatable(
458
516
  key,
459
517
  fieldSchema,
460
518
  translatedValue,
461
- parentPath,
462
- componentId,
463
- schemaName
519
+ uuid
464
520
  );
521
+ componentData.__tsuid = uuid;
465
522
  contentFields.push(translatable);
466
523
  }
467
524
  return contentFields;
468
525
  };
469
- const isFieldLocalizable = (fieldSchema, parentSchema) => {
470
- if (fieldSchema.pluginOptions?.i18n?.localized !== void 0) {
471
- return fieldSchema.pluginOptions.i18n.localized;
472
- }
473
- if (parentSchema.pluginOptions?.i18n?.localized === true) {
474
- const localizableTypes = ["string", "text", "blocks", "richtext"];
475
- return localizableTypes.includes(fieldSchema.type);
476
- }
477
- return false;
526
+ const Logger$3 = {
527
+ log: typeof strapi !== "undefined" ? strapi.log : console,
528
+ info: (val) => Logger$3.log.info(val),
529
+ warn: (val) => Logger$3.log.warn(val),
530
+ error: (val) => Logger$3.log.error(val),
531
+ debug: (val) => Logger$3.log.debug(val)
478
532
  };
479
- const DEFAULT_FIELDS = /* @__PURE__ */ new Set([
533
+ const DEFAULT_FIELDS$1 = /* @__PURE__ */ new Set([
480
534
  "id",
481
535
  "documentId",
482
536
  "createdAt",
@@ -488,45 +542,41 @@ const DEFAULT_FIELDS = /* @__PURE__ */ new Set([
488
542
  "createdBy"
489
543
  ]);
490
544
  const isEmpty = (value) => value === null || value === void 0 || value === "";
491
- const isTranslatableField = (fieldSchema) => ["string", "text", "blocks", "richtext"].includes(fieldSchema.type) && fieldSchema.pluginOptions?.i18n?.localized !== false;
492
- const processDynamicZone = async (key, value, schema) => {
545
+ const isSimpleTranslatableField = (fieldSchema) => ["string", "text", "blocks", "richtext"].includes(fieldSchema.type) && fieldSchema.pluginOptions?.i18n?.localized === true;
546
+ const processDynamicZone = async (key, value, schemata) => {
493
547
  const results = [];
494
548
  for (const component of value) {
495
549
  const componentName = component?.__component;
496
550
  if (!componentName) continue;
551
+ const schema = schemata[componentName];
552
+ if (!schema) continue;
497
553
  const fields = await processComponent(
498
554
  key,
499
- componentName,
500
555
  component,
501
- componentName,
502
- component.id
556
+ schema,
557
+ schemata
503
558
  );
504
- results.push(...fields);
559
+ if (fields.length > 0)
560
+ results.push(...fields);
505
561
  }
506
562
  return results;
507
563
  };
508
- const processComponentField = async (key, value, fieldSchema) => {
564
+ const processComponentField = async (key, value, fieldSchema, schemata) => {
509
565
  const results = [];
510
- if (fieldSchema.repeatable && Array.isArray(value)) {
511
- for (const component of value) {
512
- const fields = await processComponent(
513
- key,
514
- fieldSchema.component,
515
- component,
516
- fieldSchema.component,
517
- component.id
518
- );
519
- results.push(...fields);
520
- }
521
- } else {
566
+ const candidates = Array.isArray(value) ? value : [value];
567
+ for (const component of candidates) {
568
+ const componentName = component?.__component;
569
+ if (!componentName) continue;
570
+ const schema = schemata[componentName];
571
+ if (!schema) continue;
522
572
  const fields = await processComponent(
523
573
  key,
524
- fieldSchema.component,
525
- value,
526
- fieldSchema.component,
527
- value.id
574
+ component,
575
+ schema,
576
+ schemata
528
577
  );
529
- results.push(...fields);
578
+ if (fields.length > 0)
579
+ results.push(...fields);
530
580
  }
531
581
  return results;
532
582
  };
@@ -539,38 +589,148 @@ const processRegularField = (key, value, fieldSchema) => {
539
589
  realType: fieldSchema.type
540
590
  };
541
591
  };
542
- const processEntryFields = async (entry, schema, locale) => {
592
+ function IsLocalisableSchema(schema) {
593
+ return schema.pluginOptions?.i18n?.localized === true;
594
+ }
595
+ function IsLocalisedField(field) {
596
+ return field.pluginOptions?.i18n?.localized === true;
597
+ }
598
+ const processEntryFields = async (entry, schemaData, _locale) => {
543
599
  const contentFields = [];
600
+ const staticContent = {};
601
+ const schema = schemaData.entry.attributes;
544
602
  for (const [key, value] of Object.entries(entry)) {
545
- if (shouldSkipField(key, value)) continue;
546
- const fieldSchema = schema[key];
547
- if (!fieldSchema) continue;
548
- if (isDynamicZone(fieldSchema, value, schema)) {
549
- const zoneFields = await processDynamicZone(key, value);
550
- contentFields.push(...zoneFields);
603
+ if (shouldSkipField(key, value))
551
604
  continue;
552
- }
553
- if (isComponent(fieldSchema, value, schema)) {
554
- const componentFields = await processComponentField(key, value, fieldSchema);
555
- contentFields.push(...componentFields);
605
+ const fieldSchema = schema[key];
606
+ if (!fieldSchema || !IsLocalisedField(fieldSchema)) {
607
+ Logger$3.debug("SKipping non-local field " + key);
556
608
  continue;
557
609
  }
558
- if (isTranslatableField(fieldSchema)) {
610
+ if (isSimpleTranslatableField(fieldSchema)) {
611
+ Logger$3.debug("Processing simple field " + key);
559
612
  const translatedField = processRegularField(key, value, fieldSchema);
560
613
  contentFields.push(translatedField);
614
+ continue;
615
+ }
616
+ const zoneInfo = isDynamicZone(fieldSchema, value);
617
+ if (zoneInfo.isZone) {
618
+ if (zoneInfo.hasContent) {
619
+ Logger$3.debug("Processing dynamic zone field " + key);
620
+ const zoneFields = await processDynamicZone(key, value, schemaData.components);
621
+ contentFields.push(...zoneFields);
622
+ staticContent[key] = value;
623
+ }
624
+ continue;
625
+ }
626
+ const componentInfo = isComponent(fieldSchema);
627
+ if (componentInfo.isZone) {
628
+ const componentFields = await processComponentField(key, value, fieldSchema, schemaData.components);
629
+ contentFields.push(...componentFields);
630
+ staticContent[key] = value;
561
631
  }
562
632
  }
563
- return contentFields;
633
+ Logger$3.info("Process completed");
634
+ return {
635
+ fields: contentFields,
636
+ keep: staticContent
637
+ };
564
638
  };
565
639
  const shouldSkipField = (key, value) => {
566
- return DEFAULT_FIELDS.has(key) || isEmpty(value);
640
+ return DEFAULT_FIELDS$1.has(key) || isEmpty(value);
641
+ };
642
+ const isDynamicZone = (fieldSchema, value) => {
643
+ return {
644
+ isZone: fieldSchema.type === "dynamiczone",
645
+ hasContent: Array.isArray(value) && value.length > 0
646
+ };
567
647
  };
568
- const isDynamicZone = (fieldSchema, value, schema) => {
569
- return fieldSchema.type === "dynamiczone" && isFieldLocalizable(fieldSchema, schema) && Array.isArray(value);
648
+ const isComponent = (fieldSchema) => {
649
+ return {
650
+ isZone: fieldSchema.type === "dynamiczone",
651
+ hasContent: true
652
+ };
570
653
  };
571
- const isComponent = (fieldSchema, value, schema) => {
572
- return fieldSchema.type === "component" && isFieldLocalizable(fieldSchema, schema);
654
+ const Logger$2 = {
655
+ log: typeof strapi !== "undefined" ? strapi.log : console,
656
+ info: (val) => Logger$2.log.info(val),
657
+ warn: (val) => Logger$2.log.warn(val),
658
+ error: (val) => Logger$2.log.error(val),
659
+ debug: (val) => Logger$2.log.debug(val)
573
660
  };
661
+ const DEFAULT_FIELDS = [
662
+ "id",
663
+ "documentId",
664
+ "createdAt",
665
+ "updatedAt",
666
+ ,
667
+ "createdBy",
668
+ "updatedBy",
669
+ "publishedAt",
670
+ "locale",
671
+ "localizations"
672
+ ];
673
+ const getContentFields = function(schema) {
674
+ const nullFields = [];
675
+ for (let field in schema) {
676
+ if (!DEFAULT_FIELDS.includes(field))
677
+ nullFields.push(field);
678
+ }
679
+ return nullFields;
680
+ };
681
+ const getInvalidOrNullFields = function(document, schema) {
682
+ if (!document)
683
+ return getContentFields(schema);
684
+ const nullFields = [];
685
+ let fieldsValid = 0;
686
+ for (let field in document) {
687
+ if (DEFAULT_FIELDS.includes(field))
688
+ continue;
689
+ if (document[field] === null)
690
+ nullFields.push(field);
691
+ else
692
+ fieldsValid++;
693
+ }
694
+ if (nullFields.length > 0 || fieldsValid > 0)
695
+ return nullFields;
696
+ return getContentFields(schema);
697
+ };
698
+ function appendMissingFields(data, sourceEntry, targetSchema, targetEntry) {
699
+ const nullFields = getInvalidOrNullFields(targetEntry, targetSchema.entry.attributes);
700
+ if (nullFields.length === 0)
701
+ return;
702
+ let count = 0;
703
+ Logger$2.info("Adding missing fields to new locale: " + nullFields.join(", "));
704
+ for (let field of nullFields) {
705
+ if (data[field]) {
706
+ Logger$2.info("Field already present: " + field);
707
+ continue;
708
+ }
709
+ if (!sourceEntry[field]) {
710
+ Logger$2.info("No valid source langauge field value for " + field + " - skipping it.");
711
+ continue;
712
+ }
713
+ if (!targetSchema.entry.attributes[field]) {
714
+ Logger$2.warn("Schema does not contain field " + field);
715
+ continue;
716
+ }
717
+ Logger$2.info("Adding missing field and value for " + field);
718
+ data[field] = sourceEntry[field];
719
+ count++;
720
+ }
721
+ if (count > 0)
722
+ Logger$2.info(count + " missing fields added.");
723
+ }
724
+ async function updateEntry(contentTypeID, entryID, targetLocale, data) {
725
+ strapi.log.info("Updating target entry " + contentTypeID + "::" + entryID + " in locale " + targetLocale);
726
+ const newEntry = await strapi.documents(contentTypeID).update({
727
+ documentId: entryID,
728
+ locale: targetLocale,
729
+ data
730
+ });
731
+ if (!newEntry)
732
+ throw new Error("Cannot update target entry " + contentTypeID + "::" + entryID + " in locale " + targetLocale);
733
+ }
574
734
  function htmlToJson(html) {
575
735
  function parseHTML(html2) {
576
736
  const elements2 = [];
@@ -763,227 +923,171 @@ function htmlToJson(html) {
763
923
  }
764
924
  return blocks;
765
925
  }
766
- async function updateEntry(contentTypeID, entryID, sourceLocale, targetLocale, data, attributes) {
767
- if (!entryID) {
768
- const singleTypeData = await strapi.documents(contentTypeID).findFirst();
769
- entryID = singleTypeData.documentId;
770
- }
771
- const originalEntry = await strapi.documents(contentTypeID).findFirst({
772
- documentId: entryID,
773
- locale: sourceLocale
774
- });
775
- const processedData = processDataRecursively(data);
776
- for (const [key, value] of Object.entries(processedData)) {
777
- if (attributes[key]?.type === "blocks" && typeof value === "string") {
778
- console.warn(
779
- `Field ${key} is a blocks field but received string value. Converting to blocks format.`
780
- );
781
- processedData[key] = htmlToJson(value);
926
+ const Logger$1 = {
927
+ log: typeof strapi !== "undefined" ? strapi.log : console,
928
+ info: (val) => Logger$1.log.info(val),
929
+ warn: (val) => Logger$1.log.warn(val),
930
+ error: (val) => Logger$1.log.error(val),
931
+ debug: (val) => Logger$1.log.debug(val)
932
+ };
933
+ const removeComponentIds = function(elem) {
934
+ const list = Array.isArray(elem) ? elem : [elem];
935
+ for (let obj of list) {
936
+ if (obj.__component && obj.id)
937
+ delete obj.id;
938
+ for (let key in obj) {
939
+ const child = obj[key];
940
+ if (Array.isArray(child) && Array.length > 0)
941
+ removeComponentIds(child);
942
+ else if (typeof child === "object")
943
+ removeComponentIds(child);
782
944
  }
783
945
  }
784
- const localizedData = {};
785
- for (const field in processedData) {
786
- if (attributes[field] && (!attributes[field].pluginOptions?.i18n || attributes[field].pluginOptions?.i18n?.localized !== false)) {
787
- localizedData[field] = processedData[field];
946
+ };
947
+ function replaceDynamicZones(strapiEntry, replacableFields) {
948
+ const fields = [];
949
+ for (let key in strapiEntry) {
950
+ if (replacableFields[key]) {
951
+ removeComponentIds(replacableFields[key]);
952
+ strapiEntry[key] = replacableFields[key];
953
+ fields.push(key);
788
954
  }
789
955
  }
790
- await strapi.documents(contentTypeID).update({
791
- documentId: entryID,
792
- locale: targetLocale,
793
- data: localizedData
794
- });
795
- if (originalEntry.publishedAt !== null) {
796
- await strapi.documents(contentTypeID).publish({
797
- documentId: entryID,
798
- locale: sourceLocale
799
- });
800
- }
956
+ if (fields.length > 0)
957
+ Logger$1.info(fields.length + " dynamic fields/components replaced for later merge: " + fields.join(", "));
958
+ return fields;
801
959
  }
802
- function processDataRecursively(data, schema) {
803
- if (!data || typeof data !== "object") {
804
- return data;
960
+ const mergeValue = function(field, translatable, targetSchema, map) {
961
+ if (translatable.translatableValue.length === 0)
962
+ return false;
963
+ if (targetSchema.attributes[field] === void 0) {
964
+ Logger$1.info("Field " + field + " does not exist in schema. Skipping it");
965
+ Logger$1.info(targetSchema);
966
+ return false;
805
967
  }
806
- if (Array.isArray(data)) {
807
- if (data[0]?.fields) {
808
- const processedFields = {};
809
- for (const fieldData of data[0].fields) {
810
- if (fieldData.realType === "blocks") {
811
- if (fieldData.translatableValue?.[0]) {
812
- processedFields[fieldData.field] = htmlToJson(fieldData.translatableValue[0]);
813
- }
814
- } else {
815
- processedFields[fieldData.field] = fieldData.translatableValue?.[0] || null;
816
- }
817
- }
818
- return processedFields;
819
- }
820
- return data.map((item) => processDataRecursively(item));
968
+ if (translatable.translatableValue[0] === "") {
969
+ Logger$1.info("Skipping empty translated content for field " + field);
970
+ return false;
821
971
  }
822
- const result = {};
823
- for (const key in data) {
824
- result[key] = processDataRecursively(data[key]);
972
+ if (translatable.realType === "blocks") {
973
+ Logger$1.info("Merge block field " + field);
974
+ map[field] = htmlToJson(translatable.translatableValue[0] || "");
975
+ return true;
825
976
  }
826
- return result;
827
- }
828
- function organizeFields(fields) {
829
- const componentFieldsMap = /* @__PURE__ */ new Map();
830
- const dynamicZoneFields = /* @__PURE__ */ new Map();
831
- const regularFields = [];
832
- fields.forEach((field) => {
833
- if (!field.componentInfo) {
834
- regularFields.push(field);
835
- return;
836
- }
837
- const { namePath, id } = field.componentInfo;
838
- const pathString = namePath.join(".");
839
- if (namePath[0] === "dynamiczone") {
840
- if (!dynamicZoneFields.has(id)) {
841
- dynamicZoneFields.set(id, []);
842
- }
843
- dynamicZoneFields.get(id)?.push(field);
844
- } else {
845
- if (!componentFieldsMap.has(pathString)) {
846
- componentFieldsMap.set(pathString, []);
847
- }
848
- componentFieldsMap.get(pathString)?.push(field);
849
- }
850
- });
851
- return { regularFields, componentFieldsMap, dynamicZoneFields };
852
- }
853
- function processRegularFields(regularFields, acc) {
854
- regularFields.forEach((field) => {
855
- acc[field.field] = field.translatableValue[0];
856
- });
857
- return acc;
858
- }
859
- function processRepeatableComponents(fields, existingEntry, rootPath) {
860
- const existingComponents = existingEntry?.[rootPath] || [];
861
- const componentsById = /* @__PURE__ */ new Map();
862
- fields.forEach((field) => {
863
- if (!field.componentInfo) {
864
- console.warn(`Component info missing for field: ${field.field}`);
865
- return;
866
- }
867
- const componentId = field.componentInfo.id;
868
- if (!componentsById.has(componentId)) {
869
- const existingComponent = existingComponents.find((c) => c.id === componentId);
870
- componentsById.set(componentId, existingComponent ? { ...existingComponent } : {});
871
- }
872
- const component = componentsById.get(componentId);
873
- if (field.realType === "blocks") {
874
- component[field.field] = htmlToJson(field.translatableValue[0] || "");
875
- } else {
876
- component[field.field] = field.translatableValue[0];
877
- }
878
- });
879
- return Array.from(componentsById.values()).map((comp) => {
880
- if (!existingComponents.find((ec) => ec.id === comp.id)) {
881
- const { id, ...rest } = comp;
882
- return rest;
883
- }
884
- return comp;
885
- }).filter((comp) => Object.keys(comp).length > 0);
886
- }
887
- function processNestedComponents(fields, pathParts, existingEntry, acc) {
888
- let current = acc;
889
- let currentExisting = existingEntry;
890
- pathParts.forEach((part, index2) => {
891
- if (!current[part]) {
892
- current[part] = {};
893
- if (currentExisting?.[part]?.id) {
894
- current[part].id = currentExisting[part].id;
895
- }
896
- }
897
- if (index2 === pathParts.length - 1) {
898
- fields.forEach((field) => {
899
- if (field.realType === "blocks") {
900
- current[part][field.field] = htmlToJson(field.translatableValue[0] || "");
901
- } else {
902
- current[part][field.field] = field.translatableValue[0];
903
- }
904
- });
905
- } else {
906
- current = current[part];
907
- currentExisting = currentExisting?.[part];
908
- }
909
- });
910
- }
911
- function processComponentFields(componentFieldsMap, acc, existingEntry, targetSchema) {
912
- componentFieldsMap.forEach((fields, namePath) => {
913
- if (!fields.length) return;
914
- const pathParts = namePath.split(".");
915
- const rootPath = pathParts[0];
916
- const schema = targetSchema.attributes?.[rootPath];
917
- if (schema?.repeatable) {
918
- acc[rootPath] = processRepeatableComponents(fields, existingEntry, rootPath);
919
- } else {
920
- processNestedComponents(fields, pathParts, existingEntry, acc);
977
+ if (translatable.type === "text") {
978
+ Logger$1.info("Merge text field " + field);
979
+ map[field] = translatable.translatableValue[0];
980
+ return true;
981
+ }
982
+ Logger$1.warn("Did not process " + field);
983
+ return false;
984
+ };
985
+ const mergeSimpleFields = function(translatables, existingEntry, targetSchema, map) {
986
+ let count = 0;
987
+ for (const candidate of translatables) {
988
+ const field = candidate.field;
989
+ if (!candidate.uuid && mergeValue(field, candidate, targetSchema.entry, map))
990
+ count++;
991
+ }
992
+ if (count > 0) {
993
+ Logger$1.info("Updated " + count + " simple text fields");
994
+ return true;
995
+ }
996
+ return false;
997
+ };
998
+ const buildMapOfUuids = function(existingEntry, schemaMap, map) {
999
+ if (typeof existingEntry !== "object")
1000
+ return map;
1001
+ const componentName = existingEntry["__component"] ?? "";
1002
+ const schema = componentName && schemaMap.components[componentName] ? schemaMap.components[componentName] : null;
1003
+ if (componentName && !schema)
1004
+ Logger$1.warn("Cannot find component schema " + componentName);
1005
+ for (const key of Object.keys(existingEntry)) {
1006
+ if (key === "__component")
1007
+ continue;
1008
+ if (schema !== null && key === "__tsuid") {
1009
+ map[existingEntry[key]] = {
1010
+ entry: existingEntry,
1011
+ schema
1012
+ };
1013
+ continue;
921
1014
  }
922
- });
923
- return acc;
924
- }
925
- function transformFieldsToData(fields) {
926
- return fields.reduce((acc, field) => {
927
- if (!field.translatableValue) {
928
- acc[field.field] = "";
929
- return acc;
1015
+ const child = existingEntry[key];
1016
+ if (child) {
1017
+ if (Array.isArray(child)) {
1018
+ for (let e of child)
1019
+ buildMapOfUuids(e, schemaMap, map);
1020
+ } else
1021
+ buildMapOfUuids(child, schemaMap, map);
930
1022
  }
931
- if (field.realType === "blocks") {
932
- acc[field.field] = htmlToJson(field.translatableValue[0] || "");
933
- } else if (field.realType === "richtext") {
934
- acc[field.field] = field.translatableValue[0] || "";
935
- } else {
936
- acc[field.field] = Array.isArray(field.translatableValue) && field.translatableValue.length > 1 ? field.translatableValue.join(" ") : field.translatableValue[0] || "";
1023
+ }
1024
+ if (existingEntry["__tsuid"]) {
1025
+ delete existingEntry["__tsuid"];
1026
+ Logger$1.info("Removed cusom property __tsuid");
1027
+ }
1028
+ return map;
1029
+ };
1030
+ const mergeDynamicZones = function(translatables, schemaMap, existingEntry) {
1031
+ if (translatables.length === 0) {
1032
+ Logger$1.info("Skipping merging of dynamic zones, because none are present.");
1033
+ return;
1034
+ }
1035
+ const map = {};
1036
+ buildMapOfUuids(existingEntry, schemaMap, map);
1037
+ const mapSize = Object.keys(map).length;
1038
+ if (mapSize === 0) {
1039
+ Logger$1.warn("Could not create a uuid map");
1040
+ return false;
1041
+ }
1042
+ Logger$1.info("Built uuid map with " + mapSize + " entry(s)");
1043
+ let count = 0;
1044
+ for (const translatable of translatables) {
1045
+ if (!translatable.uuid)
1046
+ continue;
1047
+ const uuid = translatable.uuid;
1048
+ if (!map[uuid])
1049
+ continue;
1050
+ const entry = map[uuid];
1051
+ const schema = entry.schema;
1052
+ if (!schema) {
1053
+ Logger$1.warn("Cannot find schema by uuid #" + uuid);
1054
+ continue;
937
1055
  }
938
- return acc;
939
- }, {});
940
- }
941
- function processDynamicZones(dynamicZoneFields, acc, existingEntry) {
942
- if (dynamicZoneFields.size > 0) {
943
- const existingDynamicZone = existingEntry?.dynamiczone || [];
944
- acc.dynamiczone = Array.from(dynamicZoneFields.entries()).sort(([a], [b]) => a - b).map(([_, fields]) => {
945
- if (!fields[0].componentInfo) {
946
- console.warn(
947
- `Component info missing for dynamic zone field: ${fields[0].field}`
948
- );
949
- return null;
950
- }
951
- const { schemaName } = fields[0].componentInfo;
952
- const componentData = transformFieldsToData(fields);
953
- const matchingComponent = existingDynamicZone.find(
954
- (comp) => comp.__component === schemaName
955
- );
956
- return {
957
- __component: schemaName,
958
- ...componentData,
959
- ...matchingComponent?.id ? { id: matchingComponent.id } : {}
960
- };
961
- }).filter(Boolean);
1056
+ if (mergeValue(translatable.field, translatable, schema, entry.entry))
1057
+ count++;
962
1058
  }
963
- return acc;
964
- }
965
- function prepareImportData(translatables, existingEntry, targetSchema) {
966
- return translatables.reduce((acc, doc) => {
967
- const { regularFields, componentFieldsMap, dynamicZoneFields } = organizeFields(translatables);
968
- const withRegularFields = processRegularFields(regularFields, acc);
969
- const withComponentFields = processComponentFields(
970
- componentFieldsMap,
971
- withRegularFields,
972
- existingEntry,
973
- targetSchema
974
- );
975
- const withDynamicZones = processDynamicZones(
976
- dynamicZoneFields,
977
- withComponentFields,
978
- existingEntry
979
- );
980
- return withDynamicZones;
981
- }, {});
1059
+ if (count > 0) {
1060
+ Logger$1.info("Updated " + count + " entries in dynamic zones/content blocks");
1061
+ return true;
1062
+ }
1063
+ return false;
1064
+ };
1065
+ function prepareImportData(translatables, keepData, existingEntry, targetSchema) {
1066
+ const result = {};
1067
+ const simpleUpdated = mergeSimpleFields(translatables, existingEntry, targetSchema, result);
1068
+ let otherUpdated = false;
1069
+ const vsFields = replaceDynamicZones(existingEntry, keepData);
1070
+ if (vsFields.length > 0) {
1071
+ if (mergeDynamicZones(translatables, targetSchema, existingEntry)) {
1072
+ vsFields.forEach((field) => result[field] = existingEntry[field]);
1073
+ otherUpdated = true;
1074
+ } else
1075
+ Logger$1.warn("Could not merge dynamic fields");
1076
+ }
1077
+ if (simpleUpdated || otherUpdated)
1078
+ return result;
1079
+ else
1080
+ return null;
982
1081
  }
983
- require("jsonwebtoken");
984
- const crypto = require("crypto");
985
1082
  const TRANSLATIONTUDIO_URL = "https://strapi.translationstudio.tech";
986
1083
  const APP_NAME = "translationstudio";
1084
+ const Logger = {
1085
+ log: typeof strapi !== "undefined" ? strapi.log : console,
1086
+ info: (val) => Logger.log.info(val),
1087
+ warn: (val) => Logger.log.warn(val),
1088
+ error: (val) => Logger.log.error(val),
1089
+ debug: (val) => Logger.log.debug(val)
1090
+ };
987
1091
  const service = ({ strapi: strapi2 }) => {
988
1092
  const pluginStore = strapi2.store({
989
1093
  type: "plugin",
@@ -1001,7 +1105,7 @@ const service = ({ strapi: strapi2 }) => {
1001
1105
  if (typeof result === "string" && result !== "")
1002
1106
  return result;
1003
1107
  } catch (err) {
1004
- console.warn(err);
1108
+ strapi2.log.warn(err);
1005
1109
  }
1006
1110
  return TRANSLATIONTUDIO_URL;
1007
1111
  },
@@ -1046,7 +1150,7 @@ const service = ({ strapi: strapi2 }) => {
1046
1150
  }
1047
1151
  },
1048
1152
  async generateToken() {
1049
- const secretKey = crypto.randomBytes(64).toString("hex");
1153
+ const secretKey = crypto__namespace.randomBytes(64).toString("hex");
1050
1154
  await pluginStore.set({
1051
1155
  key: "token",
1052
1156
  value: secretKey
@@ -1064,9 +1168,15 @@ const service = ({ strapi: strapi2 }) => {
1064
1168
  },
1065
1169
  async exportData(payload) {
1066
1170
  const { contentTypeID, entryID, locale } = parsePayload(payload);
1067
- const contentType = await getContentType(contentTypeID);
1171
+ const contentType = getContentType(contentTypeID);
1172
+ if (contentType === null || !IsLocalisableSchema(contentType.entry)) {
1173
+ return {
1174
+ fields: [],
1175
+ keep: {}
1176
+ };
1177
+ }
1068
1178
  const entry = await getEntry(contentTypeID, entryID, locale);
1069
- const contentFields = await processEntryFields(entry, contentType.attributes);
1179
+ const contentFields = await processEntryFields(entry, contentType);
1070
1180
  return transformResponse(contentFields);
1071
1181
  },
1072
1182
  async importData(payload) {
@@ -1074,23 +1184,32 @@ const service = ({ strapi: strapi2 }) => {
1074
1184
  const sourceLocale = payload.source;
1075
1185
  const targetLocale = payload.target;
1076
1186
  try {
1077
- const existingEntry = await getEntry(contentTypeID, entryID, targetLocale);
1078
- const targetSchema = await getContentType(contentTypeID);
1079
- const data = prepareImportData(payload.document[0].fields, existingEntry, targetSchema);
1080
- if (targetSchema.pluginOptions.i18n.localized === true) {
1081
- await updateEntry(
1082
- contentTypeID,
1083
- entryID,
1084
- sourceLocale,
1085
- targetLocale,
1086
- data,
1087
- targetSchema.attributes
1088
- );
1089
- }
1090
- return { success: true };
1187
+ const sourceEntry = await getEntry(contentTypeID, entryID, sourceLocale);
1188
+ if (sourceEntry == null)
1189
+ throw new Error("Cannot find source entry " + contentTypeID + "::" + entryID + " in " + sourceLocale);
1190
+ const targetSchema = getContentType(contentTypeID);
1191
+ if (targetSchema === null || !IsLocalisableSchema(targetSchema.entry))
1192
+ throw new Error("Cannot find schema");
1193
+ const data = prepareImportData(
1194
+ payload.document[0].fields,
1195
+ payload.document[0].keep ?? {},
1196
+ sourceEntry,
1197
+ targetSchema
1198
+ );
1199
+ strapi2.log.info("Loading target language entry");
1200
+ const targetLocaleEntry = await getEntry(contentTypeID, entryID, targetLocale);
1201
+ appendMissingFields(data, sourceEntry, targetSchema, targetLocaleEntry);
1202
+ await updateEntry(
1203
+ contentTypeID,
1204
+ entryID,
1205
+ targetLocale,
1206
+ data
1207
+ );
1208
+ return true;
1091
1209
  } catch (error) {
1092
- return { success: false };
1210
+ strapi2.log.error(error);
1093
1211
  }
1212
+ return false;
1094
1213
  },
1095
1214
  async requestTranslation(payload) {
1096
1215
  const { license } = await this.getLicense();