Fix language params handling
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 30 Jan 2018 16:51:18 +0000 (17:51 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Tue, 30 Jan 2018 17:53:07 +0000 (18:53 +0100)
This fixes some issues with language params handling by separating params from configuration values per language.

This means that you can now do this:

```toml
[languages]
[languages.en]
languageName = "English"
weight = 1
title = "My Cool Site"
[languages.en.params]
myParam = "Hi!"
```

This is not a breaking change, but the above is a less suprising way of configuring custom params.

It also fixes some hard-to-debug corner-cases in multilingual sites.

Fixes #4356
Fixes #4352

helpers/language.go
helpers/language_test.go
hugolib/hugo_sites_build_test.go
hugolib/multilingual.go

index 934c82de0656e8e07360c2fdf01c5eaca24d1b9a..49a25ccf7e2ca49166dc5ab4516cd61091e8da2d 100644 (file)
@@ -16,7 +16,6 @@ package helpers
 import (
        "sort"
        "strings"
-       "sync"
 
        "github.com/gohugoio/hugo/config"
        "github.com/spf13/cast"
@@ -42,9 +41,16 @@ type Language struct {
        Title        string
        Weight       int
 
-       Cfg        config.Provider
-       params     map[string]interface{}
-       paramsInit sync.Once
+       Cfg config.Provider
+
+       // These are params declared in the [params] section of the language merged with the
+       // site's params, the most specific (language) wins on duplicate keys.
+       params map[string]interface{}
+
+       // These are config values, i.e. the settings declared outside of the [params] section of the language.
+       // This is the map Hugo looks in when looking for configuration values (baseURL etc.).
+       // Values in this map can also be fetched from the params map above.
+       settings map[string]interface{}
 }
 
 func (l *Language) String() string {
@@ -53,15 +59,14 @@ func (l *Language) String() string {
 
 // NewLanguage creates a new language.
 func NewLanguage(lang string, cfg config.Provider) *Language {
+       // Note that language specific params will be overridden later.
+       // We should improve that, but we need to make a copy:
        params := make(map[string]interface{})
-       // Merge with global config.
-       globalParams := cfg.GetStringMap("params")
-       for k, v := range globalParams {
-               if _, ok := params[k]; !ok {
-                       params[k] = v
-               }
+       for k, v := range cfg.GetStringMap("params") {
+               params[k] = v
        }
-       l := &Language{Lang: lang, Cfg: cfg, params: params}
+       ToLowerMap(params)
+       l := &Language{Lang: lang, Cfg: cfg, params: params, settings: make(map[string]interface{})}
        return l
 }
 
@@ -115,7 +120,7 @@ func (l Languages) IsMultihost() bool {
        return false
 }
 
-// SetParam sets param with the given key and value.
+// SetParam sets param with the given key and value.
 // SetParam is case-insensitive.
 func (l *Language) SetParam(k string, v interface{}) {
        l.params[strings.ToLower(k)] = v
@@ -166,7 +171,7 @@ func (l *Language) GetLocal(key string) interface{} {
        }
        key = strings.ToLower(key)
        if !globalOnlySettings[key] {
-               if v, ok := l.params[key]; ok {
+               if v, ok := l.settings[key]; ok {
                        return v
                }
        }
@@ -179,7 +184,7 @@ func (l *Language) Set(key string, value interface{}) {
                panic("language not set")
        }
        key = strings.ToLower(key)
-       l.params[key] = value
+       l.settings[key] = value
 }
 
 // IsSet checks whether the key is set in the language or the related config store.
@@ -188,7 +193,7 @@ func (l *Language) IsSet(key string) bool {
 
        key = strings.ToLower(key)
        if !globalOnlySettings[key] {
-               if _, ok := l.params[key]; ok {
+               if _, ok := l.settings[key]; ok {
                        return true
                }
        }
index 902177e1a46260cbb1cf93f0bddaebb791545f31..68ee3506d93a1980f97d8d7930ebea239c1b8b66 100644 (file)
@@ -23,11 +23,24 @@ import (
 func TestGetGlobalOnlySetting(t *testing.T) {
        v := viper.New()
        lang := NewDefaultLanguage(v)
-       lang.SetParam("defaultContentLanguageInSubdir", false)
-       lang.SetParam("paginatePath", "side")
+       lang.Set("defaultContentLanguageInSubdir", false)
+       lang.Set("paginatePath", "side")
        v.Set("defaultContentLanguageInSubdir", true)
        v.Set("paginatePath", "page")
 
        require.True(t, lang.GetBool("defaultContentLanguageInSubdir"))
        require.Equal(t, "side", lang.GetString("paginatePath"))
 }
+
+func TestLanguageParams(t *testing.T) {
+       assert := require.New(t)
+
+       v := viper.New()
+       v.Set("p1", "p1cfg")
+
+       lang := NewDefaultLanguage(v)
+       lang.SetParam("p1", "p1p")
+
+       assert.Equal("p1p", lang.Params()["p1"])
+       assert.Equal("p1cfg", lang.Get("p1"))
+}
index cc3a940de772b5288cbce43fcd49b06a0b517f1c..0cececfd762be3468956887d0e034955e7e3c160 100644 (file)
@@ -141,6 +141,9 @@ func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {
 
 func TestMultiSitesWithTwoLanguages(t *testing.T) {
        t.Parallel()
+
+       assert := require.New(t)
+
        mm := afero.NewMemMapFs()
 
        writeToFs(t, mm, "config.toml", `
@@ -152,11 +155,15 @@ defaultContentLanguage = "nn"
 languageName = "Nynorsk"
 weight = 1
 title = "Tittel på Nynorsk"
+[languages.nn.params]
+p1 = "p1nn"
 
 [languages.en]
 title = "Title in English"
 languageName = "English"
 weight = 2
+[languages.en.params]
+p1 = "p1en"
 `,
        )
 
@@ -176,15 +183,24 @@ weight = 2
        // Add some data
        writeSource(t, fs, filepath.Join("data", "hugo.toml"), "slogan = \"Hugo Rocks!\"")
 
-       require.NoError(t, sites.Build(BuildCfg{}))
-       require.Len(t, sites.Sites, 2)
+       assert.NoError(sites.Build(BuildCfg{}))
+       assert.Len(sites.Sites, 2)
 
        nnSite := sites.Sites[0]
-       nnSiteHome := nnSite.getPage(KindHome)
-       require.Len(t, nnSiteHome.AllTranslations(), 2)
-       require.Len(t, nnSiteHome.Translations(), 1)
-       require.True(t, nnSiteHome.IsTranslated())
+       nnHome := nnSite.getPage(KindHome)
+       assert.Len(nnHome.AllTranslations(), 2)
+       assert.Len(nnHome.Translations(), 1)
+       assert.True(nnHome.IsTranslated())
+
+       enHome := sites.Sites[1].getPage(KindHome)
+
+       p1, err := enHome.Param("p1")
+       assert.NoError(err)
+       assert.Equal("p1en", p1)
 
+       p1, err = nnHome.Param("p1")
+       assert.NoError(err)
+       assert.Equal("p1nn", p1)
 }
 
 //
index 589df66e0820b530ecf32a7a29446a0b9881a84f..101de7ace47967d8a579ebdd42b1360aa88c643f 100644 (file)
@@ -111,10 +111,20 @@ func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (helpers.L
                                language.LanguageName = cast.ToString(v)
                        case "weight":
                                language.Weight = cast.ToInt(v)
+                       case "params":
+                               m := cast.ToStringMap(v)
+                               // Needed for case insensitive fetching of params values
+                               helpers.ToLowerMap(m)
+                               for k, vv := range m {
+                                       language.SetParam(k, vv)
+                               }
                        }
 
                        // Put all into the Params map
                        language.SetParam(loki, v)
+
+                       // Also set it in the configuration map (for baseURL etc.)
+                       language.Set(loki, v)
                }
 
                langs[i] = language