I18N settings modal, creation (#10749)

This commit is contained in:
Marvin Frachet 2021-09-08 09:14:31 +02:00 committed by GitHub
parent 81aa2d8096
commit b77feb9764
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 402 additions and 295 deletions

View File

@ -17,7 +17,6 @@ import { Form } from '@strapi/helper-plugin';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { Formik } from 'formik';
import { Column, LayoutContent } from '../../../../layouts/UnauthenticatedLayout';
import Logo from '../Logo';
import FieldActionWrapper from '../FieldActionWrapper';

View File

@ -605,5 +605,6 @@
"notification.version.update.message": "A new version of Strapi is available!",
"or": "OR",
"request.error.model.unknown": "This model doesn't exist",
"skipToContent": "Skip to content"
"skipToContent": "Skip to content",
"clearLabel": "Clear"
}

View File

@ -40,8 +40,8 @@
"@fortawesome/react-fontawesome": "^0.1.14",
"@strapi/babel-plugin-switch-ee-ce": "1.0.0",
"@strapi/helper-plugin": "3.6.8",
"@strapi/icons": "0.0.1-alpha.22",
"@strapi/parts": "0.0.1-alpha.22",
"@strapi/icons": "0.0.1-alpha.23",
"@strapi/parts": "0.0.1-alpha.23",
"@strapi/utils": "3.6.8",
"axios": "^0.21.1",
"babel-loader": "8.2.2",

View File

@ -0,0 +1,75 @@
<!--- EmptyStateLayout.stories.mdx --->
import { Meta, ArgsTable, Canvas, Story } from '@storybook/addon-docs';
import { Main, Row } from '@strapi/parts';
import { TextInput } from '@strapi/parts/TextInput';
import { Button } from '@strapi/parts/Button';
import { Box } from '@strapi/parts/Box';
import { Text } from '@strapi/parts/Text';
import { Formik } from 'formik';
import * as Yup from 'yup';
import Form from './';
<Meta title="components/Form" />
# Form
This component is used to display a Formik form with additionnal error handling on fields
## Per field error
<Canvas>
<Story name="Per field error">
<Main>
<Formik initialValues={{ email: '' }} validationSchema={Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required')})}>
{({ handleSubmit, values, errors, handleChange }) => (
<Form onSubmit={handleSubmit}>
<TextInput
name="email"
label="Email"
hint="Enter a valid email"
value={values.email}
error={errors.email}
value={values.displayName}
onChange={handleChange}
/>
<Button type="submit">Submit</Button>
</Form>
)}
</Formik>
</Main>
</Story>
</Canvas>
## Per form error
Make sure to run this story in detached mode. The embeded iframe breaks the focus behaviour.
<Canvas>
<Story name="Per form error">
<Main>
<Formik initialValues={{ email: '' }} validationSchema={Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required')})} validateOnChange={false}>
{({ handleSubmit, values, errors, handleChange }) => (
<Form onSubmit={handleSubmit} noValidate>
{errors.email ? <Box padding={3} hasRadius background="danger100" color="danger600" tabIndex={-1} id="global-form-error">
<Text>The email is invalid</Text>
</Box> : null}
<TextInput
name="email"
label="Email"
hint="Enter a valid email"
value={values.email}
value={values.displayName}
onChange={handleChange}
/>
<Button type="submit">Submit</Button>
</Form>
)}
</Formik>
</Main>
</Story>
</Canvas>
### Props
<ArgsTable of={EmptyStateLayout} />

View File

@ -0,0 +1,37 @@
import React, { useEffect, useRef } from 'react';
import { Form, useFormikContext } from 'formik';
const FormWithFocus = props => {
const formRef = useRef(null);
const { isSubmitting, isValidating, errors, touched } = useFormikContext();
useEffect(() => {
if (isSubmitting && !isValidating) {
const errorsInForm = formRef.current.querySelectorAll('[data-strapi-field-error]');
if (errorsInForm.length > 0) {
const firstError = errorsInForm[0];
const describingId = firstError.getAttribute('id');
const formElementInError = formRef.current.querySelector(
`[aria-describedby="${describingId}"]`
);
if (formElementInError) {
formElementInError.focus();
}
}
}
if (!isSubmitting && !isValidating && Object.keys(errors).length) {
const el = document.getElementById('global-form-error');
if (el) {
el.focus();
}
}
}, [errors, isSubmitting, isValidating, touched]);
return <Form ref={formRef} {...props} noValidate />;
};
export default FormWithFocus;

View File

@ -23,7 +23,6 @@ export { default as HeaderSearch } from './old/components/HeaderSearch';
export { default as IcoContainer } from './old/components/IcoContainer';
export { default as InputAddon } from './old/components/InputAddon';
export { default as EmptyState } from './old/components/EmptyState';
export { default as Form } from './old/components/Form';
export * from './old/components/Tabs';
export * from './old/components/Select';
@ -191,6 +190,7 @@ export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPa
export { default as SettingsPageTitle } from './components/SettingsPageTitle';
export { default as Search } from './components/Search';
export { default as Status } from './components/Status';
export { default as Form } from './components/Form';
// New icons
export { default as SortIcon } from './icons/SortIcon';

View File

@ -1,37 +0,0 @@
import React, { useEffect } from 'react';
import { Form, useFormikContext, getIn } from 'formik';
// TODO move the components folder
const FormWithFocus = props => {
const { isSubmitting, isValidating, errors, touched } = useFormikContext();
useEffect(() => {
if (isSubmitting && !isValidating) {
const errorNames = Object.keys(touched).filter(error => getIn(errors, error));
if (errorNames.length) {
let errorEl;
// Does not handle field arrays
errorNames.forEach(errorKey => {
const selector = `[name="${errorKey}"]`;
if (!errorEl) {
errorEl = document.querySelector(selector);
}
});
errorEl?.focus();
}
}
if (!isSubmitting && !isValidating && Object.keys(errors).length) {
const el = document.getElementById('global-form-error');
el?.focus();
}
}, [errors, isSubmitting, isValidating, touched]);
return <Form {...props} noValidate />;
};
export default FormWithFocus;

View File

@ -39,6 +39,7 @@
"peerDependencies": {
"immer": "9.0.5",
"qs": "6.10.1",
"formik": "^2.2.6",
"react-select": "^4.0.2"
},
"devDependencies": {
@ -56,8 +57,8 @@
"@storybook/builder-webpack5": "^6.3.7",
"@storybook/manager-webpack5": "^6.3.7",
"@storybook/react": "^6.3.7",
"@strapi/icons": "0.0.1-alpha.22",
"@strapi/parts": "0.0.1-alpha.22",
"@strapi/icons": "0.0.1-alpha.23",
"@strapi/parts": "0.0.1-alpha.23",
"babel-loader": "^8.2.2",
"cross-env": "^7.0.3",
"enzyme": "^3.8.0",

View File

@ -48,27 +48,21 @@ const LocaleList = ({ canUpdateLocale, canDeleteLocale, onToggleCreateModal, isC
onEditLocale={handleEditLocale}
/>
) : (
<ContentLayout>
<EmptyStateLayout
icon={<EmptyStateDocument width={undefined} height={undefined} />}
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
action={
onToggleCreateModal ? (
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
</Button>
) : null
}
/>
</ContentLayout>
<EmptyStateLayout
icon={<EmptyStateDocument width={undefined} height={undefined} />}
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
action={
onToggleCreateModal ? (
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
</Button>
) : null
}
/>
)}
</ContentLayout>
<ModalCreate
isOpened={isCreating}
onClose={onToggleCreateModal}
alreadyUsedLocales={locales}
/>
{isCreating && <ModalCreate onClose={onToggleCreateModal} />}
<ModalDelete localeToDelete={localeToDelete} onClose={closeModalToDelete} />
<ModalEdit localeToEdit={localeToEdit} onClose={closeModalToEdit} locales={locales} />
</Main>

View File

@ -0,0 +1,88 @@
import React from 'react';
import styled from 'styled-components';
import { Select, Option } from '@strapi/parts/Select';
import { Loader } from '@strapi/parts/Loader';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
import useLocales from '../../hooks/useLocales';
import useDefaultLocales from '../../hooks/useDefaultLocales';
import { getTrad } from '../../utils';
const SmallLoader = styled(Loader)`
img {
height: 1rem;
width: 1rem;
}
`;
/**
* The component is memoized and needs a useCallback over the onLocaleChange and
* onClear props to prevent the Select from re-rendering N times when typing on a specific
* key in a formik form
*/
const LocaleSelect = React.memo(({ value, onLocaleChange, error, onClear }) => {
const { formatMessage } = useIntl();
const { defaultLocales, isLoading } = useDefaultLocales();
const { locales } = useLocales();
const options = (defaultLocales || [])
.map(locale => ({
label: locale.name,
value: locale.code,
}))
.filter(({ value: v }) => {
const foundLocale = locales.find(({ code }) => code === v);
return !foundLocale;
});
const computedValue = value || '';
return (
<Select
startIcon={isLoading ? <SmallLoader>Loading the locales...</SmallLoader> : undefined}
aria-busy={isLoading}
label={formatMessage({
id: getTrad('Settings.locales.modal.locales.label'),
defaultMessage: 'Locales',
})}
onClear={value ? onClear : undefined}
clearLabel={formatMessage({
id: 'clearLabel',
defaultMessage: 'Clear',
})}
error={error}
value={computedValue}
onChange={selectedLocaleKey => {
const selectedLocale = options.find(locale => locale.value === selectedLocaleKey);
if (selectedLocale) {
onLocaleChange({ code: selectedLocale.value, displayName: selectedLocale.label });
}
}}
>
{isLoading
? null
: options.map(option => (
<Option value={option.value} key={option.value}>
{option.label}
</Option>
))}
</Select>
);
});
LocaleSelect.defaultProps = {
error: undefined,
value: undefined,
onClear: () => undefined,
};
LocaleSelect.propTypes = {
error: PropTypes.string,
onClear: PropTypes.func,
onLocaleChange: PropTypes.func.isRequired,
value: PropTypes.string,
};
export default LocaleSelect;

View File

@ -1,8 +1,7 @@
import React from 'react';
import { Text, Checkbox, Padded } from '@buffetjs/core';
import { useFormikContext } from 'formik';
import { useIntl } from 'react-intl';
import { BaselineAlignment } from '@strapi/helper-plugin';
import { Checkbox } from '@strapi/parts/Checkbox';
import { getTrad } from '../../utils';
const AdvancedForm = () => {
@ -10,35 +9,19 @@ const AdvancedForm = () => {
const { formatMessage } = useIntl();
return (
<div>
<BaselineAlignment top size="2px" />
<Padded bottom size="sm">
<Text color="grey" textTransform="uppercase">
{formatMessage({
id: getTrad('Settings.locales.modal.advanced.settings'),
})}
</Text>
</Padded>
<BaselineAlignment top size="10px" />
<Checkbox
id="default-checkbox"
name="default-checkbox"
onChange={() => setFieldValue('isDefault', !values.isDefault)}
message={formatMessage({
id: getTrad('Settings.locales.modal.advanced.setAsDefault'),
})}
someChecked={false}
value={values.isDefault}
htmlFor="default-checkbox"
/>
<Text color="grey" fontSize="sm">
{formatMessage({
id: getTrad('Settings.locales.modal.advanced.setAsDefault.hint'),
})}
</Text>
</div>
<Checkbox
hint={formatMessage({
id: getTrad('Settings.locales.modal.advanced.setAsDefault.hint'),
defaultMessage: 'One default locale is required, change it by selecting another one',
})}
onChange={() => setFieldValue('isDefault', !values.isDefault)}
value={values.isDefault}
>
{formatMessage({
id: getTrad('Settings.locales.modal.advanced.setAsDefault'),
defaultMessage: 'Set as default locale',
})}
</Checkbox>
);
};

View File

@ -1,103 +1,74 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Label } from '@buffetjs/core';
import { Inputs } from '@buffetjs/custom';
import Select, { createFilter } from 'react-select';
import { Col, Row } from 'reactstrap';
import React, { useCallback } from 'react';
import { Grid, GridItem } from '@strapi/parts/Grid';
import { TextInput } from '@strapi/parts/TextInput';
import { useIntl } from 'react-intl';
import { useTheme } from 'styled-components';
import { BaselineAlignment, selectStyles, DropdownIndicator } from '@strapi/helper-plugin';
import { useFormikContext } from 'formik';
import { getTrad } from '../../utils';
import LocaleSelect from '../LocaleSelect';
const reactSelectLocaleFilter = createFilter({
ignoreCase: true,
ignoreAccents: true,
matchFrom: 'start',
});
const BaseForm = ({ options, defaultOption }) => {
const theme = useTheme();
const BaseForm = () => {
const { formatMessage } = useIntl();
const { values, handleChange, setFieldValue } = useFormikContext();
const { values, handleChange, setFieldValue, errors } = useFormikContext();
const styles = selectStyles(theme);
/**
* This is needed because the LocaleSelect component is a memoized component
* since it renders ~500 locales and that formik would trigger a re-render on it without
* it
*/
const handleLocaleChange = useCallback(
nextLocale => {
setFieldValue('displayName', nextLocale.displayName);
setFieldValue('code', nextLocale.code);
},
[setFieldValue]
);
/**
* This is needed because the LocaleSelect component is a memoized component
* since it renders ~500 locales and that formik would trigger a re-render on it without
* it
*/
const handleClear = useCallback(() => {
setFieldValue('displayName', '');
setFieldValue('code', '');
}, [setFieldValue]);
return (
<Row>
<Col>
<span id="locale-code">
<Label htmlFor="">
{formatMessage({
id: getTrad('Settings.locales.modal.locales.label'),
})}
</Label>
</span>
<BaselineAlignment top size="5px" />
<Select
aria-labelledby="locale-code"
options={options}
defaultValue={defaultOption}
filterOption={reactSelectLocaleFilter}
onChange={selection => {
setFieldValue('displayName', selection.value);
setFieldValue('code', selection.label);
}}
components={{ DropdownIndicator }}
styles={{
...styles,
control: (base, state) => ({ ...base, ...styles.control(base, state), height: '34px' }),
indicatorsContainer: (base, state) => ({
...base,
...styles.indicatorsContainer(base, state),
height: '32px',
}),
}}
<Grid gap={4}>
<GridItem col={6}>
<LocaleSelect
error={errors.code}
value={values.code}
onLocaleChange={handleLocaleChange}
onClear={handleClear}
/>
</Col>
</GridItem>
<Col>
<BaselineAlignment top size="2px" />
<Inputs
<GridItem col={6}>
<TextInput
name="displayName"
label={formatMessage({
id: getTrad('Settings.locales.modal.locales.displayName'),
defaultMessage: 'Locale display name',
})}
name="displayName"
description={formatMessage({
hint={formatMessage({
id: getTrad('Settings.locales.modal.locales.displayName.description'),
defaultMessage: 'Locale will be displayed under that name in the administration panel',
})}
type="text"
error={
errors.displayName
? formatMessage({
id: getTrad('Settings.locales.modal.locales.displayName.error'),
defaultMessage: 'The locale display name can only be less than 50 characters.',
})
: undefined
}
value={values.displayName}
onChange={handleChange}
validations={{
max: 50,
}}
translatedErrors={{
max: formatMessage({
id: getTrad('Settings.locales.modal.locales.displayName.error'),
}),
}}
/>
</Col>
</Row>
</GridItem>
</Grid>
);
};
BaseForm.defaultProps = {
defaultOption: undefined,
};
BaseForm.propTypes = {
options: PropTypes.arrayOf(
PropTypes.exact({ value: PropTypes.string.isRequired, label: PropTypes.string.isRequired })
).isRequired,
defaultOption: PropTypes.exact({
value: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
}),
};
export default BaseForm;

View File

@ -1,136 +1,120 @@
import React, { useRef } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { Modal, ModalFooter, TabPanel, useRBACProvider } from '@strapi/helper-plugin';
import { useRBACProvider, Form } from '@strapi/helper-plugin';
import { ModalLayout, ModalHeader, ModalBody, ModalFooter } from '@strapi/parts/ModalLayout';
import { TabGroup, Tabs, Tab, TabPanels, TabPanel } from '@strapi/parts/Tabs';
import { Button } from '@strapi/parts/Button';
import { ButtonText, H2 } from '@strapi/parts/Text';
import { Divider } from '@strapi/parts/Divider';
import { Box } from '@strapi/parts/Box';
import { Row } from '@strapi/parts/Row';
import CheckIcon from '@strapi/icons/CheckIcon';
import { useIntl } from 'react-intl';
import { Button } from '@buffetjs/core';
import { Formik } from 'formik';
import localeFormSchema from '../../schemas';
import { getTrad } from '../../utils';
import SettingsModal from '../SettingsModal';
import useDefaultLocales from '../../hooks/useDefaultLocales';
import useAddLocale from '../../hooks/useAddLocale';
import BaseForm from './BaseForm';
import AdvancedForm from './AdvancedForm';
const ModalCreate = ({ alreadyUsedLocales, onClose, isOpened }) => {
const { defaultLocales, isLoading } = useDefaultLocales();
const initialFormValues = {
code: '',
displayName: '',
isDefault: false,
};
const ModalCreate = ({ onClose }) => {
const { isAdding, addLocale } = useAddLocale();
const { formatMessage } = useIntl();
const { refetchPermissions } = useRBACProvider();
const shouldUpdatePermissions = useRef(false);
if (isLoading) {
return (
<div>
<p>
{formatMessage({ id: getTrad('Settings.locales.modal.create.defaultLocales.loading') })}
</p>
</div>
);
}
const handleClosed = async () => {
if (shouldUpdatePermissions.current) {
await refetchPermissions();
}
shouldUpdatePermissions.current = false;
};
const options = (defaultLocales || [])
.map(locale => ({
label: locale.code,
value: locale.name,
}))
.filter(({ label }) => {
const foundLocale = alreadyUsedLocales.find(({ code }) => code === label);
return !foundLocale;
/**
* No need to explicitly call the onClose prop here
* since the all tree (from the root of the page) is destroyed and re-mounted
* because of the RBAC refreshing and the potential move of the default locale
*/
const handleLocaleAdd = async values => {
await addLocale({
code: values.code,
name: values.displayName,
isDefault: values.isDefault,
});
const defaultOption = options[0];
if (!defaultOption) {
return null;
}
await refetchPermissions();
};
return (
<Modal isOpen={isOpened} onToggle={onClose} withoverflow="true" onClosed={handleClosed}>
<ModalLayout onClose={onClose} labelledBy="add-locale-title">
<Formik
initialValues={{
code: defaultOption.label,
displayName: defaultOption.value,
isDefault: false,
}}
onSubmit={values =>
addLocale({
code: values.code,
name: values.displayName,
isDefault: values.isDefault,
})
.then(() => {
shouldUpdatePermissions.current = true;
})
.then(() => {
onClose();
})}
initialValues={initialFormValues}
onSubmit={handleLocaleAdd}
validationSchema={localeFormSchema}
validateOnChange={false}
>
{({ handleSubmit, errors }) => (
<form onSubmit={handleSubmit}>
<SettingsModal
title={formatMessage({
id: getTrad('Settings.locales.modal.title'),
})}
breadCrumb={[formatMessage({ id: getTrad('Settings.list.actions.add') })]}
tabsAriaLabel={formatMessage({
id: getTrad('Settings.locales.modal.create.tab.label'),
})}
tabsId="i18n-settings-tabs-create"
>
<TabPanel>
<BaseForm
options={options}
defaultOption={defaultOption}
alreadyUsedLocales={alreadyUsedLocales}
/>
</TabPanel>
<TabPanel>
<AdvancedForm />
</TabPanel>
</SettingsModal>
{({ handleSubmit }) => (
<Form onSubmit={handleSubmit}>
<ModalHeader>
<ButtonText textColor="neutral800" as="h2" id="add-locale-title">
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
</ButtonText>
</ModalHeader>
<ModalBody>
<TabGroup label="Some stuff for the label" id="tabs" variant="simple">
<Row justifyContent="space-between">
<H2>
{formatMessage({
id: getTrad('Settings.locales.modal.title'),
})}
</H2>
<Tabs>
<Tab>
{formatMessage({
id: getTrad('Settings.locales.modal.base'),
})}
</Tab>
<Tab>
{formatMessage({
id: getTrad('Settings.locales.modal.advanced'),
})}
</Tab>
</Tabs>
</Row>
<ModalFooter>
<section>
<Button type="button" color="cancel" onClick={onClose}>
<Divider />
<Box paddingTop={7} paddingBottom={7}>
<TabPanels>
<TabPanel>
<BaseForm />
</TabPanel>
<TabPanel>
<AdvancedForm />
</TabPanel>
</TabPanels>
</Box>
</TabGroup>
</ModalBody>
<ModalFooter
startActions={
<Button variant="tertiary" onClick={onClose}>
{formatMessage({ id: 'app.components.Button.cancel' })}
</Button>
<Button
color="success"
type="submit"
isLoading={isAdding}
disabled={Object.keys(errors).length > 0}
>
{formatMessage({ id: getTrad('Settings.locales.modal.create.confirmation') })}
}
endActions={
<Button type="submit" startIcon={<CheckIcon />} disabled={isAdding}>
{formatMessage({ id: 'app.components.Button.save' })}
</Button>
</section>
</ModalFooter>
</form>
}
/>
</Form>
)}
</Formik>
</Modal>
</ModalLayout>
);
};
ModalCreate.defaultProps = {
alreadyUsedLocales: [],
};
ModalCreate.propTypes = {
alreadyUsedLocales: PropTypes.array,
onClose: PropTypes.func.isRequired,
isOpened: PropTypes.bool.isRequired,
};
export default ModalCreate;

View File

@ -1,5 +1,8 @@
import { useQuery } from 'react-query';
import { request, useNotification } from '@strapi/helper-plugin';
import { useNotifyAT } from '@strapi/parts/LiveRegions';
import { useIntl } from 'react-intl';
import { getTrad } from '../../utils';
const fetchDefaultLocalesList = async toggleNotification => {
try {
@ -19,9 +22,20 @@ const fetchDefaultLocalesList = async toggleNotification => {
};
const useDefaultLocales = () => {
const { formatMessage } = useIntl();
const { notifyStatus } = useNotifyAT();
const toggleNotification = useNotification();
const { isLoading, data } = useQuery('default-locales', () =>
fetchDefaultLocalesList(toggleNotification)
fetchDefaultLocalesList(toggleNotification).then(data => {
notifyStatus(
formatMessage({
id: getTrad('Settings.locales.modal.locales.loaded'),
defaultMessage: 'The locales have been successfully loaded.',
})
);
return data;
})
);
return { defaultLocales: data, isLoading };

View File

@ -1,23 +1,16 @@
import React from 'react';
import { useRBAC } from '@strapi/helper-plugin';
import { useIntl } from 'react-intl';
import LocaleSettingsPage from './LocaleSettingsPage';
import i18nPermissions from '../../permissions';
import { getTrad } from '../../utils';
const ProtectedLocaleSettingsPage = () => {
const { formatMessage } = useIntl();
const {
isLoading,
allowedActions: { canRead, canUpdate, canCreate, canDelete },
} = useRBAC(i18nPermissions);
if (isLoading) {
return (
<div>
<p>{formatMessage({ id: getTrad('Settings.permissions.loading') })}</p>
</div>
);
return null;
}
return (

View File

@ -1,7 +1,11 @@
import { object, string } from 'yup';
import { translatedErrors as errorsTrads } from '@strapi/helper-plugin';
const localeFormSchema = object().shape({
displayName: string().max(50, 'Settings.locales.modal.locales.displayName.error'),
code: string().required(),
displayName: string()
.max(50, 'Settings.locales.modal.locales.displayName.error')
.required(errorsTrads.required),
});
export default localeFormSchema;

View File

@ -26,7 +26,6 @@
"Settings.locales.modal.advanced.settings": "Settings",
"Settings.locales.modal.base": "Base settings",
"Settings.locales.modal.create.alreadyExist": "This locale already exists",
"Settings.locales.modal.create.confirmation": "Add locale",
"Settings.locales.modal.create.defaultLocales.loading": "Loading the available locales...",
"Settings.locales.modal.create.success": "Locale successfully added",
"Settings.locales.modal.create.tab.label": "Navigating between the I18N base settings and advanced settings",
@ -42,6 +41,7 @@
"Settings.locales.modal.locales.displayName.description": "Locale will be displayed under that name in the administration panel",
"Settings.locales.modal.locales.displayName.error": "The locale display name can only be less than 50 characters.",
"Settings.locales.modal.locales.label": "Locales",
"Settings.locales.modal.locales.loaded": "The locales have been successfully loaded.",
"Settings.locales.modal.title": "Configurations",
"Settings.locales.row.id": "ID",
"Settings.locales.row.displayName": "Display name",

View File

@ -4910,15 +4910,15 @@
resolve-from "^5.0.0"
store2 "^2.12.0"
"@strapi/icons@0.0.1-alpha.22":
version "0.0.1-alpha.22"
resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-0.0.1-alpha.22.tgz#d5e0dbdff6b1c45319aa3afcbb68381e116689d3"
integrity sha512-GsuRrCNebIP7HZ4r3vget/G1PScqAqG5Uq/fAnI1EUHo3iPrT+1soRRXT9ZrOjK5nmkLminG2JieStNRjvZPOQ==
"@strapi/icons@0.0.1-alpha.23":
version "0.0.1-alpha.23"
resolved "https://registry.yarnpkg.com/@strapi/icons/-/icons-0.0.1-alpha.23.tgz#e2474d8c431b2c270c1bbcd0c15d53e644686d82"
integrity sha512-6le+sn4yhJqdqzVjMTEGSQWecx79alTDoHEVTKLimea0cGN/vhSyGrOD9k0ef+8DCz+OkOgcu3RwnPQHj34iwA==
"@strapi/parts@0.0.1-alpha.22":
version "0.0.1-alpha.22"
resolved "https://registry.yarnpkg.com/@strapi/parts/-/parts-0.0.1-alpha.22.tgz#23b7ba76cf9ecab01bd9051d0d19f5bab93c2d75"
integrity sha512-K7vxDgdugXKGCd2xKHfKVkJ1WL3E3iqCw7/Jn9TNIQSz48GxfNJ7w9jZkH6TdwDUyiX3ugG2GYaFHS7oEBTQAA==
"@strapi/parts@0.0.1-alpha.23":
version "0.0.1-alpha.23"
resolved "https://registry.yarnpkg.com/@strapi/parts/-/parts-0.0.1-alpha.23.tgz#0822079fab14fd0795e51d634b3314079d2b0c86"
integrity sha512-ESmmJJu+uJLnPSmbaS5Xs73Ak0MOfQ5NZjczuI+ZQrnR3Hq7C/Pnw2id6MpbwtmIGCuIAH7bKEJciDIXvNpL3Q==
dependencies:
"@internationalized/number" "^3.0.2"
compute-scroll-into-view "^1.0.17"