language_content_dir_test.go (13536B)
1 // Copyright 2019 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 hugolib
15
16 import (
17 "fmt"
18 "os"
19 "path/filepath"
20 "testing"
21
22 "github.com/spf13/cast"
23
24 "github.com/gohugoio/hugo/resources/page"
25
26 qt "github.com/frankban/quicktest"
27 )
28
29 /*
30
31 /en/p1.md
32 /nn/p1.md
33
34 .Readdir
35
36 - Name() => p1.en.md, p1.nn.md
37
38 .Stat(name)
39
40 .Open() --- real file name
41
42
43 */
44
45 func TestLanguageContentRoot(t *testing.T) {
46 t.Parallel()
47 c := qt.New(t)
48
49 config := `
50 baseURL = "https://example.org/"
51
52 defaultContentLanguage = "en"
53 defaultContentLanguageInSubdir = true
54
55 contentDir = "content/main"
56 workingDir = "/my/project"
57
58 [Languages]
59 [Languages.en]
60 weight = 10
61 title = "In English"
62 languageName = "English"
63
64 [Languages.nn]
65 weight = 20
66 title = "På Norsk"
67 languageName = "Norsk"
68 # This tells Hugo that all content in this directory is in the Norwegian language.
69 # It does not have to have the "my-page.nn.md" format. It can, but that is optional.
70 contentDir = "content/norsk"
71
72 [Languages.sv]
73 weight = 30
74 title = "På Svenska"
75 languageName = "Svensk"
76 contentDir = "content/svensk"
77 `
78
79 pageTemplate := `
80 ---
81 title: %s
82 slug: %s
83 weight: %d
84 ---
85
86 Content.
87
88 SVP3-REF: {{< ref path="/sect/page3.md" lang="sv" >}}
89 SVP3-RELREF: {{< relref path="/sect/page3.md" lang="sv" >}}
90
91 `
92
93 pageBundleTemplate := `
94 ---
95 title: %s
96 weight: %d
97 ---
98
99 Content.
100
101 `
102 var contentFiles []string
103 section := "sect"
104
105 contentRoot := func(lang string) string {
106 switch lang {
107 case "nn":
108 return "content/norsk"
109 case "sv":
110 return "content/svensk"
111 default:
112 return "content/main"
113 }
114 }
115
116 contentSectionRoot := func(lang string) string {
117 return contentRoot(lang) + "/" + section
118 }
119
120 for _, lang := range []string{"en", "nn", "sv"} {
121 for j := 1; j <= 10; j++ {
122 if (lang == "nn" || lang == "en") && j%4 == 0 {
123 // Skip 4 and 8 for nn
124 // We also skip it for en, but that is added to the Swedish directory below.
125 continue
126 }
127
128 if lang == "sv" && j%5 == 0 {
129 // Skip 5 and 10 for sv
130 continue
131 }
132
133 base := fmt.Sprintf("p-%s-%d", lang, j)
134 slug := base
135 langID := ""
136
137 if lang == "sv" && j%4 == 0 {
138 // Put an English page in the Swedish content dir.
139 langID = ".en"
140 }
141
142 if lang == "en" && j == 8 {
143 // This should win over the sv variant above.
144 langID = ".en"
145 }
146
147 slug += langID
148
149 contentRoot := contentSectionRoot(lang)
150
151 filename := filepath.Join(contentRoot, fmt.Sprintf("page%d%s.md", j, langID))
152 contentFiles = append(contentFiles, filename, fmt.Sprintf(pageTemplate, slug, slug, j))
153 }
154 }
155
156 // Put common translations in all of them
157 for i, lang := range []string{"en", "nn", "sv"} {
158 contentRoot := contentSectionRoot(lang)
159
160 slug := fmt.Sprintf("common_%s", lang)
161
162 filename := filepath.Join(contentRoot, "common.md")
163 contentFiles = append(contentFiles, filename, fmt.Sprintf(pageTemplate, slug, slug, 100+i))
164
165 for j, lang2 := range []string{"en", "nn", "sv"} {
166 filename := filepath.Join(contentRoot, fmt.Sprintf("translated_all.%s.md", lang2))
167 langSlug := slug + "_translated_all_" + lang2
168 contentFiles = append(contentFiles, filename, fmt.Sprintf(pageTemplate, langSlug, langSlug, 200+i+j))
169 }
170
171 for j, lang2 := range []string{"sv", "nn"} {
172 if lang == "en" {
173 continue
174 }
175 filename := filepath.Join(contentRoot, fmt.Sprintf("translated_some.%s.md", lang2))
176 langSlug := slug + "_translated_some_" + lang2
177 contentFiles = append(contentFiles, filename, fmt.Sprintf(pageTemplate, langSlug, langSlug, 300+i+j))
178 }
179 }
180
181 // Add a bundle with some images
182 for i, lang := range []string{"en", "nn", "sv"} {
183 contentRoot := contentSectionRoot(lang)
184 slug := fmt.Sprintf("bundle_%s", lang)
185 filename := filepath.Join(contentRoot, "mybundle", "index.md")
186 contentFiles = append(contentFiles, filename, fmt.Sprintf(pageBundleTemplate, slug, 400+i))
187 if lang == "en" {
188 imageFilename := filepath.Join(contentRoot, "mybundle", "logo.png")
189 contentFiles = append(contentFiles, imageFilename, "PNG Data")
190 }
191 imageFilename := filepath.Join(contentRoot, "mybundle", "featured.png")
192 contentFiles = append(contentFiles, imageFilename, fmt.Sprintf("PNG Data for %s", lang))
193
194 // Add some bundled pages
195 contentFiles = append(contentFiles, filepath.Join(contentRoot, "mybundle", "p1.md"), fmt.Sprintf(pageBundleTemplate, slug, 401+i))
196 contentFiles = append(contentFiles, filepath.Join(contentRoot, "mybundle", "sub", "p1.md"), fmt.Sprintf(pageBundleTemplate, slug, 402+i))
197
198 }
199
200 // Add some static files inside the content dir
201 // https://github.com/gohugoio/hugo/issues/5759
202 for _, lang := range []string{"en", "nn", "sv"} {
203 contentRoot := contentRoot(lang)
204 for i := 0; i < 2; i++ {
205 filename := filepath.Join(contentRoot, "mystatic", fmt.Sprintf("file%d.yaml", i))
206 contentFiles = append(contentFiles, filename, lang)
207 }
208 }
209
210 b := newTestSitesBuilder(t)
211 b.WithWorkingDir("/my/project").WithConfigFile("toml", config).WithContent(contentFiles...).CreateSites()
212
213 _ = os.Stdout
214
215 err := b.BuildE(BuildCfg{})
216
217 // dumpPages(b.H.Sites[1].RegularPages()...)
218
219 c.Assert(err, qt.IsNil)
220
221 c.Assert(len(b.H.Sites), qt.Equals, 3)
222
223 enSite := b.H.Sites[0]
224 nnSite := b.H.Sites[1]
225 svSite := b.H.Sites[2]
226
227 b.AssertFileContent("public/en/mystatic/file1.yaml", "en")
228 b.AssertFileContent("public/nn/mystatic/file1.yaml", "nn")
229
230 // dumpPages(nnSite.RegularPages()...)
231
232 c.Assert(len(nnSite.RegularPages()), qt.Equals, 12)
233 c.Assert(len(enSite.RegularPages()), qt.Equals, 13)
234
235 c.Assert(len(svSite.RegularPages()), qt.Equals, 10)
236
237 svP2, err := svSite.getPageNew(nil, "/sect/page2.md")
238 c.Assert(err, qt.IsNil)
239 nnP2, err := nnSite.getPageNew(nil, "/sect/page2.md")
240 c.Assert(err, qt.IsNil)
241
242 enP2, err := enSite.getPageNew(nil, "/sect/page2.md")
243 c.Assert(err, qt.IsNil)
244 c.Assert(enP2.Language().Lang, qt.Equals, "en")
245 c.Assert(svP2.Language().Lang, qt.Equals, "sv")
246 c.Assert(nnP2.Language().Lang, qt.Equals, "nn")
247
248 content, _ := nnP2.Content()
249 contentStr := cast.ToString(content)
250 c.Assert(contentStr, qt.Contains, "SVP3-REF: https://example.org/sv/sect/p-sv-3/")
251 c.Assert(contentStr, qt.Contains, "SVP3-RELREF: /sv/sect/p-sv-3/")
252
253 // Test RelRef with and without language indicator.
254 nn3RefArgs := map[string]any{
255 "path": "/sect/page3.md",
256 "lang": "nn",
257 }
258 nnP3RelRef, err := svP2.RelRef(
259 nn3RefArgs,
260 )
261 c.Assert(err, qt.IsNil)
262 c.Assert(nnP3RelRef, qt.Equals, "/nn/sect/p-nn-3/")
263 nnP3Ref, err := svP2.Ref(
264 nn3RefArgs,
265 )
266 c.Assert(err, qt.IsNil)
267 c.Assert(nnP3Ref, qt.Equals, "https://example.org/nn/sect/p-nn-3/")
268
269 for i, p := range enSite.RegularPages() {
270 j := i + 1
271 c.Assert(p.Language().Lang, qt.Equals, "en")
272 c.Assert(p.Section(), qt.Equals, "sect")
273 if j < 9 {
274 if j%4 == 0 {
275 } else {
276 c.Assert(p.Title(), qt.Contains, "p-en")
277 }
278 }
279 }
280
281 for _, p := range nnSite.RegularPages() {
282 c.Assert(p.Language().Lang, qt.Equals, "nn")
283 c.Assert(p.Title(), qt.Contains, "nn")
284 }
285
286 for _, p := range svSite.RegularPages() {
287 c.Assert(p.Language().Lang, qt.Equals, "sv")
288 c.Assert(p.Title(), qt.Contains, "sv")
289 }
290
291 // Check bundles
292 bundleEn := enSite.RegularPages()[len(enSite.RegularPages())-1]
293 bundleNn := nnSite.RegularPages()[len(nnSite.RegularPages())-1]
294 bundleSv := svSite.RegularPages()[len(svSite.RegularPages())-1]
295
296 c.Assert(bundleEn.RelPermalink(), qt.Equals, "/en/sect/mybundle/")
297 c.Assert(bundleSv.RelPermalink(), qt.Equals, "/sv/sect/mybundle/")
298
299 c.Assert(len(bundleNn.Resources()), qt.Equals, 4)
300 c.Assert(len(bundleSv.Resources()), qt.Equals, 4)
301 c.Assert(len(bundleEn.Resources()), qt.Equals, 4)
302
303 b.AssertFileContent("public/en/sect/mybundle/index.html", "image/png: /en/sect/mybundle/logo.png")
304 b.AssertFileContent("public/nn/sect/mybundle/index.html", "image/png: /nn/sect/mybundle/logo.png")
305 b.AssertFileContent("public/sv/sect/mybundle/index.html", "image/png: /sv/sect/mybundle/logo.png")
306
307 b.AssertFileContent("public/sv/sect/mybundle/featured.png", "PNG Data for sv")
308 b.AssertFileContent("public/nn/sect/mybundle/featured.png", "PNG Data for nn")
309 b.AssertFileContent("public/en/sect/mybundle/featured.png", "PNG Data for en")
310 b.AssertFileContent("public/en/sect/mybundle/logo.png", "PNG Data")
311 b.AssertFileContent("public/sv/sect/mybundle/logo.png", "PNG Data")
312 b.AssertFileContent("public/nn/sect/mybundle/logo.png", "PNG Data")
313
314 nnSect := nnSite.getPage(page.KindSection, "sect")
315 c.Assert(nnSect, qt.Not(qt.IsNil))
316 c.Assert(len(nnSect.Pages()), qt.Equals, 12)
317 nnHome := nnSite.Info.Home()
318 c.Assert(nnHome.RelPermalink(), qt.Equals, "/nn/")
319 }
320
321 // https://github.com/gohugoio/hugo/issues/6463
322 func TestLanguageRootSectionsMismatch(t *testing.T) {
323 t.Parallel()
324
325 config := `
326 baseURL: "https://example.org/"
327 languageCode: "en-us"
328 title: "My New Hugo Site"
329 theme: "mytheme"
330
331 contentDir: "content/en"
332
333 languages:
334 en:
335 weight: 1
336 languageName: "English"
337 contentDir: content/en
338 es:
339 weight: 2
340 languageName: "Español"
341 contentDir: content/es
342 fr:
343 weight: 4
344 languageName: "Française"
345 contentDir: content/fr
346
347
348 `
349 createPage := func(title string) string {
350 return fmt.Sprintf(`---
351 title: %q
352 ---
353
354 `, title)
355 }
356
357 b := newTestSitesBuilder(t)
358 b.WithConfigFile("yaml", config)
359
360 b.WithSourceFile("themes/mytheme/layouts/index.html", `MYTHEME`)
361 b.WithTemplates("index.html", `
362 Lang: {{ .Lang }}
363 {{ range .Site.RegularPages }}
364 Page: {{ .RelPermalink }}|{{ .Title -}}
365 {{ end }}
366
367 `)
368 b.WithSourceFile("static/hello.txt", `hello`)
369 b.WithContent("en/_index.md", createPage("en home"))
370 b.WithContent("es/_index.md", createPage("es home"))
371 b.WithContent("fr/_index.md", createPage("fr home"))
372
373 for i := 1; i < 3; i++ {
374 b.WithContent(fmt.Sprintf("en/event/page%d.md", i), createPage(fmt.Sprintf("ev-en%d", i)))
375 b.WithContent(fmt.Sprintf("es/event/page%d.md", i), createPage(fmt.Sprintf("ev-es%d", i)))
376 b.WithContent(fmt.Sprintf("fr/event/page%d.md", i), createPage(fmt.Sprintf("ev-fr%d", i)))
377 b.WithContent(fmt.Sprintf("en/blog/page%d.md", i), createPage(fmt.Sprintf("blog-en%d", i)))
378 b.WithContent(fmt.Sprintf("es/blog/page%d.md", i), createPage(fmt.Sprintf("blog-es%d", i)))
379 b.WithContent(fmt.Sprintf("fr/other/page%d.md", i), createPage(fmt.Sprintf("other-fr%d", i)))
380 }
381
382 b.Build(BuildCfg{})
383
384 b.AssertFileContent("public/index.html", `
385 Lang: en
386 Page: /blog/page1/|blog-en1
387 Page: /blog/page2/|blog-en2
388 Page: /event/page1/|ev-en1
389 Page: /event/page2/|ev-en2
390 `)
391
392 b.AssertFileContent("public/es/index.html", `
393 Lang: es
394 Page: /es/blog/page1/|blog-es1
395 Page: /es/blog/page2/|blog-es2
396 Page: /es/event/page1/|ev-es1
397 Page: /es/event/page2/|ev-es2
398 `)
399 b.AssertFileContent("public/fr/index.html", `
400 Lang: fr
401 Page: /fr/event/page1/|ev-fr1
402 Page: /fr/event/page2/|ev-fr2
403 Page: /fr/other/page1/|other-fr1
404 Page: /fr/other/page2/|other-fr2`)
405 }
406
407 // Issue 9693
408 func TestContentMountMerge(t *testing.T) {
409 t.Parallel()
410
411 files := `
412 -- config.toml --
413 baseURL = 'https://example.org/'
414 languageCode = 'en-us'
415 title = 'Hugo Forum Topic #37225'
416 theme = 'mytheme'
417
418 disableKinds = ['sitemap','RSS','taxonomy','term']
419 defaultContentLanguage = 'en'
420 defaultContentLanguageInSubdir = true
421
422 [languages.en]
423 languageName = 'English'
424 weight = 1
425 [languages.de]
426 languageName = 'Deutsch'
427 weight = 2
428 [languages.nl]
429 languageName = 'Nederlands'
430 weight = 3
431
432 # EN content
433 [[module.mounts]]
434 source = 'content/en'
435 target = 'content'
436 lang = 'en'
437
438 # DE content
439 [[module.mounts]]
440 source = 'content/de'
441 target = 'content'
442 lang = 'de'
443
444 # This fills in the gaps in DE content with EN content
445 [[module.mounts]]
446 source = 'content/en'
447 target = 'content'
448 lang = 'de'
449
450 # NL content
451 [[module.mounts]]
452 source = 'content/nl'
453 target = 'content'
454 lang = 'nl'
455
456 # This should fill in the gaps in NL content with EN content
457 [[module.mounts]]
458 source = 'content/en'
459 target = 'content'
460 lang = 'nl'
461
462 -- content/de/_index.md --
463 ---
464 title: "home (de)"
465 ---
466 -- content/de/p1.md --
467 ---
468 title: "p1 (de)"
469 ---
470 -- content/en/_index.md --
471 ---
472 title: "home (en)"
473 ---
474 -- content/en/p1.md --
475 ---
476 title: "p1 (en)"
477 ---
478 -- content/en/p2.md --
479 ---
480 title: "p2 (en)"
481 ---
482 -- content/en/p3.md --
483 ---
484 title: "p3 (en)"
485 ---
486 -- content/nl/_index.md --
487 ---
488 title: "home (nl)"
489 ---
490 -- content/nl/p1.md --
491 ---
492 title: "p1 (nl)"
493 ---
494 -- content/nl/p3.md --
495 ---
496 title: "p3 (nl)"
497 ---
498 -- layouts/home.html --
499 {{ .Title }}: {{ site.Language.Lang }}: {{ range site.RegularPages }}{{ .Title }}|{{ end }}:END
500 -- themes/mytheme/config.toml --
501 [[module.mounts]]
502 source = 'content/nlt'
503 target = 'content'
504 lang = 'nl'
505 -- themes/mytheme/content/nlt/p3.md --
506 ---
507 title: "p3 theme (nl)"
508 ---
509 -- themes/mytheme/content/nlt/p4.md --
510 ---
511 title: "p4 theme (nl)"
512 ---
513 `
514
515 b := NewIntegrationTestBuilder(
516 IntegrationTestConfig{
517 T: t,
518 TxtarString: files,
519 },
520 ).Build()
521
522 b.AssertFileContent("public/nl/index.html", `home (nl): nl: p1 (nl)|p2 (en)|p3 (nl)|p4 theme (nl)|:END`)
523 b.AssertFileContent("public/de/index.html", `home (de): de: p1 (de)|p2 (en)|p3 (en)|:END`)
524 b.AssertFileContent("public/en/index.html", `home (en): en: p1 (en)|p2 (en)|p3 (en)|:END`)
525
526 }