@strapi/i18n 0.0.0-experimental.da85533897155e719d784f0271223c866d2f69ab → 0.0.0-experimental.dad3c50630ca4fd9eccdcbe549ee632fc572e23d

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 (48) hide show
  1. package/dist/_chunks/{SettingsPage-DW0GwDcD.mjs → SettingsPage-B6QDUmu9.mjs} +4 -4
  2. package/dist/_chunks/SettingsPage-B6QDUmu9.mjs.map +1 -0
  3. package/dist/_chunks/{SettingsPage-a96ZyFLy.js → SettingsPage-BsHtr3lV.js} +5 -6
  4. package/dist/_chunks/SettingsPage-BsHtr3lV.js.map +1 -0
  5. package/dist/_chunks/{en-BsOU9o5z.js → en-BKBz3tro.js} +10 -3
  6. package/dist/_chunks/en-BKBz3tro.js.map +1 -0
  7. package/dist/_chunks/{en-CM6Pjfyv.mjs → en-DlXfy6Gy.mjs} +10 -3
  8. package/dist/_chunks/en-DlXfy6Gy.mjs.map +1 -0
  9. package/dist/_chunks/{index-sfNkjx75.js → index-3XgwXL6T.js} +414 -134
  10. package/dist/_chunks/index-3XgwXL6T.js.map +1 -0
  11. package/dist/_chunks/{index-4KJn181Q.mjs → index-iEQ79W05.mjs} +416 -135
  12. package/dist/_chunks/index-iEQ79W05.mjs.map +1 -0
  13. package/dist/admin/index.js +1 -1
  14. package/dist/admin/index.mjs +1 -1
  15. package/dist/admin/src/components/BulkLocaleActionModal.d.ts +2 -1
  16. package/dist/admin/src/components/CMHeaderActions.d.ts +29 -3
  17. package/dist/admin/src/components/CreateLocale.d.ts +6 -6
  18. package/dist/admin/src/components/LocaleListCell.d.ts +4 -4
  19. package/dist/admin/src/utils/clean.d.ts +4 -0
  20. package/dist/server/index.js +398 -487
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/index.mjs +399 -487
  23. package/dist/server/index.mjs.map +1 -1
  24. package/dist/server/src/bootstrap.d.ts +1 -4
  25. package/dist/server/src/bootstrap.d.ts.map +1 -1
  26. package/dist/server/src/index.d.ts +7 -11
  27. package/dist/server/src/index.d.ts.map +1 -1
  28. package/dist/server/src/register.d.ts.map +1 -1
  29. package/dist/server/src/services/index.d.ts +6 -8
  30. package/dist/server/src/services/index.d.ts.map +1 -1
  31. package/dist/server/src/services/sanitize/index.d.ts +11 -0
  32. package/dist/server/src/services/sanitize/index.d.ts.map +1 -0
  33. package/dist/server/src/utils/index.d.ts +2 -2
  34. package/dist/server/src/utils/index.d.ts.map +1 -1
  35. package/package.json +13 -13
  36. package/dist/_chunks/SettingsPage-DW0GwDcD.mjs.map +0 -1
  37. package/dist/_chunks/SettingsPage-a96ZyFLy.js.map +0 -1
  38. package/dist/_chunks/en-BsOU9o5z.js.map +0 -1
  39. package/dist/_chunks/en-CM6Pjfyv.mjs.map +0 -1
  40. package/dist/_chunks/index-4KJn181Q.mjs.map +0 -1
  41. package/dist/_chunks/index-sfNkjx75.js.map +0 -1
  42. package/dist/server/src/migrations/content-type/disable/index.d.ts +0 -3
  43. package/dist/server/src/migrations/content-type/disable/index.d.ts.map +0 -1
  44. package/dist/server/src/migrations/content-type/enable/index.d.ts +0 -3
  45. package/dist/server/src/migrations/content-type/enable/index.d.ts.map +0 -1
  46. package/dist/server/src/services/entity-service-decorator.d.ts +0 -29
  47. package/dist/server/src/services/entity-service-decorator.d.ts.map +0 -1
  48. package/strapi-server.js +0 -3
@@ -1,25 +1,32 @@
1
1
  import get from "lodash/get";
2
2
  import * as yup from "yup";
3
- import { jsxs, jsx } from "react/jsx-runtime";
3
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
4
4
  import * as React from "react";
5
5
  import { Typography, Dialog, Field, Checkbox, Flex, Button, Modal, Box, Status, IconButton, Tooltip, SingleSelect, SingleSelectOption, VisuallyHidden, useCollator, Popover } from "@strapi/design-system";
6
- import { WarningCircle, Pencil, CrossCircle, CheckCircle, ArrowsCounterClockwise, Trash, ListPlus, Earth, EarthStriked, CaretDown } from "@strapi/icons";
6
+ import { WarningCircle, Pencil, CrossCircle, CheckCircle, ArrowsCounterClockwise, Trash, Plus, Download, ListPlus, Cross, Earth, EarthStriked, CaretDown } from "@strapi/icons";
7
7
  import { useIntl } from "react-intl";
8
8
  import { styled } from "styled-components";
9
9
  import { skipToken } from "@reduxjs/toolkit/query";
10
- import { useAuth, adminApi, useTable, Table, useQueryParams, useNotification, useAPIErrorHandler } from "@strapi/admin/strapi-admin";
10
+ import { useAuth, adminApi, useTable, Table, useQueryParams, useForm, useNotification, useAPIErrorHandler } from "@strapi/admin/strapi-admin";
11
11
  import { unstable_useDocument, unstable_useDocumentActions, buildValidParams } from "@strapi/content-manager/strapi-admin";
12
12
  import { useParams, Link, useNavigate, matchPath } from "react-router-dom";
13
13
  import * as qs from "qs";
14
14
  import { stringify } from "qs";
15
15
  import omit from "lodash/omit";
16
- const __variableDynamicImportRuntimeHelper = (glob, path) => {
16
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
17
17
  const v = glob[path];
18
18
  if (v) {
19
19
  return typeof v === "function" ? v() : Promise.resolve(v);
20
20
  }
21
21
  return new Promise((_, reject) => {
22
- (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(reject.bind(null, new Error("Unknown variable dynamic import: " + path)));
22
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
23
+ reject.bind(
24
+ null,
25
+ new Error(
26
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
27
+ )
28
+ )
29
+ );
23
30
  });
24
31
  };
25
32
  const pluginId = "i18n";
@@ -217,10 +224,94 @@ const relationsApi = i18nApi.injectEndpoints({
217
224
  })
218
225
  });
219
226
  const { useGetManyDraftRelationCountQuery } = relationsApi;
227
+ const cleanData = (data, schema, components) => {
228
+ const cleanedData = removeFields(data, [
229
+ "createdAt",
230
+ "createdBy",
231
+ "updatedAt",
232
+ "updatedBy",
233
+ "id",
234
+ "documentId",
235
+ "publishedAt",
236
+ "strapi_stage",
237
+ "strapi_assignee",
238
+ "locale",
239
+ "status"
240
+ ]);
241
+ const cleanedDataWithoutPasswordAndRelation = recursiveRemoveFieldTypes(
242
+ cleanedData,
243
+ schema,
244
+ components,
245
+ ["relation", "password"]
246
+ );
247
+ return cleanedDataWithoutPasswordAndRelation;
248
+ };
249
+ const removeFields = (data, fields) => {
250
+ return Object.keys(data).reduce((acc, current) => {
251
+ if (fields.includes(current)) {
252
+ return acc;
253
+ }
254
+ acc[current] = data[current];
255
+ return acc;
256
+ }, {});
257
+ };
258
+ const recursiveRemoveFieldTypes = (data, schema, components, fields) => {
259
+ return Object.keys(data).reduce((acc, current) => {
260
+ const attribute = schema.attributes[current] ?? { type: void 0 };
261
+ if (fields.includes(attribute.type)) {
262
+ return acc;
263
+ }
264
+ if (attribute.type === "dynamiczone") {
265
+ acc[current] = data[current].map((componentValue, index2) => {
266
+ const { id: _, ...rest } = recursiveRemoveFieldTypes(
267
+ componentValue,
268
+ components[componentValue.__component],
269
+ components,
270
+ fields
271
+ );
272
+ return {
273
+ ...rest,
274
+ __temp_key__: index2 + 1
275
+ };
276
+ });
277
+ } else if (attribute.type === "component") {
278
+ const { repeatable, component } = attribute;
279
+ if (repeatable) {
280
+ acc[current] = (data[current] ?? []).map((compoData, index2) => {
281
+ const { id: _, ...rest } = recursiveRemoveFieldTypes(
282
+ compoData,
283
+ components[component],
284
+ components,
285
+ fields
286
+ );
287
+ return {
288
+ ...rest,
289
+ __temp_key__: index2 + 1
290
+ };
291
+ });
292
+ } else {
293
+ const { id: _, ...rest } = recursiveRemoveFieldTypes(
294
+ data[current] ?? {},
295
+ components[component],
296
+ components,
297
+ fields
298
+ );
299
+ acc[current] = rest;
300
+ }
301
+ } else {
302
+ acc[current] = data[current];
303
+ }
304
+ return acc;
305
+ }, {});
306
+ };
220
307
  const isErrorMessageDescriptor = (object) => {
221
308
  return typeof object === "object" && object !== null && "id" in object && "defaultMessage" in object;
222
309
  };
223
- const EntryValidationText = ({ status = "draft", validationErrors }) => {
310
+ const EntryValidationText = ({
311
+ status = "draft",
312
+ validationErrors,
313
+ action
314
+ }) => {
224
315
  const { formatMessage } = useIntl();
225
316
  const getErrorStr = (key, value) => {
226
317
  if (typeof value === "string") {
@@ -254,30 +345,63 @@ const EntryValidationText = ({ status = "draft", validationErrors }) => {
254
345
  ) })
255
346
  ] });
256
347
  }
257
- if (status === "published") {
258
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
259
- /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
260
- /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
261
- id: "content-manager.bulk-publish.already-published",
262
- defaultMessage: "Already Published"
263
- }) })
264
- ] });
265
- }
266
- if (status === "modified") {
267
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
268
- /* @__PURE__ */ jsx(ArrowsCounterClockwise, { fill: "alternative600" }),
269
- /* @__PURE__ */ jsx(Typography, { children: formatMessage({
270
- id: "app.utils.ready-to-publish-changes",
271
- defaultMessage: "Ready to publish changes"
272
- }) })
273
- ] });
274
- }
348
+ const getStatusMessage = () => {
349
+ if (action === "bulk-publish") {
350
+ if (status === "published") {
351
+ return {
352
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
353
+ text: formatMessage({
354
+ id: "content-manager.bulk-publish.already-published",
355
+ defaultMessage: "Already Published"
356
+ }),
357
+ textColor: "success600",
358
+ fontWeight: "bold"
359
+ };
360
+ } else if (status === "modified") {
361
+ return {
362
+ icon: /* @__PURE__ */ jsx(ArrowsCounterClockwise, { fill: "alternative600" }),
363
+ text: formatMessage({
364
+ id: "app.utils.ready-to-publish-changes",
365
+ defaultMessage: "Ready to publish changes"
366
+ })
367
+ };
368
+ } else {
369
+ return {
370
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
371
+ text: formatMessage({
372
+ id: "app.utils.ready-to-publish",
373
+ defaultMessage: "Ready to publish"
374
+ })
375
+ };
376
+ }
377
+ } else {
378
+ if (status === "draft") {
379
+ return {
380
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
381
+ text: formatMessage({
382
+ id: "content-manager.bulk-unpublish.already-unpublished",
383
+ defaultMessage: "Already Unpublished"
384
+ }),
385
+ textColor: "success600",
386
+ fontWeight: "bold"
387
+ };
388
+ } else {
389
+ return {
390
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
391
+ text: formatMessage({
392
+ id: "app.utils.ready-to-unpublish-changes",
393
+ defaultMessage: "Ready to unpublish"
394
+ }),
395
+ textColor: "success600",
396
+ fontWeight: "bold"
397
+ };
398
+ }
399
+ }
400
+ };
401
+ const { icon, text, textColor = "success600", fontWeight = "normal" } = getStatusMessage();
275
402
  return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
276
- /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
277
- /* @__PURE__ */ jsx(Typography, { children: formatMessage({
278
- id: "app.utils.ready-to-publish",
279
- defaultMessage: "Ready to publish"
280
- }) })
403
+ icon,
404
+ /* @__PURE__ */ jsx(Typography, { textColor, fontWeight, children: text })
281
405
  ] });
282
406
  };
283
407
  const BoldChunk = (chunks) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: chunks });
@@ -285,7 +409,8 @@ const BulkLocaleActionModal = ({
285
409
  headers,
286
410
  rows,
287
411
  localesMetadata,
288
- validationErrors = {}
412
+ validationErrors = {},
413
+ action
289
414
  }) => {
290
415
  const { formatMessage } = useIntl();
291
416
  const selectedRows = useTable(
@@ -298,22 +423,24 @@ const BulkLocaleActionModal = ({
298
423
  return acc;
299
424
  }, {});
300
425
  const localesWithErrors = Object.keys(validationErrors);
301
- const alreadyPublishedCount = selectedRows.filter(
426
+ const publishedCount = selectedRows.filter(
302
427
  ({ locale }) => currentStatusByLocale[locale] === "published"
303
428
  ).length;
304
- const readyToPublishCount = selectedRows.filter(
429
+ const draftCount = selectedRows.filter(
305
430
  ({ locale }) => (currentStatusByLocale[locale] === "draft" || currentStatusByLocale[locale] === "modified") && !localesWithErrors.includes(locale)
306
431
  ).length;
307
432
  const withErrorsCount = localesWithErrors.length;
433
+ const messageId = action === "bulk-publish" ? "content-manager.containers.list.selectedEntriesModal.selectedCount.publish" : "content-manager.containers.list.selectedEntriesModal.selectedCount.unpublish";
434
+ const defaultMessage = action === "bulk-publish" ? "<b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action." : "<b>{draftCount}</b> {draftCount, plural, =0 {entries} one {entry} other {entries}} already unpublished. <b>{publishedCount}</b> {publishedCount, plural, =0 {entries} one {entry} other {entries}} ready to unpublish.";
308
435
  return formatMessage(
309
436
  {
310
- id: "content-manager.containers.list.selectedEntriesModal.selectedCount",
311
- defaultMessage: "<b>{alreadyPublishedCount}</b> {alreadyPublishedCount, plural, =0 {entries} one {entry} other {entries}} already published. <b>{readyToPublishCount}</b> {readyToPublishCount, plural, =0 {entries} one {entry} other {entries}} ready to publish. <b>{withErrorsCount}</b> {withErrorsCount, plural, =0 {entries} one {entry} other {entries}} waiting for action."
437
+ id: messageId,
438
+ defaultMessage
312
439
  },
313
440
  {
314
441
  withErrorsCount,
315
- readyToPublishCount,
316
- alreadyPublishedCount,
442
+ draftCount,
443
+ publishedCount,
317
444
  b: BoldChunk
318
445
  }
319
446
  );
@@ -339,13 +466,12 @@ const BulkLocaleActionModal = ({
339
466
  paddingRight: "6px",
340
467
  paddingTop: "2px",
341
468
  paddingBottom: "2px",
342
- showBullet: false,
343
469
  size: "S",
344
470
  variant: statusVariant,
345
471
  children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "pi", fontWeight: "bold", children: capitalize(status) })
346
472
  }
347
473
  ) }) }),
348
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(EntryValidationText, { validationErrors: error, status }) }),
474
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(EntryValidationText, { validationErrors: error, status, action }) }),
349
475
  /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
350
476
  IconButton,
351
477
  {
@@ -371,6 +497,47 @@ const BulkLocaleActionModal = ({
371
497
  ] }) })
372
498
  ] });
373
499
  };
500
+ const statusVariants = {
501
+ draft: "secondary",
502
+ published: "success",
503
+ modified: "alternative"
504
+ };
505
+ const LocaleOption = ({
506
+ isDraftAndPublishEnabled,
507
+ locale,
508
+ status,
509
+ entryExists
510
+ }) => {
511
+ const { formatMessage } = useIntl();
512
+ if (!entryExists) {
513
+ return formatMessage(
514
+ {
515
+ id: getTranslation("CMEditViewLocalePicker.locale.create"),
516
+ defaultMessage: "Create <bold>{locale}</bold> locale"
517
+ },
518
+ {
519
+ bold: (locale2) => /* @__PURE__ */ jsx("b", { children: locale2 }),
520
+ locale: locale.name
521
+ }
522
+ );
523
+ }
524
+ return /* @__PURE__ */ jsxs(Flex, { width: "100%", gap: 1, justifyContent: "space-between", children: [
525
+ /* @__PURE__ */ jsx(Typography, { children: locale.name }),
526
+ isDraftAndPublishEnabled ? /* @__PURE__ */ jsx(
527
+ Status,
528
+ {
529
+ display: "flex",
530
+ paddingLeft: "6px",
531
+ paddingRight: "6px",
532
+ paddingTop: "2px",
533
+ paddingBottom: "2px",
534
+ size: "S",
535
+ variant: statusVariants[status],
536
+ children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "pi", fontWeight: "bold", children: capitalize(status) })
537
+ }
538
+ ) : null
539
+ ] });
540
+ };
374
541
  const LocalePickerAction = ({
375
542
  document,
376
543
  meta,
@@ -382,7 +549,13 @@ const LocalePickerAction = ({
382
549
  const [{ query }, setQuery] = useQueryParams();
383
550
  const { hasI18n, canCreate, canRead } = useI18n();
384
551
  const { data: locales = [] } = useGetLocalesQuery();
385
- const { schema } = unstable_useDocument({ model, collectionType, documentId });
552
+ const currentDesiredLocale = query.plugins?.i18n?.locale;
553
+ const { schema } = unstable_useDocument({
554
+ model,
555
+ collectionType,
556
+ documentId,
557
+ params: { locale: currentDesiredLocale }
558
+ });
386
559
  const handleSelect = React.useCallback(
387
560
  (value) => {
388
561
  setQuery({
@@ -400,53 +573,50 @@ const LocalePickerAction = ({
400
573
  if (!Array.isArray(locales) || !hasI18n) {
401
574
  return;
402
575
  }
403
- const currentDesiredLocale = query.plugins?.i18n?.locale;
404
576
  const doesLocaleExist = locales.find((loc) => loc.code === currentDesiredLocale);
405
577
  const defaultLocale = locales.find((locale) => locale.isDefault);
406
578
  if (!doesLocaleExist && defaultLocale?.code) {
407
579
  handleSelect(defaultLocale.code);
408
580
  }
409
- }, [handleSelect, hasI18n, locales, query.plugins?.i18n?.locale]);
581
+ }, [handleSelect, hasI18n, locales, currentDesiredLocale]);
582
+ const currentLocale = Array.isArray(locales) ? locales.find((locale) => locale.code === currentDesiredLocale) : void 0;
583
+ const allCurrentLocales = [
584
+ { status: getDocumentStatus(document, meta), locale: currentLocale?.code },
585
+ ...document?.localizations ?? []
586
+ ];
410
587
  if (!hasI18n || !Array.isArray(locales) || locales.length === 0) {
411
588
  return null;
412
589
  }
413
- const currentLocale = query.plugins?.i18n?.locale || locales.find((loc) => loc.isDefault)?.code;
414
- const allCurrentLocales = [
415
- { status: getDocumentStatus(document, meta), locale: currentLocale },
416
- ...meta?.availableLocales ?? []
417
- ];
590
+ const displayedLocales = locales.filter((locale) => {
591
+ return canRead.includes(locale.code);
592
+ });
418
593
  return {
419
594
  label: formatMessage({
420
595
  id: getTranslation("Settings.locales.modal.locales.label"),
421
596
  defaultMessage: "Locales"
422
597
  }),
423
- options: locales.map((locale) => {
598
+ options: displayedLocales.map((locale) => {
599
+ const entryWithLocaleExists = allCurrentLocales.some((doc) => doc.locale === locale.code);
424
600
  const currentLocaleDoc = allCurrentLocales.find(
425
601
  (doc) => "locale" in doc ? doc.locale === locale.code : false
426
602
  );
427
- const status = currentLocaleDoc?.status ?? "draft";
428
- const permissionsToCheck = currentLocaleDoc ? canCreate : canRead;
429
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
603
+ const permissionsToCheck = currentLocaleDoc ? canRead : canCreate;
430
604
  return {
431
605
  disabled: !permissionsToCheck.includes(locale.code),
432
606
  value: locale.code,
433
- label: locale.name,
434
- startIcon: schema?.options?.draftAndPublish ? /* @__PURE__ */ jsx(
435
- Status,
607
+ label: /* @__PURE__ */ jsx(
608
+ LocaleOption,
436
609
  {
437
- display: "flex",
438
- paddingLeft: "6px",
439
- paddingRight: "6px",
440
- paddingTop: "2px",
441
- paddingBottom: "2px",
442
- showBullet: false,
443
- size: "S",
444
- variant: statusVariant,
445
- children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "pi", fontWeight: "bold", children: capitalize(status) })
610
+ isDraftAndPublishEnabled: !!schema?.options?.draftAndPublish,
611
+ locale,
612
+ status: currentLocaleDoc?.status,
613
+ entryExists: entryWithLocaleExists
446
614
  }
447
- ) : null
615
+ ),
616
+ startIcon: !entryWithLocaleExists ? /* @__PURE__ */ jsx(Plus, {}) : null
448
617
  };
449
618
  }),
619
+ customizeContent: () => currentLocale?.name,
450
620
  onSelect: handleSelect,
451
621
  value: currentLocale
452
622
  };
@@ -462,6 +632,99 @@ const getDocumentStatus = (document, meta) => {
462
632
  }
463
633
  return docStatus;
464
634
  };
635
+ const FillFromAnotherLocaleAction = ({
636
+ documentId,
637
+ meta,
638
+ model,
639
+ collectionType
640
+ }) => {
641
+ const { formatMessage } = useIntl();
642
+ const [{ query }] = useQueryParams();
643
+ const { hasI18n } = useI18n();
644
+ const currentDesiredLocale = query.plugins?.i18n?.locale;
645
+ const [localeSelected, setLocaleSelected] = React.useState(null);
646
+ const setValues = useForm("FillFromAnotherLocale", (state) => state.setValues);
647
+ const { getDocument } = unstable_useDocumentActions();
648
+ const { schema, components } = unstable_useDocument({
649
+ model,
650
+ documentId,
651
+ collectionType,
652
+ params: { locale: currentDesiredLocale }
653
+ });
654
+ const { data: locales = [] } = useGetLocalesQuery();
655
+ const availableLocales = Array.isArray(locales) ? locales.filter((locale) => meta?.availableLocales.some((l) => l.locale === locale.code)) : [];
656
+ const fillFromLocale = (onClose) => async () => {
657
+ const response = await getDocument({
658
+ collectionType,
659
+ model,
660
+ documentId,
661
+ params: { locale: localeSelected }
662
+ });
663
+ if (!response || !schema) {
664
+ return;
665
+ }
666
+ const { data } = response;
667
+ const cleanedData = cleanData(data, schema, components);
668
+ setValues(cleanedData);
669
+ onClose();
670
+ };
671
+ if (!hasI18n) {
672
+ return null;
673
+ }
674
+ return {
675
+ type: "icon",
676
+ icon: /* @__PURE__ */ jsx(Download, {}),
677
+ disabled: availableLocales.length === 0,
678
+ label: formatMessage({
679
+ id: getTranslation("CMEditViewCopyLocale.copy-text"),
680
+ defaultMessage: "Fill in from another locale"
681
+ }),
682
+ dialog: {
683
+ type: "dialog",
684
+ title: formatMessage({
685
+ id: getTranslation("CMEditViewCopyLocale.dialog.title"),
686
+ defaultMessage: "Confirmation"
687
+ }),
688
+ content: ({ onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [
689
+ /* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
690
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
691
+ /* @__PURE__ */ jsx(Typography, { textAlign: "center", children: formatMessage({
692
+ id: getTranslation("CMEditViewCopyLocale.dialog.body"),
693
+ defaultMessage: "Your current content will be erased and filled by the content of the selected locale:"
694
+ }) }),
695
+ /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
696
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
697
+ id: getTranslation("CMEditViewCopyLocale.dialog.field.label"),
698
+ defaultMessage: "Locale"
699
+ }) }),
700
+ /* @__PURE__ */ jsx(
701
+ SingleSelect,
702
+ {
703
+ value: localeSelected,
704
+ placeholder: formatMessage({
705
+ id: getTranslation("CMEditViewCopyLocale.dialog.field.placeholder"),
706
+ defaultMessage: "Select one locale..."
707
+ }),
708
+ onChange: (value) => setLocaleSelected(value),
709
+ children: availableLocales.map((locale) => /* @__PURE__ */ jsx(SingleSelectOption, { value: locale.code, children: locale.name }, locale.code))
710
+ }
711
+ )
712
+ ] })
713
+ ] }) }),
714
+ /* @__PURE__ */ jsx(Dialog.Footer, { children: /* @__PURE__ */ jsxs(Flex, { gap: 2, width: "100%", children: [
715
+ /* @__PURE__ */ jsx(Button, { flex: "auto", variant: "tertiary", onClick: onClose, children: formatMessage({
716
+ id: getTranslation("CMEditViewCopyLocale.cancel-text"),
717
+ defaultMessage: "No, cancel"
718
+ }) }),
719
+ /* @__PURE__ */ jsx(Button, { flex: "auto", variant: "success", onClick: fillFromLocale(onClose), children: formatMessage({
720
+ id: getTranslation("CMEditViewCopyLocale.submit-text"),
721
+ defaultMessage: "Yes, fill in"
722
+ }) })
723
+ ] }) })
724
+ ] })
725
+ }
726
+ };
727
+ };
465
728
  const DeleteLocaleAction = ({
466
729
  document,
467
730
  documentId,
@@ -473,16 +736,23 @@ const DeleteLocaleAction = ({
473
736
  const { toggleNotification } = useNotification();
474
737
  const { delete: deleteAction } = unstable_useDocumentActions();
475
738
  const { hasI18n, canDelete } = useI18n();
739
+ const [{ query }] = useQueryParams();
740
+ const { data: locales = [] } = useGetLocalesQuery();
741
+ const currentDesiredLocale = query.plugins?.i18n?.locale;
742
+ const locale = !("error" in locales) && locales.find((loc) => loc.code === currentDesiredLocale);
476
743
  if (!hasI18n) {
477
744
  return null;
478
745
  }
479
746
  return {
480
747
  disabled: document?.locale && !canDelete.includes(document.locale) || !document || !document.id,
481
748
  position: ["header", "table-row"],
482
- label: formatMessage({
483
- id: getTranslation("actions.delete.label"),
484
- defaultMessage: "Delete locale"
485
- }),
749
+ label: formatMessage(
750
+ {
751
+ id: getTranslation("actions.delete.label"),
752
+ defaultMessage: "Delete entry ({locale})"
753
+ },
754
+ { locale: locale && locale.name }
755
+ ),
486
756
  icon: /* @__PURE__ */ jsx(StyledTrash, {}),
487
757
  variant: "danger",
488
758
  dialog: {
@@ -499,7 +769,12 @@ const DeleteLocaleAction = ({
499
769
  }) })
500
770
  ] }),
501
771
  onConfirm: async () => {
502
- if (!documentId || !document?.locale) {
772
+ const unableToDelete = (
773
+ // We are unable to delete a collection type without a document ID
774
+ // & unable to delete generally if there is no document locale
775
+ collectionType !== "single-types" && !documentId || !document?.locale
776
+ );
777
+ if (unableToDelete) {
503
778
  console.error(
504
779
  "You're trying to delete a document without an id or locale, this is likely a bug with Strapi. Please open an issue."
505
780
  );
@@ -525,39 +800,36 @@ const DeleteLocaleAction = ({
525
800
  }
526
801
  };
527
802
  };
528
- const BulkLocalePublishAction = ({
529
- document: baseDocument,
803
+ const BulkLocaleAction = ({
804
+ document,
530
805
  documentId,
531
806
  model,
532
- collectionType
807
+ collectionType,
808
+ action
533
809
  }) => {
534
- const baseLocale = baseDocument?.locale ?? null;
810
+ const locale = document?.locale ?? null;
535
811
  const [{ query }] = useQueryParams();
536
812
  const params = React.useMemo(() => buildValidParams(query), [query]);
537
- const isPublishedTab = query.status === "published";
813
+ const isOnPublishedTab = query.status === "published";
538
814
  const { formatMessage } = useIntl();
539
815
  const { hasI18n, canPublish } = useI18n();
540
816
  const { toggleNotification } = useNotification();
541
817
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
542
818
  const [selectedRows, setSelectedRows] = React.useState([]);
543
819
  const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] = React.useState(false);
544
- const { publishMany: publishManyAction } = unstable_useDocumentActions();
545
- const {
546
- document,
547
- meta: documentMeta,
548
- schema,
549
- validate
550
- } = unstable_useDocument(
820
+ const { publishMany: publishManyAction, unpublishMany: unpublishManyAction } = unstable_useDocumentActions();
821
+ const { schema, validate } = unstable_useDocument(
551
822
  {
552
823
  model,
553
824
  collectionType,
554
825
  documentId,
555
826
  params: {
556
- locale: baseLocale
827
+ locale
557
828
  }
558
829
  },
559
830
  {
560
- skip: !hasI18n
831
+ // No need to fetch the document, the data is already available in the `document` prop
832
+ skip: true
561
833
  }
562
834
  );
563
835
  const { data: localesMetadata = [] } = useGetLocalesQuery(hasI18n ? void 0 : skipToken);
@@ -585,18 +857,19 @@ const BulkLocalePublishAction = ({
585
857
  }
586
858
  ];
587
859
  const [rows, validationErrors] = React.useMemo(() => {
588
- if (!document || !documentMeta?.availableLocales) {
860
+ if (!document) {
589
861
  return [[], {}];
590
862
  }
591
- const rowsFromMeta = documentMeta?.availableLocales.map((doc) => {
592
- const { locale, status } = doc;
593
- return { locale, status };
863
+ const localizations = document.localizations ?? [];
864
+ const locales = localizations.map((doc) => {
865
+ const { locale: locale2, status } = doc;
866
+ return { locale: locale2, status };
594
867
  });
595
- rowsFromMeta.unshift({
868
+ locales.unshift({
596
869
  locale: document.locale,
597
870
  status: document.status
598
871
  });
599
- const allDocuments = [document, ...documentMeta?.availableLocales ?? []];
872
+ const allDocuments = [document, ...localizations];
600
873
  const errors = allDocuments.reduce((errs, document2) => {
601
874
  if (!document2) {
602
875
  return errs;
@@ -607,14 +880,21 @@ const BulkLocalePublishAction = ({
607
880
  }
608
881
  return errs;
609
882
  }, {});
610
- return [rowsFromMeta, errors];
611
- }, [document, documentMeta?.availableLocales, validate]);
612
- const localesToPublish = selectedRows.reduce((acc, selectedRow) => {
613
- if (selectedRow.status !== "published" && !Object.keys(validationErrors).includes(selectedRow.locale)) {
883
+ return [locales, errors];
884
+ }, [document, validate]);
885
+ const isBulkPublish = action === "bulk-publish";
886
+ const localesForAction = selectedRows.reduce((acc, selectedRow) => {
887
+ const isValidLocale = (
888
+ // Validation errors are irrelevant if we are trying to unpublish
889
+ !isBulkPublish || !Object.keys(validationErrors).includes(selectedRow.locale)
890
+ );
891
+ const shouldAddLocale = isBulkPublish ? selectedRow.status !== "published" && isValidLocale : selectedRow.status !== "draft" && isValidLocale;
892
+ if (shouldAddLocale) {
614
893
  acc.push(selectedRow.locale);
615
894
  }
616
895
  return acc;
617
896
  }, []);
897
+ const enableDraftRelationsCount = false;
618
898
  const {
619
899
  data: draftRelationsCount = 0,
620
900
  isLoading: isDraftRelationsLoading,
@@ -623,10 +903,10 @@ const BulkLocalePublishAction = ({
623
903
  {
624
904
  model,
625
905
  documentIds: [documentId],
626
- locale: localesToPublish
906
+ locale: localesForAction
627
907
  },
628
908
  {
629
- skip: !documentId || localesToPublish.length === 0
909
+ skip: !enableDraftRelationsCount
630
910
  }
631
911
  );
632
912
  React.useEffect(() => {
@@ -652,7 +932,18 @@ const BulkLocalePublishAction = ({
652
932
  documentIds: [documentId],
653
933
  params: {
654
934
  ...params,
655
- locale: localesToPublish
935
+ locale: localesForAction
936
+ }
937
+ });
938
+ setSelectedRows([]);
939
+ };
940
+ const unpublish = async () => {
941
+ await unpublishManyAction({
942
+ model,
943
+ documentIds: [documentId],
944
+ params: {
945
+ ...params,
946
+ locale: localesForAction
656
947
  }
657
948
  });
658
949
  setSelectedRows([]);
@@ -660,14 +951,12 @@ const BulkLocalePublishAction = ({
660
951
  const handleAction = async () => {
661
952
  if (draftRelationsCount > 0) {
662
953
  setIsDraftRelationConfirmationOpen(true);
663
- } else {
954
+ } else if (isBulkPublish) {
664
955
  await publish();
956
+ } else {
957
+ await unpublish();
665
958
  }
666
959
  };
667
- const isUnpublish = document?.status === "published";
668
- if (isUnpublish) {
669
- console.warn(["I18N"], "Bulk locale unpublish modal not implemented");
670
- }
671
960
  if (isDraftRelationConfirmationOpen) {
672
961
  return {
673
962
  label: formatMessage({
@@ -702,21 +991,21 @@ const BulkLocalePublishAction = ({
702
991
  }
703
992
  };
704
993
  }
705
- const hasPermission = selectedRows.map(({ locale }) => locale).every((locale) => canPublish.includes(locale));
994
+ const hasPermission = selectedRows.map(({ locale: locale2 }) => locale2).every((locale2) => canPublish.includes(locale2));
706
995
  return {
707
996
  label: formatMessage({
708
- id: getTranslation("CMEditViewBulkLocale.publish-title"),
709
- defaultMessage: "Publish Multiple Locales"
997
+ id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? "publish" : "unpublish"}-title`),
998
+ defaultMessage: `${isBulkPublish ? "Publish" : "Unpublish"} Multiple Locales`
710
999
  }),
711
- icon: /* @__PURE__ */ jsx(ListPlus, {}),
712
- disabled: isPublishedTab || canPublish.length === 0,
1000
+ variant: isBulkPublish ? "secondary" : "danger",
1001
+ icon: isBulkPublish ? /* @__PURE__ */ jsx(ListPlus, {}) : /* @__PURE__ */ jsx(Cross, {}),
1002
+ disabled: isOnPublishedTab || canPublish.length === 0,
713
1003
  position: ["panel"],
714
- variant: "secondary",
715
1004
  dialog: {
716
1005
  type: "modal",
717
1006
  title: formatMessage({
718
- id: getTranslation("CMEditViewBulkLocale.publish-title"),
719
- defaultMessage: "Publish Multiple Locales"
1007
+ id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? "publish" : "unpublish"}-title`),
1008
+ defaultMessage: `${isBulkPublish ? "Publish" : "Unpublish"} Multiple Locales`
720
1009
  }),
721
1010
  content: () => {
722
1011
  return /* @__PURE__ */ jsx(
@@ -735,7 +1024,8 @@ const BulkLocalePublishAction = ({
735
1024
  validationErrors,
736
1025
  headers,
737
1026
  rows,
738
- localesMetadata
1027
+ localesMetadata,
1028
+ action: action ?? "bulk-publish"
739
1029
  }
740
1030
  )
741
1031
  }
@@ -745,18 +1035,24 @@ const BulkLocalePublishAction = ({
745
1035
  Button,
746
1036
  {
747
1037
  loading: isDraftRelationsLoading,
748
- disabled: !hasPermission || localesToPublish.length === 0,
1038
+ disabled: !hasPermission || localesForAction.length === 0,
749
1039
  variant: "default",
750
1040
  onClick: handleAction,
751
1041
  children: formatMessage({
752
- id: "app.utils.publish",
753
- defaultMessage: "Publish"
1042
+ id: isBulkPublish ? "app.utils.publish" : "app.utils.unpublish",
1043
+ defaultMessage: isBulkPublish ? "Publish" : "Unpublish"
754
1044
  })
755
1045
  }
756
1046
  ) })
757
1047
  }
758
1048
  };
759
1049
  };
1050
+ const BulkLocalePublishAction = (props) => {
1051
+ return BulkLocaleAction({ action: "bulk-publish", ...props });
1052
+ };
1053
+ const BulkLocaleUnpublishAction = (props) => {
1054
+ return BulkLocaleAction({ action: "bulk-unpublish", ...props });
1055
+ };
760
1056
  const StyledTrash = styled(Trash)`
761
1057
  path {
762
1058
  fill: currentColor;
@@ -938,29 +1234,16 @@ const Span = styled(Flex)`
938
1234
  }
939
1235
  }
940
1236
  `;
941
- const LocaleListCell = ({
942
- documentId,
943
- locale: currentLocale,
944
- collectionType,
945
- model
946
- }) => {
947
- const { meta, isLoading } = unstable_useDocument({
948
- documentId,
949
- collectionType,
950
- model,
951
- params: {
952
- locale: currentLocale
953
- }
954
- });
1237
+ const LocaleListCell = ({ locale: currentLocale, localizations }) => {
955
1238
  const { locale: language } = useIntl();
956
1239
  const { data: locales = [] } = useGetLocalesQuery();
957
1240
  const formatter = useCollator(language, {
958
1241
  sensitivity: "base"
959
1242
  });
960
- if (!Array.isArray(locales) || isLoading) {
1243
+ if (!Array.isArray(locales) || !localizations) {
961
1244
  return null;
962
1245
  }
963
- const availableLocales = meta?.availableLocales.map((doc) => doc.locale) ?? [];
1246
+ const availableLocales = localizations.map((loc) => loc.locale);
964
1247
  const localesForDocument = locales.reduce((acc, locale) => {
965
1248
  const createdLocale = [currentLocale, ...availableLocales].find((loc) => {
966
1249
  return loc === locale.code;
@@ -1108,9 +1391,6 @@ const localeMiddleware = (ctx) => (next) => (permissions) => {
1108
1391
  return next(revisedPermissions);
1109
1392
  };
1110
1393
  const prefixPluginTranslations = (trad, pluginId2) => {
1111
- if (!pluginId2) {
1112
- throw new TypeError("pluginId can't be empty");
1113
- }
1114
1394
  return Object.keys(trad).reduce((acc, current) => {
1115
1395
  acc[`${pluginId2}.${current}`] = trad[current];
1116
1396
  return acc;
@@ -1180,11 +1460,11 @@ const index = {
1180
1460
  },
1181
1461
  id: "internationalization",
1182
1462
  to: "internationalization",
1183
- Component: () => import("./SettingsPage-DW0GwDcD.mjs").then((mod) => ({ default: mod.ProtectedSettingsPage })),
1463
+ Component: () => import("./SettingsPage-B6QDUmu9.mjs").then((mod) => ({ default: mod.ProtectedSettingsPage })),
1184
1464
  permissions: PERMISSIONS.accessMain
1185
1465
  });
1186
1466
  const contentManager = app.getPlugin("content-manager");
1187
- contentManager.apis.addDocumentHeaderAction([LocalePickerAction]);
1467
+ contentManager.apis.addDocumentHeaderAction([LocalePickerAction, FillFromAnotherLocaleAction]);
1188
1468
  contentManager.apis.addDocumentAction((actions) => {
1189
1469
  const indexOfDeleteAction = actions.findIndex((action) => action.type === "delete");
1190
1470
  actions.splice(indexOfDeleteAction, 0, DeleteLocaleAction);
@@ -1192,6 +1472,7 @@ const index = {
1192
1472
  });
1193
1473
  contentManager.apis.addDocumentAction((actions) => {
1194
1474
  actions.splice(2, 0, BulkLocalePublishAction);
1475
+ actions.splice(5, 0, BulkLocaleUnpublishAction);
1195
1476
  return actions;
1196
1477
  });
1197
1478
  contentManager.injectComponent("listView", "actions", {
@@ -1297,7 +1578,7 @@ const index = {
1297
1578
  async registerTrads({ locales }) {
1298
1579
  const importedTrads = await Promise.all(
1299
1580
  locales.map((locale) => {
1300
- return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => import("./de-9eCAqqrB.mjs"), "./translations/dk.json": () => import("./dk-2qBjxt-P.mjs"), "./translations/en.json": () => import("./en-CM6Pjfyv.mjs"), "./translations/es.json": () => import("./es-DlmMVaBG.mjs"), "./translations/fr.json": () => import("./fr-3S6ke71d.mjs"), "./translations/ko.json": () => import("./ko-qTjQ8IMw.mjs"), "./translations/pl.json": () => import("./pl-B67TSHqT.mjs"), "./translations/ru.json": () => import("./ru-hagMa57T.mjs"), "./translations/tr.json": () => import("./tr-Dw_jmkG-.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-Dyc-aR-h.mjs"), "./translations/zh.json": () => import("./zh-57YM4amO.mjs") }), `./translations/${locale}.json`).then(({ default: data }) => {
1581
+ return __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => import("./de-9eCAqqrB.mjs"), "./translations/dk.json": () => import("./dk-2qBjxt-P.mjs"), "./translations/en.json": () => import("./en-DlXfy6Gy.mjs"), "./translations/es.json": () => import("./es-DlmMVaBG.mjs"), "./translations/fr.json": () => import("./fr-3S6ke71d.mjs"), "./translations/ko.json": () => import("./ko-qTjQ8IMw.mjs"), "./translations/pl.json": () => import("./pl-B67TSHqT.mjs"), "./translations/ru.json": () => import("./ru-hagMa57T.mjs"), "./translations/tr.json": () => import("./tr-Dw_jmkG-.mjs"), "./translations/zh-Hans.json": () => import("./zh-Hans-Dyc-aR-h.mjs"), "./translations/zh.json": () => import("./zh-57YM4amO.mjs") }), `./translations/${locale}.json`, 3).then(({ default: data }) => {
1301
1582
  return {
1302
1583
  data: prefixPluginTranslations(data, pluginId),
1303
1584
  locale
@@ -1323,4 +1604,4 @@ export {
1323
1604
  index as i,
1324
1605
  useCreateLocaleMutation as u
1325
1606
  };
1326
- //# sourceMappingURL=index-4KJn181Q.mjs.map
1607
+ //# sourceMappingURL=index-iEQ79W05.mjs.map