diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index 60c86d016..6b3522d6f 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -254,7 +254,6 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { doc3 := enSite.RegularPages[2] permalink = doc3.Permalink() - require.NoError(t, err, "permalink call failed") // Note that /superbob is a custom URL set in frontmatter. // We respect that URL literally (it can be /search.json) // and do no not do any language code prefixing. @@ -1155,6 +1154,7 @@ NOTE: without slug, "doc2" should be used, without ".en" as URL title: doc3 weight: 3 publishdate: "2000-01-03" +aliases: [/en/al/alias1,/al/alias2/] tags: - tag2 - tag1 diff --git a/hugolib/hugo_sites_multihost_test.go b/hugolib/hugo_sites_multihost_test.go index 88b306349..af96c766d 100644 --- a/hugolib/hugo_sites_multihost_test.go +++ b/hugolib/hugo_sites_multihost_test.go @@ -68,6 +68,22 @@ languageName = "Nynorsk" assert.Len(s1h.Translations(), 2) assert.Equal("https://example.com/", s1h.Permalink()) + // For “regular multilingual” we kept the aliases pages with url in front matter + // as a literal value that we use as is. + // There is an ambiguity in the guessing. + // For multihost, we never want any content in the root. + // + // check url in front matter: + pageWithURLInFrontMatter := s1.getPage(KindPage, "sect/doc3.en.md") + assert.NotNil(pageWithURLInFrontMatter) + assert.Equal("/superbob", pageWithURLInFrontMatter.URL()) + assert.Equal("/superbob/", pageWithURLInFrontMatter.RelPermalink()) + th.assertFileContent("public/en/superbob/index.html", "doc3|Hello|en") + + // check alias: + th.assertFileContent("public/en/al/alias1/index.html", `content="0; url=https://example.com/superbob/"`) + th.assertFileContent("public/en/al/alias2/index.html", `content="0; url=https://example.com/superbob/"`) + s2 := sites.Sites[1] assert.Equal([]string{"s1", "s2", "frs1", "frs2"}, s2.StaticDirs()) diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go index 083d6eb49..a72304651 100644 --- a/hugolib/page_paths.go +++ b/hugolib/page_paths.go @@ -53,6 +53,9 @@ type targetPathDescriptor struct { // language subdir. LangPrefix string + // Whether this is a multihost multilingual setup. + IsMultihost bool + // Page.URLPath.URL. Will override any Slug etc. for regular pages. URL string @@ -81,12 +84,13 @@ func (p *Page) createTargetPathDescriptor(t output.Format) (targetPathDescriptor func (p *Page) initTargetPathDescriptor() error { d := &targetPathDescriptor{ - PathSpec: p.s.PathSpec, - Kind: p.Kind, - Sections: p.sections, - UglyURLs: p.s.Info.uglyURLs, - Dir: filepath.ToSlash(p.Source.Dir()), - URL: p.URLPath.URL, + PathSpec: p.s.PathSpec, + Kind: p.Kind, + Sections: p.sections, + UglyURLs: p.s.Info.uglyURLs, + Dir: filepath.ToSlash(p.Source.Dir()), + URL: p.URLPath.URL, + IsMultihost: p.s.owner.IsMultihost(), } if p.Slug != "" { @@ -177,7 +181,11 @@ func createTargetPath(d targetPathDescriptor) string { if d.Kind == KindPage { // Always use URL if it's specified if d.URL != "" { - pagePath = filepath.Join(pagePath, d.URL) + if d.IsMultihost && d.LangPrefix != "" && !strings.HasPrefix(d.URL, "/"+d.LangPrefix) { + pagePath = filepath.Join(d.LangPrefix, pagePath, d.URL) + } else { + pagePath = filepath.Join(pagePath, d.URL) + } if strings.HasSuffix(d.URL, "/") || !strings.Contains(d.URL, ".") { pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix()) } diff --git a/hugolib/page_paths_test.go b/hugolib/page_paths_test.go index 80dc390cc..4147ffb85 100644 --- a/hugolib/page_paths_test.go +++ b/hugolib/page_paths_test.go @@ -40,151 +40,156 @@ func TestPageTargetPath(t *testing.T) { BaseName: "_redirects", } - for _, langPrefix := range []string{"", "no"} { - for _, uglyURLs := range []bool{false, true} { - t.Run(fmt.Sprintf("langPrefix=%q,uglyURLs=%t", langPrefix, uglyURLs), - func(t *testing.T) { + for _, multiHost := range []bool{false, true} { + for _, langPrefix := range []string{"", "no"} { + for _, uglyURLs := range []bool{false, true} { + t.Run(fmt.Sprintf("multihost=%t,langPrefix=%q,uglyURLs=%t", multiHost, langPrefix, uglyURLs), + func(t *testing.T) { - tests := []struct { - name string - d targetPathDescriptor - expected string - }{ - {"JSON home", targetPathDescriptor{Kind: KindHome, Type: output.JSONFormat}, "/index.json"}, - {"AMP home", targetPathDescriptor{Kind: KindHome, Type: output.AMPFormat}, "/amp/index.html"}, - {"HTML home", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: output.HTMLFormat}, "/index.html"}, - {"Netlify redirects", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: noExtDelimFormat}, "/_redirects"}, - {"HTML section list", targetPathDescriptor{ - Kind: KindSection, - Sections: []string{"sect1"}, - BaseName: "_index", - Type: output.HTMLFormat}, "/sect1/index.html"}, - {"HTML taxonomy list", targetPathDescriptor{ - Kind: KindTaxonomy, - Sections: []string{"tags", "hugo"}, - BaseName: "_index", - Type: output.HTMLFormat}, "/tags/hugo/index.html"}, - {"HTML taxonomy term", targetPathDescriptor{ - Kind: KindTaxonomy, - Sections: []string{"tags"}, - BaseName: "_index", - Type: output.HTMLFormat}, "/tags/index.html"}, - { - "HTML page", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "mypage", - Sections: []string{"a"}, - Type: output.HTMLFormat}, "/a/b/mypage/index.html"}, - - { - // Issue #3396 - "HTML page with index as base", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "index", - Sections: []string{"a"}, - Type: output.HTMLFormat}, "/a/b/index.html"}, - - { - "HTML page with special chars", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "My Page!", - Type: output.HTMLFormat}, "/a/b/My-Page/index.html"}, - {"RSS home", targetPathDescriptor{Kind: kindRSS, Type: output.RSSFormat}, "/index.xml"}, - {"RSS section list", targetPathDescriptor{ - Kind: kindRSS, - Sections: []string{"sect1"}, - Type: output.RSSFormat}, "/sect1/index.xml"}, - { - "AMP page", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b/c", - BaseName: "myamp", - Type: output.AMPFormat}, "/amp/a/b/c/myamp/index.html"}, - { - "AMP page with URL with suffix", targetPathDescriptor{ - Kind: KindPage, - Dir: "/sect/", - BaseName: "mypage", - URL: "/some/other/url.xhtml", - Type: output.HTMLFormat}, "/some/other/url.xhtml"}, - { - "JSON page with URL without suffix", targetPathDescriptor{ - Kind: KindPage, - Dir: "/sect/", - BaseName: "mypage", - URL: "/some/other/path/", - Type: output.JSONFormat}, "/some/other/path/index.json"}, - { - "JSON page with URL without suffix and no trailing slash", targetPathDescriptor{ - Kind: KindPage, - Dir: "/sect/", - BaseName: "mypage", - URL: "/some/other/path", - Type: output.JSONFormat}, "/some/other/path/index.json"}, - { - "HTML page with expanded permalink", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "mypage", - ExpandedPermalink: "/2017/10/my-title", - Type: output.HTMLFormat}, "/2017/10/my-title/index.html"}, - { - "Paginated HTML home", targetPathDescriptor{ - Kind: KindHome, + tests := []struct { + name string + d targetPathDescriptor + expected string + }{ + {"JSON home", targetPathDescriptor{Kind: KindHome, Type: output.JSONFormat}, "/index.json"}, + {"AMP home", targetPathDescriptor{Kind: KindHome, Type: output.AMPFormat}, "/amp/index.html"}, + {"HTML home", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: output.HTMLFormat}, "/index.html"}, + {"Netlify redirects", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: noExtDelimFormat}, "/_redirects"}, + {"HTML section list", targetPathDescriptor{ + Kind: KindSection, + Sections: []string{"sect1"}, BaseName: "_index", - Type: output.HTMLFormat, - Addends: "page/3"}, "/page/3/index.html"}, - { - "Paginated Taxonomy list", targetPathDescriptor{ + Type: output.HTMLFormat}, "/sect1/index.html"}, + {"HTML taxonomy list", targetPathDescriptor{ Kind: KindTaxonomy, - BaseName: "_index", Sections: []string{"tags", "hugo"}, - Type: output.HTMLFormat, - Addends: "page/3"}, "/tags/hugo/page/3/index.html"}, - { - "Regular page with addend", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "mypage", - Addends: "c/d/e", - Type: output.HTMLFormat}, "/a/b/mypage/c/d/e/index.html"}, - } + BaseName: "_index", + Type: output.HTMLFormat}, "/tags/hugo/index.html"}, + {"HTML taxonomy term", targetPathDescriptor{ + Kind: KindTaxonomy, + Sections: []string{"tags"}, + BaseName: "_index", + Type: output.HTMLFormat}, "/tags/index.html"}, + { + "HTML page", targetPathDescriptor{ + Kind: KindPage, + Dir: "/a/b", + BaseName: "mypage", + Sections: []string{"a"}, + Type: output.HTMLFormat}, "/a/b/mypage/index.html"}, - for i, test := range tests { - test.d.PathSpec = pathSpec - test.d.UglyURLs = uglyURLs - test.d.LangPrefix = langPrefix - test.d.Dir = filepath.FromSlash(test.d.Dir) - isUgly := uglyURLs && !test.d.Type.NoUgly + { + // Issue #3396 + "HTML page with index as base", targetPathDescriptor{ + Kind: KindPage, + Dir: "/a/b", + BaseName: "index", + Sections: []string{"a"}, + Type: output.HTMLFormat}, "/a/b/index.html"}, - expected := test.expected - - // TODO(bep) simplify - if test.d.Kind == KindPage && test.d.BaseName == test.d.Type.BaseName { - - } else if test.d.Kind == KindHome && test.d.Type.Path != "" { - } else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly { - expected = strings.Replace(expected, - "/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix, - "."+test.d.Type.MediaType.Suffix, -1) + { + "HTML page with special chars", targetPathDescriptor{ + Kind: KindPage, + Dir: "/a/b", + BaseName: "My Page!", + Type: output.HTMLFormat}, "/a/b/My-Page/index.html"}, + {"RSS home", targetPathDescriptor{Kind: kindRSS, Type: output.RSSFormat}, "/index.xml"}, + {"RSS section list", targetPathDescriptor{ + Kind: kindRSS, + Sections: []string{"sect1"}, + Type: output.RSSFormat}, "/sect1/index.xml"}, + { + "AMP page", targetPathDescriptor{ + Kind: KindPage, + Dir: "/a/b/c", + BaseName: "myamp", + Type: output.AMPFormat}, "/amp/a/b/c/myamp/index.html"}, + { + "AMP page with URL with suffix", targetPathDescriptor{ + Kind: KindPage, + Dir: "/sect/", + BaseName: "mypage", + URL: "/some/other/url.xhtml", + Type: output.HTMLFormat}, "/some/other/url.xhtml"}, + { + "JSON page with URL without suffix", targetPathDescriptor{ + Kind: KindPage, + Dir: "/sect/", + BaseName: "mypage", + URL: "/some/other/path/", + Type: output.JSONFormat}, "/some/other/path/index.json"}, + { + "JSON page with URL without suffix and no trailing slash", targetPathDescriptor{ + Kind: KindPage, + Dir: "/sect/", + BaseName: "mypage", + URL: "/some/other/path", + Type: output.JSONFormat}, "/some/other/path/index.json"}, + { + "HTML page with expanded permalink", targetPathDescriptor{ + Kind: KindPage, + Dir: "/a/b", + BaseName: "mypage", + ExpandedPermalink: "/2017/10/my-title", + Type: output.HTMLFormat}, "/2017/10/my-title/index.html"}, + { + "Paginated HTML home", targetPathDescriptor{ + Kind: KindHome, + BaseName: "_index", + Type: output.HTMLFormat, + Addends: "page/3"}, "/page/3/index.html"}, + { + "Paginated Taxonomy list", targetPathDescriptor{ + Kind: KindTaxonomy, + BaseName: "_index", + Sections: []string{"tags", "hugo"}, + Type: output.HTMLFormat, + Addends: "page/3"}, "/tags/hugo/page/3/index.html"}, + { + "Regular page with addend", targetPathDescriptor{ + Kind: KindPage, + Dir: "/a/b", + BaseName: "mypage", + Addends: "c/d/e", + Type: output.HTMLFormat}, "/a/b/mypage/c/d/e/index.html"}, } - if test.d.LangPrefix != "" && !(test.d.Kind == KindPage && test.d.URL != "") { - expected = "/" + test.d.LangPrefix + expected + for i, test := range tests { + test.d.PathSpec = pathSpec + test.d.UglyURLs = uglyURLs + test.d.LangPrefix = langPrefix + test.d.IsMultihost = multiHost + test.d.Dir = filepath.FromSlash(test.d.Dir) + isUgly := uglyURLs && !test.d.Type.NoUgly + + expected := test.expected + + // TODO(bep) simplify + if test.d.Kind == KindPage && test.d.BaseName == test.d.Type.BaseName { + + } else if test.d.Kind == KindHome && test.d.Type.Path != "" { + } else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly { + expected = strings.Replace(expected, + "/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix, + "."+test.d.Type.MediaType.Suffix, -1) + } + + if test.d.LangPrefix != "" && !(test.d.Kind == KindPage && test.d.URL != "") { + expected = "/" + test.d.LangPrefix + expected + } else if multiHost && test.d.LangPrefix != "" && test.d.URL != "" { + expected = "/" + test.d.LangPrefix + expected + } + + expected = filepath.FromSlash(expected) + + pagePath := createTargetPath(test.d) + + if pagePath != expected { + t.Fatalf("[%d] [%s] targetPath expected %q, got: %q", i, test.name, expected, pagePath) + } } - - expected = filepath.FromSlash(expected) - - pagePath := createTargetPath(test.d) - - if pagePath != expected { - t.Fatalf("[%d] [%s] targetPath expected %q, got: %q", i, test.name, expected, pagePath) - } - } - }) + }) + } } } } diff --git a/hugolib/site_render.go b/hugolib/site_render.go index 2a5fec7ba..c14592c89 100644 --- a/hugolib/site_render.go +++ b/hugolib/site_render.go @@ -16,6 +16,7 @@ package hugolib import ( "fmt" "path" + "strings" "sync" "github.com/gohugoio/hugo/helpers" @@ -380,6 +381,13 @@ func (s *Site) renderAliases() error { a = path.Join(a, f.Path) } + lang := p.Lang() + + if s.owner.multihost && !strings.HasPrefix(a, "/"+lang) { + // These need to be in its language root. + a = path.Join(lang, a) + } + if err := s.writeDestAlias(a, plink, p); err != nil { return err }