From 8da040342eb0a3098e54dc6ed2cb10bac6173230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 8 Aug 2016 13:55:18 +0200 Subject: [PATCH] Render main content language in root by default Fixes #2312 --- docs/content/content/multilingual.md | 6 +- helpers/language.go | 4 + helpers/url.go | 32 +++++-- helpers/url_test.go | 44 ++++++--- hugolib/config.go | 1 + hugolib/hugo_sites_test.go | 129 +++++++++++++++++++++++++- hugolib/node.go | 46 ++++++--- hugolib/page.go | 6 +- hugolib/site.go | 133 +++++++++++++-------------- hugolib/site_test.go | 8 +- hugolib/sitemap_test.go | 14 +-- tpl/template_embedded.go | 2 +- 12 files changed, 296 insertions(+), 129 deletions(-) diff --git a/docs/content/content/multilingual.md b/docs/content/content/multilingual.md index 35dd1382e..f23a6afcf 100644 --- a/docs/content/content/multilingual.md +++ b/docs/content/content/multilingual.md @@ -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. diff --git a/helpers/language.go b/helpers/language.go index 4ef5edbc4..994129308 100644 --- a/helpers/language.go +++ b/helpers/language.go @@ -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{})} } diff --git a/helpers/url.go b/helpers/url.go index 83273324d..4d06fb042 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -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 += "/" + } } } diff --git a/helpers/url_test.go b/helpers/url_test.go index f6dd9f9da..d1fadb252 100644 --- a/helpers/url_test.go +++ b/helpers/url_test.go @@ -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) } diff --git a/hugolib/config.go b/hugolib/config.go index c4292e81b..17673453e 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -104,4 +104,5 @@ func loadDefaultSettings() { viper.SetDefault("UseModTimeAsFallback", false) viper.SetDefault("Multilingual", false) viper.SetDefault("DefaultContentLanguage", "en") + viper.SetDefault("DefaultContentLanguageInSubdir", false) } diff --git a/hugolib/hugo_sites_test.go b/hugolib/hugo_sites_test.go index 655feba0f..6ab60d9ec 100644 --- a/hugolib/hugo_sites_test.go +++ b/hugolib/hugo_sites_test.go @@ -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, ``) + } + 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, + "http:/example.com/blog/en/sitemap.xml", + "http:/example.com/blog/fr/sitemap.xml") + + if defaultInSubDir { + assertFileContent(t, "public/fr/sitemap.xml", true, "http://example.com/blog/fr/") + } else { + assertFileContent(t, "public/fr/sitemap.xml", true, "http://example.com/blog/") + } + assertFileContent(t, "public/en/sitemap.xml", true, "http://example.com/blog/en/") + + // Check rss + assertFileContent(t, "public/fr/index.xml", defaultInSubDir, `{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Copyright }} {{.}}{{end}}{{ if not .Date.IsZero }} {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} - + {{ range first 15 .Data.Pages }} {{ .Title }}