diff --git a/docs/content/content/multilingual.md b/docs/content/content/multilingual.md index f93738f9..35dd1382 100644 --- a/docs/content/content/multilingual.md +++ b/docs/content/content/multilingual.md @@ -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. diff --git a/docs/content/templates/functions.md b/docs/content/templates/functions.md index aa55ced4..5fb54006 100644 --- a/docs/content/templates/functions.md +++ b/docs/content/templates/functions.md @@ -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 index 00000000..4ef5edbc --- /dev/null +++ b/helpers/language.go @@ -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) +} diff --git a/helpers/url.go b/helpers/url.go index 085f9e9f..f9a41dde 100644 --- a/helpers/url.go +++ b/helpers/url.go @@ -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 += "/" } diff --git a/helpers/url_test.go b/helpers/url_test.go index 9cca1cbb..f6dd9f9d 100644 --- 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,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) } } } diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go index fce29df4..0e59510d 100644 --- a/hugolib/handler_test.go +++ b/hugolib/handler_test.go @@ -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, diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 8fd2a63b..561c43a3 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -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()}) } diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go index 9ef4d09a..4eafba47 100644 --- a/hugolib/menu_test.go +++ b/hugolib/menu_test.go @@ -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(), } } diff --git a/hugolib/multilingual.go b/hugolib/multilingual.go index 9c5342f9..61058765 100644 --- 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 @@ 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) diff --git a/hugolib/node.go b/hugolib/node.go index 773573c3..e9a5ab1a 100644 --- a/hugolib/node.go +++ b/hugolib/node.go @@ -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 } diff --git a/hugolib/robotstxt_test.go b/hugolib/robotstxt_test.go index 62be9152..5b6c9082 100644 --- a/hugolib/robotstxt_test.go +++ b/hugolib/robotstxt_test.go @@ -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 { diff --git a/hugolib/rss_test.go b/hugolib/rss_test.go index f9f26cb7..007d6a17 100644 --- a/hugolib/rss_test.go +++ b/hugolib/rss_test.go @@ -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 { diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 1b5c6c84..18e6314b 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -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 { diff --git a/hugolib/site.go b/hugolib/site.go index fe3285e1..cfbe75f7 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -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 diff --git a/hugolib/site_show_plan_test.go b/hugolib/site_show_plan_test.go index d330f434..981bed7b 100644 --- a/hugolib/site_show_plan_test.go +++ b/hugolib/site_show_plan_test.go @@ -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 { diff --git a/hugolib/site_test.go b/hugolib/site_test.go index f98bc6a7..b93e08ac 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -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 { diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index 6b96b09d..522732cc 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -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 { diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go index 58408ce4..6c57084d 100644 --- a/hugolib/sitemap_test.go +++ b/hugolib/sitemap_test.go @@ -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 { diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go index 941115cd..41e616b1 100644 --- a/tpl/template_funcs.go +++ b/tpl/template_funcs.go @@ -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, diff --git a/tpl/template_funcs_test.go b/tpl/template_funcs_test.go index 88152381..3df38f38 100644 --- a/tpl/template_funcs_test.go +++ b/tpl/template_funcs_test.go @@ -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{}