Render main content language in root by default

Fixes #2312
This commit is contained in:
Bjørn Erik Pedersen 2016-08-08 13:55:18 +02:00
parent d953e39e63
commit 8da040342e
12 changed files with 296 additions and 129 deletions

View file

@ -14,6 +14,8 @@ Hugo supports multiple languages side-by-side (added in `Hugo 0.17`). Define the
Example: Example:
``` ```
DefaultContentLanguage = "en"
Languages: Languages:
en: en:
weight: 1 weight: 1
@ -34,7 +36,9 @@ Anything not defined in a `[lang]:` block will fall back to the global
value for that key (like `copyright` for the English (`en`) language in this example). value for that key (like `copyright` for the English (`en`) language in this example).
With the config above, all content, sitemap, RSS feeds, paginations With the config above, all content, sitemap, RSS feeds, paginations
and taxonomy pages will be rendered below `/en` in English, and below `/fr` in French. and taxonomy pages will be rendered below `/` in English (your default content language), and below `/fr` in French.
If you want all of the languages to be put below their respective language code, enable `DefaultContentLanguageInSubdir: true` in your configuration.
Only the obvious non-global options can be overridden per language. Examples of global options are `BaseURL`, `BuildDrafts`, etc. Only the obvious non-global options can be overridden per language. Examples of global options are `BaseURL`, `BuildDrafts`, etc.

View file

@ -31,6 +31,10 @@ type Language struct {
paramsInit sync.Once paramsInit sync.Once
} }
func (l *Language) String() string {
return l.Lang
}
func NewLanguage(lang string) *Language { func NewLanguage(lang string) *Language {
return &Language{Lang: lang, params: make(map[string]interface{})} return &Language{Lang: lang, params: make(map[string]interface{})}
} }

View file

@ -168,21 +168,32 @@ func AbsURL(in string, addLanguage bool) string {
} }
if addLanguage { if addLanguage {
addSlash := in == "" || strings.HasSuffix(in, "/") prefix := getLanguagePrefix()
in = path.Join(getLanguagePrefix(), in)
if addSlash { if prefix != "" {
in += "/" addSlash := in == "" || strings.HasSuffix(in, "/")
in = path.Join(prefix, in)
if addSlash {
in += "/"
}
} }
} }
return MakePermalink(baseURL, in).String() return MakePermalink(baseURL, in).String()
} }
func getLanguagePrefix() string { func getLanguagePrefix() string {
defaultLang := viper.GetString("DefaultContentLanguage")
defaultInSubDir := viper.GetBool("DefaultContentLanguageInSubdir")
if !viper.GetBool("Multilingual") { if !viper.GetBool("Multilingual") {
return "" return ""
} }
return viper.Get("CurrentContentLanguage").(*Language).Lang currentLang := viper.Get("CurrentContentLanguage").(*Language).Lang
if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {
return ""
}
return currentLang
} }
// IsAbsURL determines whether the given path points to an absolute URL. // IsAbsURL determines whether the given path points to an absolute URL.
@ -211,12 +222,15 @@ func RelURL(in string, addLanguage bool) string {
} }
if addLanguage { if addLanguage {
hadSlash := strings.HasSuffix(u, "/") prefix := getLanguagePrefix()
if prefix != "" {
hadSlash := strings.HasSuffix(u, "/")
u = path.Join(getLanguagePrefix(), u) u = path.Join(prefix, u)
if hadSlash { if hadSlash {
u += "/" u += "/"
}
} }
} }

View file

@ -45,19 +45,24 @@ func TestURLize(t *testing.T) {
} }
func TestAbsURL(t *testing.T) { func TestAbsURL(t *testing.T) {
for _, addLanguage := range []bool{true, false} { for _, defaultInSubDir := range []bool{true, false} {
for _, m := range []bool{true, false} { for _, addLanguage := range []bool{true, false} {
for _, l := range []string{"en", "fr"} { for _, m := range []bool{true, false} {
doTestAbsURL(t, addLanguage, m, l) for _, l := range []string{"en", "fr"} {
doTestAbsURL(t, defaultInSubDir, addLanguage, m, l)
}
} }
} }
} }
} }
func doTestAbsURL(t *testing.T, addLanguage, multilingual bool, lang string) { func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, lang string) {
viper.Reset() viper.Reset()
viper.Set("Multilingual", multilingual) viper.Set("Multilingual", multilingual)
viper.Set("CurrentContentLanguage", NewLanguage(lang)) viper.Set("CurrentContentLanguage", NewLanguage(lang))
viper.Set("DefaultContentLanguage", "en")
viper.Set("DefaultContentLanguageInSubdir", defaultInSubDir)
tests := []struct { tests := []struct {
input string input string
baseURL string baseURL string
@ -79,12 +84,17 @@ func doTestAbsURL(t *testing.T, addLanguage, multilingual bool, lang string) {
output := AbsURL(test.input, addLanguage) output := AbsURL(test.input, addLanguage)
expected := test.expected expected := test.expected
if multilingual && addLanguage { if multilingual && addLanguage {
expected = strings.Replace(expected, "MULTI", lang+"/", 1) if !defaultInSubDir && lang == "en" {
expected = strings.Replace(expected, "MULTI", "", 1)
} else {
expected = strings.Replace(expected, "MULTI", lang+"/", 1)
}
} else { } else {
expected = strings.Replace(expected, "MULTI", "", 1) expected = strings.Replace(expected, "MULTI", "", 1)
} }
if output != expected { if output != expected {
t.Errorf("Expected %#v, got %#v\n", expected, output) t.Fatalf("Expected %#v, got %#v\n", expected, output)
} }
} }
} }
@ -106,19 +116,23 @@ func TestIsAbsURL(t *testing.T) {
} }
func TestRelURL(t *testing.T) { func TestRelURL(t *testing.T) {
for _, addLanguage := range []bool{true, false} { for _, defaultInSubDir := range []bool{true, false} {
for _, m := range []bool{true, false} { for _, addLanguage := range []bool{true, false} {
for _, l := range []string{"en", "fr"} { for _, m := range []bool{true, false} {
doTestRelURL(t, addLanguage, m, l) for _, l := range []string{"en", "fr"} {
doTestRelURL(t, defaultInSubDir, addLanguage, m, l)
}
} }
} }
} }
} }
func doTestRelURL(t *testing.T, addLanguage, multilingual bool, lang string) { func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, lang string) {
viper.Reset() viper.Reset()
viper.Set("Multilingual", multilingual) viper.Set("Multilingual", multilingual)
viper.Set("CurrentContentLanguage", NewLanguage(lang)) viper.Set("CurrentContentLanguage", NewLanguage(lang))
viper.Set("DefaultContentLanguage", "en")
viper.Set("DefaultContentLanguageInSubdir", defaultInSubDir)
tests := []struct { tests := []struct {
input string input string
@ -146,7 +160,11 @@ func doTestRelURL(t *testing.T, addLanguage, multilingual bool, lang string) {
expected := test.expected expected := test.expected
if multilingual && addLanguage { if multilingual && addLanguage {
expected = strings.Replace(expected, "MULTI", "/"+lang, 1) if !defaultInSubDir && lang == "en" {
expected = strings.Replace(expected, "MULTI", "", 1)
} else {
expected = strings.Replace(expected, "MULTI", "/"+lang, 1)
}
} else { } else {
expected = strings.Replace(expected, "MULTI", "", 1) expected = strings.Replace(expected, "MULTI", "", 1)
} }

View file

@ -104,4 +104,5 @@ func loadDefaultSettings() {
viper.SetDefault("UseModTimeAsFallback", false) viper.SetDefault("UseModTimeAsFallback", false)
viper.SetDefault("Multilingual", false) viper.SetDefault("Multilingual", false)
viper.SetDefault("DefaultContentLanguage", "en") viper.SetDefault("DefaultContentLanguage", "en")
viper.SetDefault("DefaultContentLanguageInSubdir", false)
} }

View file

@ -32,12 +32,134 @@ func testCommonResetState() {
viper.SetFs(hugofs.Source()) viper.SetFs(hugofs.Source())
loadDefaultSettings() loadDefaultSettings()
// Default is false, but true is easier to use as default in tests
viper.Set("DefaultContentLanguageInSubdir", true)
if err := hugofs.Source().Mkdir("content", 0755); err != nil { if err := hugofs.Source().Mkdir("content", 0755); err != nil {
panic("Content folder creation failed.") panic("Content folder creation failed.")
} }
} }
func TestMultiSitesMainLangInRoot(t *testing.T) {
for _, b := range []bool{false, true} {
doTestMultiSitesMainLangInRoot(t, b)
}
}
func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
testCommonResetState()
viper.Set("DefaultContentLanguageInSubdir", defaultInSubDir)
sites := createMultiTestSites(t, multiSiteTomlConfig)
err := sites.Build(BuildCfg{})
if err != nil {
t.Fatalf("Failed to build sites: %s", err)
}
require.Len(t, sites.Sites, 2)
enSite := sites.Sites[0]
frSite := sites.Sites[1]
require.Equal(t, "/en", enSite.Info.LanguagePrefix)
if defaultInSubDir {
require.Equal(t, "/fr", frSite.Info.LanguagePrefix)
} else {
require.Equal(t, "", frSite.Info.LanguagePrefix)
}
doc1en := enSite.Pages[0]
doc1fr := frSite.Pages[0]
enPerm, _ := doc1en.Permalink()
enRelPerm, _ := doc1en.RelPermalink()
require.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", enPerm)
require.Equal(t, "/blog/en/sect/doc1-slug/", enRelPerm)
frPerm, _ := doc1fr.Permalink()
frRelPerm, _ := doc1fr.RelPermalink()
// Main language in root
require.Equal(t, replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)
require.Equal(t, replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)
assertFileContent(t, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
assertFileContent(t, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
// Check home
if defaultInSubDir {
// should have a redirect on top level.
assertFileContent(t, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
}
assertFileContent(t, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
assertFileContent(t, "public/en/index.html", defaultInSubDir, "Home", "Hello")
// Check list pages
assertFileContent(t, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
assertFileContent(t, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
assertFileContent(t, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
assertFileContent(t, "public/en/tags/tag1/index.html", defaultInSubDir, "List", "Hello")
// Check sitemaps
// Sitemaps behaves different: In a multilanguage setup there will always be a index file and
// one sitemap in each lang folder.
assertFileContent(t, "public/sitemap.xml", true,
"<loc>http:/example.com/blog/en/sitemap.xml</loc>",
"<loc>http:/example.com/blog/fr/sitemap.xml</loc>")
if defaultInSubDir {
assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
} else {
assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
}
assertFileContent(t, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
// Check rss
assertFileContent(t, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
assertFileContent(t, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
assertFileContent(t, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
assertFileContent(t, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
assertFileContent(t, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
assertFileContent(t, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
// Check paginators
assertFileContent(t, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
assertFileContent(t, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
assertFileContent(t, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
assertFileContent(t, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
assertFileContent(t, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
assertFileContent(t, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
assertFileContent(t, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
assertFileContent(t, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
assertFileContent(t, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
assertFileContent(t, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
assertFileContent(t, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
assertFileContent(t, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
}
func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {
replace := viper.GetString("DefaultContentLanguage") + "/"
if !defaultInSubDir {
value = strings.Replace(value, replace, "", 1)
}
return value
}
func assertFileContent(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {
filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
content := readDestination(t, filename)
for _, match := range matches {
match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
require.True(t, strings.Contains(content, match), fmt.Sprintf("File no match for %q in %q: %s", match, filename, content))
}
}
func TestMultiSitesBuild(t *testing.T) { func TestMultiSitesBuild(t *testing.T) {
testCommonResetState() testCommonResetState()
sites := createMultiTestSites(t, multiSiteTomlConfig) sites := createMultiTestSites(t, multiSiteTomlConfig)
@ -397,7 +519,7 @@ DisableSitemap = false
DisableRSS = false DisableRSS = false
RSSUri = "index.xml" RSSUri = "index.xml"
paginate = 2 paginate = 1
DefaultContentLanguage = "fr" DefaultContentLanguage = "fr"
[permalinks] [permalinks]
@ -435,14 +557,14 @@ func createMultiTestSites(t *testing.T, tomlConfig string) *HugoSites {
if err := afero.WriteFile(hugofs.Source(), if err := afero.WriteFile(hugofs.Source(),
filepath.Join("layouts", "_default/list.html"), filepath.Join("layouts", "_default/list.html"),
[]byte("List: {{ .Title }}"), []byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
0755); err != nil { 0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err) t.Fatalf("Failed to write layout file: %s", err)
} }
if err := afero.WriteFile(hugofs.Source(), if err := afero.WriteFile(hugofs.Source(),
filepath.Join("layouts", "index.html"), filepath.Join("layouts", "index.html"),
[]byte("Home: {{ .Title }}|{{ .IsHome }}"), []byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
0755); err != nil { 0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err) t.Fatalf("Failed to write layout file: %s", err)
} }
@ -505,6 +627,7 @@ title: doc3
publishdate: "2000-01-03" publishdate: "2000-01-03"
tags: tags:
- tag2 - tag2
- tag1
url: /superbob url: /superbob
--- ---
# doc3 # doc3

View file

@ -176,7 +176,7 @@ type URLPath struct {
} }
func (n *Node) URL() string { func (n *Node) URL() string {
return n.addMultilingualWebPrefix(n.URLPath.URL) return n.addLangPathPrefix(n.URLPath.URL)
} }
func (n *Node) Permalink() string { func (n *Node) Permalink() string {
@ -206,8 +206,27 @@ func (n *Node) Lang() string {
return n.lang return n.lang
} }
func (n *Node) shouldAddLanguagePrefix() bool {
if !n.Site.IsMultiLingual() {
return false
}
if n.Lang() == "" {
return false
}
if !n.Site.defaultContentLanguageInSubdir && n.Lang() == n.Site.multilingual.DefaultLang.Lang {
return false
}
return true
}
func (n *Node) initLanguage() { func (n *Node) initLanguage() {
n.languageInit.Do(func() { n.languageInit.Do(func() {
if n.language != nil {
return
}
pageLang := n.lang pageLang := n.lang
ml := n.Site.multilingual ml := n.Site.multilingual
if ml == nil { if ml == nil {
@ -278,29 +297,34 @@ func (n *Node) initTranslations() {
}) })
} }
func (n *Node) addMultilingualWebPrefix(outfile string) string { func (n *Node) addLangPathPrefix(outfile string) string {
return n.addLangPathPrefixIfFlagSet(outfile, n.shouldAddLanguagePrefix())
}
func (n *Node) addLangPathPrefixIfFlagSet(outfile string, should bool) string {
if helpers.IsAbsURL(outfile) { if helpers.IsAbsURL(outfile) {
return outfile return outfile
} }
if !should {
return outfile
}
hadSlashSuffix := strings.HasSuffix(outfile, "/") hadSlashSuffix := strings.HasSuffix(outfile, "/")
lang := n.Lang() outfile = "/" + path.Join(n.Lang(), outfile)
if lang == "" || !n.Site.IsMultiLingual() {
return outfile
}
outfile = "/" + path.Join(lang, outfile)
if hadSlashSuffix { if hadSlashSuffix {
outfile += "/" outfile += "/"
} }
return outfile return outfile
} }
func (n *Node) addMultilingualFilesystemPrefix(outfile string) string { func (n *Node) addLangFilepathPrefix(outfile string) string {
lang := n.Lang() if outfile == "" {
if lang == "" || !n.Site.IsMultiLingual() { outfile = helpers.FilePathSeparator
}
if !n.shouldAddLanguagePrefix() {
return outfile return outfile
} }
return string(filepath.Separator) + filepath.Join(lang, outfile) return helpers.FilePathSeparator + filepath.Join(n.Lang(), outfile)
} }

View file

@ -514,7 +514,7 @@ func (p *Page) permalink() (*url.URL, error) {
} }
} }
permalink = p.addMultilingualWebPrefix(permalink) permalink = p.addLangPathPrefix(permalink)
return helpers.MakePermalink(baseURL, permalink), nil return helpers.MakePermalink(baseURL, permalink), nil
} }
@ -1059,7 +1059,7 @@ func (p *Page) TargetPath() (outfile string) {
outfile += "index.html" outfile += "index.html"
} }
outfile = filepath.FromSlash(outfile) outfile = filepath.FromSlash(outfile)
outfile = p.addMultilingualFilesystemPrefix(outfile) outfile = p.addLangFilepathPrefix(outfile)
return return
} }
} }
@ -1071,5 +1071,5 @@ func (p *Page) TargetPath() (outfile string) {
outfile = helpers.ReplaceExtension(p.Source.TranslationBaseName(), p.Extension()) outfile = helpers.ReplaceExtension(p.Source.TranslationBaseName(), p.Extension())
} }
return p.addMultilingualFilesystemPrefix(filepath.Join(strings.ToLower(helpers.MakePath(p.Source.Dir())), strings.TrimSpace(outfile))) return p.addLangFilepathPrefix(filepath.Join(strings.ToLower(helpers.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
} }

View file

@ -167,10 +167,11 @@ type SiteInfo struct {
paginationPageCount uint64 paginationPageCount uint64
Data *map[string]interface{} Data *map[string]interface{}
multilingual *Multilingual multilingual *Multilingual
Language *helpers.Language Language *helpers.Language
LanguagePrefix string LanguagePrefix string
Languages helpers.Languages Languages helpers.Languages
defaultContentLanguageInSubdir bool
} }
// Used in tests. // Used in tests.
@ -864,42 +865,45 @@ func (s *Site) initializeSiteInfo() {
permalinks[k] = pathPattern(v) permalinks[k] = pathPattern(v)
} }
defaultContentInSubDir := viper.GetBool("DefaultContentLanguageInSubdir")
defaultContentLanguage := viper.GetString("DefaultContentLanguage")
languagePrefix := "" languagePrefix := ""
if s.multilingualEnabled() { if s.multilingualEnabled() && (defaultContentInSubDir || lang.Lang != defaultContentLanguage) {
languagePrefix = "/" + lang.Lang languagePrefix = "/" + lang.Lang
} }
var multilingual *Multilingual var multilingual *Multilingual
if s.owner != nil { if s.owner != nil {
multilingual = s.owner.multilingual multilingual = s.owner.multilingual
} }
s.Info = SiteInfo{ s.Info = SiteInfo{
BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("BaseURL"))), BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("BaseURL"))),
Title: lang.GetString("Title"), Title: lang.GetString("Title"),
Author: lang.GetStringMap("author"), Author: lang.GetStringMap("author"),
Social: lang.GetStringMapString("social"), Social: lang.GetStringMapString("social"),
LanguageCode: lang.GetString("languagecode"), LanguageCode: lang.GetString("languagecode"),
Copyright: lang.GetString("copyright"), Copyright: lang.GetString("copyright"),
DisqusShortname: lang.GetString("DisqusShortname"), DisqusShortname: lang.GetString("DisqusShortname"),
multilingual: multilingual, multilingual: multilingual,
Language: lang, Language: lang,
LanguagePrefix: languagePrefix, LanguagePrefix: languagePrefix,
Languages: languages, Languages: languages,
GoogleAnalytics: lang.GetString("GoogleAnalytics"), defaultContentLanguageInSubdir: defaultContentInSubDir,
RSSLink: permalinkStr(viper.GetString("RSSUri")), GoogleAnalytics: lang.GetString("GoogleAnalytics"),
BuildDrafts: viper.GetBool("BuildDrafts"), RSSLink: permalinkStr(viper.GetString("RSSUri")),
canonifyURLs: viper.GetBool("CanonifyURLs"), BuildDrafts: viper.GetBool("BuildDrafts"),
preserveTaxonomyNames: viper.GetBool("PreserveTaxonomyNames"), canonifyURLs: viper.GetBool("CanonifyURLs"),
AllPages: &s.AllPages, preserveTaxonomyNames: viper.GetBool("PreserveTaxonomyNames"),
Pages: &s.Pages, AllPages: &s.AllPages,
rawAllPages: &s.rawAllPages, Pages: &s.Pages,
Files: &s.Files, rawAllPages: &s.rawAllPages,
Menus: &s.Menus, Files: &s.Files,
Params: params, Menus: &s.Menus,
Permalinks: permalinks, Params: params,
Data: &s.Data, Permalinks: permalinks,
Data: &s.Data,
} }
} }
@ -1280,7 +1284,7 @@ func (s *Site) assembleMenus() {
if p.Section() != "" { if p.Section() != "" {
me := MenuEntry{Identifier: p.Section(), me := MenuEntry{Identifier: p.Section(),
Name: helpers.MakeTitle(helpers.FirstUpper(p.Section())), Name: helpers.MakeTitle(helpers.FirstUpper(p.Section())),
URL: s.Info.createNodeMenuEntryURL(p.addMultilingualWebPrefix("/"+p.Section()) + "/")} URL: s.Info.createNodeMenuEntryURL(p.addLangPathPrefix("/"+p.Section()) + "/")}
if _, ok := flat[twoD{sectionPagesMenu, me.KeyName()}]; ok { if _, ok := flat[twoD{sectionPagesMenu, me.KeyName()}]; ok {
// menu with same id defined in config, let that one win // menu with same id defined in config, let that one win
continue continue
@ -1422,7 +1426,7 @@ func (s *Site) renderAliases() error {
} }
} }
if s.owner.multilingual.enabled() { if s.owner.multilingual.enabled() && s.Info.defaultContentLanguageInSubdir {
mainLang := s.owner.multilingual.DefaultLang.Lang mainLang := s.owner.multilingual.DefaultLang.Lang
mainLangURL := helpers.AbsURL(mainLang, false) mainLangURL := helpers.AbsURL(mainLang, false)
jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL) jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
@ -1612,19 +1616,6 @@ func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) {
return n, base return n, base
} }
// addMultilingualPrefix adds the `en/` prefix to the path passed as parameter.
// `basePath` must not start with http://
func (s *Site) addMultilingualPrefix(basePath string) string {
hadPrefix := strings.HasPrefix(basePath, "/")
if s.multilingualEnabled() {
basePath = path.Join(s.Language.Lang, basePath)
if hadPrefix {
basePath = "/" + basePath
}
}
return basePath
}
func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error, wg *sync.WaitGroup) { func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error, wg *sync.WaitGroup) {
defer wg.Done() defer wg.Done()
@ -1637,14 +1628,13 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
[]string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"}) []string{"taxonomy/" + t.singular + ".html", "indexes/" + t.singular + ".html", "_default/taxonomy.html", "_default/list.html"})
n, base = s.newTaxonomyNode(t) n, base = s.newTaxonomyNode(t)
baseWithLanguagePrefix := n.addLangPathPrefix(base)
base = s.addMultilingualPrefix(base)
dest := base dest := base
if viper.GetBool("UglyURLs") { if viper.GetBool("UglyURLs") {
dest = helpers.Uglify(base + ".html") dest = helpers.Uglify(baseWithLanguagePrefix + ".html")
} else { } else {
dest = helpers.PrettifyPath(base + "/index.html") dest = helpers.PrettifyPath(baseWithLanguagePrefix + "/index.html")
} }
if err := s.renderAndWritePage("taxonomy "+t.singular, dest, n, layouts...); err != nil { if err := s.renderAndWritePage("taxonomy "+t.singular, dest, n, layouts...); err != nil {
@ -1657,7 +1647,7 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
paginatePath := viper.GetString("paginatePath") paginatePath := viper.GetString("paginatePath")
// write alias for page 1 // write alias for page 1
s.writeDestAlias(helpers.PaginateAliasPath(base, 1), permalink(base)) s.writeDestAlias(helpers.PaginateAliasPath(baseWithLanguagePrefix, 1), n.Permalink())
pagers := n.paginator.Pagers() pagers := n.paginator.Pagers()
@ -1675,7 +1665,7 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
taxonomyPagerNode.Lastmod = first.Lastmod taxonomyPagerNode.Lastmod = first.Lastmod
} }
pageNumber := i + 1 pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%s/%d", base, paginatePath, pageNumber) htmlBase := fmt.Sprintf("/%s/%s/%d", baseWithLanguagePrefix, paginatePath, pageNumber)
if err := s.renderAndWritePage(fmt.Sprintf("taxonomy %s", t.singular), htmlBase, taxonomyPagerNode, layouts...); err != nil { if err := s.renderAndWritePage(fmt.Sprintf("taxonomy %s", t.singular), htmlBase, taxonomyPagerNode, layouts...); err != nil {
results <- err results <- err
continue continue
@ -1686,11 +1676,10 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
if !viper.GetBool("DisableRSS") { if !viper.GetBool("DisableRSS") {
// XML Feed // XML Feed
rssuri := viper.GetString("RSSUri") rssuri := viper.GetString("RSSUri")
n.URLPath.URL = permalinkStr(base + "/" + rssuri) s.setURLs(n, base+"/"+rssuri)
n.URLPath.Permalink = permalink(base)
rssLayouts := []string{"taxonomy/" + t.singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"} rssLayouts := []string{"taxonomy/" + t.singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
if err := s.renderAndWriteXML("taxonomy "+t.singular+" rss", base+"/"+rssuri, n, s.appendThemeTemplates(rssLayouts)...); err != nil { if err := s.renderAndWriteXML("taxonomy "+t.singular+" rss", baseWithLanguagePrefix+"/"+rssuri, n, s.appendThemeTemplates(rssLayouts)...); err != nil {
results <- err results <- err
continue continue
} }
@ -1714,7 +1703,7 @@ func (s *Site) renderListsOfTaxonomyTerms() (err error) {
layouts := []string{"taxonomy/" + singular + ".terms.html", "_default/terms.html", "indexes/indexes.html"} layouts := []string{"taxonomy/" + singular + ".terms.html", "_default/terms.html", "indexes/indexes.html"}
layouts = s.appendThemeTemplates(layouts) layouts = s.appendThemeTemplates(layouts)
if s.layoutExists(layouts...) { if s.layoutExists(layouts...) {
if err := s.renderAndWritePage("taxonomy terms for "+singular, s.addMultilingualPrefix(plural+"/index.html"), n, layouts...); err != nil { if err := s.renderAndWritePage("taxonomy terms for "+singular, n.addLangPathPrefix(plural+"/index.html"), n, layouts...); err != nil {
return err return err
} }
} }
@ -1755,9 +1744,9 @@ func (s *Site) renderSectionLists() error {
section = helpers.MakePathSanitized(section) section = helpers.MakePathSanitized(section)
} }
base := s.addMultilingualPrefix(section)
n := s.newSectionListNode(sectionName, section, data) n := s.newSectionListNode(sectionName, section, data)
base := n.addLangPathPrefix(section)
if err := s.renderAndWritePage(fmt.Sprintf("section %s", section), base, n, s.appendThemeTemplates(layouts)...); err != nil { if err := s.renderAndWritePage(fmt.Sprintf("section %s", section), base, n, s.appendThemeTemplates(layouts)...); err != nil {
return err return err
} }
@ -1795,10 +1784,9 @@ func (s *Site) renderSectionLists() error {
if !viper.GetBool("DisableRSS") && section != "" { if !viper.GetBool("DisableRSS") && section != "" {
// XML Feed // XML Feed
rssuri := viper.GetString("RSSUri") rssuri := viper.GetString("RSSUri")
n.URLPath.URL = permalinkStr(base + "/" + rssuri) s.setURLs(n, section+"/"+rssuri)
n.URLPath.Permalink = permalink(base)
rssLayouts := []string{"section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"} rssLayouts := []string{"section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
if err := s.renderAndWriteXML("section "+section+" rss", base+"/"+rssuri, n, s.appendThemeTemplates(rssLayouts)...); err != nil { if err := s.renderAndWriteXML("section "+section+" rss", n.addLangPathPrefix(section+"/"+rssuri), n, s.appendThemeTemplates(rssLayouts)...); err != nil {
return err return err
} }
} }
@ -1807,19 +1795,21 @@ func (s *Site) renderSectionLists() error {
} }
func (s *Site) renderHomePage() error { func (s *Site) renderHomePage() error {
n := s.newHomeNode()
layouts := s.appendThemeTemplates([]string{"index.html", "_default/list.html"})
if err := s.renderAndWritePage("homepage", s.addMultilingualPrefix(helpers.FilePathSeparator), n, layouts...); err != nil { n := s.newHomeNode()
layouts := s.appendThemeTemplates([]string{"index.html", "_default/list.html"})
base := n.addLangFilepathPrefix("")
if err := s.renderAndWritePage("homepage", base, n, layouts...); err != nil {
return err return err
} }
if n.paginator != nil { if n.paginator != nil {
paginatePath := viper.GetString("paginatePath") paginatePath := viper.GetString("paginatePath")
// write alias for page 1 // write alias for page 1
s.writeDestAlias(s.addMultilingualPrefix(helpers.PaginateAliasPath("", 1)), permalink("/")) // TODO(bep) ml all of these n.addLang ... fix.
s.writeDestAlias(n.addLangPathPrefix(helpers.PaginateAliasPath("", 1)), n.Permalink())
pagers := n.paginator.Pagers() pagers := n.paginator.Pagers()
@ -1838,7 +1828,7 @@ func (s *Site) renderHomePage() error {
} }
pageNumber := i + 1 pageNumber := i + 1
htmlBase := fmt.Sprintf("/%s/%d", paginatePath, pageNumber) htmlBase := fmt.Sprintf("/%s/%d", paginatePath, pageNumber)
htmlBase = s.addMultilingualPrefix(htmlBase) htmlBase = n.addLangPathPrefix(htmlBase)
if err := s.renderAndWritePage(fmt.Sprintf("homepage"), filepath.FromSlash(htmlBase), homePagerNode, layouts...); err != nil { if err := s.renderAndWritePage(fmt.Sprintf("homepage"), filepath.FromSlash(htmlBase), homePagerNode, layouts...); err != nil {
return err return err
} }
@ -1847,7 +1837,7 @@ func (s *Site) renderHomePage() error {
if !viper.GetBool("DisableRSS") { if !viper.GetBool("DisableRSS") {
// XML Feed // XML Feed
n.URLPath.URL = permalinkStr(viper.GetString("RSSUri")) s.setURLs(n, viper.GetString("RSSUri"))
n.Title = "" n.Title = ""
high := 50 high := 50
if len(s.Pages) < high { if len(s.Pages) < high {
@ -1861,7 +1851,7 @@ func (s *Site) renderHomePage() error {
rssLayouts := []string{"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml"} rssLayouts := []string{"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml"}
if err := s.renderAndWriteXML("homepage rss", s.addMultilingualPrefix(viper.GetString("RSSUri")), n, s.appendThemeTemplates(rssLayouts)...); err != nil { if err := s.renderAndWriteXML("homepage rss", n.addLangPathPrefix(viper.GetString("RSSUri")), n, s.appendThemeTemplates(rssLayouts)...); err != nil {
return err return err
} }
} }
@ -1911,10 +1901,11 @@ func (s *Site) renderSitemap() error {
pages := make(Pages, 0) pages := make(Pages, 0)
page := &Page{} page := &Page{}
page.language = s.Language
page.Date = s.Info.LastChange page.Date = s.Info.LastChange
page.Lastmod = s.Info.LastChange page.Lastmod = s.Info.LastChange
page.Site = &s.Info page.Site = &s.Info
page.URLPath.URL = "/" page.URLPath.URL = ""
page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
page.Sitemap.Priority = sitemapDefault.Priority page.Sitemap.Priority = sitemapDefault.Priority
@ -1938,8 +1929,8 @@ func (s *Site) renderSitemap() error {
} }
smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"} smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
addLanguagePrefix := n.Site.IsMultiLingual()
if err := s.renderAndWriteXML("sitemap", s.addMultilingualPrefix(page.Sitemap.Filename), n, s.appendThemeTemplates(smLayouts)...); err != nil { if err := s.renderAndWriteXML("sitemap", n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...); err != nil {
return err return err
} }

View file

@ -317,7 +317,6 @@ THE END.`, refShortcode)),
// Issue #939 // Issue #939
// Issue #1923 // Issue #1923
func TestShouldAlwaysHaveUglyURLs(t *testing.T) { func TestShouldAlwaysHaveUglyURLs(t *testing.T) {
hugofs.InitMemFs()
for _, uglyURLs := range []bool{true, false} { for _, uglyURLs := range []bool{true, false} {
doTestShouldAlwaysHaveUglyURLs(t, uglyURLs) doTestShouldAlwaysHaveUglyURLs(t, uglyURLs)
} }
@ -383,12 +382,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
} }
for _, test := range tests { for _, test := range tests {
file, err := hugofs.Destination().Open(test.doc) content := readDestination(t, test.doc)
if err != nil {
t.Fatalf("Did not find %s in target: %s", test.doc, err)
}
content := helpers.ReaderToString(file)
if content != test.expected { if content != test.expected {
t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)

View file

@ -14,13 +14,12 @@
package hugolib package hugolib
import ( import (
"bytes"
"testing" "testing"
"reflect" "reflect"
"strings"
"github.com/spf13/hugo/helpers" "github.com/spf13/hugo/helpers"
"github.com/spf13/hugo/hugofs"
"github.com/spf13/hugo/source" "github.com/spf13/hugo/source"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -50,15 +49,10 @@ func TestSitemapOutput(t *testing.T) {
t.Fatalf("Failed to build site: %s", err) t.Fatalf("Failed to build site: %s", err)
} }
sitemapFile, err := hugofs.Destination().Open("public/sitemap.xml") sitemapContent := readDestination(t, "public/sitemap.xml")
if err != nil { if !strings.HasPrefix(sitemapContent, "<?xml") {
t.Fatalf("Unable to locate: sitemap.xml") t.Errorf("Sitemap file should start with <?xml. %s", sitemapContent)
}
sitemap := helpers.ReaderToBytes(sitemapFile)
if !bytes.HasPrefix(sitemap, []byte("<?xml")) {
t.Errorf("Sitemap file should start with <?xml. %s", sitemap)
} }
} }

View file

@ -75,7 +75,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
<webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }} <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
<copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }} <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
<lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }} <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
<atom:link href="{{.URL}}" rel="self" type="application/rss+xml" /> <atom:link href="{{.Permalink}}" rel="self" type="application/rss+xml" />
{{ range first 15 .Data.Pages }} {{ range first 15 .Data.Pages }}
<item> <item>
<title>{{ .Title }}</title> <title>{{ .Title }}</title>