Skip to content

Commit

Permalink
Render main content language in root by default
Browse files Browse the repository at this point in the history
Fixes #2312
  • Loading branch information
bep committed Aug 8, 2016
1 parent bd611ff commit 66971e7
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 128 deletions.
6 changes: 5 additions & 1 deletion docs/content/content/multilingual.md
Expand Up @@ -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
Expand All @@ -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.

Expand Down
4 changes: 4 additions & 0 deletions helpers/language.go
Expand Up @@ -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{})}
}
Expand Down
32 changes: 23 additions & 9 deletions helpers/url.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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 += "/"
}
}
}

Expand Down
44 changes: 31 additions & 13 deletions helpers/url_test.go
Expand Up @@ -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
Expand All @@ -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)
}
}
}
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions hugolib/config.go
Expand Up @@ -104,4 +104,5 @@ func loadDefaultSettings() {
viper.SetDefault("UseModTimeAsFallback", false)
viper.SetDefault("Multilingual", false)
viper.SetDefault("DefaultContentLanguage", "en")
viper.SetDefault("DefaultContentLanguageInSubdir", false)
}
129 changes: 126 additions & 3 deletions hugolib/hugo_sites_test.go
Expand Up @@ -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)
Expand Down Expand Up @@ -397,7 +519,7 @@ DisableSitemap = false
DisableRSS = false
RSSUri = "index.xml"
paginate = 2
paginate = 1
DefaultContentLanguage = "fr"
[permalinks]
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -505,6 +627,7 @@ title: doc3
publishdate: "2000-01-03"
tags:
- tag2
- tag1
url: /superbob
---
# doc3
Expand Down

0 comments on commit 66971e7

Please sign in to comment.