github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/langs/language.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 "sort" 18 "strings" 19 "sync" 20 "time" 21 22 "github.com/pkg/errors" 23 24 translators "github.com/gohugoio/localescompressed" 25 "github.com/gohugoio/locales" 26 "github.com/gohugoio/hugo/common/maps" 27 "github.com/gohugoio/hugo/config" 28 ) 29 30 // These are the settings that should only be looked up in the global Viper 31 // config and not per language. 32 // This list may not be complete, but contains only settings that we know 33 // will be looked up in both. 34 // This isn't perfect, but it is ultimately the user who shoots him/herself in 35 // the foot. 36 // See the pathSpec. 37 var globalOnlySettings = map[string]bool{ 38 strings.ToLower("defaultContentLanguageInSubdir"): true, 39 strings.ToLower("defaultContentLanguage"): true, 40 strings.ToLower("multilingual"): true, 41 strings.ToLower("assetDir"): true, 42 strings.ToLower("resourceDir"): true, 43 strings.ToLower("build"): true, 44 } 45 46 // Language manages specific-language configuration. 47 type Language struct { 48 Lang string 49 LanguageName string 50 LanguageDirection string 51 Title string 52 Weight int 53 54 Disabled bool 55 56 // If set per language, this tells Hugo that all content files without any 57 // language indicator (e.g. my-page.en.md) is in this language. 58 // This is usually a path relative to the working dir, but it can be an 59 // absolute directory reference. It is what we get. 60 ContentDir string 61 62 // Global config. 63 Cfg config.Provider 64 65 // Language specific config. 66 LocalCfg config.Provider 67 68 // Composite config. 69 config.Provider 70 71 // These are params declared in the [params] section of the language merged with the 72 // site's params, the most specific (language) wins on duplicate keys. 73 params map[string]interface{} 74 paramsMu sync.Mutex 75 paramsSet bool 76 77 // Used for date formatting etc. We don't want these exported to the 78 // templates. 79 // TODO(bep) do the same for some of the others. 80 translator locales.Translator 81 82 location *time.Location 83 84 // Error during initialization. Will fail the buld. 85 initErr error 86 } 87 88 func (l *Language) String() string { 89 return l.Lang 90 } 91 92 // NewLanguage creates a new language. 93 func NewLanguage(lang string, cfg config.Provider) *Language { 94 // Note that language specific params will be overridden later. 95 // We should improve that, but we need to make a copy: 96 params := make(map[string]interface{}) 97 for k, v := range cfg.GetStringMap("params") { 98 params[k] = v 99 } 100 maps.PrepareParams(params) 101 102 localCfg := config.New() 103 compositeConfig := config.NewCompositeConfig(cfg, localCfg) 104 translator := translators.GetTranslator(lang) 105 if translator == nil { 106 translator = translators.GetTranslator(cfg.GetString("defaultContentLanguage")) 107 if translator == nil { 108 translator = translators.GetTranslator("en") 109 } 110 } 111 112 l := &Language{ 113 Lang: lang, 114 ContentDir: cfg.GetString("contentDir"), 115 Cfg: cfg, LocalCfg: localCfg, 116 Provider: compositeConfig, 117 params: params, 118 translator: translator, 119 } 120 121 if err := l.loadLocation(cfg.GetString("timeZone")); err != nil { 122 l.initErr = err 123 } 124 125 return l 126 } 127 128 // NewDefaultLanguage creates the default language for a config.Provider. 129 // If not otherwise specified the default is "en". 130 func NewDefaultLanguage(cfg config.Provider) *Language { 131 defaultLang := cfg.GetString("defaultContentLanguage") 132 133 if defaultLang == "" { 134 defaultLang = "en" 135 } 136 137 return NewLanguage(defaultLang, cfg) 138 } 139 140 // Languages is a sortable list of languages. 141 type Languages []*Language 142 143 // NewLanguages creates a sorted list of languages. 144 // NOTE: function is currently unused. 145 func NewLanguages(l ...*Language) Languages { 146 languages := make(Languages, len(l)) 147 for i := 0; i < len(l); i++ { 148 languages[i] = l[i] 149 } 150 sort.Sort(languages) 151 return languages 152 } 153 154 func (l Languages) Len() int { return len(l) } 155 func (l Languages) Less(i, j int) bool { 156 wi, wj := l[i].Weight, l[j].Weight 157 158 if wi == wj { 159 return l[i].Lang < l[j].Lang 160 } 161 162 return wj == 0 || wi < wj 163 } 164 165 func (l Languages) Swap(i, j int) { l[i], l[j] = l[j], l[i] } 166 167 // Params returns language-specific params merged with the global params. 168 func (l *Language) Params() maps.Params { 169 // TODO(bep) this construct should not be needed. Create the 170 // language params in one go. 171 l.paramsMu.Lock() 172 defer l.paramsMu.Unlock() 173 if !l.paramsSet { 174 maps.PrepareParams(l.params) 175 l.paramsSet = true 176 } 177 return l.params 178 } 179 180 func (l Languages) AsSet() map[string]bool { 181 m := make(map[string]bool) 182 for _, lang := range l { 183 m[lang.Lang] = true 184 } 185 186 return m 187 } 188 189 func (l Languages) AsOrdinalSet() map[string]int { 190 m := make(map[string]int) 191 for i, lang := range l { 192 m[lang.Lang] = i 193 } 194 195 return m 196 } 197 198 // IsMultihost returns whether there are more than one language and at least one of 199 // the languages has baseURL specificed on the language level. 200 func (l Languages) IsMultihost() bool { 201 if len(l) <= 1 { 202 return false 203 } 204 205 for _, lang := range l { 206 if lang.GetLocal("baseURL") != nil { 207 return true 208 } 209 } 210 return false 211 } 212 213 // SetParam sets a param with the given key and value. 214 // SetParam is case-insensitive. 215 func (l *Language) SetParam(k string, v interface{}) { 216 l.paramsMu.Lock() 217 defer l.paramsMu.Unlock() 218 if l.paramsSet { 219 panic("params cannot be changed once set") 220 } 221 l.params[k] = v 222 } 223 224 // GetLocal gets a configuration value set on language level. It will 225 // not fall back to any global value. 226 // It will return nil if a value with the given key cannot be found. 227 func (l *Language) GetLocal(key string) interface{} { 228 if l == nil { 229 panic("language not set") 230 } 231 key = strings.ToLower(key) 232 if !globalOnlySettings[key] { 233 return l.LocalCfg.Get(key) 234 } 235 return nil 236 } 237 238 func (l *Language) Set(k string, v interface{}) { 239 k = strings.ToLower(k) 240 if globalOnlySettings[k] { 241 return 242 } 243 l.Provider.Set(k, v) 244 } 245 246 // Merge is currently not supported for Language. 247 func (l *Language) Merge(key string, value interface{}) { 248 panic("Not supported") 249 } 250 251 // IsSet checks whether the key is set in the language or the related config store. 252 func (l *Language) IsSet(key string) bool { 253 key = strings.ToLower(key) 254 if !globalOnlySettings[key] { 255 return l.Provider.IsSet(key) 256 } 257 return l.Cfg.IsSet(key) 258 } 259 260 // Internal access to unexported Language fields. 261 // This construct is to prevent them from leaking to the templates. 262 263 func GetTranslator(l *Language) locales.Translator { 264 return l.translator 265 } 266 267 func GetLocation(l *Language) *time.Location { 268 return l.location 269 } 270 271 func (l *Language) loadLocation(tzStr string) error { 272 location, err := time.LoadLocation(tzStr) 273 if err != nil { 274 return errors.Wrapf(err, "invalid timeZone for language %q", l.Lang) 275 } 276 l.location = location 277 278 return nil 279 }