@strapi/i18n 0.0.0-experimental.25e22c6cc9bc6b35392bb55d09f641a0a65e7403 → 0.0.0-experimental.31a5317d54179a9ce6225bf1b6e1f9fb6c372fa9

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 (41) hide show
  1. package/LICENSE +18 -3
  2. package/dist/_chunks/{SettingsPage-Dsi2qGtq.mjs → SettingsPage-BjxjwEOb.mjs} +15 -10
  3. package/dist/_chunks/SettingsPage-BjxjwEOb.mjs.map +1 -0
  4. package/dist/_chunks/{SettingsPage-VN7sTzkb.js → SettingsPage-CfTmCkup.js} +15 -10
  5. package/dist/_chunks/SettingsPage-CfTmCkup.js.map +1 -0
  6. package/dist/_chunks/{en-18tWw4P6.mjs → en-2xztdZE1.mjs} +9 -1
  7. package/dist/_chunks/en-2xztdZE1.mjs.map +1 -0
  8. package/dist/_chunks/{en-Kv6y9zPQ.js → en-DWpfm8h5.js} +9 -1
  9. package/dist/_chunks/en-DWpfm8h5.js.map +1 -0
  10. package/dist/_chunks/{index-kedPlCo6.js → index-5XLZwzwx.js} +356 -164
  11. package/dist/_chunks/index-5XLZwzwx.js.map +1 -0
  12. package/dist/_chunks/{index-DhtjJYrx.mjs → index-D-qx3tz4.mjs} +352 -160
  13. package/dist/_chunks/index-D-qx3tz4.mjs.map +1 -0
  14. package/dist/admin/index.js +1 -1
  15. package/dist/admin/index.mjs +1 -1
  16. package/dist/admin/src/components/BulkLocaleActionModal.d.ts +2 -1
  17. package/dist/admin/src/components/CMHeaderActions.d.ts +27 -3
  18. package/dist/admin/src/contentReleasesHooks/releaseDetailsView.d.ts +9 -5
  19. package/dist/admin/src/utils/clean.d.ts +4 -0
  20. package/dist/admin/src/utils/schemas.d.ts +1 -0
  21. package/dist/server/index.js +60 -9
  22. package/dist/server/index.js.map +1 -1
  23. package/dist/server/index.mjs +61 -10
  24. package/dist/server/index.mjs.map +1 -1
  25. package/dist/server/src/bootstrap.d.ts.map +1 -1
  26. package/dist/server/src/index.d.ts +14 -2
  27. package/dist/server/src/index.d.ts.map +1 -1
  28. package/dist/server/src/services/index.d.ts +14 -2
  29. package/dist/server/src/services/index.d.ts.map +1 -1
  30. package/dist/server/src/services/permissions/actions.d.ts +14 -2
  31. package/dist/server/src/services/permissions/actions.d.ts.map +1 -1
  32. package/dist/server/src/services/permissions.d.ts +14 -2
  33. package/dist/server/src/services/permissions.d.ts.map +1 -1
  34. package/package.json +9 -9
  35. package/dist/_chunks/SettingsPage-Dsi2qGtq.mjs.map +0 -1
  36. package/dist/_chunks/SettingsPage-VN7sTzkb.js.map +0 -1
  37. package/dist/_chunks/en-18tWw4P6.mjs.map +0 -1
  38. package/dist/_chunks/en-Kv6y9zPQ.js.map +0 -1
  39. package/dist/_chunks/index-DhtjJYrx.mjs.map +0 -1
  40. package/dist/_chunks/index-kedPlCo6.js.map +0 -1
  41. package/dist/admin/src/components/Initializer.d.ts +0 -5
@@ -1,12 +1,13 @@
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
- import { Typography, Dialog, Field, Checkbox, Flex, Button as Button$1, 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";
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, Download, ListPlus, Cross, Earth, EarthStriked, CaretDown } from "@strapi/icons";
7
7
  import { useIntl } from "react-intl";
8
8
  import { styled } from "styled-components";
9
- import { useAuth, adminApi, useTable, Table, useQueryParams, useNotification, useAPIErrorHandler } from "@strapi/admin/strapi-admin";
9
+ import { skipToken } from "@reduxjs/toolkit/query";
10
+ import { useAuth, adminApi, useTable, Table, useQueryParams, useForm, useNotification, useAPIErrorHandler } from "@strapi/admin/strapi-admin";
10
11
  import { unstable_useDocument, unstable_useDocumentActions, buildValidParams } from "@strapi/content-manager/strapi-admin";
11
12
  import { useParams, Link, useNavigate, matchPath } from "react-router-dom";
12
13
  import * as qs from "qs";
@@ -77,11 +78,11 @@ const CheckboxConfirmation = ({
77
78
  }) }) })
78
79
  ] }) }),
79
80
  /* @__PURE__ */ jsxs(Dialog.Footer, { children: [
80
- /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button$1, { variant: "tertiary", children: formatMessage({
81
+ /* @__PURE__ */ jsx(Dialog.Cancel, { children: /* @__PURE__ */ jsx(Button, { variant: "tertiary", children: formatMessage({
81
82
  id: "components.popUpWarning.button.cancel",
82
83
  defaultMessage: "No, cancel"
83
84
  }) }) }),
84
- /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(Button$1, { variant: "danger-light", onClick: handleConfirm, children: formatMessage({
85
+ /* @__PURE__ */ jsx(Dialog.Action, { children: /* @__PURE__ */ jsx(Button, { variant: "danger-light", onClick: handleConfirm, children: formatMessage({
85
86
  id: getTranslation("CheckboxConfirmation.Modal.button-confirm"),
86
87
  defaultMessage: "Yes, disable"
87
88
  }) }) })
@@ -136,7 +137,7 @@ const useI18n = () => {
136
137
  model: params.slug
137
138
  },
138
139
  {
139
- skip: !params.slug || !params.collectionType
140
+ skip: true
140
141
  }
141
142
  );
142
143
  if (doesPluginOptionsHaveI18nLocalized(schema?.pluginOptions)) {
@@ -216,10 +217,93 @@ const relationsApi = i18nApi.injectEndpoints({
216
217
  })
217
218
  });
218
219
  const { useGetManyDraftRelationCountQuery } = relationsApi;
220
+ const cleanData = (data, schema, components) => {
221
+ const cleanedData = removeFields(data, [
222
+ "createdAt",
223
+ "createdBy",
224
+ "updatedAt",
225
+ "updatedBy",
226
+ "id",
227
+ "documentId",
228
+ "publishedAt",
229
+ "strapi_stage",
230
+ "strapi_assignee",
231
+ "locale"
232
+ ]);
233
+ const cleanedDataWithoutPasswordAndRelation = recursiveRemoveFieldTypes(
234
+ cleanedData,
235
+ schema,
236
+ components,
237
+ ["relation", "password"]
238
+ );
239
+ return cleanedDataWithoutPasswordAndRelation;
240
+ };
241
+ const removeFields = (data, fields) => {
242
+ return Object.keys(data).reduce((acc, current) => {
243
+ if (fields.includes(current)) {
244
+ return acc;
245
+ }
246
+ acc[current] = data[current];
247
+ return acc;
248
+ }, {});
249
+ };
250
+ const recursiveRemoveFieldTypes = (data, schema, components, fields) => {
251
+ return Object.keys(data).reduce((acc, current) => {
252
+ const attribute = schema.attributes[current] ?? { type: void 0 };
253
+ if (fields.includes(attribute.type)) {
254
+ return acc;
255
+ }
256
+ if (attribute.type === "dynamiczone") {
257
+ acc[current] = data[current].map((componentValue, index2) => {
258
+ const { id: _, ...rest } = recursiveRemoveFieldTypes(
259
+ componentValue,
260
+ components[componentValue.__component],
261
+ components,
262
+ fields
263
+ );
264
+ return {
265
+ ...rest,
266
+ __temp_key__: index2 + 1
267
+ };
268
+ });
269
+ } else if (attribute.type === "component") {
270
+ const { repeatable, component } = attribute;
271
+ if (repeatable) {
272
+ acc[current] = (data[current] ?? []).map((compoData, index2) => {
273
+ const { id: _, ...rest } = recursiveRemoveFieldTypes(
274
+ compoData,
275
+ components[component],
276
+ components,
277
+ fields
278
+ );
279
+ return {
280
+ ...rest,
281
+ __temp_key__: index2 + 1
282
+ };
283
+ });
284
+ } else {
285
+ const { id: _, ...rest } = recursiveRemoveFieldTypes(
286
+ data[current] ?? {},
287
+ components[component],
288
+ components,
289
+ fields
290
+ );
291
+ acc[current] = rest;
292
+ }
293
+ } else {
294
+ acc[current] = data[current];
295
+ }
296
+ return acc;
297
+ }, {});
298
+ };
219
299
  const isErrorMessageDescriptor = (object) => {
220
300
  return typeof object === "object" && object !== null && "id" in object && "defaultMessage" in object;
221
301
  };
222
- const EntryValidationText = ({ status = "draft", validationErrors }) => {
302
+ const EntryValidationText = ({
303
+ status = "draft",
304
+ validationErrors,
305
+ action
306
+ }) => {
223
307
  const { formatMessage } = useIntl();
224
308
  const getErrorStr = (key, value) => {
225
309
  if (typeof value === "string") {
@@ -253,30 +337,63 @@ const EntryValidationText = ({ status = "draft", validationErrors }) => {
253
337
  ) })
254
338
  ] });
255
339
  }
256
- if (status === "published") {
257
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
258
- /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
259
- /* @__PURE__ */ jsx(Typography, { textColor: "success600", fontWeight: "bold", children: formatMessage({
260
- id: "content-manager.bulk-publish.already-published",
261
- defaultMessage: "Already Published"
262
- }) })
263
- ] });
264
- }
265
- if (status === "modified") {
266
- return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
267
- /* @__PURE__ */ jsx(ArrowsCounterClockwise, { fill: "alternative600" }),
268
- /* @__PURE__ */ jsx(Typography, { children: formatMessage({
269
- id: "app.utils.ready-to-publish-changes",
270
- defaultMessage: "Ready to publish changes"
271
- }) })
272
- ] });
273
- }
340
+ const getStatusMessage = () => {
341
+ if (action === "bulk-publish") {
342
+ if (status === "published") {
343
+ return {
344
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
345
+ text: formatMessage({
346
+ id: "content-manager.bulk-publish.already-published",
347
+ defaultMessage: "Already Published"
348
+ }),
349
+ textColor: "success600",
350
+ fontWeight: "bold"
351
+ };
352
+ } else if (status === "modified") {
353
+ return {
354
+ icon: /* @__PURE__ */ jsx(ArrowsCounterClockwise, { fill: "alternative600" }),
355
+ text: formatMessage({
356
+ id: "app.utils.ready-to-publish-changes",
357
+ defaultMessage: "Ready to publish changes"
358
+ })
359
+ };
360
+ } else {
361
+ return {
362
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
363
+ text: formatMessage({
364
+ id: "app.utils.ready-to-publish",
365
+ defaultMessage: "Ready to publish"
366
+ })
367
+ };
368
+ }
369
+ } else {
370
+ if (status === "draft") {
371
+ return {
372
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
373
+ text: formatMessage({
374
+ id: "content-manager.bulk-unpublish.already-unpublished",
375
+ defaultMessage: "Already Unpublished"
376
+ }),
377
+ textColor: "success600",
378
+ fontWeight: "bold"
379
+ };
380
+ } else {
381
+ return {
382
+ icon: /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
383
+ text: formatMessage({
384
+ id: "app.utils.ready-to-unpublish-changes",
385
+ defaultMessage: "Ready to unpublish"
386
+ }),
387
+ textColor: "success600",
388
+ fontWeight: "bold"
389
+ };
390
+ }
391
+ }
392
+ };
393
+ const { icon, text, textColor = "success600", fontWeight = "normal" } = getStatusMessage();
274
394
  return /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
275
- /* @__PURE__ */ jsx(CheckCircle, { fill: "success600" }),
276
- /* @__PURE__ */ jsx(Typography, { children: formatMessage({
277
- id: "app.utils.ready-to-publish",
278
- defaultMessage: "Ready to publish"
279
- }) })
395
+ icon,
396
+ /* @__PURE__ */ jsx(Typography, { textColor, fontWeight, children: text })
280
397
  ] });
281
398
  };
282
399
  const BoldChunk = (chunks) => /* @__PURE__ */ jsx(Typography, { fontWeight: "bold", children: chunks });
@@ -284,7 +401,8 @@ const BulkLocaleActionModal = ({
284
401
  headers,
285
402
  rows,
286
403
  localesMetadata,
287
- validationErrors = {}
404
+ validationErrors = {},
405
+ action
288
406
  }) => {
289
407
  const { formatMessage } = useIntl();
290
408
  const selectedRows = useTable(
@@ -297,27 +415,29 @@ const BulkLocaleActionModal = ({
297
415
  return acc;
298
416
  }, {});
299
417
  const localesWithErrors = Object.keys(validationErrors);
300
- const alreadyPublishedCount = selectedRows.filter(
418
+ const publishedCount = selectedRows.filter(
301
419
  ({ locale }) => currentStatusByLocale[locale] === "published"
302
420
  ).length;
303
- const readyToPublishCount = selectedRows.filter(
421
+ const draftCount = selectedRows.filter(
304
422
  ({ locale }) => (currentStatusByLocale[locale] === "draft" || currentStatusByLocale[locale] === "modified") && !localesWithErrors.includes(locale)
305
423
  ).length;
306
424
  const withErrorsCount = localesWithErrors.length;
425
+ const messageId = action === "bulk-publish" ? "content-manager.containers.list.selectedEntriesModal.selectedCount.publish" : "content-manager.containers.list.selectedEntriesModal.selectedCount.unpublish";
426
+ 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.";
307
427
  return formatMessage(
308
428
  {
309
- id: "content-manager.containers.list.selectedEntriesModal.selectedCount",
310
- 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."
429
+ id: messageId,
430
+ defaultMessage
311
431
  },
312
432
  {
313
433
  withErrorsCount,
314
- readyToPublishCount,
315
- alreadyPublishedCount,
434
+ draftCount,
435
+ publishedCount,
316
436
  b: BoldChunk
317
437
  }
318
438
  );
319
439
  };
320
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
440
+ return /* @__PURE__ */ jsxs(Modal.Body, { children: [
321
441
  /* @__PURE__ */ jsx(Typography, { children: getFormattedCountMessage() }),
322
442
  /* @__PURE__ */ jsx(Box, { marginTop: 5, children: /* @__PURE__ */ jsxs(Table.Content, { children: [
323
443
  /* @__PURE__ */ jsxs(Table.Head, { children: [
@@ -344,7 +464,7 @@ const BulkLocaleActionModal = ({
344
464
  children: /* @__PURE__ */ jsx(Typography, { tag: "span", variant: "pi", fontWeight: "bold", children: capitalize(status) })
345
465
  }
346
466
  ) }) }),
347
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(EntryValidationText, { validationErrors: error, status }) }),
467
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(EntryValidationText, { validationErrors: error, status, action }) }),
348
468
  /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
349
469
  IconButton,
350
470
  {
@@ -361,7 +481,7 @@ const BulkLocaleActionModal = ({
361
481
  name: locale
362
482
  }
363
483
  ),
364
- borderWidth: 0,
484
+ variant: "ghost",
365
485
  children: /* @__PURE__ */ jsx(Pencil, {})
366
486
  }
367
487
  ) })
@@ -381,7 +501,13 @@ const LocalePickerAction = ({
381
501
  const [{ query }, setQuery] = useQueryParams();
382
502
  const { hasI18n, canCreate, canRead } = useI18n();
383
503
  const { data: locales = [] } = useGetLocalesQuery();
384
- const { schema } = unstable_useDocument({ model, collectionType, documentId });
504
+ const currentDesiredLocale = query.plugins?.i18n?.locale;
505
+ const { schema } = unstable_useDocument({
506
+ model,
507
+ collectionType,
508
+ documentId,
509
+ params: { locale: currentDesiredLocale }
510
+ });
385
511
  const handleSelect = React.useCallback(
386
512
  (value) => {
387
513
  setQuery({
@@ -399,21 +525,20 @@ const LocalePickerAction = ({
399
525
  if (!Array.isArray(locales) || !hasI18n) {
400
526
  return;
401
527
  }
402
- const currentDesiredLocale = query.plugins?.i18n?.locale;
403
528
  const doesLocaleExist = locales.find((loc) => loc.code === currentDesiredLocale);
404
529
  const defaultLocale = locales.find((locale) => locale.isDefault);
405
530
  if (!doesLocaleExist && defaultLocale?.code) {
406
531
  handleSelect(defaultLocale.code);
407
532
  }
408
- }, [handleSelect, hasI18n, locales, query.plugins?.i18n?.locale]);
409
- if (!hasI18n || !Array.isArray(locales) || locales.length === 0) {
410
- return null;
411
- }
412
- const currentLocale = query.plugins?.i18n?.locale || locales.find((loc) => loc.isDefault)?.code;
533
+ }, [handleSelect, hasI18n, locales, currentDesiredLocale]);
534
+ const currentLocale = Array.isArray(locales) ? locales.find((locale) => locale.code === currentDesiredLocale)?.code : void 0;
413
535
  const allCurrentLocales = [
414
536
  { status: getDocumentStatus(document, meta), locale: currentLocale },
415
537
  ...meta?.availableLocales ?? []
416
538
  ];
539
+ if (!hasI18n || !Array.isArray(locales) || locales.length === 0) {
540
+ return null;
541
+ }
417
542
  return {
418
543
  label: formatMessage({
419
544
  id: getTranslation("Settings.locales.modal.locales.label"),
@@ -425,7 +550,7 @@ const LocalePickerAction = ({
425
550
  );
426
551
  const status = currentLocaleDoc?.status ?? "draft";
427
552
  const permissionsToCheck = currentLocaleDoc ? canCreate : canRead;
428
- const statusVariant = status === "draft" ? "primary" : status === "published" ? "success" : "alternative";
553
+ const statusVariant = status === "draft" ? "secondary" : status === "published" ? "success" : "alternative";
429
554
  return {
430
555
  disabled: !permissionsToCheck.includes(locale.code),
431
556
  value: locale.code,
@@ -461,6 +586,95 @@ const getDocumentStatus = (document, meta) => {
461
586
  }
462
587
  return docStatus;
463
588
  };
589
+ const FillFromAnotherLocaleAction = ({
590
+ documentId,
591
+ meta,
592
+ model,
593
+ collectionType
594
+ }) => {
595
+ const { formatMessage } = useIntl();
596
+ const [{ query }] = useQueryParams();
597
+ const currentDesiredLocale = query.plugins?.i18n?.locale;
598
+ const [localeSelected, setLocaleSelected] = React.useState(null);
599
+ const setValues = useForm("FillFromAnotherLocale", (state) => state.setValues);
600
+ const { getDocument } = unstable_useDocumentActions();
601
+ const { schema, components } = unstable_useDocument({
602
+ model,
603
+ documentId,
604
+ collectionType,
605
+ params: { locale: currentDesiredLocale }
606
+ });
607
+ const { data: locales = [] } = useGetLocalesQuery();
608
+ const availableLocales = Array.isArray(locales) ? locales.filter((locale) => meta?.availableLocales.some((l) => l.locale === locale.code)) : [];
609
+ const fillFromLocale = (onClose) => async () => {
610
+ const response = await getDocument({
611
+ collectionType,
612
+ model,
613
+ documentId,
614
+ params: { locale: localeSelected }
615
+ });
616
+ if (!response || !schema) {
617
+ return;
618
+ }
619
+ const { data } = response;
620
+ const cleanedData = cleanData(data, schema, components);
621
+ setValues(cleanedData);
622
+ onClose();
623
+ };
624
+ return {
625
+ type: "icon",
626
+ icon: /* @__PURE__ */ jsx(Download, {}),
627
+ disabled: availableLocales.length === 0,
628
+ label: formatMessage({
629
+ id: getTranslation("CMEditViewCopyLocale.copy-text"),
630
+ defaultMessage: "Fill in from another locale"
631
+ }),
632
+ dialog: {
633
+ type: "dialog",
634
+ title: formatMessage({
635
+ id: getTranslation("CMEditViewCopyLocale.dialog.title"),
636
+ defaultMessage: "Confirmation"
637
+ }),
638
+ content: ({ onClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [
639
+ /* @__PURE__ */ jsx(Dialog.Body, { children: /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, children: [
640
+ /* @__PURE__ */ jsx(WarningCircle, { width: "24px", height: "24px", fill: "danger600" }),
641
+ /* @__PURE__ */ jsx(Typography, { textAlign: "center", children: formatMessage({
642
+ id: getTranslation("CMEditViewCopyLocale.dialog.body"),
643
+ defaultMessage: "Your current content will be erased and filled by the content of the selected locale:"
644
+ }) }),
645
+ /* @__PURE__ */ jsxs(Field.Root, { width: "100%", children: [
646
+ /* @__PURE__ */ jsx(Field.Label, { children: formatMessage({
647
+ id: getTranslation("CMEditViewCopyLocale.dialog.field.label"),
648
+ defaultMessage: "Locale"
649
+ }) }),
650
+ /* @__PURE__ */ jsx(
651
+ SingleSelect,
652
+ {
653
+ value: localeSelected,
654
+ placeholder: formatMessage({
655
+ id: getTranslation("CMEditViewCopyLocale.dialog.field.placeholder"),
656
+ defaultMessage: "Select one locale..."
657
+ }),
658
+ onChange: (value) => setLocaleSelected(value),
659
+ children: availableLocales.map((locale) => /* @__PURE__ */ jsx(SingleSelectOption, { value: locale.code, children: locale.name }, locale.code))
660
+ }
661
+ )
662
+ ] })
663
+ ] }) }),
664
+ /* @__PURE__ */ jsx(Dialog.Footer, { children: /* @__PURE__ */ jsxs(Flex, { gap: 2, width: "100%", children: [
665
+ /* @__PURE__ */ jsx(Button, { flex: "auto", variant: "tertiary", onClick: onClose, children: formatMessage({
666
+ id: getTranslation("CMEditViewCopyLocale.cancel-text"),
667
+ defaultMessage: "No, cancel"
668
+ }) }),
669
+ /* @__PURE__ */ jsx(Button, { flex: "auto", variant: "success", onClick: fillFromLocale(onClose), children: formatMessage({
670
+ id: getTranslation("CMEditViewCopyLocale.submit-text"),
671
+ defaultMessage: "Yes, fill in"
672
+ }) })
673
+ ] }) })
674
+ ] })
675
+ }
676
+ };
677
+ };
464
678
  const DeleteLocaleAction = ({
465
679
  document,
466
680
  documentId,
@@ -524,37 +738,43 @@ const DeleteLocaleAction = ({
524
738
  }
525
739
  };
526
740
  };
527
- const BulkLocalePublishAction = ({
741
+ const BulkLocaleAction = ({
528
742
  document: baseDocument,
529
743
  documentId,
530
744
  model,
531
- collectionType
745
+ collectionType,
746
+ action
532
747
  }) => {
533
748
  const baseLocale = baseDocument?.locale ?? null;
534
749
  const [{ query }] = useQueryParams();
535
750
  const params = React.useMemo(() => buildValidParams(query), [query]);
536
- const isPublishedTab = query.status === "published";
751
+ const isOnPublishedTab = query.status === "published";
537
752
  const { formatMessage } = useIntl();
538
753
  const { hasI18n, canPublish } = useI18n();
539
754
  const { toggleNotification } = useNotification();
540
755
  const { _unstableFormatAPIError: formatAPIError } = useAPIErrorHandler();
541
756
  const [selectedRows, setSelectedRows] = React.useState([]);
542
- const [isConfirmationOpen, setIsConfirmationOpen] = React.useState(false);
543
- const { publishMany: publishManyAction } = unstable_useDocumentActions();
757
+ const [isDraftRelationConfirmationOpen, setIsDraftRelationConfirmationOpen] = React.useState(false);
758
+ const { publishMany: publishManyAction, unpublishMany: unpublishManyAction } = unstable_useDocumentActions();
544
759
  const {
545
760
  document,
546
761
  meta: documentMeta,
547
762
  schema,
548
763
  validate
549
- } = unstable_useDocument({
550
- model,
551
- collectionType,
552
- documentId,
553
- params: {
554
- locale: baseLocale
764
+ } = unstable_useDocument(
765
+ {
766
+ model,
767
+ collectionType,
768
+ documentId,
769
+ params: {
770
+ locale: baseLocale
771
+ }
772
+ },
773
+ {
774
+ skip: !hasI18n || !baseLocale
555
775
  }
556
- });
557
- const { data: localesMetadata = [] } = useGetLocalesQuery();
776
+ );
777
+ const { data: localesMetadata = [] } = useGetLocalesQuery(hasI18n ? void 0 : skipToken);
558
778
  const headers = [
559
779
  {
560
780
  label: formatMessage({
@@ -603,12 +823,19 @@ const BulkLocalePublishAction = ({
603
823
  }, {});
604
824
  return [rowsFromMeta, errors];
605
825
  }, [document, documentMeta?.availableLocales, validate]);
606
- const localesToPublish = selectedRows.reduce((acc, selectedRow) => {
607
- if (selectedRow.status !== "published" && !Object.keys(validationErrors).includes(selectedRow.locale)) {
826
+ const isBulkPublish = action === "bulk-publish";
827
+ const localesForAction = selectedRows.reduce((acc, selectedRow) => {
828
+ const isValidLocale = (
829
+ // Validation errors are irrelevant if we are trying to unpublish
830
+ !isBulkPublish || !Object.keys(validationErrors).includes(selectedRow.locale)
831
+ );
832
+ const shouldAddLocale = isBulkPublish ? selectedRow.status !== "published" && isValidLocale : selectedRow.status !== "draft" && isValidLocale;
833
+ if (shouldAddLocale) {
608
834
  acc.push(selectedRow.locale);
609
835
  }
610
836
  return acc;
611
837
  }, []);
838
+ const enableDraftRelationsCount = false;
612
839
  const {
613
840
  data: draftRelationsCount = 0,
614
841
  isLoading: isDraftRelationsLoading,
@@ -617,10 +844,10 @@ const BulkLocalePublishAction = ({
617
844
  {
618
845
  model,
619
846
  documentIds: [documentId],
620
- locale: localesToPublish
847
+ locale: localesForAction
621
848
  },
622
849
  {
623
- skip: !documentId || localesToPublish.length === 0
850
+ skip: !enableDraftRelationsCount
624
851
  }
625
852
  );
626
853
  React.useEffect(() => {
@@ -646,23 +873,32 @@ const BulkLocalePublishAction = ({
646
873
  documentIds: [documentId],
647
874
  params: {
648
875
  ...params,
649
- locale: localesToPublish
876
+ locale: localesForAction
877
+ }
878
+ });
879
+ setSelectedRows([]);
880
+ };
881
+ const unpublish = async () => {
882
+ await unpublishManyAction({
883
+ model,
884
+ documentIds: [documentId],
885
+ params: {
886
+ ...params,
887
+ locale: localesForAction
650
888
  }
651
889
  });
652
890
  setSelectedRows([]);
653
891
  };
654
892
  const handleAction = async () => {
655
893
  if (draftRelationsCount > 0) {
656
- setIsConfirmationOpen(true);
657
- } else {
894
+ setIsDraftRelationConfirmationOpen(true);
895
+ } else if (isBulkPublish) {
658
896
  await publish();
897
+ } else {
898
+ await unpublish();
659
899
  }
660
900
  };
661
- const isUnpublish = document?.status === "published";
662
- if (isUnpublish) {
663
- console.warn(["I18N"], "Bulk locale unpublish modal not implemented");
664
- }
665
- if (isConfirmationOpen) {
901
+ if (isDraftRelationConfirmationOpen) {
666
902
  return {
667
903
  label: formatMessage({
668
904
  id: "app.components.ConfirmDialog.title",
@@ -671,11 +907,11 @@ const BulkLocalePublishAction = ({
671
907
  variant: "danger",
672
908
  dialog: {
673
909
  onCancel: () => {
674
- setIsConfirmationOpen(false);
910
+ setIsDraftRelationConfirmationOpen(false);
675
911
  },
676
912
  onConfirm: async () => {
677
913
  await publish();
678
- setIsConfirmationOpen(false);
914
+ setIsDraftRelationConfirmationOpen(false);
679
915
  },
680
916
  type: "dialog",
681
917
  title: formatMessage({
@@ -685,27 +921,32 @@ const BulkLocalePublishAction = ({
685
921
  content: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "center", gap: 2, children: [
686
922
  /* @__PURE__ */ jsx(WarningCircle, { width: "2.4rem", height: "2.4rem", fill: "danger600" }),
687
923
  /* @__PURE__ */ jsx(Typography, { textAlign: "center", children: formatMessage({
688
- id: "content-manager.actions.discard.dialog.body",
689
- defaultMessage: "Are you sure you want to discard the changes? This action is irreversible."
924
+ id: getTranslation("CMEditViewBulkLocale.draft-relation-warning"),
925
+ defaultMessage: "Some locales are related to draft entries. Publishing them could leave broken links in your app."
926
+ }) }),
927
+ /* @__PURE__ */ jsx(Typography, { textAlign: "center", children: formatMessage({
928
+ id: getTranslation("CMEditViewBulkLocale.continue-confirmation"),
929
+ defaultMessage: "Are you sure you want to continue?"
690
930
  }) })
691
931
  ] })
692
932
  }
693
933
  };
694
934
  }
935
+ const hasPermission = selectedRows.map(({ locale }) => locale).every((locale) => canPublish.includes(locale));
695
936
  return {
696
937
  label: formatMessage({
697
- id: getTranslation("CMEditViewBulkLocale.publish-title"),
698
- defaultMessage: "Publish Multiple Locales"
938
+ id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? "publish" : "unpublish"}-title`),
939
+ defaultMessage: `${isBulkPublish ? "Publish" : "Unpublish"} Multiple Locales`
699
940
  }),
700
- icon: /* @__PURE__ */ jsx(ListPlus, {}),
701
- disabled: isPublishedTab || !canPublish,
941
+ variant: isBulkPublish ? "secondary" : "danger",
942
+ icon: isBulkPublish ? /* @__PURE__ */ jsx(ListPlus, {}) : /* @__PURE__ */ jsx(Cross, {}),
943
+ disabled: isOnPublishedTab || canPublish.length === 0,
702
944
  position: ["panel"],
703
- variant: "secondary",
704
945
  dialog: {
705
946
  type: "modal",
706
947
  title: formatMessage({
707
- id: getTranslation("CMEditViewBulkLocale.publish-title"),
708
- defaultMessage: "Publish Multiple Locales"
948
+ id: getTranslation(`CMEditViewBulkLocale.${isBulkPublish ? "publish" : "unpublish"}-title`),
949
+ defaultMessage: `${isBulkPublish ? "Publish" : "Unpublish"} Multiple Locales`
709
950
  }),
710
951
  content: () => {
711
952
  return /* @__PURE__ */ jsx(
@@ -724,28 +965,35 @@ const BulkLocalePublishAction = ({
724
965
  validationErrors,
725
966
  headers,
726
967
  rows,
727
- localesMetadata
968
+ localesMetadata,
969
+ action: action ?? "bulk-publish"
728
970
  }
729
971
  )
730
972
  }
731
973
  );
732
974
  },
733
- footer: () => /* @__PURE__ */ jsx(Flex, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx(
734
- Button$1,
975
+ footer: () => /* @__PURE__ */ jsx(Modal.Footer, { justifyContent: "flex-end", children: /* @__PURE__ */ jsx(
976
+ Button,
735
977
  {
736
978
  loading: isDraftRelationsLoading,
737
- disabled: localesToPublish.length === 0,
979
+ disabled: !hasPermission || localesForAction.length === 0,
738
980
  variant: "default",
739
981
  onClick: handleAction,
740
982
  children: formatMessage({
741
- id: "app.utils.publish",
742
- defaultMessage: "Publish"
983
+ id: isBulkPublish ? "app.utils.publish" : "app.utils.unpublish",
984
+ defaultMessage: isBulkPublish ? "Publish" : "Unpublish"
743
985
  })
744
986
  }
745
987
  ) })
746
988
  }
747
989
  };
748
990
  };
991
+ const BulkLocalePublishAction = (props) => {
992
+ return BulkLocaleAction({ action: "bulk-publish", ...props });
993
+ };
994
+ const BulkLocaleUnpublishAction = (props) => {
995
+ return BulkLocaleAction({ action: "bulk-unpublish", ...props });
996
+ };
749
997
  const StyledTrash = styled(Trash)`
750
998
  path {
751
999
  fill: currentColor;
@@ -802,13 +1050,6 @@ const UnpublishModalAdditionalInfo = () => {
802
1050
  }
803
1051
  ) });
804
1052
  };
805
- const Initializer = ({ setPlugin }) => {
806
- const setPluginRef = React.useRef(setPlugin);
807
- React.useEffect(() => {
808
- setPluginRef.current(pluginId);
809
- }, []);
810
- return null;
811
- };
812
1053
  const LocalePicker = () => {
813
1054
  const { formatMessage } = useIntl();
814
1055
  const [{ query }, setQuery] = useQueryParams();
@@ -972,54 +1213,13 @@ const LocaleListCell = ({
972
1213
  return locale.name;
973
1214
  }).toSorted((a, b) => formatter.compare(a, b));
974
1215
  return /* @__PURE__ */ jsxs(Popover.Root, { children: [
975
- /* @__PURE__ */ jsx(Popover.Trigger, { children: /* @__PURE__ */ jsx(Button, { type: "button", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(
976
- ActionWrapper,
977
- {
978
- minWidth: "100%",
979
- alignItems: "center",
980
- justifyContent: "center",
981
- height: "3.2rem",
982
- width: "3.2rem",
983
- children: [
984
- /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", ellipsis: true, children: localesForDocument.join(", ") }),
985
- /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(CaretDown, {}) })
986
- ]
987
- }
988
- ) }) }),
1216
+ /* @__PURE__ */ jsx(Popover.Trigger, { children: /* @__PURE__ */ jsx(Button, { variant: "ghost", type: "button", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Flex, { minWidth: "100%", alignItems: "center", justifyContent: "center", fontWeight: "regular", children: [
1217
+ /* @__PURE__ */ jsx(Typography, { textColor: "neutral800", ellipsis: true, marginRight: 2, children: localesForDocument.join(", ") }),
1218
+ /* @__PURE__ */ jsx(Flex, { children: /* @__PURE__ */ jsx(CaretDown, { width: "1.2rem", height: "1.2rem" }) })
1219
+ ] }) }) }),
989
1220
  /* @__PURE__ */ jsx(Popover.Content, { sideOffset: 16, children: /* @__PURE__ */ jsx("ul", { children: localesForDocument.map((name) => /* @__PURE__ */ jsx(Box, { padding: 3, tag: "li", children: /* @__PURE__ */ jsx(Typography, { children: name }) }, name)) }) })
990
1221
  ] });
991
1222
  };
992
- const Button = styled.button`
993
- width: 100%;
994
-
995
- svg {
996
- > g,
997
- path {
998
- fill: ${({ theme }) => theme.colors.neutral500};
999
- }
1000
- }
1001
- &:hover {
1002
- svg {
1003
- > g,
1004
- path {
1005
- fill: ${({ theme }) => theme.colors.neutral600};
1006
- }
1007
- }
1008
- }
1009
- &:active {
1010
- svg {
1011
- > g,
1012
- path {
1013
- fill: ${({ theme }) => theme.colors.neutral400};
1014
- }
1015
- }
1016
- }
1017
- `;
1018
- const ActionWrapper = styled(Flex)`
1019
- svg {
1020
- height: 0.4rem;
1021
- }
1022
- `;
1023
1223
  const addColumnToTableHook = ({ displayedHeaders, layout }) => {
1024
1224
  const { options } = layout;
1025
1225
  const isFieldLocalized = doesPluginOptionsHaveI18nLocalized(options) ? options.i18n.localized : false;
@@ -1048,18 +1248,11 @@ const addColumnToTableHook = ({ displayedHeaders, layout }) => {
1048
1248
  const addLocaleToReleasesHook = ({ displayedHeaders = [] }) => {
1049
1249
  return {
1050
1250
  displayedHeaders: [
1051
- // TODO: Fix when migrating to v5
1052
- // ...displayedHeaders,
1251
+ ...displayedHeaders,
1053
1252
  {
1054
- key: "__locale__",
1055
- fieldSchema: { type: "string" },
1056
- metadatas: {
1057
- label: {
1058
- id: "content-releases.page.ReleaseDetails.table.header.label.locale",
1059
- defaultMessage: "locale"
1060
- },
1061
- searchable: false,
1062
- sortable: false
1253
+ label: {
1254
+ id: "content-releases.page.ReleaseDetails.table.header.label.locale",
1255
+ defaultMessage: "locale"
1063
1256
  },
1064
1257
  name: "locale"
1065
1258
  }
@@ -1207,8 +1400,6 @@ const index = {
1207
1400
  app.addRBACMiddleware([localeMiddleware]);
1208
1401
  app.registerPlugin({
1209
1402
  id: pluginId,
1210
- initializer: Initializer,
1211
- isReady: false,
1212
1403
  name: pluginId
1213
1404
  });
1214
1405
  },
@@ -1226,11 +1417,11 @@ const index = {
1226
1417
  },
1227
1418
  id: "internationalization",
1228
1419
  to: "internationalization",
1229
- Component: () => import("./SettingsPage-Dsi2qGtq.mjs").then((mod) => ({ default: mod.ProtectedSettingsPage })),
1420
+ Component: () => import("./SettingsPage-BjxjwEOb.mjs").then((mod) => ({ default: mod.ProtectedSettingsPage })),
1230
1421
  permissions: PERMISSIONS.accessMain
1231
1422
  });
1232
1423
  const contentManager = app.getPlugin("content-manager");
1233
- contentManager.apis.addDocumentHeaderAction([LocalePickerAction]);
1424
+ contentManager.apis.addDocumentHeaderAction([LocalePickerAction, FillFromAnotherLocaleAction]);
1234
1425
  contentManager.apis.addDocumentAction((actions) => {
1235
1426
  const indexOfDeleteAction = actions.findIndex((action) => action.type === "delete");
1236
1427
  actions.splice(indexOfDeleteAction, 0, DeleteLocaleAction);
@@ -1238,6 +1429,7 @@ const index = {
1238
1429
  });
1239
1430
  contentManager.apis.addDocumentAction((actions) => {
1240
1431
  actions.splice(2, 0, BulkLocalePublishAction);
1432
+ actions.splice(5, 0, BulkLocaleUnpublishAction);
1241
1433
  return actions;
1242
1434
  });
1243
1435
  contentManager.injectComponent("listView", "actions", {
@@ -1343,7 +1535,7 @@ const index = {
1343
1535
  async registerTrads({ locales }) {
1344
1536
  const importedTrads = await Promise.all(
1345
1537
  locales.map((locale) => {
1346
- 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-18tWw4P6.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 }) => {
1538
+ 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-2xztdZE1.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 }) => {
1347
1539
  return {
1348
1540
  data: prefixPluginTranslations(data, pluginId),
1349
1541
  locale
@@ -1369,4 +1561,4 @@ export {
1369
1561
  index as i,
1370
1562
  useCreateLocaleMutation as u
1371
1563
  };
1372
- //# sourceMappingURL=index-DhtjJYrx.mjs.map
1564
+ //# sourceMappingURL=index-D-qx3tz4.mjs.map