diff --git a/commands/hugo.go b/commands/hugo.go index 57a426458..c773ac5c4 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -493,9 +493,8 @@ func InitializeConfig(subCmdVs ...*cobra.Command) error { helpers.HugoReleaseVersion(), minVersion) } - readMultilingualConfiguration() + return readMultilingualConfiguration() - return nil } func flagChanged(flags *flag.FlagSet, key string) bool { @@ -715,11 +714,11 @@ func buildSite(watching ...bool) (err error) { for _, lang := range langConfigsList { t1 := time.Now() - mainSite, present := MainSites[lang] + mainSite, present := MainSites[lang.Lang] if !present { mainSite = new(hugolib.Site) - MainSites[lang] = mainSite - mainSite.SetMultilingualConfig(lang, langConfigsList, langConfigs) + MainSites[lang.Lang] = mainSite + mainSite.SetMultilingualConfig(lang, langConfigsList) } if len(watching) > 0 && watching[0] { @@ -730,7 +729,7 @@ func buildSite(watching ...bool) (err error) { return err } - mainSite.Stats(lang, t1) + mainSite.Stats(lang.Lang, t1) } jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds())) @@ -743,13 +742,13 @@ func rebuildSite(events []fsnotify.Event) error { for _, lang := range langConfigsList { t1 := time.Now() - mainSite := MainSites[lang] + mainSite := MainSites[lang.Lang] if err := mainSite.ReBuild(events); err != nil { return err } - mainSite.Stats(lang, t1) + mainSite.Stats(lang.Lang, t1) } jww.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds())) diff --git a/commands/multilingual.go b/commands/multilingual.go index 68da7c96d..983dc756d 100644 --- a/commands/multilingual.go +++ b/commands/multilingual.go @@ -1,41 +1,66 @@ package commands import ( + "fmt" "sort" + "strings" + "github.com/spf13/cast" + "github.com/spf13/hugo/hugolib" "github.com/spf13/viper" ) -var langConfigs map[string]interface{} -var langConfigsList langConfigsSortable +var langConfigsList hugolib.Languages -func readMultilingualConfiguration() { +func readMultilingualConfiguration() error { multilingual := viper.GetStringMap("Multilingual") if len(multilingual) == 0 { - langConfigsList = append(langConfigsList, "") - return + // TODO(bep) multilingo langConfigsList = append(langConfigsList, hugolib.NewLanguage("en")) + return nil } - langConfigs = make(map[string]interface{}) - for lang, config := range multilingual { - langConfigs[lang] = config - langConfigsList = append(langConfigsList, lang) + var err error + + langConfigsList, err = toSortedLanguages(multilingual) + + if err != nil { + return fmt.Errorf("Failed to parse multilingual config: %s", err) } - sort.Sort(langConfigsList) + + return nil } -type langConfigsSortable []string +func toSortedLanguages(l map[string]interface{}) (hugolib.Languages, error) { + langs := make(hugolib.Languages, len(l)) -func (p langConfigsSortable) Len() int { return len(p) } -func (p langConfigsSortable) Less(i, j int) bool { return weightForLang(p[i]) < weightForLang(p[j]) } -func (p langConfigsSortable) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + for lang, langConf := range l { + langsMap, ok := langConf.(map[string]interface{}) -func weightForLang(lang string) int { - conf := langConfigs[lang] - if conf == nil { - return 0 + 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 = append(langs, language) } - m := cast.ToStringMap(conf) - return cast.ToInt(m["weight"]) + + sort.Sort(langs) + + return langs, nil } diff --git a/hugolib/multilingual.go b/hugolib/multilingual.go index 2552bad4c..eebd43e3d 100644 --- a/hugolib/multilingual.go +++ b/hugolib/multilingual.go @@ -1,43 +1,80 @@ package hugolib import ( + "sync" + + "strings" + "github.com/spf13/cast" "github.com/spf13/viper" ) -type Multilingual struct { - enabled bool - config *viper.Viper - - Languages []string +type Language struct { + Lang string + Title string + Weight int + params map[string]interface{} + paramsInit sync.Once } -func (ml *Multilingual) GetString(key string) string { return cast.ToString(ml.Get(key)) } -func (ml *Multilingual) GetStringMap(key string) map[string]interface{} { +func NewLanguage(lang string) *Language { + return &Language{Lang: lang, params: make(map[string]interface{})} +} + +type Languages []*Language + +func (l Languages) Len() int { return len(l) } +func (l Languages) Less(i, j int) bool { return l[i].Weight < l[j].Weight } +func (l Languages) Swap(i, j int) { l[i], l[j] = l[j], l[i] } + +type Multilingual struct { + enabled bool + + Languages Languages +} + +func (l *Language) Params() map[string]interface{} { + l.paramsInit.Do(func() { + // Merge with global config. + // TODO(bep) consider making this part of a constructor func. + globalParams := viper.GetStringMap("Params") + for k, v := range globalParams { + if _, ok := l.params[k]; !ok { + l.params[k] = v + } + } + }) + return l.params +} + +func (l *Language) SetParam(k string, v interface{}) { + l.params[k] = v +} + +func (l *Language) GetString(key string) string { return cast.ToString(l.Get(key)) } +func (ml *Language) GetStringMap(key string) map[string]interface{} { return cast.ToStringMap(ml.Get(key)) } -func (ml *Multilingual) GetStringMapString(key string) map[string]string { - return cast.ToStringMapString(ml.Get(key)) +func (l *Language) GetStringMapString(key string) map[string]string { + return cast.ToStringMapString(l.Get(key)) } -func (ml *Multilingual) Get(key string) interface{} { - if ml != nil && ml.config != nil && ml.config.IsSet(key) { - return ml.config.Get(key) +func (l *Language) Get(key string) interface{} { + key = strings.ToLower(key) + if v, ok := l.params[key]; ok { + return v } return viper.Get(key) } -func (s *Site) SetMultilingualConfig(currentLang string, orderedLanguages []string, langConfigs map[string]interface{}) { - conf := viper.New() - for k, val := range cast.ToStringMap(langConfigs[currentLang]) { - conf.Set(k, val) - } - conf.Set("CurrentLanguage", currentLang) +func (s *Site) SetMultilingualConfig(currentLang *Language, languages Languages) { + + // TODO(bep) multilingo evaluate + viper.Set("CurrentLanguage", currentLang) ml := &Multilingual{ - enabled: len(langConfigs) > 0, - config: conf, - Languages: orderedLanguages, + enabled: len(languages) > 0, + Languages: languages, } viper.Set("Multilingual", ml.enabled) s.Multilingual = ml @@ -46,3 +83,15 @@ func (s *Site) SetMultilingualConfig(currentLang string, orderedLanguages []stri func (s *Site) multilingualEnabled() bool { return s.Multilingual != nil && s.Multilingual.enabled } + +func currentLanguageString() string { + return currentLanguage().Lang +} + +func currentLanguage() *Language { + l := viper.Get("CurrentLanguage") + if l == nil { + panic("CurrentLanguage not set") + } + return l.(*Language) +} diff --git a/hugolib/site.go b/hugolib/site.go index 308e35c90..92ce48535 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -131,7 +131,7 @@ type SiteInfo struct { Multilingual bool CurrentLanguage string LanguagePrefix string - Languages []string + Languages Languages } // SiteSocial is a place to put social details on a site level. These are the @@ -705,7 +705,7 @@ func (s *Site) Process() (err error) { i18nSources = []source.Input{&source.Filesystem{Base: themeI18nDir}, i18nSources[0]} } - if err = loadI18n(i18nSources, s.Multilingual.GetString("CurrentLanguage")); err != nil { + if err = loadI18n(i18nSources, currentLanguageString()); err != nil { return } s.timerStep("load i18n") @@ -742,7 +742,7 @@ func (s *Site) setupTranslations() { return } - currentLang := s.Multilingual.GetString("CurrentLanguage") + currentLang := currentLanguageString() allTranslations := pagesToTranslationsMap(s.AllPages) assignTranslationsToPages(allTranslations, s.AllPages) @@ -817,7 +817,27 @@ func (s *Site) initialize() (err error) { } func (s *Site) initializeSiteInfo() { - params := s.Multilingual.GetStringMap("Params") + + var ( + lang *Language + languages Languages + ) + + cl := viper.Get("CurrentLanguage") + if cl == nil { + // Set default to english + // TODO(bep) multilingo this looks clumsy + lang = NewLanguage("en") + viper.Set("CurrentLanguage", lang) + } else { + lang = cl.(*Language) + } + + if s.Multilingual != nil { + languages = s.Multilingual.Languages + } + + params := lang.Params() permalinks := make(PermalinkOverrides) for k, v := range viper.GetStringMapString("Permalinks") { @@ -826,24 +846,20 @@ func (s *Site) initializeSiteInfo() { languagePrefix := "" if s.multilingualEnabled() { - languagePrefix = "/" + s.Multilingual.GetString("CurrentLanguage") - } - - languages := []string{} - if s.Multilingual != nil { - languages = s.Multilingual.Languages + languagePrefix = "/" + lang.Lang } s.Info = SiteInfo{ - BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("BaseURL"))), - Title: s.Multilingual.GetString("Title"), - Author: s.Multilingual.GetStringMap("author"), - Social: s.Multilingual.GetStringMapString("social"), - LanguageCode: s.Multilingual.GetString("languagecode"), - Copyright: s.Multilingual.GetString("copyright"), - DisqusShortname: s.Multilingual.GetString("DisqusShortname"), + BaseURL: template.URL(helpers.SanitizeURLKeepTrailingSlash(viper.GetString("BaseURL"))), + Title: lang.GetString("Title"), + Author: lang.GetStringMap("author"), + Social: lang.GetStringMapString("social"), + LanguageCode: lang.GetString("languagecode"), + Copyright: lang.GetString("copyright"), + DisqusShortname: lang.GetString("DisqusShortname"), + // TODO(bep) multilang, consolidate the below (make into methods etc.) Multilingual: s.multilingualEnabled(), - CurrentLanguage: s.Multilingual.GetString("CurrentLanguage"), + CurrentLanguage: lang.Lang, LanguagePrefix: languagePrefix, Languages: languages, GoogleAnalytics: viper.GetString("GoogleAnalytics"), @@ -1594,7 +1610,7 @@ func (s *Site) newTaxonomyNode(t taxRenderInfo) (*Node, string) { func (s *Site) addMultilingualPrefix(basePath string) string { hadPrefix := strings.HasPrefix(basePath, "/") if s.multilingualEnabled() { - basePath = path.Join(s.Multilingual.GetString("CurrentLanguage"), basePath) + basePath = path.Join(currentLanguageString(), basePath) if hadPrefix { basePath = "/" + basePath } diff --git a/hugolib/site_test.go b/hugolib/site_test.go index 2f03dec97..9f29bf376 100644 --- a/hugolib/site_test.go +++ b/hugolib/site_test.go @@ -1402,13 +1402,13 @@ NOTE: should use the "permalinks" configuration with :filename s := &Site{ Source: &source.InMemorySource{ByteSource: sources}, Multilingual: &Multilingual{ - config: viper.New(), enabled: true, }, } // Multilingual settings viper.Set("Multilingual", true) - s.Multilingual.config.Set("CurrentLanguage", "en") + en := NewLanguage("en") + viper.Set("CurrentLanguage", en) viper.Set("DefaultContentLanguage", "fr") viper.Set("paginate", "2")