shithub: hugo

Download patch

ref: 2079a23dd89734cea39e523faf46e44201151279
parent: 596e0e98e4483d2a0a709412a338ceddb6538757
author: Bjørn Erik Pedersen <[email protected]>
date: Sun Aug 7 10:03:03 EDT 2016

Make it possible to configure Blackfroday per language

See #2309

--- a/docs/content/content/multilingual.md
+++ b/docs/content/content/multilingual.md
@@ -38,16 +38,22 @@
 
 Only the obvious non-global options can be overridden per language. Examples of global options are `BaseURL`, `BuildDrafts`, etc.
 
-Taxonomies configuration can also be set per language, example:
+Taxonomies and Blackfriday configuration can also be set per language, example:
 
 ```
 [Taxonomies]
 tag = "tags"
 
+[blackfriday]
+angledQuotes = true
+hrefTargetBlank = true
+
 [Languages]
 [Languages.en]
 weight = 1
 title = "English"
+[Languages.en.blackfriday]
+angledQuotes = false
 
 [Languages.fr]
 weight = 2
--- /dev/null
+++ b/helpers/configProvider.go
@@ -1,0 +1,25 @@
+// 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 implements general utility functions that work with
+// and on content.  The helper functions defined here lay down the
+// foundation of how Hugo works with files and filepaths, and perform
+// string operations on content.
+package helpers
+
+// ConfigProvider provides the configuration settings for Hugo.
+type ConfigProvider interface {
+	GetString(key string) string
+	GetStringMap(key string) map[string]interface{}
+	GetStringMapString(key string) map[string]string
+}
--- a/helpers/content.go
+++ b/helpers/content.go
@@ -59,7 +59,7 @@
 }
 
 // NewBlackfriday creates a new Blackfriday filled with site config or some sane defaults.
-func NewBlackfriday() *Blackfriday {
+func NewBlackfriday(c ConfigProvider) *Blackfriday {
 	combinedParam := map[string]interface{}{
 		"smartypants":                      true,
 		"angledQuotes":                     false,
@@ -72,7 +72,7 @@
 		"sourceRelativeLinksProjectFolder": "/docs/content",
 	}
 
-	siteParam := viper.GetStringMap("blackfriday")
+	siteParam := c.GetStringMap("blackfriday")
 	if siteParam != nil {
 		siteConfig := cast.ToStringMap(siteParam)
 
@@ -341,20 +341,25 @@
 // RenderingContext holds contextual information, like content and configuration,
 // for a given content rendering.
 type RenderingContext struct {
-	Content      []byte
-	PageFmt      string
-	DocumentID   string
-	Config       *Blackfriday
-	RenderTOC    bool
-	FileResolver FileResolverFunc
-	LinkResolver LinkResolverFunc
-	configInit   sync.Once
+	Content        []byte
+	PageFmt        string
+	DocumentID     string
+	Config         *Blackfriday
+	RenderTOC      bool
+	FileResolver   FileResolverFunc
+	LinkResolver   LinkResolverFunc
+	ConfigProvider ConfigProvider
+	configInit     sync.Once
 }
 
+func newViperProvidedRenderingContext() *RenderingContext {
+	return &RenderingContext{ConfigProvider: viper.GetViper()}
+}
+
 func (c *RenderingContext) getConfig() *Blackfriday {
 	c.configInit.Do(func() {
 		if c.Config == nil {
-			c.Config = NewBlackfriday()
+			c.Config = NewBlackfriday(c.ConfigProvider)
 		}
 	})
 	return c.Config
--- a/helpers/content_renderer_test.go
+++ b/helpers/content_renderer_test.go
@@ -23,7 +23,7 @@
 
 // Renders a codeblock using Blackfriday
 func render(input string) string {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	render := getHTMLRenderer(0, ctx)
 
 	buf := &bytes.Buffer{}
@@ -33,7 +33,7 @@
 
 // Renders a codeblock using Mmark
 func renderWithMmark(input string) string {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	render := getMmarkHTMLRenderer(0, ctx)
 
 	buf := &bytes.Buffer{}
--- a/helpers/content_test.go
+++ b/helpers/content_test.go
@@ -22,6 +22,7 @@
 
 	"github.com/miekg/mmark"
 	"github.com/russross/blackfriday"
+	"github.com/spf13/viper"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -124,7 +125,7 @@
 }
 
 func TestGetHTMLRendererFlags(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	renderer := getHTMLRenderer(blackfriday.HTML_USE_XHTML, ctx)
 	flags := renderer.GetFlags()
 	if flags&blackfriday.HTML_USE_XHTML != blackfriday.HTML_USE_XHTML {
@@ -148,7 +149,7 @@
 		{blackfriday.HTML_SMARTYPANTS_LATEX_DASHES},
 	}
 	defaultFlags := blackfriday.HTML_USE_XHTML
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Config = ctx.getConfig()
 	ctx.Config.AngledQuotes = true
 	ctx.Config.Fractions = true
@@ -171,7 +172,7 @@
 }
 
 func TestGetHTMLRendererAnchors(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.DocumentID = "testid"
 	ctx.Config = ctx.getConfig()
 	ctx.Config.PlainIDAnchors = false
@@ -195,7 +196,7 @@
 }
 
 func TestGetMmarkHTMLRenderer(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.DocumentID = "testid"
 	ctx.Config = ctx.getConfig()
 	ctx.Config.PlainIDAnchors = false
@@ -219,7 +220,7 @@
 }
 
 func TestGetMarkdownExtensionsMasksAreRemovedFromExtensions(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Config = ctx.getConfig()
 	ctx.Config.Extensions = []string{"headerId"}
 	ctx.Config.ExtensionsMask = []string{"noIntraEmphasis"}
@@ -234,7 +235,7 @@
 	type data struct {
 		testFlag int
 	}
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Config = ctx.getConfig()
 	ctx.Config.Extensions = []string{""}
 	ctx.Config.ExtensionsMask = []string{""}
@@ -266,7 +267,7 @@
 }
 
 func TestGetMarkdownExtensionsAddingFlagsThroughRenderingContext(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Config = ctx.getConfig()
 	ctx.Config.Extensions = []string{"definitionLists"}
 	ctx.Config.ExtensionsMask = []string{""}
@@ -278,7 +279,7 @@
 }
 
 func TestGetMarkdownRenderer(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Content = []byte("testContent")
 	ctx.Config = ctx.getConfig()
 	actualRenderedMarkdown := markdownRender(ctx)
@@ -289,7 +290,7 @@
 }
 
 func TestGetMarkdownRendererWithTOC(t *testing.T) {
-	ctx := &RenderingContext{RenderTOC: true}
+	ctx := &RenderingContext{RenderTOC: true, ConfigProvider: viper.GetViper()}
 	ctx.Content = []byte("testContent")
 	ctx.Config = ctx.getConfig()
 	actualRenderedMarkdown := markdownRender(ctx)
@@ -304,7 +305,7 @@
 	type data struct {
 		testFlag int
 	}
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Config = ctx.getConfig()
 	ctx.Config.Extensions = []string{"tables"}
 	ctx.Config.ExtensionsMask = []string{""}
@@ -333,7 +334,7 @@
 }
 
 func TestMmarkRender(t *testing.T) {
-	ctx := &RenderingContext{}
+	ctx := newViperProvidedRenderingContext()
 	ctx.Content = []byte("testContent")
 	ctx.Config = ctx.getConfig()
 	actualRenderedMarkdown := mmarkRender(ctx)
--- a/hugolib/config.go
+++ b/hugolib/config.go
@@ -94,7 +94,7 @@
 	viper.SetDefault("NewContentEditor", "")
 	viper.SetDefault("Paginate", 10)
 	viper.SetDefault("PaginatePath", "page")
-	viper.SetDefault("Blackfriday", helpers.NewBlackfriday())
+	viper.SetDefault("Blackfriday", helpers.NewBlackfriday(viper.GetViper()))
 	viper.SetDefault("RSSUri", "index.xml")
 	viper.SetDefault("SectionPagesMenu", "")
 	viper.SetDefault("DisablePathToLower", false)
--- a/hugolib/embedded_shortcodes_test.go
+++ b/hugolib/embedded_shortcodes_test.go
@@ -15,7 +15,6 @@
 
 import (
 	"fmt"
-	"html/template"
 	"net/url"
 	"os"
 	"path/filepath"
@@ -55,10 +54,9 @@
 
 	templ := tpl.New()
 	p, _ := pageFromString(simplePageWithURL, path)
-	p.Node.Site = &SiteInfo{
-		rawAllPages: &(Pages{p}),
-		BaseURL:     template.URL(helpers.SanitizeURLKeepTrailingSlash(baseURL)),
-	}
+	p.Node.Site = newSiteInfoDefaultLanguage(
+		helpers.SanitizeURLKeepTrailingSlash(baseURL),
+		p)
 
 	output, err := HandleShortcodes(in, p, templ)
 
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -39,7 +39,7 @@
 
 // NewHugoSites creates a new collection of sites given the input sites, building
 // a language configuration based on those.
-func NewHugoSites(sites ...*Site) (*HugoSites, error) {
+func newHugoSites(sites ...*Site) (*HugoSites, error) {
 	langConfig, err := newMultiLingualFromSites(sites...)
 
 	if err != nil {
@@ -55,7 +55,7 @@
 	if err != nil {
 		return nil, err
 	}
-	return NewHugoSites(sites...)
+	return newHugoSites(sites...)
 }
 
 func createSitesFromConfig() ([]*Site, error) {
@@ -461,7 +461,7 @@
 
 // Convenience func used in tests to build a single site/language.
 func doBuildSite(s *Site, render bool, additionalTemplates ...string) error {
-	sites, err := NewHugoSites(s)
+	sites, err := newHugoSites(s)
 	if err != nil {
 		return err
 	}
@@ -490,7 +490,7 @@
 		Language: languages[0],
 	}
 	if len(languages) == 1 {
-		return NewHugoSites(first)
+		return newHugoSites(first)
 	}
 
 	sites := make([]*Site, len(languages))
@@ -499,7 +499,7 @@
 		sites[i] = &Site{Language: languages[i]}
 	}
 
-	return NewHugoSites(sites...)
+	return newHugoSites(sites...)
 
 }
 
@@ -506,4 +506,8 @@
 // Convenience func used in tests.
 func newHugoSitesFromLanguages(languages Languages) (*HugoSites, error) {
 	return newHugoSitesFromSourceAndLanguages(nil, languages)
+}
+
+func newHugoSitesDefaultLanguage() (*HugoSites, error) {
+	return newHugoSitesFromSourceAndLanguages(nil, Languages{newDefaultLanguage()})
 }
--- a/hugolib/hugo_sites_test.go
+++ b/hugolib/hugo_sites_test.go
@@ -137,6 +137,12 @@
 	require.NotNil(t, frTags["frtag1"])
 	readDestination(t, "public/fr/plaques/frtag1/index.html")
 	readDestination(t, "public/en/tags/tag1/index.html")
+
+	// Check Blackfriday config
+	assert.True(t, strings.Contains(string(doc1fr.Content), "&laquo;"), string(doc1fr.Content))
+	assert.False(t, strings.Contains(string(doc1en.Content), "&laquo;"), string(doc1en.Content))
+	assert.True(t, strings.Contains(string(doc1en.Content), "&ldquo;"), string(doc1en.Content))
+
 }
 
 func TestMultiSitesRebuild(t *testing.T) {
@@ -326,7 +332,6 @@
 
 	// Watching does not work with in-memory fs, so we trigger a reload manually
 	require.NoError(t, viper.ReadInConfig())
-
 	err = sites.Build(BuildCfg{CreateSitesFromConfig: true})
 
 	if err != nil {
@@ -370,8 +375,11 @@
 DefaultContentLanguage = "fr"
 
 [permalinks]
-  other = "/somewhere/else/:filename"
+other = "/somewhere/else/:filename"
 
+[blackfriday]
+angledQuotes = true
+
 [Taxonomies]
 tag = "tags"
 
@@ -379,6 +387,8 @@
 [Languages.en]
 weight = 10
 title = "English"
+[Languages.en.blackfriday]
+angledQuotes = false
 
 [Languages.fr]
 weight = 20
@@ -441,7 +451,7 @@
 publishdate: "2000-01-01"
 ---
 # doc1
-*some content*
+*some "content"*
 NOTE: slug should be used as URL
 `)},
 		{filepath.FromSlash("sect/doc1.fr.md"), []byte(`---
@@ -452,7 +462,7 @@
 publishdate: "2000-01-04"
 ---
 # doc1
-*quelque contenu*
+*quelque "contenu"*
 NOTE: should be in the 'en' Page's 'Translations' field.
 NOTE: date is after "doc3"
 `)},
--- a/hugolib/multilingual.go
+++ b/hugolib/multilingual.go
@@ -25,9 +25,14 @@
 	return &Language{Lang: lang, params: make(map[string]interface{})}
 }
 
-// TODO(bep) multilingo
 func newDefaultLanguage() *Language {
-	return NewLanguage("en")
+	defaultLang := viper.GetString("DefaultContentLanguage")
+
+	if defaultLang == "" {
+		defaultLang = "en"
+	}
+
+	return NewLanguage(defaultLang)
 }
 
 type Languages []*Language
@@ -74,16 +79,18 @@
 		languages[i] = s.Language
 	}
 
-	defaultLang := viper.GetString("DefaultContentLanguage")
+	return &Multilingual{Languages: languages, DefaultLang: newDefaultLanguage()}, nil
 
-	if defaultLang == "" {
-		defaultLang = "en"
-	}
+}
 
-	return &Multilingual{Languages: languages, DefaultLang: NewLanguage(defaultLang)}, nil
-
+func newMultiLingualDefaultLanguage() *Multilingual {
+	return newMultiLingualForLanguage(newDefaultLanguage())
 }
 
+func newMultiLingualForLanguage(language *Language) *Multilingual {
+	languages := Languages{language}
+	return &Multilingual{Languages: languages, DefaultLang: language}
+}
 func (ml *Multilingual) enabled() bool {
 	return len(ml.Languages) > 1
 }
@@ -92,6 +99,7 @@
 	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 {
@@ -116,6 +124,9 @@
 }
 
 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
@@ -159,7 +170,7 @@
 			}
 
 			// Put all into the Params map
-			// TODO(bep) reconsile with the type handling etc. from other params handlers.
+			// TODO(bep) ml reconsile with the type handling etc. from other params handlers.
 			language.SetParam(loki, v)
 		}
 
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -22,6 +22,8 @@
 	"sync"
 	"time"
 
+	jww "github.com/spf13/jwalterweatherman"
+
 	"github.com/spf13/hugo/helpers"
 
 	"github.com/spf13/cast"
@@ -45,8 +47,9 @@
 	paginatorInit sync.Once
 	scratch       *Scratch
 
-	language *Language
-	lang     string // TODO(bep) multilingo
+	language     *Language
+	languageInit sync.Once
+	lang         string // TODO(bep) multilingo
 
 	translations     Nodes
 	translationsInit sync.Once
@@ -191,6 +194,7 @@
 
 // TODO(bep) multilingo consolidate. See Page.
 func (n *Node) Language() *Language {
+	n.initLanguage()
 	return n.language
 }
 
@@ -204,6 +208,31 @@
 	return n.lang
 }
 
+func (n *Node) initLanguage() {
+	n.languageInit.Do(func() {
+		pageLang := n.lang
+		ml := n.Site.multilingual
+		if ml == nil {
+			panic("Multilanguage not set")
+		}
+		if pageLang == "" {
+			n.language = ml.DefaultLang
+			return
+		}
+
+		language := ml.Language(pageLang)
+
+		if language == nil {
+			// TODO(bep) ml
+			// This may or may not be serious. It can be a file named stefano.chiodino.md.
+			jww.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", pageLang)
+			language = ml.DefaultLang
+		}
+
+		n.language = language
+	})
+}
+
 func (n *Node) LanguagePrefix() string {
 	return n.Site.LanguagePrefix
 }
@@ -261,7 +290,7 @@
 	hadSlashSuffix := strings.HasSuffix(outfile, "/")
 
 	lang := n.Lang()
-	if lang == "" || !n.Site.Multilingual {
+	if lang == "" || !n.Site.IsMultiLingual() {
 		return outfile
 	}
 	outfile = "/" + path.Join(lang, outfile)
@@ -273,7 +302,7 @@
 
 func (n *Node) addMultilingualFilesystemPrefix(outfile string) string {
 	lang := n.Lang()
-	if lang == "" || !n.Site.Multilingual {
+	if lang == "" || !n.Site.IsMultiLingual() {
 		return outfile
 	}
 	return string(filepath.Separator) + filepath.Join(lang, outfile)
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -319,7 +319,8 @@
 	return nil
 }
 
-func (p *Page) renderBytes(content []byte) []byte {
+// TODO(bep) ml not used???
+func (p *Page) _renderBytes(content []byte) []byte {
 	var fn helpers.LinkResolverFunc
 	var fileFn helpers.FileResolverFunc
 	if p.getRenderingConfig().SourceRelativeLinksEval {
@@ -331,8 +332,10 @@
 		}
 	}
 	return helpers.RenderBytes(
-		&helpers.RenderingContext{Content: content, PageFmt: p.determineMarkupType(),
-			DocumentID: p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
+		&helpers.RenderingContext{
+			Content: content, PageFmt: p.determineMarkupType(),
+			ConfigProvider: p.Language(),
+			DocumentID:     p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
 }
 
 func (p *Page) renderContent(content []byte) []byte {
@@ -346,8 +349,10 @@
 			return p.Node.Site.SourceRelativeLinkFile(ref, p)
 		}
 	}
-	return helpers.RenderBytes(&helpers.RenderingContext{Content: content, RenderTOC: true, PageFmt: p.determineMarkupType(),
-		DocumentID: p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
+	return helpers.RenderBytes(&helpers.RenderingContext{
+		Content: content, RenderTOC: true, PageFmt: p.determineMarkupType(),
+		ConfigProvider: p.Language(),
+		DocumentID:     p.UniqueID(), Config: p.getRenderingConfig(), LinkResolver: fn, FileResolver: fileFn})
 }
 
 func (p *Page) getRenderingConfig() *helpers.Blackfriday {
@@ -354,8 +359,10 @@
 
 	p.renderingConfigInit.Do(func() {
 		pageParam := cast.ToStringMap(p.GetParam("blackfriday"))
-
-		p.renderingConfig = helpers.NewBlackfriday()
+		if p.Language() == nil {
+			panic(fmt.Sprintf("nil language for %s with source lang %s", p.BaseFileName(), p.lang))
+		}
+		p.renderingConfig = helpers.NewBlackfriday(p.Language())
 		if err := mapstructure.Decode(pageParam, p.renderingConfig); err != nil {
 			jww.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error())
 		}
--- a/hugolib/pageSort_test.go
+++ b/hugolib/pageSort_test.go
@@ -141,9 +141,7 @@
 					Section: "z",
 					URL:     fmt.Sprintf("http://base/x/y/p%d.html", i),
 				},
-				Site: &SiteInfo{
-					BaseURL: "http://base/",
-				},
+				Site: newSiteInfoDefaultLanguage("http://base/"),
 			},
 			Source: Source{File: *source.NewFile(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))},
 		}
--- a/hugolib/page_permalink_test.go
+++ b/hugolib/page_permalink_test.go
@@ -69,9 +69,7 @@
 					Section: "z",
 					URL:     test.url,
 				},
-				Site: &SiteInfo{
-					BaseURL: test.base,
-				},
+				Site: newSiteInfoDefaultLanguage(string(test.base)),
 			},
 			Source: Source{File: *source.NewFile(filepath.FromSlash(test.file))},
 		}
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -1117,7 +1117,7 @@
 
 	for _, test := range tests {
 		p, _ := NewPageFrom(strings.NewReader(test.content), filepath.FromSlash(test.path))
-		p.Node.Site = &SiteInfo{}
+		p.Node.Site = newSiteInfoDefaultLanguage("")
 
 		if test.hasPermalink {
 			p.Node.Site.Permalinks = siteParmalinksSetting
--- a/hugolib/pagination_test.go
+++ b/hugolib/pagination_test.go
@@ -460,9 +460,7 @@
 					Section: "z",
 					URL:     fmt.Sprintf("http://base/x/y/p%d.html", i),
 				},
-				Site: &SiteInfo{
-					BaseURL: "http://base/",
-				},
+				Site: newSiteInfoDefaultLanguage("http://base/"),
 			},
 			Source: Source{File: *source.NewFile(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))},
 		}
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -242,7 +242,8 @@
 		if sc.doMarkup {
 			newInner := helpers.RenderBytes(&helpers.RenderingContext{
 				Content: []byte(inner), PageFmt: p.determineMarkupType(),
-				DocumentID: p.UniqueID(), Config: p.getRenderingConfig()})
+				ConfigProvider: p.Language(),
+				DocumentID:     p.UniqueID(), Config: p.getRenderingConfig()})
 
 			// If the type is “unknown” or “markdown”, we assume the markdown
 			// generation has been performed. Given the input: `a line`, markdown
--- a/hugolib/shortcode_test.go
+++ b/hugolib/shortcode_test.go
@@ -28,23 +28,41 @@
 	"github.com/spf13/hugo/target"
 	"github.com/spf13/hugo/tpl"
 	"github.com/spf13/viper"
+	"github.com/stretchr/testify/require"
 )
 
+// TODO(bep) remove
 func pageFromString(in, filename string) (*Page, error) {
 	return NewPageFrom(strings.NewReader(in), filename)
 }
 
-func CheckShortCodeMatch(t *testing.T, input, expected string, template tpl.Template) {
-	CheckShortCodeMatchAndError(t, input, expected, template, false)
+func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error) {
+	CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
 }
 
-func CheckShortCodeMatchAndError(t *testing.T, input, expected string, template tpl.Template, expectError bool) {
+func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.Template) error, expectError bool) {
+	testCommonResetState()
 
-	p, _ := pageFromString(simplePage, "simple.md")
-	output, err := HandleShortcodes(input, p, template)
+	// Need some front matter, see https://github.com/spf13/hugo/issues/2337
+	contentFile := `---
+title: "Title"
+---
+` + input
 
+	writeSource(t, "content/simple.md", contentFile)
+
+	h, err := newHugoSitesDefaultLanguage()
+
+	if err != nil {
+		t.Fatalf("Failed to create sites: %s", err)
+	}
+
+	cfg := BuildCfg{SkipRender: true, withTemplate: withTemplate}
+
+	err = h.Build(cfg)
+
 	if err != nil && !expectError {
-		t.Fatalf("Shortcode rendered error %s. Expected: %q, Got: %q", err, expected, output)
+		t.Fatalf("Shortcode rendered error %s.", err)
 	}
 
 	if err == nil && expectError {
@@ -51,6 +69,18 @@
 		t.Fatalf("No error from shortcode")
 	}
 
+	require.Len(t, h.Sites[0].Pages, 1)
+
+	output := strings.TrimSpace(string(h.Sites[0].Pages[0].Content))
+	if strings.HasPrefix(output, "<p>") {
+		output = output[3:]
+	}
+	if strings.HasSuffix(output, "</p>") {
+		output = output[:len(output)-4]
+	}
+
+	expected = strings.TrimSpace(expected)
+
 	if output != expected {
 		t.Fatalf("Shortcode render didn't match. got \n%q but expected \n%q", output, expected)
 	}
@@ -86,115 +116,123 @@
 }
 
 func TestNonSC(t *testing.T) {
-	tem := tpl.New()
+
 	// notice the syntax diff from 0.12, now comment delims must be added
-	CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", tem)
+	CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil)
 }
 
 // Issue #929
 func TestHyphenatedSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
+		return nil
+	}
 
-	CheckShortCodeMatch(t, "{{< hyphenated-video 47238zzb >}}", "Playing Video 47238zzb", tem)
+	CheckShortCodeMatch(t, "{{< hyphenated-video 47238zzb >}}", "Playing Video 47238zzb", wt)
 }
 
 // Issue #1753
 func TestNoTrailingNewline(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("a.html", `{{ .Get 0 }}`)
+		return nil
+	}
 
-	CheckShortCodeMatch(t, "ab{{< a c >}}d", "abcd", tem)
+	CheckShortCodeMatch(t, "ab{{< a c >}}d", "abcd", wt)
 }
 
 func TestPositionalParamSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 0 }}`)
+		return nil
+	}
 
-	CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", tem)
-	CheckShortCodeMatch(t, "{{< video 47238zzb 132 >}}", "Playing Video 47238zzb", tem)
-	CheckShortCodeMatch(t, "{{<video 47238zzb>}}", "Playing Video 47238zzb", tem)
-	CheckShortCodeMatch(t, "{{<video 47238zzb    >}}", "Playing Video 47238zzb", tem)
-	CheckShortCodeMatch(t, "{{<   video   47238zzb    >}}", "Playing Video 47238zzb", tem)
+	CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt)
+	CheckShortCodeMatch(t, "{{< video 47238zzb 132 >}}", "Playing Video 47238zzb", wt)
+	CheckShortCodeMatch(t, "{{<video 47238zzb>}}", "Playing Video 47238zzb", wt)
+	CheckShortCodeMatch(t, "{{<video 47238zzb    >}}", "Playing Video 47238zzb", wt)
+	CheckShortCodeMatch(t, "{{<   video   47238zzb    >}}", "Playing Video 47238zzb", wt)
 }
 
 func TestPositionalParamIndexOutOfBounds(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
-	CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video error: index out of range for positional param at position 1", tem)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("video.html", `Playing Video {{ .Get 1 }}`)
+		return nil
+	}
+	CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video error: index out of range for positional param at position 1", wt)
 }
 
 // some repro issues for panics in Go Fuzz testing
-func TestShortcodeGoFuzzRepros(t *testing.T) {
-	tt := tpl.New()
-	tt.AddInternalShortcode("inner.html", `Shortcode... {{ with .Get 0 }}{{ . }}{{ end }}-- {{ with .Get 1 }}{{ . }}{{ end }}- {{ with .Inner }}{{ . }}{{ end }}`)
-	// Issue #1337
-	CheckShortCodeMatchAndError(t, "{{%inner\"\"\"\"=\"\"", "", tt, true)
-}
 
 func TestNamedParamSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
-
-	CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, tem)
-	CheckShortCodeMatch(t, `{{< img class="aspen" >}}`, `<img class="aspen">`, tem)
-	CheckShortCodeMatch(t, `{{< img src= "one" >}}`, `<img src="one">`, tem)
-	CheckShortCodeMatch(t, `{{< img src ="one" >}}`, `<img src="one">`, tem)
-	CheckShortCodeMatch(t, `{{< img src = "one" >}}`, `<img src="one">`, tem)
-	CheckShortCodeMatch(t, `{{< img src = "one" class = "aspen grove" >}}`, `<img src="one" class="aspen grove">`, tem)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
+		return nil
+	}
+	CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, wt)
+	CheckShortCodeMatch(t, `{{< img class="aspen" >}}`, `<img class="aspen">`, wt)
+	CheckShortCodeMatch(t, `{{< img src= "one" >}}`, `<img src="one">`, wt)
+	CheckShortCodeMatch(t, `{{< img src ="one" >}}`, `<img src="one">`, wt)
+	CheckShortCodeMatch(t, `{{< img src = "one" >}}`, `<img src="one">`, wt)
+	CheckShortCodeMatch(t, `{{< img src = "one" class = "aspen grove" >}}`, `<img src="one" class="aspen grove">`, wt)
 }
 
 // Issue #2294
 func TestNestedNamedMissingParam(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
-	tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
-	tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
-
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("acc.html", `<div class="acc">{{ .Inner }}</div>`)
+		tem.AddInternalShortcode("div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
+		tem.AddInternalShortcode("div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
+		return nil
+	}
 	CheckShortCodeMatch(t,
 		`{{% acc %}}{{% div %}}d1{{% /div %}}{{% div2 %}}d2{{% /div2 %}}{{% /acc %}}`,
-		"<div class=\"acc\"><div >d1</div><div >d2</div>\n</div>", tem)
+		"<div class=\"acc\"><div >d1</div><div >d2</div>\n</div>", wt)
 }
 
 func TestIsNamedParamsSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
-	tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
-	tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
-
-	CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, tem)
-	CheckShortCodeMatch(t, `{{< ifnamedparams position >}}`, `<div id="position">`, tem)
-	CheckShortCodeMatch(t, `{{< byname id="name" >}}`, `<div id="name">`, tem)
-	CheckShortCodeMatch(t, `{{< byname position >}}`, `<div id="error: cannot access positional params by string name">`, tem)
-	CheckShortCodeMatch(t, `{{< byposition position >}}`, `<div id="position">`, tem)
-	CheckShortCodeMatch(t, `{{< byposition id="name" >}}`, `<div id="error: cannot access named params by position">`, tem)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("byposition.html", `<div id="{{ .Get 0 }}">`)
+		tem.AddInternalShortcode("byname.html", `<div id="{{ .Get "id" }}">`)
+		tem.AddInternalShortcode("ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
+		return nil
+	}
+	CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, wt)
+	CheckShortCodeMatch(t, `{{< ifnamedparams position >}}`, `<div id="position">`, wt)
+	CheckShortCodeMatch(t, `{{< byname id="name" >}}`, `<div id="name">`, wt)
+	CheckShortCodeMatch(t, `{{< byname position >}}`, `<div id="error: cannot access positional params by string name">`, wt)
+	CheckShortCodeMatch(t, `{{< byposition position >}}`, `<div id="position">`, wt)
+	CheckShortCodeMatch(t, `{{< byposition id="name" >}}`, `<div id="error: cannot access named params by position">`, wt)
 }
 
 func TestInnerSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
-
-	CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, tem)
-	CheckShortCodeMatch(t, `{{< inside class="aspen" >}}More Here{{< /inside >}}`, "<div class=\"aspen\">More Here</div>", tem)
-	CheckShortCodeMatch(t, `{{< inside >}}More Here{{< /inside >}}`, "<div>More Here</div>", tem)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
+		return nil
+	}
+	CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, wt)
+	CheckShortCodeMatch(t, `{{< inside class="aspen" >}}More Here{{< /inside >}}`, "<div class=\"aspen\">More Here</div>", wt)
+	CheckShortCodeMatch(t, `{{< inside >}}More Here{{< /inside >}}`, "<div>More Here</div>", wt)
 }
 
 func TestInnerSCWithMarkdown(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
-
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
+		return nil
+	}
 	CheckShortCodeMatch(t, `{{% inside %}}
 # More Here
 
 [link](http://spf13.com) and text
 
-{{% /inside %}}`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>", tem)
+{{% /inside %}}`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>", wt)
 }
 
 func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
-
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
+		return nil
+	}
 	CheckShortCodeMatch(t, `{{% inside %}}
 # More Here
 
@@ -210,54 +248,55 @@
 This is **plain** text.
 
 {{< /inside >}}
-`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>\n\nAnd then:\n\n<div>\n# More Here\n\nThis is **plain** text.\n\n</div>\n", tem)
+`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>\n\n<p>And then:</p>\n\n<p><div>\n# More Here\n\nThis is **plain** text.\n\n</div>", wt)
 }
 
 func TestEmbeddedSC(t *testing.T) {
-	tem := tpl.New()
-	CheckShortCodeMatch(t, "{{% test %}}", "This is a simple Test", tem)
-	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" />\n    \n    \n</figure>\n", tem)
-	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"This is a caption\" />\n    \n    \n    <figcaption>\n        <p>\n        This is a caption\n        \n            \n        \n        </p> \n    </figcaption>\n    \n</figure>\n", tem)
+	CheckShortCodeMatch(t, "{{% test %}}", "This is a simple Test", nil)
+	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" />\n    \n    \n</figure>\n", nil)
+	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"This is a caption\" />\n    \n    \n    <figcaption>\n        <p>\n        This is a caption\n        \n            \n        \n        </p> \n    </figcaption>\n    \n</figure>\n", nil)
 }
 
 func TestNestedSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
-	tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
+		tem.AddInternalShortcode("scn2.html", `<div>SC2</div>`)
+		return nil
+	}
+	CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div>\n</div>", wt)
 
-	CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div>\n</div>", tem)
-
-	CheckShortCodeMatch(t, `{{< scn1 >}}{{% scn2 %}}{{< /scn1 >}}`, "<div>Outer, inner is <div>SC2</div></div>", tem)
+	CheckShortCodeMatch(t, `{{< scn1 >}}{{% scn2 %}}{{< /scn1 >}}`, "<div>Outer, inner is <div>SC2</div></div>", wt)
 }
 
 func TestNestedComplexSC(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
-	tem.AddInternalShortcode("column.html", `-col-{{.Inner    }}-colStop-`)
-	tem.AddInternalShortcode("aside.html", `-aside-{{    .Inner  }}-asideStop-`)
-
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("row.html", `-row-{{ .Inner}}-rowStop-`)
+		tem.AddInternalShortcode("column.html", `-col-{{.Inner    }}-colStop-`)
+		tem.AddInternalShortcode("aside.html", `-aside-{{    .Inner  }}-asideStop-`)
+		return nil
+	}
 	CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`,
-		"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", tem)
+		"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt)
 
 	// turn around the markup flag
 	CheckShortCodeMatch(t, `{{% row %}}1-s{{< column >}}2-**s**{{% aside %}}3-**s**{{% /aside %}}4-s{{< /column >}}5-s{{% /row %}}6-s`,
-		"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", tem)
+		"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt)
 }
 
 func TestParentShortcode(t *testing.T) {
-	tem := tpl.New()
-	tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
-	tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
-	tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
-
+	wt := func(tem tpl.Template) error {
+		tem.AddInternalShortcode("r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
+		tem.AddInternalShortcode("r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
+		tem.AddInternalShortcode("r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
+		return nil
+	}
 	CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`,
-		"1: p1 1: 2: p1p2 2: 3: p1p2p3 ", tem)
+		"1: p1 1: 2: p1p2 2: 3: p1p2p3 ", wt)
 
 }
 
 func TestFigureImgWidth(t *testing.T) {
-	tem := tpl.New()
-	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"apple\" width=\"100px\" />\n    \n    \n</figure>\n", tem)
+	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"apple\" width=\"100px\" />\n    \n    \n</figure>\n", nil)
 }
 
 func TestHighlight(t *testing.T) {
@@ -539,7 +578,7 @@
 
 	}
 
-	sites, err := NewHugoSites(s)
+	sites, err := newHugoSites(s)
 
 	if err != nil {
 		t.Fatalf("Failed to build site: %s", err)
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -107,7 +107,7 @@
 
 // newSite creates a new site in the given language.
 func newSite(lang *Language) *Site {
-	return &Site{Language: lang}
+	return &Site{Language: lang, Info: SiteInfo{multilingual: newMultiLingualForLanguage(lang)}}
 }
 
 // newSite creates a new site in the default language.
@@ -172,12 +172,23 @@
 	paginationPageCount   uint64
 	Data                  *map[string]interface{}
 
-	Multilingual   bool
+	multilingual   *Multilingual
 	Language       *Language
 	LanguagePrefix string
 	Languages      Languages
 }
 
+// Used in tests.
+func newSiteInfoDefaultLanguage(baseURL string, pages ...*Page) *SiteInfo {
+	ps := Pages(pages)
+
+	return &SiteInfo{
+		BaseURL:      template.URL(baseURL),
+		rawAllPages:  &ps,
+		multilingual: newMultiLingualDefaultLanguage(),
+	}
+}
+
 // SiteSocial is a place to put social details on a site level. These are the
 // standard keys that themes will expect to have available, but can be
 // expanded to any others on a per site basis
@@ -218,6 +229,10 @@
 	return nil
 }
 
+func (s *SiteInfo) IsMultiLingual() bool {
+	return len(s.Languages) > 1
+}
+
 func (s *SiteInfo) refLink(ref string, page *Page, relative bool) (string, error) {
 	var refURL *url.URL
 	var err error
@@ -837,7 +852,7 @@
 // HomeAbsURL is a convenience method giving the absolute URL to the home page.
 func (s *SiteInfo) HomeAbsURL() string {
 	base := "/"
-	if s.Multilingual {
+	if s.IsMultiLingual() {
 		base = s.Language.Lang
 	}
 	return helpers.AbsURL(base)
@@ -880,8 +895,8 @@
 		LanguageCode:    lang.GetString("languagecode"),
 		Copyright:       lang.GetString("copyright"),
 		DisqusShortname: lang.GetString("DisqusShortname"),
-		// TODO(bep) multilang, consolidate the below (make into methods etc.)
-		Multilingual:          s.multilingualEnabled(),
+		// TODO(bep) ml consolidate the below (make into methods etc.)
+		multilingual:          s.Multilingual,
 		Language:              lang,
 		LanguagePrefix:        languagePrefix,
 		Languages:             languages,
--- a/hugolib/translations.go
+++ b/hugolib/translations.go
@@ -13,10 +13,6 @@
 
 package hugolib
 
-import (
-	jww "github.com/spf13/jwalterweatherman"
-)
-
 // Translations represent the other translations for a given page. The
 // string here is the language code, as affected by the `post.LANG.md`
 // filename.
@@ -37,17 +33,6 @@
 		if pageLang == "" {
 			continue
 		}
-
-		language := ml.Language(pageLang)
-
-		if language == nil {
-			// TODO(bep) ml
-			// This may or may not be serious. It can be a file named stefano.chiodino.md.
-			jww.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", pageLang)
-			language = ml.DefaultLang
-		}
-
-		page.language = language
 
 		pageTranslation[pageLang] = page
 		out[base] = pageTranslation
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -38,6 +38,8 @@
 	"time"
 	"unicode/utf8"
 
+	"github.com/spf13/viper"
+
 	"github.com/spf13/afero"
 	"github.com/spf13/hugo/hugofs"
 
@@ -1212,7 +1214,10 @@
 	if err != nil {
 		return "", err
 	}
-	m := helpers.RenderBytes(&helpers.RenderingContext{Content: []byte(text), PageFmt: "markdown"})
+	// TODO(bep) ml language
+	m := helpers.RenderBytes(&helpers.RenderingContext{
+		ConfigProvider: viper.GetViper(),
+		Content:        []byte(text), PageFmt: "markdown"})
 	m = bytes.TrimPrefix(m, markdownTrimPrefix)
 	m = bytes.TrimSuffix(m, markdownTrimSuffix)
 	return template.HTML(m), nil