Render main content language in root by default
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 8 Aug 2016 11:55:18 +0000 (13:55 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 6 Sep 2016 15:32:18 +0000 (18:32 +0300)
Fixes #2312

12 files changed:
docs/content/content/multilingual.md
helpers/language.go
helpers/url.go
helpers/url_test.go
hugolib/config.go
hugolib/hugo_sites_test.go
hugolib/node.go
hugolib/page.go
hugolib/site.go
hugolib/site_test.go
hugolib/sitemap_test.go
tpl/template_embedded.go

index 35dd1382e4ecaebf84896d7f420af2e3858fe766..f23a6afcfd6a6cdb071703b84dbad8780013a05c 100644 (file)
@@ -14,6 +14,8 @@ Hugo supports multiple languages side-by-side (added in `Hugo 0.17`). Define the
 Example:
 
 ```
+DefaultContentLanguage = "en"
+
 Languages:
   en:
     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).
 
 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.
 
index 4ef5edbc42fcfc58d4091c68ab530f082e1bda64..99412930838b6a10a813da96eae558d79f95a43b 100644 (file)
@@ -31,6 +31,10 @@ type Language struct {
        paramsInit sync.Once
 }
 
+func (l *Language) String() string {
+       return l.Lang
+}
+
 func NewLanguage(lang string) *Language {
        return &Language{Lang: lang, params: make(map[string]interface{})}
 }
index 83273324dfba9edad79aa11a5803b91c2dad0351..4d06fb042918596072956779cc08438c83fef2d0 100644 (file)
@@ -168,21 +168,32 @@ func AbsURL(in string, addLanguage bool) string {
        }
 
        if addLanguage {
-               addSlash := in == "" || strings.HasSuffix(in, "/")
-               in = path.Join(getLanguagePrefix(), in)
+               prefix := getLanguagePrefix()
 
-               if addSlash {
-                       in += "/"
+               if prefix != "" {
+                       addSlash := in == "" || strings.HasSuffix(in, "/")
+                       in = path.Join(prefix, in)
+
+                       if addSlash {
+                               in += "/"
+                       }
                }
        }
        return MakePermalink(baseURL, in).String()
 }
 
 func getLanguagePrefix() string {
+       defaultLang := viper.GetString("DefaultContentLanguage")
+       defaultInSubDir := viper.GetBool("DefaultContentLanguageInSubdir")
+
        if !viper.GetBool("Multilingual") {
                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.
@@ -211,12 +222,15 @@ func RelURL(in string, addLanguage bool) string {
        }
 
        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 {
-                       u += "/"
+                       if hadSlash {
+                               u += "/"
+                       }
                }
        }
 
index f6dd9f9daf976f6da9b9b5ef63e45d4c58292cf7..d1fadb25299db1e7a4fb4ca146f8f4adbe541b12 100644 (file)
@@ -45,19 +45,24 @@ func TestURLize(t *testing.T) {
 }
 
 func TestAbsURL(t *testing.T) {
-       for _, addLanguage := range []bool{true, false} {
-               for _, m := range []bool{true, false} {
-                       for _, l := range []string{"en", "fr"} {
-                               doTestAbsURL(t, addLanguage, m, l)
+       for _, defaultInSubDir := range []bool{true, false} {
+               for _, addLanguage := range []bool{true, false} {
+                       for _, m := range []bool{true, false} {
+                               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.Set("Multilingual", multilingual)
        viper.Set("CurrentContentLanguage", NewLanguage(lang))
+       viper.Set("DefaultContentLanguage", "en")
+       viper.Set("DefaultContentLanguageInSubdir", defaultInSubDir)
+
        tests := []struct {
                input    string
                baseURL  string
@@ -79,12 +84,17 @@ func doTestAbsURL(t *testing.T, addLanguage, multilingual bool, lang string) {
                output := AbsURL(test.input, addLanguage)
                expected := test.expected
                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 {
                        expected = strings.Replace(expected, "MULTI", "", 1)
                }
                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) {
-       for _, addLanguage := range []bool{true, false} {
-               for _, m := range []bool{true, false} {
-                       for _, l := range []string{"en", "fr"} {
-                               doTestRelURL(t, addLanguage, m, l)
+       for _, defaultInSubDir := range []bool{true, false} {
+               for _, addLanguage := range []bool{true, false} {
+                       for _, m := range []bool{true, false} {
+                               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.Set("Multilingual", multilingual)
        viper.Set("CurrentContentLanguage", NewLanguage(lang))
+       viper.Set("DefaultContentLanguage", "en")
+       viper.Set("DefaultContentLanguageInSubdir", defaultInSubDir)
 
        tests := []struct {
                input    string
@@ -146,7 +160,11 @@ func doTestRelURL(t *testing.T, addLanguage, multilingual bool, lang string) {
 
                expected := test.expected
                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 {
                        expected = strings.Replace(expected, "MULTI", "", 1)
                }
index c4292e81bf434cfeff167bdd17cf6ab730702b46..17673453e5c40e91e42024ac5d8eb7e4332a91ca 100644 (file)
@@ -104,4 +104,5 @@ func loadDefaultSettings() {
        viper.SetDefault("UseModTimeAsFallback", false)
        viper.SetDefault("Multilingual", false)
        viper.SetDefault("DefaultContentLanguage", "en")
+       viper.SetDefault("DefaultContentLanguageInSubdir", false)
 }
index 655feba0f49f5b5ca11dfb1139940ae649e46b6d..6ab60d9ece2ce67a8a0bf8f8ebf43ec5636bce51 100644 (file)
@@ -32,12 +32,134 @@ func testCommonResetState() {
        viper.SetFs(hugofs.Source())
        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 {
                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) {
        testCommonResetState()
        sites := createMultiTestSites(t, multiSiteTomlConfig)
@@ -397,7 +519,7 @@ DisableSitemap = false
 DisableRSS = false
 RSSUri = "index.xml"
 
-paginate = 2
+paginate = 1
 DefaultContentLanguage = "fr"
 
 [permalinks]
@@ -435,14 +557,14 @@ func createMultiTestSites(t *testing.T, tomlConfig string) *HugoSites {
 
        if err := afero.WriteFile(hugofs.Source(),
                filepath.Join("layouts", "_default/list.html"),
-               []byte("List: {{ .Title }}"),
+               []byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
                0755); err != nil {
                t.Fatalf("Failed to write layout file: %s", err)
        }
 
        if err := afero.WriteFile(hugofs.Source(),
                filepath.Join("layouts", "index.html"),
-               []byte("Home: {{ .Title }}|{{ .IsHome }}"),
+               []byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),
                0755); err != nil {
                t.Fatalf("Failed to write layout file: %s", err)
        }
@@ -505,6 +627,7 @@ title: doc3
 publishdate: "2000-01-03"
 tags:
  - tag2
+ - tag1
 url: /superbob
 ---
 # doc3
index 57bd5021f28eba1e1d11da2a92d0bb58c84f111d..53c22af13c5ab7712fe337221ce2ae211cc4b599 100644 (file)
@@ -176,7 +176,7 @@ type URLPath struct {
 }
 
 func (n *Node) URL() string {
-       return n.addMultilingualWebPrefix(n.URLPath.URL)
+       return n.addLangPathPrefix(n.URLPath.URL)
 }
 
 func (n *Node) Permalink() string {
@@ -206,8 +206,27 @@ func (n *Node) Lang() string {
        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() {
        n.languageInit.Do(func() {
+               if n.language != nil {
+                       return
+               }
                pageLang := n.lang
                ml := n.Site.multilingual
                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) {
                return outfile
        }
 
-       hadSlashSuffix := strings.HasSuffix(outfile, "/")
-
-       lang := n.Lang()
-       if lang == "" || !n.Site.IsMultiLingual() {
+       if !should {
                return outfile
        }
-       outfile = "/" + path.Join(lang, outfile)
+
+       hadSlashSuffix := strings.HasSuffix(outfile, "/")
+
+       outfile = "/" + path.Join(n.Lang(), outfile)
        if hadSlashSuffix {
                outfile += "/"
        }
        return outfile
 }
 
-func (n *Node) addMultilingualFilesystemPrefix(outfile string) string {
-       lang := n.Lang()
-       if lang == "" || !n.Site.IsMultiLingual() {
+func (n *Node) addLangFilepathPrefix(outfile string) string {
+       if outfile == "" {
+               outfile = helpers.FilePathSeparator
+       }
+       if !n.shouldAddLanguagePrefix() {
                return outfile
        }
-       return string(filepath.Separator) + filepath.Join(lang, outfile)
+       return helpers.FilePathSeparator + filepath.Join(n.Lang(), outfile)
 }
index a15af60bf8618ff78ab6dfd0ce5e8a97c2effdaf..74f51c0bb66809490419979e4dd7e1d369c74503 100644 (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
 }
@@ -1059,7 +1059,7 @@ func (p *Page) TargetPath() (outfile string) {
                                outfile += "index.html"
                        }
                        outfile = filepath.FromSlash(outfile)
-                       outfile = p.addMultilingualFilesystemPrefix(outfile)
+                       outfile = p.addLangFilepathPrefix(outfile)
                        return
                }
        }
@@ -1071,5 +1071,5 @@ func (p *Page) TargetPath() (outfile string) {
                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)))
 }
index 1b92006ff2de3fe999883d84a3b1cb6c1ba2887b..edcccfe2849b310120caf63ff14b6be971d462fa 100644 (file)
@@ -167,10 +167,11 @@ type SiteInfo struct {
        paginationPageCount   uint64
        Data                  *map[string]interface{}
 
-       multilingual   *Multilingual
-       Language       *helpers.Language
-       LanguagePrefix string
-       Languages      helpers.Languages
+       multilingual                   *Multilingual
+       Language                       *helpers.Language
+       LanguagePrefix                 string
+       Languages                      helpers.Languages
+       defaultContentLanguageInSubdir bool
 }
 
 // Used in tests.
@@ -864,42 +865,45 @@ func (s *Site) initializeSiteInfo() {
                permalinks[k] = pathPattern(v)
        }
 
+       defaultContentInSubDir := viper.GetBool("DefaultContentLanguageInSubdir")
+       defaultContentLanguage := viper.GetString("DefaultContentLanguage")
+
        languagePrefix := ""
-       if s.multilingualEnabled() {
+       if s.multilingualEnabled() && (defaultContentInSubDir || lang.Lang != defaultContentLanguage) {
                languagePrefix = "/" + lang.Lang
        }
 
        var multilingual *Multilingual
-
        if s.owner != nil {
                multilingual = s.owner.multilingual
        }
 
        s.Info = SiteInfo{
-               BaseURL:               template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("BaseURL"))),
-               Title:                 lang.GetString("Title"),
-               Author:                lang.GetStringMap("author"),
-               Social:                lang.GetStringMapString("social"),
-               LanguageCode:          lang.GetString("languagecode"),
-               Copyright:             lang.GetString("copyright"),
-               DisqusShortname:       lang.GetString("DisqusShortname"),
-               multilingual:          multilingual,
-               Language:              lang,
-               LanguagePrefix:        languagePrefix,
-               Languages:             languages,
-               GoogleAnalytics:       lang.GetString("GoogleAnalytics"),
-               RSSLink:               permalinkStr(viper.GetString("RSSUri")),
-               BuildDrafts:           viper.GetBool("BuildDrafts"),
-               canonifyURLs:          viper.GetBool("CanonifyURLs"),
-               preserveTaxonomyNames: viper.GetBool("PreserveTaxonomyNames"),
-               AllPages:              &s.AllPages,
-               Pages:                 &s.Pages,
-               rawAllPages:           &s.rawAllPages,
-               Files:                 &s.Files,
-               Menus:                 &s.Menus,
-               Params:                params,
-               Permalinks:            permalinks,
-               Data:                  &s.Data,
+               BaseURL:                        template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("BaseURL"))),
+               Title:                          lang.GetString("Title"),
+               Author:                         lang.GetStringMap("author"),
+               Social:                         lang.GetStringMapString("social"),
+               LanguageCode:                   lang.GetString("languagecode"),
+               Copyright:                      lang.GetString("copyright"),
+               DisqusShortname:                lang.GetString("DisqusShortname"),
+               multilingual:                   multilingual,
+               Language:                       lang,
+               LanguagePrefix:                 languagePrefix,
+               Languages:                      languages,
+               defaultContentLanguageInSubdir: defaultContentInSubDir,
+               GoogleAnalytics:                lang.GetString("GoogleAnalytics"),
+               RSSLink:                        permalinkStr(viper.GetString("RSSUri")),
+               BuildDrafts:                    viper.GetBool("BuildDrafts"),
+               canonifyURLs:                   viper.GetBool("CanonifyURLs"),
+               preserveTaxonomyNames:          viper.GetBool("PreserveTaxonomyNames"),
+               AllPages:                       &s.AllPages,
+               Pages:                          &s.Pages,
+               rawAllPages:                    &s.rawAllPages,
+               Files:                          &s.Files,
+               Menus:                          &s.Menus,
+               Params:                         params,
+               Permalinks:                     permalinks,
+               Data:                           &s.Data,
        }
 }
 
@@ -1280,7 +1284,7 @@ func (s *Site) assembleMenus() {
                                if p.Section() != "" {
                                        me := MenuEntry{Identifier: 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 {
                                                // menu with same id defined in config, let that one win
                                                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
                mainLangURL := helpers.AbsURL(mainLang, false)
                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
 }
 
-// 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) {
        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"})
 
                n, base = s.newTaxonomyNode(t)
-
-               base = s.addMultilingualPrefix(base)
+               baseWithLanguagePrefix := n.addLangPathPrefix(base)
 
                dest := base
                if viper.GetBool("UglyURLs") {
-                       dest = helpers.Uglify(base + ".html")
+                       dest = helpers.Uglify(baseWithLanguagePrefix + ".html")
                } 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 {
@@ -1657,7 +1647,7 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
                        paginatePath := viper.GetString("paginatePath")
 
                        // 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()
 
@@ -1675,7 +1665,7 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
                                        taxonomyPagerNode.Lastmod = first.Lastmod
                                }
                                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 {
                                        results <- err
                                        continue
@@ -1686,11 +1676,10 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
                if !viper.GetBool("DisableRSS") {
                        // XML Feed
                        rssuri := viper.GetString("RSSUri")
-                       n.URLPath.URL = permalinkStr(base + "/" + rssuri)
-                       n.URLPath.Permalink = permalink(base)
+                       s.setURLs(n, base+"/"+rssuri)
                        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
                                continue
                        }
@@ -1714,7 +1703,7 @@ func (s *Site) renderListsOfTaxonomyTerms() (err error) {
                layouts := []string{"taxonomy/" + singular + ".terms.html", "_default/terms.html", "indexes/indexes.html"}
                layouts = s.appendThemeTemplates(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
                        }
                }
@@ -1755,9 +1744,9 @@ func (s *Site) renderSectionLists() error {
                        section = helpers.MakePathSanitized(section)
                }
 
-               base := s.addMultilingualPrefix(section)
-
                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 {
                        return err
                }
@@ -1795,10 +1784,9 @@ func (s *Site) renderSectionLists() error {
                if !viper.GetBool("DisableRSS") && section != "" {
                        // XML Feed
                        rssuri := viper.GetString("RSSUri")
-                       n.URLPath.URL = permalinkStr(base + "/" + rssuri)
-                       n.URLPath.Permalink = permalink(base)
+                       s.setURLs(n, section+"/"+rssuri)
                        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
                        }
                }
@@ -1807,19 +1795,21 @@ func (s *Site) renderSectionLists() 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 {
+       layouts := s.appendThemeTemplates([]string{"index.html", "_default/list.html"})
+       base := n.addLangFilepathPrefix("")
+       if err := s.renderAndWritePage("homepage", base, n, layouts...); err != nil {
                return err
        }
 
        if n.paginator != nil {
-
                paginatePath := viper.GetString("paginatePath")
 
                // 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()
 
@@ -1838,7 +1828,7 @@ func (s *Site) renderHomePage() error {
                        }
                        pageNumber := i + 1
                        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 {
                                return err
                        }
@@ -1847,7 +1837,7 @@ func (s *Site) renderHomePage() error {
 
        if !viper.GetBool("DisableRSS") {
                // XML Feed
-               n.URLPath.URL = permalinkStr(viper.GetString("RSSUri"))
+               s.setURLs(n, viper.GetString("RSSUri"))
                n.Title = ""
                high := 50
                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"}
 
-               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
                }
        }
@@ -1911,10 +1901,11 @@ func (s *Site) renderSitemap() error {
        pages := make(Pages, 0)
 
        page := &Page{}
+       page.language = s.Language
        page.Date = s.Info.LastChange
        page.Lastmod = s.Info.LastChange
        page.Site = &s.Info
-       page.URLPath.URL = "/"
+       page.URLPath.URL = ""
        page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
        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"}
-
-       if err := s.renderAndWriteXML("sitemap", s.addMultilingualPrefix(page.Sitemap.Filename), n, s.appendThemeTemplates(smLayouts)...); err != nil {
+       addLanguagePrefix := n.Site.IsMultiLingual()
+       if err := s.renderAndWriteXML("sitemap", n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...); err != nil {
                return err
        }
 
index 3f6e25bad537e3fbe65ee71c8eeb94115366591d..8360d7b94ad371dcf805c78fedc7ce61ea18db1e 100644 (file)
@@ -317,7 +317,6 @@ THE END.`, refShortcode)),
 // Issue #939
 // Issue #1923
 func TestShouldAlwaysHaveUglyURLs(t *testing.T) {
-       hugofs.InitMemFs()
        for _, uglyURLs := range []bool{true, false} {
                doTestShouldAlwaysHaveUglyURLs(t, uglyURLs)
        }
@@ -383,12 +382,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
        }
 
        for _, test := range tests {
-               file, err := hugofs.Destination().Open(test.doc)
-               if err != nil {
-                       t.Fatalf("Did not find %s in target: %s", test.doc, err)
-               }
-
-               content := helpers.ReaderToString(file)
+               content := readDestination(t, test.doc)
 
                if content != test.expected {
                        t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content)
index 6c57084dbe7f000beaa5584a98f8fc6dc2fd0fdb..d46c8b05c50d6753b2dc9719828ca34f7925a0c3 100644 (file)
 package hugolib
 
 import (
-       "bytes"
        "testing"
 
        "reflect"
+       "strings"
 
        "github.com/spf13/hugo/helpers"
-       "github.com/spf13/hugo/hugofs"
        "github.com/spf13/hugo/source"
        "github.com/spf13/viper"
 )
@@ -50,15 +49,10 @@ func TestSitemapOutput(t *testing.T) {
                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 {
-               t.Fatalf("Unable to locate: sitemap.xml")
-       }
-
-       sitemap := helpers.ReaderToBytes(sitemapFile)
-       if !bytes.HasPrefix(sitemap, []byte("<?xml")) {
-               t.Errorf("Sitemap file should start with <?xml. %s", sitemap)
+       if !strings.HasPrefix(sitemapContent, "<?xml") {
+               t.Errorf("Sitemap file should start with <?xml. %s", sitemapContent)
        }
 }
 
index c13768dda9b8d943b9cc60c7eb0688678feadbe0..c418511ac3c0c92a1e9ece7d57404e605a016e3a 100644 (file)
@@ -75,7 +75,7 @@ func (t *GoHTMLTemplate) EmbedTemplates() {
     <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
     <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
     <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 }}
     <item>
       <title>{{ .Title }}</title>