@plone/volto 19.1.2 → 19.1.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.
- package/CHANGELOG.md +14 -0
- package/locales/af/LC_MESSAGES/volto.po +23 -9
- package/locales/af.json +1 -1
- package/locales/ar/LC_MESSAGES/volto.po +23 -9
- package/locales/ar.json +1 -1
- package/locales/bg/LC_MESSAGES/volto.po +23 -9
- package/locales/bg.json +1 -1
- package/locales/bn/LC_MESSAGES/volto.po +23 -9
- package/locales/bn.json +1 -1
- package/locales/ca/LC_MESSAGES/volto.po +23 -9
- package/locales/ca.json +1 -1
- package/locales/cs/LC_MESSAGES/volto.po +23 -9
- package/locales/cs.json +1 -1
- package/locales/cy/LC_MESSAGES/volto.po +23 -9
- package/locales/cy.json +1 -1
- package/locales/da/LC_MESSAGES/volto.po +23 -9
- package/locales/da.json +1 -1
- package/locales/de/LC_MESSAGES/volto.po +23 -9
- package/locales/de.json +1 -1
- package/locales/el/LC_MESSAGES/volto.po +23 -9
- package/locales/el.json +1 -1
- package/locales/en/LC_MESSAGES/volto.po +23 -9
- package/locales/en.json +1 -1
- package/locales/en_AU/LC_MESSAGES/volto.po +23 -9
- package/locales/en_AU.json +1 -1
- package/locales/en_GB/LC_MESSAGES/volto.po +23 -9
- package/locales/en_GB.json +1 -1
- package/locales/eo/LC_MESSAGES/volto.po +23 -9
- package/locales/eo.json +1 -1
- package/locales/es/LC_MESSAGES/volto.po +23 -9
- package/locales/es.json +1 -1
- package/locales/et/LC_MESSAGES/volto.po +23 -9
- package/locales/et.json +1 -1
- package/locales/eu/LC_MESSAGES/volto.po +23 -9
- package/locales/eu.json +1 -1
- package/locales/fa/LC_MESSAGES/volto.po +23 -9
- package/locales/fa.json +1 -1
- package/locales/fi/LC_MESSAGES/volto.po +23 -9
- package/locales/fi.json +1 -1
- package/locales/fr/LC_MESSAGES/volto.po +23 -9
- package/locales/fr.json +1 -1
- package/locales/fu/LC_MESSAGES/volto.po +23 -9
- package/locales/fu.json +1 -1
- package/locales/gl/LC_MESSAGES/volto.po +23 -9
- package/locales/gl.json +1 -1
- package/locales/he/LC_MESSAGES/volto.po +23 -9
- package/locales/he.json +1 -1
- package/locales/hi/LC_MESSAGES/volto.po +23 -9
- package/locales/hi.json +1 -1
- package/locales/hr/LC_MESSAGES/volto.po +23 -9
- package/locales/hr.json +1 -1
- package/locales/hu/LC_MESSAGES/volto.po +23 -9
- package/locales/hu.json +1 -1
- package/locales/hy/LC_MESSAGES/volto.po +23 -9
- package/locales/hy.json +1 -1
- package/locales/id/LC_MESSAGES/volto.po +23 -9
- package/locales/id.json +1 -1
- package/locales/it/LC_MESSAGES/volto.po +23 -9
- package/locales/it.json +1 -1
- package/locales/ja/LC_MESSAGES/volto.po +23 -9
- package/locales/ja.json +1 -1
- package/locales/ka/LC_MESSAGES/volto.po +23 -9
- package/locales/ka.json +1 -1
- package/locales/kn/LC_MESSAGES/volto.po +23 -9
- package/locales/kn.json +1 -1
- package/locales/ko/LC_MESSAGES/volto.po +23 -9
- package/locales/ko.json +1 -1
- package/locales/lt/LC_MESSAGES/volto.po +23 -9
- package/locales/lt.json +1 -1
- package/locales/lv/LC_MESSAGES/volto.po +23 -9
- package/locales/lv.json +1 -1
- package/locales/mi/LC_MESSAGES/volto.po +23 -9
- package/locales/mi.json +1 -1
- package/locales/mk/LC_MESSAGES/volto.po +23 -9
- package/locales/mk.json +1 -1
- package/locales/my/LC_MESSAGES/volto.po +23 -9
- package/locales/my.json +1 -1
- package/locales/nb_NO/LC_MESSAGES/volto.po +23 -9
- package/locales/nb_NO.json +1 -1
- package/locales/nl/LC_MESSAGES/volto.po +23 -9
- package/locales/nl.json +1 -1
- package/locales/nn/LC_MESSAGES/volto.po +23 -9
- package/locales/nn.json +1 -1
- package/locales/pl/LC_MESSAGES/volto.po +23 -9
- package/locales/pl.json +1 -1
- package/locales/pt/LC_MESSAGES/volto.po +23 -9
- package/locales/pt.json +1 -1
- package/locales/pt_BR/LC_MESSAGES/volto.po +23 -9
- package/locales/pt_BR.json +1 -1
- package/locales/rm/LC_MESSAGES/volto.po +23 -9
- package/locales/rm.json +1 -1
- package/locales/ro/LC_MESSAGES/volto.po +23 -9
- package/locales/ro.json +1 -1
- package/locales/ru/LC_MESSAGES/volto.po +23 -9
- package/locales/ru.json +1 -1
- package/locales/sk/LC_MESSAGES/volto.po +23 -9
- package/locales/sk.json +1 -1
- package/locales/sl/LC_MESSAGES/volto.po +23 -9
- package/locales/sl.json +1 -1
- package/locales/sm/LC_MESSAGES/volto.po +23 -9
- package/locales/sm.json +1 -1
- package/locales/sq/LC_MESSAGES/volto.po +23 -9
- package/locales/sq.json +1 -1
- package/locales/sr/LC_MESSAGES/volto.po +23 -9
- package/locales/sr.json +1 -1
- package/locales/sr@cyrl/LC_MESSAGES/volto.po +23 -9
- package/locales/sr@cyrl.json +1 -1
- package/locales/sr@latn/LC_MESSAGES/volto.po +23 -9
- package/locales/sr@latn.json +1 -1
- package/locales/sv/LC_MESSAGES/volto.po +23 -9
- package/locales/sv.json +1 -1
- package/locales/ta/LC_MESSAGES/volto.po +23 -9
- package/locales/ta.json +1 -1
- package/locales/te/LC_MESSAGES/volto.po +23 -9
- package/locales/te.json +1 -1
- package/locales/th/LC_MESSAGES/volto.po +23 -9
- package/locales/th.json +1 -1
- package/locales/to/LC_MESSAGES/volto.po +23 -9
- package/locales/to.json +1 -1
- package/locales/tr/LC_MESSAGES/volto.po +23 -9
- package/locales/tr.json +1 -1
- package/locales/uk/LC_MESSAGES/volto.po +23 -9
- package/locales/uk.json +1 -1
- package/locales/vi/LC_MESSAGES/volto.po +23 -9
- package/locales/vi.json +1 -1
- package/locales/volto.pot +23 -9
- package/locales/zh_CN/LC_MESSAGES/volto.po +23 -9
- package/locales/zh_CN.json +1 -1
- package/locales/zh_Hant/LC_MESSAGES/volto.po +23 -9
- package/locales/zh_Hant.json +1 -1
- package/locales/zh_Hant_HK/LC_MESSAGES/volto.po +23 -9
- package/locales/zh_Hant_HK.json +1 -1
- package/package.json +7 -7
- package/src/components/manage/Multilingual/CompareLanguages.jsx +15 -13
- package/src/components/manage/Widgets/FileWidget.jsx +64 -20
- package/src/components/manage/Widgets/FormFieldWrapper.jsx +11 -3
- package/src/components/manage/Widgets/RegistryImageWidget.jsx +2 -2
- package/src/components/manage/Widgets/TextWidget.test.jsx +44 -0
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.jsx +5 -1
- package/src/components/theme/MultilingualRedirector/MultilingualRedirector.test.jsx +39 -9
- package/theme/themes/pastanaga/extras/main.less +1 -0
- package/theme/themes/pastanaga/extras/toolbar.less +10 -2
|
@@ -28,19 +28,9 @@ const CompareLanguagesMenu = ({
|
|
|
28
28
|
}) => {
|
|
29
29
|
const intl = useIntl();
|
|
30
30
|
|
|
31
|
-
const ClickOutsideListener = () => {
|
|
32
|
-
closeMenu();
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const ref = useDetectClickOutside({
|
|
36
|
-
onTriggered: ClickOutsideListener,
|
|
37
|
-
triggerKeys: ['Escape'],
|
|
38
|
-
});
|
|
39
|
-
|
|
40
31
|
return (
|
|
41
32
|
<div
|
|
42
33
|
className="toolbar-content show compare-languages"
|
|
43
|
-
ref={ref}
|
|
44
34
|
style={{
|
|
45
35
|
flex: theToolbar.current
|
|
46
36
|
? `0 0 ${theToolbar.current.getBoundingClientRect().width}px`
|
|
@@ -106,6 +96,10 @@ const CompareLanguages = React.forwardRef((props, ref) => {
|
|
|
106
96
|
const intl = useIntl();
|
|
107
97
|
const [viewMenu, setViewMenu] = useState(false);
|
|
108
98
|
const translations = content?.['@components']?.translations?.items || [];
|
|
99
|
+
const compareLanguagesRef = useDetectClickOutside({
|
|
100
|
+
onTriggered: () => setViewMenu(false),
|
|
101
|
+
triggerKeys: ['Escape'],
|
|
102
|
+
});
|
|
109
103
|
|
|
110
104
|
const translationsObject = {};
|
|
111
105
|
translations.forEach((t) => {
|
|
@@ -114,19 +108,27 @@ const CompareLanguages = React.forwardRef((props, ref) => {
|
|
|
114
108
|
|
|
115
109
|
if (translations.length > 0) {
|
|
116
110
|
return (
|
|
117
|
-
<div
|
|
111
|
+
<div
|
|
112
|
+
className="toolbar-compare-translations-wrapper"
|
|
113
|
+
ref={compareLanguagesRef}
|
|
114
|
+
>
|
|
118
115
|
<div className="toolbar-button-spacer" />
|
|
119
116
|
|
|
120
117
|
<Button
|
|
118
|
+
type="button"
|
|
121
119
|
aria-label={intl.formatMessage(messages.compare_to)}
|
|
122
120
|
title={intl.formatMessage(messages.compare_to)}
|
|
123
121
|
onClick={() => {
|
|
124
|
-
setViewMenu(!viewMenu);
|
|
122
|
+
setViewMenu((viewMenu) => !viewMenu);
|
|
125
123
|
}}
|
|
126
124
|
id="toolbar-compare-translations"
|
|
127
125
|
className="toolbar-button-compare-translations"
|
|
128
126
|
>
|
|
129
|
-
<Icon
|
|
127
|
+
<Icon
|
|
128
|
+
className="mobile hidden"
|
|
129
|
+
name={viewMenu ? clearSVG : translateSVG}
|
|
130
|
+
size="30px"
|
|
131
|
+
/>
|
|
130
132
|
{viewMenu ? (
|
|
131
133
|
<Icon className="mobile only" name={clearSVG} size="30px" />
|
|
132
134
|
) : (
|
|
@@ -35,12 +35,12 @@ const messages = defineMessages({
|
|
|
35
35
|
defaultMessage: 'Drop files here ...',
|
|
36
36
|
},
|
|
37
37
|
editFile: {
|
|
38
|
-
id: 'Drop file here to replace the existing file',
|
|
39
|
-
defaultMessage: 'Drop file here to replace the existing file',
|
|
38
|
+
id: 'Drop a file here or click to replace the existing file',
|
|
39
|
+
defaultMessage: 'Drop a file here or click to replace the existing file',
|
|
40
40
|
},
|
|
41
41
|
fileDrag: {
|
|
42
|
-
id: 'Drop file here to upload
|
|
43
|
-
defaultMessage: 'Drop file here to upload
|
|
42
|
+
id: 'Drop a file here or click to upload',
|
|
43
|
+
defaultMessage: 'Drop a file here or click to upload',
|
|
44
44
|
},
|
|
45
45
|
replaceFile: {
|
|
46
46
|
id: 'Replace existing file',
|
|
@@ -60,8 +60,16 @@ const messages = defineMessages({
|
|
|
60
60
|
defaultMessage: 'File is not of the accepted type {accept}',
|
|
61
61
|
},
|
|
62
62
|
dragAndDropActionA11y: {
|
|
63
|
-
id: 'Press Enter to
|
|
64
|
-
defaultMessage: 'Press Enter to
|
|
63
|
+
id: 'File upload area. Press Enter to open the file browser',
|
|
64
|
+
defaultMessage: 'File upload area. Press Enter to open the file browser',
|
|
65
|
+
},
|
|
66
|
+
requiredField: {
|
|
67
|
+
id: 'This field is required.',
|
|
68
|
+
defaultMessage: 'This field is required.',
|
|
69
|
+
},
|
|
70
|
+
downloadFile: {
|
|
71
|
+
id: 'Download {filename}',
|
|
72
|
+
defaultMessage: 'Download {filename}',
|
|
65
73
|
},
|
|
66
74
|
});
|
|
67
75
|
|
|
@@ -89,7 +97,7 @@ const messages = defineMessages({
|
|
|
89
97
|
*
|
|
90
98
|
*/
|
|
91
99
|
const FileWidget = (props) => {
|
|
92
|
-
const { id, value, onChange, isDisabled } = props;
|
|
100
|
+
const { id, value, onChange, isDisabled, fieldSet } = props;
|
|
93
101
|
const [fileType, setFileType] = React.useState(false);
|
|
94
102
|
const intl = useIntl();
|
|
95
103
|
|
|
@@ -171,6 +179,16 @@ const FileWidget = (props) => {
|
|
|
171
179
|
reader.readAsDataURL(files[0]);
|
|
172
180
|
};
|
|
173
181
|
|
|
182
|
+
const statusTextA11y = [
|
|
183
|
+
intl.formatMessage(messages.dragAndDropActionA11y), // Interaction instructions
|
|
184
|
+
props.required && intl.formatMessage(messages.requiredField), // Required field status
|
|
185
|
+
value?.filename, // Current file name if a file is uploaded
|
|
186
|
+
]
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.join('. ');
|
|
189
|
+
|
|
190
|
+
const errorTextA11y = props.error?.length ? props.error.join('. ') : null;
|
|
191
|
+
|
|
174
192
|
return (
|
|
175
193
|
<FormFieldWrapper {...props}>
|
|
176
194
|
<Dropzone
|
|
@@ -179,7 +197,13 @@ const FileWidget = (props) => {
|
|
|
179
197
|
{...(props.accept ? { accept: props.accept } : {})}
|
|
180
198
|
>
|
|
181
199
|
{({ getRootProps, getInputProps, isDragActive }) => (
|
|
182
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
className="file-widget-dropzone"
|
|
202
|
+
role="button"
|
|
203
|
+
aria-labelledby={`fieldset-${fieldSet}-field-label-${id}`}
|
|
204
|
+
aria-describedby={`field-${id}-status`}
|
|
205
|
+
{...getRootProps()}
|
|
206
|
+
>
|
|
183
207
|
{isDragActive && <Dimmer active></Dimmer>}
|
|
184
208
|
{fileType ? (
|
|
185
209
|
<Image
|
|
@@ -205,34 +229,52 @@ const FileWidget = (props) => {
|
|
|
205
229
|
</div>
|
|
206
230
|
)}
|
|
207
231
|
|
|
208
|
-
|
|
232
|
+
{/* aria-hidden: keyboard access is handled by the parent div (role="button").
|
|
233
|
+
The label is a visual affordance only. The stopPropagation prevents the Dropzone
|
|
234
|
+
from opening the file dialog twice on click. */}
|
|
235
|
+
<label
|
|
236
|
+
className="label-file-widget-input"
|
|
237
|
+
htmlFor={`field-${id}`}
|
|
238
|
+
aria-hidden="true"
|
|
239
|
+
onClick={(e) => e.stopPropagation()}
|
|
240
|
+
>
|
|
209
241
|
{value
|
|
210
242
|
? intl.formatMessage(messages.replaceFile)
|
|
211
243
|
: intl.formatMessage(messages.addNewFile)}
|
|
212
|
-
<span className="visually-hidden">
|
|
213
|
-
{intl.formatMessage(messages.dragAndDropActionA11y)}
|
|
214
|
-
</span>
|
|
215
244
|
</label>
|
|
245
|
+
<span id={`field-${id}-status`} className="visually-hidden">
|
|
246
|
+
{statusTextA11y}
|
|
247
|
+
</span>
|
|
248
|
+
{errorTextA11y && (
|
|
249
|
+
<span role="alert" className="visually-hidden">
|
|
250
|
+
{errorTextA11y}
|
|
251
|
+
</span>
|
|
252
|
+
)}
|
|
216
253
|
<input
|
|
217
254
|
{...getInputProps({
|
|
218
255
|
type: 'file',
|
|
219
256
|
style: { display: 'none' },
|
|
220
257
|
})}
|
|
221
258
|
id={`field-${id}`}
|
|
259
|
+
{...(props.required && { 'aria-required': true })}
|
|
260
|
+
{...(props.error?.length > 0 && { 'aria-invalid': true })}
|
|
222
261
|
name={id}
|
|
223
|
-
type="file"
|
|
224
262
|
disabled={isDisabled}
|
|
225
263
|
/>
|
|
226
264
|
</div>
|
|
227
265
|
)}
|
|
228
266
|
</Dropzone>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<UniversalLink
|
|
267
|
+
{value && (
|
|
268
|
+
<div className="field-file-name">
|
|
269
|
+
<UniversalLink
|
|
270
|
+
href={value.download}
|
|
271
|
+
aria-label={intl.formatMessage(messages.downloadFile, {
|
|
272
|
+
filename: value.filename,
|
|
273
|
+
})}
|
|
274
|
+
download={true}
|
|
275
|
+
>
|
|
232
276
|
{value.filename}
|
|
233
277
|
</UniversalLink>
|
|
234
|
-
)}
|
|
235
|
-
{value && (
|
|
236
278
|
<Button
|
|
237
279
|
type="button"
|
|
238
280
|
icon
|
|
@@ -247,8 +289,8 @@ const FileWidget = (props) => {
|
|
|
247
289
|
>
|
|
248
290
|
<Icon name={deleteSVG} size="20px" />
|
|
249
291
|
</Button>
|
|
250
|
-
|
|
251
|
-
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
252
294
|
</FormFieldWrapper>
|
|
253
295
|
);
|
|
254
296
|
};
|
|
@@ -263,6 +305,7 @@ FileWidget.propTypes = {
|
|
|
263
305
|
title: PropTypes.string.isRequired,
|
|
264
306
|
description: PropTypes.string,
|
|
265
307
|
required: PropTypes.bool,
|
|
308
|
+
fieldSet: PropTypes.string,
|
|
266
309
|
error: PropTypes.arrayOf(PropTypes.string),
|
|
267
310
|
value: PropTypes.shape({
|
|
268
311
|
'@type': PropTypes.string,
|
|
@@ -280,6 +323,7 @@ FileWidget.propTypes = {
|
|
|
280
323
|
FileWidget.defaultProps = {
|
|
281
324
|
description: null,
|
|
282
325
|
required: false,
|
|
326
|
+
fieldSet: null,
|
|
283
327
|
error: [],
|
|
284
328
|
value: null,
|
|
285
329
|
};
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* FormFieldWrapper component.
|
|
3
3
|
* @module components/manage/Widgets/FormFieldWrapper
|
|
4
4
|
*/
|
|
5
|
-
import React from 'react';
|
|
5
|
+
import React, { Children, isValidElement, cloneElement } from 'react';
|
|
6
6
|
import PropTypes from 'prop-types';
|
|
7
|
-
import { Form, Grid, Icon as IconOld, Label } from 'semantic-ui-react';
|
|
7
|
+
import { Form, Grid, Icon as IconOld, Input, Label } from 'semantic-ui-react';
|
|
8
8
|
import map from 'lodash/map';
|
|
9
9
|
import cx from 'classnames';
|
|
10
10
|
import { defineMessages, useIntl } from 'react-intl';
|
|
@@ -59,7 +59,15 @@ const FormFieldWrapper = ({
|
|
|
59
59
|
|
|
60
60
|
const wdg = (
|
|
61
61
|
<>
|
|
62
|
-
{children
|
|
62
|
+
{Children.map(children, (child) => {
|
|
63
|
+
if (isValidElement(child) && required && child.type === Input) {
|
|
64
|
+
return cloneElement(child, {
|
|
65
|
+
'aria-required': true,
|
|
66
|
+
'aria-invalid': !!(error && error.length > 0),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return child;
|
|
70
|
+
})}
|
|
63
71
|
|
|
64
72
|
<div
|
|
65
73
|
aria-live="polite"
|
|
@@ -33,8 +33,8 @@ const messages = defineMessages({
|
|
|
33
33
|
defaultMessage: 'Drop files here ...',
|
|
34
34
|
},
|
|
35
35
|
editFile: {
|
|
36
|
-
id: 'Drop file here to replace the existing file',
|
|
37
|
-
defaultMessage: 'Drop file here to replace the existing file',
|
|
36
|
+
id: 'Drop a file here or click to replace the existing file',
|
|
37
|
+
defaultMessage: 'Drop a file here or click to replace the existing file',
|
|
38
38
|
},
|
|
39
39
|
fileDrag: {
|
|
40
40
|
id: 'Drop file here to upload a new file',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import renderer from 'react-test-renderer';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
3
4
|
import configureStore from 'redux-mock-store';
|
|
4
5
|
import { Provider } from 'react-intl-redux';
|
|
5
6
|
|
|
@@ -30,3 +31,46 @@ test('renders a text widget component', () => {
|
|
|
30
31
|
const json = component.toJSON();
|
|
31
32
|
expect(json).toMatchSnapshot();
|
|
32
33
|
});
|
|
34
|
+
|
|
35
|
+
test('adds aria-required attribute to input when required prop is true', () => {
|
|
36
|
+
const store = mockStore({
|
|
37
|
+
intl: { locale: 'en', messages: {} },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
render(
|
|
41
|
+
<Provider store={store}>
|
|
42
|
+
<TextWidget
|
|
43
|
+
id="my-field"
|
|
44
|
+
title="My field"
|
|
45
|
+
onChange={() => {}}
|
|
46
|
+
onBlur={() => {}}
|
|
47
|
+
onClick={() => {}}
|
|
48
|
+
required={true}
|
|
49
|
+
/>
|
|
50
|
+
</Provider>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('aria-required', 'true');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('adds aria-invalid attribute to input when field has errors', () => {
|
|
57
|
+
const store = mockStore({
|
|
58
|
+
intl: { locale: 'en', messages: {} },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
render(
|
|
62
|
+
<Provider store={store}>
|
|
63
|
+
<TextWidget
|
|
64
|
+
id="my-field"
|
|
65
|
+
title="My field"
|
|
66
|
+
onChange={() => {}}
|
|
67
|
+
onBlur={() => {}}
|
|
68
|
+
onClick={() => {}}
|
|
69
|
+
required={true}
|
|
70
|
+
error={['This field is required']}
|
|
71
|
+
/>
|
|
72
|
+
</Provider>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(screen.getByRole('textbox')).toHaveAttribute('aria-invalid', 'true');
|
|
76
|
+
});
|
|
@@ -6,7 +6,7 @@ import { changeLanguage } from '@plone/volto/actions/language/language';
|
|
|
6
6
|
import { toGettextLang, toBackendLang } from '@plone/volto/helpers/Utils/Utils';
|
|
7
7
|
|
|
8
8
|
const MultilingualRedirector = (props) => {
|
|
9
|
-
const { pathname, children } = props;
|
|
9
|
+
const { pathname, contentLanguage, children } = props;
|
|
10
10
|
const [cookies] = useCookies();
|
|
11
11
|
const site = useSelector((state) => state.site.data);
|
|
12
12
|
const isMultilingual = site.features?.multilingual;
|
|
@@ -44,8 +44,11 @@ const MultilingualRedirector = (props) => {
|
|
|
44
44
|
performLanguageSwitch(redirectToLanguage);
|
|
45
45
|
} else {
|
|
46
46
|
const lang = pathname.split('/')[1];
|
|
47
|
+
const contentLanguageMatchesPath =
|
|
48
|
+
!contentLanguage || contentLanguage === lang;
|
|
47
49
|
if (
|
|
48
50
|
availableLanguages?.includes(lang) &&
|
|
51
|
+
contentLanguageMatchesPath &&
|
|
49
52
|
lang !== toBackendLang(currentLanguage)
|
|
50
53
|
) {
|
|
51
54
|
performLanguageSwitch(lang);
|
|
@@ -62,6 +65,7 @@ const MultilingualRedirector = (props) => {
|
|
|
62
65
|
isMultilingual,
|
|
63
66
|
availableLanguages,
|
|
64
67
|
currentLanguage,
|
|
68
|
+
contentLanguage,
|
|
65
69
|
]);
|
|
66
70
|
return pathname === '/' && isMultilingual ? (
|
|
67
71
|
<Redirect to={`/${toBackendLang(redirectToLanguage)}`} />
|
|
@@ -10,6 +10,17 @@ import MultilingualRedirector from './MultilingualRedirector';
|
|
|
10
10
|
const mockStore = configureStore();
|
|
11
11
|
|
|
12
12
|
describe('MultilingualRedirector', () => {
|
|
13
|
+
const renderComponent = (store, props) =>
|
|
14
|
+
renderer.create(
|
|
15
|
+
<Provider store={store}>
|
|
16
|
+
<CookiesProvider>
|
|
17
|
+
<MemoryRouter>
|
|
18
|
+
<MultilingualRedirector {...props} />
|
|
19
|
+
</MemoryRouter>
|
|
20
|
+
</CookiesProvider>
|
|
21
|
+
</Provider>,
|
|
22
|
+
);
|
|
23
|
+
|
|
13
24
|
it('renders a MultilingualRedirector component', () => {
|
|
14
25
|
const store = mockStore({
|
|
15
26
|
site: {
|
|
@@ -20,16 +31,35 @@ describe('MultilingualRedirector', () => {
|
|
|
20
31
|
messages: {},
|
|
21
32
|
},
|
|
22
33
|
});
|
|
23
|
-
const component =
|
|
24
|
-
<Provider store={store}>
|
|
25
|
-
<CookiesProvider>
|
|
26
|
-
<MemoryRouter>
|
|
27
|
-
<MultilingualRedirector pathname={'/'} />
|
|
28
|
-
</MemoryRouter>
|
|
29
|
-
</CookiesProvider>
|
|
30
|
-
</Provider>,
|
|
31
|
-
);
|
|
34
|
+
const component = renderComponent(store, { pathname: '/' });
|
|
32
35
|
const json = component.toJSON();
|
|
33
36
|
expect(json).toMatchSnapshot();
|
|
34
37
|
});
|
|
38
|
+
|
|
39
|
+
it('does not switch to the path language when content language differs', () => {
|
|
40
|
+
const store = mockStore({
|
|
41
|
+
site: {
|
|
42
|
+
data: {
|
|
43
|
+
features: {
|
|
44
|
+
multilingual: true,
|
|
45
|
+
},
|
|
46
|
+
'plone.available_languages': ['en', 'es'],
|
|
47
|
+
'plone.default_language': 'en',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
intl: {
|
|
51
|
+
locale: 'es',
|
|
52
|
+
messages: {},
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
renderer.act(() => {
|
|
57
|
+
renderComponent(store, {
|
|
58
|
+
pathname: '/en/document',
|
|
59
|
+
contentLanguage: 'es',
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(store.getActions()).toEqual([]);
|
|
64
|
+
});
|
|
35
65
|
});
|
|
@@ -714,8 +714,16 @@ body:not(.has-sidebar):not(.has-sidebar-collapsed) {
|
|
|
714
714
|
.toolbar-compare-translations-wrapper {
|
|
715
715
|
position: relative;
|
|
716
716
|
|
|
717
|
-
.toolbar-button-compare-translations
|
|
718
|
-
|
|
717
|
+
.toolbar-button-compare-translations {
|
|
718
|
+
display: flex;
|
|
719
|
+
width: 42px;
|
|
720
|
+
height: 42px;
|
|
721
|
+
align-items: center;
|
|
722
|
+
justify-content: center;
|
|
723
|
+
|
|
724
|
+
.icon {
|
|
725
|
+
padding: 4px;
|
|
726
|
+
}
|
|
719
727
|
}
|
|
720
728
|
|
|
721
729
|
.compare-languages {
|