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 }