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
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..9af027a5cf
--- /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..7ebb9f0aa0
--- /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 */}