From 3a02807970e792299a80738befe32eea8d10984d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 26 Jul 2016 19:04:10 +0200 Subject: [PATCH] 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. --- hugolib/menu_test.go | 2 +- hugolib/node.go | 109 +++++++++++++++++++++++++++++++++++++++++- hugolib/page.go | 32 ++----------- hugolib/pagination.go | 11 +++-- hugolib/site.go | 72 ++++++++++++++-------------- hugolib/site_test.go | 5 +- tpl/template_i18n.go | 5 +- 7 files changed, 162 insertions(+), 74 deletions(-) diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go index 03219a48a..898f035d6 100644 --- 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 index c9e60701e..77a26603a 100644 --- 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 index f28482c57..d02472f97 100644 --- 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 index e322ab5b3..f7f9cbf8c 100644 --- 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 index d902f693e..eaf4fd95d 100644 --- 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 index ecf3d834d..8f2022db5 100644 --- 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 index 5590fe29a..462b30a80 100644 --- 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