hugo

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

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

language.go (8496B)

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