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
--
⑨