github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/langs/config.go (about)

     1  // Copyright 2018 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package langs
    15  
    16  import (
    17  	"fmt"
    18  	"path/filepath"
    19  	"sort"
    20  	"strings"
    21  
    22  	"github.com/gohugoio/hugo/common/maps"
    23  
    24  	"github.com/spf13/cast"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	"github.com/gohugoio/hugo/config"
    29  )
    30  
    31  type LanguagesConfig struct {
    32  	Languages                      Languages
    33  	Multihost                      bool
    34  	DefaultContentLanguageInSubdir bool
    35  }
    36  
    37  func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesConfig, err error) {
    38  	defaultLang := strings.ToLower(cfg.GetString("defaultContentLanguage"))
    39  	if defaultLang == "" {
    40  		defaultLang = "en"
    41  		cfg.Set("defaultContentLanguage", defaultLang)
    42  	}
    43  
    44  	var languages map[string]interface{}
    45  
    46  	languagesFromConfig := cfg.GetParams("languages")
    47  	disableLanguages := cfg.GetStringSlice("disableLanguages")
    48  
    49  	if len(disableLanguages) == 0 {
    50  		languages = languagesFromConfig
    51  	} else {
    52  		languages = make(maps.Params)
    53  		for k, v := range languagesFromConfig {
    54  			for _, disabled := range disableLanguages {
    55  				if disabled == defaultLang {
    56  					return c, fmt.Errorf("cannot disable default language %q", defaultLang)
    57  				}
    58  
    59  				if strings.EqualFold(k, disabled) {
    60  					v.(maps.Params)["disabled"] = true
    61  					break
    62  				}
    63  			}
    64  			languages[k] = v
    65  		}
    66  	}
    67  
    68  	var languages2 Languages
    69  
    70  	if len(languages) == 0 {
    71  		languages2 = append(languages2, NewDefaultLanguage(cfg))
    72  	} else {
    73  		languages2, err = toSortedLanguages(cfg, languages)
    74  		if err != nil {
    75  			return c, errors.Wrap(err, "Failed to parse multilingual config")
    76  		}
    77  	}
    78  
    79  	if oldLangs != nil {
    80  		// When in multihost mode, the languages are mapped to a server, so
    81  		// some structural language changes will need a restart of the dev server.
    82  		// The validation below isn't complete, but should cover the most
    83  		// important cases.
    84  		var invalid bool
    85  		if languages2.IsMultihost() != oldLangs.IsMultihost() {
    86  			invalid = true
    87  		} else {
    88  			if languages2.IsMultihost() && len(languages2) != len(oldLangs) {
    89  				invalid = true
    90  			}
    91  		}
    92  
    93  		if invalid {
    94  			return c, errors.New("language change needing a server restart detected")
    95  		}
    96  
    97  		if languages2.IsMultihost() {
    98  			// We need to transfer any server baseURL to the new language
    99  			for i, ol := range oldLangs {
   100  				nl := languages2[i]
   101  				nl.Set("baseURL", ol.GetString("baseURL"))
   102  			}
   103  		}
   104  	}
   105  
   106  	// The defaultContentLanguage is something the user has to decide, but it needs
   107  	// to match a language in the language definition list.
   108  	langExists := false
   109  	for _, lang := range languages2 {
   110  		if lang.Lang == defaultLang {
   111  			langExists = true
   112  			break
   113  		}
   114  	}
   115  
   116  	if !langExists {
   117  		return c, fmt.Errorf("site config value %q for defaultContentLanguage does not match any language definition", defaultLang)
   118  	}
   119  
   120  	c.Languages = languages2
   121  	c.Multihost = languages2.IsMultihost()
   122  	c.DefaultContentLanguageInSubdir = c.Multihost
   123  
   124  	sortedDefaultFirst := make(Languages, len(c.Languages))
   125  	for i, v := range c.Languages {
   126  		sortedDefaultFirst[i] = v
   127  	}
   128  	sort.Slice(sortedDefaultFirst, func(i, j int) bool {
   129  		li, lj := sortedDefaultFirst[i], sortedDefaultFirst[j]
   130  		if li.Lang == defaultLang {
   131  			return true
   132  		}
   133  
   134  		if lj.Lang == defaultLang {
   135  			return false
   136  		}
   137  
   138  		return i < j
   139  	})
   140  
   141  	cfg.Set("languagesSorted", c.Languages)
   142  	cfg.Set("languagesSortedDefaultFirst", sortedDefaultFirst)
   143  	cfg.Set("multilingual", len(languages2) > 1)
   144  
   145  	multihost := c.Multihost
   146  
   147  	if multihost {
   148  		cfg.Set("defaultContentLanguageInSubdir", true)
   149  		cfg.Set("multihost", true)
   150  	}
   151  
   152  	if multihost {
   153  		// The baseURL may be provided at the language level. If that is true,
   154  		// then every language must have a baseURL. In this case we always render
   155  		// to a language sub folder, which is then stripped from all the Permalink URLs etc.
   156  		for _, l := range languages2 {
   157  			burl := l.GetLocal("baseURL")
   158  			if burl == nil {
   159  				return c, errors.New("baseURL must be set on all or none of the languages")
   160  			}
   161  		}
   162  	}
   163  
   164  	for _, language := range c.Languages {
   165  		if language.initErr != nil {
   166  			return c, language.initErr
   167  		}
   168  	}
   169  
   170  	return c, nil
   171  }
   172  
   173  func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (Languages, error) {
   174  	languages := make(Languages, len(l))
   175  	i := 0
   176  
   177  	for lang, langConf := range l {
   178  		langsMap, err := maps.ToStringMapE(langConf)
   179  		if err != nil {
   180  			return nil, fmt.Errorf("Language config is not a map: %T", langConf)
   181  		}
   182  
   183  		language := NewLanguage(lang, cfg)
   184  
   185  		for loki, v := range langsMap {
   186  			switch loki {
   187  			case "title":
   188  				language.Title = cast.ToString(v)
   189  			case "languagename":
   190  				language.LanguageName = cast.ToString(v)
   191  			case "languagedirection":
   192  				language.LanguageDirection = cast.ToString(v)
   193  			case "weight":
   194  				language.Weight = cast.ToInt(v)
   195  			case "contentdir":
   196  				language.ContentDir = filepath.Clean(cast.ToString(v))
   197  			case "disabled":
   198  				language.Disabled = cast.ToBool(v)
   199  			case "params":
   200  				m := maps.ToStringMap(v)
   201  				// Needed for case insensitive fetching of params values
   202  				maps.PrepareParams(m)
   203  				for k, vv := range m {
   204  					language.SetParam(k, vv)
   205  				}
   206  			case "timezone":
   207  				if err := language.loadLocation(cast.ToString(v)); err != nil {
   208  					return nil, err
   209  				}
   210  			}
   211  
   212  			// Put all into the Params map
   213  			language.SetParam(loki, v)
   214  
   215  			// Also set it in the configuration map (for baseURL etc.)
   216  			language.Set(loki, v)
   217  		}
   218  
   219  		languages[i] = language
   220  		i++
   221  	}
   222  
   223  	sort.Sort(languages)
   224  
   225  	return languages, nil
   226  }