@translationstudio/translationstudio-strapi-extension 1.0.0 → 1.0.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.
Files changed (54) hide show
  1. package/README.md +35 -32
  2. package/dist/Types.d.ts +146 -0
  3. package/dist/_chunks/App-Bw7t0TMJ.mjs +277 -0
  4. package/dist/_chunks/App-CUOeu4IU.js +277 -0
  5. package/dist/_chunks/en-B4KWt_jN.js +4 -0
  6. package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
  7. package/dist/admin/index.js +323 -0
  8. package/dist/admin/index.mjs +324 -0
  9. package/dist/admin/src/components/Initializer.d.ts +5 -0
  10. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  11. package/dist/admin/src/components/TranslationMenu.d.ts +2 -0
  12. package/dist/admin/src/index.d.ts +11 -0
  13. package/dist/admin/src/pages/App.d.ts +2 -0
  14. package/dist/admin/src/pages/SettingsPage.d.ts +2 -0
  15. package/dist/admin/src/pluginId.d.ts +1 -0
  16. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  17. package/dist/server/index.js +1038 -0
  18. package/dist/server/index.mjs +1039 -0
  19. package/dist/server/src/bootstrap.d.ts +5 -0
  20. package/dist/server/src/config/index.d.ts +5 -0
  21. package/dist/server/src/content-types/index.d.ts +2 -0
  22. package/dist/server/src/controllers/controller.d.ts +19 -0
  23. package/dist/server/src/controllers/index.d.ts +20 -0
  24. package/dist/server/src/destroy.d.ts +5 -0
  25. package/dist/server/src/index.d.ts +85 -0
  26. package/dist/server/src/middlewares/index.d.ts +2 -0
  27. package/dist/server/src/policies/index.d.ts +2 -0
  28. package/dist/server/src/register.d.ts +5 -0
  29. package/dist/server/src/routes/index.d.ts +18 -0
  30. package/dist/server/src/services/functions/exportData/extractRichtextContent.d.ts +2 -0
  31. package/dist/server/src/services/functions/exportData/getContentType.d.ts +2 -0
  32. package/dist/server/src/services/functions/exportData/getEntry.d.ts +2 -0
  33. package/dist/server/src/services/functions/exportData/isFieldLocalizable.d.ts +2 -0
  34. package/dist/server/src/services/functions/exportData/jsonToHtml.d.ts +2 -0
  35. package/dist/server/src/services/functions/exportData/parsePayload.d.ts +7 -0
  36. package/dist/server/src/services/functions/exportData/processComponent.d.ts +3 -0
  37. package/dist/server/src/services/functions/exportData/processEntryFields.d.ts +2 -0
  38. package/dist/server/src/services/functions/exportData/transformResponse.d.ts +2 -0
  39. package/dist/server/src/services/functions/importData/extract.d.ts +6 -0
  40. package/dist/server/src/services/functions/importData/handleError.d.ts +1 -0
  41. package/dist/server/src/services/functions/importData/htmlToJson.d.ts +2 -0
  42. package/dist/server/src/services/functions/importData/organizeFields.d.ts +2 -0
  43. package/dist/server/src/services/functions/importData/parseImportPayload.d.ts +6 -0
  44. package/dist/server/src/services/functions/importData/parseInlineElements.d.ts +2 -0
  45. package/dist/server/src/services/functions/importData/parseNewValuesIntoStructure.d.ts +1 -0
  46. package/dist/server/src/services/functions/importData/prepareImportData.d.ts +2 -0
  47. package/dist/server/src/services/functions/importData/processComponentFields.d.ts +2 -0
  48. package/dist/server/src/services/functions/importData/processDynamicZones.d.ts +2 -0
  49. package/dist/server/src/services/functions/importData/processRegularFields.d.ts +2 -0
  50. package/dist/server/src/services/functions/importData/transformFieldsToData.d.ts +2 -0
  51. package/dist/server/src/services/functions/importData/updateEntry.d.ts +2 -0
  52. package/dist/server/src/services/index.d.ts +31 -0
  53. package/dist/server/src/services/service.d.ts +31 -0
  54. package/package.json +4 -2
@@ -0,0 +1,1038 @@
1
+ "use strict";
2
+ const bootstrap = ({ strapi: strapi2 }) => {
3
+ };
4
+ const destroy = ({ strapi: strapi2 }) => {
5
+ };
6
+ const register = ({ strapi: strapi2 }) => {
7
+ };
8
+ const config = {
9
+ default: {},
10
+ validator() {
11
+ }
12
+ };
13
+ const contentTypes = {};
14
+ const controller = ({ strapi: strapi2 }) => ({
15
+ async validateToken(ctx) {
16
+ const authHeader = ctx.request.header.authorization;
17
+ if (!authHeader) {
18
+ ctx.status = 401;
19
+ ctx.body = { error: "Missing authorization header" };
20
+ return false;
21
+ }
22
+ const storedToken = await strapi2.plugin("translationstudio").service("service").getToken();
23
+ if (!storedToken?.token || authHeader !== storedToken.token) {
24
+ ctx.status = 401;
25
+ ctx.body = { error: "Invalid token" };
26
+ return false;
27
+ }
28
+ return true;
29
+ },
30
+ async getLicense(ctx) {
31
+ const result = await strapi2.plugin("translationstudio").service("service").getLicense();
32
+ if (result.license) {
33
+ ctx.status = 200;
34
+ } else {
35
+ ctx.status = 404;
36
+ }
37
+ ctx.body = result;
38
+ },
39
+ async setLicense(ctx) {
40
+ const license = ctx.request.body.license;
41
+ const result = await strapi2.plugin("translationstudio").service("service").setLicense(license);
42
+ if (result.success) {
43
+ ctx.status = 200;
44
+ } else {
45
+ ctx.status = 500;
46
+ }
47
+ ctx.body = result;
48
+ },
49
+ async getToken(ctx) {
50
+ const result = await strapi2.plugin("translationstudio").service("service").getToken();
51
+ if (result.token) {
52
+ ctx.status = 200;
53
+ } else {
54
+ ctx.status = 404;
55
+ }
56
+ ctx.body = result;
57
+ },
58
+ async generateToken(ctx) {
59
+ const result = await strapi2.plugin("translationstudio").service("service").generateToken();
60
+ ctx.status = 200;
61
+ ctx.body = result;
62
+ },
63
+ async getLanguageOptions(ctx) {
64
+ const result = await strapi2.plugin("translationstudio").service("service").getLanguageOptions();
65
+ ctx.status = 200;
66
+ ctx.body = result;
67
+ },
68
+ async requestTranslation(ctx) {
69
+ const payload = ctx.request.body;
70
+ const result = await strapi2.plugin("translationstudio").service("service").requestTranslation(payload);
71
+ ctx.body = result;
72
+ },
73
+ async exportData(ctx) {
74
+ if (!await this.validateToken(ctx)) {
75
+ ctx.status = 400;
76
+ return;
77
+ }
78
+ const payload = JSON.parse(ctx.request.body);
79
+ const result = await strapi2.plugin("translationstudio").service("service").exportData(payload);
80
+ ctx.status = 200;
81
+ ctx.body = [{ fields: result }];
82
+ },
83
+ async importData(ctx) {
84
+ if (!await this.validateToken(ctx)) {
85
+ ctx.status = 400;
86
+ return;
87
+ }
88
+ const payload = JSON.parse(ctx.request.body);
89
+ const result = await strapi2.plugin("translationstudio").service("service").importData(payload);
90
+ ctx.body = result;
91
+ },
92
+ async ping(ctx) {
93
+ const result = await strapi2.plugin("translationstudio").service("service").ping();
94
+ ctx.status = 204;
95
+ ctx.body = result;
96
+ },
97
+ async getLanguages(ctx) {
98
+ if (!await this.validateToken(ctx)) {
99
+ ctx.status = 400;
100
+ return;
101
+ }
102
+ const result = await strapi2.plugin("translationstudio").service("service").getLanguages();
103
+ ctx.status = 200;
104
+ ctx.body = result;
105
+ },
106
+ async getEmail(ctx) {
107
+ const result = await strapi2.plugin("translationstudio").service("service").getEmail(ctx);
108
+ ctx.body = result;
109
+ },
110
+ async getEntryData(ctx) {
111
+ const { uid, locale } = ctx.request.body;
112
+ if (!uid) {
113
+ return ctx.badRequest("Missing uid parameter");
114
+ }
115
+ try {
116
+ const [contentTypeID, entryID] = uid.split("#");
117
+ const entry = await strapi2.plugin("translationstudio").service("service").getEntryData(contentTypeID, entryID, locale);
118
+ return entry;
119
+ } catch (error) {
120
+ return ctx.badRequest("Failed to get entry data", { error: error.message });
121
+ }
122
+ }
123
+ });
124
+ const controllers = {
125
+ controller
126
+ };
127
+ const middlewares = {};
128
+ const policies = {};
129
+ const routes = [
130
+ {
131
+ method: "POST",
132
+ path: "/setLicense",
133
+ handler: "controller.setLicense",
134
+ config: {
135
+ policies: []
136
+ }
137
+ },
138
+ {
139
+ method: "GET",
140
+ path: "/getLicense",
141
+ handler: "controller.getLicense",
142
+ config: {
143
+ policies: []
144
+ }
145
+ },
146
+ {
147
+ method: "GET",
148
+ path: "/getToken",
149
+ handler: "controller.getToken",
150
+ config: {
151
+ policies: []
152
+ }
153
+ },
154
+ {
155
+ method: "POST",
156
+ path: "/generateToken",
157
+ handler: "controller.generateToken",
158
+ config: {
159
+ policies: []
160
+ }
161
+ },
162
+ {
163
+ method: "GET",
164
+ path: "/mappings",
165
+ handler: "controller.getLanguageOptions",
166
+ config: {
167
+ policies: []
168
+ }
169
+ },
170
+ {
171
+ method: "POST",
172
+ path: "/translate",
173
+ handler: "controller.requestTranslation",
174
+ config: {
175
+ policies: []
176
+ }
177
+ },
178
+ {
179
+ method: "POST",
180
+ path: "/import",
181
+ handler: "controller.importData",
182
+ config: {
183
+ auth: false,
184
+ policies: []
185
+ }
186
+ },
187
+ {
188
+ method: "POST",
189
+ path: "/export",
190
+ handler: "controller.exportData",
191
+ config: {
192
+ auth: false,
193
+ policies: []
194
+ }
195
+ },
196
+ {
197
+ method: "GET",
198
+ path: "/",
199
+ handler: "controller.ping",
200
+ config: {
201
+ auth: false,
202
+ policies: []
203
+ }
204
+ },
205
+ {
206
+ method: "GET",
207
+ path: "/languages",
208
+ handler: "controller.getLanguages",
209
+ config: {
210
+ auth: false,
211
+ policies: []
212
+ }
213
+ },
214
+ {
215
+ method: "GET",
216
+ path: "/email",
217
+ handler: "controller.getEmail",
218
+ config: {
219
+ policies: []
220
+ }
221
+ },
222
+ {
223
+ method: "POST",
224
+ path: "/entrydata",
225
+ handler: "controller.getEntryData",
226
+ config: {
227
+ policies: []
228
+ }
229
+ }
230
+ ];
231
+ const getContentType = async (contentTypeID) => {
232
+ const contentType = await strapi.contentType(contentTypeID);
233
+ if (!contentType?.attributes) {
234
+ throw new Error(`Content type or schema not found: ${contentTypeID}`);
235
+ }
236
+ return contentType;
237
+ };
238
+ const parsePayload = (payload) => {
239
+ const [contentTypeID, entryID] = payload.element.includes("#") ? payload.element.split("#") : [payload.element, void 0];
240
+ const locale = payload.source.includes("-") ? payload.source.split("-")[0] : payload.source;
241
+ return { contentTypeID, entryID, locale };
242
+ };
243
+ const getComponentSchema = async (componentName) => {
244
+ try {
245
+ return await strapi.components[componentName];
246
+ } catch (error) {
247
+ console.error(`Failed to get component schema for ${componentName}:`, error);
248
+ return null;
249
+ }
250
+ };
251
+ const buildPopulateConfig = async (schema) => {
252
+ const populate = {};
253
+ for (const [fieldName, attribute] of Object.entries(schema.attributes)) {
254
+ if (attribute.type === "component") {
255
+ const componentSchema = await getComponentSchema(attribute.component);
256
+ if (componentSchema) {
257
+ const nestedPopulate = await buildPopulateConfig(componentSchema);
258
+ populate[fieldName] = {
259
+ populate: Object.keys(nestedPopulate).length ? nestedPopulate : "*"
260
+ };
261
+ } else {
262
+ populate[fieldName] = { populate: "*" };
263
+ }
264
+ } else if (attribute.type === "dynamiczone") {
265
+ populate[fieldName] = { populate: "*" };
266
+ }
267
+ }
268
+ return populate;
269
+ };
270
+ const getEntry = async (contentTypeID, entryID, locale) => {
271
+ try {
272
+ const contentType = await strapi.contentTypes[contentTypeID];
273
+ const populateConfig = await buildPopulateConfig(contentType);
274
+ const query = {
275
+ locale,
276
+ populate: populateConfig
277
+ };
278
+ if (entryID) {
279
+ Object.assign(query, { documentId: entryID });
280
+ }
281
+ const entry = await strapi.documents(contentTypeID).findFirst(query);
282
+ return entry;
283
+ } catch (error) {
284
+ console.error("Entry fetch error:", error);
285
+ return null;
286
+ }
287
+ };
288
+ const transformResponse = (data) => data.map(
289
+ (item) => item.realType === "blocks" && Array.isArray(item.translatableValue[0]) ? { ...item, translatableValue: item.translatableValue[0] } : item
290
+ );
291
+ function formatText(child) {
292
+ if (child.type === "link") {
293
+ return `<a href="${child.url}">${child.children.map((sub) => sub.text).join("")}</a>`;
294
+ }
295
+ let text = child.text || "";
296
+ if (child.bold) text = `<strong>${text}</strong>`;
297
+ if (child.italic) text = `<em>${text}</em>`;
298
+ if (child.underline) text = `<u>${text}</u>`;
299
+ if (child.strikethrough) text = `<del>${text}</del>`;
300
+ return text;
301
+ }
302
+ function renderChildren(children) {
303
+ return children.map(formatText).join("");
304
+ }
305
+ function renderHeading(element) {
306
+ return `<h${element.level}>${renderChildren(element.children)}</h${element.level}>`;
307
+ }
308
+ function renderParagraph(element) {
309
+ return `<p>${renderChildren(element.children)}</p>`;
310
+ }
311
+ function renderList(element) {
312
+ const tag = element.format === "unordered" ? "ul" : "ol";
313
+ const items = element.children.map(renderListItem).join("");
314
+ return `<${tag}>${items}</${tag}>`;
315
+ }
316
+ function renderListItem(item) {
317
+ const content = item.children.map(formatText).join("");
318
+ return `<li>${content}</li>`;
319
+ }
320
+ function jsonToHtml(jsonData) {
321
+ return jsonData.map((element) => {
322
+ switch (element.type) {
323
+ case "heading":
324
+ return renderHeading(element);
325
+ case "paragraph":
326
+ return renderParagraph(element);
327
+ case "list":
328
+ return renderList(element);
329
+ default:
330
+ return "";
331
+ }
332
+ }).join("");
333
+ }
334
+ const processComponent = async (fieldName, componentName, value, schemaName, componentId) => {
335
+ const contentFields = [];
336
+ const componentSchema = await strapi.components[componentName];
337
+ if (!componentSchema) {
338
+ throw new Error(`Component schema not found for ${componentName}`);
339
+ }
340
+ const schemaAttributes = componentSchema.attributes || {};
341
+ const dataToProcess = value || {};
342
+ if (Array.isArray(dataToProcess)) {
343
+ for (const item of dataToProcess) {
344
+ const processedFields = await processComponentFields$1(
345
+ item,
346
+ schemaAttributes,
347
+ fieldName,
348
+ componentName,
349
+ schemaName,
350
+ item.id
351
+ );
352
+ contentFields.push(...processedFields);
353
+ }
354
+ } else {
355
+ const processedFields = await processComponentFields$1(
356
+ dataToProcess,
357
+ schemaAttributes,
358
+ fieldName,
359
+ componentName,
360
+ schemaName,
361
+ componentId
362
+ );
363
+ contentFields.push(...processedFields);
364
+ }
365
+ return contentFields;
366
+ };
367
+ const shouldSkipField$1 = (key, fieldSchema) => {
368
+ return key === "id" || fieldSchema.private;
369
+ };
370
+ const isTranslatableField$1 = (type) => {
371
+ return ["string", "text", "blocks", "richtext"].includes(type);
372
+ };
373
+ const getTranslatedValue = (type, value) => {
374
+ if (type === "blocks") {
375
+ return value ? jsonToHtml(value) : "";
376
+ }
377
+ return value.toString();
378
+ };
379
+ const buildTranslatable = (key, fieldSchema, value, parentPath, componentId, schemaName) => {
380
+ return {
381
+ field: key,
382
+ type: ["richtext", "blocks"].includes(fieldSchema.type) ? "html" : "text",
383
+ translatableValue: [value],
384
+ realType: fieldSchema.type,
385
+ componentInfo: {
386
+ namePath: parentPath,
387
+ id: componentId,
388
+ schemaName
389
+ }
390
+ };
391
+ };
392
+ const processComponentFields$1 = async (componentData, schema, parentField, componentName, schemaName, componentId) => {
393
+ const contentFields = [];
394
+ const parentPath = parentField.split(".");
395
+ for (const [key, fieldSchema] of Object.entries(schema)) {
396
+ if (shouldSkipField$1(key, fieldSchema)) continue;
397
+ const value = componentData?.[key];
398
+ const fieldPath = `${parentField}.${key}`;
399
+ if (fieldSchema.type === "component") {
400
+ if (!fieldSchema.component) continue;
401
+ const nestedFields = await processComponent(
402
+ fieldPath,
403
+ fieldSchema.component,
404
+ value || {},
405
+ fieldSchema.component,
406
+ value?.id
407
+ );
408
+ contentFields.push(...nestedFields);
409
+ continue;
410
+ }
411
+ if (!isTranslatableField$1(fieldSchema.type)) continue;
412
+ if (value === null || value === void 0 || value === "") continue;
413
+ const translatedValue = getTranslatedValue(fieldSchema.type, value);
414
+ const translatable = buildTranslatable(
415
+ key,
416
+ fieldSchema,
417
+ translatedValue,
418
+ parentPath,
419
+ componentId,
420
+ schemaName
421
+ );
422
+ contentFields.push(translatable);
423
+ }
424
+ return contentFields;
425
+ };
426
+ const isFieldLocalizable = (fieldSchema, parentSchema) => {
427
+ if (fieldSchema.pluginOptions?.i18n?.localized !== void 0) {
428
+ return fieldSchema.pluginOptions.i18n.localized;
429
+ }
430
+ if (parentSchema.pluginOptions?.i18n?.localized === true) {
431
+ const localizableTypes = ["string", "text", "blocks", "richtext"];
432
+ return localizableTypes.includes(fieldSchema.type);
433
+ }
434
+ return false;
435
+ };
436
+ const DEFAULT_FIELDS = /* @__PURE__ */ new Set([
437
+ "id",
438
+ "documentId",
439
+ "createdAt",
440
+ "updatedAt",
441
+ "publishedAt",
442
+ "locale",
443
+ "localizations",
444
+ "updatedBy",
445
+ "createdBy"
446
+ ]);
447
+ const isEmpty = (value) => value === null || value === void 0 || value === "";
448
+ const isTranslatableField = (fieldSchema) => ["string", "text", "blocks", "richtext"].includes(fieldSchema.type) && fieldSchema.pluginOptions?.i18n?.localized !== false;
449
+ const processDynamicZone = async (key, value, schema) => {
450
+ const results = [];
451
+ for (const component of value) {
452
+ const componentName = component?.__component;
453
+ if (!componentName) continue;
454
+ const fields = await processComponent(
455
+ key,
456
+ componentName,
457
+ component,
458
+ componentName,
459
+ component.id
460
+ );
461
+ results.push(...fields);
462
+ }
463
+ return results;
464
+ };
465
+ const processComponentField = async (key, value, fieldSchema) => {
466
+ const results = [];
467
+ if (fieldSchema.repeatable && Array.isArray(value)) {
468
+ for (const component of value) {
469
+ const fields = await processComponent(
470
+ key,
471
+ fieldSchema.component,
472
+ component,
473
+ fieldSchema.component,
474
+ component.id
475
+ );
476
+ results.push(...fields);
477
+ }
478
+ } else {
479
+ const fields = await processComponent(
480
+ key,
481
+ fieldSchema.component,
482
+ value,
483
+ fieldSchema.component,
484
+ value.id
485
+ );
486
+ results.push(...fields);
487
+ }
488
+ return results;
489
+ };
490
+ const processRegularField = (key, value, fieldSchema) => {
491
+ const translatedValue = fieldSchema.type === "blocks" ? jsonToHtml(value) : value.toString();
492
+ return {
493
+ field: key,
494
+ type: ["richtext", "blocks"].includes(fieldSchema.type) ? "html" : "text",
495
+ translatableValue: [translatedValue],
496
+ realType: fieldSchema.type
497
+ };
498
+ };
499
+ const processEntryFields = async (entry, schema, locale) => {
500
+ const contentFields = [];
501
+ for (const [key, value] of Object.entries(entry)) {
502
+ if (shouldSkipField(key, value)) continue;
503
+ const fieldSchema = schema[key];
504
+ if (!fieldSchema) continue;
505
+ if (isDynamicZone(fieldSchema, value, schema)) {
506
+ const zoneFields = await processDynamicZone(key, value);
507
+ contentFields.push(...zoneFields);
508
+ continue;
509
+ }
510
+ if (isComponent(fieldSchema, value, schema)) {
511
+ const componentFields = await processComponentField(key, value, fieldSchema);
512
+ contentFields.push(...componentFields);
513
+ continue;
514
+ }
515
+ if (isTranslatableField(fieldSchema)) {
516
+ const translatedField = processRegularField(key, value, fieldSchema);
517
+ contentFields.push(translatedField);
518
+ }
519
+ }
520
+ return contentFields;
521
+ };
522
+ const shouldSkipField = (key, value) => {
523
+ return DEFAULT_FIELDS.has(key) || isEmpty(value);
524
+ };
525
+ const isDynamicZone = (fieldSchema, value, schema) => {
526
+ return fieldSchema.type === "dynamiczone" && isFieldLocalizable(fieldSchema, schema) && Array.isArray(value);
527
+ };
528
+ const isComponent = (fieldSchema, value, schema) => {
529
+ return fieldSchema.type === "component" && isFieldLocalizable(fieldSchema, schema);
530
+ };
531
+ function parseInlineElements(text) {
532
+ if (!text.includes("<")) {
533
+ return [{ type: "text", text }];
534
+ }
535
+ const linkRegex = /<a\s+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
536
+ if (text.includes("<a ")) {
537
+ let linkMatch;
538
+ let lastIndex = 0;
539
+ const elements = [];
540
+ while ((linkMatch = linkRegex.exec(text)) !== null) {
541
+ const [fullMatch, url, linkContent] = linkMatch;
542
+ if (linkMatch.index > lastIndex) {
543
+ const beforeText = text.substring(lastIndex, linkMatch.index);
544
+ if (beforeText) {
545
+ elements.push(...parseInlineElements(beforeText));
546
+ }
547
+ }
548
+ elements.push({
549
+ type: "link",
550
+ url,
551
+ children: parseInlineElements(linkContent)
552
+ });
553
+ lastIndex = linkMatch.index + fullMatch.length;
554
+ }
555
+ if (lastIndex < text.length) {
556
+ const afterText = text.substring(lastIndex);
557
+ if (afterText) {
558
+ elements.push(...parseInlineElements(afterText));
559
+ }
560
+ }
561
+ return elements;
562
+ }
563
+ const formatRegex = /<(strong|em|u|del)>([\s\S]*?)<\/\1>/;
564
+ const match = formatRegex.exec(text);
565
+ if (match) {
566
+ const [fullMatch, tag, content] = match;
567
+ const beforeText = text.substring(0, match.index);
568
+ const afterText = text.substring(match.index + fullMatch.length);
569
+ const elements = [];
570
+ if (beforeText) {
571
+ elements.push(...parseInlineElements(beforeText));
572
+ }
573
+ const nestedElements = parseInlineElements(content);
574
+ nestedElements.forEach((element) => {
575
+ if (tag === "strong") element.bold = true;
576
+ else if (tag === "em") element.italic = true;
577
+ else if (tag === "u") element.underline = true;
578
+ else if (tag === "del") element.strikethrough = true;
579
+ });
580
+ elements.push(...nestedElements);
581
+ if (afterText) {
582
+ elements.push(...parseInlineElements(afterText));
583
+ }
584
+ return elements;
585
+ }
586
+ return [{ type: "text", text }];
587
+ }
588
+ function parseHeading(tag, innerText) {
589
+ const level = parseInt(tag[1]);
590
+ return {
591
+ type: "heading",
592
+ level,
593
+ children: [{ type: "text", text: innerText.trim() }]
594
+ };
595
+ }
596
+ function parseParagraph(innerText) {
597
+ return {
598
+ type: "paragraph",
599
+ children: parseInlineElements(innerText)
600
+ };
601
+ }
602
+ function parseList(tag, innerText) {
603
+ const listType = tag === "ul" ? "unordered" : "ordered";
604
+ const listItems = [];
605
+ const listItemRegex = /<li>(.*?)<\/li>/g;
606
+ let itemMatch;
607
+ while ((itemMatch = listItemRegex.exec(innerText)) !== null) {
608
+ listItems.push({
609
+ type: "list-item",
610
+ children: parseInlineElements(itemMatch[1])
611
+ });
612
+ }
613
+ return {
614
+ type: "list",
615
+ format: listType,
616
+ children: listItems
617
+ };
618
+ }
619
+ function htmlToJson(htmlData) {
620
+ const jsonData = [];
621
+ const blockRegex = /<(h[1-3]|p|ul|ol)(?:[^>]*?)>([\s\S]*?)<\/\1>/g;
622
+ let match;
623
+ while ((match = blockRegex.exec(htmlData)) !== null) {
624
+ const [, tag, content] = match;
625
+ switch (tag) {
626
+ case "h1":
627
+ case "h2":
628
+ case "h3":
629
+ jsonData.push(parseHeading(tag, content));
630
+ break;
631
+ case "p":
632
+ if (content.includes("<a ")) {
633
+ const linkRegex = /<a\s+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
634
+ let linkMatch;
635
+ let lastIndex = 0;
636
+ const children = [];
637
+ children.push({ type: "text", text: "" });
638
+ while ((linkMatch = linkRegex.exec(content)) !== null) {
639
+ const [fullMatch, url, linkText] = linkMatch;
640
+ if (linkMatch.index > lastIndex) {
641
+ const beforeLinkText = content.substring(lastIndex, linkMatch.index);
642
+ if (beforeLinkText) {
643
+ children.push({ type: "text", text: beforeLinkText });
644
+ }
645
+ }
646
+ children.push({
647
+ type: "link",
648
+ url,
649
+ children: [{ type: "text", text: linkText }]
650
+ });
651
+ lastIndex = linkMatch.index + fullMatch.length;
652
+ }
653
+ const afterLastLink = content.substring(lastIndex);
654
+ if (afterLastLink) {
655
+ children.push({ type: "text", text: afterLastLink });
656
+ } else {
657
+ children.push({ type: "text", text: "" });
658
+ }
659
+ jsonData.push({
660
+ type: "paragraph",
661
+ children
662
+ });
663
+ } else {
664
+ jsonData.push(parseParagraph(content));
665
+ }
666
+ break;
667
+ case "ul":
668
+ case "ol":
669
+ jsonData.push(parseList(tag, content));
670
+ break;
671
+ }
672
+ }
673
+ return jsonData;
674
+ }
675
+ async function updateEntry(contentTypeID, entryID, sourceLocale, targetLocale, data, attributes) {
676
+ if (!entryID) {
677
+ const singleTypeData = await strapi.documents(contentTypeID).findFirst();
678
+ entryID = singleTypeData.documentId;
679
+ }
680
+ const originalEntry = await strapi.documents(contentTypeID).findFirst({
681
+ documentId: entryID,
682
+ locale: sourceLocale
683
+ });
684
+ const processedData = processDataRecursively(data);
685
+ const localizedData = {};
686
+ for (const field in processedData) {
687
+ if (attributes[field] && (!attributes[field].pluginOptions?.i18n || attributes[field].pluginOptions?.i18n?.localized !== false)) {
688
+ localizedData[field] = processedData[field];
689
+ }
690
+ }
691
+ await strapi.documents(contentTypeID).update({
692
+ documentId: entryID,
693
+ locale: targetLocale,
694
+ data: localizedData
695
+ });
696
+ if (originalEntry.publishedAt !== null) {
697
+ await strapi.documents(contentTypeID).publish({
698
+ documentId: entryID,
699
+ locale: sourceLocale
700
+ });
701
+ }
702
+ }
703
+ function processDataRecursively(data) {
704
+ if (!data || typeof data !== "object") {
705
+ return data;
706
+ }
707
+ if (Array.isArray(data)) {
708
+ return data.map((item) => processDataRecursively(item));
709
+ }
710
+ const result = {};
711
+ for (const key in data) {
712
+ const value = data[key];
713
+ if (typeof value === "string" && (key.includes("Blocks") || key.includes("RTBlocks") || key === "blocks")) {
714
+ result[key] = htmlToJson(value);
715
+ } else if (value && typeof value === "object") {
716
+ result[key] = processDataRecursively(value);
717
+ } else {
718
+ result[key] = value;
719
+ }
720
+ }
721
+ return result;
722
+ }
723
+ function organizeFields(fields) {
724
+ const componentFieldsMap = /* @__PURE__ */ new Map();
725
+ const dynamicZoneFields = /* @__PURE__ */ new Map();
726
+ const regularFields = [];
727
+ fields.forEach((field) => {
728
+ if (!field.componentInfo) {
729
+ regularFields.push(field);
730
+ return;
731
+ }
732
+ const { namePath, id } = field.componentInfo;
733
+ const pathString = namePath.join(".");
734
+ if (namePath[0] === "dynamiczone") {
735
+ if (!dynamicZoneFields.has(id)) {
736
+ dynamicZoneFields.set(id, []);
737
+ }
738
+ dynamicZoneFields.get(id)?.push(field);
739
+ } else {
740
+ if (!componentFieldsMap.has(pathString)) {
741
+ componentFieldsMap.set(pathString, []);
742
+ }
743
+ componentFieldsMap.get(pathString)?.push(field);
744
+ }
745
+ });
746
+ return { regularFields, componentFieldsMap, dynamicZoneFields };
747
+ }
748
+ function processRegularFields(regularFields, acc) {
749
+ regularFields.forEach((field) => {
750
+ acc[field.field] = field.translatableValue[0];
751
+ });
752
+ return acc;
753
+ }
754
+ function processRepeatableComponents(fields, existingEntry, rootPath) {
755
+ const existingComponents = existingEntry?.[rootPath] || [];
756
+ const componentsById = /* @__PURE__ */ new Map();
757
+ fields.forEach((field) => {
758
+ if (!field.componentInfo) {
759
+ console.warn(`Component info missing for field: ${field.field}`);
760
+ return;
761
+ }
762
+ const componentId = field.componentInfo.id;
763
+ if (!componentsById.has(componentId)) {
764
+ const existingComponent = existingComponents.find(
765
+ (c) => c.id === componentId
766
+ );
767
+ componentsById.set(
768
+ componentId,
769
+ existingComponent ? { ...existingComponent } : {}
770
+ );
771
+ }
772
+ const component = componentsById.get(componentId);
773
+ component[field.field] = field.translatableValue[0];
774
+ });
775
+ return Array.from(componentsById.values()).map((comp) => {
776
+ if (!existingComponents.find((ec) => ec.id === comp.id)) {
777
+ const { id, ...rest } = comp;
778
+ return rest;
779
+ }
780
+ return comp;
781
+ }).filter((comp) => Object.keys(comp).length > 0);
782
+ }
783
+ function processNestedComponents(fields, pathParts, existingEntry, acc) {
784
+ let current = acc;
785
+ let currentExisting = existingEntry;
786
+ pathParts.forEach((part, index2) => {
787
+ if (!current[part]) {
788
+ current[part] = {};
789
+ if (currentExisting?.[part]?.id) {
790
+ current[part].id = currentExisting[part].id;
791
+ }
792
+ }
793
+ if (index2 === pathParts.length - 1) {
794
+ fields.forEach((field) => {
795
+ current[part][field.field] = field.translatableValue[0];
796
+ });
797
+ } else {
798
+ current = current[part];
799
+ currentExisting = currentExisting?.[part];
800
+ }
801
+ });
802
+ }
803
+ function processComponentFields(componentFieldsMap, acc, existingEntry, targetSchema) {
804
+ componentFieldsMap.forEach((fields, namePath) => {
805
+ if (!fields.length) return;
806
+ const pathParts = namePath.split(".");
807
+ const rootPath = pathParts[0];
808
+ const schema = targetSchema.attributes?.[rootPath];
809
+ if (schema?.repeatable) {
810
+ acc[rootPath] = processRepeatableComponents(
811
+ fields,
812
+ existingEntry,
813
+ rootPath
814
+ );
815
+ } else {
816
+ processNestedComponents(fields, pathParts, existingEntry, acc);
817
+ }
818
+ });
819
+ return acc;
820
+ }
821
+ function transformFieldsToData(fields) {
822
+ return fields.reduce((acc, field) => {
823
+ if (!field.translatableValue) {
824
+ acc[field.field] = "";
825
+ return acc;
826
+ }
827
+ if (field.realType === "blocks") {
828
+ acc[field.field] = htmlToJson(field.translatableValue[0] || "");
829
+ } else if (field.realType === "richtext") {
830
+ acc[field.field] = field.translatableValue[0] || "";
831
+ } else {
832
+ acc[field.field] = Array.isArray(field.translatableValue) && field.translatableValue.length > 1 ? field.translatableValue.join(" ") : field.translatableValue[0] || "";
833
+ }
834
+ return acc;
835
+ }, {});
836
+ }
837
+ function processDynamicZones(dynamicZoneFields, acc, existingEntry) {
838
+ if (dynamicZoneFields.size > 0) {
839
+ const existingDynamicZone = existingEntry?.dynamiczone || [];
840
+ acc.dynamiczone = Array.from(dynamicZoneFields.entries()).sort(([a], [b]) => a - b).map(([_, fields]) => {
841
+ if (!fields[0].componentInfo) {
842
+ console.warn(
843
+ `Component info missing for dynamic zone field: ${fields[0].field}`
844
+ );
845
+ return null;
846
+ }
847
+ const { schemaName } = fields[0].componentInfo;
848
+ const componentData = transformFieldsToData(fields);
849
+ const matchingComponent = existingDynamicZone.find(
850
+ (comp) => comp.__component === schemaName
851
+ );
852
+ return {
853
+ __component: schemaName,
854
+ ...componentData,
855
+ ...matchingComponent?.id ? { id: matchingComponent.id } : {}
856
+ };
857
+ }).filter(Boolean);
858
+ }
859
+ return acc;
860
+ }
861
+ function prepareImportData(translatables, existingEntry, targetSchema) {
862
+ return translatables.reduce((acc, doc) => {
863
+ const { regularFields, componentFieldsMap, dynamicZoneFields } = organizeFields(translatables);
864
+ const withRegularFields = processRegularFields(regularFields, acc);
865
+ const withComponentFields = processComponentFields(
866
+ componentFieldsMap,
867
+ withRegularFields,
868
+ existingEntry,
869
+ targetSchema
870
+ );
871
+ const withDynamicZones = processDynamicZones(
872
+ dynamicZoneFields,
873
+ withComponentFields,
874
+ existingEntry
875
+ );
876
+ return withDynamicZones;
877
+ }, {});
878
+ }
879
+ const jwt = require("jsonwebtoken");
880
+ const crypto = require("crypto");
881
+ const service = ({ strapi: strapi2 }) => {
882
+ const pluginStore = strapi2.store({
883
+ type: "plugin",
884
+ name: "translationstudio"
885
+ });
886
+ return {
887
+ // translationstudio Lizenz
888
+ async getLicense() {
889
+ const result = await pluginStore.get({ key: "license" });
890
+ return { license: result };
891
+ },
892
+ async setLicense(license) {
893
+ try {
894
+ await pluginStore.set({
895
+ key: "license",
896
+ value: license
897
+ });
898
+ return { success: true };
899
+ } catch (error) {
900
+ return { success: false };
901
+ }
902
+ },
903
+ // Access Token
904
+ async getToken() {
905
+ try {
906
+ const result = await pluginStore.get({ key: "token" });
907
+ return { token: result };
908
+ } catch (error) {
909
+ return { token: null };
910
+ }
911
+ },
912
+ async generateToken() {
913
+ const secretKey = crypto.randomBytes(64).toString("hex");
914
+ const token = jwt.sign(
915
+ {
916
+ app: "translationstudio",
917
+ iat: Math.floor(Date.now() / 1e3)
918
+ },
919
+ secretKey,
920
+ { expiresIn: "10y" }
921
+ );
922
+ await pluginStore.set({
923
+ key: "token",
924
+ value: token
925
+ });
926
+ return { token };
927
+ },
928
+ async getLanguageOptions() {
929
+ const { license } = await this.getLicense();
930
+ const response = await fetch(
931
+ "https://cms-strapi-service-7866fdd79eab.herokuapp.com/mappings",
932
+ {
933
+ headers: { Authorization: `${license}` }
934
+ }
935
+ );
936
+ const responseData = await response.json();
937
+ return responseData;
938
+ },
939
+ async exportData(payload) {
940
+ const { contentTypeID, entryID, locale } = parsePayload(payload);
941
+ const contentType = await getContentType(contentTypeID);
942
+ const entry = await getEntry(contentTypeID, entryID, locale);
943
+ const contentFields = await processEntryFields(
944
+ entry,
945
+ contentType.attributes
946
+ );
947
+ return transformResponse(contentFields);
948
+ },
949
+ async importData(payload) {
950
+ const { contentTypeID, entryID } = parsePayload(payload);
951
+ const sourceLocale = payload.source;
952
+ const targetLocale = payload.target;
953
+ try {
954
+ const existingEntry = await getEntry(
955
+ contentTypeID,
956
+ entryID,
957
+ targetLocale
958
+ );
959
+ const targetSchema = await getContentType(contentTypeID);
960
+ const data = prepareImportData(
961
+ payload.document[0].fields,
962
+ existingEntry,
963
+ targetSchema
964
+ );
965
+ if (targetSchema.pluginOptions.i18n.localized === true) {
966
+ await updateEntry(
967
+ contentTypeID,
968
+ entryID,
969
+ sourceLocale,
970
+ targetLocale,
971
+ data,
972
+ targetSchema.attributes
973
+ );
974
+ }
975
+ return { success: true };
976
+ } catch (error) {
977
+ return { success: false };
978
+ }
979
+ },
980
+ async requestTranslation(payload) {
981
+ const { license } = await this.getLicense();
982
+ const response = await fetch(
983
+ "https://cms-strapi-service-7866fdd79eab.herokuapp.com/translate",
984
+ {
985
+ method: "POST",
986
+ headers: {
987
+ Authorization: `${license}`,
988
+ "Content-Type": "application/json"
989
+ },
990
+ body: JSON.stringify(payload)
991
+ }
992
+ );
993
+ if (response.status === 204) return true;
994
+ },
995
+ async getEmail(ctx) {
996
+ const user = ctx.state.user;
997
+ if (!user) {
998
+ return { email: null };
999
+ }
1000
+ return { email: user.email };
1001
+ },
1002
+ async getLanguages() {
1003
+ try {
1004
+ const locales = await strapi2.plugin("i18n").service("locales").find();
1005
+ const localeMap = {};
1006
+ locales.forEach((locale) => {
1007
+ localeMap[locale.code] = locale.name;
1008
+ });
1009
+ return localeMap;
1010
+ } catch (error) {
1011
+ return {};
1012
+ }
1013
+ },
1014
+ async ping() {
1015
+ return;
1016
+ },
1017
+ async getEntryData(contentTypeID, entryID, locale) {
1018
+ const entry = await getEntry(contentTypeID, entryID, locale);
1019
+ return entry;
1020
+ }
1021
+ };
1022
+ };
1023
+ const services = {
1024
+ service
1025
+ };
1026
+ const index = {
1027
+ register,
1028
+ bootstrap,
1029
+ destroy,
1030
+ config,
1031
+ controllers,
1032
+ routes,
1033
+ services,
1034
+ contentTypes,
1035
+ policies,
1036
+ middlewares
1037
+ };
1038
+ module.exports = index;