From ecc968c5adae8031b20d093083ded159368a401f Mon Sep 17 00:00:00 2001 From: marcuspollio Date: Fri, 25 Sep 2026 14:18:12 +0200 Subject: [PATCH 1/4] Theme: Add nginx redirects template based on Aliases --- config/_default/hugo.yaml | 17 +++- content/en/about/conduct/_index.md | 1 + content/en/about/privacy/_index.md | 1 + content/en/community/calendar/_index.md | 1 + content/en/dev/_index.md | 1 + content/en/donate/_index.md | 1 + content/en/download/_index.md | 1 + content/en/download/addons/_index.md | 1 + content/en/features/_index.md | 1 + content/en/news/_index.md | 1 + themes/trigo/layouts/home.redirects.nginx | 98 +++++++++++++++++++++++ 11 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 themes/trigo/layouts/home.redirects.nginx diff --git a/config/_default/hugo.yaml b/config/_default/hugo.yaml index 0909f30ba44..1a3e934aaec 100644 --- a/config/_default/hugo.yaml +++ b/config/_default/hugo.yaml @@ -177,11 +177,16 @@ cascade: target: path: /dev/** +mediaTypes: + text/nginx: + suffixes: nginx + outputs: home: - html - cms_config - rss + - redirects section: - html taxonomy: @@ -193,9 +198,16 @@ outputFormats: cms_config: baseName: cms-config mediaType: application/yaml - isHTML: false isPlainText: true notAlternative: true + redirects: + baseName: .nginx/301 + mediaType: text/nginx + rel: nginx + isPlainText: true + notAlternative: true + root: true + ugly: true # Keep for local dev. Production build uses --minify. minify: @@ -273,4 +285,5 @@ caches: images: dir: :cacheDir/images -disableHugoGeneratorInject: true \ No newline at end of file +disableHugoGeneratorInject: true +disableAliases: true \ No newline at end of file diff --git a/content/en/about/conduct/_index.md b/content/en/about/conduct/_index.md index fa588a9d17e..052f6267418 100644 --- a/content/en/about/conduct/_index.md +++ b/content/en/about/conduct/_index.md @@ -2,5 +2,6 @@ title: FreeCAD Code of Conduct linkTitle: Conduct draft: true +aliases: ["/codeofconduct"] redirect: https://github.com/FreeCAD/FreeCAD/blob/main/CODE_OF_CONDUCT.md --- diff --git a/content/en/about/privacy/_index.md b/content/en/about/privacy/_index.md index ff9596cab57..57277ad0c3c 100644 --- a/content/en/about/privacy/_index.md +++ b/content/en/about/privacy/_index.md @@ -2,5 +2,6 @@ title: FreeCAD Privacy Policy linkTitle: Privacy draft: true +aliases: ["/privacy"] redirect: https://github.com/FreeCAD/FreeCAD/blob/main/PRIVACY_POLICY.md --- diff --git a/content/en/community/calendar/_index.md b/content/en/community/calendar/_index.md index 798489604ff..d799a17e54a 100644 --- a/content/en/community/calendar/_index.md +++ b/content/en/community/calendar/_index.md @@ -1,6 +1,7 @@ --- title: Calendar description: Browse all scheduled happenings. +aliases: ["/events"] weight: 1 icon: date --- diff --git a/content/en/dev/_index.md b/content/en/dev/_index.md index 139ce330fd0..1a59f005a80 100644 --- a/content/en/dev/_index.md +++ b/content/en/dev/_index.md @@ -1,6 +1,7 @@ --- title: Development Handbook description: Start contributing, extending, and shaping the future of the FreeCAD ecosystem. +aliases: ["/contributing"] icon: dev icon_cms: logo_dev --- \ No newline at end of file diff --git a/content/en/donate/_index.md b/content/en/donate/_index.md index 146323a4f97..67775f0cff8 100644 --- a/content/en/donate/_index.md +++ b/content/en/donate/_index.md @@ -2,6 +2,7 @@ title: Support the FreeCAD project linkTitle: Donate description: Donate once or sponsor monthly to make FreeCAD even more awesome! +aliases: ["/sponsor"] icon: heart icon_cms: favorite --- diff --git a/content/en/download/_index.md b/content/en/download/_index.md index 1b518c34996..c53a1f0ec72 100644 --- a/content/en/download/_index.md +++ b/content/en/download/_index.md @@ -2,6 +2,7 @@ title: Launch into design! linkTitle: Download description: Get FreeCAD and start with freedom right away. +aliases: ["/downloads"] icon: download icon_cms: download --- diff --git a/content/en/download/addons/_index.md b/content/en/download/addons/_index.md index c8cc5cb8b56..08ab78fd0c9 100644 --- a/content/en/download/addons/_index.md +++ b/content/en/download/addons/_index.md @@ -1,6 +1,7 @@ --- title: Addons description: Explore FreeCAD ecosystem and take advantage of specialized tools. +aliases: ["/addons"] weight: 3 icon: puzzle --- diff --git a/content/en/features/_index.md b/content/en/features/_index.md index 54c11a5b521..20f9b4eaaa2 100644 --- a/content/en/features/_index.md +++ b/content/en/features/_index.md @@ -2,6 +2,7 @@ title: Innovate without limits linkTitle: Features description: Powerful and open parametric design for everyone. +aliases: ["/features"] icon: stars icon_cms: bolt --- diff --git a/content/en/news/_index.md b/content/en/news/_index.md index b6ba73eac45..abc54beccee 100644 --- a/content/en/news/_index.md +++ b/content/en/news/_index.md @@ -1,6 +1,7 @@ --- title: News description: Browse the latest news +aliases: ["/blog"] icon: news icon_cms: brand_awareness --- diff --git a/themes/trigo/layouts/home.redirects.nginx b/themes/trigo/layouts/home.redirects.nginx new file mode 100644 index 00000000000..e5fa1899c9a --- /dev/null +++ b/themes/trigo/layouts/home.redirects.nginx @@ -0,0 +1,98 @@ +{{- /* +SPDX-License-Identifier: MIT +SPDX-FileCopyrightText: 2026 FreeCAD +SPDX-FileNotice: Part of the Trigo theme for Hugo. +*/ -}} + +{{- /* Nginx redirects: + +- Get all pages with aliases. +- Generate nginx redirects map for each alias. +- Match languages by normalized lang name without locale, e.g. es_CO -> es, zh_TW -> zh. + + /old, /old/, and /old.php -> /new/ + /old.php?lang= -> translated //new/ or default /new/ page if translation missing +*/ -}} + + +{{- if site.IsDefault -}} + + {{- $redirects := dict -}} + {{- $old_locales := slice + "be" + "ca" + "cs" + "de" + "el" + "en" + "es" + "es_CO" + "eu" + "fa" + "fi" + "fr" + "ga" + "hr" + "hu" + "it" + "ja" + "ka" + "ko" + "lo" + "nl" + "pl" + "pt_BR" + "ru" + "sl" + "sr" + "sv" + "ta" + "tr" + "uk" + "zh_CN" + "zh_TW" + -}} + + {{- range $new_page := hugo.Sites.Default.Pages -}} + {{- if .Aliases -}} + {{- $new_default := $new_page.RelPermalink -}} + {{- $new_langs := dict -}} + + {{- range $translation := $new_page.Translations -}} + {{- $normalized := replace (lower $translation.Language.Name) "_" "-" -}} + {{- $lang_name := index (split $normalized "-") 0 -}} + {{- if not (isset $new_langs $lang_name) -}} + {{- $new_langs = merge $new_langs (dict $lang_name $translation.RelPermalink) -}} + {{- end -}} + {{- end -}} + + {{- range $alias := $new_page.Aliases -}} + {{- if findRE `\s` $alias -}} + {{- errorf "Alias %q on %q contains whitespace" $alias $new_page.String -}} + {{- end -}} + + {{- $alias = trim $alias "/" -}} + {{- $php_alias := printf "%s.php" $alias -}} + {{- range $old_source := slice $alias (printf "%s/" $alias) $php_alias -}} + {{- if ne $old_source $new_default -}} + {{- $redirects = merge $redirects (dict $old_source $new_default) -}} + {{- end -}} + {{- end -}} + + {{- range $old_locale := $old_locales -}} + {{- $normalized := replace (lower $old_locale) "_" "-" -}} + {{- $lang_name := index (split $normalized "-") 0 -}} + {{- $new_target := default $new_default (index $new_langs $lang_name) -}} + {{- $old_source := printf "%s?lang=%s" $php_alias $old_locale -}} + {{- if ne $old_source $new_target -}} + {{- $redirects = merge $redirects (dict $old_source $new_target) -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- range $old, $new := $redirects -}} + {{- printf "/%s /%s/;\n" $old (trim $new "/") -}} + {{- end -}} +{{- end -}} From 0e8def6bce20a41fdb60460d9ce5a055f4f59cbf Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 14 Oct 2026 22:24:51 -0500 Subject: [PATCH 2/4] Skip entries that would redirect a page to itself Also add old homepage URLs, and resolve targets the way the menus do. Finally, check against the allowed character set to prevent any sort of injection. --- config/_default/hugo.yaml | 3 ++ themes/trigo/layouts/home.redirects.nginx | 52 +++++++++++++++-------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/config/_default/hugo.yaml b/config/_default/hugo.yaml index 1a3e934aaec..36952d4f2b2 100644 --- a/config/_default/hugo.yaml +++ b/config/_default/hugo.yaml @@ -286,4 +286,7 @@ caches: dir: :cacheDir/images disableHugoGeneratorInject: true +# Aliases are served as nginx 301 redirects from the generated .nginx/301.nginx +# map (themes/trigo/layouts/home.redirects.nginx), not as HTML redirect pages. +# Hosts without that nginx config, e.g. GitHub Pages, have no alias redirects. disableAliases: true \ No newline at end of file diff --git a/themes/trigo/layouts/home.redirects.nginx b/themes/trigo/layouts/home.redirects.nginx index e5fa1899c9a..533c2a5a62d 100644 --- a/themes/trigo/layouts/home.redirects.nginx +++ b/themes/trigo/layouts/home.redirects.nginx @@ -6,18 +6,24 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. {{- /* Nginx redirects: -- Get all pages with aliases. +- Get all pages with aliases. The home page also gets the old /index.php. - Generate nginx redirects map for each alias. +- Resolve targets like site links do, so pages with a `redirect` param point to its target. - Match languages by normalized lang name without locale, e.g. es_CO -> es, zh_TW -> zh. - /old, /old/, and /old.php -> /new/ + /old, /old/, and /old.php -> /new/ (skipped when /old is /new itself) /old.php?lang= -> translated //new/ or default /new/ page if translation missing + /?lang= -> translated home page + +- The output is parsed as nginx config, so every line is checked against a safe character set. */ -}} {{- if site.IsDefault -}} {{- $redirects := dict -}} + {{- $safe_source := `^/[A-Za-z0-9._~/%-]*(\?lang=[A-Za-z_]+)?$` -}} + {{- $safe_target := `^(/|https://)[A-Za-z0-9._~/%:?&=+()-]*$` -}} {{- $old_locales := slice "be" "ca" @@ -54,38 +60,44 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. -}} {{- range $new_page := hugo.Sites.Default.Pages -}} - {{- if .Aliases -}} - {{- $new_default := $new_page.RelPermalink -}} + {{- $aliases := $new_page.Aliases -}} + {{- if $new_page.IsHome -}} + {{- $aliases = $aliases | append "/index" -}} + {{- end -}} + + {{- if $aliases -}} + {{- $new_default := partial "utils/link_redirect.html" $new_page -}} {{- $new_langs := dict -}} {{- range $translation := $new_page.Translations -}} {{- $normalized := replace (lower $translation.Language.Name) "_" "-" -}} {{- $lang_name := index (split $normalized "-") 0 -}} {{- if not (isset $new_langs $lang_name) -}} - {{- $new_langs = merge $new_langs (dict $lang_name $translation.RelPermalink) -}} + {{- $new_langs = merge $new_langs (dict $lang_name (partial "utils/link_redirect.html" $translation)) -}} {{- end -}} {{- end -}} - {{- range $alias := $new_page.Aliases -}} - {{- if findRE `\s` $alias -}} - {{- errorf "Alias %q on %q contains whitespace" $alias $new_page.String -}} - {{- end -}} - + {{- range $alias := $aliases -}} {{- $alias = trim $alias "/" -}} - {{- $php_alias := printf "%s.php" $alias -}} - {{- range $old_source := slice $alias (printf "%s/" $alias) $php_alias -}} - {{- if ne $old_source $new_default -}} + {{- $php_alias := printf "/%s.php" $alias -}} + {{- range $old_source := slice (printf "/%s" $alias) (printf "/%s/" $alias) $php_alias -}} + {{- /* Skip /new and /new/, which would redirect to themselves. */ -}} + {{- if ne (trim $old_source "/") (trim $new_default "/") -}} {{- $redirects = merge $redirects (dict $old_source $new_default) -}} {{- end -}} {{- end -}} + {{- $lang_sources := slice $php_alias -}} + {{- if $new_page.IsHome -}} + {{- $lang_sources = $lang_sources | append "/" -}} + {{- end -}} + {{- range $old_locale := $old_locales -}} {{- $normalized := replace (lower $old_locale) "_" "-" -}} {{- $lang_name := index (split $normalized "-") 0 -}} {{- $new_target := default $new_default (index $new_langs $lang_name) -}} - {{- $old_source := printf "%s?lang=%s" $php_alias $old_locale -}} - {{- if ne $old_source $new_target -}} - {{- $redirects = merge $redirects (dict $old_source $new_target) -}} + {{- range $lang_source := $lang_sources -}} + {{- $redirects = merge $redirects (dict (printf "%s?lang=%s" $lang_source $old_locale) $new_target) -}} {{- end -}} {{- end -}} {{- end -}} @@ -93,6 +105,12 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. {{- end -}} {{- range $old, $new := $redirects -}} - {{- printf "/%s /%s/;\n" $old (trim $new "/") -}} + {{- if not (findRE $safe_source $old) -}} + {{- errorf "Redirect source %q contains characters not allowed in the nginx map" $old -}} + {{- end -}} + {{- if not (findRE $safe_target $new) -}} + {{- errorf "Redirect target %q for %q contains characters not allowed in the nginx map" $new $old -}} + {{- end -}} + {{- printf "%s %s;\n" $old $new -}} {{- end -}} {{- end -}} From de81dd157ec6c7699280c235d4d9b4e5ddcd1e60 Mon Sep 17 00:00:00 2001 From: marcuspollio Date: Wed, 21 Oct 2026 08:34:43 +0200 Subject: [PATCH 3/4] Support redirecting pages with redirect param --- .../_partials/utils/link_redirect.html | 6 +- themes/trigo/layouts/home.redirects.nginx | 66 +++++++++++++------ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/themes/trigo/layouts/_partials/utils/link_redirect.html b/themes/trigo/layouts/_partials/utils/link_redirect.html index 3011f4dc854..34093c3a3b2 100644 --- a/themes/trigo/layouts/_partials/utils/link_redirect.html +++ b/themes/trigo/layouts/_partials/utils/link_redirect.html @@ -21,12 +21,12 @@ {{- $link = .RelPermalink -}} {{- end -}} {{- else -}} - {{- $target := site.GetPage . -}} + {{- $target := $page.Site.GetPage . -}} {{- with $target -}} {{- $link = .RelPermalink -}} {{- else -}} - {{- if and (hasPrefix . $wiki_url) (not site.Language.IsDefault) -}} - {{- $link = printf "%s/%s" . site.Language.Name -}} + {{- if and (hasPrefix . $wiki_url) (not $page.Site.Language.IsDefault) -}} + {{- $link = printf "%s/%s" . $page.Site.Language.Name -}} {{- else -}} {{- $link = . -}} {{- end -}} diff --git a/themes/trigo/layouts/home.redirects.nginx b/themes/trigo/layouts/home.redirects.nginx index 533c2a5a62d..a063d41314f 100644 --- a/themes/trigo/layouts/home.redirects.nginx +++ b/themes/trigo/layouts/home.redirects.nginx @@ -6,19 +6,22 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. {{- /* Nginx redirects: -- Get all pages with aliases. The home page also gets the old /index.php. -- Generate nginx redirects map for each alias. -- Resolve targets like site links do, so pages with a `redirect` param point to its target. +- Get all pages with aliases or redirect param. +- Homepage also gets the old /index.php. +- Resolve targets like site links do, so pages with a `redirect` param point to their target. - Match languages by normalized lang name without locale, e.g. es_CO -> es, zh_TW -> zh. +- Do not include equal old and new paths to avoid circular self-redirecting loop. +- Output is parsed as nginx config, so every line is checked against a safe character set. +- Generate nginx redirects map for each alias or redirect-only page. - /old, /old/, and /old.php -> /new/ (skipped when /old is /new itself) - /old.php?lang= -> translated //new/ or default /new/ page if translation missing - /?lang= -> translated home page + @example /old, /old/, and /old.php -> /new/ + @example /old.php?lang= -> translated //new/ or default /new/ page if translation missing + @example /?lang= -> translated homepage + @example //internal -> https://external.org/ + @example //releases/0-17 -> https://wiki.freecad.org/Release_notes_0.17/ -- The output is parsed as nginx config, so every line is checked against a safe character set. */ -}} - {{- if site.IsDefault -}} {{- $redirects := dict -}} @@ -59,48 +62,64 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. "zh_TW" -}} - {{- range $new_page := hugo.Sites.Default.Pages -}} - {{- $aliases := $new_page.Aliases -}} - {{- if $new_page.IsHome -}} + {{- range $page := hugo.Sites.Default.Pages -}} + {{- $aliases := $page.Aliases -}} + + {{- if $page.IsHome -}} {{- $aliases = $aliases | append "/index" -}} {{- end -}} {{- if $aliases -}} - {{- $new_default := partial "utils/link_redirect.html" $new_page -}} + {{- $key := partial "utils/cache_key.html" $page -}} + {{- $new_redirect := partialCached "utils/link_redirect.html" $page $key -}} {{- $new_langs := dict -}} - {{- range $translation := $new_page.Translations -}} + {{- range $translation := $page.Translations -}} {{- $normalized := replace (lower $translation.Language.Name) "_" "-" -}} {{- $lang_name := index (split $normalized "-") 0 -}} {{- if not (isset $new_langs $lang_name) -}} - {{- $new_langs = merge $new_langs (dict $lang_name (partial "utils/link_redirect.html" $translation)) -}} + {{- $key := partial "utils/cache_key.html" $translation -}} + {{- $lang_target := partialCached "utils/link_redirect.html" $translation $key .Language.Name -}} + {{- $new_langs = merge $new_langs (dict $lang_name $lang_target) -}} {{- end -}} {{- end -}} {{- range $alias := $aliases -}} {{- $alias = trim $alias "/" -}} {{- $php_alias := printf "/%s.php" $alias -}} + {{- range $old_source := slice (printf "/%s" $alias) (printf "/%s/" $alias) $php_alias -}} - {{- /* Skip /new and /new/, which would redirect to themselves. */ -}} - {{- if ne (trim $old_source "/") (trim $new_default "/") -}} - {{- $redirects = merge $redirects (dict $old_source $new_default) -}} - {{- end -}} + {{- $redirects = merge $redirects (dict $old_source $new_redirect) -}} {{- end -}} {{- $lang_sources := slice $php_alias -}} - {{- if $new_page.IsHome -}} + {{- if $page.IsHome -}} {{- $lang_sources = $lang_sources | append "/" -}} {{- end -}} {{- range $old_locale := $old_locales -}} {{- $normalized := replace (lower $old_locale) "_" "-" -}} {{- $lang_name := index (split $normalized "-") 0 -}} - {{- $new_target := default $new_default (index $new_langs $lang_name) -}} + {{- $new_target := default $new_redirect (index $new_langs $lang_name) -}} + {{- range $lang_source := $lang_sources -}} {{- $redirects = merge $redirects (dict (printf "%s?lang=%s" $lang_source $old_locale) $new_target) -}} {{- end -}} {{- end -}} {{- end -}} + + {{- else if and $page.Params.redirect (ne $page.Params.redirect "auto") -}} + {{- $key := partial "utils/cache_key.html" $page -}} + {{- $new_redirect := partialCached "utils/link_redirect.html" $page $key -}} + {{- $page_source := trim $page.RelPermalink "/" -}} + {{- $redirects = merge $redirects (dict (printf "/%s" $page_source) $new_redirect) -}} + + {{- range $translation := $page.Translations -}} + {{- $key := partial "utils/cache_key.html" $translation -}} + {{- $redirect_target := partialCached "utils/link_redirect.html" $translation $key .Language.Name -}} + {{- $translated_source := trim $translation.RelPermalink "/" -}} + {{- $redirects = merge $redirects (dict (printf "/%s" $translated_source) $redirect_target) -}} + {{- end -}} {{- end -}} {{- end -}} @@ -108,9 +127,14 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. {{- if not (findRE $safe_source $old) -}} {{- errorf "Redirect source %q contains characters not allowed in the nginx map" $old -}} {{- end -}} + {{- if not (findRE $safe_target $new) -}} {{- errorf "Redirect target %q for %q contains characters not allowed in the nginx map" $new $old -}} {{- end -}} - {{- printf "%s %s;\n" $old $new -}} + + {{- /* Skip equal old and new paths, which would redirect to themselves. */ -}} + {{- if ne (trim $old "/") (trim $new "/") -}} + {{- printf "%s %s;\n" $old $new -}} + {{- end -}} {{- end -}} {{- end -}} From c6a0d30e1ac591e96cb9f9ec759a36073f3b72c5 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 22 Oct 2026 21:55:53 -0500 Subject: [PATCH 4/4] Fix trailing slash redirects, FPN links, and CoC and Privacy settings --- content/en/about/conduct/_index.md | 3 ++- content/en/about/privacy/_index.md | 3 ++- content/en/community/_index.md | 1 + themes/trigo/layouts/home.redirects.nginx | 14 ++++++++++---- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/content/en/about/conduct/_index.md b/content/en/about/conduct/_index.md index 052f6267418..e96ec445a4d 100644 --- a/content/en/about/conduct/_index.md +++ b/content/en/about/conduct/_index.md @@ -1,7 +1,8 @@ --- title: FreeCAD Code of Conduct linkTitle: Conduct -draft: true +build: + render: never aliases: ["/codeofconduct"] redirect: https://github.com/FreeCAD/FreeCAD/blob/main/CODE_OF_CONDUCT.md --- diff --git a/content/en/about/privacy/_index.md b/content/en/about/privacy/_index.md index 57277ad0c3c..1fecd5210d5 100644 --- a/content/en/about/privacy/_index.md +++ b/content/en/about/privacy/_index.md @@ -1,7 +1,8 @@ --- title: FreeCAD Privacy Policy linkTitle: Privacy -draft: true +build: + render: never aliases: ["/privacy"] redirect: https://github.com/FreeCAD/FreeCAD/blob/main/PRIVACY_POLICY.md --- diff --git a/content/en/community/_index.md b/content/en/community/_index.md index 54e718a3d0f..ba3a19a25da 100644 --- a/content/en/community/_index.md +++ b/content/en/community/_index.md @@ -1,6 +1,7 @@ --- title: Community description: Get in touch with the community around the world. +aliases: ["/professional-network"] icon: people icon_cms: groups --- diff --git a/themes/trigo/layouts/home.redirects.nginx b/themes/trigo/layouts/home.redirects.nginx index a063d41314f..51401c53ae6 100644 --- a/themes/trigo/layouts/home.redirects.nginx +++ b/themes/trigo/layouts/home.redirects.nginx @@ -17,8 +17,8 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. @example /old, /old/, and /old.php -> /new/ @example /old.php?lang= -> translated //new/ or default /new/ page if translation missing @example /?lang= -> translated homepage - @example //internal -> https://external.org/ - @example //releases/0-17 -> https://wiki.freecad.org/Release_notes_0.17/ + @example //internal and //internal/ -> https://external.org/ + @example //releases/0-17 and //releases/0-17/ -> https://wiki.freecad.org/Release_notes_0.17/ */ -}} @@ -112,13 +112,19 @@ SPDX-FileNotice: Part of the Trigo theme for Hugo. {{- $key := partial "utils/cache_key.html" $page -}} {{- $new_redirect := partialCached "utils/link_redirect.html" $page $key -}} {{- $page_source := trim $page.RelPermalink "/" -}} - {{- $redirects = merge $redirects (dict (printf "/%s" $page_source) $new_redirect) -}} + + {{- /* Include the canonical trailing-slash path, which would otherwise serve the placeholder page. */ -}} + {{- range $old_source := slice (printf "/%s" $page_source) (printf "/%s/" $page_source) -}} + {{- $redirects = merge $redirects (dict $old_source $new_redirect) -}} + {{- end -}} {{- range $translation := $page.Translations -}} {{- $key := partial "utils/cache_key.html" $translation -}} {{- $redirect_target := partialCached "utils/link_redirect.html" $translation $key .Language.Name -}} {{- $translated_source := trim $translation.RelPermalink "/" -}} - {{- $redirects = merge $redirects (dict (printf "/%s" $translated_source) $redirect_target) -}} + {{- range $old_source := slice (printf "/%s" $translated_source) (printf "/%s/" $translated_source) -}} + {{- $redirects = merge $redirects (dict $old_source $redirect_target) -}} + {{- end -}} {{- end -}} {{- end -}} {{- end -}}