@plone/volto 19.0.0-alpha.2 → 19.0.0-alpha.4

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.
Files changed (153) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +0 -2
  3. package/cypress/downloads/downloads.html +0 -0
  4. package/cypress/support/commands.js +5 -0
  5. package/locales/af.json +1 -0
  6. package/locales/ar.json +1 -0
  7. package/locales/bg.json +1 -0
  8. package/locales/bn.json +1 -0
  9. package/locales/ca/LC_MESSAGES/volto.po +44 -10
  10. package/locales/ca.json +1 -1
  11. package/locales/cs.json +1 -0
  12. package/locales/cy.json +1 -0
  13. package/locales/da.json +1 -0
  14. package/locales/de/LC_MESSAGES/volto.po +52 -18
  15. package/locales/de.json +1 -1
  16. package/locales/el.json +1 -0
  17. package/locales/en/LC_MESSAGES/volto.po +45 -11
  18. package/locales/en.json +1 -1
  19. package/locales/en_AU.json +1 -0
  20. package/locales/en_GB.json +1 -0
  21. package/locales/eo.json +1 -0
  22. package/locales/es/LC_MESSAGES/volto.po +45 -11
  23. package/locales/es.json +1 -1
  24. package/locales/et.json +1 -0
  25. package/locales/eu/LC_MESSAGES/volto.po +44 -10
  26. package/locales/eu.json +1 -1
  27. package/locales/fa.json +1 -0
  28. package/locales/fi/LC_MESSAGES/volto.po +44 -10
  29. package/locales/fi.json +1 -1
  30. package/locales/fr/LC_MESSAGES/volto.po +45 -11
  31. package/locales/fr.json +1 -1
  32. package/locales/fu.json +1 -0
  33. package/locales/gl.json +1 -0
  34. package/locales/he.json +1 -0
  35. package/locales/hi/LC_MESSAGES/volto.po +48 -14
  36. package/locales/hi.json +1 -1
  37. package/locales/hr.json +1 -0
  38. package/locales/hu.json +1 -0
  39. package/locales/hy.json +1 -0
  40. package/locales/id.json +1 -0
  41. package/locales/it/LC_MESSAGES/volto.po +48 -14
  42. package/locales/it.json +1 -1
  43. package/locales/ja/LC_MESSAGES/volto.po +44 -10
  44. package/locales/ja.json +1 -1
  45. package/locales/ka.json +1 -0
  46. package/locales/kn.json +1 -0
  47. package/locales/ko.json +1 -0
  48. package/locales/lt.json +1 -0
  49. package/locales/lv.json +1 -0
  50. package/locales/mi.json +1 -0
  51. package/locales/mk.json +1 -0
  52. package/locales/my.json +1 -0
  53. package/locales/nb_NO.json +1 -0
  54. package/locales/nl/LC_MESSAGES/volto.po +48 -14
  55. package/locales/nl.json +1 -1
  56. package/locales/nn.json +1 -0
  57. package/locales/pl.json +1 -0
  58. package/locales/pt/LC_MESSAGES/volto.po +44 -10
  59. package/locales/pt.json +1 -1
  60. package/locales/pt_BR/LC_MESSAGES/volto.po +59 -25
  61. package/locales/pt_BR.json +1 -1
  62. package/locales/rm.json +1 -0
  63. package/locales/ro/LC_MESSAGES/volto.po +48 -14
  64. package/locales/ro.json +1 -1
  65. package/locales/ru/LC_MESSAGES/volto.po +48 -14
  66. package/locales/ru.json +1 -1
  67. package/locales/sk.json +1 -0
  68. package/locales/sl.json +1 -0
  69. package/locales/sm.json +1 -0
  70. package/locales/sq.json +1 -0
  71. package/locales/sr.json +1 -0
  72. package/locales/sr@cyrl.json +1 -0
  73. package/locales/sr@latn.json +1 -0
  74. package/locales/sv.json +1 -1
  75. package/locales/ta.json +1 -0
  76. package/locales/te.json +1 -0
  77. package/locales/th.json +1 -0
  78. package/locales/to.json +1 -0
  79. package/locales/tr.json +1 -0
  80. package/locales/uk.json +1 -0
  81. package/locales/vi.json +1 -0
  82. package/locales/volto.pot +45 -11
  83. package/locales/zh_CN/LC_MESSAGES/volto.po +44 -10
  84. package/locales/zh_CN.json +1 -1
  85. package/locales/zh_Hant.json +1 -0
  86. package/locales/zh_Hant_HK.json +1 -0
  87. package/package.json +9 -9
  88. package/src/actions/content/content.js +0 -1
  89. package/src/actions/controlpanels/controlpanels.js +13 -7
  90. package/src/actions/controlpanels/controlpanels.test.js +11 -5
  91. package/src/actions/users/users.js +2 -2
  92. package/src/components/manage/Add/Add.jsx +5 -6
  93. package/src/components/manage/Blocks/Block/Edit.jsx +1 -0
  94. package/src/components/manage/Blocks/Teaser/schema.js +8 -3
  95. package/src/components/manage/Contents/Contents.jsx +3 -0
  96. package/src/components/manage/Contents/Contents.test.jsx +7 -0
  97. package/src/components/manage/Contents/ContentsBreadcrumbs.Multilingual.test.jsx +18 -5
  98. package/src/components/manage/Contents/ContentsBreadcrumbs.jsx +20 -26
  99. package/src/components/manage/Contents/ContentsBreadcrumbs.test.jsx +14 -0
  100. package/src/components/manage/Contents/ContentsDeleteModal.jsx +258 -206
  101. package/src/components/manage/Contents/ContentsDeleteModal.stories.jsx +26 -8
  102. package/src/components/manage/Contents/ContentsItem.jsx +10 -2
  103. package/src/components/manage/Contents/ContentsUploadModal.test.jsx +13 -22
  104. package/src/components/manage/Edit/Edit.jsx +2 -3
  105. package/src/components/manage/Multilingual/CompareLanguages.jsx +2 -5
  106. package/src/components/manage/Multilingual/ManageTranslations.jsx +4 -2
  107. package/src/components/manage/Multilingual/ManageTranslations.test.jsx +5 -1
  108. package/src/components/manage/Multilingual/TranslationObject.jsx +1 -1
  109. package/src/components/manage/Toolbar/More.jsx +4 -1
  110. package/src/components/manage/Toolbar/More.test.jsx +3 -0
  111. package/src/components/manage/Toolbar/Toolbar.jsx +3 -4
  112. package/src/components/manage/Toolbar/Types.jsx +7 -7
  113. package/src/components/manage/UniversalLink/UniversalLink.tsx +1 -0
  114. package/src/components/manage/Widgets/RegistryImageWidget.test.jsx +1 -1
  115. package/src/components/manage/Widgets/SelectAutoComplete.jsx +29 -12
  116. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +1 -3
  117. package/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +0 -4
  118. package/src/components/theme/App/App.jsx +3 -1
  119. package/src/components/theme/App/App.test.jsx +1 -0
  120. package/src/components/theme/FormattedDate/FormattedDate.stories.jsx +20 -2
  121. package/src/components/theme/LanguageSelector/LanguageSelector.jsx +9 -7
  122. package/src/components/theme/LanguageSelector/LanguageSelector.test.jsx +6 -6
  123. package/src/components/theme/Logo/Logo.Multilingual.test.jsx +0 -5
  124. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +8 -12
  125. package/src/components/theme/MultilingualRedirector/MultilingualRedirector.test.jsx +3 -5
  126. package/src/components/theme/Navigation/NavItem.jsx +1 -5
  127. package/src/components/theme/Navigation/Navigation.Multilingual.test.jsx +0 -5
  128. package/src/components/theme/NotFound/NotFound.jsx +5 -2
  129. package/src/components/theme/NotFound/NotFound.test.jsx +3 -0
  130. package/src/components/theme/Sitemap/Sitemap.jsx +6 -5
  131. package/src/components/theme/Sitemap/Sitemap.test.jsx +0 -1
  132. package/src/components/theme/View/FileView.jsx +9 -1
  133. package/src/config/ControlPanels.js +1 -0
  134. package/src/config/index.js +6 -15
  135. package/src/express-middleware/devproxy.js +7 -2
  136. package/src/helpers/Html/Html.jsx +9 -4
  137. package/src/helpers/LanguageMap/LanguageMap.js +115 -8
  138. package/src/helpers/Url/bulkFlattenToAppURL.test.ts +122 -0
  139. package/src/helpers/Url/bulkFlattenToAppURL.ts +24 -0
  140. package/src/middleware/Api.test.js +4 -0
  141. package/src/middleware/api.js +74 -25
  142. package/src/routes.js +1 -1
  143. package/src/server.jsx +5 -6
  144. package/src/start-client.jsx +0 -4
  145. package/test-setup-config.jsx +0 -2
  146. package/theme/themes/pastanaga/extras/blocks.less +7 -0
  147. package/theme/themes/pastanaga/extras/contents.less +5 -5
  148. package/types/components/manage/Blocks/Teaser/schema.d.ts +1 -0
  149. package/types/components/theme/FormattedDate/FormattedDate.stories.d.ts +1 -1
  150. package/types/helpers/LanguageMap/LanguageMap.d.ts +428 -4
  151. package/types/helpers/Url/bulkFlattenToAppURL.d.ts +5 -0
  152. package/types/middleware/api.d.ts +6 -9
  153. package/src/actions/content/content.multilingual.test.js +0 -17
@@ -1,8 +1,7 @@
1
1
  import React from 'react';
2
- import renderer from 'react-test-renderer';
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 component = renderer.create(
23
+ const { baseElement } = render(
25
24
  <Provider store={store}>
26
25
  <ContentsUploadModal
27
26
  pathname="/blog"
28
- open={false}
27
+ open={true}
29
28
  onOk={() => {}}
30
29
  onCancel={() => {}}
31
30
  />
32
31
  </Provider>,
33
32
  );
34
- const json = component.toJSON();
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 component = renderer.create(
49
+ const { baseElement } = render(
53
50
  <Provider store={store}>
54
51
  <ContentsUploadModal
55
52
  pathname="/blog"
56
- open={false}
53
+ open={true}
57
54
  onOk={() => {}}
58
55
  onCancel={() => {}}
59
56
  accept={['image/*']}
60
57
  />
61
58
  </Provider>,
62
59
  );
63
- const json = component.toJSON();
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 component = renderer.create(
75
+ const { baseElement } = render(
81
76
  <Provider store={store}>
82
77
  <ContentsUploadModal
83
78
  pathname="/blog"
84
- open={false}
79
+ open={true}
85
80
  onOk={() => {}}
86
81
  onCancel={() => {}}
87
82
  maxSize={1000000}
88
83
  />
89
84
  </Provider>,
90
85
  );
91
- const json = component.toJSON();
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 component = renderer.create(
101
+ const { baseElement } = render(
109
102
  <Provider store={store}>
110
103
  <ContentsUploadModal
111
104
  pathname="/blog"
112
- open={false}
105
+ open={true}
113
106
  onOk={() => {}}
114
107
  onCancel={() => {}}
115
108
  multiple={false}
116
109
  />
117
110
  </Provider>,
118
111
  );
119
- const json = component.toJSON();
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
- {config.settings.isMultilingual && (
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 = config.settings.isMultilingual
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 (config.settings.isMultilingual && translations.length > 0) {
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
- {config.settings.supportedLanguages.map((lang) => (
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: config.settings.defaultLanguage,
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 && config.settings.isMultilingual && (
441
+ {editAction && isMultilingual && (
439
442
  <Plug pluggable="toolbar-more-manage-content" id="multilingual">
440
443
  <li>
441
444
  <Link to={`${path}/manage-translations`}>
@@ -135,6 +135,9 @@ const store = mockStore({
135
135
  is_folderish: true,
136
136
  },
137
137
  },
138
+ site: {
139
+ data: {},
140
+ },
138
141
  workflow: { history: [], transition: { loaded: true } },
139
142
  });
140
143
 
@@ -537,10 +537,9 @@ class Toolbar extends Component {
537
537
  </Link>
538
538
  )}
539
539
  {this.props.content &&
540
- ((this.props.content.is_folderish &&
541
- this.props.types.length > 0) ||
542
- (config.settings.isMultilingual &&
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 { settings } = config;
17
- return types.length > 0 ||
18
- (settings.isMultilingual && content['@components'].translations) ? (
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
- {settings.isMultilingual &&
57
- content['@components'].translations &&
57
+ {content['@components'].translations &&
58
58
  (() => {
59
59
  const translationsLeft = filter(
60
- settings.supportedLanguages,
60
+ availableLanguages,
61
61
  (lang) =>
62
62
  !Boolean(
63
63
  content['@components'].translations &&
@@ -148,6 +148,7 @@ const UniversalLink = React.memo(
148
148
  target={openLinkInNewTab ?? false ? '_blank' : undefined}
149
149
  title={title}
150
150
  className={className}
151
+ onClick={onClick}
151
152
  smooth={smooth ?? config.settings.hashLinkSmoothScroll}
152
153
  // @ts-ignore
153
154
  ref={ref}
@@ -85,7 +85,7 @@ describe('RegistryImageWidget', () => {
85
85
 
86
86
  return dropzone && preview && filename;
87
87
  },
88
- { timeout: 1000 },
88
+ { timeout: 2000 },
89
89
  );
90
90
 
91
91
  expect(container).toMatchSnapshot();
@@ -105,6 +105,7 @@ class SelectAutoComplete extends Component {
105
105
  error: [],
106
106
  choices: [],
107
107
  value: null,
108
+ isMulti: true,
108
109
  };
109
110
 
110
111
  /**
@@ -127,10 +128,16 @@ class SelectAutoComplete extends Component {
127
128
  componentDidMount() {
128
129
  const { id, lang, value, choices } = this.props;
129
130
  if (value && value?.length > 0) {
130
- const tokensQuery = convertValueToVocabQuery(
131
- normalizeValue(choices, value, this.props.intl),
132
- );
133
-
131
+ let tokensQuery;
132
+ if (typeof value === 'string') {
133
+ tokensQuery = convertValueToVocabQuery(
134
+ normalizeValue(choices, [value], this.props.intl),
135
+ );
136
+ } else {
137
+ tokensQuery = convertValueToVocabQuery(
138
+ normalizeValue(choices, value, this.props.intl),
139
+ );
140
+ }
134
141
  this.props.getVocabularyTokenTitle({
135
142
  vocabNameOrURL: this.props.vocabBaseUrl,
136
143
  subrequest: `widget-${id}-${lang}`,
@@ -160,13 +167,22 @@ class SelectAutoComplete extends Component {
160
167
  * @returns {undefined}
161
168
  */
162
169
  handleChange(selectedOption) {
163
- this.props.onChange(
164
- this.props.id,
165
- selectedOption ? selectedOption.map((item) => item.value) : null,
166
- );
167
- this.setState((state) => ({
168
- termsPairsCache: [...state.termsPairsCache, ...selectedOption],
169
- }));
170
+ if (!Array.isArray(selectedOption)) {
171
+ this.props.onChange(this.props.id, selectedOption?.value || null);
172
+ if (selectedOption) {
173
+ this.setState((state) => ({
174
+ termsPairsCache: [...state.termsPairsCache, selectedOption],
175
+ }));
176
+ }
177
+ } else {
178
+ this.props.onChange(
179
+ this.props.id,
180
+ selectedOption ? selectedOption.map((item) => item.value) : null,
181
+ );
182
+ this.setState((state) => ({
183
+ termsPairsCache: [...state.termsPairsCache, ...selectedOption],
184
+ }));
185
+ }
170
186
  }
171
187
 
172
188
  timeoutRef = React.createRef();
@@ -251,7 +267,8 @@ class SelectAutoComplete extends Component {
251
267
  this.props.intl.formatMessage(messages.select)
252
268
  }
253
269
  onChange={this.handleChange}
254
- isMulti
270
+ isMulti={this.props.isMulti}
271
+ isClearable={!this.props.isMulti}
255
272
  />
256
273
  </FormFieldWrapper>
257
274
  );
@@ -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
- {config.settings.isMultilingual &&
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 { getAsyncData } = blocksConfig[blockType];
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,
@@ -51,6 +51,7 @@ describe('App', () => {
51
51
  locale: 'en',
52
52
  messages: {},
53
53
  },
54
+ site: { data: {} },
54
55
  });
55
56
  const component = renderer.create(
56
57
  <Provider store={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: 'select',
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
- const { settings } = config;
39
-
40
- return settings.isMultilingual ? (
42
+ return isMultilingual ? (
41
43
  <div className="language-selector">
42
- {map(settings.supportedLanguages, (lang) => {
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(settings.defaultLanguage)} />
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 currentLanguage = cookies['I18N_LANGUAGE'] || settings.defaultLanguage;
14
- const redirectToLanguage = settings.supportedLanguages.includes(
15
- currentLanguage,
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 (settings.isMultilingual && pathname === '/') {
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, settings.isMultilingual]);
34
+ }, [pathname, dispatch, redirectToLanguage, isMultilingual]);
39
35
 
40
- return pathname === '/' && settings.isMultilingual ? (
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>