@strapi/admin 4.5.1 → 4.5.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.
Files changed (139) hide show
  1. package/admin/src/components/LeftMenu/index.js +22 -4
  2. package/admin/src/content-manager/components/DynamicTable/CellContent/RelationMultiple/index.js +4 -3
  3. package/admin/src/content-manager/components/Inputs/index.js +5 -19
  4. package/admin/src/content-manager/hooks/useLazyComponents/index.js +44 -0
  5. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/components/ErrorFallback.js +13 -0
  6. package/admin/src/content-manager/pages/CollectionTypeRecursivePath/index.js +2 -1
  7. package/admin/src/content-manager/pages/EditView/GridRow/index.js +62 -0
  8. package/admin/src/content-manager/pages/EditView/index.js +74 -154
  9. package/admin/src/content-manager/pages/EditView/selectors.js +14 -0
  10. package/admin/src/content-manager/pages/EditView/utils/createAttributesLayout.js +11 -6
  11. package/admin/src/content-manager/pages/EditView/utils/getCustomFieldUidsFromLayout.js +18 -0
  12. package/admin/src/content-manager/pages/EditView/utils/index.js +1 -0
  13. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +1 -1
  14. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +79 -76
  15. package/admin/src/core/apis/CustomFields.js +46 -1
  16. package/admin/src/pages/MarketplacePage/components/SortSelect/index.js +20 -0
  17. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +2 -2
  18. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +1 -1
  19. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +1 -1
  20. package/admin/src/translations/en.json +4 -0
  21. package/admin/src/translations/nl.json +210 -23
  22. package/admin/src/translations/pt.json +33 -32
  23. package/admin/src/translations/ru.json +789 -489
  24. package/admin/src/translations/zh.json +280 -95
  25. package/build/{7692.31e83caa.chunk.js → 1233.80b05d66.chunk.js} +144 -445
  26. package/build/1920.74a262e7.chunk.js +245 -0
  27. package/build/2438.61291207.chunk.js +2183 -0
  28. package/build/2517.9b4940f3.chunk.js +117 -0
  29. package/build/4306.f03c2b46.chunk.js +98 -0
  30. package/build/4318.7931eee7.chunk.js +30 -0
  31. package/build/4986.3820d11d.chunk.js +145 -0
  32. package/build/{8469.41c8d25f.chunk.js → 5015.f080b64e.chunk.js} +6 -1
  33. package/build/504.9aeff724.chunk.js +758 -0
  34. package/build/805.e991a370.chunk.js +138 -0
  35. package/build/8633.8da5488a.chunk.js +1 -0
  36. package/build/9707.a0cc4ad8.chunk.js +70 -0
  37. package/build/Admin-authenticatedApp.1e1d3bdd.chunk.js +80 -0
  38. package/build/Admin_InternalErrorPage.e0317a5e.chunk.js +1 -0
  39. package/build/Admin_homePage.54e33c2d.chunk.js +77 -0
  40. package/build/Admin_marketplace.8219fda6.chunk.js +26 -0
  41. package/build/Admin_pluginsPage.3c872de7.chunk.js +6 -0
  42. package/build/Admin_profilePage.e9fcce92.chunk.js +15 -0
  43. package/build/Admin_settingsPage.a1a5218b.chunk.js +178 -0
  44. package/build/admin-app.9cb0abc7.chunk.js +112 -0
  45. package/build/admin-edit-roles-page.23f15909.chunk.js +1 -0
  46. package/build/admin-edit-users.283b49ed.chunk.js +10 -0
  47. package/build/admin-users.a0748674.chunk.js +11 -0
  48. package/build/api-tokens-list-page.700e575f.chunk.js +16 -0
  49. package/build/content-manager.01e04e11.chunk.js +1200 -0
  50. package/build/content-type-builder-list-view.4412efc3.chunk.js +201 -0
  51. package/build/content-type-builder-translation-pt-json.96a31576.chunk.js +1 -0
  52. package/build/content-type-builder-translation-zh-json.3b0afd31.chunk.js +1 -0
  53. package/build/content-type-builder.848a9610.chunk.js +145 -0
  54. package/build/email-settings-page.d44a57cb.chunk.js +15 -0
  55. package/build/email-translation-pt-json.159505ab.chunk.js +1 -0
  56. package/build/email-translation-zh-json.62b1c6fe.chunk.js +1 -0
  57. package/build/en-json.7dd57947.chunk.js +1 -0
  58. package/build/i18n-settings-page.195d42fe.chunk.js +1 -0
  59. package/build/i18n-translation-zh-json.eeebb849.chunk.js +1 -0
  60. package/build/index.html +1 -1
  61. package/build/main.f31112a5.js +2034 -0
  62. package/build/nl-json.26f39180.chunk.js +1 -0
  63. package/build/pt-json.cd67ba86.chunk.js +1 -0
  64. package/build/ru-json.8830286f.chunk.js +1 -0
  65. package/build/runtime~main.f91e75cd.js +2 -0
  66. package/build/sso-settings-page.9f091262.chunk.js +1 -0
  67. package/build/upload-settings.450cab1a.chunk.js +18 -0
  68. package/build/upload-translation-pt-json.5c452b48.chunk.js +1 -0
  69. package/build/upload-translation-zh-json.ac5711de.chunk.js +1 -0
  70. package/build/upload.a73936d9.chunk.js +64 -0
  71. package/build/users-advanced-settings-page.dc23bc56.chunk.js +13 -0
  72. package/build/users-email-settings-page.6541d372.chunk.js +28 -0
  73. package/build/users-permissions-translation-zh-json.92f406f9.chunk.js +1 -0
  74. package/build/users-providers-settings-page.e11a2f64.chunk.js +33 -0
  75. package/build/users-roles-settings-page.445e5e16.chunk.js +30 -0
  76. package/build/webhook-edit-page.14ad1e6e.chunk.js +75 -0
  77. package/build/webhook-list-page.b87821f2.chunk.js +42 -0
  78. package/build/zh-json.2ecc6b99.chunk.js +1 -0
  79. package/jest.config.front.js +0 -1
  80. package/package.json +11 -10
  81. package/webpack.alias.js +3 -2
  82. package/webpack.config.js +13 -1
  83. package/build/1856.db9f5782.chunk.js +0 -174
  84. package/build/2077.fed8c9c3.chunk.js +0 -206
  85. package/build/2912.fccb2c43.chunk.js +0 -259
  86. package/build/4318.5e670740.chunk.js +0 -30
  87. package/build/4610.7614b003.chunk.js +0 -342
  88. package/build/4715.8e33d630.chunk.js +0 -387
  89. package/build/4800.a6935af6.chunk.js +0 -1
  90. package/build/4982.9e58ea3f.chunk.js +0 -325
  91. package/build/6925.bb6dd64d.chunk.js +0 -762
  92. package/build/7379.e972985f.chunk.js +0 -1
  93. package/build/7841.4804bd98.chunk.js +0 -259
  94. package/build/7866.6db2248d.chunk.js +0 -505
  95. package/build/8380.37126e0d.chunk.js +0 -299
  96. package/build/8549.5e5fb6b6.chunk.js +0 -159
  97. package/build/8738.5a02bffb.chunk.js +0 -463
  98. package/build/9066.5d980488.chunk.js +0 -101
  99. package/build/9420.7addc099.chunk.js +0 -505
  100. package/build/9649.b6afc945.chunk.js +0 -199
  101. package/build/Admin-authenticatedApp.c07d2a86.chunk.js +0 -80
  102. package/build/Admin_InternalErrorPage.12e24216.chunk.js +0 -1
  103. package/build/Admin_homePage.26d32e30.chunk.js +0 -72
  104. package/build/Admin_marketplace.444ff7b8.chunk.js +0 -22
  105. package/build/Admin_pluginsPage.4d59785a.chunk.js +0 -1
  106. package/build/Admin_profilePage.da32abbc.chunk.js +0 -15
  107. package/build/Admin_settingsPage.bf2234e1.chunk.js +0 -178
  108. package/build/admin-app.b157c10a.chunk.js +0 -112
  109. package/build/admin-edit-roles-page.69d9fcb2.chunk.js +0 -1
  110. package/build/admin-edit-users.c585212f.chunk.js +0 -10
  111. package/build/admin-users.d71f198a.chunk.js +0 -11
  112. package/build/api-tokens-list-page.bb36535f.chunk.js +0 -16
  113. package/build/content-manager.f38edbb6.chunk.js +0 -1202
  114. package/build/content-type-builder-list-view.5b3cd768.chunk.js +0 -194
  115. package/build/content-type-builder-translation-pt-json.766bd747.chunk.js +0 -1
  116. package/build/content-type-builder-translation-zh-json.2cc55621.chunk.js +0 -1
  117. package/build/content-type-builder.16af63a6.chunk.js +0 -145
  118. package/build/email-settings-page.91c925a5.chunk.js +0 -103
  119. package/build/email-translation-pt-json.959ea070.chunk.js +0 -1
  120. package/build/email-translation-zh-json.3455468b.chunk.js +0 -1
  121. package/build/en-json.4a269f6b.chunk.js +0 -1
  122. package/build/i18n-settings-page.4ef64441.chunk.js +0 -101
  123. package/build/main.ca8b0ee3.js +0 -9465
  124. package/build/nl-json.2b8cc3a0.chunk.js +0 -1
  125. package/build/pt-json.3161ca22.chunk.js +0 -1
  126. package/build/ru-json.d7cfc2ff.chunk.js +0 -1
  127. package/build/runtime~main.ede9da1e.js +0 -2
  128. package/build/sso-settings-page.9ceb0140.chunk.js +0 -1
  129. package/build/upload-settings.3f7ad973.chunk.js +0 -101
  130. package/build/upload-translation-zh-json.ee8fba96.chunk.js +0 -1
  131. package/build/upload.7084cea6.chunk.js +0 -7
  132. package/build/users-advanced-settings-page.6a838320.chunk.js +0 -101
  133. package/build/users-email-settings-page.73c41236.chunk.js +0 -1
  134. package/build/users-permissions-translation-zh-json.e03ae2a4.chunk.js +0 -1
  135. package/build/users-providers-settings-page.f8e78537.chunk.js +0 -1
  136. package/build/users-roles-settings-page.b33ec5e5.chunk.js +0 -30
  137. package/build/webhook-edit-page.dc9442ce.chunk.js +0 -23
  138. package/build/webhook-list-page.a110c462.chunk.js +0 -134
  139. package/build/zh-json.608aaf24.chunk.js +0 -1
@@ -1,3 +1,4 @@
1
1
  // eslint-disable-next-line import/prefer-default-export
2
2
  export { default as createAttributesLayout } from './createAttributesLayout';
3
3
  export { default as getFieldsActionMatchingPermissions } from './getFieldsActionMatchingPermissions';
4
+ export { default as getCustomFieldUidsFromLayout } from './getCustomFieldUidsFromLayout';
@@ -30,7 +30,7 @@ const EditViewLayoutManager = ({ layout, ...rest }) => {
30
30
  return <LoadingIndicatorPage />;
31
31
  }
32
32
 
33
- return <Permissions {...rest} layout={currentLayout} userPermissions={permissions} />;
33
+ return <Permissions {...rest} userPermissions={permissions} />;
34
34
  };
35
35
 
36
36
  EditViewLayoutManager.propTypes = {
@@ -1,92 +1,95 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import styled from 'styled-components';
4
3
  import { useIntl } from 'react-intl';
5
- import { Flex } from '@strapi/design-system/Flex';
4
+ import { Box } from '@strapi/design-system/Box';
6
5
  import { Grid, GridItem } from '@strapi/design-system/Grid';
7
6
  import { Select, Option } from '@strapi/design-system/Select';
8
7
  import { ToggleInput } from '@strapi/design-system/ToggleInput';
9
- import { Box } from '@strapi/design-system/Box';
8
+ import { Stack } from '@strapi/design-system/Stack';
10
9
  import { Typography } from '@strapi/design-system/Typography';
11
10
  import { getTrad } from '../../../utils';
12
11
 
13
- const FlexGap = styled(Flex)`
14
- gap: ${({ theme }) => theme.spaces[4]};
15
- `;
16
-
17
12
  const Settings = ({ modifiedData, onChange, sortOptions }) => {
18
13
  const { formatMessage } = useIntl();
19
14
  const { settings, metadatas } = modifiedData;
20
15
 
21
16
  return (
22
- <>
23
- <Box paddingBottom={4}>
24
- <Typography variant="delta" as="h2">
25
- {formatMessage({
26
- id: getTrad('containers.SettingPage.settings'),
27
- defaultMessage: 'Settings',
28
- })}
29
- </Typography>
30
- </Box>
31
- <FlexGap justifyContent="space-between" wrap="wrap" paddingBottom={6}>
32
- <ToggleInput
33
- label={formatMessage({
34
- id: getTrad('form.Input.search'),
35
- defaultMessage: 'Enable search',
36
- })}
37
- onChange={(e) => {
38
- onChange({ target: { name: 'settings.searchable', value: e.target.checked } });
39
- }}
40
- onLabel={formatMessage({
41
- id: 'app.components.ToggleCheckbox.on-label',
42
- defaultMessage: 'on',
43
- })}
44
- offLabel={formatMessage({
45
- id: 'app.components.ToggleCheckbox.off-label',
46
- defaultMessage: 'off',
47
- })}
48
- name="settings.searchable"
49
- checked={settings.searchable}
50
- />
51
- <ToggleInput
52
- label={formatMessage({
53
- id: getTrad('form.Input.filters'),
54
- defaultMessage: 'Enable filters',
55
- })}
56
- onChange={(e) => {
57
- onChange({ target: { name: 'settings.filterable', value: e.target.checked } });
58
- }}
59
- onLabel={formatMessage({
60
- id: 'app.components.ToggleCheckbox.on-label',
61
- defaultMessage: 'on',
62
- })}
63
- offLabel={formatMessage({
64
- id: 'app.components.ToggleCheckbox.off-label',
65
- defaultMessage: 'off',
66
- })}
67
- name="settings.filterable"
68
- checked={settings.filterable}
69
- />
70
- <ToggleInput
71
- label={formatMessage({
72
- id: getTrad('form.Input.bulkActions'),
73
- defaultMessage: 'Enable bulk actions',
74
- })}
75
- onChange={(e) => {
76
- onChange({ target: { name: 'settings.bulkable', value: e.target.checked } });
77
- }}
78
- onLabel={formatMessage({
79
- id: 'app.components.ToggleCheckbox.on-label',
80
- defaultMessage: 'on',
81
- })}
82
- offLabel={formatMessage({
83
- id: 'app.components.ToggleCheckbox.off-label',
84
- defaultMessage: 'off',
85
- })}
86
- name="settings.bulkable"
87
- checked={settings.bulkable}
88
- />
89
- </FlexGap>
17
+ <Stack spacing={4}>
18
+ <Typography variant="delta" as="h2">
19
+ {formatMessage({
20
+ id: getTrad('containers.SettingPage.settings'),
21
+ defaultMessage: 'Settings',
22
+ })}
23
+ </Typography>
24
+
25
+ <Stack horizontal justifyContent="space-between" spacing={4}>
26
+ <Box width="100%">
27
+ <ToggleInput
28
+ label={formatMessage({
29
+ id: getTrad('form.Input.search'),
30
+ defaultMessage: 'Enable search',
31
+ })}
32
+ onChange={(e) => {
33
+ onChange({ target: { name: 'settings.searchable', value: e.target.checked } });
34
+ }}
35
+ onLabel={formatMessage({
36
+ id: 'app.components.ToggleCheckbox.on-label',
37
+ defaultMessage: 'on',
38
+ })}
39
+ offLabel={formatMessage({
40
+ id: 'app.components.ToggleCheckbox.off-label',
41
+ defaultMessage: 'off',
42
+ })}
43
+ name="settings.searchable"
44
+ checked={settings.searchable}
45
+ />
46
+ </Box>
47
+
48
+ <Box width="100%">
49
+ <ToggleInput
50
+ label={formatMessage({
51
+ id: getTrad('form.Input.filters'),
52
+ defaultMessage: 'Enable filters',
53
+ })}
54
+ onChange={(e) => {
55
+ onChange({ target: { name: 'settings.filterable', value: e.target.checked } });
56
+ }}
57
+ onLabel={formatMessage({
58
+ id: 'app.components.ToggleCheckbox.on-label',
59
+ defaultMessage: 'on',
60
+ })}
61
+ offLabel={formatMessage({
62
+ id: 'app.components.ToggleCheckbox.off-label',
63
+ defaultMessage: 'off',
64
+ })}
65
+ name="settings.filterable"
66
+ checked={settings.filterable}
67
+ />
68
+ </Box>
69
+
70
+ <Box width="100%">
71
+ <ToggleInput
72
+ label={formatMessage({
73
+ id: getTrad('form.Input.bulkActions'),
74
+ defaultMessage: 'Enable bulk actions',
75
+ })}
76
+ onChange={(e) => {
77
+ onChange({ target: { name: 'settings.bulkable', value: e.target.checked } });
78
+ }}
79
+ onLabel={formatMessage({
80
+ id: 'app.components.ToggleCheckbox.on-label',
81
+ defaultMessage: 'on',
82
+ })}
83
+ offLabel={formatMessage({
84
+ id: 'app.components.ToggleCheckbox.off-label',
85
+ defaultMessage: 'off',
86
+ })}
87
+ name="settings.bulkable"
88
+ checked={settings.bulkable}
89
+ />
90
+ </Box>
91
+ </Stack>
92
+
90
93
  <Grid gap={4}>
91
94
  <GridItem s={12} col={6}>
92
95
  <Select
@@ -145,7 +148,7 @@ const Settings = ({ modifiedData, onChange, sortOptions }) => {
145
148
  </Select>
146
149
  </GridItem>
147
150
  </Grid>
148
- </>
151
+ </Stack>
149
152
  );
150
153
  };
151
154
 
@@ -19,6 +19,40 @@ const ALLOWED_TYPES = [
19
19
  'uid',
20
20
  ];
21
21
 
22
+ const ALLOWED_ROOT_LEVEL_OPTIONS = [
23
+ 'min',
24
+ 'minLength',
25
+ 'max',
26
+ 'maxLength',
27
+ 'required',
28
+ 'regex',
29
+ 'enum',
30
+ 'unique',
31
+ 'private',
32
+ 'default',
33
+ ];
34
+
35
+ const optionValidationsReducer = (acc, option) => {
36
+ if (option.items) {
37
+ return option.items.reduce(optionValidationsReducer, acc);
38
+ }
39
+
40
+ if (!option.name) {
41
+ acc.push({
42
+ isValidOptionPath: false,
43
+ errorMessage: "The 'name' property is required on an options object",
44
+ });
45
+ } else {
46
+ acc.push({
47
+ isValidOptionPath:
48
+ ALLOWED_ROOT_LEVEL_OPTIONS.includes(option.name) || option.name.startsWith('options'),
49
+ errorMessage: `'${option.name}' must be prefixed with 'options.'`,
50
+ });
51
+ }
52
+
53
+ return acc;
54
+ };
55
+
22
56
  class CustomFields {
23
57
  constructor() {
24
58
  this.customFields = {};
@@ -32,7 +66,8 @@ class CustomFields {
32
66
  });
33
67
  } else {
34
68
  // Handle individual custom field
35
- const { name, pluginId, type, intlLabel, intlDescription, components } = customFields;
69
+ const { name, pluginId, type, intlLabel, intlDescription, components, options } =
70
+ customFields;
36
71
 
37
72
  // Ensure required attributes are provided
38
73
  invariant(name, 'A name must be provided');
@@ -55,6 +90,16 @@ class CustomFields {
55
90
  `Custom field name: '${name}' is not a valid object key`
56
91
  );
57
92
 
93
+ // Ensure options have valid name paths
94
+ const allFormOptions = [...(options?.base || []), ...(options?.advanced || [])];
95
+
96
+ if (allFormOptions.length) {
97
+ const optionPathValidations = allFormOptions.reduce(optionValidationsReducer, []);
98
+ optionPathValidations.forEach(({ isValidOptionPath, errorMessage }) => {
99
+ invariant(isValidOptionPath, errorMessage);
100
+ });
101
+ }
102
+
58
103
  // When no plugin is specified, default to the global namespace
59
104
  const uid = pluginId ? `plugin::${pluginId}.${name}` : `global::${name}`;
60
105
 
@@ -37,6 +37,26 @@ const SortSelect = ({ sortQuery, handleSelectChange }) => {
37
37
  defaultMessage: 'Newest',
38
38
  },
39
39
  },
40
+ 'githubStars:desc': {
41
+ selected: {
42
+ id: 'admin.pages.MarketPlacePage.sort.githubStars.selected',
43
+ defaultMessage: 'Sort by GitHub stars',
44
+ },
45
+ option: {
46
+ id: 'admin.pages.MarketPlacePage.sort.githubStars',
47
+ defaultMessage: 'Number of GitHub stars',
48
+ },
49
+ },
50
+ 'npmDownloads:desc': {
51
+ selected: {
52
+ id: 'admin.pages.MarketPlacePage.sort.npmDownloads.selected',
53
+ defaultMessage: 'Sort by npm downloads',
54
+ },
55
+ option: {
56
+ id: 'admin.pages.MarketPlacePage.sort.npmDownloads',
57
+ defaultMessage: 'Number of downloads',
58
+ },
59
+ },
40
60
  };
41
61
 
42
62
  return (
@@ -229,14 +229,14 @@ FormApiTokenContainer.propTypes = {
229
229
  values: PropTypes.shape({
230
230
  name: PropTypes.string,
231
231
  description: PropTypes.string,
232
- lifespan: PropTypes.string,
232
+ lifespan: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
233
233
  type: PropTypes.string,
234
234
  }).isRequired,
235
235
  isCreating: PropTypes.bool.isRequired,
236
236
  apiToken: PropTypes.shape({
237
237
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
238
238
  type: PropTypes.string,
239
- lifespan: PropTypes.number,
239
+ lifespan: PropTypes.string,
240
240
  name: PropTypes.string,
241
241
  accessKey: PropTypes.string,
242
242
  permissions: PropTypes.array,
@@ -50,7 +50,7 @@ FormBody.propTypes = {
50
50
  apiToken: PropTypes.shape({
51
51
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
52
52
  type: PropTypes.string,
53
- lifespan: PropTypes.number,
53
+ lifespan: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
54
54
  name: PropTypes.string,
55
55
  accessKey: PropTypes.string,
56
56
  permissions: PropTypes.array,
@@ -69,7 +69,7 @@ FormHead.propTypes = {
69
69
  apiToken: PropTypes.shape({
70
70
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
71
71
  type: PropTypes.string,
72
- lifespan: PropTypes.number,
72
+ lifespan: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
73
73
  name: PropTypes.string,
74
74
  accessKey: PropTypes.string,
75
75
  permissions: PropTypes.array,
@@ -290,6 +290,10 @@
290
290
  "admin.pages.MarketPlacePage.sort.newest": "Newest",
291
291
  "admin.pages.MarketPlacePage.sort.alphabetical.selected": "Sort by alphabetical order",
292
292
  "admin.pages.MarketPlacePage.sort.newest.selected": "Sort by newest",
293
+ "admin.pages.MarketPlacePage.sort.githubStars": "Number of GitHub stars",
294
+ "admin.pages.MarketPlacePage.sort.githubStars.selected": "Sort by GitHub stars",
295
+ "admin.pages.MarketPlacePage.sort.npmDownloads": "Number of downloads",
296
+ "admin.pages.MarketPlacePage.sort.npmDownloads.selected": "Sort by npm downloads",
293
297
  "admin.pages.MarketPlacePage.filters.collections": "Collections",
294
298
  "admin.pages.MarketPlacePage.filters.collectionsSelected": "{count, plural, =0 {No collections} one {# collection} other {# collections}} selected",
295
299
  "admin.pages.MarketPlacePage.filters.categories": "Categories",