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  }