Improve language handling in URLs
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 7 Aug 2016 20:01:55 +0000 (22:01 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 6 Sep 2016 15:32:18 +0000 (18:32 +0300)
The current "rendering language" is needed outside of Site. This commit moves the Language type to the helpers package, and then used to get correct correct language configuration in the markdownify template func.
This commit also adds two new template funcs: relLangURL and absLangURL.

See #2309

20 files changed:
docs/content/content/multilingual.md
docs/content/templates/functions.md
helpers/language.go [new file with mode: 0644]
helpers/url.go
helpers/url_test.go
hugolib/handler_test.go
hugolib/hugo_sites.go
hugolib/menu_test.go
hugolib/multilingual.go
hugolib/node.go
hugolib/robotstxt_test.go
hugolib/rss_test.go
hugolib/shortcode_test.go
hugolib/site.go
hugolib/site_show_plan_test.go
hugolib/site_test.go
hugolib/site_url_test.go
hugolib/sitemap_test.go
tpl/template_funcs.go
tpl/template_funcs_test.go

index f93738f9f637114eb9e13818d7b54ef0f2bcceb7..35dd1382e4ecaebf84896d7f420af2e3858fe766 100644 (file)
@@ -135,7 +135,7 @@ This uses a definition like this one in `i18n/en-US.yaml`:
 
 ### Multilingual Themes support
 
-To support Multilingual mode in your themes, some considerations must be taken for the URLs in the templates. If there are more than one language, URLs  must either  come from the built-in `.Permalink` or `.URL`, be constructed with `relURL` or `absURL` -- or prefixed with `{{.LanguagePrefix }}`.
+To support Multilingual mode in your themes, some considerations must be taken for the URLs in the templates. If there are more than one language, URLs  must either  come from the built-in `.Permalink` or `.URL`, be constructed with `relLangURL` or `absLangURL` template funcs -- or prefixed with `{{.LanguagePrefix }}`.
 
 If there are more than one language defined, the`LanguagePrefix` variable will equal `"/en"` (or whatever your `CurrentLanguage` is). If not enabled, it will be an empty string, so it is harmless for single-language sites.
 
index aa55ced4ffcf4198983679752773d460dce5bfa9..5fb540065a59309ef08df4f24df7d47c8a4577fc 100644 (file)
@@ -787,6 +787,13 @@ e.g.: `{{ i18n "translation_id" }}`
 * `{{ mul 1000 (time "2016-05-28T10:30:00.00+10:00").Unix }}` → 1464395400000 (Unix time in milliseconds)
 
 ## URLs
+### absLangURL, relLangURL
+These are similar to the `absURL` and `relURL` relatives below, but will add the correct language prefix when the site is configured with more than one language.
+
+So for a site  `baseURL` set to `http://mysite.com/hugo/` and the current language is `en`:
+
+* `{{ "blog/" | absLangURL }}` → "http://mysite.com/hugo/en/blog/"
+* `{{ "blog/" | relLangURL }}` → "/hugo/en/blog/"
 
 ### absURL, relURL
 
diff --git a/helpers/language.go b/helpers/language.go
new file mode 100644 (file)
index 0000000..4ef5edb
--- /dev/null
@@ -0,0 +1,100 @@
+// Copyright 2016-present The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package helpers
+
+import (
+       "sort"
+       "strings"
+       "sync"
+
+       "github.com/spf13/cast"
+
+       "github.com/spf13/viper"
+)
+
+type Language struct {
+       Lang       string
+       Title      string
+       Weight     int
+       params     map[string]interface{}
+       paramsInit sync.Once
+}
+
+func NewLanguage(lang string) *Language {
+       return &Language{Lang: lang, params: make(map[string]interface{})}
+}
+
+func NewDefaultLanguage() *Language {
+       defaultLang := viper.GetString("DefaultContentLanguage")
+
+       if defaultLang == "" {
+               defaultLang = "en"
+       }
+
+       return NewLanguage(defaultLang)
+}
+
+type Languages []*Language
+
+func NewLanguages(l ...*Language) Languages {
+       languages := make(Languages, len(l))
+       for i := 0; i < len(l); i++ {
+               languages[i] = l[i]
+       }
+       sort.Sort(languages)
+       return languages
+}
+
+func (l Languages) Len() int           { return len(l) }
+func (l Languages) Less(i, j int) bool { return l[i].Weight < l[j].Weight }
+func (l Languages) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
+
+func (l *Language) Params() map[string]interface{} {
+       l.paramsInit.Do(func() {
+               // Merge with global config.
+               // TODO(bep) consider making this part of a constructor func.
+
+               globalParams := viper.GetStringMap("Params")
+               for k, v := range globalParams {
+                       if _, ok := l.params[k]; !ok {
+                               l.params[k] = v
+                       }
+               }
+       })
+       return l.params
+}
+
+func (l *Language) SetParam(k string, v interface{}) {
+       l.params[k] = v
+}
+
+func (l *Language) GetString(key string) string { return cast.ToString(l.Get(key)) }
+func (ml *Language) GetStringMap(key string) map[string]interface{} {
+       return cast.ToStringMap(ml.Get(key))
+}
+
+func (l *Language) GetStringMapString(key string) map[string]string {
+       return cast.ToStringMapString(l.Get(key))
+}
+
+func (l *Language) Get(key string) interface{} {
+       if l == nil {
+               panic("language not set")
+       }
+       key = strings.ToLower(key)
+       if v, ok := l.params[key]; ok {
+               return v
+       }
+       return viper.Get(key)
+}
index 085f9e9fa374b398af391567e14b9d7bcd3c3b1a..f9a41dde3e62abf7bc5e8973760b2ac3014a0da5 100644 (file)
@@ -147,18 +147,18 @@ func MakePermalink(host, plink string) *url.URL {
 }
 
 // AbsURL creates a absolute URL from the relative path given and the BaseURL set in config.
-func AbsURL(path string) string {
-       url, err := url.Parse(path)
+func AbsURL(in string, addLanguage bool) string {
+       url, err := url.Parse(in)
        if err != nil {
-               return path
+               return in
        }
 
-       if url.IsAbs() || strings.HasPrefix(path, "//") {
-               return path
+       if url.IsAbs() || strings.HasPrefix(in, "//") {
+               return in
        }
 
        baseURL := viper.GetString("BaseURL")
-       if strings.HasPrefix(path, "/") {
+       if strings.HasPrefix(in, "/") {
                p, err := url.Parse(baseURL)
                if err != nil {
                        panic(err)
@@ -166,7 +166,23 @@ func AbsURL(path string) string {
                p.Path = ""
                baseURL = p.String()
        }
-       return MakePermalink(baseURL, path).String()
+
+       if addLanguage {
+               addSlash := in == "" || strings.HasSuffix(in, "/")
+               in = path.Join(getLanguagePrefix(), in)
+
+               if addSlash {
+                       in += "/"
+               }
+       }
+       return MakePermalink(baseURL, in).String()
+}
+
+func getLanguagePrefix() string {
+       if !viper.GetBool("Multilingual") {
+               return ""
+       }
+       return viper.Get("CurrentContentLanguage").(*Language).Lang
 }
 
 // IsAbsURL determines whether the given path points to an absolute URL.
@@ -182,23 +198,34 @@ func IsAbsURL(path string) bool {
 
 // RelURL creates a URL relative to the BaseURL root.
 // Note: The result URL will not include the context root if canonifyURLs is enabled.
-func RelURL(path string) string {
+func RelURL(in string, addLanguage bool) string {
        baseURL := viper.GetString("BaseURL")
        canonifyURLs := viper.GetBool("canonifyURLs")
-       if (!strings.HasPrefix(path, baseURL) && strings.HasPrefix(path, "http")) || strings.HasPrefix(path, "//") {
-               return path
+       if (!strings.HasPrefix(in, baseURL) && strings.HasPrefix(in, "http")) || strings.HasPrefix(in, "//") {
+               return in
        }
 
-       u := path
+       u := in
 
-       if strings.HasPrefix(path, baseURL) {
+       if strings.HasPrefix(in, baseURL) {
                u = strings.TrimPrefix(u, baseURL)
        }
 
+       if addLanguage {
+               hadSlash := strings.HasSuffix(u, "/")
+
+               u = path.Join(getLanguagePrefix(), u)
+
+               if hadSlash {
+                       u += "/"
+               }
+       }
+
        if !canonifyURLs {
                u = AddContextRoot(baseURL, u)
        }
-       if path == "" && !strings.HasSuffix(u, "/") && strings.HasSuffix(baseURL, "/") {
+
+       if in == "" && !strings.HasSuffix(u, "/") && strings.HasSuffix(baseURL, "/") {
                u += "/"
        }
 
index 9cca1cbb88af7bb2a6b0c96be870d26f5f4ec0af..f6dd9f9daf976f6da9b9b5ef63e45d4c58292cf7 100644 (file)
 package helpers
 
 import (
+       "fmt"
        "strings"
        "testing"
 
        "github.com/spf13/viper"
        "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
 )
 
 func TestURLize(t *testing.T) {
@@ -43,62 +45,114 @@ func TestURLize(t *testing.T) {
 }
 
 func TestAbsURL(t *testing.T) {
-       defer viper.Reset()
+       for _, addLanguage := range []bool{true, false} {
+               for _, m := range []bool{true, false} {
+                       for _, l := range []string{"en", "fr"} {
+                               doTestAbsURL(t, addLanguage, m, l)
+                       }
+               }
+       }
+}
+
+func doTestAbsURL(t *testing.T, addLanguage, multilingual bool, lang string) {
+       viper.Reset()
+       viper.Set("Multilingual", multilingual)
+       viper.Set("CurrentContentLanguage", NewLanguage(lang))
        tests := []struct {
                input    string
                baseURL  string
                expected string
        }{
-               {"/test/foo", "http://base/", "http://base/test/foo"},
-               {"", "http://base/ace/", "http://base/ace/"},
-               {"/test/2/foo/", "http://base", "http://base/test/2/foo/"},
+               {"/test/foo", "http://base/", "http://base/MULTItest/foo"},
+               {"", "http://base/ace/", "http://base/ace/MULTI"},
+               {"/test/2/foo/", "http://base", "http://base/MULTItest/2/foo/"},
                {"http://abs", "http://base/", "http://abs"},
                {"schema://abs", "http://base/", "schema://abs"},
                {"//schemaless", "http://base/", "//schemaless"},
-               {"test/2/foo/", "http://base/path", "http://base/path/test/2/foo/"},
-               {"/test/2/foo/", "http://base/path", "http://base/test/2/foo/"},
-               {"http//foo", "http://base/path", "http://base/path/http/foo"},
+               {"test/2/foo/", "http://base/path", "http://base/path/MULTItest/2/foo/"},
+               {"/test/2/foo/", "http://base/path", "http://base/MULTItest/2/foo/"},
+               {"http//foo", "http://base/path", "http://base/path/MULTIhttp/foo"},
        }
 
        for _, test := range tests {
-               viper.Reset()
                viper.Set("BaseURL", test.baseURL)
-               output := AbsURL(test.input)
-               if output != test.expected {
-                       t.Errorf("Expected %#v, got %#v\n", test.expected, output)
+               output := AbsURL(test.input, addLanguage)
+               expected := test.expected
+               if multilingual && addLanguage {
+                       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)
                }
        }
 }
 
+func TestIsAbsURL(t *testing.T) {
+       for i, this := range []struct {
+               a string
+               b bool
+       }{
+               {"http://gohugo.io", true},
+               {"https://gohugo.io", true},
+               {"//gohugo.io", true},
+               {"http//gohugo.io", false},
+               {"/content", false},
+               {"content", false},
+       } {
+               require.True(t, IsAbsURL(this.a) == this.b, fmt.Sprintf("Test %d", i))
+       }
+}
+
 func TestRelURL(t *testing.T) {
-       defer viper.Reset()
-       //defer viper.Set("canonifyURLs", viper.GetBool("canonifyURLs"))
+       for _, addLanguage := range []bool{true, false} {
+               for _, m := range []bool{true, false} {
+                       for _, l := range []string{"en", "fr"} {
+                               doTestRelURL(t, addLanguage, m, l)
+                       }
+               }
+       }
+}
+
+func doTestRelURL(t *testing.T, addLanguage, multilingual bool, lang string) {
+       viper.Reset()
+       viper.Set("Multilingual", multilingual)
+       viper.Set("CurrentContentLanguage", NewLanguage(lang))
+
        tests := []struct {
                input    string
                baseURL  string
                canonify bool
                expected string
        }{
-               {"/test/foo", "http://base/", false, "/test/foo"},
-               {"test.css", "http://base/sub", false, "/sub/test.css"},
-               {"test.css", "http://base/sub", true, "/test.css"},
-               {"/test/", "http://base/", false, "/test/"},
-               {"/test/", "http://base/sub/", false, "/sub/test/"},
-               {"/test/", "http://base/sub/", true, "/test/"},
-               {"", "http://base/ace/", false, "/ace/"},
-               {"", "http://base/ace", false, "/ace"},
+               {"/test/foo", "http://base/", false, "MULTI/test/foo"},
+               {"test.css", "http://base/sub", false, "/subMULTI/test.css"},
+               {"test.css", "http://base/sub", true, "MULTI/test.css"},
+               {"/test/", "http://base/", false, "MULTI/test/"},
+               {"/test/", "http://base/sub/", false, "/subMULTI/test/"},
+               {"/test/", "http://base/sub/", true, "MULTI/test/"},
+               {"", "http://base/ace/", false, "/aceMULTI/"},
+               {"", "http://base/ace", false, "/aceMULTI"},
                {"http://abs", "http://base/", false, "http://abs"},
                {"//schemaless", "http://base/", false, "//schemaless"},
        }
 
        for i, test := range tests {
-               viper.Reset()
                viper.Set("BaseURL", test.baseURL)
                viper.Set("canonifyURLs", test.canonify)
 
-               output := RelURL(test.input)
-               if output != test.expected {
-                       t.Errorf("[%d][%t] Expected %#v, got %#v\n", i, test.canonify, test.expected, output)
+               output := RelURL(test.input, addLanguage)
+
+               expected := test.expected
+               if multilingual && addLanguage {
+                       expected = strings.Replace(expected, "MULTI", "/"+lang, 1)
+               } else {
+                       expected = strings.Replace(expected, "MULTI", "", 1)
+               }
+
+               if output != expected {
+                       t.Errorf("[%d][%t] Expected %#v, got %#v\n", i, test.canonify, expected, output)
                }
        }
 }
index fce29df44e23af635f5249403ea87037b3aa4bcb..0e59510d35751804eb744c08f9dc0268b94033ad 100644 (file)
@@ -46,7 +46,7 @@ func TestDefaultHandler(t *testing.T) {
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
                targets:  targetList{page: &target.PagePub{UglyURLs: true, PublishDir: "public"}},
-               Language: NewLanguage("en"),
+               Language: helpers.NewLanguage("en"),
        }
 
        if err := buildAndRenderSite(s,
index 8fd2a63b973cc69feaf6f36a627a3b5e7893d91d..561c43a3a93c2a35d8b35c984036f0f36dfaa205 100644 (file)
@@ -63,7 +63,7 @@ func createSitesFromConfig() ([]*Site, error) {
        multilingual := viper.GetStringMap("Languages")
        if len(multilingual) == 0 {
                // TODO(bep) multilingo langConfigsList = append(langConfigsList, NewLanguage("en"))
-               sites = append(sites, newSite(NewLanguage("en")))
+               sites = append(sites, newSite(helpers.NewLanguage("en")))
        }
 
        if len(multilingual) > 0 {
@@ -481,7 +481,7 @@ func doBuildSite(s *Site, render bool, additionalTemplates ...string) error {
 }
 
 // Convenience func used in tests.
-func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages Languages) (*HugoSites, error) {
+func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages helpers.Languages) (*HugoSites, error) {
        if len(languages) == 0 {
                panic("Must provide at least one language")
        }
@@ -504,10 +504,10 @@ func newHugoSitesFromSourceAndLanguages(input []source.ByteSource, languages Lan
 }
 
 // Convenience func used in tests.
-func newHugoSitesFromLanguages(languages Languages) (*HugoSites, error) {
+func newHugoSitesFromLanguages(languages helpers.Languages) (*HugoSites, error) {
        return newHugoSitesFromSourceAndLanguages(nil, languages)
 }
 
 func newHugoSitesDefaultLanguage() (*HugoSites, error) {
-       return newHugoSitesFromSourceAndLanguages(nil, Languages{newDefaultLanguage()})
+       return newHugoSitesFromSourceAndLanguages(nil, helpers.Languages{helpers.NewDefaultLanguage()})
 }
index 9ef4d09ad0e23b349e75f42b47cafc28a0b4cb75..4eafba47ebd3e3210f99d37cabc60950ea701626 100644 (file)
@@ -18,6 +18,8 @@ import (
        "strings"
        "testing"
 
+       "github.com/spf13/hugo/helpers"
+
        "path/filepath"
 
        toml "github.com/pelletier/go-toml"
@@ -673,7 +675,7 @@ func createTestSite(pageSources []source.ByteSource) *Site {
 
        return &Site{
                Source:   &source.InMemorySource{ByteSource: pageSources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
 }
index 9c5342f995788aabe8cc8d8b36dfebfa73f970ce..6105876591139b154addd26a589740ceec0132de 100644 (file)
@@ -1,3 +1,16 @@
+// Copyright 2016-present The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package hugolib
 
 import (
@@ -10,58 +23,21 @@ import (
        "fmt"
 
        "github.com/spf13/cast"
-       "github.com/spf13/viper"
+       "github.com/spf13/hugo/helpers"
 )
 
-type Language struct {
-       Lang       string
-       Title      string
-       Weight     int
-       params     map[string]interface{}
-       paramsInit sync.Once
-}
-
-func NewLanguage(lang string) *Language {
-       return &Language{Lang: lang, params: make(map[string]interface{})}
-}
-
-func newDefaultLanguage() *Language {
-       defaultLang := viper.GetString("DefaultContentLanguage")
-
-       if defaultLang == "" {
-               defaultLang = "en"
-       }
-
-       return NewLanguage(defaultLang)
-}
-
-type Languages []*Language
-
-func NewLanguages(l ...*Language) Languages {
-       languages := make(Languages, len(l))
-       for i := 0; i < len(l); i++ {
-               languages[i] = l[i]
-       }
-       sort.Sort(languages)
-       return languages
-}
-
-func (l Languages) Len() int           { return len(l) }
-func (l Languages) Less(i, j int) bool { return l[i].Weight < l[j].Weight }
-func (l Languages) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
-
 type Multilingual struct {
-       Languages Languages
+       Languages helpers.Languages
 
-       DefaultLang *Language
+       DefaultLang *helpers.Language
 
-       langMap     map[string]*Language
+       langMap     map[string]*helpers.Language
        langMapInit sync.Once
 }
 
-func (ml *Multilingual) Language(lang string) *Language {
+func (ml *Multilingual) Language(lang string) *helpers.Language {
        ml.langMapInit.Do(func() {
-               ml.langMap = make(map[string]*Language)
+               ml.langMap = make(map[string]*helpers.Language)
                for _, l := range ml.Languages {
                        ml.langMap[l.Lang] = l
                }
@@ -70,7 +46,7 @@ func (ml *Multilingual) Language(lang string) *Language {
 }
 
 func newMultiLingualFromSites(sites ...*Site) (*Multilingual, error) {
-       languages := make(Languages, len(sites))
+       languages := make(helpers.Languages, len(sites))
 
        for i, s := range sites {
                if s.Language == nil {
@@ -79,61 +55,22 @@ func newMultiLingualFromSites(sites ...*Site) (*Multilingual, error) {
                languages[i] = s.Language
        }
 
-       return &Multilingual{Languages: languages, DefaultLang: newDefaultLanguage()}, nil
+       return &Multilingual{Languages: languages, DefaultLang: helpers.NewDefaultLanguage()}, nil
 
 }
 
 func newMultiLingualDefaultLanguage() *Multilingual {
-       return newMultiLingualForLanguage(newDefaultLanguage())
+       return newMultiLingualForLanguage(helpers.NewDefaultLanguage())
 }
 
-func newMultiLingualForLanguage(language *Language) *Multilingual {
-       languages := Languages{language}
+func newMultiLingualForLanguage(language *helpers.Language) *Multilingual {
+       languages := helpers.Languages{language}
        return &Multilingual{Languages: languages, DefaultLang: language}
 }
 func (ml *Multilingual) enabled() bool {
        return len(ml.Languages) > 1
 }
 
-func (l *Language) Params() map[string]interface{} {
-       l.paramsInit.Do(func() {
-               // Merge with global config.
-               // TODO(bep) consider making this part of a constructor func.
-
-               globalParams := viper.GetStringMap("Params")
-               for k, v := range globalParams {
-                       if _, ok := l.params[k]; !ok {
-                               l.params[k] = v
-                       }
-               }
-       })
-       return l.params
-}
-
-func (l *Language) SetParam(k string, v interface{}) {
-       l.params[k] = v
-}
-
-func (l *Language) GetString(key string) string { return cast.ToString(l.Get(key)) }
-func (ml *Language) GetStringMap(key string) map[string]interface{} {
-       return cast.ToStringMap(ml.Get(key))
-}
-
-func (l *Language) GetStringMapString(key string) map[string]string {
-       return cast.ToStringMapString(l.Get(key))
-}
-
-func (l *Language) Get(key string) interface{} {
-       if l == nil {
-               panic("language not set")
-       }
-       key = strings.ToLower(key)
-       if v, ok := l.params[key]; ok {
-               return v
-       }
-       return viper.Get(key)
-}
-
 func (s *Site) multilingualEnabled() bool {
        return s.Multilingual != nil && s.Multilingual.enabled()
 }
@@ -143,12 +80,12 @@ func (s *Site) currentLanguageString() string {
        return s.currentLanguage().Lang
 }
 
-func (s *Site) currentLanguage() *Language {
+func (s *Site) currentLanguage() *helpers.Language {
        return s.Language
 }
 
-func toSortedLanguages(l map[string]interface{}) (Languages, error) {
-       langs := make(Languages, len(l))
+func toSortedLanguages(l map[string]interface{}) (helpers.Languages, error) {
+       langs := make(helpers.Languages, len(l))
        i := 0
 
        for lang, langConf := range l {
@@ -158,7 +95,7 @@ func toSortedLanguages(l map[string]interface{}) (Languages, error) {
                        return nil, fmt.Errorf("Language config is not a map: %v", langsMap)
                }
 
-               language := NewLanguage(lang)
+               language := helpers.NewLanguage(lang)
 
                for k, v := range langsMap {
                        loki := strings.ToLower(k)
index 773573c3cfc77d42a050e22f0395a38072a287bb..e9a5ab1a9148652e155ef9f52adb4a6ce9bb777f 100644 (file)
@@ -47,7 +47,7 @@ type Node struct {
        paginatorInit sync.Once
        scratch       *Scratch
 
-       language     *Language
+       language     *helpers.Language
        languageInit sync.Once
        lang         string // TODO(bep) multilingo
 
@@ -193,7 +193,7 @@ func (n *Node) Scratch() *Scratch {
 }
 
 // TODO(bep) multilingo consolidate. See Page.
-func (n *Node) Language() *Language {
+func (n *Node) Language() *helpers.Language {
        n.initLanguage()
        return n.language
 }
index 62be915227b2e492040cf6f71094d4a575a3f225..5b6c908264554e27a0ac631a44f11a6c2bac0dfc 100644 (file)
@@ -39,7 +39,7 @@ func TestRobotsTXTOutput(t *testing.T) {
 
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: weightedSources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s, "robots.txt", robotTxtTemplate); err != nil {
index f9f26cb7b8309e4fc77e1a04b7fd58e726dca85d..007d6a17ce7a1cc8020e6873f42deb9ec01ca04e 100644 (file)
@@ -55,7 +55,7 @@ func TestRSSOutput(t *testing.T) {
        hugofs.InitMemFs()
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: weightedSources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s, "rss.xml", rssTemplate); err != nil {
index 1b5c6c8470ab203cef72fa70b260f808dbe7ddd1..18e6314b3aad57d37aa9da8d3a355b8886476803 100644 (file)
@@ -562,7 +562,7 @@ tags:
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
                targets:  targetList{page: &target.PagePub{UglyURLs: false}},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        addTemplates := func(templ tpl.Template) error {
index fe3285e1412b5d4e9538c009f288b9d438164c0f..cfbe75f70695027982ee2a8b61d449cf9e616d37 100644 (file)
@@ -96,7 +96,7 @@ type Site struct {
        futureCount  int
        expiredCount int
        Data         map[string]interface{}
-       Language     *Language
+       Language     *helpers.Language
 }
 
 // Reset returns a new Site prepared for rebuild.
@@ -106,13 +106,13 @@ func (s *Site) Reset() *Site {
 }
 
 // newSite creates a new site in the given language.
-func newSite(lang *Language) *Site {
+func newSite(lang *helpers.Language) *Site {
        return &Site{Language: lang, Info: SiteInfo{multilingual: newMultiLingualForLanguage(lang)}}
 }
 
 // newSite creates a new site in the default language.
 func newSiteDefaultLang() *Site {
-       return newSite(newDefaultLanguage())
+       return newSite(helpers.NewDefaultLanguage())
 }
 
 // Convenience func used in tests.
@@ -131,7 +131,7 @@ func newSiteFromSources(pathContentPairs ...string) *Site {
 
        return &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 }
 
@@ -173,9 +173,9 @@ type SiteInfo struct {
        Data                  *map[string]interface{}
 
        multilingual   *Multilingual
-       Language       *Language
+       Language       *helpers.Language
        LanguagePrefix string
-       Languages      Languages
+       Languages      helpers.Languages
 }
 
 // Used in tests.
@@ -782,6 +782,9 @@ func (s *Site) setupPrevNext() {
 }
 
 func (s *Site) render() (err error) {
+       // There are sadly some global template funcs etc. that needs the language information.
+       viper.Set("Multilingual", s.multilingualEnabled())
+       viper.Set("CurrentContentLanguage", s.Language)
        if err = tpl.SetTranslateLang(s.Language.Lang); err != nil {
                return
        }
@@ -851,11 +854,11 @@ func (s *Site) initialize() (err error) {
 
 // HomeAbsURL is a convenience method giving the absolute URL to the home page.
 func (s *SiteInfo) HomeAbsURL() string {
-       base := "/"
+       base := ""
        if s.IsMultiLingual() {
                base = s.Language.Lang
        }
-       return helpers.AbsURL(base)
+       return helpers.AbsURL(base, false)
 }
 
 // SitemapAbsURL is a convenience method giving the absolute URL to the sitemap.
@@ -867,8 +870,8 @@ func (s *SiteInfo) SitemapAbsURL() string {
 func (s *Site) initializeSiteInfo() {
 
        var (
-               lang      *Language = s.Language
-               languages Languages
+               lang      *helpers.Language = s.Language
+               languages helpers.Languages
        )
 
        if s.Multilingual != nil {
@@ -1435,7 +1438,7 @@ func (s *Site) renderAliases() error {
 
        if s.Multilingual.enabled() {
                mainLang := s.Multilingual.DefaultLang.Lang
-               mainLangURL := helpers.AbsURL(mainLang)
+               mainLangURL := helpers.AbsURL(mainLang, false)
                jww.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
                if err := s.publishDestAlias(s.languageAliasTarget(), "/", mainLangURL); err != nil {
                        return err
index d330f4344a6b77c423691b6b1489789c48b36893..981bed7bfacac3e81aef2601780e4f524689aea8 100644 (file)
@@ -112,7 +112,7 @@ func _TestPageTargetUgly(t *testing.T) {
        s := &Site{
                targets:  targetList{page: &target.PagePub{UglyURLs: true, PublishDir: "public"}},
                Source:   &source.InMemorySource{ByteSource: fakeSource},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s); err != nil {
index f98bc6a7deccbe6f0fb230fecacea342edad4054..b93e08acaba3bb0a563a38fe620bb7dcfe35638a 100644 (file)
@@ -128,7 +128,7 @@ func TestDraftAndFutureRender(t *testing.T) {
        siteSetup := func(t *testing.T) *Site {
                s := &Site{
                        Source:   &source.InMemorySource{ByteSource: sources},
-                       Language: newDefaultLanguage(),
+                       Language: helpers.NewDefaultLanguage(),
                }
 
                if err := buildSiteSkipRender(s); err != nil {
@@ -186,7 +186,7 @@ func TestFutureExpirationRender(t *testing.T) {
        siteSetup := func(t *testing.T) *Site {
                s := &Site{
                        Source:   &source.InMemorySource{ByteSource: sources},
-                       Language: newDefaultLanguage(),
+                       Language: helpers.NewDefaultLanguage(),
                }
 
                if err := buildSiteSkipRender(s); err != nil {
@@ -280,7 +280,7 @@ THE END.`, refShortcode)),
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
                targets:  targetList{page: &target.PagePub{UglyURLs: uglyURLs}},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s, "_default/single.html", "{{.Content}}"); err != nil {
@@ -348,7 +348,7 @@ func doTestShouldAlwaysHaveUglyURLs(t *testing.T, uglyURLs bool) {
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
                targets:  targetList{page: &target.PagePub{UglyURLs: uglyURLs, PublishDir: "public"}},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s,
@@ -438,7 +438,7 @@ func doTestSectionNaming(t *testing.T, canonify, uglify, pluralize bool) {
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
                targets:  targetList{page: &target.PagePub{UglyURLs: uglify}},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s,
@@ -500,7 +500,7 @@ func TestSkipRender(t *testing.T) {
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
                targets:  targetList{page: &target.PagePub{UglyURLs: true}},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s,
@@ -555,7 +555,7 @@ func TestAbsURLify(t *testing.T) {
                        s := &Site{
                                Source:   &source.InMemorySource{ByteSource: sources},
                                targets:  targetList{page: &target.PagePub{UglyURLs: true}},
-                               Language: newDefaultLanguage(),
+                               Language: helpers.NewDefaultLanguage(),
                        }
                        t.Logf("Rendering with BaseURL %q and CanonifyURLs set %v", viper.GetString("baseURL"), canonify)
 
@@ -649,7 +649,7 @@ func TestOrderedPages(t *testing.T) {
        viper.Set("baseurl", "http://auth/bub")
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: weightedSources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildSiteSkipRender(s); err != nil {
@@ -718,7 +718,7 @@ func TestGroupedPages(t *testing.T) {
        viper.Set("baseurl", "http://auth/bub")
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: groupedSources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildSiteSkipRender(s); err != nil {
@@ -903,7 +903,7 @@ func TestWeightedTaxonomies(t *testing.T) {
        viper.Set("taxonomies", taxonomies)
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildSiteSkipRender(s); err != nil {
@@ -972,7 +972,7 @@ func setupLinkingMockSite(t *testing.T) *Site {
 
        site := &Site{
                Source:   &source.InMemorySource{ByteSource: sources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildSiteSkipRender(site); err != nil {
index 6b96b09dc05502576e3836e53c0e2db64174382d..522732cc24190f7659bfc91a30b4524baf0f30ad 100644 (file)
@@ -17,6 +17,8 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/spf13/hugo/helpers"
+
        "html/template"
 
        "github.com/spf13/hugo/hugofs"
@@ -90,7 +92,7 @@ func TestPageCount(t *testing.T) {
        viper.Set("paginate", 10)
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: urlFakeSource},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s, "indexes/blue.html", indexTemplate); err != nil {
index 58408ce4755ade8d7ad23af11bf3939aa9c8c048..6c57084dbe7f000beaa5584a98f8fc6dc2fd0fdb 100644 (file)
@@ -43,7 +43,7 @@ func TestSitemapOutput(t *testing.T) {
 
        s := &Site{
                Source:   &source.InMemorySource{ByteSource: weightedSources},
-               Language: newDefaultLanguage(),
+               Language: helpers.NewDefaultLanguage(),
        }
 
        if err := buildAndRenderSite(s, "sitemap.xml", SITEMAP_TEMPLATE); err != nil {
index 941115cd9a31f00785da71fa5e4d19467d76e318..41e616b1b3875ad324caa9c771405e14f9c252df 100644 (file)
@@ -1214,9 +1214,11 @@ func markdownify(in interface{}) (template.HTML, error) {
        if err != nil {
                return "", err
        }
-       // TODO(bep) ml language
+
+       language := viper.Get("CurrentContentLanguage").(*helpers.Language)
+
        m := helpers.RenderBytes(&helpers.RenderingContext{
-               ConfigProvider: viper.GetViper(),
+               ConfigProvider: language,
                Content:        []byte(text), PageFmt: "markdown"})
        m = bytes.TrimPrefix(m, markdownTrimPrefix)
        m = bytes.TrimSuffix(m, markdownTrimSuffix)
@@ -1831,7 +1833,7 @@ func absURL(a interface{}) (template.HTML, error) {
        if err != nil {
                return "", nil
        }
-       return template.HTML(helpers.AbsURL(s)), nil
+       return template.HTML(helpers.AbsURL(s, false)), nil
 }
 
 func relURL(a interface{}) (template.HTML, error) {
@@ -1839,12 +1841,13 @@ func relURL(a interface{}) (template.HTML, error) {
        if err != nil {
                return "", nil
        }
-       return template.HTML(helpers.RelURL(s)), nil
+       return template.HTML(helpers.RelURL(s, false)), nil
 }
 
 func init() {
        funcMap = template.FuncMap{
                "absURL":       absURL,
+               "absLangURL":   func(a string) template.HTML { return template.HTML(helpers.AbsURL(a, true)) },
                "add":          func(a, b interface{}) (interface{}, error) { return helpers.DoArithmetic(a, b, '+') },
                "after":        after,
                "apply":        apply,
@@ -1898,6 +1901,7 @@ func init() {
                "readFile":     readFileFromWorkingDir,
                "ref":          ref,
                "relURL":       relURL,
+               "relLangURL":   func(a string) template.HTML { return template.HTML(helpers.RelURL(a, true)) },
                "relref":       relRef,
                "replace":      replace,
                "replaceRE":    replaceRE,
index 881523811b524877dcd019fcafe04f9414d897fb..3df38f380fa4aadcdb9970fe679d83dbcba638ad 100644 (file)
@@ -71,6 +71,8 @@ func TestFuncsInTemplate(t *testing.T) {
        workingDir := "/home/hugo"
 
        viper.Set("WorkingDir", workingDir)
+       viper.Set("CurrentContentLanguage", helpers.NewDefaultLanguage())
+       viper.Set("Multilingual", true)
 
        fs := &afero.MemMapFs{}
        hugofs.InitFs(fs)
@@ -80,7 +82,8 @@ func TestFuncsInTemplate(t *testing.T) {
        // Add the examples from the docs: As a smoke test and to make sure the examples work.
        // TODO(bep): docs: fix title example
        in :=
-               `absURL: {{ "http://gohugo.io/" | absURL }}
+               `absLangURL: {{ "index.html" | absLangURL }}
+absURL: {{ "http://gohugo.io/" | absURL }}
 absURL: {{ "mystyle.css" | absURL }}
 absURL: {{ 42 | absURL }}
 add: {{add 1 2}}
@@ -120,6 +123,7 @@ pluralize: {{ "cat" | pluralize }}
 querify: {{ (querify "foo" 1 "bar" 2 "baz" "with spaces" "qux" "this&that=those") | safeHTML }}
 readDir: {{ range (readDir ".") }}{{ .Name }}{{ end }}
 readFile: {{ readFile "README.txt" }}
+relLangURL: {{ "index.html" | relLangURL }}
 relURL 1: {{ "http://gohugo.io/" | relURL }}
 relURL 2: {{ "mystyle.css" | relURL }}
 relURL 3: {{ mul 2 21 | relURL }}
@@ -146,7 +150,8 @@ upper: {{upper "BatMan"}}
 urlize: {{ "Bat Man" | urlize }}
 `
 
-       expected := `absURL: http://gohugo.io/
+       expected := `absLangURL: http://mysite.com/hugo/en/index.html
+absURL: http://gohugo.io/
 absURL: http://mysite.com/hugo/mystyle.css
 absURL: http://mysite.com/hugo/42
 add: 3
@@ -186,6 +191,7 @@ pluralize: cats
 querify: bar=2&baz=with+spaces&foo=1&qux=this%26that%3Dthose
 readDir: README.txt
 readFile: Hugo Rocks!
+relLangURL: /hugo/en/index.html
 relURL 1: http://gohugo.io/
 relURL 2: /hugo/mystyle.css
 relURL 3: /hugo/42
@@ -1733,6 +1739,8 @@ func TestReturnWhenSet(t *testing.T) {
 }
 
 func TestMarkdownify(t *testing.T) {
+       viper.Set("CurrentContentLanguage", helpers.NewDefaultLanguage())
+
        for i, this := range []struct {
                in     interface{}
                expect interface{}