Add Translations and AllTranslations to Node - hugo - [fork] hugo port for 9front
 (HTM) git clone git@git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 3a02807970e792299a80738befe32eea8d10984d
 (DIR) parent 52bf8f90958e75007ca0a2761edc6ca331ca3280
 (HTM) 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:
         M hugolib/menu_test.go                |       2 +-
         M hugolib/node.go                     |     109 ++++++++++++++++++++++++++++++-
         M hugolib/page.go                     |      32 ++++---------------------------
         M hugolib/pagination.go               |      11 ++++++-----
         M hugolib/site.go                     |      72 ++++++++++++++++---------------
         M hugolib/site_test.go                |       5 ++++-
         M tpl/template_i18n.go                |       5 +++--
       
       7 files changed, 162 insertions(+), 74 deletions(-)
       ---
 (DIR) 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
 (DIR) 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)
       +}
 (DIR) 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)
       -}
 (DIR) 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
 (DIR) 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()
 (DIR) 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")
        
 (DIR) 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