shithub: hugo

Download patch

ref: 3a02807970e792299a80738befe32eea8d10984d
parent: 52bf8f90958e75007ca0a2761edc6ca331ca3280
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Tue Jul 26 15:04:10 EDT 2016

Add Translations and AllTranslations to Node

This commit also consolidates URLs on Node vs Page, so now .Permalink should be interoperable.

Note that this implementations should be fairly short-livded, waiting for #2297, but the API should be stable.

--- a/hugolib/menu_test.go
+++ b/hugolib/menu_test.go
@@ -554,7 +554,7 @@
 	s := setupMenuTests(t, menuPageSources)
 
 	home := s.newHomeNode()
-	homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL}
+	homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL()}
 
 	for i, this := range []struct {
 		menu           string
--- a/hugolib/node.go
+++ b/hugolib/node.go
@@ -15,6 +15,9 @@
 
 import (
 	"html/template"
+	"path"
+	"path/filepath"
+	"sort"
 	"sync"
 	"time"
 
@@ -38,8 +41,29 @@
 	paginator     *Pager
 	paginatorInit sync.Once
 	scratch       *Scratch
+
+	language *Language
+	lang     string // TODO(bep) multilingo
+
+	translations     Nodes
+	translationsInit sync.Once
 }
 
+// The Nodes type is temporary until we get https://github.com/spf13/hugo/issues/2297 fixed.
+type Nodes []*Node
+
+func (n Nodes) Len() int {
+	return len(n)
+}
+
+func (n Nodes) Less(i, j int) bool {
+	return n[i].language.Weight < n[j].language.Weight
+}
+
+func (n Nodes) Swap(i, j int) {
+	n[i], n[j] = n[j], n[i]
+}
+
 func (n *Node) Now() time.Time {
 	return time.Now()
 }
@@ -46,7 +70,7 @@
 
 func (n *Node) HasMenuCurrent(menuID string, inme *MenuEntry) bool {
 	if inme.HasChildren() {
-		me := MenuEntry{Name: n.Title, URL: n.URL}
+		me := MenuEntry{Name: n.Title, URL: n.URL()}
 
 		for _, child := range inme.Children {
 			if me.IsSameResource(child) {
@@ -63,7 +87,7 @@
 
 func (n *Node) IsMenuCurrent(menuID string, inme *MenuEntry) bool {
 
-	me := MenuEntry{Name: n.Title, URL: n.Site.createNodeMenuEntryURL(n.URL)}
+	me := MenuEntry{Name: n.Title, URL: n.Site.createNodeMenuEntryURL(n.URL())}
 
 	if !me.IsSameResource(inme) {
 		return false
@@ -138,6 +162,7 @@
 	return n.Site.RelRef(ref, nil)
 }
 
+// TODO(bep) multilingo some of these are now hidden. Consider unexport.
 type URLPath struct {
 	URL       string
 	Permalink string
@@ -145,6 +170,14 @@
 	Section   string
 }
 
+func (n *Node) URL() string {
+	return n.addMultilingualWebPrefix(n.URLPath.URL)
+}
+
+func (n *Node) Permalink() string {
+	return permalink(n.URL())
+}
+
 // Scratch returns the writable context associated with this Node.
 func (n *Node) Scratch() *Scratch {
 	if n.scratch == nil {
@@ -151,4 +184,76 @@
 		n.scratch = newScratch()
 	}
 	return n.scratch
+}
+
+// TODO(bep) multilingo consolidate. See Page.
+func (n *Node) Language() *Language {
+	return n.language
+}
+
+func (n *Node) Lang() string {
+	if n.Language() != nil {
+		return n.Language().Lang
+	}
+	return n.lang
+}
+
+// AllTranslations returns all translations, including the current Node.
+// Note that this and the one below is kind of a temporary hack before #2297 is solved.
+func (n *Node) AllTranslations() Nodes {
+	n.initTranslations()
+	return n.translations
+}
+
+// Translations returns the translations excluding the current Node.
+func (n *Node) Translations() Nodes {
+	n.initTranslations()
+	translations := make(Nodes, 0)
+
+	for _, t := range n.translations {
+
+		if t != n {
+			translations = append(translations, t)
+		}
+	}
+
+	return translations
+}
+
+func (n *Node) initTranslations() {
+	n.translationsInit.Do(func() {
+		if n.translations != nil {
+			return
+		}
+		n.translations = make(Nodes, 0)
+		for _, l := range n.Site.Languages {
+			if l == n.language {
+				n.translations = append(n.translations, n)
+				continue
+			}
+
+			translation := *n
+			translation.language = l
+			translation.translations = n.translations
+			n.translations = append(n.translations, &translation)
+		}
+
+		sort.Sort(n.translations)
+	})
+}
+
+func (n *Node) addMultilingualWebPrefix(outfile string) string {
+	lang := n.Lang()
+	if lang == "" || !n.Site.Multilingual {
+		return outfile
+	}
+	return "/" + path.Join(lang, outfile)
+}
+
+func (n *Node) addMultilingualFilesystemPrefix(outfile string) string {
+	lang := n.Lang()
+	if lang == "" || !n.Site.Multilingual {
+		return outfile
+	}
+	return string(filepath.Separator) + filepath.Join(lang, outfile)
 }
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -64,8 +64,6 @@
 	translations        Pages
 	extension           string
 	contentType         string
-	lang                string
-	language            *Language
 	renderable          bool
 	Layout              string
 	layoutsCalculated   []string
@@ -431,7 +429,7 @@
 	baseURL := string(p.Site.BaseURL)
 	dir := strings.TrimSpace(helpers.MakePath(filepath.ToSlash(strings.ToLower(p.Source.Dir()))))
 	pSlug := strings.TrimSpace(helpers.URLize(p.Slug))
-	pURL := strings.TrimSpace(helpers.URLize(p.URL))
+	pURL := strings.TrimSpace(helpers.URLize(p.URLPath.URL))
 	var permalink string
 	var err error
 
@@ -467,14 +465,6 @@
 	return viper.GetString("DefaultExtension")
 }
 
-// TODO(bep) multilingo consolidate
-func (p *Page) Language() *Language {
-	return p.language
-}
-func (p *Page) Lang() string {
-	return p.lang
-}
-
 // AllTranslations returns all translations, including the current Page.
 func (p *Page) AllTranslations() Pages {
 	return p.translations
@@ -591,7 +581,7 @@
 			if url := cast.ToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
 				return fmt.Errorf("Only relative URLs are supported, %v provided", url)
 			}
-			p.URL = cast.ToString(v)
+			p.URLPath.URL = cast.ToString(v)
 		case "type":
 			p.contentType = cast.ToString(v)
 		case "extension", "ext":
@@ -1008,8 +998,8 @@
 
 func (p *Page) TargetPath() (outfile string) {
 	// Always use URL if it's specified
-	if len(strings.TrimSpace(p.URL)) > 2 {
-		outfile = strings.TrimSpace(p.URL)
+	if len(strings.TrimSpace(p.URLPath.URL)) > 2 {
+		outfile = strings.TrimSpace(p.URLPath.URL)
 
 		if strings.HasSuffix(outfile, "/") {
 			outfile = outfile + "index.html"
@@ -1041,18 +1031,4 @@
 	}
 
 	return p.addMultilingualFilesystemPrefix(filepath.Join(strings.ToLower(helpers.MakePath(p.Source.Dir())), strings.TrimSpace(outfile)))
-}
-
-func (p *Page) addMultilingualWebPrefix(outfile string) string {
-	if p.Lang() == "" {
-		return outfile
-	}
-	return "/" + path.Join(p.Lang(), outfile)
-}
-
-func (p *Page) addMultilingualFilesystemPrefix(outfile string) string {
-	if p.Lang() == "" {
-		return outfile
-	}
-	return string(filepath.Separator) + filepath.Join(p.Lang(), outfile)
 }
--- a/hugolib/pagination.go
+++ b/hugolib/pagination.go
@@ -16,14 +16,15 @@
 import (
 	"errors"
 	"fmt"
-	"github.com/spf13/cast"
-	"github.com/spf13/hugo/helpers"
-	"github.com/spf13/viper"
 	"html/template"
 	"math"
 	"path"
 	"reflect"
 	"strings"
+
+	"github.com/spf13/cast"
+	"github.com/spf13/hugo/helpers"
+	"github.com/spf13/viper"
 )
 
 // Pager represents one of the elements in a paginator.
@@ -274,7 +275,7 @@
 			return
 		}
 
-		pagers, err := paginatePages(n.Data["Pages"], pagerSize, n.URL)
+		pagers, err := paginatePages(n.Data["Pages"], pagerSize, n.URL())
 
 		if err != nil {
 			initError = err
@@ -324,7 +325,7 @@
 		if n.paginator != nil {
 			return
 		}
-		pagers, err := paginatePages(seq, pagerSize, n.URL)
+		pagers, err := paginatePages(seq, pagerSize, n.URL())
 
 		if err != nil {
 			initError = err
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -117,19 +117,20 @@
 }
 
 type SiteInfo struct {
-	BaseURL               template.URL
-	Taxonomies            TaxonomyList
-	Authors               AuthorList
-	Social                SiteSocial
-	Sections              Taxonomy
-	Pages                 *Pages // Includes only pages in this language
-	AllPages              *Pages // Includes other translated pages, excluding those in this language.
-	Files                 *[]*source.File
-	Menus                 *Menus
-	Hugo                  *HugoInfo
-	Title                 string
-	RSSLink               string
-	Author                map[string]interface{}
+	BaseURL    template.URL
+	Taxonomies TaxonomyList
+	Authors    AuthorList
+	Social     SiteSocial
+	Sections   Taxonomy
+	Pages      *Pages // Includes only pages in this language
+	AllPages   *Pages // Includes other translated pages, excluding those in this language.
+	Files      *[]*source.File
+	Menus      *Menus
+	Hugo       *HugoInfo
+	Title      string
+	RSSLink    string
+	Author     map[string]interface{}
+	// TODO(bep) multilingo
 	LanguageCode          string
 	DisqusShortname       string
 	GoogleAnalytics       string
@@ -885,7 +886,7 @@
 		LanguagePrefix:        languagePrefix,
 		Languages:             languages,
 		GoogleAnalytics:       viper.GetString("GoogleAnalytics"),
-		RSSLink:               s.permalinkStr(viper.GetString("RSSUri")),
+		RSSLink:               permalinkStr(viper.GetString("RSSUri")),
 		BuildDrafts:           viper.GetBool("BuildDrafts"),
 		canonifyURLs:          viper.GetBool("CanonifyURLs"),
 		preserveTaxonomyNames: viper.GetBool("PreserveTaxonomyNames"),
@@ -1672,7 +1673,7 @@
 			paginatePath := viper.GetString("paginatePath")
 
 			// write alias for page 1
-			s.writeDestAlias(helpers.PaginateAliasPath(base, 1), s.permalink(base))
+			s.writeDestAlias(helpers.PaginateAliasPath(base, 1), permalink(base))
 
 			pagers := n.paginator.Pagers()
 
@@ -1701,8 +1702,8 @@
 		if !viper.GetBool("DisableRSS") {
 			// XML Feed
 			rssuri := viper.GetString("RSSUri")
-			n.URL = s.permalinkStr(base + "/" + rssuri)
-			n.Permalink = s.permalink(base)
+			n.URLPath.URL = permalinkStr(base + "/" + rssuri)
+			n.URLPath.Permalink = permalink(base)
 			rssLayouts := []string{"taxonomy/" + t.singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
 
 			if err := s.renderAndWriteXML("taxonomy "+t.singular+" rss", base+"/"+rssuri, n, s.appendThemeTemplates(rssLayouts)...); err != nil {
@@ -1782,7 +1783,7 @@
 			paginatePath := viper.GetString("paginatePath")
 
 			// write alias for page 1
-			s.writeDestAlias(helpers.PaginateAliasPath(base, 1), s.permalink(base))
+			s.writeDestAlias(helpers.PaginateAliasPath(base, 1), permalink(base))
 
 			pagers := n.paginator.Pagers()
 
@@ -1810,8 +1811,8 @@
 		if !viper.GetBool("DisableRSS") && section != "" {
 			// XML Feed
 			rssuri := viper.GetString("RSSUri")
-			n.URL = s.permalinkStr(base + "/" + rssuri)
-			n.Permalink = s.permalink(base)
+			n.URLPath.URL = permalinkStr(base + "/" + rssuri)
+			n.URLPath.Permalink = permalink(base)
 			rssLayouts := []string{"section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}
 			if err := s.renderAndWriteXML("section "+section+" rss", base+"/"+rssuri, n, s.appendThemeTemplates(rssLayouts)...); err != nil {
 				return err
@@ -1834,7 +1835,7 @@
 		paginatePath := viper.GetString("paginatePath")
 
 		// write alias for page 1
-		s.writeDestAlias(s.addMultilingualPrefix(helpers.PaginateAliasPath("", 1)), s.permalink("/"))
+		s.writeDestAlias(s.addMultilingualPrefix(helpers.PaginateAliasPath("", 1)), permalink("/"))
 
 		pagers := n.paginator.Pagers()
 
@@ -1862,7 +1863,7 @@
 
 	if !viper.GetBool("DisableRSS") {
 		// XML Feed
-		n.URL = s.permalinkStr(viper.GetString("RSSUri"))
+		n.URLPath.URL = permalinkStr(viper.GetString("RSSUri"))
 		n.Title = ""
 		high := 50
 		if len(s.Pages) < high {
@@ -1886,10 +1887,10 @@
 	}
 
 	// TODO(bep) reusing the Home Node smells trouble
-	n.URL = helpers.URLize("404.html")
+	n.URLPath.URL = helpers.URLize("404.html")
 	n.IsHome = false
 	n.Title = "404 Page not found"
-	n.Permalink = s.permalink("404.html")
+	n.URLPath.Permalink = permalink("404.html")
 	n.scratch = newScratch()
 
 	nfLayouts := []string{"404.html"}
@@ -1929,7 +1930,7 @@
 	page.Date = s.Info.LastChange
 	page.Lastmod = s.Info.LastChange
 	page.Site = &s.Info
-	page.URL = "/"
+	page.URLPath.URL = "/"
 	page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
 	page.Sitemap.Priority = sitemapDefault.Priority
 
@@ -2002,24 +2003,25 @@
 }
 
 func (s *Site) setURLs(n *Node, in string) {
-	in = s.addMultilingualPrefix(in)
-	n.URL = helpers.URLizeAndPrep(in)
-	n.Permalink = s.permalink(n.URL)
-	n.RSSLink = template.HTML(s.permalink(in + ".xml"))
+	n.URLPath.URL = helpers.URLizeAndPrep(in)
+	n.URLPath.Permalink = permalink(n.URLPath.URL)
+	// TODO(bep) multilingo
+	n.RSSLink = template.HTML(permalink(in + ".xml"))
 }
 
-func (s *Site) permalink(plink string) string {
-	return s.permalinkStr(plink)
+func permalink(plink string) string {
+	return permalinkStr(plink)
 }
 
-func (s *Site) permalinkStr(plink string) string {
+func permalinkStr(plink string) string {
 	return helpers.MakePermalink(viper.GetString("BaseURL"), helpers.URLizeAndPrep(plink)).String()
 }
 
 func (s *Site) newNode() *Node {
 	return &Node{
-		Data: make(map[string]interface{}),
-		Site: &s.Info,
+		Data:     make(map[string]interface{}),
+		Site:     &s.Info,
+		language: s.Lang,
 	}
 }
 
@@ -2075,7 +2077,7 @@
 
 	var pageTarget target.Output
 
-	if p, ok := d.(*Page); ok && path.Ext(p.URL) != "" {
+	if p, ok := d.(*Page); ok && path.Ext(p.URLPath.URL) != "" {
 		// user has explicitly set a URL with extension for this page
 		// make sure it sticks even if "ugly URLs" are turned off.
 		pageTarget = s.pageUglyTarget()
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -1448,7 +1448,10 @@
 	permalink, err = doc3.Permalink()
 	assert.NoError(t, err, "permalink call failed")
 	assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
-	assert.Equal(t, "/superbob", doc3.URL, "invalid url, was specified on doc3")
+
+	// TODO(bep) multilingo. Check this case. This has url set in frontmatter, but we must split into lang folders
+	// The assertion below was missing the /en prefix.
+	assert.Equal(t, "/en/superbob", doc3.URL(), "invalid url, was specified on doc3 TODO(bep)")
 
 	assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
 
--- a/tpl/template_i18n.go
+++ b/tpl/template_i18n.go
@@ -35,7 +35,8 @@
 		translater.current = f
 		return nil
 	}
-	return fmt.Errorf("Translation func for language %v not found", lang)
+	jww.WARN.Printf("Translation func for language %v not found", lang)
+	return nil
 }
 
 func SetI18nTfuncs(bndl *bundle.Bundle) {
@@ -58,7 +59,7 @@
 }
 
 func I18nTranslate(id string, args ...interface{}) (string, error) {
-	if translater == nil {
+	if translater == nil || translater.current == nil {
 		return "", fmt.Errorf("i18n not initialized, have you configured everything properly?")
 	}
 	return translater.current(id, args...), nil
--