@plone/volto 19.0.0-alpha.2 → 19.0.0-alpha.3
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/CHANGELOG.md +37 -0
- package/README.md +0 -2
- package/cypress/downloads/downloads.html +0 -0
- package/locales/af.json +1 -0
- package/locales/ar.json +1 -0
- package/locales/bg.json +1 -0
- package/locales/bn.json +1 -0
- package/locales/ca/LC_MESSAGES/volto.po +39 -9
- package/locales/ca.json +1 -1
- package/locales/cs.json +1 -0
- package/locales/cy.json +1 -0
- package/locales/da.json +1 -0
- package/locales/de/LC_MESSAGES/volto.po +47 -17
- package/locales/de.json +1 -1
- package/locales/el.json +1 -0
- package/locales/en/LC_MESSAGES/volto.po +40 -10
- package/locales/en.json +1 -1
- package/locales/en_AU.json +1 -0
- package/locales/en_GB.json +1 -0
- package/locales/eo.json +1 -0
- package/locales/es/LC_MESSAGES/volto.po +40 -10
- package/locales/es.json +1 -1
- package/locales/et.json +1 -0
- package/locales/eu/LC_MESSAGES/volto.po +39 -9
- package/locales/eu.json +1 -1
- package/locales/fa.json +1 -0
- package/locales/fi/LC_MESSAGES/volto.po +39 -9
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +40 -10
- package/locales/fr.json +1 -1
- package/locales/fu.json +1 -0
- package/locales/gl.json +1 -0
- package/locales/he.json +1 -0
- package/locales/hi/LC_MESSAGES/volto.po +43 -13
- package/locales/hi.json +1 -1
- package/locales/hr.json +1 -0
- package/locales/hu.json +1 -0
- package/locales/hy.json +1 -0
- package/locales/id.json +1 -0
- package/locales/it/LC_MESSAGES/volto.po +43 -13
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +39 -9
- package/locales/ja.json +1 -1
- package/locales/ka.json +1 -0
- package/locales/kn.json +1 -0
- package/locales/ko.json +1 -0
- package/locales/lt.json +1 -0
- package/locales/lv.json +1 -0
- package/locales/mi.json +1 -0
- package/locales/mk.json +1 -0
- package/locales/my.json +1 -0
- package/locales/nb_NO.json +1 -0
- package/locales/nl/LC_MESSAGES/volto.po +43 -13
- package/locales/nl.json +1 -1
- package/locales/nn.json +1 -0
- package/locales/pl.json +1 -0
- package/locales/pt/LC_MESSAGES/volto.po +39 -9
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +54 -24
- package/locales/pt_BR.json +1 -1
- package/locales/rm.json +1 -0
- package/locales/ro/LC_MESSAGES/volto.po +43 -13
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +43 -13
- package/locales/ru.json +1 -1
- package/locales/sk.json +1 -0
- package/locales/sl.json +1 -0
- package/locales/sm.json +1 -0
- package/locales/sq.json +1 -0
- package/locales/sr.json +1 -0
- package/locales/sr@cyrl.json +1 -0
- package/locales/sr@latn.json +1 -0
- package/locales/sv.json +1 -1
- package/locales/ta.json +1 -0
- package/locales/te.json +1 -0
- package/locales/th.json +1 -0
- package/locales/to.json +1 -0
- package/locales/tr.json +1 -0
- package/locales/uk.json +1 -0
- package/locales/vi.json +1 -0
- package/locales/volto.pot +40 -10
- package/locales/zh_CN/LC_MESSAGES/volto.po +39 -9
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant.json +1 -0
- package/locales/zh_Hant_HK.json +1 -0
- package/package.json +7 -7
- package/src/actions/content/content.js +0 -1
- package/src/actions/controlpanels/controlpanels.js +13 -7
- package/src/actions/controlpanels/controlpanels.test.js +11 -5
- package/src/actions/users/users.js +2 -2
- package/src/components/manage/Add/Add.jsx +5 -6
- package/src/components/manage/Blocks/Block/Edit.jsx +1 -0
- package/src/components/manage/Blocks/Teaser/schema.js +8 -3
- package/src/components/manage/Contents/Contents.jsx +3 -0
- package/src/components/manage/Contents/Contents.test.jsx +7 -0
- package/src/components/manage/Contents/ContentsBreadcrumbs.Multilingual.test.jsx +18 -5
- package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +20 -26
- package/src/components/manage/Contents/ContentsBreadcrumbs.test.jsx +14 -0
- package/src/components/manage/Contents/ContentsDeleteModal.jsx +258 -206
- package/src/components/manage/Contents/ContentsDeleteModal.stories.jsx +26 -8
- package/src/components/manage/Contents/ContentsItem.jsx +9 -1
- package/src/components/manage/Contents/ContentsUploadModal.test.jsx +13 -22
- package/src/components/manage/Edit/Edit.jsx +2 -3
- package/src/components/manage/Multilingual/CompareLanguages.jsx +2 -5
- package/src/components/manage/Multilingual/ManageTranslations.jsx +4 -2
- package/src/components/manage/Multilingual/ManageTranslations.test.jsx +5 -1
- package/src/components/manage/Multilingual/TranslationObject.jsx +1 -1
- package/src/components/manage/Toolbar/More.jsx +4 -1
- package/src/components/manage/Toolbar/More.test.jsx +3 -0
- package/src/components/manage/Toolbar/Toolbar.jsx +3 -4
- package/src/components/manage/Toolbar/Types.jsx +7 -7
- package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +1 -3
- package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +0 -4
- package/src/components/theme/App/App.jsx +3 -1
- package/src/components/theme/App/App.test.jsx +1 -0
- package/src/components/theme/FormattedDate/FormattedDate.stories.jsx +20 -2
- package/src/components/theme/LanguageSelector/LanguageSelector.jsx +9 -7
- package/src/components/theme/LanguageSelector/LanguageSelector.test.jsx +6 -6
- package/src/components/theme/Logo/Logo.Multilingual.test.jsx +0 -5
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +8 -12
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.test.jsx +3 -5
- package/src/components/theme/Navigation/NavItem.jsx +1 -5
- package/src/components/theme/Navigation/Navigation.Multilingual.test.jsx +0 -5
- package/src/components/theme/NotFound/NotFound.jsx +5 -2
- package/src/components/theme/NotFound/NotFound.test.jsx +3 -0
- package/src/components/theme/Sitemap/Sitemap.jsx +6 -5
- package/src/components/theme/Sitemap/Sitemap.test.jsx +0 -1
- package/src/components/theme/View/FileView.jsx +9 -1
- package/src/config/index.js +5 -14
- package/src/helpers/Html/Html.jsx +0 -3
- package/src/helpers/LanguageMap/LanguageMap.js +115 -8
- package/src/middleware/Api.test.js +4 -0
- package/src/middleware/api.js +74 -25
- package/src/routes.js +1 -1
- package/src/server.jsx +5 -6
- package/src/start-client.jsx +0 -4
- package/test-setup-config.jsx +0 -2
- package/theme/themes/pastanaga/extras/blocks.less +7 -0
- package/theme/themes/pastanaga/extras/contents.less +5 -5
- package/types/components/manage/Blocks/Teaser/schema.d.ts +1 -0
- package/types/components/theme/FormattedDate/FormattedDate.stories.d.ts +1 -1
- package/types/helpers/LanguageMap/LanguageMap.d.ts +428 -4
- package/types/middleware/api.d.ts +6 -9
- package/src/actions/content/content.multilingual.test.js +0 -17
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
|
-
import { waitFor } from '@testing-library/react';
|
|
6
5
|
import ContentsUploadModal from './ContentsUploadModal';
|
|
7
6
|
|
|
8
7
|
const mockStore = configureStore();
|
|
@@ -21,19 +20,17 @@ describe('ContentsUploadModal', () => {
|
|
|
21
20
|
messages: {},
|
|
22
21
|
},
|
|
23
22
|
});
|
|
24
|
-
const
|
|
23
|
+
const { baseElement } = render(
|
|
25
24
|
<Provider store={store}>
|
|
26
25
|
<ContentsUploadModal
|
|
27
26
|
pathname="/blog"
|
|
28
|
-
open={
|
|
27
|
+
open={true}
|
|
29
28
|
onOk={() => {}}
|
|
30
29
|
onCancel={() => {}}
|
|
31
30
|
/>
|
|
32
31
|
</Provider>,
|
|
33
32
|
);
|
|
34
|
-
|
|
35
|
-
await waitFor(() => {});
|
|
36
|
-
expect(json).toMatchSnapshot();
|
|
33
|
+
expect(baseElement).toMatchSnapshot();
|
|
37
34
|
});
|
|
38
35
|
|
|
39
36
|
it('renders a contents upload modal component that only allows images', async () => {
|
|
@@ -49,20 +46,18 @@ describe('ContentsUploadModal', () => {
|
|
|
49
46
|
messages: {},
|
|
50
47
|
},
|
|
51
48
|
});
|
|
52
|
-
const
|
|
49
|
+
const { baseElement } = render(
|
|
53
50
|
<Provider store={store}>
|
|
54
51
|
<ContentsUploadModal
|
|
55
52
|
pathname="/blog"
|
|
56
|
-
open={
|
|
53
|
+
open={true}
|
|
57
54
|
onOk={() => {}}
|
|
58
55
|
onCancel={() => {}}
|
|
59
56
|
accept={['image/*']}
|
|
60
57
|
/>
|
|
61
58
|
</Provider>,
|
|
62
59
|
);
|
|
63
|
-
|
|
64
|
-
await waitFor(() => {});
|
|
65
|
-
expect(json).toMatchSnapshot();
|
|
60
|
+
expect(baseElement).toMatchSnapshot();
|
|
66
61
|
});
|
|
67
62
|
it('renders a contents upload modal component that only allows 10MB files', async () => {
|
|
68
63
|
const store = mockStore({
|
|
@@ -77,20 +72,18 @@ describe('ContentsUploadModal', () => {
|
|
|
77
72
|
messages: {},
|
|
78
73
|
},
|
|
79
74
|
});
|
|
80
|
-
const
|
|
75
|
+
const { baseElement } = render(
|
|
81
76
|
<Provider store={store}>
|
|
82
77
|
<ContentsUploadModal
|
|
83
78
|
pathname="/blog"
|
|
84
|
-
open={
|
|
79
|
+
open={true}
|
|
85
80
|
onOk={() => {}}
|
|
86
81
|
onCancel={() => {}}
|
|
87
82
|
maxSize={1000000}
|
|
88
83
|
/>
|
|
89
84
|
</Provider>,
|
|
90
85
|
);
|
|
91
|
-
|
|
92
|
-
await waitFor(() => {});
|
|
93
|
-
expect(json).toMatchSnapshot();
|
|
86
|
+
expect(baseElement).toMatchSnapshot();
|
|
94
87
|
});
|
|
95
88
|
it('renders a contents upload modal component that only allows 1 file', async () => {
|
|
96
89
|
const store = mockStore({
|
|
@@ -105,19 +98,17 @@ describe('ContentsUploadModal', () => {
|
|
|
105
98
|
messages: {},
|
|
106
99
|
},
|
|
107
100
|
});
|
|
108
|
-
const
|
|
101
|
+
const { baseElement } = render(
|
|
109
102
|
<Provider store={store}>
|
|
110
103
|
<ContentsUploadModal
|
|
111
104
|
pathname="/blog"
|
|
112
|
-
open={
|
|
105
|
+
open={true}
|
|
113
106
|
onOk={() => {}}
|
|
114
107
|
onCancel={() => {}}
|
|
115
108
|
multiple={false}
|
|
116
109
|
/>
|
|
117
110
|
</Provider>,
|
|
118
111
|
);
|
|
119
|
-
|
|
120
|
-
await waitFor(() => {});
|
|
121
|
-
expect(json).toMatchSnapshot();
|
|
112
|
+
expect(baseElement).toMatchSnapshot();
|
|
122
113
|
});
|
|
123
114
|
});
|
|
@@ -44,8 +44,6 @@ import { tryParseJSON } from '@plone/volto/helpers/FormValidation/FormValidation
|
|
|
44
44
|
import saveSVG from '@plone/volto/icons/save.svg';
|
|
45
45
|
import clearSVG from '@plone/volto/icons/clear.svg';
|
|
46
46
|
|
|
47
|
-
import config from '@plone/volto/registry';
|
|
48
|
-
|
|
49
47
|
const messages = defineMessages({
|
|
50
48
|
edit: {
|
|
51
49
|
id: 'Edit {title}',
|
|
@@ -460,7 +458,7 @@ class Edit extends Component {
|
|
|
460
458
|
/>
|
|
461
459
|
</Button>
|
|
462
460
|
|
|
463
|
-
{
|
|
461
|
+
{this.props.isMultilingual && (
|
|
464
462
|
<CompareLanguages
|
|
465
463
|
content={this.props.content}
|
|
466
464
|
visual={this.state.visual}
|
|
@@ -545,6 +543,7 @@ export default compose(
|
|
|
545
543
|
updateRequest: state.content.update,
|
|
546
544
|
pathname: props.location.pathname,
|
|
547
545
|
returnUrl: qs.parse(props.location.search).return_url,
|
|
546
|
+
isMultilingual: state.site.data.features?.multilingual,
|
|
548
547
|
}),
|
|
549
548
|
{
|
|
550
549
|
updateContent,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { defineMessages, useIntl } from 'react-intl';
|
|
3
|
-
import config from '@plone/volto/registry';
|
|
4
3
|
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
5
4
|
import { useDetectClickOutside } from '@plone/volto/helpers/Utils/useDetectClickOutside';
|
|
6
5
|
|
|
@@ -106,16 +105,14 @@ const CompareLanguages = React.forwardRef((props, ref) => {
|
|
|
106
105
|
|
|
107
106
|
const intl = useIntl();
|
|
108
107
|
const [viewMenu, setViewMenu] = useState(false);
|
|
109
|
-
const translations =
|
|
110
|
-
? content?.['@components']?.translations?.items || []
|
|
111
|
-
: [];
|
|
108
|
+
const translations = content?.['@components']?.translations?.items || [];
|
|
112
109
|
|
|
113
110
|
const translationsObject = {};
|
|
114
111
|
translations.forEach((t) => {
|
|
115
112
|
translationsObject[t.language] = t['@id'];
|
|
116
113
|
});
|
|
117
114
|
|
|
118
|
-
if (
|
|
115
|
+
if (translations.length > 0) {
|
|
119
116
|
return (
|
|
120
117
|
<div className="toolbar-compare-translations-wrapper">
|
|
121
118
|
<div className="toolbar-button-spacer" />
|
|
@@ -8,7 +8,6 @@ import { Link, useLocation } from 'react-router-dom';
|
|
|
8
8
|
import Icon from '@plone/volto/components/theme/Icon/Icon';
|
|
9
9
|
import Toast from '@plone/volto/components/manage/Toast/Toast';
|
|
10
10
|
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
|
|
11
|
-
import config from '@plone/volto/registry';
|
|
12
11
|
|
|
13
12
|
import withObjectBrowser from '@plone/volto/components/manage/Sidebar/ObjectBrowser';
|
|
14
13
|
import {
|
|
@@ -66,6 +65,9 @@ const ManageTranslations = (props) => {
|
|
|
66
65
|
const pathname = useLocation().pathname;
|
|
67
66
|
const content = useSelector((state) => state.content.data);
|
|
68
67
|
const dispatch = useDispatch();
|
|
68
|
+
const availableLanguages = useSelector(
|
|
69
|
+
(state) => state.site.data['plone.available_languages'],
|
|
70
|
+
);
|
|
69
71
|
|
|
70
72
|
const [isClient, setIsClient] = useState(false);
|
|
71
73
|
|
|
@@ -189,7 +191,7 @@ const ManageTranslations = (props) => {
|
|
|
189
191
|
</Table.Row>
|
|
190
192
|
</Table.Header>
|
|
191
193
|
<Table.Body>
|
|
192
|
-
{
|
|
194
|
+
{availableLanguages.map((lang) => (
|
|
193
195
|
<Table.Row key={lang}>
|
|
194
196
|
<Table.Cell collapsing>
|
|
195
197
|
{lang === content.language.token ? (
|
|
@@ -8,7 +8,6 @@ import config from '@plone/volto/registry';
|
|
|
8
8
|
import ManageTranslations from './ManageTranslations';
|
|
9
9
|
|
|
10
10
|
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
11
|
config.settings.supportedLanguages = ['de', 'es'];
|
|
13
12
|
});
|
|
14
13
|
vi.mock('../Toolbar/Toolbar', () => ({
|
|
@@ -34,6 +33,11 @@ describe('ManageTranslations', () => {
|
|
|
34
33
|
language: 'en',
|
|
35
34
|
},
|
|
36
35
|
},
|
|
36
|
+
site: {
|
|
37
|
+
data: {
|
|
38
|
+
'plone.available_languages': ['de', 'es'],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
37
41
|
});
|
|
38
42
|
const { container } = render(
|
|
39
43
|
<Provider store={store}>
|
|
@@ -63,7 +63,7 @@ const TranslationObject = ({
|
|
|
63
63
|
{
|
|
64
64
|
...window.__data,
|
|
65
65
|
intl: {
|
|
66
|
-
defaultLocale:
|
|
66
|
+
defaultLocale: translationObject.language.token,
|
|
67
67
|
locale: translationObject.language.token,
|
|
68
68
|
messages: locales[translationObject.language.token],
|
|
69
69
|
},
|
|
@@ -114,6 +114,9 @@ const More = (props) => {
|
|
|
114
114
|
|
|
115
115
|
const content = useSelector((state) => state.content?.data, shallowEqual);
|
|
116
116
|
const workingCopy = useSelector((state) => state.workingCopy, shallowEqual);
|
|
117
|
+
const isMultilingual = useSelector(
|
|
118
|
+
(state) => state.site.data.features?.multilingual,
|
|
119
|
+
);
|
|
117
120
|
|
|
118
121
|
const actions = useSelector((state) => state.actions.actions, shallowEqual);
|
|
119
122
|
|
|
@@ -435,7 +438,7 @@ const More = (props) => {
|
|
|
435
438
|
</li>
|
|
436
439
|
</Plug>
|
|
437
440
|
)}
|
|
438
|
-
{editAction &&
|
|
441
|
+
{editAction && isMultilingual && (
|
|
439
442
|
<Plug pluggable="toolbar-more-manage-content" id="multilingual">
|
|
440
443
|
<li>
|
|
441
444
|
<Link to={`${path}/manage-translations`}>
|
|
@@ -537,10 +537,9 @@ class Toolbar extends Component {
|
|
|
537
537
|
</Link>
|
|
538
538
|
)}
|
|
539
539
|
{this.props.content &&
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
this.props.content['@components']?.translations)) && (
|
|
540
|
+
this.props.content.is_folderish &&
|
|
541
|
+
(this.props.types.length > 0 ||
|
|
542
|
+
this.props.content['@components']?.translations) && (
|
|
544
543
|
<button
|
|
545
544
|
className="add"
|
|
546
545
|
aria-label={this.props.intl.formatMessage(
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { connect } from 'react-redux';
|
|
4
4
|
import { Link } from 'react-router-dom';
|
|
5
|
+
import { useSelector } from 'react-redux';
|
|
5
6
|
import filter from 'lodash/filter';
|
|
6
7
|
import find from 'lodash/find';
|
|
7
8
|
import isEmpty from 'lodash/isEmpty';
|
|
@@ -10,12 +11,12 @@ import { FormattedMessage } from 'react-intl';
|
|
|
10
11
|
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
11
12
|
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
12
13
|
import { toBackendLang } from '@plone/volto/helpers/Utils/Utils';
|
|
13
|
-
import config from '@plone/volto/registry';
|
|
14
14
|
|
|
15
15
|
const Types = ({ types, pathname, content, currentLanguage }) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const availableLanguages = useSelector(
|
|
17
|
+
(state) => state.site?.data?.['plone.available_languages'] || [],
|
|
18
|
+
);
|
|
19
|
+
return types.length > 0 || content?.['@components']?.translations ? (
|
|
19
20
|
<div className="menu-more pastanaga-menu">
|
|
20
21
|
{types.length > 0 && (
|
|
21
22
|
<>
|
|
@@ -53,11 +54,10 @@ const Types = ({ types, pathname, content, currentLanguage }) => {
|
|
|
53
54
|
</div>
|
|
54
55
|
</>
|
|
55
56
|
)}
|
|
56
|
-
{
|
|
57
|
-
content['@components'].translations &&
|
|
57
|
+
{content['@components'].translations &&
|
|
58
58
|
(() => {
|
|
59
59
|
const translationsLeft = filter(
|
|
60
|
-
|
|
60
|
+
availableLanguages,
|
|
61
61
|
(lang) =>
|
|
62
62
|
!Boolean(
|
|
63
63
|
content['@components'].translations &&
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import config from '@plone/volto/registry';
|
|
2
1
|
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
|
|
3
2
|
import { flattenToAppURL, toPublicURL } from '@plone/volto/helpers/Url/Url';
|
|
4
3
|
|
|
@@ -6,8 +5,7 @@ const AlternateHrefLangs = (props) => {
|
|
|
6
5
|
const { content } = props;
|
|
7
6
|
return (
|
|
8
7
|
<Helmet>
|
|
9
|
-
{
|
|
10
|
-
content['@components']?.translations?.items &&
|
|
8
|
+
{content['@components']?.translations?.items &&
|
|
11
9
|
[
|
|
12
10
|
...content['@components']?.translations?.items,
|
|
13
11
|
{ '@id': content['@id'], language: content.language.token },
|
|
@@ -13,7 +13,6 @@ const mockStore = configureStore();
|
|
|
13
13
|
describe('AlternateHrefLangs', () => {
|
|
14
14
|
beforeEach(() => {});
|
|
15
15
|
it('non multilingual site, renders nothing', () => {
|
|
16
|
-
config.settings.isMultilingual = false;
|
|
17
16
|
const content = {
|
|
18
17
|
'@id': '/',
|
|
19
18
|
'@components': {},
|
|
@@ -38,7 +37,6 @@ describe('AlternateHrefLangs', () => {
|
|
|
38
37
|
|
|
39
38
|
it('multilingual site, with some translations', () => {
|
|
40
39
|
config.settings.publicURL = 'https://plone.org';
|
|
41
|
-
config.settings.isMultilingual = true;
|
|
42
40
|
config.settings.supportedLanguages = ['en', 'es', 'eu'];
|
|
43
41
|
|
|
44
42
|
const content = {
|
|
@@ -85,7 +83,6 @@ describe('AlternateHrefLangs', () => {
|
|
|
85
83
|
|
|
86
84
|
it('multilingual site, with all available translations', () => {
|
|
87
85
|
config.settings.publicURL = 'https://plone.org';
|
|
88
|
-
config.settings.isMultilingual = true;
|
|
89
86
|
config.settings.supportedLanguages = ['en', 'es', 'eu'];
|
|
90
87
|
const store = mockStore({
|
|
91
88
|
intl: {
|
|
@@ -139,7 +136,6 @@ describe('AlternateHrefLangs', () => {
|
|
|
139
136
|
|
|
140
137
|
it('multilingual site, with all available translations - with server URL', () => {
|
|
141
138
|
config.settings.publicURL = 'https://plone.org';
|
|
142
|
-
config.settings.isMultilingual = true;
|
|
143
139
|
config.settings.supportedLanguages = ['en', 'es', 'eu'];
|
|
144
140
|
const store = mockStore({
|
|
145
141
|
intl: {
|
|
@@ -235,7 +235,9 @@ export const fetchContent = async ({ store, location }) => {
|
|
|
235
235
|
|
|
236
236
|
const visitor = ([id, data]) => {
|
|
237
237
|
const blockType = data['@type'];
|
|
238
|
-
const
|
|
238
|
+
const block = blocksConfig[blockType];
|
|
239
|
+
if (!block) return;
|
|
240
|
+
const { getAsyncData } = block;
|
|
239
241
|
if (getAsyncData) {
|
|
240
242
|
const p = getAsyncData({
|
|
241
243
|
store,
|
|
@@ -82,9 +82,27 @@ export default {
|
|
|
82
82
|
],
|
|
83
83
|
argTypes: {
|
|
84
84
|
locale: {
|
|
85
|
+
options: [
|
|
86
|
+
'ca',
|
|
87
|
+
'de',
|
|
88
|
+
'en',
|
|
89
|
+
'es',
|
|
90
|
+
'eu',
|
|
91
|
+
'fi',
|
|
92
|
+
'fr',
|
|
93
|
+
'hi',
|
|
94
|
+
'it',
|
|
95
|
+
'ja',
|
|
96
|
+
'nl',
|
|
97
|
+
'pt',
|
|
98
|
+
'pt-BR',
|
|
99
|
+
'ro',
|
|
100
|
+
'ru',
|
|
101
|
+
'sv',
|
|
102
|
+
'zh-CN',
|
|
103
|
+
],
|
|
85
104
|
control: {
|
|
86
|
-
type: '
|
|
87
|
-
options: ['en', 'de', 'us'],
|
|
105
|
+
type: 'radio',
|
|
88
106
|
},
|
|
89
107
|
},
|
|
90
108
|
},
|
|
@@ -17,8 +17,6 @@ import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
|
|
|
17
17
|
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
|
|
18
18
|
import { toReactIntlLang } from '@plone/volto/helpers/Utils/Utils';
|
|
19
19
|
|
|
20
|
-
import config from '@plone/volto/registry';
|
|
21
|
-
|
|
22
20
|
import { defineMessages, useIntl } from 'react-intl';
|
|
23
21
|
|
|
24
22
|
const messages = defineMessages({
|
|
@@ -34,12 +32,16 @@ const LanguageSelector = (props) => {
|
|
|
34
32
|
const translations = useSelector(
|
|
35
33
|
(state) => state.content.data?.['@components']?.translations?.items,
|
|
36
34
|
);
|
|
35
|
+
const isMultilingual = useSelector(
|
|
36
|
+
(state) => state.site.data.features?.multilingual,
|
|
37
|
+
);
|
|
38
|
+
const availableLanguages = useSelector(
|
|
39
|
+
(state) => state.site.data?.['plone.available_languages'],
|
|
40
|
+
);
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return settings.isMultilingual ? (
|
|
42
|
+
return isMultilingual ? (
|
|
41
43
|
<div className="language-selector">
|
|
42
|
-
{map(
|
|
44
|
+
{map(availableLanguages, (lang) => {
|
|
43
45
|
const translation = find(translations, { language: lang });
|
|
44
46
|
return (
|
|
45
47
|
<Link
|
|
@@ -61,7 +63,7 @@ const LanguageSelector = (props) => {
|
|
|
61
63
|
</div>
|
|
62
64
|
) : (
|
|
63
65
|
<Helmet>
|
|
64
|
-
<html lang={toReactIntlLang(
|
|
66
|
+
<html lang={toReactIntlLang(currentLang)} />
|
|
65
67
|
</Helmet>
|
|
66
68
|
);
|
|
67
69
|
};
|
|
@@ -3,15 +3,9 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import LanguageSelector from './LanguageSelector';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
config.settings.supportedLanguages = ['de', 'es'];
|
|
13
|
-
});
|
|
14
|
-
|
|
15
9
|
const mockStore = configureStore();
|
|
16
10
|
|
|
17
11
|
describe('LanguageSelector', () => {
|
|
@@ -30,6 +24,12 @@ describe('LanguageSelector', () => {
|
|
|
30
24
|
},
|
|
31
25
|
},
|
|
32
26
|
},
|
|
27
|
+
site: {
|
|
28
|
+
data: {
|
|
29
|
+
features: { multilingual: true },
|
|
30
|
+
'plone.available_languages': ['es'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
33
|
});
|
|
34
34
|
const component = renderer.create(
|
|
35
35
|
<Provider store={store}>
|
|
@@ -3,14 +3,9 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import Logo from './Logo';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
9
|
const mockStore = configureStore();
|
|
15
10
|
|
|
16
11
|
describe('Multilingual Logo', () => {
|
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Redirect } from 'react-router-dom';
|
|
3
|
-
import { useDispatch } from 'react-redux';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
4
|
import { useCookies } from 'react-cookie';
|
|
5
|
-
import config from '@plone/volto/registry';
|
|
6
5
|
import { changeLanguage } from '@plone/volto/actions/language/language';
|
|
7
6
|
import { toGettextLang } from '@plone/volto/helpers/Utils/Utils';
|
|
8
7
|
|
|
9
8
|
const MultilingualRedirector = (props) => {
|
|
10
|
-
const { settings } = config;
|
|
11
9
|
const { pathname, children } = props;
|
|
12
10
|
const [cookies] = useCookies();
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
? currentLanguage
|
|
18
|
-
: settings.defaultLanguage;
|
|
11
|
+
const site = useSelector((state) => state.site.data);
|
|
12
|
+
const isMultilingual = site.features?.multilingual;
|
|
13
|
+
const redirectToLanguage =
|
|
14
|
+
cookies['I18N_LANGUAGE'] || site['plone.default_language'];
|
|
19
15
|
const dispatch = useDispatch();
|
|
20
16
|
|
|
21
17
|
React.useEffect(() => {
|
|
22
18
|
// ToDo: Add means to support language negotiation (with config)
|
|
23
19
|
// const detectedLang = (navigator.language || navigator.userLanguage).substring(0, 2);
|
|
24
20
|
let mounted = true;
|
|
25
|
-
if (
|
|
21
|
+
if (isMultilingual && pathname === '/') {
|
|
26
22
|
const langFileName = toGettextLang(redirectToLanguage);
|
|
27
23
|
import(
|
|
28
24
|
/* @vite-ignore */ '@root/../locales/' + langFileName + '.json'
|
|
@@ -35,9 +31,9 @@ const MultilingualRedirector = (props) => {
|
|
|
35
31
|
return () => {
|
|
36
32
|
mounted = false;
|
|
37
33
|
};
|
|
38
|
-
}, [pathname, dispatch, redirectToLanguage,
|
|
34
|
+
}, [pathname, dispatch, redirectToLanguage, isMultilingual]);
|
|
39
35
|
|
|
40
|
-
return pathname === '/' &&
|
|
36
|
+
return pathname === '/' && isMultilingual ? (
|
|
41
37
|
<Redirect to={`/${redirectToLanguage}`} />
|
|
42
38
|
) : (
|
|
43
39
|
<>{children}</>
|
|
@@ -3,19 +3,17 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import MultilingualRedirector from './MultilingualRedirector';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
9
|
const mockStore = configureStore();
|
|
15
10
|
|
|
16
11
|
describe('MultilingualRedirector', () => {
|
|
17
12
|
it('renders a MultilingualRedirector component', () => {
|
|
18
13
|
const store = mockStore({
|
|
14
|
+
site: {
|
|
15
|
+
data: {},
|
|
16
|
+
},
|
|
19
17
|
intl: {
|
|
20
18
|
locale: 'en',
|
|
21
19
|
messages: {},
|
|
@@ -15,11 +15,7 @@ const NavItem = ({ item, lang }) => {
|
|
|
15
15
|
key={item.url}
|
|
16
16
|
className="item"
|
|
17
17
|
activeClassName="active"
|
|
18
|
-
exact={
|
|
19
|
-
settings.isMultilingual
|
|
20
|
-
? item.url === `/${toBackendLang(lang)}`
|
|
21
|
-
: item.url === ''
|
|
22
|
-
}
|
|
18
|
+
exact={item.url === '' || item.url === `/${toBackendLang(lang)}`}
|
|
23
19
|
>
|
|
24
20
|
{item.title}
|
|
25
21
|
</NavLink>
|
|
@@ -3,14 +3,9 @@ import renderer from 'react-test-renderer';
|
|
|
3
3
|
import configureStore from 'redux-mock-store';
|
|
4
4
|
import { Provider } from 'react-intl-redux';
|
|
5
5
|
import { MemoryRouter } from 'react-router-dom';
|
|
6
|
-
import config from '@plone/volto/registry';
|
|
7
6
|
|
|
8
7
|
import Navigation from './Navigation';
|
|
9
8
|
|
|
10
|
-
beforeAll(() => {
|
|
11
|
-
config.settings.isMultilingual = true;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
9
|
const mockStore = configureStore();
|
|
15
10
|
|
|
16
11
|
describe('Navigation Multilingual', () => {
|
|
@@ -19,15 +19,18 @@ import config from '@plone/volto/registry';
|
|
|
19
19
|
const NotFound = () => {
|
|
20
20
|
const dispatch = useDispatch();
|
|
21
21
|
const lang = useSelector((state) => state.intl.locale);
|
|
22
|
+
const isMultilingual = useSelector(
|
|
23
|
+
(state) => state.site.data.features?.multilingual,
|
|
24
|
+
);
|
|
22
25
|
|
|
23
26
|
useEffect(() => {
|
|
24
27
|
dispatch(
|
|
25
28
|
getNavigation(
|
|
26
|
-
|
|
29
|
+
isMultilingual ? `/${toBackendLang(lang)}` : '/',
|
|
27
30
|
config.settings.navDepth,
|
|
28
31
|
),
|
|
29
32
|
);
|
|
30
|
-
}, [dispatch, lang]);
|
|
33
|
+
}, [dispatch, lang, isMultilingual]);
|
|
31
34
|
|
|
32
35
|
return (
|
|
33
36
|
<Container className="view-wrapper">
|
|
@@ -36,14 +36,14 @@ function Sitemap(props) {
|
|
|
36
36
|
location: { pathname },
|
|
37
37
|
lang,
|
|
38
38
|
getNavigation,
|
|
39
|
+
isMultilingual,
|
|
39
40
|
} = props;
|
|
40
41
|
|
|
41
42
|
useEffect(() => {
|
|
42
|
-
const
|
|
43
|
-
const language = settings.isMultilingual ? `${toBackendLang(lang)}` : null;
|
|
43
|
+
const language = isMultilingual ? `${toBackendLang(lang)}` : null;
|
|
44
44
|
const path = getSitemapPath(pathname, language);
|
|
45
45
|
getNavigation(path, 4);
|
|
46
|
-
}, [pathname, lang, getNavigation]);
|
|
46
|
+
}, [pathname, lang, getNavigation, isMultilingual]);
|
|
47
47
|
|
|
48
48
|
const renderItems = (items) => {
|
|
49
49
|
return (
|
|
@@ -100,6 +100,7 @@ export default compose(
|
|
|
100
100
|
(state) => ({
|
|
101
101
|
items: state.navigation.items,
|
|
102
102
|
lang: state.intl.locale,
|
|
103
|
+
isMultilingual: state.site.data.features?.multilingual,
|
|
103
104
|
}),
|
|
104
105
|
{ getNavigation },
|
|
105
106
|
),
|
|
@@ -108,10 +109,10 @@ export default compose(
|
|
|
108
109
|
key: 'navigation',
|
|
109
110
|
promise: ({ location, store: { dispatch, getState } }) => {
|
|
110
111
|
if (!__SERVER__) return;
|
|
111
|
-
const
|
|
112
|
+
const state = getState();
|
|
112
113
|
const path = getSitemapPath(
|
|
113
114
|
location.pathname,
|
|
114
|
-
|
|
115
|
+
state.site.data.features?.multilingual
|
|
115
116
|
? toBackendLang(getState().intl.locale)
|
|
116
117
|
: null,
|
|
117
118
|
);
|