From a37c7c0cf9ff99bf988d325bddc0e7faa2d7f65e Mon Sep 17 00:00:00 2001 From: rofe Date: Thu, 20 Mar 2025 10:57:53 +0100 Subject: [PATCH 1/4] feat(metadata): hreflang links --- src/steps/render.js | 12 +++++++- .../page-metadata-hreflang-invalid.html | 27 ++++++++++++++++++ .../content/page-metadata-hreflang-invalid.md | 11 ++++++++ .../content/page-metadata-hreflang.html | 28 +++++++++++++++++++ .../content/page-metadata-hreflang.md | 11 ++++++++ test/rendering.test.js | 10 +++++++ 6 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/content/page-metadata-hreflang-invalid.html create mode 100644 test/fixtures/content/page-metadata-hreflang-invalid.md create mode 100644 test/fixtures/content/page-metadata-hreflang.html create mode 100644 test/fixtures/content/page-metadata-hreflang.md diff --git a/src/steps/render.js b/src/steps/render.js index 2532f589..4ab2f701 100644 --- a/src/steps/render.js +++ b/src/steps/render.js @@ -17,6 +17,8 @@ import rehypeParse from 'rehype-parse'; import { cleanupHeaderValue } from '@adobe/helix-shared-utils'; import { contentSecurityPolicyOnAST } from './csp.js'; +const LANG_REGEX = /^[a-z]{2}([-_]{1}[a-z]{2})?$/i; + function appendElement($parent, $el) { if ($el) { $parent.children.push($el); @@ -76,12 +78,20 @@ export default async function render(state, req, res) { continue; } if (name.toLowerCase() === 'html-lang') { - if (/^[a-z]{2}([-_]{1}[a-z]{2})?$/i.test(value)) { + if (LANG_REGEX.test(value)) { htmlLang = value; } // eslint-disable-next-line no-continue continue; } + if (name.toLowerCase().startsWith('hreflang-')) { + const lang = name.substring(9); + if (LANG_REGEX.test(lang)) { + appendElement($head, createElement('link', 'rel', 'alternate', 'hreflang', lang, 'href', value)); + } + // eslint-disable-next-line no-continue + continue; + } const attr = name.includes(':') && !name.startsWith('twitter:') ? 'property' : 'name'; if (Array.isArray(value)) { for (const v of value) { diff --git a/test/fixtures/content/page-metadata-hreflang-invalid.html b/test/fixtures/content/page-metadata-hreflang-invalid.html new file mode 100644 index 00000000..71fe8436 --- /dev/null +++ b/test/fixtures/content/page-metadata-hreflang-invalid.html @@ -0,0 +1,27 @@ + + + Home | Helix Project Boilerplate + + + + + + + + + + + + + + +
+
+
+

HTML lang test

+

This is great.

+
+
+ + + \ No newline at end of file diff --git a/test/fixtures/content/page-metadata-hreflang-invalid.md b/test/fixtures/content/page-metadata-hreflang-invalid.md new file mode 100644 index 00000000..7a9744f3 --- /dev/null +++ b/test/fixtures/content/page-metadata-hreflang-invalid.md @@ -0,0 +1,11 @@ +# HTML lang test + +This is great. + ++-------------------------------------------------------------------------------------------+ +| Metadata | ++================+==========================================================================+ +| title | Home \| Helix Project Boilerplate | ++----------------+--------------------------------------------------------------------------| +| hreflang-x | https://helix-pages.com/x/page-metadata-hreflang | ++----------------+--------------------------------------------------------------------------+ diff --git a/test/fixtures/content/page-metadata-hreflang.html b/test/fixtures/content/page-metadata-hreflang.html new file mode 100644 index 00000000..74b4b7af --- /dev/null +++ b/test/fixtures/content/page-metadata-hreflang.html @@ -0,0 +1,28 @@ + + + Home | Helix Project Boilerplate + + + + + + + + + + + + + + + +
+
+
+

HTML lang test

+

This is great.

+
+
+ + + \ No newline at end of file diff --git a/test/fixtures/content/page-metadata-hreflang.md b/test/fixtures/content/page-metadata-hreflang.md new file mode 100644 index 00000000..2a491a86 --- /dev/null +++ b/test/fixtures/content/page-metadata-hreflang.md @@ -0,0 +1,11 @@ +# HTML lang test + +This is great. + ++-------------------------------------------------------------------------------------------+ +| Metadata | ++================+==========================================================================+ +| title | Home \| Helix Project Boilerplate | ++----------------+--------------------------------------------------------------------------| +| hreflang-de | https://helix-pages.com/de/page-metadata-hreflang | ++----------------+--------------------------------------------------------------------------+ diff --git a/test/rendering.test.js b/test/rendering.test.js index c2136020..a998d814 100644 --- a/test/rendering.test.js +++ b/test/rendering.test.js @@ -535,6 +535,16 @@ describe('Rendering', () => { config = DEFAULT_CONFIG_EMPTY; await testRender('page-metadata-htmllang-invalid', ':scope'); }); + + it('injects hreflang link', async () => { + config = DEFAULT_CONFIG_EMPTY; + await testRender('page-metadata-hreflang', ':scope'); + }); + + it('rejects invalid hreflang', async () => { + config = DEFAULT_CONFIG_EMPTY; + await testRender('page-metadata-hreflang-invalid', ':scope'); + }); }); describe('Miscellaneous', () => { From 66c356e8e01e1cf09ac416a69d4b8db3a159c84a Mon Sep 17 00:00:00 2001 From: rofe Date: Thu, 20 Mar 2025 11:20:45 +0100 Subject: [PATCH 2/4] chore: uppercase country code --- src/steps/render.js | 4 +++- test/fixtures/content/page-metadata-hreflang.html | 1 + test/fixtures/content/page-metadata-hreflang.md | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/steps/render.js b/src/steps/render.js index 4ab2f701..b4dac80a 100644 --- a/src/steps/render.js +++ b/src/steps/render.js @@ -85,7 +85,9 @@ export default async function render(state, req, res) { continue; } if (name.toLowerCase().startsWith('hreflang-')) { - const lang = name.substring(9); + const lang = name + .substring(9) // cut off prefix + .replace(/[-_]{1}[a-z]{2}$/i, (str) => str.toUpperCase()); // uppercase country code if (LANG_REGEX.test(lang)) { appendElement($head, createElement('link', 'rel', 'alternate', 'hreflang', lang, 'href', value)); } diff --git a/test/fixtures/content/page-metadata-hreflang.html b/test/fixtures/content/page-metadata-hreflang.html index 74b4b7af..4731077e 100644 --- a/test/fixtures/content/page-metadata-hreflang.html +++ b/test/fixtures/content/page-metadata-hreflang.html @@ -10,6 +10,7 @@ + diff --git a/test/fixtures/content/page-metadata-hreflang.md b/test/fixtures/content/page-metadata-hreflang.md index 2a491a86..801e59be 100644 --- a/test/fixtures/content/page-metadata-hreflang.md +++ b/test/fixtures/content/page-metadata-hreflang.md @@ -7,5 +7,7 @@ This is great. +================+==========================================================================+ | title | Home \| Helix Project Boilerplate | +----------------+--------------------------------------------------------------------------| -| hreflang-de | https://helix-pages.com/de/page-metadata-hreflang | +| hreflang-de | | ++----------------+--------------------------------------------------------------------------+ +| hreflang-fr-FR | | +----------------+--------------------------------------------------------------------------+ From eaf9139a0d3257d98d59d34fe98d8a187101ad5d Mon Sep 17 00:00:00 2001 From: rofe Date: Thu, 20 Mar 2025 11:28:30 +0100 Subject: [PATCH 3/4] chore: use all lowercase for locale --- src/steps/render.js | 17 ++++++++++++----- .../content/page-metadata-hreflang.html | 2 +- .../content/page-metadata-htmllang.html | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/steps/render.js b/src/steps/render.js index b4dac80a..4d852634 100644 --- a/src/steps/render.js +++ b/src/steps/render.js @@ -19,6 +19,12 @@ import { contentSecurityPolicyOnAST } from './csp.js'; const LANG_REGEX = /^[a-z]{2}([-_]{1}[a-z]{2})?$/i; +function formatLang(lang) { + return lang + .replace('_', '-') + .toLowerCase(); +} + function appendElement($parent, $el) { if ($el) { $parent.children.push($el); @@ -79,17 +85,18 @@ export default async function render(state, req, res) { } if (name.toLowerCase() === 'html-lang') { if (LANG_REGEX.test(value)) { - htmlLang = value; + htmlLang = formatLang(value); } // eslint-disable-next-line no-continue continue; } if (name.toLowerCase().startsWith('hreflang-')) { - const lang = name - .substring(9) // cut off prefix - .replace(/[-_]{1}[a-z]{2}$/i, (str) => str.toUpperCase()); // uppercase country code + const lang = name.substring(9); if (LANG_REGEX.test(lang)) { - appendElement($head, createElement('link', 'rel', 'alternate', 'hreflang', lang, 'href', value)); + appendElement( + $head, + createElement('link', 'rel', 'alternate', 'hreflang', formatLang(lang), 'href', value), + ); } // eslint-disable-next-line no-continue continue; diff --git a/test/fixtures/content/page-metadata-hreflang.html b/test/fixtures/content/page-metadata-hreflang.html index 4731077e..90afa22d 100644 --- a/test/fixtures/content/page-metadata-hreflang.html +++ b/test/fixtures/content/page-metadata-hreflang.html @@ -10,7 +10,7 @@ - + diff --git a/test/fixtures/content/page-metadata-htmllang.html b/test/fixtures/content/page-metadata-htmllang.html index ed3841fe..74074db8 100644 --- a/test/fixtures/content/page-metadata-htmllang.html +++ b/test/fixtures/content/page-metadata-htmllang.html @@ -1,4 +1,4 @@ - + Home | Helix Project Boilerplate From ff2a75ccb1a14a42781310f00b0d51e5bf93a362 Mon Sep 17 00:00:00 2001 From: rofe Date: Thu, 20 Mar 2025 11:53:48 +0100 Subject: [PATCH 4/4] chore: optimize regex --- src/steps/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/steps/render.js b/src/steps/render.js index 4d852634..4a612b79 100644 --- a/src/steps/render.js +++ b/src/steps/render.js @@ -17,7 +17,7 @@ import rehypeParse from 'rehype-parse'; import { cleanupHeaderValue } from '@adobe/helix-shared-utils'; import { contentSecurityPolicyOnAST } from './csp.js'; -const LANG_REGEX = /^[a-z]{2}([-_]{1}[a-z]{2})?$/i; +const LANG_REGEX = /^[a-z]{2}(?:[-_][a-z]{2})?$/i; function formatLang(lang) { return lang