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 }