Add Translations and AllTranslations to Node
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 26 Jul 2016 17:04:10 +0000 (19:04 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 6 Sep 2016 15:32:16 +0000 (18:32 +0300)
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.

hugolib/menu_test.go
hugolib/node.go
hugolib/page.go
hugolib/pagination.go
hugolib/site.go
hugolib/site_test.go
tpl/template_i18n.go

index 03219a48aa85ed25e4ec03d0e27910b6c7d7b6ec..898f035d67974782912bcbe1024784912c684944 100644 (file)
@@ -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
index c9e60701e03684829622044088f59d3cfa03987b..77a26603ad44b3dd5b9a93e1b8acfb320d19da72 100644 (file)
@@ -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)
+}
index f28482c57a94debd4d56e8c1e9565784f7264fcc..d02472f97fefbc90709a5e163d03671c8bcf866a 100644 (file)
@@ -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)
-}
index e322ab5b33d277f14a2a1cff60b1edb447869688..f7f9cbf8c2fd906f890fd7b750ef039659cec4e5 100644 (file)
@@ -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
index d902f693e55f2429d3307af79c6de4fb1039346b..eaf4fd95d0dfabde3ce6660743ada6f8d2e5d224 100644 (file)
@@ -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()
index ecf3d834dd60ff902678dec95cb8f2ba144ec290..8f2022db5570413ac36170806ba76c007aca3c34 100644 (file)
@@ -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")
 
index 5590fe29aab39d74647d750e49cf36de12c2eb1c..462b30a8081bb03ae0ee0f6a79e132c1ce699bfb 100644 (file)
@@ -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