mirror of
https://github.com/strapi/strapi.git
synced 2025-09-19 21:38:05 +00:00
[i18n] add a locale from the ISO list (#9397)
This commit is contained in:
parent
615905d120
commit
cdcbb0de4b
@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Label } from '@buffetjs/core';
|
||||||
|
import { Inputs } from '@buffetjs/custom';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { Col, Row } from 'reactstrap';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { getTrad } from '../../utils';
|
||||||
|
|
||||||
|
const BaseForm = ({ options, defaultOption }) => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
const { values, handleChange, setFieldValue } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<span id="locale-code">
|
||||||
|
<Label htmlFor="">
|
||||||
|
{formatMessage({
|
||||||
|
id: getTrad('Settings.locales.modal.locales.label'),
|
||||||
|
})}
|
||||||
|
</Label>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Select
|
||||||
|
aria-labelledby="locale-code"
|
||||||
|
options={options}
|
||||||
|
defaultValue={defaultOption}
|
||||||
|
onChange={selection => {
|
||||||
|
setFieldValue('displayName', selection.value);
|
||||||
|
setFieldValue('code', selection.label);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col>
|
||||||
|
<Inputs
|
||||||
|
label={formatMessage({
|
||||||
|
id: getTrad('Settings.locales.modal.locales.displayName'),
|
||||||
|
})}
|
||||||
|
name="displayName"
|
||||||
|
description={formatMessage({
|
||||||
|
id: getTrad('Settings.locales.modal.locales.displayName.description'),
|
||||||
|
})}
|
||||||
|
type="text"
|
||||||
|
value={values.displayName}
|
||||||
|
onChange={handleChange}
|
||||||
|
validations={{
|
||||||
|
max: 50,
|
||||||
|
}}
|
||||||
|
translatedErrors={{
|
||||||
|
max: formatMessage({
|
||||||
|
id: getTrad('Settings.locales.modal.locales.displayName.error'),
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
@ -7,15 +7,40 @@ import { Formik } from 'formik';
|
|||||||
import localeFormSchema from '../../schemas';
|
import localeFormSchema from '../../schemas';
|
||||||
import { getTrad } from '../../utils';
|
import { getTrad } from '../../utils';
|
||||||
import SettingsModal from '../SettingsModal';
|
import SettingsModal from '../SettingsModal';
|
||||||
|
import useDefaultLocales from '../../hooks/useDefaultLocales';
|
||||||
|
import useAddLocale from '../../hooks/useAddLocale';
|
||||||
|
import BaseForm from './BaseForm';
|
||||||
|
|
||||||
const ModalCreate = ({ onClose, isOpened }) => {
|
const ModalCreate = ({ onClose, isOpened }) => {
|
||||||
|
const { defaultLocales, isLoading } = useDefaultLocales();
|
||||||
|
const { isAdding, addLocale } = useAddLocale();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
if (!isOpened) return null;
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{formatMessage({ id: getTrad('Settings.locales.modal.create.defaultLocales.loading') })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = (defaultLocales || []).map(locale => ({
|
||||||
|
label: locale.code,
|
||||||
|
value: locale.name,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const defaultOption = options[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpened} onToggle={onClose}>
|
<Modal isOpen={isOpened} onToggle={onClose}>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{ displayName: '' }}
|
initialValues={{ code: defaultOption.label, displayName: defaultOption.value }}
|
||||||
onSubmit={() => null}
|
onSubmit={values =>
|
||||||
|
addLocale({ code: values.code, name: values.displayName }).then(onClose)}
|
||||||
validationSchema={localeFormSchema}
|
validationSchema={localeFormSchema}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, errors }) => (
|
{({ handleSubmit, errors }) => (
|
||||||
@ -30,7 +55,9 @@ const ModalCreate = ({ onClose, isOpened }) => {
|
|||||||
})}
|
})}
|
||||||
tabsId="i18n-settings-tabs-create"
|
tabsId="i18n-settings-tabs-create"
|
||||||
>
|
>
|
||||||
<TabPanel>Base form</TabPanel>
|
<TabPanel>
|
||||||
|
<BaseForm options={options} defaultOption={defaultOption} />
|
||||||
|
</TabPanel>
|
||||||
<TabPanel>advanced</TabPanel>
|
<TabPanel>advanced</TabPanel>
|
||||||
</SettingsModal>
|
</SettingsModal>
|
||||||
|
|
||||||
@ -42,7 +69,7 @@ const ModalCreate = ({ onClose, isOpened }) => {
|
|||||||
<Button
|
<Button
|
||||||
color="success"
|
color="success"
|
||||||
type="submit"
|
type="submit"
|
||||||
isLoading={false}
|
isLoading={isAdding}
|
||||||
disabled={Object.keys(errors).length > 0}
|
disabled={Object.keys(errors).length > 0}
|
||||||
>
|
>
|
||||||
{formatMessage({ id: getTrad('Settings.locales.modal.create.confirmation') })}
|
{formatMessage({ id: getTrad('Settings.locales.modal.create.confirmation') })}
|
||||||
|
@ -33,11 +33,11 @@ const BaseForm = ({ options, defaultOption }) => {
|
|||||||
<Col>
|
<Col>
|
||||||
<Inputs
|
<Inputs
|
||||||
label={formatMessage({
|
label={formatMessage({
|
||||||
id: getTrad('Settings.locales.modal.edit.locales.displayName'),
|
id: getTrad('Settings.locales.modal.locales.displayName'),
|
||||||
})}
|
})}
|
||||||
name="displayName"
|
name="displayName"
|
||||||
description={formatMessage({
|
description={formatMessage({
|
||||||
id: getTrad('Settings.locales.modal.edit.locales.displayName.description'),
|
id: getTrad('Settings.locales.modal.locales.displayName.description'),
|
||||||
})}
|
})}
|
||||||
type="text"
|
type="text"
|
||||||
value={values.displayName}
|
value={values.displayName}
|
||||||
|
@ -179,13 +179,13 @@ describe('i18n settings page', () => {
|
|||||||
const rowUtils = within(row);
|
const rowUtils = within(row);
|
||||||
|
|
||||||
fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
|
fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
|
||||||
fireEvent.change(screen.getByLabelText('Settings.locales.modal.edit.locales.displayName'), {
|
fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
|
||||||
target: {
|
target: {
|
||||||
value:
|
value:
|
||||||
'a very very very very long string that has more than fifty characters in order to show a warning',
|
'a very very very very long string that has more than fifty characters in order to show a warning',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
fireEvent.blur(screen.getByLabelText('Settings.locales.modal.edit.locales.displayName'));
|
fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName'));
|
||||||
|
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(screen.getByText('Settings.locales.modal.edit.confirmation')).toBeDisabled()
|
expect(screen.getByText('Settings.locales.modal.edit.confirmation')).toBeDisabled()
|
||||||
@ -341,7 +341,7 @@ describe('i18n settings page', () => {
|
|||||||
|
|
||||||
fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
|
fireEvent.click(rowUtils.getByLabelText('Settings.list.actions.edit'));
|
||||||
|
|
||||||
fireEvent.change(screen.getByLabelText('Settings.locales.modal.edit.locales.displayName'), {
|
fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
|
||||||
target: {
|
target: {
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
@ -487,7 +487,17 @@ describe('i18n settings page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
// TODO: add the select verifications
|
beforeEach(() => {
|
||||||
|
request.mockImplementation(url =>
|
||||||
|
url.includes('/i18n/locales')
|
||||||
|
? Promise.resolve([])
|
||||||
|
: Promise.resolve([
|
||||||
|
{ code: 'fr-FR', name: 'Francais' },
|
||||||
|
{ code: 'en-EN', name: 'English' },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('shows the default create modal layout', async () => {
|
it('shows the default create modal layout', async () => {
|
||||||
render(
|
render(
|
||||||
<TestWrapper>
|
<TestWrapper>
|
||||||
@ -497,7 +507,18 @@ describe('i18n settings page', () => {
|
|||||||
|
|
||||||
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
||||||
|
|
||||||
expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible();
|
expect(
|
||||||
|
screen.getByText(`Settings.locales.modal.create.defaultLocales.loading`)
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(`fr-FR`)).toBeVisible();
|
||||||
|
expect(screen.getByLabelText('Settings.locales.modal.locales.displayName')).toHaveValue(
|
||||||
|
'Francais'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes the create modal when clicking on cancel', async () => {
|
it('closes the create modal when clicking on cancel', async () => {
|
||||||
@ -508,9 +529,142 @@ describe('i18n settings page', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
|
||||||
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText('app.components.Button.cancel'));
|
fireEvent.click(screen.getByText('app.components.Button.cancel'));
|
||||||
|
|
||||||
expect(screen.queryByText(`Settings.locales.modal.create.confirmation`)).toBeFalsy();
|
expect(screen.queryByText(`Settings.locales.modal.create.confirmation`)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('shows a warning and disabled the confirmation button when display name length is over 50', async () => {
|
||||||
|
render(
|
||||||
|
<TestWrapper>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</TestWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
|
||||||
|
target: {
|
||||||
|
value:
|
||||||
|
'a very very very very long string that has more than fifty characters in order to show a warning',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName'));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText(`Settings.locales.modal.locales.displayName.error`)).toBeVisible()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sync the select and the text input', async () => {
|
||||||
|
render(
|
||||||
|
<TestWrapper>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</TestWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(screen.getByText(`Settings.locales.modal.create.confirmation`)).toBeVisible()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Put some data in the input in order to make sure it resets well when changing locale
|
||||||
|
fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
|
||||||
|
target: {
|
||||||
|
value:
|
||||||
|
'a very very very very long string that has more than fifty characters in order to show a warning',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const DOWN_ARROW = { keyCode: 40 };
|
||||||
|
fireEvent.keyDown(screen.getByLabelText('Settings.locales.modal.locales.label'), DOWN_ARROW);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('en-EN'));
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Settings.locales.modal.locales.displayName')).toHaveValue(
|
||||||
|
'English'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an error when something went wrong when adding a locale', async () => {
|
||||||
|
request.mockImplementation((url, opts) => {
|
||||||
|
if (opts.method === 'POST') {
|
||||||
|
return Promise.reject(new Error('Something went wrong when adding a locale'));
|
||||||
|
}
|
||||||
|
if (url.includes('/i18n/locales')) return Promise.resolve([]);
|
||||||
|
|
||||||
|
return Promise.resolve([
|
||||||
|
{ code: 'fr-FR', name: 'Francais' },
|
||||||
|
{ code: 'en-EN', name: 'English' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TestWrapper>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</TestWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
||||||
|
|
||||||
|
const confirmationButton = await waitFor(() =>
|
||||||
|
screen.getByText(`Settings.locales.modal.create.confirmation`)
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(confirmationButton);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(strapi.notification.toggle).toBeCalledWith({
|
||||||
|
type: 'warning',
|
||||||
|
message: { id: 'notification.error' },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows an success toast when adding a locale is successful', async () => {
|
||||||
|
request.mockImplementation((url, opts) => {
|
||||||
|
if (opts.method === 'POST') {
|
||||||
|
return Promise.resolve({ id: 3, code: 'en-CA', name: 'Canadien' });
|
||||||
|
}
|
||||||
|
if (url.includes('/i18n/locales')) return Promise.resolve([]);
|
||||||
|
|
||||||
|
return Promise.resolve([
|
||||||
|
{ code: 'fr-FR', name: 'Francais' },
|
||||||
|
{ code: 'en-EN', name: 'English' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
render(
|
||||||
|
<TestWrapper>
|
||||||
|
<LocaleSettingsPage />
|
||||||
|
</TestWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Settings.list.actions.add'));
|
||||||
|
|
||||||
|
const confirmationButton = await waitFor(() =>
|
||||||
|
screen.getByText(`Settings.locales.modal.create.confirmation`)
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(confirmationButton);
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(strapi.notification.toggle).toBeCalledWith({
|
||||||
|
type: 'success',
|
||||||
|
message: { id: 'Settings.locales.modal.create.success' },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
import { request } from 'strapi-helper-plugin';
|
||||||
|
import { useMutation, useQueryClient } from 'react-query';
|
||||||
|
import { getTrad } from '../../utils';
|
||||||
|
|
||||||
|
const addLocale = async ({ code, name }) => {
|
||||||
|
try {
|
||||||
|
const data = await request(`/i18n/locales`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
name,
|
||||||
|
code,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
strapi.notification.toggle({
|
||||||
|
type: 'success',
|
||||||
|
message: { id: getTrad('Settings.locales.modal.create.success') },
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
strapi.notification.toggle({
|
||||||
|
type: 'warning',
|
||||||
|
message: { id: 'notification.error' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useAddLocale = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { isLoading, mutateAsync } = useMutation(addLocale, {
|
||||||
|
onSuccess: data => queryClient.setQueryData(['locales', { id: data.id }], data),
|
||||||
|
});
|
||||||
|
|
||||||
|
return { isAdding: isLoading, addLocale: mutateAsync };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useAddLocale;
|
@ -1,7 +1,7 @@
|
|||||||
import { object, string } from 'yup';
|
import { object, string } from 'yup';
|
||||||
|
|
||||||
const localeFormSchema = object().shape({
|
const localeFormSchema = object().shape({
|
||||||
displayName: string().max(50, 'Settings.locales.modal.create.locales.displayName.error'),
|
displayName: string().max(50, 'Settings.locales.modal.locales.displayName.error'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default localeFormSchema;
|
export default localeFormSchema;
|
||||||
|
@ -30,18 +30,15 @@
|
|||||||
"Settings.locales.modal.delete.success": "Locale successfully deleted",
|
"Settings.locales.modal.delete.success": "Locale successfully deleted",
|
||||||
"Settings.locales.modal.edit.confirmation": "Finish",
|
"Settings.locales.modal.edit.confirmation": "Finish",
|
||||||
"Settings.locales.modal.edit.success": "Locale successfully edited",
|
"Settings.locales.modal.edit.success": "Locale successfully edited",
|
||||||
"Settings.locales.modal.edit.locales.label": "Locales",
|
|
||||||
"Settings.locales.modal.edit.locales.displayName": "Locale display name",
|
|
||||||
"Settings.locales.modal.edit.locales.displayName.description":"Locale will be displayed under that name in the administration panel",
|
|
||||||
"Settings.locales.modal.edit.tab.label":"Navigating between the I18N base settings and advanced settings",
|
"Settings.locales.modal.edit.tab.label":"Navigating between the I18N base settings and advanced settings",
|
||||||
"Settings.locales.modal.base": "Base settings",
|
"Settings.locales.modal.base": "Base settings",
|
||||||
"Settings.locales.modal.advanced": "Advanced settings",
|
"Settings.locales.modal.advanced": "Advanced settings",
|
||||||
|
"Settings.locales.modal.create.defaultLocales.loading": "Loading the available locales...",
|
||||||
"Settings.locales.modal.create.confirmation": "Add locale",
|
"Settings.locales.modal.create.confirmation": "Add locale",
|
||||||
"Settings.locales.modal.create.success": "Locale successfully added",
|
"Settings.locales.modal.create.success": "Locale successfully added",
|
||||||
"Settings.locales.modal.create.locales.label": "Locales",
|
"Settings.locales.modal.locales.label": "Locales",
|
||||||
"Settings.locales.modal.create.locales.displayName": "Locale display name",
|
"Settings.locales.modal.locales.displayName": "Locale display name",
|
||||||
"Settings.locales.modal.locales.displayName.error": "The locale display name can only be less than 50 characters.",
|
"Settings.locales.modal.locales.displayName.error": "The locale display name can only be less than 50 characters.",
|
||||||
"Settings.locales.modal.create.locales.displayName.description":"Locale will be displayed under that name in the administration panel",
|
"Settings.locales.modal.locales.displayName.description":"Locale will be displayed under that name in the administration panel",
|
||||||
"Settings.locales.modal.create.tab.label":"Navigating between the I18N base settings and advanced settings"
|
"Settings.locales.modal.create.tab.label":"Navigating between the I18N base settings and advanced settings"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user