@akemona-org/strapi-plugin-i18n 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +19 -0
- package/admin/src/assets/images/logo.svg +1 -0
- package/admin/src/components/CMEditViewCopyLocale/index.js +183 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/cleanData.js +36 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/generateOptions.js +22 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/index.js +2 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/removePasswordAndRelationsFieldFromData.js +54 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/cleanData.test.js +83 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/data.js +219 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/generateOptions.test.js +79 -0
- package/admin/src/components/CMEditViewCopyLocale/utils/tests/removePasswordAndRelationsFieldFromData.test.js +40 -0
- package/admin/src/components/CMEditViewInjectedComponents/index.js +58 -0
- package/admin/src/components/CMEditViewLocalePicker/Option.js +66 -0
- package/admin/src/components/CMEditViewLocalePicker/Wrapper.js +8 -0
- package/admin/src/components/CMEditViewLocalePicker/index.js +160 -0
- package/admin/src/components/CMEditViewLocalePicker/utils/addStatusColorToLocale.js +24 -0
- package/admin/src/components/CMEditViewLocalePicker/utils/createLocalesOption.js +20 -0
- package/admin/src/components/CMEditViewLocalePicker/utils/index.js +2 -0
- package/admin/src/components/CheckboxConfirmation/Wrapper.js +12 -0
- package/admin/src/components/CheckboxConfirmation/index.js +70 -0
- package/admin/src/components/DeleteModalAdditionalInfos/index.js +25 -0
- package/admin/src/components/LocaleList/index.js +101 -0
- package/admin/src/components/LocaleListCell/LocaleListCell.js +90 -0
- package/admin/src/components/LocaleListCell/tests/LocaleListCell.test.js +128 -0
- package/admin/src/components/LocalePicker/index.js +126 -0
- package/admin/src/components/LocaleRow/index.js +77 -0
- package/admin/src/components/ModalCreate/AdvancedForm.js +45 -0
- package/admin/src/components/ModalCreate/BaseForm.js +103 -0
- package/admin/src/components/ModalCreate/index.js +136 -0
- package/admin/src/components/ModalDelete/index.js +49 -0
- package/admin/src/components/ModalEdit/AdvancedForm.js +51 -0
- package/admin/src/components/ModalEdit/BaseForm.js +91 -0
- package/admin/src/components/ModalEdit/index.js +122 -0
- package/admin/src/components/SettingsModal.js +66 -0
- package/admin/src/components/index.js +2 -0
- package/admin/src/containers/Initializer.js +31 -0
- package/admin/src/containers/SettingsPage/LocaleSettingsPage.js +69 -0
- package/admin/src/containers/SettingsPage/index.js +33 -0
- package/admin/src/containers/SettingsPage/tests/SettingsPage.test.js +744 -0
- package/admin/src/containers/SettingsPage/tests/__snapshots__/SettingsPage.test.js.snap +241 -0
- package/admin/src/hooks/constants.js +6 -0
- package/admin/src/hooks/reducers.js +63 -0
- package/admin/src/hooks/tests/reducers.test.js +203 -0
- package/admin/src/hooks/useAddLocale/index.js +60 -0
- package/admin/src/hooks/useContentTypePermissions/index.js +16 -0
- package/admin/src/hooks/useDefaultLocales/index.js +27 -0
- package/admin/src/hooks/useDeleteLocale/index.js +45 -0
- package/admin/src/hooks/useEditLocale/index.js +46 -0
- package/admin/src/hooks/useHasI18n/index.js +13 -0
- package/admin/src/hooks/useLocales/index.js +35 -0
- package/admin/src/index.js +169 -0
- package/admin/src/middlewares/addCommonFieldsToInitialDataMiddleware.js +83 -0
- package/admin/src/middlewares/addLocaleColumnToListViewMiddleware.js +32 -0
- package/admin/src/middlewares/addLocaleToCollectionTypesMiddleware.js +25 -0
- package/admin/src/middlewares/addLocaleToSingleTypesMiddleware.js +25 -0
- package/admin/src/middlewares/extendCMEditViewLayoutMiddleware.js +159 -0
- package/admin/src/middlewares/extendCTBAttributeInitialDataMiddleware.js +58 -0
- package/admin/src/middlewares/extendCTBInitialDataMiddleware.js +33 -0
- package/admin/src/middlewares/index.js +21 -0
- package/admin/src/middlewares/localePermissionMiddleware.js +39 -0
- package/admin/src/middlewares/tests/addCommonFieldsToInitialDataMiddleware.test.js +97 -0
- package/admin/src/middlewares/tests/addLocaleColumnToListViewMiddleware.test.js +68 -0
- package/admin/src/middlewares/tests/addLocaleToCollectionTypesMiddleware.test.js +200 -0
- package/admin/src/middlewares/tests/addLocaleToSingleTypesMiddleware.test.js +193 -0
- package/admin/src/middlewares/tests/extendCMEditViewLayoutMiddleware.test.js +556 -0
- package/admin/src/middlewares/tests/extendCTBAttrributeInitialDataMiddleware.test.js +124 -0
- package/admin/src/middlewares/tests/extendCTBInitialDataMiddleware.test.js +92 -0
- package/admin/src/middlewares/tests/localePermissionMiddleware.test.js +150 -0
- package/admin/src/middlewares/utils/addLocaleToLinksSearch.js +56 -0
- package/admin/src/middlewares/utils/tests/addLocaleToLinksSearch.test.js +137 -0
- package/admin/src/permissions.js +9 -0
- package/admin/src/pluginId.js +5 -0
- package/admin/src/schemas.js +7 -0
- package/admin/src/selectors/selectCollectionTypesRelatedPermissions.js +4 -0
- package/admin/src/selectors/selectI18nLocales.js +3 -0
- package/admin/src/translations/en.json +60 -0
- package/admin/src/translations/fr.json +9 -0
- package/admin/src/translations/index.js +11 -0
- package/admin/src/translations/zh-Hans.json +60 -0
- package/admin/src/utils/getDefaultLocale.js +60 -0
- package/admin/src/utils/getInitialLocale.js +14 -0
- package/admin/src/utils/getLocaleFromQuery.js +7 -0
- package/admin/src/utils/getTrad.js +5 -0
- package/admin/src/utils/index.js +2 -0
- package/admin/src/utils/localizedFields.js +23 -0
- package/admin/src/utils/mutateCTBContentTypeSchema.js +66 -0
- package/admin/src/utils/tests/getDefaultLocale.test.js +337 -0
- package/admin/src/utils/tests/getInitialLocale.test.js +106 -0
- package/admin/src/utils/tests/mutateCTBContentTypeSchema.test.js +205 -0
- package/config/functions/bootstrap.js +57 -0
- package/config/functions/migrations/__tests__/content-type.test.js +255 -0
- package/config/functions/migrations/__tests__/field.test.js +150 -0
- package/config/functions/migrations/content-type/disable/index.js +34 -0
- package/config/functions/migrations/content-type/disable/migrate-for-bookshelf.js +58 -0
- package/config/functions/migrations/content-type/disable/migrate-for-mongoose.js +39 -0
- package/config/functions/migrations/content-type/enable/index.js +40 -0
- package/config/functions/migrations/content-type/utils/index.js +27 -0
- package/config/functions/migrations/field/__tests__/utils.test.js +53 -0
- package/config/functions/migrations/field/index.js +37 -0
- package/config/functions/migrations/field/migrate-for-bookshelf.js +72 -0
- package/config/functions/migrations/field/migrate-for-mongoose.js +24 -0
- package/config/functions/migrations/field/migrate.js +55 -0
- package/config/functions/migrations/field/utils.js +58 -0
- package/config/functions/register.js +46 -0
- package/config/policies/validateLocaleCreation.js +68 -0
- package/config/routes.json +64 -0
- package/constants/__tests__/index.test.js +27 -0
- package/constants/index.js +36 -0
- package/constants/iso-locales.json +2002 -0
- package/controllers/__tests__/content-types.test.js +113 -0
- package/controllers/__tests__/iso-locales.test.js +26 -0
- package/controllers/__tests__/locales.test.js +308 -0
- package/controllers/content-types.js +64 -0
- package/controllers/iso-locales.js +11 -0
- package/controllers/locales.js +104 -0
- package/domain/locale.js +10 -0
- package/middlewares/i18n/defaults.json +5 -0
- package/middlewares/i18n/index.js +28 -0
- package/models/Locale.settings.json +31 -0
- package/oas.yml +195 -0
- package/package.json +31 -0
- package/services/__tests__/__snapshots__/iso-locales.test.js.snap +2006 -0
- package/services/__tests__/content-types.test.js +545 -0
- package/services/__tests__/core-api.test.js +106 -0
- package/services/__tests__/entity-service-decorator.test.js +280 -0
- package/services/__tests__/iso-locales.test.js +11 -0
- package/services/__tests__/locales.test.js +237 -0
- package/services/__tests__/localizations.test.js +187 -0
- package/services/__tests__/metrics.test.js +90 -0
- package/services/content-types.js +200 -0
- package/services/core-api.js +296 -0
- package/services/entity-service-decorator.js +155 -0
- package/services/iso-locales.js +9 -0
- package/services/locales.js +97 -0
- package/services/localizations.js +65 -0
- package/services/metrics.js +24 -0
- package/services/permissions/actions.js +124 -0
- package/services/permissions/engine.js +63 -0
- package/services/permissions/sections-builder.js +48 -0
- package/services/permissions.js +11 -0
- package/tests/content-manager/list-relation.test.e2e.js +122 -0
- package/tests/graphql.test.e2e.js +120 -0
- package/tests/locales.test.e2e.js +414 -0
- package/utils/index.js +20 -0
- package/validation/content-types.js +30 -0
- package/validation/locales.js +39 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Padded, Text } from '@buffetjs/core';
|
|
4
|
+
import { Tooltip } from '@buffetjs/styles';
|
|
5
|
+
import get from 'lodash/get';
|
|
6
|
+
import styled from 'styled-components';
|
|
7
|
+
|
|
8
|
+
const mapToLocaleName = (locales, localeCode) =>
|
|
9
|
+
get(
|
|
10
|
+
locales.find(({ code }) => code === localeCode),
|
|
11
|
+
'name',
|
|
12
|
+
localeCode
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const LocaleName = styled.div`
|
|
16
|
+
text-overflow: ellipsis;
|
|
17
|
+
overflow: hidden;
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const LocaleListCell = ({ locales, localizations, locale: currentLocaleCode, id }) => {
|
|
22
|
+
const allLocalizations = [{ locale: currentLocaleCode }, ...localizations];
|
|
23
|
+
const localizationNames = allLocalizations.map(locale => locale.locale);
|
|
24
|
+
const defaultLocale = locales.find(locale => locale.isDefault);
|
|
25
|
+
const hasDefaultLocale = localizationNames.includes(defaultLocale.code);
|
|
26
|
+
|
|
27
|
+
let localesArray = [];
|
|
28
|
+
|
|
29
|
+
if (hasDefaultLocale) {
|
|
30
|
+
const ctLocalesWithoutDefault = localizationNames.filter(
|
|
31
|
+
locale => locale !== defaultLocale.code
|
|
32
|
+
);
|
|
33
|
+
const ctLocalesNamesWithoutDefault = ctLocalesWithoutDefault.map(locale =>
|
|
34
|
+
mapToLocaleName(locales, locale)
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
ctLocalesNamesWithoutDefault.sort();
|
|
38
|
+
|
|
39
|
+
const ctLocalesNamesWithDefault = [
|
|
40
|
+
`${defaultLocale.name} (default)`,
|
|
41
|
+
...ctLocalesNamesWithoutDefault,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
localesArray = ctLocalesNamesWithDefault;
|
|
45
|
+
} else {
|
|
46
|
+
const ctLocales = localizationNames.map(locale => mapToLocaleName(locales, locale));
|
|
47
|
+
ctLocales.sort();
|
|
48
|
+
|
|
49
|
+
localesArray = ctLocales;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const elId = `entry-${id}__locale`;
|
|
53
|
+
const localesNames = localesArray.join(', ');
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div>
|
|
57
|
+
<LocaleName data-for={elId} data-tip={localesNames}>
|
|
58
|
+
{localesNames}
|
|
59
|
+
</LocaleName>
|
|
60
|
+
<Tooltip id={elId} place="bottom" delay={0}>
|
|
61
|
+
{localesArray.map(name => (
|
|
62
|
+
<Padded key={name} top bottom size="xs">
|
|
63
|
+
<Text ellipsis color="white">
|
|
64
|
+
{name}
|
|
65
|
+
</Text>
|
|
66
|
+
</Padded>
|
|
67
|
+
))}
|
|
68
|
+
</Tooltip>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
LocaleListCell.propTypes = {
|
|
74
|
+
id: PropTypes.number.isRequired,
|
|
75
|
+
localizations: PropTypes.arrayOf(
|
|
76
|
+
PropTypes.shape({
|
|
77
|
+
locale: PropTypes.string.isRequired,
|
|
78
|
+
})
|
|
79
|
+
).isRequired,
|
|
80
|
+
locales: PropTypes.arrayOf(
|
|
81
|
+
PropTypes.shape({
|
|
82
|
+
name: PropTypes.string.isRequired,
|
|
83
|
+
code: PropTypes.string.isRequired,
|
|
84
|
+
isDefault: PropTypes.bool,
|
|
85
|
+
})
|
|
86
|
+
).isRequired,
|
|
87
|
+
locale: PropTypes.string.isRequired,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default LocaleListCell;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import LocaleListCell from '../LocaleListCell';
|
|
4
|
+
|
|
5
|
+
jest.mock('@buffetjs/styles', () => ({
|
|
6
|
+
Tooltip: () => null,
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock('@buffetjs/core', () => ({
|
|
10
|
+
Padded: props => <div {...props} />,
|
|
11
|
+
Text: props => <p {...props} />,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('LocaleListCell', () => {
|
|
15
|
+
it('returns the default locale first, then the others sorted alphabetically', () => {
|
|
16
|
+
const locales = [
|
|
17
|
+
{
|
|
18
|
+
id: 1,
|
|
19
|
+
name: 'English',
|
|
20
|
+
code: 'en',
|
|
21
|
+
created_at: '2021-03-09T14:57:03.016Z',
|
|
22
|
+
updated_at: '2021-03-09T14:57:03.016Z',
|
|
23
|
+
isDefault: false,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 2,
|
|
27
|
+
name: 'French',
|
|
28
|
+
code: 'fr-FR',
|
|
29
|
+
created_at: '2021-03-09T15:03:06.992Z',
|
|
30
|
+
updated_at: '2021-03-17T13:01:03.569Z',
|
|
31
|
+
isDefault: true,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 3,
|
|
35
|
+
name: 'Arabic',
|
|
36
|
+
code: 'ar',
|
|
37
|
+
created_at: '2021-03-09T15:03:06.992Z',
|
|
38
|
+
updated_at: '2021-03-17T13:01:03.569Z',
|
|
39
|
+
isDefault: false,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const locale = 'en';
|
|
44
|
+
const localizations = [{ locale: 'fr-FR' }, { locale: 'ar' }];
|
|
45
|
+
|
|
46
|
+
render(
|
|
47
|
+
<LocaleListCell id={12} locales={locales} locale={locale} localizations={localizations} />
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(screen.getByText('French (default), Arabic, English')).toBeVisible();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('returns the "ar" when there s 2 locales available', () => {
|
|
54
|
+
const locales = [
|
|
55
|
+
{
|
|
56
|
+
id: 1,
|
|
57
|
+
name: 'English',
|
|
58
|
+
code: 'en',
|
|
59
|
+
created_at: '2021-03-09T14:57:03.016Z',
|
|
60
|
+
updated_at: '2021-03-09T14:57:03.016Z',
|
|
61
|
+
isDefault: false,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 2,
|
|
65
|
+
name: 'French',
|
|
66
|
+
code: 'fr-FR',
|
|
67
|
+
created_at: '2021-03-09T15:03:06.992Z',
|
|
68
|
+
updated_at: '2021-03-17T13:01:03.569Z',
|
|
69
|
+
isDefault: true,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 3,
|
|
73
|
+
name: 'Arabic',
|
|
74
|
+
code: 'ar',
|
|
75
|
+
created_at: '2021-03-09T15:03:06.992Z',
|
|
76
|
+
updated_at: '2021-03-17T13:01:03.569Z',
|
|
77
|
+
isDefault: false,
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const locale = 'en';
|
|
82
|
+
const localizations = [{ locale: 'ar' }];
|
|
83
|
+
|
|
84
|
+
render(
|
|
85
|
+
<LocaleListCell id={12} locales={locales} locale={locale} localizations={localizations} />
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(screen.getByText('Arabic, English')).toBeVisible();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('returns the "ar" and "en" locales alphabetically sorted', () => {
|
|
92
|
+
const locales = [
|
|
93
|
+
{
|
|
94
|
+
id: 1,
|
|
95
|
+
name: 'English',
|
|
96
|
+
code: 'en',
|
|
97
|
+
created_at: '2021-03-09T14:57:03.016Z',
|
|
98
|
+
updated_at: '2021-03-09T14:57:03.016Z',
|
|
99
|
+
isDefault: false,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 2,
|
|
103
|
+
name: 'French',
|
|
104
|
+
code: 'fr-FR',
|
|
105
|
+
created_at: '2021-03-09T15:03:06.992Z',
|
|
106
|
+
updated_at: '2021-03-17T13:01:03.569Z',
|
|
107
|
+
isDefault: true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 3,
|
|
111
|
+
name: 'Arabic',
|
|
112
|
+
code: 'ar',
|
|
113
|
+
created_at: '2021-03-09T15:03:06.992Z',
|
|
114
|
+
updated_at: '2021-03-17T13:01:03.569Z',
|
|
115
|
+
isDefault: false,
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const locale = 'fr-FR';
|
|
120
|
+
const localizations = [{ locale: 'en' }, { locale: 'ar' }];
|
|
121
|
+
|
|
122
|
+
render(
|
|
123
|
+
<LocaleListCell id={12} locales={locales} locale={locale} localizations={localizations} />
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect(screen.getByText('French (default), Arabic, English')).toBeVisible();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useSelector, useDispatch } from 'react-redux';
|
|
3
|
+
import { Picker, Padded, Text, Flex } from '@buffetjs/core';
|
|
4
|
+
import { Carret, useQueryParams } from 'strapi-helper-plugin';
|
|
5
|
+
import { useRouteMatch } from 'react-router-dom';
|
|
6
|
+
import styled from 'styled-components';
|
|
7
|
+
import get from 'lodash/get';
|
|
8
|
+
import useContentTypePermissions from '../../hooks/useContentTypePermissions';
|
|
9
|
+
import useHasI18n from '../../hooks/useHasI18n';
|
|
10
|
+
import selectI18NLocales from '../../selectors/selectI18nLocales';
|
|
11
|
+
import getInitialLocale from '../../utils/getInitialLocale';
|
|
12
|
+
|
|
13
|
+
const List = styled.ul`
|
|
14
|
+
list-style-type: none;
|
|
15
|
+
padding: 3px 0;
|
|
16
|
+
margin: 0;
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
const ListItem = styled.li`
|
|
20
|
+
margin-top: 0;
|
|
21
|
+
margin-bottom: 0;
|
|
22
|
+
margin-left: -10px;
|
|
23
|
+
margin-right: -10px;
|
|
24
|
+
padding-left: 10px;
|
|
25
|
+
padding-right: 10px;
|
|
26
|
+
height: 36px;
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
|
|
30
|
+
&:hover {
|
|
31
|
+
background: ${props => props.theme.main.colors.mediumGrey};
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
const EllipsisParagraph = styled(Text)`
|
|
36
|
+
width: ${props => props.width};
|
|
37
|
+
text-overflow: ellipsis;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
white-space: nowrap;
|
|
40
|
+
text-align: left;
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const LocalePicker = () => {
|
|
44
|
+
const dispatch = useDispatch();
|
|
45
|
+
const locales = useSelector(selectI18NLocales);
|
|
46
|
+
const [{ query }, setQuery] = useQueryParams();
|
|
47
|
+
const {
|
|
48
|
+
params: { slug },
|
|
49
|
+
} = useRouteMatch('/plugins/content-manager/collectionType/:slug');
|
|
50
|
+
const isFieldLocalized = useHasI18n();
|
|
51
|
+
const { createPermissions, readPermissions } = useContentTypePermissions(slug);
|
|
52
|
+
|
|
53
|
+
const initialLocale = getInitialLocale(query, locales);
|
|
54
|
+
const [selected, setSelected] = useState(initialLocale);
|
|
55
|
+
|
|
56
|
+
if (!isFieldLocalized) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!locales || locales.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const displayedLocales = locales.filter(locale => {
|
|
65
|
+
const canCreate = createPermissions.find(({ properties }) => {
|
|
66
|
+
return get(properties, 'locales', []).includes(locale.code);
|
|
67
|
+
});
|
|
68
|
+
const canRead = readPermissions.find(({ properties }) =>
|
|
69
|
+
get(properties, 'locales', []).includes(locale.code)
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return canCreate || canRead;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Picker
|
|
77
|
+
position="right"
|
|
78
|
+
renderButtonContent={isOpen => (
|
|
79
|
+
<Flex>
|
|
80
|
+
<EllipsisParagraph width="20ch">{selected.name}</EllipsisParagraph>
|
|
81
|
+
|
|
82
|
+
<Padded left size="sm">
|
|
83
|
+
<Carret fill={isOpen ? '#007eff' : '#292b2c'} isUp={isOpen} />
|
|
84
|
+
</Padded>
|
|
85
|
+
</Flex>
|
|
86
|
+
)}
|
|
87
|
+
renderSectionContent={onToggle => {
|
|
88
|
+
const handleClick = locale => {
|
|
89
|
+
dispatch({ type: 'ContentManager/RBACManager/RESET_PERMISSIONS' });
|
|
90
|
+
setSelected(locale);
|
|
91
|
+
|
|
92
|
+
setQuery({
|
|
93
|
+
plugins: { ...query.plugins, i18n: { locale: locale.code } },
|
|
94
|
+
});
|
|
95
|
+
onToggle();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const hasMultipleLocales = displayedLocales.length > 1;
|
|
99
|
+
|
|
100
|
+
return hasMultipleLocales ? (
|
|
101
|
+
<Padded left right>
|
|
102
|
+
<List>
|
|
103
|
+
{displayedLocales.map(locale => {
|
|
104
|
+
if (locale.id === selected.id) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<ListItem key={locale.id}>
|
|
110
|
+
<button onClick={() => handleClick(locale)} type="button">
|
|
111
|
+
<EllipsisParagraph width="200px">
|
|
112
|
+
{locale.name || locale.code}
|
|
113
|
+
</EllipsisParagraph>
|
|
114
|
+
</button>
|
|
115
|
+
</ListItem>
|
|
116
|
+
);
|
|
117
|
+
})}
|
|
118
|
+
</List>
|
|
119
|
+
</Padded>
|
|
120
|
+
) : null;
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default LocalePicker;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
import { Pencil } from '@buffetjs/icons';
|
|
5
|
+
import { Text, IconLinks } from '@buffetjs/core';
|
|
6
|
+
import { CustomRow } from '@buffetjs/styles';
|
|
7
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
8
|
+
import { getTrad } from '../../utils';
|
|
9
|
+
|
|
10
|
+
const LocaleSettingsPage = ({ locale, onDelete, onEdit }) => {
|
|
11
|
+
const { formatMessage } = useIntl();
|
|
12
|
+
|
|
13
|
+
const links = [];
|
|
14
|
+
|
|
15
|
+
if (onEdit) {
|
|
16
|
+
links.push({
|
|
17
|
+
icon: (
|
|
18
|
+
<span aria-label={formatMessage({ id: getTrad('Settings.list.actions.edit') })}>
|
|
19
|
+
<Pencil fill="#0e1622" />
|
|
20
|
+
</span>
|
|
21
|
+
),
|
|
22
|
+
onClick: () => onEdit(locale),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (onDelete && !locale.isDefault) {
|
|
27
|
+
links.push({
|
|
28
|
+
icon: !locale.isDefault ? (
|
|
29
|
+
<span aria-label={formatMessage({ id: getTrad('Settings.list.actions.delete') })}>
|
|
30
|
+
<FontAwesomeIcon icon="trash-alt" />
|
|
31
|
+
</span>
|
|
32
|
+
) : null,
|
|
33
|
+
onClick: e => {
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
onDelete(locale);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<CustomRow onClick={() => onEdit(locale)}>
|
|
42
|
+
<td>
|
|
43
|
+
<Text>{locale.code}</Text>
|
|
44
|
+
</td>
|
|
45
|
+
<td>
|
|
46
|
+
<Text fontWeight="regular">{locale.name}</Text>
|
|
47
|
+
</td>
|
|
48
|
+
<td>
|
|
49
|
+
<Text>
|
|
50
|
+
{locale.isDefault
|
|
51
|
+
? formatMessage({ id: getTrad('Settings.locales.row.default-locale') })
|
|
52
|
+
: null}
|
|
53
|
+
</Text>
|
|
54
|
+
</td>
|
|
55
|
+
<td>
|
|
56
|
+
<IconLinks links={links} />
|
|
57
|
+
</td>
|
|
58
|
+
</CustomRow>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
LocaleSettingsPage.defaultProps = {
|
|
63
|
+
onDelete: undefined,
|
|
64
|
+
onEdit: undefined,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
LocaleSettingsPage.propTypes = {
|
|
68
|
+
locale: PropTypes.shape({
|
|
69
|
+
isDefault: PropTypes.bool,
|
|
70
|
+
name: PropTypes.string,
|
|
71
|
+
code: PropTypes.string.isRequired,
|
|
72
|
+
}).isRequired,
|
|
73
|
+
onDelete: PropTypes.func,
|
|
74
|
+
onEdit: PropTypes.func,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default LocaleSettingsPage;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text, Checkbox, Padded } from '@buffetjs/core';
|
|
3
|
+
import { useFormikContext } from 'formik';
|
|
4
|
+
import { useIntl } from 'react-intl';
|
|
5
|
+
import { BaselineAlignment } from 'strapi-helper-plugin';
|
|
6
|
+
import { getTrad } from '../../utils';
|
|
7
|
+
|
|
8
|
+
const AdvancedForm = () => {
|
|
9
|
+
const { values, setFieldValue } = useFormikContext();
|
|
10
|
+
const { formatMessage } = useIntl();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div>
|
|
14
|
+
<BaselineAlignment top size="2px" />
|
|
15
|
+
<Padded bottom size="sm">
|
|
16
|
+
<Text color="grey" textTransform="uppercase">
|
|
17
|
+
{formatMessage({
|
|
18
|
+
id: getTrad('Settings.locales.modal.advanced.settings'),
|
|
19
|
+
})}
|
|
20
|
+
</Text>
|
|
21
|
+
</Padded>
|
|
22
|
+
|
|
23
|
+
<BaselineAlignment top size="10px" />
|
|
24
|
+
<Checkbox
|
|
25
|
+
id="default-checkbox"
|
|
26
|
+
name="default-checkbox"
|
|
27
|
+
onChange={() => setFieldValue('isDefault', !values.isDefault)}
|
|
28
|
+
message={formatMessage({
|
|
29
|
+
id: getTrad('Settings.locales.modal.advanced.setAsDefault'),
|
|
30
|
+
})}
|
|
31
|
+
someChecked={false}
|
|
32
|
+
value={values.isDefault}
|
|
33
|
+
htmlFor="default-checkbox"
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<Text color="grey" fontSize="sm">
|
|
37
|
+
{formatMessage({
|
|
38
|
+
id: getTrad('Settings.locales.modal.advanced.setAsDefault.hint'),
|
|
39
|
+
})}
|
|
40
|
+
</Text>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default AdvancedForm;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Label } from '@buffetjs/core';
|
|
4
|
+
import { Inputs } from '@buffetjs/custom';
|
|
5
|
+
import Select, { createFilter } from 'react-select';
|
|
6
|
+
import { Col, Row } from 'reactstrap';
|
|
7
|
+
import { useIntl } from 'react-intl';
|
|
8
|
+
import { useTheme } from 'styled-components';
|
|
9
|
+
import { BaselineAlignment, selectStyles, DropdownIndicator } from 'strapi-helper-plugin';
|
|
10
|
+
import { useFormikContext } from 'formik';
|
|
11
|
+
import { getTrad } from '../../utils';
|
|
12
|
+
|
|
13
|
+
const reactSelectLocaleFilter = createFilter({
|
|
14
|
+
ignoreCase: true,
|
|
15
|
+
ignoreAccents: true,
|
|
16
|
+
matchFrom: 'start',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const BaseForm = ({ options, defaultOption }) => {
|
|
20
|
+
const theme = useTheme();
|
|
21
|
+
const { formatMessage } = useIntl();
|
|
22
|
+
const { values, handleChange, setFieldValue } = useFormikContext();
|
|
23
|
+
|
|
24
|
+
const styles = selectStyles(theme);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Row>
|
|
28
|
+
<Col>
|
|
29
|
+
<span id="locale-code">
|
|
30
|
+
<Label htmlFor="">
|
|
31
|
+
{formatMessage({
|
|
32
|
+
id: getTrad('Settings.locales.modal.locales.label'),
|
|
33
|
+
})}
|
|
34
|
+
</Label>
|
|
35
|
+
</span>
|
|
36
|
+
|
|
37
|
+
<BaselineAlignment top size="5px" />
|
|
38
|
+
|
|
39
|
+
<Select
|
|
40
|
+
aria-labelledby="locale-code"
|
|
41
|
+
options={options}
|
|
42
|
+
defaultValue={defaultOption}
|
|
43
|
+
filterOption={reactSelectLocaleFilter}
|
|
44
|
+
onChange={selection => {
|
|
45
|
+
setFieldValue('displayName', selection.value);
|
|
46
|
+
setFieldValue('code', selection.label);
|
|
47
|
+
}}
|
|
48
|
+
components={{ DropdownIndicator }}
|
|
49
|
+
styles={{
|
|
50
|
+
...styles,
|
|
51
|
+
control: (base, state) => ({ ...base, ...styles.control(base, state), height: '34px' }),
|
|
52
|
+
indicatorsContainer: (base, state) => ({
|
|
53
|
+
...base,
|
|
54
|
+
...styles.indicatorsContainer(base, state),
|
|
55
|
+
height: '32px',
|
|
56
|
+
}),
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
</Col>
|
|
60
|
+
|
|
61
|
+
<Col>
|
|
62
|
+
<BaselineAlignment top size="2px" />
|
|
63
|
+
|
|
64
|
+
<Inputs
|
|
65
|
+
label={formatMessage({
|
|
66
|
+
id: getTrad('Settings.locales.modal.locales.displayName'),
|
|
67
|
+
})}
|
|
68
|
+
name="displayName"
|
|
69
|
+
description={formatMessage({
|
|
70
|
+
id: getTrad('Settings.locales.modal.locales.displayName.description'),
|
|
71
|
+
})}
|
|
72
|
+
type="text"
|
|
73
|
+
value={values.displayName}
|
|
74
|
+
onChange={handleChange}
|
|
75
|
+
validations={{
|
|
76
|
+
max: 50,
|
|
77
|
+
}}
|
|
78
|
+
translatedErrors={{
|
|
79
|
+
max: formatMessage({
|
|
80
|
+
id: getTrad('Settings.locales.modal.locales.displayName.error'),
|
|
81
|
+
}),
|
|
82
|
+
}}
|
|
83
|
+
/>
|
|
84
|
+
</Col>
|
|
85
|
+
</Row>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
BaseForm.defaultProps = {
|
|
90
|
+
defaultOption: undefined,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
BaseForm.propTypes = {
|
|
94
|
+
options: PropTypes.arrayOf(
|
|
95
|
+
PropTypes.exact({ value: PropTypes.string.isRequired, label: PropTypes.string.isRequired })
|
|
96
|
+
).isRequired,
|
|
97
|
+
defaultOption: PropTypes.exact({
|
|
98
|
+
value: PropTypes.string.isRequired,
|
|
99
|
+
label: PropTypes.string.isRequired,
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export default BaseForm;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { Modal, ModalFooter, TabPanel, useUser } from 'strapi-helper-plugin';
|
|
4
|
+
import { useIntl } from 'react-intl';
|
|
5
|
+
import { Button } from '@buffetjs/core';
|
|
6
|
+
import { Formik } from 'formik';
|
|
7
|
+
import localeFormSchema from '../../schemas';
|
|
8
|
+
import { getTrad } from '../../utils';
|
|
9
|
+
import SettingsModal from '../SettingsModal';
|
|
10
|
+
import useDefaultLocales from '../../hooks/useDefaultLocales';
|
|
11
|
+
import useAddLocale from '../../hooks/useAddLocale';
|
|
12
|
+
import BaseForm from './BaseForm';
|
|
13
|
+
import AdvancedForm from './AdvancedForm';
|
|
14
|
+
|
|
15
|
+
const ModalCreate = ({ alreadyUsedLocales, onClose, isOpened }) => {
|
|
16
|
+
const { defaultLocales, isLoading } = useDefaultLocales();
|
|
17
|
+
const { isAdding, addLocale } = useAddLocale();
|
|
18
|
+
const { formatMessage } = useIntl();
|
|
19
|
+
|
|
20
|
+
const { fetchUserPermissions } = useUser();
|
|
21
|
+
const shouldUpdatePermissions = useRef(false);
|
|
22
|
+
|
|
23
|
+
if (isLoading) {
|
|
24
|
+
return (
|
|
25
|
+
<div>
|
|
26
|
+
<p>
|
|
27
|
+
{formatMessage({ id: getTrad('Settings.locales.modal.create.defaultLocales.loading') })}
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handleClosed = async () => {
|
|
34
|
+
if (shouldUpdatePermissions.current) {
|
|
35
|
+
await fetchUserPermissions();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
shouldUpdatePermissions.current = true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const options = (defaultLocales || [])
|
|
42
|
+
.map(locale => ({
|
|
43
|
+
label: locale.code,
|
|
44
|
+
value: locale.name,
|
|
45
|
+
}))
|
|
46
|
+
.filter(({ label }) => {
|
|
47
|
+
const foundLocale = alreadyUsedLocales.find(({ code }) => code === label);
|
|
48
|
+
|
|
49
|
+
return !foundLocale;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const defaultOption = options[0];
|
|
53
|
+
|
|
54
|
+
if (!defaultOption) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<Modal isOpen={isOpened} onToggle={onClose} withoverflow="true" onClosed={handleClosed}>
|
|
60
|
+
<Formik
|
|
61
|
+
initialValues={{
|
|
62
|
+
code: defaultOption.label,
|
|
63
|
+
displayName: defaultOption.value,
|
|
64
|
+
isDefault: false,
|
|
65
|
+
}}
|
|
66
|
+
onSubmit={values =>
|
|
67
|
+
addLocale({
|
|
68
|
+
code: values.code,
|
|
69
|
+
name: values.displayName,
|
|
70
|
+
isDefault: values.isDefault,
|
|
71
|
+
})
|
|
72
|
+
.then(() => {
|
|
73
|
+
shouldUpdatePermissions.current = true;
|
|
74
|
+
})
|
|
75
|
+
.then(() => {
|
|
76
|
+
onClose();
|
|
77
|
+
})}
|
|
78
|
+
validationSchema={localeFormSchema}
|
|
79
|
+
>
|
|
80
|
+
{({ handleSubmit, errors }) => (
|
|
81
|
+
<form onSubmit={handleSubmit}>
|
|
82
|
+
<SettingsModal
|
|
83
|
+
title={formatMessage({
|
|
84
|
+
id: getTrad('Settings.locales.modal.title'),
|
|
85
|
+
})}
|
|
86
|
+
breadCrumb={[formatMessage({ id: getTrad('Settings.list.actions.add') })]}
|
|
87
|
+
tabsAriaLabel={formatMessage({
|
|
88
|
+
id: getTrad('Settings.locales.modal.create.tab.label'),
|
|
89
|
+
})}
|
|
90
|
+
tabsId="i18n-settings-tabs-create"
|
|
91
|
+
>
|
|
92
|
+
<TabPanel>
|
|
93
|
+
<BaseForm
|
|
94
|
+
options={options}
|
|
95
|
+
defaultOption={defaultOption}
|
|
96
|
+
alreadyUsedLocales={alreadyUsedLocales}
|
|
97
|
+
/>
|
|
98
|
+
</TabPanel>
|
|
99
|
+
<TabPanel>
|
|
100
|
+
<AdvancedForm />
|
|
101
|
+
</TabPanel>
|
|
102
|
+
</SettingsModal>
|
|
103
|
+
|
|
104
|
+
<ModalFooter>
|
|
105
|
+
<section>
|
|
106
|
+
<Button type="button" color="cancel" onClick={onClose}>
|
|
107
|
+
{formatMessage({ id: 'app.components.Button.cancel' })}
|
|
108
|
+
</Button>
|
|
109
|
+
<Button
|
|
110
|
+
color="success"
|
|
111
|
+
type="submit"
|
|
112
|
+
isLoading={isAdding}
|
|
113
|
+
disabled={Object.keys(errors).length > 0}
|
|
114
|
+
>
|
|
115
|
+
{formatMessage({ id: getTrad('Settings.locales.modal.create.confirmation') })}
|
|
116
|
+
</Button>
|
|
117
|
+
</section>
|
|
118
|
+
</ModalFooter>
|
|
119
|
+
</form>
|
|
120
|
+
)}
|
|
121
|
+
</Formik>
|
|
122
|
+
</Modal>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
ModalCreate.defaultProps = {
|
|
127
|
+
alreadyUsedLocales: [],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
ModalCreate.propTypes = {
|
|
131
|
+
alreadyUsedLocales: PropTypes.array,
|
|
132
|
+
onClose: PropTypes.func.isRequired,
|
|
133
|
+
isOpened: PropTypes.bool.isRequired,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export default ModalCreate;
|