shithub: hugo

Download patch

ref: 54141f71dd0ffbd2af326581b78ecafe7f054f51
parent: 2079a23dd89734cea39e523faf46e44201151279
author: Bjørn Erik Pedersen <[email protected]>
date: Sun Aug 7 18:01:55 EDT 2016

Improve language handling in URLs

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

--- a/docs/content/content/multilingual.md
+++ b/docs/content/content/multilingual.md
@@ -135,7 +135,7 @@
 
 ### 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.
 
--- a/docs/content/templates/functions.md
+++ b/docs/content/templates/functions.md
@@ -787,6 +787,13 @@
 * `{{ 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
 
--- /dev/null
+++ b/helpers/language.go
@@ -1,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)
+}
--- a/helpers/url.go
+++ b/helpers/url.go
@@ -147,18 +147,18 @@
 }
 
 // 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,9 +166,25 @@
 		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.
 // TODO(bep) ml tests
 func IsAbsURL(path string) bool {
@@ -182,23 +198,34 @@
 
 // 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 += "/"
 	}
 
--- a/helpers/url_test.go
+++ b/helpers/url_test.go
@@ -14,11 +14,13 @@
 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,36 +45,81 @@
 }
 
 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
@@ -79,26 +126,33 @@
 		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)
 		}
 	}
 }
--- a/hugolib/handler_test.go
+++ b/hugolib/handler_test.go
@@ -46,7 +46,7 @@
 	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,
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -63,7 +63,7 @@
 	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 @@
 }
 
 // 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 @@
 }
 
 // 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()})
 }
--- a/hugolib/menu_test.go
+++ b/hugolib/menu_test.go
@@ -18,6 +18,8 @@
 	"strings"
 	"testing"
 
+	"github.com/spf13/hugo/helpers"
+
 	"path/filepath"
 
 	toml "github.com/pelletier/go-toml"
@@ -673,7 +675,7 @@
 
 	return &Site{
 		Source:   &source.InMemorySource{ByteSource: pageSources},
-		Language: newDefaultLanguage(),
+		Language: helpers.NewDefaultLanguage(),
 	}
 
 }
--- a/hugolib/multilingual.go
+++ b/hugolib/multilingual.go
@@ -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 @@
 	"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 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,16 +55,16 @@
 		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 {
@@ -95,45 +71,6 @@
 	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 @@
 	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 @@
 			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)
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -47,7 +47,7 @@
 	paginatorInit sync.Once
 	scratch       *Scratch
 
-	language     *Language
+	language     *helpers.Language
 	languageInit sync.Once
 	lang         string // TODO(bep) multilingo
 
@@ -193,7 +193,7 @@
 }
 
 // TODO(bep) multilingo consolidate. See Page.
-func (n *Node) Language() *Language {
+func (n *Node) Language() *helpers.Language {
 	n.initLanguage()
 	return n.language
 }
--- a/hugolib/robotstxt_test.go
+++ b/hugolib/robotstxt_test.go
@@ -39,7 +39,7 @@
 
 	s := &Site{
 		Source:   &source.InMemorySource{ByteSource: weightedSources},
-		Language: newDefaultLanguage(),
+		Language: helpers.NewDefaultLanguage(),
 	}
 
 	if err := buildAndRenderSite(s, "robots.txt", robotTxtTemplate); err != nil {
--- a/hugolib/rss_test.go
+++ b/hugolib/rss_test.go
@@ -55,7 +55,7 @@
 	hugofs.InitMemFs()
 	s := &Site{
 		Source:   &source.InMemorySource{ByteSource: weightedSources},
-		Language: newDefaultLanguage(),
+		Language: helpers.NewDefaultLanguage(),
 	}
 
 	if err := buildAndRenderSite(s, "rss.xml", rssTemplate); err != nil {
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -562,7 +562,7 @@
 	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 {
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -96,7 +96,7 @@
 	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 @@
 }
 
 // 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 @@
 
 	return &Site{
 		Source:   &source.InMemorySource{ByteSource: sources},
-		Language: newDefaultLanguage(),
+		Language: helpers.NewDefaultLanguage(),
 	}
 }
 
@@ -173,9 +173,9 @@
 	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) 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 @@
 
 // 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 *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 @@
 
 	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
--- a/hugolib/site_show_plan_test.go
+++ b/hugolib/site_show_plan_test.go
@@ -112,7 +112,7 @@
 	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 {
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -128,7 +128,7 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 			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 @@
 	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 @@
 	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 @@
 	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 @@
 
 	site := &Site{
 		Source:   &source.InMemorySource{ByteSource: sources},
-		Language: newDefaultLanguage(),
+		Language: helpers.NewDefaultLanguage(),
 	}
 
 	if err := buildSiteSkipRender(site); err != nil {
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -17,6 +17,8 @@
 	"path/filepath"
 	"testing"
 
+	"github.com/spf13/hugo/helpers"
+
 	"html/template"
 
 	"github.com/spf13/hugo/hugofs"
@@ -90,7 +92,7 @@
 	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 {
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -43,7 +43,7 @@
 
 	s := &Site{
 		Source:   &source.InMemorySource{ByteSource: weightedSources},
-		Language: newDefaultLanguage(),
+		Language: helpers.NewDefaultLanguage(),
 	}
 
 	if err := buildAndRenderSite(s, "sitemap.xml", SITEMAP_TEMPLATE); err != nil {
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -1214,9 +1214,11 @@
 	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 @@
 	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 @@
 	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 @@
 		"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,
--- a/tpl/template_funcs_test.go
+++ b/tpl/template_funcs_test.go
@@ -71,6 +71,8 @@
 	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 @@
 	// 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 @@
 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 @@
 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 @@
 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 TestMarkdownify(t *testing.T) {
+	viper.Set("CurrentContentLanguage", helpers.NewDefaultLanguage())
+
 	for i, this := range []struct {
 		in     interface{}
 		expect interface{}