From 577e175941f0f881e627d115effdbf8a7bdc8ce1 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Sat, 18 Jan 2025 13:41:36 +0100 Subject: [PATCH 1/4] feat: provide an Alternate language content links --- .../AlternateHrefLangs/AlternateHrefLangs.jsx | 23 +++ .../AlternateHrefLangs.test.jsx | 135 ++++++++++++++++++ .../volto/src/components/theme/View/View.jsx | 2 + 3 files changed, 160 insertions(+) create mode 100644 packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx create mode 100644 packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx diff --git a/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx new file mode 100644 index 0000000000..0791e08067 --- /dev/null +++ b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx @@ -0,0 +1,23 @@ +import config from '@plone/volto/registry'; +import Helmet from '@plone/volto/helpers/Helmet/Helmet'; + +const AlternateHrefLangs = (props) => { + const { content } = props; + return ( + + {config.settings.isMultilingual && + content['@components']?.translations?.items.map((item, key) => { + return ( + + ); + })} + + ); +}; + +export { AlternateHrefLangs }; diff --git a/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx new file mode 100644 index 0000000000..e67dbcc082 --- /dev/null +++ b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx @@ -0,0 +1,135 @@ +import React from 'react'; +import Helmet from '@plone/volto/helpers/Helmet/Helmet'; + +import renderer from 'react-test-renderer'; +import configureStore from 'redux-mock-store'; +import { Provider } from 'react-intl-redux'; +import config from '@plone/volto/registry'; + +import { AlternateHrefLangs } from './AlternateHrefLangs'; + +const mockStore = configureStore(); + +describe('AlternateHrefLangs', () => { + beforeEach(() => {}); + it('non multilingual site, renders nothing', () => { + config.settings.isMultilingual = false; + const content = { + '@id': '/', + '@components': {}, + }; + const store = mockStore({ + intl: { + locale: 'en', + messages: {}, + }, + }); + // We need to force the component rendering + // to fill the Helmet + renderer.create( + + + , + ); + + const helmetLinks = Helmet.peek().linkTags; + expect(helmetLinks.length).toBe(0); + }); + it('multilingual site, with some translations', () => { + config.settings.isMultilingual = true; + config.settings.supportedLanguages = ['en', 'es', 'eu']; + + const content = { + '@components': { + translations: { + items: [ + { '@id': '/en', language: 'en' }, + { '@id': '/es', language: 'es' }, + ], + }, + }, + }; + + const store = mockStore({ + intl: { + locale: 'en', + messages: {}, + }, + }); + + // We need to force the component rendering + // to fill the Helmet + renderer.create( + + <> + + + , + ); + const helmetLinks = Helmet.peek().linkTags; + + expect(helmetLinks.length).toBe(2); + + expect(helmetLinks).toContainEqual({ + rel: 'alternate', + href: '/es', + hreflang: 'es', + }); + expect(helmetLinks).toContainEqual({ + rel: 'alternate', + href: '/en', + hreflang: 'en', + }); + }); + it('multilingual site, with all available translations', () => { + config.settings.isMultilingual = true; + config.settings.supportedLanguages = ['en', 'es', 'eu']; + const store = mockStore({ + intl: { + locale: 'en', + messages: {}, + }, + }); + + const content = { + '@components': { + translations: { + items: [ + { '@id': '/en', language: 'en' }, + { '@id': '/eu', language: 'eu' }, + { '@id': '/es', language: 'es' }, + ], + }, + }, + }; + + // We need to force the component rendering + // to fill the Helmet + renderer.create( + + + , + ); + + const helmetLinks = Helmet.peek().linkTags; + + // We expect having 3 links + expect(helmetLinks.length).toBe(3); + + expect(helmetLinks).toContainEqual({ + rel: 'alternate', + href: '/eu', + hreflang: 'eu', + }); + expect(helmetLinks).toContainEqual({ + rel: 'alternate', + href: '/es', + hreflang: 'es', + }); + expect(helmetLinks).toContainEqual({ + rel: 'alternate', + href: '/en', + hreflang: 'en', + }); + }); +}); diff --git a/packages/volto/src/components/theme/View/View.jsx b/packages/volto/src/components/theme/View/View.jsx index 902204c869..da8e9a7d82 100644 --- a/packages/volto/src/components/theme/View/View.jsx +++ b/packages/volto/src/components/theme/View/View.jsx @@ -21,6 +21,7 @@ import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass'; import { getBaseUrl, flattenToAppURL } from '@plone/volto/helpers/Url/Url'; import { getLayoutFieldname } from '@plone/volto/helpers/Content/Content'; import { hasApiExpander } from '@plone/volto/helpers/Utils/Utils'; +import { AlternateHrefLangs } from '@plone/volto/components/theme/AlternateHrefLangs/AlternateHrefLangs'; import config from '@plone/volto/registry'; import SlotRenderer from '../SlotRenderer/SlotRenderer'; @@ -234,6 +235,7 @@ class View extends Component { return (
+ {/* Body class if displayName in component is set */} Date: Sat, 18 Jan 2025 18:31:43 +0100 Subject: [PATCH 2/4] changelog --- packages/volto/news/6602.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/volto/news/6602.feature diff --git a/packages/volto/news/6602.feature b/packages/volto/news/6602.feature new file mode 100644 index 0000000000..c0d9292115 --- /dev/null +++ b/packages/volto/news/6602.feature @@ -0,0 +1 @@ +Provide language alternate links @erral From 1bdea9c43aad8c4b24c4ff259acde717f6c443bc Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Wed, 22 Jan 2025 16:05:51 +0100 Subject: [PATCH 3/4] s/hreflang/hrefLang --- .../components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx index 0791e08067..9af027a5cf 100644 --- a/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx +++ b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.jsx @@ -11,7 +11,7 @@ const AlternateHrefLangs = (props) => { ); From 84963e229b7ac379b72dc77bb3aaffb2a3ea03e2 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Wed, 22 Jan 2025 16:36:20 +0100 Subject: [PATCH 4/4] update test --- .../AlternateHrefLangs/AlternateHrefLangs.test.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx index e67dbcc082..7ebb9f0aa0 100644 --- a/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx +++ b/packages/volto/src/components/theme/AlternateHrefLangs/AlternateHrefLangs.test.jsx @@ -73,12 +73,12 @@ describe('AlternateHrefLangs', () => { expect(helmetLinks).toContainEqual({ rel: 'alternate', href: '/es', - hreflang: 'es', + hrefLang: 'es', }); expect(helmetLinks).toContainEqual({ rel: 'alternate', href: '/en', - hreflang: 'en', + hrefLang: 'en', }); }); it('multilingual site, with all available translations', () => { @@ -119,17 +119,17 @@ describe('AlternateHrefLangs', () => { expect(helmetLinks).toContainEqual({ rel: 'alternate', href: '/eu', - hreflang: 'eu', + hrefLang: 'eu', }); expect(helmetLinks).toContainEqual({ rel: 'alternate', href: '/es', - hreflang: 'es', + hrefLang: 'es', }); expect(helmetLinks).toContainEqual({ rel: 'alternate', href: '/en', - hreflang: 'en', + hrefLang: 'en', }); }); });