hugo

Unnamed repository; edit this file 'description' to name the repository.

git clone git://git.shimmy1996.com/hugo.git
commit 3a02807970e792299a80738befe32eea8d10984d
parent 52bf8f90958e75007ca0a2761edc6ca331ca3280
Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Date:   Tue, 26 Jul 2016 19:04:10 +0200

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.

Diffstat:
Mhugolib/menu_test.go | 2+-
Mhugolib/node.go | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mhugolib/page.go | 32++++----------------------------
Mhugolib/pagination.go | 11++++++-----
Mhugolib/site.go | 72+++++++++++++++++++++++++++++++++++++-----------------------------------
Mhugolib/site_test.go | 5++++-
Mtpl/template_i18n.go | 5+++--
7 files changed, 162 insertions(+), 74 deletions(-)
diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go
@@ -554,7 +554,7 @@ func TestHomeNodeMenu(t *testing.T) {
 	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
diff --git a/hugolib/node.go b/hugolib/node.go
@@ -15,6 +15,9 @@ package hugolib
 
 import (
 	"html/template"
+	"path"
+	"path/filepath"
+	"sort"
 	"sync"
 	"time"
 
@@ -38,6 +41,27 @@ type Node struct {
 	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 {
@@ -46,7 +70,7 @@ func (n *Node) Now() time.Time {
 
 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) HasMenuCurrent(menuID string, inme *MenuEntry) bool {
 
 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 @@ func (n *Node) RelRef(ref string) (string, error) {
 	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 @@ type URLPath struct {
 	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 {
@@ -152,3 +185,75 @@ func (n *Node) Scratch() *Scratch {
 	}
 	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)
+}
diff --git a/hugolib/page.go b/hugolib/page.go
@@ -64,8 +64,6 @@ type Page struct {
 	translations        Pages
 	extension           string
 	contentType         string
-	lang                string
-	language            *Language
 	renderable          bool
 	Layout              string
 	layoutsCalculated   []string
@@ -431,7 +429,7 @@ func (p *Page) permalink() (*url.URL, error) {
 	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 @@ func (p *Page) Extension() string {
 	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 @@ func (p *Page) update(f interface{}) error {
 			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) FullFilePath() string {
 
 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"
@@ -1042,17 +1032,3 @@ func (p *Page) TargetPath() (outfile string) {
 
 	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)
-}
diff --git a/hugolib/pagination.go b/hugolib/pagination.go
@@ -16,14 +16,15 @@ package hugolib
 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 @@ func (n *Node) Paginator(options ...interface{}) (*Pager, error) {
 			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 @@ func (n *Node) Paginate(seq interface{}, options ...interface{}) (*Pager, error)
 		if n.paginator != nil {
 			return
 		}
-		pagers, err := paginatePages(seq, pagerSize, n.URL)
+		pagers, err := paginatePages(seq, pagerSize, n.URL())
 
 		if err != nil {
 			initError = err
diff --git a/hugolib/site.go b/hugolib/site.go
@@ -117,19 +117,20 @@ type targetList struct {
 }
 
 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 @@ func (s *Site) initializeSiteInfo() {
 		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 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
 			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 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
 		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 @@ func (s *Site) renderSectionLists() error {
 			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 @@ func (s *Site) renderSectionLists() error {
 		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 @@ func (s *Site) renderHomePage() error {
 		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 @@ func (s *Site) renderHomePage() error {
 
 	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 @@ func (s *Site) renderHomePage() error {
 	}
 
 	// 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 @@ func (s *Site) renderSitemap() error {
 	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) Stats(t0 time.Time) {
 }
 
 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 @@ func (s *Site) renderAndWritePage(name string, dest string, d interface{}, layou
 
 	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()
diff --git a/hugolib/site_test.go b/hugolib/site_test.go
@@ -1448,7 +1448,10 @@ NOTE: should use the "permalinks" configuration with :filename
 	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")
 
diff --git a/tpl/template_i18n.go b/tpl/template_i18n.go
@@ -35,7 +35,8 @@ func SetTranslateLang(lang string) error {
 		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 SetI18nTfuncs(bndl *bundle.Bundle) {
 }
 
 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