mirror of
https://github.com/strapi/strapi.git
synced 2025-09-19 05:23: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 { getTrad } from '../../utils';
|
||||
import SettingsModal from '../SettingsModal';
|
||||
import useDefaultLocales from '../../hooks/useDefaultLocales';
|
||||
import useAddLocale from '../../hooks/useAddLocale';
|
||||
import BaseForm from './BaseForm';
|
||||
|
||||
const ModalCreate = ({ onClose, isOpened }) => {
|
||||
const { defaultLocales, isLoading } = useDefaultLocales();
|
||||
const { isAdding, addLocale } = useAddLocale();
|
||||
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 (
|
||||
<Modal isOpen={isOpened} onToggle={onClose}>
|
||||
<Formik
|
||||
initialValues={{ displayName: '' }}
|
||||
onSubmit={() => null}
|
||||
initialValues={{ code: defaultOption.label, displayName: defaultOption.value }}
|
||||
onSubmit={values =>
|
||||
addLocale({ code: values.code, name: values.displayName }).then(onClose)}
|
||||
validationSchema={localeFormSchema}
|
||||
>
|
||||
{({ handleSubmit, errors }) => (
|
||||
@ -30,7 +55,9 @@ const ModalCreate = ({ onClose, isOpened }) => {
|
||||
})}
|
||||
tabsId="i18n-settings-tabs-create"
|
||||
>
|
||||
<TabPanel>Base form</TabPanel>
|
||||
<TabPanel>
|
||||
<BaseForm options={options} defaultOption={defaultOption} />
|
||||
</TabPanel>
|
||||
<TabPanel>advanced</TabPanel>
|
||||
</SettingsModal>
|
||||
|
||||
@ -42,7 +69,7 @@ const ModalCreate = ({ onClose, isOpened }) => {
|
||||
<Button
|
||||
color="success"
|
||||
type="submit"
|
||||
isLoading={false}
|
||||
isLoading={isAdding}
|
||||
disabled={Object.keys(errors).length > 0}
|
||||
>
|
||||
{formatMessage({ id: getTrad('Settings.locales.modal.create.confirmation') })}
|
||||
|
@ -33,11 +33,11 @@ const BaseForm = ({ options, defaultOption }) => {
|
||||
<Col>
|
||||
<Inputs
|
||||
label={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.edit.locales.displayName'),
|
||||
id: getTrad('Settings.locales.modal.locales.displayName'),
|
||||
})}
|
||||
name="displayName"
|
||||
description={formatMessage({
|
||||
id: getTrad('Settings.locales.modal.edit.locales.displayName.description'),
|
||||
id: getTrad('Settings.locales.modal.locales.displayName.description'),
|
||||
})}
|
||||
type="text"
|
||||
value={values.displayName}
|
||||
|
@ -179,13 +179,13 @@ describe('i18n settings page', () => {
|
||||
const rowUtils = within(row);
|
||||
|
||||
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: {
|
||||
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.edit.locales.displayName'));
|
||||
fireEvent.blur(screen.getByLabelText('Settings.locales.modal.locales.displayName'));
|
||||
|
||||
await waitFor(() =>
|
||||
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.change(screen.getByLabelText('Settings.locales.modal.edit.locales.displayName'), {
|
||||
fireEvent.change(screen.getByLabelText('Settings.locales.modal.locales.displayName'), {
|
||||
target: {
|
||||
value: '',
|
||||
},
|
||||
@ -487,7 +487,17 @@ describe('i18n settings page', () => {
|
||||
});
|
||||
|
||||
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 () => {
|
||||
render(
|
||||
<TestWrapper>
|
||||
@ -497,7 +507,18 @@ describe('i18n settings page', () => {
|
||||
|
||||
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 () => {
|
||||
@ -508,9 +529,142 @@ describe('i18n settings page', () => {
|
||||
);
|
||||
|
||||
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'));
|
||||
|
||||
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';
|
||||
|
||||
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;
|
||||
|
@ -30,18 +30,15 @@
|
||||
"Settings.locales.modal.delete.success": "Locale successfully deleted",
|
||||
"Settings.locales.modal.edit.confirmation": "Finish",
|
||||
"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.base": "Base 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.success": "Locale successfully added",
|
||||
"Settings.locales.modal.create.locales.label": "Locales",
|
||||
"Settings.locales.modal.create.locales.displayName": "Locale display name",
|
||||
"Settings.locales.modal.locales.label": "Locales",
|
||||
"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.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"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user