mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
This commit is contained in:
227
langs/config.go
227
langs/config.go
@@ -14,213 +14,34 @@
|
||||
package langs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type LanguagesConfig struct {
|
||||
Languages Languages
|
||||
Multihost bool
|
||||
DefaultContentLanguageInSubdir bool
|
||||
// LanguageConfig holds the configuration for a single language.
|
||||
// This is what is read from the config file.
|
||||
type LanguageConfig struct {
|
||||
// The language name, e.g. "English".
|
||||
LanguageName string
|
||||
|
||||
// The language title. When set, this will
|
||||
// override site.Title for this language.
|
||||
Title string
|
||||
|
||||
// The language direction, e.g. "ltr" or "rtl".
|
||||
LanguageDirection string
|
||||
|
||||
// The language weight. When set to a non-zero value, this will
|
||||
// be the main sort criteria for the language.
|
||||
Weight int
|
||||
}
|
||||
|
||||
func LoadLanguageSettings(cfg config.Provider, oldLangs Languages) (c LanguagesConfig, err error) {
|
||||
defaultLang := strings.ToLower(cfg.GetString("defaultContentLanguage"))
|
||||
if defaultLang == "" {
|
||||
defaultLang = "en"
|
||||
cfg.Set("defaultContentLanguage", defaultLang)
|
||||
func DecodeConfig(m map[string]any) (map[string]LanguageConfig, error) {
|
||||
m = maps.CleanConfigStringMap(m)
|
||||
var langs map[string]LanguageConfig
|
||||
|
||||
if err := mapstructure.WeakDecode(m, &langs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var languages map[string]any
|
||||
|
||||
languagesFromConfig := cfg.GetParams("languages")
|
||||
disableLanguages := cfg.GetStringSlice("disableLanguages")
|
||||
|
||||
if len(disableLanguages) == 0 {
|
||||
languages = languagesFromConfig
|
||||
} else {
|
||||
languages = make(maps.Params)
|
||||
for k, v := range languagesFromConfig {
|
||||
for _, disabled := range disableLanguages {
|
||||
if disabled == defaultLang {
|
||||
return c, fmt.Errorf("cannot disable default language %q", defaultLang)
|
||||
}
|
||||
|
||||
if strings.EqualFold(k, disabled) {
|
||||
v.(maps.Params)["disabled"] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
languages[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
var languages2 Languages
|
||||
|
||||
if len(languages) == 0 {
|
||||
languages2 = append(languages2, NewDefaultLanguage(cfg))
|
||||
} else {
|
||||
languages2, err = toSortedLanguages(cfg, languages)
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("Failed to parse multilingual config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if oldLangs != nil {
|
||||
// When in multihost mode, the languages are mapped to a server, so
|
||||
// some structural language changes will need a restart of the dev server.
|
||||
// The validation below isn't complete, but should cover the most
|
||||
// important cases.
|
||||
var invalid bool
|
||||
if languages2.IsMultihost() != oldLangs.IsMultihost() {
|
||||
invalid = true
|
||||
} else {
|
||||
if languages2.IsMultihost() && len(languages2) != len(oldLangs) {
|
||||
invalid = true
|
||||
}
|
||||
}
|
||||
|
||||
if invalid {
|
||||
return c, errors.New("language change needing a server restart detected")
|
||||
}
|
||||
|
||||
if languages2.IsMultihost() {
|
||||
// We need to transfer any server baseURL to the new language
|
||||
for i, ol := range oldLangs {
|
||||
nl := languages2[i]
|
||||
nl.Set("baseURL", ol.GetString("baseURL"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The defaultContentLanguage is something the user has to decide, but it needs
|
||||
// to match a language in the language definition list.
|
||||
langExists := false
|
||||
for _, lang := range languages2 {
|
||||
if lang.Lang == defaultLang {
|
||||
langExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !langExists {
|
||||
return c, fmt.Errorf("site config value %q for defaultContentLanguage does not match any language definition", defaultLang)
|
||||
}
|
||||
|
||||
c.Languages = languages2
|
||||
c.Multihost = languages2.IsMultihost()
|
||||
c.DefaultContentLanguageInSubdir = c.Multihost
|
||||
|
||||
sortedDefaultFirst := make(Languages, len(c.Languages))
|
||||
for i, v := range c.Languages {
|
||||
sortedDefaultFirst[i] = v
|
||||
}
|
||||
sort.Slice(sortedDefaultFirst, func(i, j int) bool {
|
||||
li, lj := sortedDefaultFirst[i], sortedDefaultFirst[j]
|
||||
if li.Lang == defaultLang {
|
||||
return true
|
||||
}
|
||||
|
||||
if lj.Lang == defaultLang {
|
||||
return false
|
||||
}
|
||||
|
||||
return i < j
|
||||
})
|
||||
|
||||
cfg.Set("languagesSorted", c.Languages)
|
||||
cfg.Set("languagesSortedDefaultFirst", sortedDefaultFirst)
|
||||
cfg.Set("multilingual", len(languages2) > 1)
|
||||
|
||||
multihost := c.Multihost
|
||||
|
||||
if multihost {
|
||||
cfg.Set("defaultContentLanguageInSubdir", true)
|
||||
cfg.Set("multihost", true)
|
||||
}
|
||||
|
||||
if multihost {
|
||||
// The baseURL may be provided at the language level. If that is true,
|
||||
// then every language must have a baseURL. In this case we always render
|
||||
// to a language sub folder, which is then stripped from all the Permalink URLs etc.
|
||||
for _, l := range languages2 {
|
||||
burl := l.GetLocal("baseURL")
|
||||
if burl == nil {
|
||||
return c, errors.New("baseURL must be set on all or none of the languages")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, language := range c.Languages {
|
||||
if language.initErr != nil {
|
||||
return c, language.initErr
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func toSortedLanguages(cfg config.Provider, l map[string]any) (Languages, error) {
|
||||
languages := make(Languages, len(l))
|
||||
i := 0
|
||||
|
||||
for lang, langConf := range l {
|
||||
langsMap, err := maps.ToStringMapE(langConf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Language config is not a map: %T", langConf)
|
||||
}
|
||||
|
||||
language := NewLanguage(lang, cfg)
|
||||
|
||||
for loki, v := range langsMap {
|
||||
switch loki {
|
||||
case "title":
|
||||
language.Title = cast.ToString(v)
|
||||
case "languagename":
|
||||
language.LanguageName = cast.ToString(v)
|
||||
case "languagedirection":
|
||||
language.LanguageDirection = cast.ToString(v)
|
||||
case "weight":
|
||||
language.Weight = cast.ToInt(v)
|
||||
case "contentdir":
|
||||
language.ContentDir = filepath.Clean(cast.ToString(v))
|
||||
case "disabled":
|
||||
language.Disabled = cast.ToBool(v)
|
||||
case "params":
|
||||
m := maps.ToStringMap(v)
|
||||
// Needed for case insensitive fetching of params values
|
||||
maps.PrepareParams(m)
|
||||
for k, vv := range m {
|
||||
language.SetParam(k, vv)
|
||||
}
|
||||
case "timezone":
|
||||
if err := language.loadLocation(cast.ToString(v)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Put all into the Params map
|
||||
language.SetParam(loki, v)
|
||||
|
||||
// Also set it in the configuration map (for baseURL etc.)
|
||||
language.Set(loki, v)
|
||||
}
|
||||
|
||||
languages[i] = language
|
||||
i++
|
||||
}
|
||||
|
||||
sort.Sort(languages)
|
||||
|
||||
return languages, nil
|
||||
return langs, nil
|
||||
}
|
||||
|
@@ -37,12 +37,12 @@ var i18nWarningLogger = helpers.NewDistinctErrorLogger()
|
||||
// Translator handles i18n translations.
|
||||
type Translator struct {
|
||||
translateFuncs map[string]translateFunc
|
||||
cfg config.Provider
|
||||
cfg config.AllProvider
|
||||
logger loggers.Logger
|
||||
}
|
||||
|
||||
// NewTranslator creates a new Translator for the given language bundle and configuration.
|
||||
func NewTranslator(b *i18n.Bundle, cfg config.Provider, logger loggers.Logger) Translator {
|
||||
func NewTranslator(b *i18n.Bundle, cfg config.AllProvider, logger loggers.Logger) Translator {
|
||||
t := Translator{cfg: cfg, logger: logger, translateFuncs: make(map[string]translateFunc)}
|
||||
t.initFuncs(b)
|
||||
return t
|
||||
@@ -55,7 +55,7 @@ func (t Translator) Func(lang string) translateFunc {
|
||||
return f
|
||||
}
|
||||
t.logger.Infof("Translation func for language %v not found, use default.", lang)
|
||||
if f, ok := t.translateFuncs[t.cfg.GetString("defaultContentLanguage")]; ok {
|
||||
if f, ok := t.translateFuncs[t.cfg.DefaultContentLanguage()]; ok {
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (t Translator) Func(lang string) translateFunc {
|
||||
}
|
||||
|
||||
func (t Translator) initFuncs(bndl *i18n.Bundle) {
|
||||
enableMissingTranslationPlaceholders := t.cfg.GetBool("enableMissingTranslationPlaceholders")
|
||||
enableMissingTranslationPlaceholders := t.cfg.EnableMissingTranslationPlaceholders()
|
||||
for _, lang := range bndl.LanguageTags() {
|
||||
currentLang := lang
|
||||
currentLangStr := currentLang.String()
|
||||
@@ -122,7 +122,7 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
|
||||
t.logger.Warnf("Failed to get translated string for language %q and ID %q: %s", currentLangStr, translationID, err)
|
||||
}
|
||||
|
||||
if t.cfg.GetBool("logI18nWarnings") {
|
||||
if t.cfg.LogI18nWarnings() {
|
||||
i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID)
|
||||
}
|
||||
|
||||
|
@@ -20,13 +20,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/config/testconfig"
|
||||
|
||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -34,7 +32,6 @@ import (
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
)
|
||||
|
||||
var logger = loggers.NewErrorLogger()
|
||||
@@ -394,26 +391,22 @@ other = "{{ . }} miesiąca"
|
||||
} {
|
||||
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
cfg := getConfig()
|
||||
cfg := config.New()
|
||||
cfg.Set("enableMissingTranslationPlaceholders", true)
|
||||
fs := hugofs.NewMem(cfg)
|
||||
cfg.Set("publishDir", "public")
|
||||
afs := afero.NewMemMapFs()
|
||||
|
||||
err := afero.WriteFile(fs.Source, filepath.Join("i18n", test.lang+".toml"), []byte(test.templ), 0755)
|
||||
err := afero.WriteFile(afs, filepath.Join("i18n", test.lang+".toml"), []byte(test.templ), 0755)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
tp := NewTranslationProvider()
|
||||
depsCfg := newDepsConfig(tp, cfg, fs)
|
||||
depsCfg.Logger = loggers.NewWarningLogger()
|
||||
d, err := deps.New(depsCfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(d.LoadResources(), qt.IsNil)
|
||||
d, tp := prepareDeps(afs, cfg)
|
||||
|
||||
f := tp.t.Func(test.lang)
|
||||
ctx := context.Background()
|
||||
|
||||
for _, variant := range test.variants {
|
||||
c.Assert(f(ctx, test.id, variant.Key), qt.Equals, variant.Value, qt.Commentf("input: %v", variant.Key))
|
||||
c.Assert(int(depsCfg.Logger.LogCounters().WarnCounter.Count()), qt.Equals, 0)
|
||||
c.Assert(int(d.Log.LogCounters().WarnCounter.Count()), qt.Equals, 0)
|
||||
}
|
||||
|
||||
})
|
||||
@@ -471,52 +464,33 @@ func TestGetPluralCount(t *testing.T) {
|
||||
|
||||
func prepareTranslationProvider(t testing.TB, test i18nTest, cfg config.Provider) *TranslationProvider {
|
||||
c := qt.New(t)
|
||||
fs := hugofs.NewMem(cfg)
|
||||
afs := afero.NewMemMapFs()
|
||||
|
||||
for file, content := range test.data {
|
||||
err := afero.WriteFile(fs.Source, filepath.Join("i18n", file), []byte(content), 0755)
|
||||
err := afero.WriteFile(afs, filepath.Join("i18n", file), []byte(content), 0755)
|
||||
c.Assert(err, qt.IsNil)
|
||||
}
|
||||
|
||||
tp := NewTranslationProvider()
|
||||
depsCfg := newDepsConfig(tp, cfg, fs)
|
||||
d, err := deps.New(depsCfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(d.LoadResources(), qt.IsNil)
|
||||
|
||||
_, tp := prepareDeps(afs, cfg)
|
||||
return tp
|
||||
}
|
||||
|
||||
func newDepsConfig(tp *TranslationProvider, cfg config.Provider, fs *hugofs.Fs) deps.DepsCfg {
|
||||
l := langs.NewLanguage("en", cfg)
|
||||
l.Set("i18nDir", "i18n")
|
||||
return deps.DepsCfg{
|
||||
Language: l,
|
||||
Site: page.NewDummyHugoSite(cfg),
|
||||
Cfg: cfg,
|
||||
Fs: fs,
|
||||
Logger: logger,
|
||||
TemplateProvider: tplimpl.DefaultTemplateProvider,
|
||||
TranslationProvider: tp,
|
||||
}
|
||||
}
|
||||
|
||||
func getConfig() config.Provider {
|
||||
v := config.NewWithTestDefaults()
|
||||
langs.LoadLanguageSettings(v, nil)
|
||||
mod, err := modules.CreateProjectModule(v)
|
||||
if err != nil {
|
||||
func prepareDeps(afs afero.Fs, cfg config.Provider) (*deps.Deps, *TranslationProvider) {
|
||||
d := testconfig.GetTestDeps(afs, cfg)
|
||||
translationProvider := NewTranslationProvider()
|
||||
d.TemplateProvider = tplimpl.DefaultTemplateProvider
|
||||
d.TranslationProvider = translationProvider
|
||||
d.Site = page.NewDummyHugoSite(cfg)
|
||||
if err := d.Compile(nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.Set("allModules", modules.Modules{mod})
|
||||
|
||||
return v
|
||||
return d, translationProvider
|
||||
}
|
||||
|
||||
func TestI18nTranslate(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
var actual, expected string
|
||||
v := getConfig()
|
||||
v := config.New()
|
||||
|
||||
// Test without and with placeholders
|
||||
for _, enablePlaceholders := range []bool{false, true} {
|
||||
@@ -537,7 +511,7 @@ func TestI18nTranslate(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkI18nTranslate(b *testing.B) {
|
||||
v := getConfig()
|
||||
v := config.New()
|
||||
for _, test := range i18nTests {
|
||||
b.Run(test.name, func(b *testing.B) {
|
||||
tp := prepareTranslationProvider(b, test, v)
|
||||
|
@@ -45,10 +45,10 @@ func NewTranslationProvider() *TranslationProvider {
|
||||
}
|
||||
|
||||
// Update updates the i18n func in the provided Deps.
|
||||
func (tp *TranslationProvider) Update(d *deps.Deps) error {
|
||||
spec := source.NewSourceSpec(d.PathSpec, nil, nil)
|
||||
func (tp *TranslationProvider) NewResource(dst *deps.Deps) error {
|
||||
spec := source.NewSourceSpec(dst.PathSpec, nil, nil)
|
||||
|
||||
var defaultLangTag, err = language.Parse(d.Cfg.GetString("defaultContentLanguage"))
|
||||
var defaultLangTag, err = language.Parse(dst.Conf.DefaultContentLanguage())
|
||||
if err != nil {
|
||||
defaultLangTag = language.English
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func (tp *TranslationProvider) Update(d *deps.Deps) error {
|
||||
|
||||
// The source dirs are ordered so the most important comes first. Since this is a
|
||||
// last key win situation, we have to reverse the iteration order.
|
||||
dirs := d.BaseFs.I18n.Dirs
|
||||
dirs := dst.BaseFs.I18n.Dirs
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
dir := dirs[i]
|
||||
src := spec.NewFilesystemFromFileMetaInfo(dir)
|
||||
@@ -76,11 +76,12 @@ func (tp *TranslationProvider) Update(d *deps.Deps) error {
|
||||
}
|
||||
}
|
||||
|
||||
tp.t = NewTranslator(bundle, d.Cfg, d.Log)
|
||||
tp.t = NewTranslator(bundle, dst.Conf, dst.Log)
|
||||
|
||||
d.Translate = tp.t.Func(d.Language.Lang)
|
||||
dst.Translate = tp.t.Func(dst.Conf.Language().Lang)
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
const artificialLangTagPrefix = "art-x-"
|
||||
@@ -123,9 +124,8 @@ func addTranslationFile(bundle *i18n.Bundle, r source.File) error {
|
||||
}
|
||||
|
||||
// Clone sets the language func for the new language.
|
||||
func (tp *TranslationProvider) Clone(d *deps.Deps) error {
|
||||
d.Translate = tp.t.Func(d.Language.Lang)
|
||||
|
||||
func (tp *TranslationProvider) CloneResource(dst, src *deps.Deps) error {
|
||||
dst.Translate = tp.t.Func(dst.Conf.Language().Lang)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,8 +16,6 @@ package langs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -25,97 +23,32 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/locales"
|
||||
translators "github.com/gohugoio/localescompressed"
|
||||
)
|
||||
|
||||
// These are the settings that should only be looked up in the global Viper
|
||||
// config and not per language.
|
||||
// This list may not be complete, but contains only settings that we know
|
||||
// will be looked up in both.
|
||||
// This isn't perfect, but it is ultimately the user who shoots him/herself in
|
||||
// the foot.
|
||||
// See the pathSpec.
|
||||
var globalOnlySettings = map[string]bool{
|
||||
strings.ToLower("defaultContentLanguageInSubdir"): true,
|
||||
strings.ToLower("defaultContentLanguage"): true,
|
||||
strings.ToLower("multilingual"): true,
|
||||
strings.ToLower("assetDir"): true,
|
||||
strings.ToLower("resourceDir"): true,
|
||||
strings.ToLower("build"): true,
|
||||
}
|
||||
|
||||
// Language manages specific-language configuration.
|
||||
type Language struct {
|
||||
Lang string
|
||||
LanguageName string
|
||||
LanguageDirection string
|
||||
Title string
|
||||
Weight int
|
||||
// The language code, e.g. "en" or "no".
|
||||
// This is currently only settable as the key in the language map in the config.
|
||||
Lang string
|
||||
|
||||
// For internal use.
|
||||
Disabled bool
|
||||
|
||||
// If set per language, this tells Hugo that all content files without any
|
||||
// language indicator (e.g. my-page.en.md) is in this language.
|
||||
// This is usually a path relative to the working dir, but it can be an
|
||||
// absolute directory reference. It is what we get.
|
||||
// For internal use.
|
||||
ContentDir string
|
||||
|
||||
// Global config.
|
||||
// For internal use.
|
||||
Cfg config.Provider
|
||||
|
||||
// Language specific config.
|
||||
// For internal use.
|
||||
LocalCfg config.Provider
|
||||
|
||||
// Composite config.
|
||||
// For internal use.
|
||||
config.Provider
|
||||
|
||||
// These are params declared in the [params] section of the language merged with the
|
||||
// site's params, the most specific (language) wins on duplicate keys.
|
||||
params map[string]any
|
||||
paramsMu sync.Mutex
|
||||
paramsSet bool
|
||||
// Fields from the language config.
|
||||
LanguageConfig
|
||||
|
||||
// Used for date formatting etc. We don't want these exported to the
|
||||
// templates.
|
||||
// TODO(bep) do the same for some of the others.
|
||||
translator locales.Translator
|
||||
timeFormatter htime.TimeFormatter
|
||||
tag language.Tag
|
||||
collator *Collator
|
||||
location *time.Location
|
||||
|
||||
// Error during initialization. Will fail the build.
|
||||
initErr error
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
func (l *Language) String() string {
|
||||
return l.Lang
|
||||
}
|
||||
|
||||
// NewLanguage creates a new language.
|
||||
func NewLanguage(lang string, cfg config.Provider) *Language {
|
||||
// Note that language specific params will be overridden later.
|
||||
// We should improve that, but we need to make a copy:
|
||||
params := make(map[string]any)
|
||||
for k, v := range cfg.GetStringMap("params") {
|
||||
params[k] = v
|
||||
}
|
||||
maps.PrepareParams(params)
|
||||
|
||||
localCfg := config.New()
|
||||
compositeConfig := config.NewCompositeConfig(cfg, localCfg)
|
||||
func NewLanguage(lang, defaultContentLanguage, timeZone string, languageConfig LanguageConfig) (*Language, error) {
|
||||
translator := translators.GetTranslator(lang)
|
||||
if translator == nil {
|
||||
translator = translators.GetTranslator(cfg.GetString("defaultContentLanguage"))
|
||||
translator = translators.GetTranslator(defaultContentLanguage)
|
||||
if translator == nil {
|
||||
translator = translators.GetTranslator("en")
|
||||
}
|
||||
@@ -134,76 +67,31 @@ func NewLanguage(lang string, cfg config.Provider) *Language {
|
||||
}
|
||||
|
||||
l := &Language{
|
||||
Lang: lang,
|
||||
ContentDir: cfg.GetString("contentDir"),
|
||||
Cfg: cfg, LocalCfg: localCfg,
|
||||
Provider: compositeConfig,
|
||||
params: params,
|
||||
translator: translator,
|
||||
timeFormatter: htime.NewTimeFormatter(translator),
|
||||
tag: tag,
|
||||
collator: coll,
|
||||
Lang: lang,
|
||||
LanguageConfig: languageConfig,
|
||||
translator: translator,
|
||||
timeFormatter: htime.NewTimeFormatter(translator),
|
||||
tag: tag,
|
||||
collator: coll,
|
||||
}
|
||||
|
||||
if err := l.loadLocation(cfg.GetString("timeZone")); err != nil {
|
||||
l.initErr = err
|
||||
}
|
||||
return l, l.loadLocation(timeZone)
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// NewDefaultLanguage creates the default language for a config.Provider.
|
||||
// If not otherwise specified the default is "en".
|
||||
func NewDefaultLanguage(cfg config.Provider) *Language {
|
||||
defaultLang := cfg.GetString("defaultContentLanguage")
|
||||
|
||||
if defaultLang == "" {
|
||||
defaultLang = "en"
|
||||
func (l *Language) loadLocation(tzStr string) error {
|
||||
location, err := time.LoadLocation(tzStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid timeZone for language %q: %w", l.Lang, err)
|
||||
}
|
||||
l.location = location
|
||||
|
||||
return NewLanguage(defaultLang, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Languages is a sortable list of languages.
|
||||
type Languages []*Language
|
||||
|
||||
// NewLanguages creates a sorted list of languages.
|
||||
// NOTE: function is currently unused.
|
||||
func NewLanguages(l ...*Language) Languages {
|
||||
languages := make(Languages, len(l))
|
||||
for i := 0; i < len(l); i++ {
|
||||
languages[i] = l[i]
|
||||
}
|
||||
sort.Sort(languages)
|
||||
return languages
|
||||
}
|
||||
|
||||
func (l Languages) Len() int { return len(l) }
|
||||
func (l Languages) Less(i, j int) bool {
|
||||
wi, wj := l[i].Weight, l[j].Weight
|
||||
|
||||
if wi == wj {
|
||||
return l[i].Lang < l[j].Lang
|
||||
}
|
||||
|
||||
return wj == 0 || wi < wj
|
||||
}
|
||||
|
||||
func (l Languages) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
|
||||
// Params returns language-specific params merged with the global params.
|
||||
func (l *Language) Params() maps.Params {
|
||||
// TODO(bep) this construct should not be needed. Create the
|
||||
// language params in one go.
|
||||
l.paramsMu.Lock()
|
||||
defer l.paramsMu.Unlock()
|
||||
if !l.paramsSet {
|
||||
maps.PrepareParams(l.params)
|
||||
l.paramsSet = true
|
||||
}
|
||||
return l.params
|
||||
}
|
||||
|
||||
func (l Languages) AsSet() map[string]bool {
|
||||
m := make(map[string]bool)
|
||||
for _, lang := range l {
|
||||
@@ -222,73 +110,6 @@ func (l Languages) AsOrdinalSet() map[string]int {
|
||||
return m
|
||||
}
|
||||
|
||||
// IsMultihost returns whether there are more than one language and at least one of
|
||||
// the languages has baseURL specified on the language level.
|
||||
func (l Languages) IsMultihost() bool {
|
||||
if len(l) <= 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, lang := range l {
|
||||
if lang.GetLocal("baseURL") != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetParam sets a param with the given key and value.
|
||||
// SetParam is case-insensitive.
|
||||
// For internal use.
|
||||
func (l *Language) SetParam(k string, v any) {
|
||||
l.paramsMu.Lock()
|
||||
defer l.paramsMu.Unlock()
|
||||
if l.paramsSet {
|
||||
panic("params cannot be changed once set")
|
||||
}
|
||||
l.params[k] = v
|
||||
}
|
||||
|
||||
// GetLocal gets a configuration value set on language level. It will
|
||||
// not fall back to any global value.
|
||||
// It will return nil if a value with the given key cannot be found.
|
||||
// For internal use.
|
||||
func (l *Language) GetLocal(key string) any {
|
||||
if l == nil {
|
||||
panic("language not set")
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
if !globalOnlySettings[key] {
|
||||
return l.LocalCfg.Get(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
func (l *Language) Set(k string, v any) {
|
||||
k = strings.ToLower(k)
|
||||
if globalOnlySettings[k] {
|
||||
return
|
||||
}
|
||||
l.Provider.Set(k, v)
|
||||
}
|
||||
|
||||
// Merge is currently not supported for Language.
|
||||
// For internal use.
|
||||
func (l *Language) Merge(key string, value any) {
|
||||
panic("Not supported")
|
||||
}
|
||||
|
||||
// IsSet checks whether the key is set in the language or the related config store.
|
||||
// For internal use.
|
||||
func (l *Language) IsSet(key string) bool {
|
||||
key = strings.ToLower(key)
|
||||
if !globalOnlySettings[key] {
|
||||
return l.Provider.IsSet(key)
|
||||
}
|
||||
return l.Cfg.IsSet(key)
|
||||
}
|
||||
|
||||
// Internal access to unexported Language fields.
|
||||
// This construct is to prevent them from leaking to the templates.
|
||||
|
||||
@@ -308,16 +129,6 @@ func GetCollator(l *Language) *Collator {
|
||||
return l.collator
|
||||
}
|
||||
|
||||
func (l *Language) loadLocation(tzStr string) error {
|
||||
location, err := time.LoadLocation(tzStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid timeZone for language %q: %w", l.Lang, err)
|
||||
}
|
||||
l.location = location
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Collator struct {
|
||||
sync.Mutex
|
||||
c *collate.Collator
|
||||
|
@@ -18,39 +18,10 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func TestGetGlobalOnlySetting(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
v := config.NewWithTestDefaults()
|
||||
v.Set("defaultContentLanguageInSubdir", true)
|
||||
v.Set("contentDir", "content")
|
||||
v.Set("paginatePath", "page")
|
||||
lang := NewDefaultLanguage(v)
|
||||
lang.Set("defaultContentLanguageInSubdir", false)
|
||||
lang.Set("paginatePath", "side")
|
||||
|
||||
c.Assert(lang.GetBool("defaultContentLanguageInSubdir"), qt.Equals, true)
|
||||
c.Assert(lang.GetString("paginatePath"), qt.Equals, "side")
|
||||
}
|
||||
|
||||
func TestLanguageParams(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
v := config.NewWithTestDefaults()
|
||||
v.Set("p1", "p1cfg")
|
||||
v.Set("contentDir", "content")
|
||||
|
||||
lang := NewDefaultLanguage(v)
|
||||
lang.SetParam("p1", "p1p")
|
||||
|
||||
c.Assert(lang.Params()["p1"], qt.Equals, "p1p")
|
||||
c.Assert(lang.Get("p1"), qt.Equals, "p1cfg")
|
||||
}
|
||||
|
||||
func TestCollator(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
|
Reference in New Issue
Block a user