diff --git a/packages/core/admin/admin/src/content-manager/components/Hint/index.js b/packages/core/admin/admin/src/content-manager/components/Hint/index.js
index 92822a9be9..a4d7daaf0e 100644
--- a/packages/core/admin/admin/src/content-manager/components/Hint/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/Hint/index.js
@@ -1,18 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { useIntl } from 'react-intl';
import { Typography } from '@strapi/design-system/Typography';
-export const Hint = ({ id, error, name, description }) => {
- const { formatMessage } = useIntl();
- const hint = description
- ? formatMessage(
- { id: description.id, defaultMessage: description.defaultMessage },
- { ...description.values }
- )
- : '';
-
- if (!hint || error) {
+export const Hint = ({ id, error, name, hint }) => {
+ if (hint.length === 0 || error) {
return null;
}
@@ -25,16 +16,12 @@ export const Hint = ({ id, error, name, description }) => {
Hint.defaultProps = {
id: undefined,
- description: undefined,
error: undefined,
+ hint: '',
};
Hint.propTypes = {
- description: PropTypes.shape({
- id: PropTypes.string.isRequired,
- defaultMessage: PropTypes.string.isRequired,
- values: PropTypes.object,
- }),
+ hint: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
error: PropTypes.string,
id: PropTypes.string,
name: PropTypes.string.isRequired,
diff --git a/packages/core/admin/admin/src/content-manager/components/InputUID/index.js b/packages/core/admin/admin/src/content-manager/components/InputUID/index.js
index ea443a1382..2c79c74a24 100644
--- a/packages/core/admin/admin/src/content-manager/components/InputUID/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/InputUID/index.js
@@ -23,7 +23,7 @@ import {
const InputUID = ({
attribute,
contentTypeUID,
- description,
+ hint,
disabled,
error,
intlLabel,
@@ -54,13 +54,6 @@ const InputUID = ({
)
: name;
- const hint = description
- ? formatMessage(
- { id: description.id, defaultMessage: description.defaultMessage },
- { ...description.values }
- )
- : '';
-
const formattedPlaceholder = placeholder
? formatMessage(
{ id: placeholder.id, defaultMessage: placeholder.defaultMessage },
@@ -251,11 +244,6 @@ InputUID.propTypes = {
required: PropTypes.bool,
}).isRequired,
contentTypeUID: PropTypes.string.isRequired,
- description: PropTypes.shape({
- id: PropTypes.string.isRequired,
- defaultMessage: PropTypes.string.isRequired,
- values: PropTypes.object,
- }),
disabled: PropTypes.bool,
error: PropTypes.string,
intlLabel: PropTypes.shape({
@@ -273,16 +261,17 @@ InputUID.propTypes = {
values: PropTypes.object,
}),
required: PropTypes.bool,
+ hint: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
};
InputUID.defaultProps = {
- description: undefined,
disabled: false,
error: undefined,
labelAction: undefined,
placeholder: undefined,
value: '',
required: false,
+ hint: '',
};
export default InputUID;
diff --git a/packages/core/admin/admin/src/content-manager/components/Wysiwyg/index.js b/packages/core/admin/admin/src/content-manager/components/Wysiwyg/index.js
index d8fdbe8e3c..cdf094b577 100644
--- a/packages/core/admin/admin/src/content-manager/components/Wysiwyg/index.js
+++ b/packages/core/admin/admin/src/content-manager/components/Wysiwyg/index.js
@@ -31,7 +31,7 @@ const TypographyAsterisk = styled(Typography)`
`;
const Wysiwyg = ({
- description,
+ hint,
disabled,
error,
intlLabel,
@@ -167,7 +167,7 @@ const Wysiwyg = ({
{!isExpandMode && }
-
+
{error && (
@@ -186,21 +186,17 @@ const Wysiwyg = ({
};
Wysiwyg.defaultProps = {
- description: null,
disabled: false,
error: '',
labelAction: undefined,
placeholder: null,
required: false,
value: '',
+ hint: '',
};
Wysiwyg.propTypes = {
- description: PropTypes.shape({
- id: PropTypes.string.isRequired,
- defaultMessage: PropTypes.string.isRequired,
- values: PropTypes.object,
- }),
+ hint: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
disabled: PropTypes.bool,
error: PropTypes.string,
intlLabel: PropTypes.shape({
diff --git a/packages/core/admin/admin/src/content-manager/pages/EditView/Informations/index.js b/packages/core/admin/admin/src/content-manager/pages/EditView/Information/index.js
similarity index 53%
rename from packages/core/admin/admin/src/content-manager/pages/EditView/Informations/index.js
rename to packages/core/admin/admin/src/content-manager/pages/EditView/Information/index.js
index 3e3aa43616..092a48fa04 100644
--- a/packages/core/admin/admin/src/content-manager/pages/EditView/Informations/index.js
+++ b/packages/core/admin/admin/src/content-manager/pages/EditView/Information/index.js
@@ -1,25 +1,40 @@
import React, { useRef } from 'react';
+import propTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { useCMEditViewDataManager } from '@strapi/helper-plugin';
-import { Box } from '@strapi/design-system/Box';
-import { Divider } from '@strapi/design-system/Divider';
-import { Typography } from '@strapi/design-system/Typography';
-import { Flex } from '@strapi/design-system/Flex';
-import { Stack } from '@strapi/design-system/Stack';
+import { Box, Divider, Flex, Stack, Typography } from '@strapi/design-system';
+
import { getTrad } from '../../../utils';
import getUnits from './utils/getUnits';
import { getFullName } from '../../../../utils';
-const Informations = () => {
+const KeyValuePair = ({ label, value }) => {
+ return (
+
+
+ {label}
+
+ {value}
+
+ );
+};
+
+KeyValuePair.propTypes = {
+ label: propTypes.string.isRequired,
+ value: propTypes.string.isRequired,
+};
+
+const Information = () => {
const { formatMessage, formatRelativeTime } = useIntl();
const { initialData, isCreatingEntry } = useCMEditViewDataManager();
const currentTime = useRef(Date.now());
const getFieldInfo = (atField, byField) => {
- const userFirstname = initialData[byField]?.firstname || '';
- const userLastname = initialData[byField]?.lastname || '';
- const userUsername = initialData[byField]?.username;
- const user = userUsername || getFullName(userFirstname, userLastname);
+ const { firstname, lastname, username } = initialData[byField] ?? {};
+
+ const userFirstname = firstname ?? '';
+ const userLastname = lastname ?? '';
+ const user = username ?? getFullName(userFirstname, userLastname);
const timestamp = initialData[atField] ? new Date(initialData[atField]).getTime() : Date.now();
const elapsed = timestamp - currentTime.current;
const { unit, value } = getUnits(-elapsed);
@@ -34,56 +49,57 @@ const Informations = () => {
const created = getFieldInfo('createdAt', 'createdBy');
return (
-
-
+
+
{formatMessage({
id: getTrad('containers.Edit.information'),
defaultMessage: 'Information',
})}
-
+
+
+
-
-
- {formatMessage({
+
+
- {created.at}
-
-
-
- {formatMessage({
+ value={created.at}
+ />
+
+
- {created.by}
-
-
-
- {formatMessage({
+ value={created.by}
+ />
+
+
+
+
- {updated.at}
-
-
-
- {formatMessage({
+ value={updated.at}
+ />
+
+
- {updated.by}
-
+ value={updated.by}
+ />
+
-
+
);
};
-export default Informations;
+export default Information;
diff --git a/packages/core/admin/admin/src/content-manager/pages/EditView/Information/tests/index.test.js b/packages/core/admin/admin/src/content-manager/pages/EditView/Information/tests/index.test.js
new file mode 100644
index 0000000000..39b8f0e113
--- /dev/null
+++ b/packages/core/admin/admin/src/content-manager/pages/EditView/Information/tests/index.test.js
@@ -0,0 +1,113 @@
+/**
+ *
+ * Tests for Information
+ *
+ */
+
+import React from 'react';
+import { render } from '@testing-library/react';
+import { IntlProvider } from 'react-intl';
+import { useCMEditViewDataManager } from '@strapi/helper-plugin';
+import { lightTheme, darkTheme } from '@strapi/design-system';
+import Theme from '../../../../../components/Theme';
+import ThemeToggleProvider from '../../../../../components/ThemeToggleProvider';
+import Information from '../index';
+
+jest.mock('@strapi/helper-plugin', () => ({
+ ...jest.requireActual('@strapi/helper-plugin'),
+ useCMEditViewDataManager: jest.fn(),
+}));
+
+const makeApp = () => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+describe('CONTENT MANAGER | EditView | Information', () => {
+ const RealNow = Date.now;
+
+ beforeAll(() => {
+ global.Date.now = jest.fn(() => new Date('2022-09-20').getTime());
+ });
+
+ afterAll(() => {
+ global.Date.now = RealNow;
+ });
+
+ it('renders and matches the snaphsot in case an entry is created', () => {
+ useCMEditViewDataManager.mockImplementationOnce(() => ({
+ initialData: {},
+ isCreatingEntry: true,
+ }));
+
+ const { getByText, getAllByText } = render(makeApp());
+
+ expect(getByText('Created')).toBeInTheDocument();
+ expect(getByText('Last update')).toBeInTheDocument();
+ expect(getAllByText('By').length).toBe(2);
+ expect(getAllByText('now').length).toBe(2);
+ expect(getAllByText('-').length).toBe(2);
+ });
+
+ it('renders and matches the snaphsot in case an entry is edited', () => {
+ useCMEditViewDataManager.mockImplementationOnce(() => ({
+ initialData: {
+ updatedAt: 'Fri Jan 13 2022 13:10:14 GMT+0100',
+ updatedBy: {
+ firstname: 'First name',
+ lastname: 'Last name',
+ },
+
+ createdAt: 'Fri Jan 13 2022 12:10:14 GMT+0100',
+ createdBy: {
+ firstname: 'First name',
+ lastname: 'Last name',
+ },
+ },
+ isCreatingEntry: false,
+ }));
+
+ const { getAllByText } = render(makeApp());
+
+ expect(getAllByText('8 months ago').length).toBe(2);
+ expect(getAllByText('First name Last name').length).toBe(2);
+ });
+
+ it('renders and matches the snaphsot in case a username is set', () => {
+ useCMEditViewDataManager.mockImplementationOnce(() => ({
+ initialData: {
+ updatedAt: 'Fri Jan 13 2022 13:10:14 GMT+0100',
+ updatedBy: {
+ firstname: 'First name',
+ lastname: 'Last name',
+ username: 'user@strapi.io',
+ },
+
+ createdAt: 'Fri Jan 13 2022 12:10:14 GMT+0100',
+ createdBy: {
+ firstname: 'First name',
+ lastname: 'Last name',
+ username: 'user@strapi.io',
+ },
+ },
+ isCreatingEntry: false,
+ }));
+
+ const { queryByText, getAllByText } = render(makeApp());
+
+ expect(getAllByText('user@strapi.io').length).toBe(2);
+ expect(queryByText('First name')).toBeNull();
+ expect(queryByText('Last name')).toBeNull();
+ });
+});
diff --git a/packages/core/admin/admin/src/content-manager/pages/EditView/Informations/utils/getUnits.js b/packages/core/admin/admin/src/content-manager/pages/EditView/Information/utils/getUnits.js
similarity index 100%
rename from packages/core/admin/admin/src/content-manager/pages/EditView/Informations/utils/getUnits.js
rename to packages/core/admin/admin/src/content-manager/pages/EditView/Information/utils/getUnits.js
diff --git a/packages/core/admin/admin/src/content-manager/pages/EditView/Informations/tests/index.test.js b/packages/core/admin/admin/src/content-manager/pages/EditView/Informations/tests/index.test.js
deleted file mode 100644
index ce3d0c4cbe..0000000000
--- a/packages/core/admin/admin/src/content-manager/pages/EditView/Informations/tests/index.test.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/**
- *
- * Tests for Informations
- *
- */
-
-import React from 'react';
-import { render } from '@testing-library/react';
-import { IntlProvider } from 'react-intl';
-import { useCMEditViewDataManager } from '@strapi/helper-plugin';
-import { lightTheme, darkTheme } from '@strapi/design-system';
-import Theme from '../../../../../components/Theme';
-import ThemeToggleProvider from '../../../../../components/ThemeToggleProvider';
-import Informations from '../index';
-
-jest.mock('@strapi/helper-plugin', () => ({
- useCMEditViewDataManager: jest.fn(),
- wrapAxiosInstance: jest.fn(() => {}),
-}));
-
-const makeApp = () => {
- return (
-
-
-
-
-
-
-
- );
-};
-
-describe('CONTENT MANAGER | EditView | Header', () => {
- const RealNow = Date.now;
-
- beforeAll(() => {
- global.Date.now = jest.fn(() => new Date('2021-09-20').getTime());
- });
-
- afterAll(() => {
- global.Date.now = RealNow;
- });
-
- it('renders and matches the snaphsot', () => {
- useCMEditViewDataManager.mockImplementationOnce(() => ({
- initialData: {},
- isCreatingEntry: true,
- }));
-
- const {
- container: { firstChild },
- } = render(makeApp());
-
- expect(firstChild).toMatchInlineSnapshot(`
- .c1 {
- padding-top: 8px;
- padding-bottom: 24px;
- }
-
- .c2 {
- background: #eaeaef;
- }
-
- .c3 {
- height: 1px;
- border: none;
- -webkit-flex-shrink: 0;
- -ms-flex-negative: 0;
- flex-shrink: 0;
- margin: 0;
- }
-
- .c0 {
- font-weight: 600;
- font-size: 0.6875rem;
- line-height: 1.45;
- text-transform: uppercase;
- color: #666687;
- }
-
- .c7 {
- font-size: 0.875rem;
- line-height: 1.43;
- font-weight: 600;
- color: #32324d;
- }
-
- .c8 {
- font-size: 0.875rem;
- line-height: 1.43;
- color: #32324d;
- }
-
- .c4 {
- -webkit-align-items: stretch;
- -webkit-box-align: stretch;
- -ms-flex-align: stretch;
- align-items: stretch;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-flex-direction: column;
- -ms-flex-direction: column;
- flex-direction: column;
- }
-
- .c6 {
- -webkit-align-items: center;
- -webkit-box-align: center;
- -ms-flex-align: center;
- align-items: center;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-flex-direction: row;
- -ms-flex-direction: row;
- flex-direction: row;
- -webkit-box-pack: justify;
- -webkit-justify-content: space-between;
- -ms-flex-pack: justify;
- justify-content: space-between;
- }
-
- .c5 > * {
- margin-top: 0;
- margin-bottom: 0;
- }
-
- .c5 > * + * {
- margin-top: 16px;
- }
-
-
-
- Information
-
-
-
-
-
-
-
- Created
-
-
- now
-
-
-
-
- By
-
-
- -
-
-
-
-
- Last update
-
-
- now
-
-
-
-
- By
-
-
- -
-
-
-
-
- `);
- });
-});
diff --git a/packages/core/admin/admin/src/content-manager/pages/EditView/index.js b/packages/core/admin/admin/src/content-manager/pages/EditView/index.js
index a692cf0f5c..ea4e4b654d 100644
--- a/packages/core/admin/admin/src/content-manager/pages/EditView/index.js
+++ b/packages/core/admin/admin/src/content-manager/pages/EditView/index.js
@@ -25,7 +25,7 @@ import SingleTypeFormWrapper from '../../components/SingleTypeFormWrapper';
import { getTrad } from '../../utils';
import useLazyComponents from '../../hooks/useLazyComponents';
import DraftAndPublishBadge from './DraftAndPublishBadge';
-import Informations from './Informations';
+import Information from './Information';
import Header from './Header';
import { getFieldsActionMatchingPermissions } from './utils';
import DeleteLink from './DeleteLink';
@@ -175,7 +175,7 @@ const EditView = ({ allowedActions, isSingleType, goBack, slug, id, origin, user
-
+
diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json
index 1f5b787d42..80e8ffdc46 100644
--- a/packages/core/admin/admin/src/translations/en.json
+++ b/packages/core/admin/admin/src/translations/en.json
@@ -465,7 +465,7 @@
"clearLabel": "Clear",
"selectButtonTitle": "Select",
"coming.soon": "This content is currently under construction and will be back in a few weeks!",
- "component.Input.error.validation.integer": "The value must be an integer",
+ "component.Input.error.validation.integer": "Value must be an integer",
"components.AutoReloadBlocker.description": "Run Strapi with one of the following commands:",
"components.AutoReloadBlocker.header": "Reload feature is required for this plugin.",
"components.ErrorBoundary.title": "Something went wrong...",
@@ -481,26 +481,26 @@
"components.FilterOptions.FILTER_TYPES.$notNull": "is not null",
"components.FilterOptions.FILTER_TYPES.$null": "is null",
"components.FilterOptions.FILTER_TYPES.$startsWith": "starts with",
- "components.Input.error.attribute.key.taken": "This value already exists",
+ "components.Input.error.attribute.key.taken": "Value already exists",
"components.Input.error.attribute.sameKeyAndName": "Can't be equal",
- "components.Input.error.attribute.taken": "This field name already exists",
+ "components.Input.error.attribute.taken": "Field name already exists",
"components.Input.error.contain.lowercase": "Password must contain at least one lowercase character",
"components.Input.error.contain.number": "Password must contain at least one number",
"components.Input.error.contain.uppercase": "Password must contain at least one uppercase character",
- "components.Input.error.contentTypeName.taken": "This name already exists",
+ "components.Input.error.contentTypeName.taken": "Name already exists",
"components.Input.error.custom-error": "{errorMessage} ",
"components.Input.error.password.noMatch": "Passwords do not match",
- "components.Input.error.validation.email": "This is an invalid email",
- "components.Input.error.validation.json": "This doesn't match the JSON format",
+ "components.Input.error.validation.email": "Value is an invalid email",
+ "components.Input.error.validation.json": "Value is invalid JSON",
"components.Input.error.validation.lowercase": "The value must be a lowercase string",
- "components.Input.error.validation.max": "The value is too high.",
- "components.Input.error.validation.maxLength": "The value is too long.",
- "components.Input.error.validation.min": "The value is too low.",
- "components.Input.error.validation.minLength": "The value is too short.",
- "components.Input.error.validation.minSupMax": "Can't be superior",
- "components.Input.error.validation.regex": "The value does not match the regex.",
- "components.Input.error.validation.required": "This value is required.",
- "components.Input.error.validation.unique": "This value is already used.",
+ "components.Input.error.validation.max": "Value is larger than the maximum",
+ "components.Input.error.validation.maxLength": "Value is longer than the maximum",
+ "components.Input.error.validation.min": "Value is smaller than the minimum",
+ "components.Input.error.validation.minLength": "Value is shorter than the minimum",
+ "components.Input.error.validation.minSupMax": "Value cannot be superior",
+ "components.Input.error.validation.regex": "Value does not match the required pattern",
+ "components.Input.error.validation.required": "Value is required",
+ "components.Input.error.validation.unique": "Value is already used",
"components.InputSelect.option.placeholder": "Choose here",
"components.ListRow.empty": "There is no data to be shown.",
"components.NotAllowedInput.text": "No permissions to see this field",
@@ -699,6 +699,9 @@
"content-manager.form.Input.sort.field": "Enable sort on this field",
"content-manager.form.Input.sort.order": "Default sort order",
"content-manager.form.Input.wysiwyg": "Display as WYSIWYG",
+ "content-manager.form.Input.hint.text": "{min, select, undefined {} other {min. {min}}}{divider}{max, select, undefined {} other {max. {max}}}{unit}{br}{description}",
+ "content-manager.form.Input.hint.minMaxDivider": " / ",
+ "content-manager.form.Input.hint.character.unit": "{maxValue, plural, one { character} other { characters}}",
"content-manager.global.displayedFields": "Displayed Fields",
"content-manager.groups": "Groups",
"content-manager.groups.numbered": "Groups ({number})",
diff --git a/packages/core/content-manager/server/services/utils/configuration/metadatas.js b/packages/core/content-manager/server/services/utils/configuration/metadatas.js
index 5033970694..406055e1b0 100644
--- a/packages/core/content-manager/server/services/utils/configuration/metadatas.js
+++ b/packages/core/content-manager/server/services/utils/configuration/metadatas.js
@@ -36,8 +36,9 @@ function createDefaultMetadata(schema, name) {
editable: true,
};
- if (isRelation(schema.attributes[name])) {
- const { targetModel } = schema.attributes[name];
+ const fieldAttributes = schema.attributes[name];
+ if (isRelation(fieldAttributes)) {
+ const { targetModel } = fieldAttributes;
const targetSchema = getTargetSchema(targetModel);
diff --git a/packages/core/database/lib/dialects/postgresql/index.js b/packages/core/database/lib/dialects/postgresql/index.js
index b865534f4f..431f452eaf 100644
--- a/packages/core/database/lib/dialects/postgresql/index.js
+++ b/packages/core/database/lib/dialects/postgresql/index.js
@@ -16,8 +16,16 @@ class PostgresDialect extends Dialect {
}
async initialize() {
- this.db.connection.client.driver.types.setTypeParser(1082, 'text', (v) => v); // Don't cast DATE string to Date()
- this.db.connection.client.driver.types.setTypeParser(1700, 'text', parseFloat);
+ this.db.connection.client.driver.types.setTypeParser(
+ this.db.connection.client.driver.types.builtins.DATE,
+ 'text',
+ (v) => v
+ ); // Don't cast DATE string to Date()
+ this.db.connection.client.driver.types.setTypeParser(
+ this.db.connection.client.driver.types.builtins.NUMERIC,
+ 'text',
+ parseFloat
+ );
}
usesForeignKeys() {
diff --git a/packages/core/database/package.json b/packages/core/database/package.json
index df2ca89ca5..4014f0b8f0 100644
--- a/packages/core/database/package.json
+++ b/packages/core/database/package.json
@@ -34,7 +34,7 @@
"date-fns": "2.29.3",
"debug": "4.3.4",
"fs-extra": "10.0.0",
- "knex": "1.0.7",
+ "knex": "2.4.0",
"lodash": "4.17.21",
"semver": "7.3.8",
"umzug": "3.1.1"
diff --git a/packages/core/helper-plugin/lib/src/components/GenericInput/index.js b/packages/core/helper-plugin/lib/src/components/GenericInput/index.js
index 1b584b0076..aa5e1910a1 100644
--- a/packages/core/helper-plugin/lib/src/components/GenericInput/index.js
+++ b/packages/core/helper-plugin/lib/src/components/GenericInput/index.js
@@ -9,6 +9,7 @@ import PropTypes from 'prop-types';
import parseISO from 'date-fns/parseISO';
import formatISO from 'date-fns/formatISO';
import { useIntl } from 'react-intl';
+
import {
Checkbox,
DatePicker,
@@ -24,7 +25,9 @@ import {
import { Option } from '@strapi/design-system/Select';
import EyeStriked from '@strapi/icons/EyeStriked';
import Eye from '@strapi/icons/Eye';
+
import NotSupported from './NotSupported';
+import useFieldHint from '../../hooks/useFieldHint';
const GenericInput = ({
autoComplete,
@@ -43,9 +46,16 @@ const GenericInput = ({
type,
value: defaultValue,
isNullable,
+ attribute,
...rest
}) => {
const { formatMessage } = useIntl();
+
+ const { hint } = useFieldHint({
+ description,
+ fieldSchema: attribute,
+ type: attribute?.type || type,
+ });
const [showPassword, setShowPassword] = useState(false);
const CustomInput = customInputs ? customInputs[type] : null;
@@ -91,7 +101,9 @@ const GenericInput = ({
return (
`Date picker, current is ${formattedDate}`}
- selectButtonTitle={formatMessage({ id: 'selectButtonTitle', defaultMessage: 'Select' })}
+ selectButtonTitle={formatMessage({
+ id: 'selectButtonTitle',
+ defaultMessage: 'Select',
+ })}
/>
);
}
@@ -451,6 +459,7 @@ GenericInput.defaultProps = {
options: [],
step: 1,
value: undefined,
+ attribute: null,
};
GenericInput.propTypes = {
@@ -461,6 +470,7 @@ GenericInput.propTypes = {
defaultMessage: PropTypes.string.isRequired,
values: PropTypes.object,
}),
+ attribute: PropTypes.object,
disabled: PropTypes.bool,
error: PropTypes.oneOfType([
PropTypes.string,
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/index.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/index.js
new file mode 100644
index 0000000000..a5db9e2b9d
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/index.js
@@ -0,0 +1,75 @@
+import React from 'react';
+import { useIntl } from 'react-intl';
+import { getFieldUnits, getMinMax } from './utils';
+
+/**
+ * @description
+ * A hook for generating the hint for a field
+ * @type {
+ * ({ description: { id: string, defaultMessage: string },
+ * type: string,
+ * fieldSchema: { minLength?: number|string; maxLength?: number|string; max?: number|string; min?: number|string }
+ * })
+ * => { hint: ''|Array }
+ * }
+ */
+const useFieldHint = ({ description, fieldSchema, type }) => {
+ const { formatMessage } = useIntl();
+
+ /**
+ * @returns {String}
+ */
+ const buildDescription = () =>
+ description?.id
+ ? formatMessage(
+ { id: description.id, defaultMessage: description.defaultMessage },
+ { ...description.values }
+ )
+ : '';
+
+ /**
+ * @returns {''|Array}
+ */
+ const buildHint = () => {
+ const { maximum, minimum } = getMinMax(fieldSchema);
+ const units = getFieldUnits({
+ type,
+ minimum,
+ maximum,
+ });
+
+ const minIsNumber = typeof minimum === 'number';
+ const maxIsNumber = typeof maximum === 'number';
+ const hasMinAndMax = maxIsNumber && minIsNumber;
+ const hasMinOrMax = maxIsNumber || minIsNumber;
+
+ if (!description?.id && !hasMinOrMax) {
+ return '';
+ }
+
+ return formatMessage(
+ {
+ id: 'content-manager.form.Input.hint.text',
+ defaultMessage:
+ '{min, select, undefined {} other {min. {min}}}{divider}{max, select, undefined {} other {max. {max}}}{unit}{br}{description}',
+ },
+ {
+ min: minimum,
+ max: maximum,
+ description: buildDescription(),
+ unit: units?.message && hasMinOrMax ? formatMessage(units.message, units.values) : null,
+ divider: hasMinAndMax
+ ? formatMessage({
+ id: 'content-manager.form.Input.hint.minMaxDivider',
+ defaultMessage: ' / ',
+ })
+ : null,
+ br: hasMinOrMax ?
: null,
+ }
+ );
+ };
+
+ return { hint: buildHint() };
+};
+
+export default useFieldHint;
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/tests/useFieldHint.test.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/tests/useFieldHint.test.js
new file mode 100644
index 0000000000..e27656d44e
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/tests/useFieldHint.test.js
@@ -0,0 +1,110 @@
+import React from 'react';
+import { renderHook, act } from '@testing-library/react-hooks';
+import { IntlProvider } from 'react-intl';
+
+import useFieldHint from '../index';
+
+const messages = { 'message.id': 'response' };
+const knownDescription = { id: 'message.id', defaultMessage: '' };
+
+// eslint-disable-next-line react/prop-types
+export const IntlWrapper = ({ children }) => (
+
+ {children}
+
+);
+
+function setup(args) {
+ return new Promise((resolve) => {
+ act(() => {
+ resolve(renderHook(() => useFieldHint(args), { wrapper: IntlWrapper }));
+ });
+ });
+}
+
+describe('useFieldHint', () => {
+ describe('descriptions', () => {
+ test('generates a known description', async () => {
+ const { result } = await setup({
+ description: knownDescription,
+ });
+
+ expect(result.current.hint).toEqual('response');
+ });
+
+ test('fails to generate an unknown description', async () => {
+ const { result } = await setup({
+ description: {},
+ });
+
+ expect(result.current.hint).toEqual('');
+ });
+ });
+
+ describe('minimum/maximum limits', () => {
+ test('generates a minimum limit', async () => {
+ const minimum = 1;
+ const fieldSchema = { min: minimum };
+
+ const { result } = await setup({
+ fieldSchema,
+ });
+
+ expect(result.current.hint.length).toEqual(3);
+
+ expect(result.current.hint[0]).toEqual(`min. ${minimum} character`);
+ expect(result.current.hint[2]).toEqual('');
+ });
+
+ test('generates a maximum limit', async () => {
+ const maximum = 5;
+ const fieldSchema = { max: maximum };
+
+ const { result } = await setup({
+ fieldSchema,
+ });
+
+ expect(result.current.hint.length).toEqual(3);
+
+ expect(result.current.hint[0]).toEqual(`max. ${maximum} characters`);
+ expect(result.current.hint[2]).toEqual('');
+ });
+
+ test('generates a minimum/maximum limits', async () => {
+ const minimum = 1;
+ const maximum = 5;
+ const fieldSchema = { minLength: minimum, maxLength: maximum };
+
+ const { result } = await setup({
+ fieldSchema,
+ });
+
+ expect(result.current.hint.length).toEqual(3);
+
+ expect(result.current.hint).toContain(`min. ${minimum} / max. ${maximum} characters`);
+ expect(result.current.hint[2]).toEqual('');
+ });
+ });
+
+ test('returns an empty string when there is no description or minimum and maximum limits', async () => {
+ const { result } = await setup({});
+
+ expect(result.current.hint).toEqual('');
+ });
+
+ test('generates the description and min max hint', async () => {
+ const minimum = 1;
+ const maximum = 5;
+ const fieldSchema = { minLength: minimum, maxLength: maximum };
+
+ const { result } = await setup({
+ description: knownDescription,
+ fieldSchema,
+ });
+
+ expect(result.current.hint.length).toEqual(3);
+
+ expect(result.current.hint[0]).toEqual(`min. ${minimum} / max. ${maximum} characters`);
+ expect(result.current.hint[2]).toEqual('response');
+ });
+});
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/getFieldUnits.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/getFieldUnits.js
new file mode 100644
index 0000000000..8a129af6b2
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/getFieldUnits.js
@@ -0,0 +1,22 @@
+/**
+ * @type { ({ type?: string; minimum?: number; maximum: number; } ) => {
+ * message?: {id: string, defaultMessage: string}; values?: {maxValue: number} } }
+ */
+const getFieldUnits = ({ type, minimum, maximum }) => {
+ if (['biginteger', 'integer', 'number'].includes(type)) {
+ return {};
+ }
+ const maxValue = Math.max(minimum || 0, maximum || 0);
+
+ return {
+ message: {
+ id: 'content-manager.form.Input.hint.character.unit',
+ defaultMessage: '{maxValue, plural, one { character} other { characters}}',
+ },
+ values: {
+ maxValue,
+ },
+ };
+};
+
+export default getFieldUnits;
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/getMinMax.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/getMinMax.js
new file mode 100644
index 0000000000..453092c4ba
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/getMinMax.js
@@ -0,0 +1,38 @@
+/**
+ * Get the minimum and maximum limits for an input
+ * @type {
+ * (fieldSchema: { minLength?: number|string; maxLength?: number|string; max?: number|string; min?: number|string } )
+ * => { maximum: number; minimum: number } }
+ */
+const getMinMax = (fieldSchema) => {
+ if (!fieldSchema) {
+ return { maximum: undefined, minimum: undefined };
+ }
+
+ const { minLength, maxLength, max, min } = fieldSchema;
+
+ let minimum;
+ let maximum;
+
+ const parsedMin = parseInt(min, 10);
+ const parsedMinLength = parseInt(minLength, 10);
+
+ if (!Number.isNaN(parsedMin)) {
+ minimum = parsedMin;
+ } else if (!Number.isNaN(parsedMinLength)) {
+ minimum = parsedMinLength;
+ }
+
+ const parsedMax = parseInt(max, 10);
+ const parsedMaxLength = parseInt(maxLength, 10);
+
+ if (!Number.isNaN(parsedMax)) {
+ maximum = parsedMax;
+ } else if (!Number.isNaN(parsedMaxLength)) {
+ maximum = parsedMaxLength;
+ }
+
+ return { maximum, minimum };
+};
+
+export default getMinMax;
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/index.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/index.js
new file mode 100644
index 0000000000..9cd686e859
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/index.js
@@ -0,0 +1,2 @@
+export { default as getFieldUnits } from './getFieldUnits';
+export { default as getMinMax } from './getMinMax';
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/tests/getFieldUnits.test.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/tests/getFieldUnits.test.js
new file mode 100644
index 0000000000..2921f28fd0
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/tests/getFieldUnits.test.js
@@ -0,0 +1,29 @@
+import { getFieldUnits } from '../index';
+
+describe('Content Manager | Inputs | Utils', () => {
+ describe('getFieldUnits', () => {
+ it('returns for number types', () => {
+ expect(getFieldUnits({ type: 'number' })).toEqual({});
+ });
+
+ it('returns for biginteger types', () => {
+ expect(getFieldUnits({ type: 'biginteger' })).toEqual({});
+ });
+
+ it('returns for integer types', () => {
+ expect(getFieldUnits({ type: 'integer' })).toEqual({});
+ });
+
+ it('correctly returns units translation object', () => {
+ expect(getFieldUnits({ type: 'text', minimum: 1, maximum: 5 })).toEqual({
+ message: {
+ id: 'content-manager.form.Input.hint.character.unit',
+ defaultMessage: '{maxValue, plural, one { character} other { characters}}',
+ },
+ values: {
+ maxValue: 5,
+ },
+ });
+ });
+ });
+});
diff --git a/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/tests/getMinMax.test.js b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/tests/getMinMax.test.js
new file mode 100644
index 0000000000..ea36389cc3
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/hooks/useFieldHint/utils/tests/getMinMax.test.js
@@ -0,0 +1,50 @@
+import getMinMax from '../getMinMax';
+
+describe('Content Manager | Inputs | Utils', () => {
+ describe('getMinMax', () => {
+ it('ignores a blank schema', () => {
+ expect(getMinMax({})).toEqual({ maximum: undefined, minimium: undefined });
+ });
+
+ it('ignores a null schema', () => {
+ expect(getMinMax(null)).toEqual({ maximum: undefined, minimium: undefined });
+ });
+
+ it('ignores values provided as strings that cannot be parsed to integers', () => {
+ const notANumber = 'NOT_A_NUMBER';
+ const fieldSchema = {
+ min: notANumber,
+ max: notANumber,
+ minLength: notANumber,
+ maxLength: notANumber,
+ };
+ expect(getMinMax(fieldSchema)).toEqual({ maximum: undefined, minimum: undefined });
+ });
+
+ it('correctly parses integer values from strings', () => {
+ const fieldSchema = {
+ min: '2',
+ max: '5',
+ };
+ expect(getMinMax(fieldSchema)).toEqual({ maximum: 5, minimum: 2 });
+ });
+
+ it('returns based on minLength and maxLength values', () => {
+ const fieldSchema = {
+ minLength: 10,
+ maxLength: 20,
+ };
+
+ expect(getMinMax(fieldSchema)).toEqual({ maximum: 20, minimum: 10 });
+ });
+
+ it('returns based on min and max values', () => {
+ const fieldSchema = {
+ min: 10,
+ max: 20,
+ };
+
+ expect(getMinMax(fieldSchema)).toEqual({ maximum: 20, minimum: 10 });
+ });
+ });
+});
diff --git a/packages/core/strapi/lib/core/loaders/plugins/index.js b/packages/core/strapi/lib/core/loaders/plugins/index.js
index d7b1b8e39b..bc6f5e8b3b 100644
--- a/packages/core/strapi/lib/core/loaders/plugins/index.js
+++ b/packages/core/strapi/lib/core/loaders/plugins/index.js
@@ -85,7 +85,15 @@ const loadPlugins = async (strapi) => {
for (const pluginName of Object.keys(enabledPlugins)) {
const enabledPlugin = enabledPlugins[pluginName];
- const serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');
+ let serverEntrypointPath;
+
+ try {
+ serverEntrypointPath = join(enabledPlugin.pathToPlugin, 'strapi-server.js');
+ } catch (e) {
+ throw new Error(
+ `Error loading the plugin ${pluginName} because ${pluginName} is not installed. Please either install the plugin or remove it's configuration.`
+ );
+ }
// only load plugins with a server entrypoint
if (!(await fse.pathExists(serverEntrypointPath))) {
diff --git a/packages/core/strapi/lib/utils/startup-logger.js b/packages/core/strapi/lib/utils/startup-logger.js
index 4aada5632c..0774c8fe11 100644
--- a/packages/core/strapi/lib/utils/startup-logger.js
+++ b/packages/core/strapi/lib/utils/startup-logger.js
@@ -27,7 +27,8 @@ module.exports = (app) => {
[chalk.blue('Environment'), app.config.environment],
[chalk.blue('Process PID'), process.pid],
[chalk.blue('Version'), `${app.config.info.strapi} (node ${process.version})`],
- [chalk.blue('Edition'), isEE ? 'Enterprise' : 'Community']
+ [chalk.blue('Edition'), isEE ? 'Enterprise' : 'Community'],
+ [chalk.blue('Database'), app.db.dialect.client]
);
console.log(infoTable.toString());
diff --git a/test/config/front/testUtils/commonTrads.json b/test/config/front/testUtils/commonTrads.json
deleted file mode 100644
index 0569a5827e..0000000000
--- a/test/config/front/testUtils/commonTrads.json
+++ /dev/null
@@ -1,151 +0,0 @@
-{
- "Analytics": "Analytics",
- "Content Manager": "Content Manager",
- "Content Type Builder": " Content Types Builder",
- "Email": "Email",
- "Files Upload": "Files Upload",
- "HomePage.notification.newsLetter.success": "Successfully subscribed to the newsletter",
- "New entry": "New entry",
- "Password": "Password",
- "Provider": "Provider",
- "ResetPasswordToken": "Reset Password Token",
- "Role": "Role",
- "Roles & Permissions": "Roles & Permission",
- "Settings Manager": "Settings Manager",
- "Username": "Username",
- "Users": "Users",
- "Users & Permissions": "Users & Permissions",
- "app.components.BlockLink.code": "Code examples",
- "app.components.BlockLink.code.content": "Learn by testing real projects developed the community.",
- "app.components.BlockLink.documentation": "Read the documentation",
- "app.components.BlockLink.documentation.content": "Discover the concepts, reference guides and tutorials.",
- "app.components.Button.cancel": "Cancel",
- "app.components.Button.save": "Save",
- "app.components.ComingSoonPage.comingSoon": "Coming soon",
- "app.components.ComingSoonPage.featuresNotAvailable": "This feature is still under active development.",
- "app.components.DownloadInfo.download": "Download in progress...",
- "app.components.DownloadInfo.text": "This could take a minute. Thanks for your patience.",
- "app.components.EmptyAttributes.title": "There are no fields yet",
- "app.components.HomePage.button.blog": "SEE MORE ON THE BLOG",
- "app.components.HomePage.button.quickStart": "START THE QUICK START TUTORIAL",
- "app.components.HomePage.community": "Find the community on the web",
- "app.components.HomePage.community.content": "Discuss with team members, contributors and developers on different channels.",
- "app.components.HomePage.createBlock.content.first": "The ",
- "app.components.HomePage.createBlock.content.second": " plugin will help you to define the data structure of your models. If you’re new here, we highly recommend you to follow our ",
- "app.components.HomePage.createBlock.content.tutorial": " tutorial.",
- "app.components.HomePage.cta": "CONFIRM",
- "app.components.HomePage.newsLetter": "Subscribe to the newsletter to get in touch about Strapi",
- "app.components.HomePage.support": "SUPPORT US",
- "app.components.HomePage.support.content": "By buying the T-shirt, it will allow us to continue our work on the project to give you the best possible experience!",
- "app.components.HomePage.support.link": "GET YOUR T-SHIRT NOW",
- "app.components.HomePage.welcome": "Welcome on board!",
- "app.components.HomePage.welcome.again": "Welcome ",
- "app.components.HomePage.welcomeBlock.content": "We are happy to have you as part of the community. We are constantly looking for feedback so feel free to send us DM on ",
- "app.components.HomePage.welcomeBlock.content.again": "We hope you are making progress on your project... Feel free to read the latest new about Strapi. We are giving our best to improve the product based on your feedback.",
- "app.components.HomePage.welcomeBlock.content.issues": "issues.",
- "app.components.HomePage.welcomeBlock.content.raise": " or raise ",
- "app.components.ImgPreview.hint": "Drag & drop your file into this area or {browse} for a file to upload",
- "app.components.ImgPreview.hint.browse": "browse",
- "app.components.InputFile.newFile": "Add new file",
- "app.components.InputFileDetails.open": "Open in a new tab",
- "app.components.InputFileDetails.originalName": "Original name:",
- "app.components.InputFileDetails.remove": "Remove this file",
- "app.components.InputFileDetails.size": "Size:",
- "app.components.InstallPluginPage.Download.title": "Downloading...",
- "app.components.InstallPluginPage.Download.description": "It might take a few seconds to download and install the plugin.",
- "app.components.InstallPluginPage.InputSearch.label": " ",
- "app.components.InstallPluginPage.InputSearch.placeholder": "Search for a plugin... (ex: authentication)",
- "app.components.InstallPluginPage.description": "Extend your app effortlessly.",
- "app.components.InstallPluginPage.helmet": "Marketplace - Plugins",
- "app.components.InstallPluginPage.plugin.support-us.description": "Support us by buying the Strapi T-shirt. That will allow us to keep working on the project and try giving you the best possible experience!",
- "app.components.InstallPluginPage.title": "Marketplace - Plugins",
- "app.components.InstallPluginPopup.downloads": "download",
- "app.components.InstallPluginPopup.navLink.avis": "avis",
- "app.components.InstallPluginPopup.navLink.changelog": "changelog",
- "app.components.InstallPluginPopup.navLink.description": "Description",
- "app.components.InstallPluginPopup.navLink.faq": "faq",
- "app.components.InstallPluginPopup.navLink.screenshots": "Screenshots",
- "app.components.InstallPluginPopup.noDescription": "No description available",
- "app.components.LeftMenuFooter.documentation": "Documentation",
- "app.components.LeftMenuFooter.help": "Help",
- "app.components.LeftMenuFooter.poweredBy": "Powered by ",
- "app.components.LeftMenuLinkContainer.configuration": "Configurations",
- "app.components.LeftMenuLinkContainer.general": "General",
- "app.components.LeftMenuLinkContainer.installNewPlugin": "Marketplace",
- "app.components.LeftMenuLinkContainer.listPlugins": "Plugins",
- "app.components.LeftMenuLinkContainer.noPluginsInstalled": "No plugins installed yet",
- "app.components.LeftMenuLinkContainer.plugins": "Plugins",
- "app.components.ListPluginsPage.description": "List of the installed plugins in the project.",
- "app.components.ListPluginsPage.helmet.title": "List plugins",
- "app.components.ListPluginsPage.title": "Plugins",
- "app.components.Logout.profile": "Profile",
- "app.components.Logout.logout": "Logout",
- "app.components.NotFoundPage.back": "Back to homepage",
- "app.components.NotFoundPage.description": "Not Found",
- "app.components.Official": "Official",
- "app.components.Onboarding.label.completed": "% completed",
- "app.components.Onboarding.title": "Get Started Videos",
- "app.components.PluginCard.Button.label.download": "Download",
- "app.components.PluginCard.Button.label.install": "Already installed",
- "app.components.PluginCard.Button.label.support": "Support us",
- "app.components.PluginCard.compatible": "Compatible with your app",
- "app.components.PluginCard.compatibleCommunity": "Compatible with the community",
- "app.components.PluginCard.more-details": "More details",
- "app.components.PluginCard.price.free": "Free",
- "app.components.PluginCard.settings": "Settings",
- "app.components.listPlugins.button": "Add New Plugin",
- "app.components.listPlugins.title.none": "No plugins installed",
- "app.components.listPlugins.title.plural": "{number} plugins are installed",
- "app.components.listPlugins.title.singular": "{number} plugin is installed",
- "app.components.listPluginsPage.deletePlugin.error": "An error occurred while uninstalling the plugin",
- "app.utils.SelectOption.defaultMessage": " ",
- "app.utils.defaultMessage": " ",
- "app.utils.placeholder.defaultMessage": " ",
- "components.AutoReloadBlocker.description": "Open the following file and enable the feature.",
- "components.AutoReloadBlocker.header": "Reload feature is required for this plugin.",
- "components.ErrorBoundary.title": "Something went wrong...",
- "components.Input.error.attribute.key.taken": "This value already exists",
- "components.Input.error.attribute.sameKeyAndName": "Can't be equal",
- "components.Input.error.attribute.taken": "This field name already exists",
- "components.Input.error.contentTypeName.taken": "This name already exists",
- "components.Input.error.custom-error": "{errorMessage} ",
- "components.Input.error.validation.email": "This is an invalid email",
- "components.Input.error.validation.json": "This doesn't match the JSON format",
- "components.Input.error.validation.max": "The value is too high.",
- "components.Input.error.validation.maxLength": "The value is too long.",
- "components.Input.error.validation.min": "The value is too low.",
- "components.Input.error.validation.minLength": "The value is too short.",
- "components.Input.error.validation.minSupMax": "Can't be superior",
- "components.Input.error.validation.regex": "The value does not match the regex.",
- "components.Input.error.validation.required": "This value is required.",
- "components.ListRow.empty": "There is no data to be shown.",
- "components.OverlayBlocker.description": "You're using a feature that needs the server to restart. Please wait until the server is up.",
- "components.OverlayBlocker.description.serverError": "The server should have restarted, please check your logs in the terminal.",
- "components.OverlayBlocker.title": "Waiting for restart...",
- "components.OverlayBlocker.title.serverError": "The restart is taking longer than expected",
- "components.PageFooter.select": "entries per page",
- "components.ProductionBlocker.description": "For safety purposes we have to disable this plugin in other environments.",
- "components.ProductionBlocker.header": "This plugin is only available in development.",
- "components.Wysiwyg.ToggleMode.markdown": "Switch to markdown",
- "components.Wysiwyg.ToggleMode.preview": "Switch to preview",
- "components.Wysiwyg.collapse": "Collapse",
- "components.Wysiwyg.selectOptions.H1": "Title H1",
- "components.Wysiwyg.selectOptions.H2": "Title H2",
- "components.Wysiwyg.selectOptions.H3": "Title H3",
- "components.Wysiwyg.selectOptions.H4": "Title H4",
- "components.Wysiwyg.selectOptions.H5": "Title H5",
- "components.Wysiwyg.selectOptions.H6": "Title H6",
- "components.Wysiwyg.selectOptions.title": "Add a title",
- "components.WysiwygBottomControls.charactersIndicators": "characters",
- "components.WysiwygBottomControls.fullscreen": "Expand",
- "components.WysiwygBottomControls.uploadFiles": "Drag & drop files, paste from the clipboard or {browse}.",
- "components.WysiwygBottomControls.uploadFiles.browse": "select them",
- "components.popUpWarning.button.cancel": "Cancel",
- "components.popUpWarning.button.confirm": "Confirm",
- "components.popUpWarning.message": "Are you sure you want to delete this?",
- "components.popUpWarning.title": "Please confirm",
- "notification.error": "An error occurred",
- "notification.error.layout": "Couldn't retrieve the layout",
- "request.error.model.unknown": "This model doesn't exist",
- "app.utils.delete": "Delete"
-}
diff --git a/test/config/front/testUtils/formatMessages.js b/test/config/front/testUtils/formatMessages.js
deleted file mode 100644
index 2e170b97c8..0000000000
--- a/test/config/front/testUtils/formatMessages.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import commonTrads from './commonTrads.json';
-
-const formatMessagesWithPluginId = (pluginId, messages) => {
- return Object.keys(messages).reduce((acc, current) => {
- acc[`${pluginId}.${current}`] = messages[current];
-
- return acc;
- }, commonTrads);
-};
-
-export default formatMessagesWithPluginId;
diff --git a/yarn.lock b/yarn.lock
index 1254bc6f36..3ec116a4f6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8939,21 +8939,16 @@ color@^4.2.3:
color-convert "^2.0.1"
color-string "^1.9.0"
-colorette@2.0.16:
- version "2.0.16"
- resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
- integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==
+colorette@2.0.19, colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16, colorette@^2.0.17:
+ version "2.0.19"
+ resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
+ integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
colorette@^1.2.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
-colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16, colorette@^2.0.17:
- version "2.0.19"
- resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
- integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
-
colors@~1.2.1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc"
@@ -14786,12 +14781,12 @@ klona@^2.0.4:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
-knex@1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/knex/-/knex-1.0.7.tgz#965f4490efc451b140aac4c5c6efa39fd877597b"
- integrity sha512-89jxuRATt4qJMb9ZyyaKBy0pQ4d5h7eOFRqiNFnUvsgU+9WZ2eIaZKrAPG1+F3mgu5UloPUnkVE5Yo2sKZUs6Q==
+knex@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/knex/-/knex-2.4.0.tgz#7d33cc36f320cdac98741010544b4c6a98b8b19e"
+ integrity sha512-i0GWwqYp1Hs2yvc2rlDO6nzzkLhwdyOZKRdsMTB8ZxOs2IXQyL5rBjSbS1krowCh6V65T4X9CJaKtuIfkaPGSA==
dependencies:
- colorette "2.0.16"
+ colorette "2.0.19"
commander "^9.1.0"
debug "4.3.4"
escalade "^3.1.1"