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 }