@nkhang1902/strapi-plugin-export-import-clsx 1.0.3
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 +95 -0
- package/admin/src/components/BulkActions/index.js +70 -0
- package/admin/src/components/ExportButton/index.js +48 -0
- package/admin/src/components/ExportImportButtons/index.js +245 -0
- package/admin/src/components/ImportButton/index.js +54 -0
- package/admin/src/components/Initializer/index.js +15 -0
- package/admin/src/components/PluginIcon/index.js +6 -0
- package/admin/src/pages/App/index.js +8 -0
- package/admin/src/pages/HomePage/index.js +297 -0
- package/admin/src/pluginId.js +3 -0
- package/admin/src/translations/en.json +14 -0
- package/package.json +60 -0
- package/server/bootstrap.js +3 -0
- package/server/config/index.js +4 -0
- package/server/content-types/index.js +1 -0
- package/server/controllers/export-controller.js +62 -0
- package/server/controllers/import-controller.js +42 -0
- package/server/controllers/index.js +7 -0
- package/server/destroy.js +3 -0
- package/server/middlewares/index.js +1 -0
- package/server/policies/index.js +1 -0
- package/server/register.js +3 -0
- package/server/routes/index.js +29 -0
- package/server/services/export-service.js +407 -0
- package/server/services/import-service.js +416 -0
- package/server/services/index.js +7 -0
- package/strapi-admin.js +88 -0
- package/strapi-server.js +34 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
const XLSX = require('xlsx');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
function toCamel(str) {
|
|
5
|
+
return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const SYSTEM_KEYS = [
|
|
9
|
+
'documentId',
|
|
10
|
+
'locale',
|
|
11
|
+
'createdAt',
|
|
12
|
+
'updatedAt',
|
|
13
|
+
'publishedAt',
|
|
14
|
+
'createdBy',
|
|
15
|
+
'updatedBy',
|
|
16
|
+
'localizations',
|
|
17
|
+
'status'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const SHORTCUT_FIELDS = [
|
|
21
|
+
'email','businessEmail','name','title','tickerCode',
|
|
22
|
+
]
|
|
23
|
+
async function importData(file) {
|
|
24
|
+
let result;
|
|
25
|
+
try {
|
|
26
|
+
let importData;
|
|
27
|
+
// Check file extension
|
|
28
|
+
const fileName = file.name || file.originalFilename || 'unknown.json';
|
|
29
|
+
const fileExtension = fileName.split('.').pop().toLowerCase();
|
|
30
|
+
const filePath = file.path || file.filepath;
|
|
31
|
+
if (!filePath) {
|
|
32
|
+
throw new Error('File path not found');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (fileExtension === 'json') {
|
|
36
|
+
const fileContent = fs.readFileSync(filePath, 'utf8');
|
|
37
|
+
importData = JSON.parse(fileContent);
|
|
38
|
+
strapi.log.info('Parsed JSON data:', Object.keys(importData));
|
|
39
|
+
} else if (fileExtension === 'xlsx' || fileExtension === 'xls') {
|
|
40
|
+
importData = transformExcelData(filePath);
|
|
41
|
+
}
|
|
42
|
+
result = await bulkInsertData(importData);
|
|
43
|
+
return result;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// Clean up uploaded file on error
|
|
46
|
+
const filePath = file && (file.path || file.filepath);
|
|
47
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
48
|
+
fs.unlinkSync(filePath);
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
function transformExcelData(filePath) {
|
|
56
|
+
const workbook = XLSX.readFile(filePath);
|
|
57
|
+
const importData = {};
|
|
58
|
+
|
|
59
|
+
const parseJsonIfNeeded = (value) => {
|
|
60
|
+
if (typeof value !== 'string') return value;
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) return value;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(trimmed);
|
|
66
|
+
} catch {
|
|
67
|
+
return value; // keep as string if invalid JSON
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const isComponentField = (key) => {
|
|
72
|
+
const parts = key.split('_');
|
|
73
|
+
return parts.length === 2; // exactly one underscore
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
function unflattenRow(rows, targetContentType) {
|
|
77
|
+
const result = [];
|
|
78
|
+
const attr = strapi.contentTypes[targetContentType].attributes;
|
|
79
|
+
for (const row of rows) {
|
|
80
|
+
const rowData = {};
|
|
81
|
+
|
|
82
|
+
for (const [key, value] of Object.entries(row)) {
|
|
83
|
+
if (value === null || value === undefined || value === '') {
|
|
84
|
+
rowData[key] = null
|
|
85
|
+
} else if (attr[key] && attr[key].customField && attr[key].type === 'json' && attr[key].default === '[]') {
|
|
86
|
+
rowData[key] = parseJsonIfNeeded(value).split('|');
|
|
87
|
+
} else if (isComponentField(key)) {
|
|
88
|
+
const [comp, field] = key.split('_');
|
|
89
|
+
if (!rowData[comp]) rowData[comp] = {};
|
|
90
|
+
rowData[comp][field] = parseJsonIfNeeded(value);
|
|
91
|
+
} else {
|
|
92
|
+
rowData[key] = parseJsonIfNeeded(value);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
result.push(rowData);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const mapSheetNameToContentType = (sheetName) => {
|
|
102
|
+
return "api::" + sheetName + "." + sheetName;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
workbook.SheetNames.forEach(sheetName => {
|
|
106
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
107
|
+
const rows = XLSX.utils.sheet_to_json(worksheet);
|
|
108
|
+
|
|
109
|
+
if (!rows.length) return;
|
|
110
|
+
|
|
111
|
+
const contentTypeName = mapSheetNameToContentType(sheetName);
|
|
112
|
+
|
|
113
|
+
strapi.log.info(`Reading sheet "${sheetName}" -> ${rows.length} rows`);
|
|
114
|
+
strapi.log.info(`Mapped sheet to content-type: ${contentTypeName}`);
|
|
115
|
+
|
|
116
|
+
if (contentTypeName.startsWith('api::')) {
|
|
117
|
+
importData[contentTypeName] = unflattenRow(rows, contentTypeName);
|
|
118
|
+
} else {
|
|
119
|
+
strapi.log.error(`Unknown content-type: ${contentTypeName}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
strapi.log.info('Final import data keys:', Object.keys(importData));
|
|
125
|
+
return importData;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getRelationFields(contentType) {
|
|
129
|
+
const schema = strapi.contentTypes[contentType];
|
|
130
|
+
|
|
131
|
+
if (!schema) {
|
|
132
|
+
strapi.log.warn(`Content type ${contentType} not found`);
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Object.entries(schema.attributes)
|
|
137
|
+
.filter(([_, attr]) => attr.type === "relation")
|
|
138
|
+
.map(([fieldName, attr]) => ({
|
|
139
|
+
field: toCamel(fieldName),
|
|
140
|
+
target: attr.target, // e.g. "api::category.category"
|
|
141
|
+
relation: attr.relation,
|
|
142
|
+
}));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getComponentFields(contentType) {
|
|
146
|
+
const schema = strapi.contentTypes[contentType];
|
|
147
|
+
|
|
148
|
+
if (!schema) {
|
|
149
|
+
strapi.log.warn(`Content type ${contentType} not found`);
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Object.entries(schema.attributes)
|
|
154
|
+
.filter(([_, attr]) => attr.type === "component")
|
|
155
|
+
.map(([fieldName, attr]) => toCamel(fieldName));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function handleRelations(entry, contentType) {
|
|
159
|
+
async function resolveRelationValue(field, value, target) {
|
|
160
|
+
const targetAttr = strapi.contentTypes[target].attributes;
|
|
161
|
+
for (const field of SHORTCUT_FIELDS) {
|
|
162
|
+
if (!targetAttr[field]) continue;
|
|
163
|
+
const existing = await strapi.documents(target).findFirst({
|
|
164
|
+
filters: { [field]: { $eq: value } },
|
|
165
|
+
});
|
|
166
|
+
if (existing) return {id: existing.id};
|
|
167
|
+
throw new Error(`Data with ${field} ${value} not found`);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const relationFields = getRelationFields(contentType);
|
|
173
|
+
if (relationFields.length === 0) return entry;
|
|
174
|
+
|
|
175
|
+
const updatedEntry = { ...entry };
|
|
176
|
+
|
|
177
|
+
for (const rel of relationFields) {
|
|
178
|
+
const { field, target, relation } = rel;
|
|
179
|
+
|
|
180
|
+
let value = entry[field];
|
|
181
|
+
if (!value || value === "") {
|
|
182
|
+
if (relation === "manyToMany" || relation === "oneToMany") {
|
|
183
|
+
updatedEntry[field] = [];
|
|
184
|
+
} else {
|
|
185
|
+
updatedEntry[field] = null;
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// Convert CSV to array
|
|
191
|
+
if (typeof value === "string" && (relation === "manyToMany" || relation === "oneToMany")) {
|
|
192
|
+
value = value.split("|");
|
|
193
|
+
} else if (typeof value === "string" && value.includes("|")) {
|
|
194
|
+
throw new Error(`Invalid value for field ${field}: ${value}, ${field} is not an array`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const values = Array.isArray(value) ? value : [value];
|
|
198
|
+
try {
|
|
199
|
+
const processed = [];
|
|
200
|
+
|
|
201
|
+
for (const v of values) {
|
|
202
|
+
if (!v || v === "") continue;
|
|
203
|
+
const resolved = await resolveRelationValue(field, v, target);
|
|
204
|
+
if (resolved) processed.push(resolved);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
|
|
208
|
+
} catch (err) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return updatedEntry;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function handleComponents(data, existing, contentType) {
|
|
219
|
+
// Get the component fields for this content type
|
|
220
|
+
const compFields = getComponentFields(contentType);
|
|
221
|
+
|
|
222
|
+
for (const field of compFields) {
|
|
223
|
+
const newValue = data[field];
|
|
224
|
+
const oldValue = existing?.[field];
|
|
225
|
+
|
|
226
|
+
if (!newValue || !oldValue) continue;
|
|
227
|
+
|
|
228
|
+
//single component
|
|
229
|
+
if (!Array.isArray(newValue)) {
|
|
230
|
+
if (oldValue?.id) {
|
|
231
|
+
data[field].id = oldValue.id;
|
|
232
|
+
}
|
|
233
|
+
for (const key of Object.keys(data[field])) {
|
|
234
|
+
if (Array.isArray(oldValue[key])) {
|
|
235
|
+
data[field][key] = data[field][key].split("|");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//multiple components
|
|
242
|
+
if (Array.isArray(newValue) && Array.isArray(oldValue)) {
|
|
243
|
+
data[field] = newValue.map((block, i) => {
|
|
244
|
+
const oldBlock = oldValue[i];
|
|
245
|
+
if (oldBlock?.id) {
|
|
246
|
+
return { id: oldBlock.id, ...block };
|
|
247
|
+
}
|
|
248
|
+
for (const key of Object.keys(block)) {
|
|
249
|
+
if (Array.isArray(oldBlock[key])) {
|
|
250
|
+
block[key] = block[key].split("|");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return block;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return data;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function hasChanges(existing, incoming) {
|
|
262
|
+
if (!incoming || typeof incoming !== "object") return false;
|
|
263
|
+
if (!existing || typeof existing !== "object") return true;
|
|
264
|
+
for (const key of Object.keys(incoming)) {
|
|
265
|
+
// Skip system keys
|
|
266
|
+
if (SYSTEM_KEYS.includes(key)) continue;
|
|
267
|
+
const newVal = incoming[key];
|
|
268
|
+
const oldVal = existing[key];
|
|
269
|
+
|
|
270
|
+
// If incoming defines a field but existing doesn't → change
|
|
271
|
+
if (oldVal === undefined || newVal === undefined) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Primitive comparison
|
|
276
|
+
if (newVal === null || typeof newVal !== "object") {
|
|
277
|
+
if (oldVal !== newVal) {
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ARRAY comparison
|
|
284
|
+
if (Array.isArray(newVal)) {
|
|
285
|
+
if (!Array.isArray(oldVal)) return true;
|
|
286
|
+
if (newVal.length !== oldVal.length) return true;
|
|
287
|
+
// Compare values shallowly
|
|
288
|
+
for (let i = 0; i < newVal.length; i++) {
|
|
289
|
+
if (typeof newVal[i] === "object" && typeof oldVal[i] === "object" && hasChanges(oldVal[i], newVal[i])) {
|
|
290
|
+
return true;
|
|
291
|
+
} else if (typeof newVal[i] !== "object" && typeof oldVal[i] !== "object" && newVal[i] !== oldVal[i]) {
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// OBJECT comparison (recursive, but ONLY fields in incoming object)
|
|
299
|
+
if (typeof newVal === "object" && typeof oldVal === "object") {
|
|
300
|
+
if (hasChanges(oldVal, newVal)) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
async function bulkInsertData(importData) {
|
|
312
|
+
const results = {
|
|
313
|
+
created: 0,
|
|
314
|
+
updated: 0,
|
|
315
|
+
errors: [],
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
for (const [contentType, entries] of Object.entries(importData)) {
|
|
319
|
+
// Validate entries
|
|
320
|
+
if (!strapi.contentTypes[contentType]) {
|
|
321
|
+
results.errors.push(`Content type ${contentType} not found`);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!Array.isArray(entries)) {
|
|
325
|
+
results.errors.push(`Invalid data format for ${contentType}`);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const { created, updated, errors } = await importEntries(entries, contentType);
|
|
331
|
+
results.created += created;
|
|
332
|
+
results.updated += updated;
|
|
333
|
+
results.errors = results.errors.concat(errors);
|
|
334
|
+
} catch (err) {
|
|
335
|
+
results.errors.push(err.message);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return results;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function importEntries(entries, contentType) {
|
|
343
|
+
const results = { created: 0, updated: 0, errors: [] };
|
|
344
|
+
|
|
345
|
+
await strapi.db.transaction(async ({ trx, rollback, onRollback }) => {
|
|
346
|
+
onRollback(() => {
|
|
347
|
+
strapi.log.error("Transaction rolled back due to an error!");
|
|
348
|
+
strapi.log.error(results.errors);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
for (let i = 0; i < entries.length; i++) {
|
|
352
|
+
const entry = entries[i];
|
|
353
|
+
let existing = null;
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
let { id, ...data } = entry;
|
|
357
|
+
|
|
358
|
+
// Check if document exists
|
|
359
|
+
if (id && id !== "null" && id !== "undefined") {
|
|
360
|
+
existing = await strapi.documents(contentType).findFirst(
|
|
361
|
+
{
|
|
362
|
+
filters: { id },
|
|
363
|
+
populate: "*",
|
|
364
|
+
},
|
|
365
|
+
{ transaction: trx }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Handle relations & components
|
|
370
|
+
data = await handleRelations(data, contentType, trx);
|
|
371
|
+
data = await handleComponents(data, existing, contentType);
|
|
372
|
+
|
|
373
|
+
// Update
|
|
374
|
+
if (existing) {
|
|
375
|
+
if (hasChanges(existing, data)) {
|
|
376
|
+
await strapi.documents(contentType).update(
|
|
377
|
+
{
|
|
378
|
+
documentId: existing.documentId,
|
|
379
|
+
data,
|
|
380
|
+
},
|
|
381
|
+
{ transaction: trx }
|
|
382
|
+
);
|
|
383
|
+
results.updated++;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Create
|
|
388
|
+
else {
|
|
389
|
+
await strapi.documents(contentType).create(
|
|
390
|
+
{ data },
|
|
391
|
+
{ transaction: trx }
|
|
392
|
+
);
|
|
393
|
+
results.created++;
|
|
394
|
+
}
|
|
395
|
+
} catch (err) {
|
|
396
|
+
results.errors.push(
|
|
397
|
+
`Failed ${existing ? "updating" : "creating"} on row ${
|
|
398
|
+
i + 2
|
|
399
|
+
}: ${err.message}`
|
|
400
|
+
);
|
|
401
|
+
results.created = 0;
|
|
402
|
+
results.updated = 0;
|
|
403
|
+
|
|
404
|
+
// IMPORTANT: force rollback
|
|
405
|
+
throw err;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
return results;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
module.exports = {
|
|
415
|
+
importData,
|
|
416
|
+
};
|
package/strapi-admin.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import pluginPkg from './package.json';
|
|
3
|
+
import pluginId from './admin/src/pluginId';
|
|
4
|
+
import Initializer from './admin/src/components/Initializer';
|
|
5
|
+
import ExportImportButtons from './admin/src/components/ExportImportButtons';
|
|
6
|
+
|
|
7
|
+
const name = pluginPkg.strapi.name;
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
register(app) {
|
|
11
|
+
const plugin = {
|
|
12
|
+
id: pluginId,
|
|
13
|
+
initializer: Initializer,
|
|
14
|
+
isReady: false,
|
|
15
|
+
name,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
app.registerPlugin(plugin);
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
bootstrap(app) {
|
|
22
|
+
// Try different injection methods for Strapi v5
|
|
23
|
+
try {
|
|
24
|
+
// Method 1: Direct injection
|
|
25
|
+
if (app.injectContentManagerComponent) {
|
|
26
|
+
app.injectContentManagerComponent('listView', 'actions', {
|
|
27
|
+
name: 'export-import-buttons',
|
|
28
|
+
Component: ExportImportButtons,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
// Method 2: Plugin-based injection
|
|
32
|
+
else if (app.getPlugin) {
|
|
33
|
+
const contentManager = app.getPlugin('content-manager');
|
|
34
|
+
if (contentManager && contentManager.injectComponent) {
|
|
35
|
+
contentManager.injectComponent('listView', 'actions', {
|
|
36
|
+
name: 'export-import-buttons',
|
|
37
|
+
Component: ExportImportButtons,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Method 3: Global injection
|
|
42
|
+
else if (app.addComponent) {
|
|
43
|
+
app.addComponent('content-manager.listView.actions', ExportImportButtons);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn('Failed to inject export-import buttons:', error);
|
|
47
|
+
|
|
48
|
+
// Fallback: Add as menu item if injection fails
|
|
49
|
+
app.addMenuLink({
|
|
50
|
+
to: `/plugins/${pluginId}`,
|
|
51
|
+
icon: () => React.createElement('span', null, '📊'),
|
|
52
|
+
intlLabel: {
|
|
53
|
+
id: `${pluginId}.plugin.name`,
|
|
54
|
+
defaultMessage: 'Export Import',
|
|
55
|
+
},
|
|
56
|
+
Component: async () => {
|
|
57
|
+
const component = await import('./admin/src/pages/App');
|
|
58
|
+
return component;
|
|
59
|
+
},
|
|
60
|
+
permissions: [],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async registerTrads(app) {
|
|
66
|
+
const { locales } = app;
|
|
67
|
+
|
|
68
|
+
const importedTrads = await Promise.all(
|
|
69
|
+
locales.map((locale) => {
|
|
70
|
+
return import(`./admin/src/translations/${locale}.json`)
|
|
71
|
+
.then(({ default: data }) => {
|
|
72
|
+
return {
|
|
73
|
+
data: data,
|
|
74
|
+
locale,
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
.catch(() => {
|
|
78
|
+
return {
|
|
79
|
+
data: {},
|
|
80
|
+
locale,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return Promise.resolve(importedTrads);
|
|
87
|
+
},
|
|
88
|
+
};
|
package/strapi-server.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
register({ strapi }) {
|
|
3
|
+
// Register phase
|
|
4
|
+
},
|
|
5
|
+
|
|
6
|
+
bootstrap({ strapi }) {
|
|
7
|
+
// Bootstrap phase
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
destroy({ strapi }) {
|
|
11
|
+
// Destroy phase
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
config: {
|
|
15
|
+
default: {},
|
|
16
|
+
validator() {},
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
controllers: {
|
|
20
|
+
'export-controller': require('./server/controllers/export-controller'),
|
|
21
|
+
'import-controller': require('./server/controllers/import-controller'),
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
routes: require('./server/routes'),
|
|
25
|
+
|
|
26
|
+
services: {
|
|
27
|
+
'export-service': require('./server/services/export-service'),
|
|
28
|
+
'import-service': require('./server/services/import-service'),
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
contentTypes: {},
|
|
32
|
+
policies: {},
|
|
33
|
+
middlewares: {},
|
|
34
|
+
};
|