Make taxonomies configurable per language

See #2312
This commit is contained in:
Bjørn Erik Pedersen 2016-08-05 13:10:58 +02:00
parent 36f2a1f676
commit 90de511017
9 changed files with 150 additions and 113 deletions

View file

@ -493,12 +493,12 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error {
helpers.HugoReleaseVersion(), minVersion) helpers.HugoReleaseVersion(), minVersion)
} }
h, err := readMultilingualConfiguration() h, err := hugolib.NewHugoSitesFromConfiguration()
if err != nil { if err != nil {
return err return err
} }
//TODO(bep) refactor ... //TODO(bep) ml refactor ...
Hugo = h Hugo = h
return nil return nil

View file

@ -1,75 +0,0 @@
package commands
import (
"fmt"
"sort"
"strings"
"github.com/spf13/cast"
"github.com/spf13/hugo/hugolib"
"github.com/spf13/viper"
)
func readMultilingualConfiguration() (*hugolib.HugoSites, error) {
sites := make([]*hugolib.Site, 0)
multilingual := viper.GetStringMap("Languages")
if len(multilingual) == 0 {
// TODO(bep) multilingo langConfigsList = append(langConfigsList, hugolib.NewLanguage("en"))
sites = append(sites, hugolib.NewSite(hugolib.NewLanguage("en")))
}
if len(multilingual) > 0 {
var err error
languages, err := toSortedLanguages(multilingual)
if err != nil {
return nil, fmt.Errorf("Failed to parse multilingual config: %s", err)
}
for _, lang := range languages {
sites = append(sites, hugolib.NewSite(lang))
}
}
return hugolib.NewHugoSites(sites...)
}
func toSortedLanguages(l map[string]interface{}) (hugolib.Languages, error) {
langs := make(hugolib.Languages, len(l))
i := 0
for lang, langConf := range l {
langsMap, ok := langConf.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Language config is not a map: %v", langsMap)
}
language := hugolib.NewLanguage(lang)
for k, v := range langsMap {
loki := strings.ToLower(k)
switch loki {
case "title":
language.Title = cast.ToString(v)
case "weight":
language.Weight = cast.ToInt(v)
}
// Put all into the Params map
// TODO(bep) reconsile with the type handling etc. from other params handlers.
language.SetParam(loki, v)
}
langs[i] = language
i++
}
sort.Sort(langs)
return langs, nil
}

View file

@ -38,6 +38,25 @@ and taxonomy pages will be rendered below `/en` in English, and below `/fr` in F
Only the obvious non-global options can be overridden per language. Examples of global options are `BaseURL`, `BuildDrafts`, etc. Only the obvious non-global options can be overridden per language. Examples of global options are `BaseURL`, `BuildDrafts`, etc.
Taxonomies configuration can also be set per language, example:
```
[Taxonomies]
tag = "tags"
[Languages]
[Languages.en]
weight = 1
title = "English"
[Languages.fr]
weight = 2
title = "Français"
[Languages.fr.Taxonomies]
plaque = "plaques"
```
### Translating your content ### Translating your content
Translated articles are identified by the name of the content file. Translated articles are identified by the name of the content file.

View file

@ -28,9 +28,9 @@ func TestLoadGlobalConfig(t *testing.T) {
PaginatePath = "side" PaginatePath = "side"
` `
writeSource(t, "config.toml", configContent) writeSource(t, "hugo.toml", configContent)
require.NoError(t, LoadGlobalConfig("", "config.toml")) require.NoError(t, LoadGlobalConfig("", "hugo.toml"))
assert.Equal(t, "side", viper.GetString("PaginatePath")) assert.Equal(t, "side", viper.GetString("PaginatePath"))
// default // default
assert.Equal(t, "layouts", viper.GetString("LayoutDir")) assert.Equal(t, "layouts", viper.GetString("LayoutDir"))

View file

@ -15,6 +15,7 @@ package hugolib
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -53,6 +54,34 @@ func NewHugoSites(sites ...*Site) (*HugoSites, error) {
return &HugoSites{Multilingual: langConfig, Sites: sites}, nil return &HugoSites{Multilingual: langConfig, Sites: sites}, nil
} }
// NewHugoSitesFromConfiguration creates HugoSites from the global Viper config.
func NewHugoSitesFromConfiguration() (*HugoSites, error) {
sites := make([]*Site, 0)
multilingual := viper.GetStringMap("Languages")
if len(multilingual) == 0 {
// TODO(bep) multilingo langConfigsList = append(langConfigsList, NewLanguage("en"))
sites = append(sites, NewSite(NewLanguage("en")))
}
if len(multilingual) > 0 {
var err error
languages, err := toSortedLanguages(multilingual)
if err != nil {
return nil, fmt.Errorf("Failed to parse multilingual config: %s", err)
}
for _, lang := range languages {
sites = append(sites, NewSite(lang))
}
}
return NewHugoSites(sites...)
}
// Reset resets the sites, making it ready for a full rebuild. // Reset resets the sites, making it ready for a full rebuild.
// TODO(bep) multilingo // TODO(bep) multilingo
func (h HugoSites) Reset() { func (h HugoSites) Reset() {
@ -61,7 +90,7 @@ func (h HugoSites) Reset() {
} }
} }
func (h HugoSites) siteInfos() []*SiteInfo { func (h HugoSites) toSiteInfos() []*SiteInfo {
infos := make([]*SiteInfo, len(h.Sites)) infos := make([]*SiteInfo, len(h.Sites))
for i, s := range h.Sites { for i, s := range h.Sites {
infos[i] = &s.Info infos[i] = &s.Info
@ -220,7 +249,7 @@ func (h *HugoSites) render() error {
smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"} smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"}
if err := s.renderAndWriteXML("sitemapindex", sitemapDefault.Filename, if err := s.renderAndWriteXML("sitemapindex", sitemapDefault.Filename,
h.siteInfos(), s.appendThemeTemplates(smLayouts)...); err != nil { h.toSiteInfos(), s.appendThemeTemplates(smLayouts)...); err != nil {
return err return err
} }

View file

@ -22,8 +22,7 @@ import (
func init() { func init() {
testCommonResetState() testCommonResetState()
jww.SetStdoutThreshold(jww.LevelError) jww.SetStdoutThreshold(jww.LevelCritical)
} }
func testCommonResetState() { func testCommonResetState() {
@ -38,8 +37,8 @@ func testCommonResetState() {
} }
func TestMultiSites(t *testing.T) { func TestMultiSitesBuild(t *testing.T) {
testCommonResetState()
sites := createMultiTestSites(t) sites := createMultiTestSites(t)
err := sites.Build(BuildCfg{}) err := sites.Build(BuildCfg{})
@ -128,10 +127,20 @@ func TestMultiSites(t *testing.T) {
sitemapFr := readDestination(t, "public/fr/sitemap.xml") sitemapFr := readDestination(t, "public/fr/sitemap.xml")
require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn) require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn)
require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr) require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr)
// Check taxonomies
enTags := enSite.Taxonomies["tags"]
frTags := frSite.Taxonomies["plaques"]
require.Len(t, enTags, 2, fmt.Sprintf("Tags in en: %=v", enTags))
require.Len(t, frTags, 2, fmt.Sprintf("Tags in fr: %=v", frTags))
require.NotNil(t, enTags["tag1"])
require.NotNil(t, frTags["frtag1"])
readDestination(t, "public/fr/plaques/frtag1/index.html")
readDestination(t, "public/en/tags/tag1/index.html")
} }
func TestMultiSitesRebuild(t *testing.T) { func TestMultiSitesRebuild(t *testing.T) {
testCommonResetState()
sites := createMultiTestSites(t) sites := createMultiTestSites(t)
cfg := BuildCfg{} cfg := BuildCfg{}
@ -294,16 +303,6 @@ func TestMultiSitesRebuild(t *testing.T) {
} }
func createMultiTestSites(t *testing.T) *HugoSites { func createMultiTestSites(t *testing.T) *HugoSites {
// General settings
hugofs.InitMemFs()
viper.Set("DefaultExtension", "html")
viper.Set("baseurl", "http://example.com/blog")
viper.Set("DisableSitemap", false)
viper.Set("DisableRSS", false)
viper.Set("RSSUri", "index.xml")
viper.Set("Taxonomies", map[string]string{"tag": "tags"})
viper.Set("Permalinks", map[string]string{"other": "/somewhere/else/:filename"})
// Add some layouts // Add some layouts
if err := afero.WriteFile(hugofs.Source(), if err := afero.WriteFile(hugofs.Source(),
@ -362,9 +361,9 @@ NOTE: slug should be used as URL
`)}, `)},
{filepath.FromSlash("sect/doc1.fr.md"), []byte(`--- {filepath.FromSlash("sect/doc1.fr.md"), []byte(`---
title: doc1 title: doc1
tags: plaques:
- tag1 - frtag1
- tag2 - frtag2
publishdate: "2000-01-04" publishdate: "2000-01-04"
--- ---
# doc1 # doc1
@ -393,8 +392,8 @@ NOTE: third 'en' doc, should trigger pagination on home page.
`)}, `)},
{filepath.FromSlash("sect/doc4.md"), []byte(`--- {filepath.FromSlash("sect/doc4.md"), []byte(`---
title: doc4 title: doc4
tags: plaques:
- tag1 - frtag1
publishdate: "2000-01-05" publishdate: "2000-01-05"
--- ---
# doc4 # doc4
@ -446,13 +445,39 @@ draft: true
`)}, `)},
} }
// Multilingual settings tomlConfig := `
viper.Set("Multilingual", true) DefaultExtension = "html"
en := NewLanguage("en") baseurl = "http://example.com/blog"
viper.Set("DefaultContentLanguage", "fr") DisableSitemap = false
viper.Set("paginate", "2") DisableRSS = false
RSSUri = "index.xml"
languages := NewLanguages(en, NewLanguage("fr")) paginate = 2
DefaultContentLanguage = "fr"
[permalinks]
other = "/somewhere/else/:filename"
[Taxonomies]
tag = "tags"
[Languages]
[Languages.en]
weight = 1
title = "English"
[Languages.fr]
weight = 2
title = "Français"
[Languages.fr.Taxonomies]
plaque = "plaques"
`
writeSource(t, "multilangconfig.toml", tomlConfig)
if err := LoadGlobalConfig("", "multilangconfig.toml"); err != nil {
t.Fatalf("Failed to load config: %s", err)
}
// Hugo support using ByteSource's directly (for testing), // Hugo support using ByteSource's directly (for testing),
// but to make it more real, we write them to the mem file system. // but to make it more real, we write them to the mem file system.
@ -466,7 +491,8 @@ draft: true
if err != nil { if err != nil {
t.Fatalf("Unable to locate file") t.Fatalf("Unable to locate file")
} }
sites, err := newHugoSitesFromLanguages(languages)
sites, err := NewHugoSitesFromConfiguration()
if err != nil { if err != nil {
t.Fatalf("Failed to create sites: %s", err) t.Fatalf("Failed to create sites: %s", err)

View file

@ -6,6 +6,8 @@ import (
"sort" "sort"
"strings" "strings"
"fmt"
"github.com/spf13/cast" "github.com/spf13/cast"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -112,3 +114,39 @@ func (s *Site) currentLanguageString() string {
func (s *Site) currentLanguage() *Language { func (s *Site) currentLanguage() *Language {
return s.Language return s.Language
} }
func toSortedLanguages(l map[string]interface{}) (Languages, error) {
langs := make(Languages, len(l))
i := 0
for lang, langConf := range l {
langsMap, ok := langConf.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("Language config is not a map: %v", langsMap)
}
language := NewLanguage(lang)
for k, v := range langsMap {
loki := strings.ToLower(k)
switch loki {
case "title":
language.Title = cast.ToString(v)
case "weight":
language.Weight = cast.ToInt(v)
}
// Put all into the Params map
// TODO(bep) reconsile with the type handling etc. from other params handlers.
language.SetParam(loki, v)
}
langs[i] = language
i++
}
sort.Sort(langs)
return langs, nil
}

View file

@ -1325,7 +1325,7 @@ func (s *Site) assembleMenus() {
func (s *Site) assembleTaxonomies() { func (s *Site) assembleTaxonomies() {
s.Taxonomies = make(TaxonomyList) s.Taxonomies = make(TaxonomyList)
taxonomies := viper.GetStringMapString("Taxonomies") taxonomies := s.Language.GetStringMapString("Taxonomies")
jww.INFO.Printf("found taxonomies: %#v\n", taxonomies) jww.INFO.Printf("found taxonomies: %#v\n", taxonomies)
for _, plural := range taxonomies { for _, plural := range taxonomies {
@ -1563,7 +1563,7 @@ func (s *Site) renderTaxonomiesLists() error {
go errorCollator(results, errs) go errorCollator(results, errs)
taxonomies := viper.GetStringMapString("Taxonomies") taxonomies := s.Language.GetStringMapString("Taxonomies")
for singular, plural := range taxonomies { for singular, plural := range taxonomies {
for key, pages := range s.Taxonomies[plural] { for key, pages := range s.Taxonomies[plural] {
taxes <- taxRenderInfo{key, pages, singular, plural} taxes <- taxRenderInfo{key, pages, singular, plural}
@ -1693,7 +1693,7 @@ func taxonomyRenderer(s *Site, taxes <-chan taxRenderInfo, results chan<- error,
// renderListsOfTaxonomyTerms renders a page per taxonomy that lists the terms for that taxonomy // renderListsOfTaxonomyTerms renders a page per taxonomy that lists the terms for that taxonomy
func (s *Site) renderListsOfTaxonomyTerms() (err error) { func (s *Site) renderListsOfTaxonomyTerms() (err error) {
taxonomies := viper.GetStringMapString("Taxonomies") taxonomies := s.Language.GetStringMapString("Taxonomies")
for singular, plural := range taxonomies { for singular, plural := range taxonomies {
n := s.newNode() n := s.newNode()
n.Title = strings.Title(plural) n.Title = strings.Title(plural)
@ -1969,7 +1969,7 @@ func (s *Site) Stats() {
jww.FEEDBACK.Printf("%d pages created\n", len(s.Pages)) jww.FEEDBACK.Printf("%d pages created\n", len(s.Pages))
jww.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files)) jww.FEEDBACK.Printf("%d non-page files copied\n", len(s.Files))
jww.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount) jww.FEEDBACK.Printf("%d paginator pages created\n", s.Info.paginationPageCount)
taxonomies := viper.GetStringMapString("Taxonomies") taxonomies := s.Language.GetStringMapString("Taxonomies")
for _, pl := range taxonomies { for _, pl := range taxonomies {
jww.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl) jww.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)

View file

@ -30,7 +30,7 @@ func TestByCountOrderOfTaxonomies(t *testing.T) {
viper.Set("taxonomies", taxonomies) viper.Set("taxonomies", taxonomies)
site := new(Site) site := newSiteDefaultLang()
page, _ := NewPageFrom(strings.NewReader(pageYamlWithTaxonomiesA), "path/to/page") page, _ := NewPageFrom(strings.NewReader(pageYamlWithTaxonomiesA), "path/to/page")
site.Pages = append(site.Pages, page) site.Pages = append(site.Pages, page)
site.assembleTaxonomies() site.assembleTaxonomies()