hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

config.go (5911B)

    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 	"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]any
   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, fmt.Errorf("Failed to parse multilingual config: %w", err)
   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]any) (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 }