From ce009e3aa9fdc0e9f8d2d209d0d4df591707a094 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 6 Apr 2026 18:01:22 +0200 Subject: [PATCH] Fix auto-creation of root sections in multilingual sites Fixes #14681 Co-authored-by: Joe Mooring --- hugolib/content_map_page_assembler.go | 55 ++++++++++++++++++++++--- hugolib/language_content_dir_test.go | 59 +++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/hugolib/content_map_page_assembler.go b/hugolib/content_map_page_assembler.go index 5a61795c7..db0fb0ede 100644 --- a/hugolib/content_map_page_assembler.go +++ b/hugolib/content_map_page_assembler.go @@ -497,14 +497,14 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error { isTaxonomy := !a.h.getFirstTaxonomyConfig(s).IsZero() isRootSection := !isTaxonomy && level == 1 && cnh.isBranchNode(n) - - if isRootSection { - // This is a root section. - a.seenRootSections.SetIfAbsent(cnh.PathInfo(n).Section(), true) - } else if !isTaxonomy { + if !isTaxonomy { p := cnh.PathInfo(n) rootSection := p.Section() _, err := a.seenRootSections.GetOrCreate(rootSection, func() (bool, error) { + if isRootSection { + return true, nil + } + // Try to preserve the original casing if possible. sectionUnnormalized := p.Unnormalized().Section() rootSectionPath := a.h.Conf.PathParser().Parse(files.ComponentFolderContent, "/"+sectionUnnormalized+"/_index.md") @@ -520,6 +520,51 @@ func (a *allPagesAssembler) doCreatePages(prefix string, depth int) error { return true, err } treePages.InsertRaw(rootSectionPath.Base(), rootSectionPages) + + // Collect vectors from descendant pages to create section + // pages for all languages that have content in this section. + nm, replaced := contentNodeToContentNodesPage(rootSectionPages) + if replaced { + treePages.InsertRaw(rootSectionPath.Base(), nm) + } + missingVectors := sitesmatrix.Vectors{} + pw.WalkContext.AddEventListener("sitesmatrix", rootSectionPath.Base(), + func(e *doctree.Event[contentNode]) { + if cnh.isBranchNode(e.Source) && !a.h.getFirstTaxonomyConfig(e.Path).IsZero() { + return + } + n := e.Source + e.StopPropagation() + n.forEeachContentNode( + func(vec sitesmatrix.Vector, nn contentNode) bool { + if _, found := nm[vec]; !found { + missingVectors[vec] = struct{}{} + } + return true + }) + }, + ) + pw.WalkContext.HooksPost().Push( + func() error { + if len(missingVectors) > 0 { + vec := missingVectors.VectorSample() + nm[vec] = &pageMetaSource{ + pathInfo: rootSectionPath, + sitesMatrixBase: missingVectors, + sitesMatrixBaseOnly: true, + pageConfigSource: &pagemeta.PageConfigEarly{ + Kind: kinds.KindSection, + }, + } + _, _, err := transformPages(rootSectionPath.Base(), nm, cascades) + if err != nil { + return err + } + } + return nil + }, + ) + return true, nil }) if err != nil { diff --git a/hugolib/language_content_dir_test.go b/hugolib/language_content_dir_test.go index 53bd8de2c..888f592f5 100644 --- a/hugolib/language_content_dir_test.go +++ b/hugolib/language_content_dir_test.go @@ -171,6 +171,65 @@ title: "p4 theme (nl)" b.AssertFileContent("public/en/index.html", `home (en): en: p1 (en)|p2 (en)|p3 (en)|:END`) } +// Issue 14681 +func TestPublishMultilingualSectionCreation(t *testing.T) { + t.Parallel() + + files := ` +-- hugo.toml -- +capitalizeListTitles = false +pluralizeListTitles = false +disableKinds = ['rss','sitemap','taxonomy','term'] + +defaultContentLanguage = "fr" +defaultContentLanguageInSubdir = true + +[languages.fr] +contentDir = "content/fr" +weight = 1 + +[languages.en] +contentDir = "content/en" +weight = 2 + +[languages.de] +contentDir = "content/de" +weight = 3 +-- layouts/home.html -- +HOME {{ .Language.Name }} +-- layouts/page.html -- +{{ .Title }} {{ .Language.Name }} +-- layouts/section.html -- +{{ .Title }} {{ .Language.Name }} +-- content/de/s1/p1.md -- +--- +title: p1 +--- +-- content/en/s1/p2.md -- +--- +title: p2 +--- +-- content/fr/s1/p3.md -- +--- +title: p3 +--- +` + + b := Test(t, files) + + b.AssertFileContent("public/de/index.html", "HOME de") + b.AssertFileContent("public/de/s1/index.html", "s1 de") + b.AssertFileContent("public/de/s1/p1/index.html", "p1 de") + + b.AssertFileContent("public/en/index.html", "HOME en") + b.AssertFileContent("public/en/s1/index.html", "s1 en") // fail (file does not exist) + b.AssertFileContent("public/en/s1/p2/index.html", "p2 en") + + b.AssertFileContent("public/fr/index.html", "HOME fr") + b.AssertFileContent("public/fr/s1/index.html", "s1 fr") // fail (file does not exist) + b.AssertFileContent("public/fr/s1/p3/index.html", "p3 fr") +} + // Issue 13993 func TestIssue13993(t *testing.T) { t.Parallel() -- 2.39.5