diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go index c343f6087a8..96e2c66b2ed 100644 --- a/hugolib/hugo_sites_build_test.go +++ b/hugolib/hugo_sites_build_test.go @@ -305,12 +305,12 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect) // check home page content (including data files rendering) - th.assertFileContent("public/en/index.html", "Home Page 1", "Hello", "Hugo Rocks!") - th.assertFileContent("public/fr/index.html", "Home Page 1", "Bonjour", "Hugo Rocks!") + th.assertFileContent("public/en/index.html", "Default Home Page 1", "Hello", "Hugo Rocks!") + th.assertFileContent("public/fr/index.html", "French Home Page 1", "Bonjour", "Hugo Rocks!") // check single page content - th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour") - th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello") + th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour", "LingoFrench") + th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello", "LingoDefault") // Check node translations homeEn := enSite.getPage(KindHome) @@ -1042,7 +1042,14 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf if err := afero.WriteFile(mf, filepath.Join("layouts", "index.html"), - []byte("{{ $p := .Paginator }}Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), + []byte("{{ $p := .Paginator }}Default Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), + 0755); err != nil { + t.Fatalf("Failed to write layout file: %s", err) + } + + if err := afero.WriteFile(mf, + filepath.Join("layouts", "index.fr.html"), + []byte("{{ $p := .Paginator }}French Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } @@ -1055,6 +1062,21 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf t.Fatalf("Failed to write layout file: %s", err) } + // A shortcode in multiple languages + if err := afero.WriteFile(mf, + filepath.Join("layouts", "shortcodes", "lingo.html"), + []byte("LingoDefault"), + 0755); err != nil { + t.Fatalf("Failed to write layout file: %s", err) + } + + if err := afero.WriteFile(mf, + filepath.Join("layouts", "shortcodes", "lingo.fr.html"), + []byte("LingoFrench"), + 0755); err != nil { + t.Fatalf("Failed to write layout file: %s", err) + } + // Add some language files if err := afero.WriteFile(mf, filepath.Join("i18n", "en.yaml"), @@ -1098,6 +1120,8 @@ publishdate: "2000-01-01" {{< shortcode >}} +{{< lingo >}} + NOTE: slug should be used as URL `)}, {Name: filepath.FromSlash("sect/doc1.fr.md"), Content: []byte(`--- @@ -1113,6 +1137,8 @@ publishdate: "2000-01-04" {{< shortcode >}} +{{< lingo >}} + NOTE: should be in the 'en' Page's 'Translations' field. NOTE: date is after "doc3" `)}, diff --git a/hugolib/page.go b/hugolib/page.go index cf0a2144c5a..0f44b8b99cf 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -250,6 +250,7 @@ func (p *Page) createLayoutDescriptor() output.LayoutDescriptor { return output.LayoutDescriptor{ Kind: p.Kind, Type: p.Type(), + Lang: p.Lang(), Layout: p.Layout, Section: section, } diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 150d82c444f..3cf472f82ce 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -157,6 +157,7 @@ func (sc shortcode) String() string { // Note that in the below, OutputFormat may be empty. // We will try to look for the most specific shortcode template available. type scKey struct { + Lang string OutputFormat string Suffix string ShortcodePlaceholder string @@ -166,8 +167,8 @@ func newScKey(m media.Type, shortcodeplaceholder string) scKey { return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder} } -func newScKeyFromOutputFormat(o output.Format, shortcodeplaceholder string) scKey { - return scKey{Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder} +func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey { + return scKey{Lang: lang, Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder} } func newDefaultScKey(shortcodeplaceholder string) scKey { @@ -251,10 +252,11 @@ const innerCleanupExpand = "$1" func prepareShortcodeForPage(placeholder string, sc shortcode, parent *ShortcodeWithPage, p *Page) map[scKey]func() (string, error) { m := make(map[scKey]func() (string, error)) + lang := p.Lang() for _, f := range p.outputFormats { // The most specific template will win. - key := newScKeyFromOutputFormat(f, placeholder) + key := newScKeyFromLangAndOutputFormat(lang, f, placeholder) m[key] = func() (string, error) { return renderShortcode(key, sc, nil, p), nil } @@ -371,9 +373,11 @@ func (s *shortcodeHandler) updateDelta() bool { func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map[scKey]func() (string, error) { contentShortcodesForOuputFormat := make(map[scKey]func() (string, error)) + lang := s.p.Lang() + for shortcodePlaceholder := range s.shortcodes { - key := newScKeyFromOutputFormat(f, shortcodePlaceholder) + key := newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder) renderFn, found := s.contentShortcodes[key] if !found { @@ -390,7 +394,7 @@ func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map if !found { panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder)) } - contentShortcodesForOuputFormat[newScKeyFromOutputFormat(f, shortcodePlaceholder)] = renderFn + contentShortcodesForOuputFormat[newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder)] = renderFn } return contentShortcodesForOuputFormat @@ -676,12 +680,19 @@ func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.T suffix := strings.ToLower(key.Suffix) outFormat := strings.ToLower(key.OutputFormat) + lang := strings.ToLower(key.Lang) if outFormat != "" && suffix != "" { + if lang != "" { + names = append(names, fmt.Sprintf("%s.%s.%s.%s", shortcodeName, lang, outFormat, suffix)) + } names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, outFormat, suffix)) } if suffix != "" { + if lang != "" { + names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, lang, suffix)) + } names = append(names, fmt.Sprintf("%s.%s", shortcodeName, suffix)) } diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 42da9e46549..3d355f947ee 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -837,8 +837,8 @@ func TestReplaceShortcodeTokens(t *testing.T) { func TestScKey(t *testing.T) { require.Equal(t, scKey{Suffix: "xml", ShortcodePlaceholder: "ABCD"}, newScKey(media.XMLType, "ABCD")) - require.Equal(t, scKey{Suffix: "html", OutputFormat: "AMP", ShortcodePlaceholder: "EFGH"}, - newScKeyFromOutputFormat(output.AMPFormat, "EFGH")) + require.Equal(t, scKey{Lang: "en", Suffix: "html", OutputFormat: "AMP", ShortcodePlaceholder: "EFGH"}, + newScKeyFromLangAndOutputFormat("en", output.AMPFormat, "EFGH")) require.Equal(t, scKey{Suffix: "html", ShortcodePlaceholder: "IJKL"}, newDefaultScKey("IJKL")) diff --git a/output/docshelper.go b/output/docshelper.go index 1df45726c64..c45e956e645 100644 --- a/output/docshelper.go +++ b/output/docshelper.go @@ -44,6 +44,7 @@ func createLayoutExamples() interface{} { f Format }{ {`AMP home, with theme "demoTheme".`, LayoutDescriptor{Kind: "home"}, true, "", AMPFormat}, + {`AMP home, French language".`, LayoutDescriptor{Kind: "home", Lang: "fr"}, false, "", AMPFormat}, {"JSON home, no theme.", LayoutDescriptor{Kind: "home"}, false, "", JSONFormat}, {fmt.Sprintf(`CSV regular, "layout: %s" in front matter.`, demoLayout), LayoutDescriptor{Kind: "page", Layout: demoLayout}, false, "", CSVFormat}, {fmt.Sprintf(`JSON regular, "type: %s" in front matter.`, demoType), LayoutDescriptor{Kind: "page", Type: demoType}, false, "", JSONFormat}, diff --git a/output/layout.go b/output/layout.go index cacb92b80d4..6c054b6c44a 100644 --- a/output/layout.go +++ b/output/layout.go @@ -26,6 +26,7 @@ type LayoutDescriptor struct { Type string Section string Kind string + Lang string Layout string } @@ -55,31 +56,33 @@ func NewLayoutHandler(hasTheme bool) *LayoutHandler { const ( + // TODO(bep) variations reduce to 1 "." + // The RSS templates doesn't map easily into the regular pages. - layoutsRSSHome = `NAME.SUFFIX _default/NAME.SUFFIX _internal/_default/rss.xml` - layoutsRSSSection = `section/SECTION.NAME.SUFFIX _default/NAME.SUFFIX NAME.SUFFIX _internal/_default/rss.xml` - layoutsRSSTaxonomy = `taxonomy/SECTION.NAME.SUFFIX _default/NAME.SUFFIX NAME.SUFFIX _internal/_default/rss.xml` - layoutsRSSTaxonomyTerm = `taxonomy/SECTION.terms.NAME.SUFFIX _default/NAME.SUFFIX NAME.SUFFIX _internal/_default/rss.xml` + layoutsRSSHome = `VARIATIONS _default/VARIATIONS _internal/_default/rss.xml` + layoutsRSSSection = `section/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml` + layoutsRSSTaxonomy = `taxonomy/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml` + layoutsRSSTaxonomyTerm = `taxonomy/SECTION.terms.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml` - layoutsHome = "index.NAME.SUFFIX index.SUFFIX _default/list.NAME.SUFFIX _default/list.SUFFIX" + layoutsHome = "index.VARIATIONS _default/list.VARIATIONS" layoutsSection = ` -section/SECTION.NAME.SUFFIX section/SECTION.SUFFIX -SECTION/list.NAME.SUFFIX SECTION/list.SUFFIX -_default/section.NAME.SUFFIX _default/section.SUFFIX -_default/list.NAME.SUFFIX _default/list.SUFFIX -indexes/SECTION.NAME.SUFFIX indexes/SECTION.SUFFIX -_default/indexes.NAME.SUFFIX _default/indexes.SUFFIX +section/SECTION.VARIATIONS +SECTION/list.VARIATIONS +_default/section.VARIATIONS +_default/list.VARIATIONS +indexes/SECTION.VARIATIONS +_default/indexes.VARIATIONS ` layoutsTaxonomy = ` -taxonomy/SECTION.NAME.SUFFIX taxonomy/SECTION.SUFFIX -indexes/SECTION.NAME.SUFFIX indexes/SECTION.SUFFIX -_default/taxonomy.NAME.SUFFIX _default/taxonomy.SUFFIX -_default/list.NAME.SUFFIX _default/list.SUFFIX +taxonomy/SECTION.VARIATIONS +indexes/SECTION.VARIATIONS +_default/taxonomy.VARIATIONS +_default/list.VARIATIONS ` layoutsTaxonomyTerm = ` -taxonomy/SECTION.terms.NAME.SUFFIX taxonomy/SECTION.terms.SUFFIX -_default/terms.NAME.SUFFIX _default/terms.SUFFIX -indexes/indexes.NAME.SUFFIX indexes/indexes.SUFFIX +taxonomy/SECTION.terms.VARIATIONS +_default/terms.VARIATIONS +indexes/indexes.VARIATIONS ` ) @@ -185,14 +188,41 @@ func resolveListTemplate(d LayoutDescriptor, f Format, } func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string { - delim := "." - if f.MediaType.Delimiter == "" { - delim = "" + + // VARIATIONS will be replaced with + // .lang.name.suffix + // .name.suffix + // .lang.suffix + // .suffix + var replacementValues []string + + name := strings.ToLower(f.Name) + + if d.Lang != "" { + replacementValues = append(replacementValues, fmt.Sprintf("%s.%s.%s", d.Lang, name, f.MediaType.Suffix)) + } + + replacementValues = append(replacementValues, fmt.Sprintf("%s.%s", name, f.MediaType.Suffix)) + + if d.Lang != "" { + replacementValues = append(replacementValues, fmt.Sprintf("%s.%s", d.Lang, f.MediaType.Suffix)) + } + + isRSS := f.Name == RSSFormat.Name + + if !isRSS { + replacementValues = append(replacementValues, f.MediaType.Suffix) + } + + var layouts []string + + templFields := strings.Fields(templ) + + for _, field := range templFields { + for _, replacements := range replacementValues { + layouts = append(layouts, replaceKeyValues(field, "VARIATIONS", replacements, "SECTION", d.Section)) + } } - layouts := strings.Fields(replaceKeyValues(templ, - ".SUFFIX", delim+f.MediaType.Suffix, - "NAME", strings.ToLower(f.Name), - "SECTION", d.Section)) return filterDotLess(layouts) } @@ -201,9 +231,7 @@ func filterDotLess(layouts []string) []string { var filteredLayouts []string for _, l := range layouts { - // This may be constructed, but media types can be suffix-less, but can contain - // a delimiter. - l = strings.TrimSuffix(l, ".") + l = strings.Trim(l, ".") // If media type has no suffix, we have "index" type of layouts in this list, which // doesn't make much sense. if strings.Contains(l, ".") { diff --git a/output/layout_test.go b/output/layout_test.go index 9d4d2f6d5f0..6fb958c9dfe 100644 --- a/output/layout_test.go +++ b/output/layout_test.go @@ -59,6 +59,8 @@ func TestLayout(t *testing.T) { }{ {"Home", LayoutDescriptor{Kind: "home"}, true, "", ampType, []string{"index.amp.html", "index.html", "_default/list.amp.html", "_default/list.html", "theme/index.amp.html", "theme/index.html"}}, + {"Home, french language", LayoutDescriptor{Kind: "home", Lang: "fr"}, true, "", ampType, + []string{"index.fr.amp.html", "index.amp.html", "index.fr.html", "index.html", "_default/list.fr.amp.html", "_default/list.amp.html", "_default/list.fr.html", "_default/list.html", "theme/index.fr.amp.html", "theme/index.amp.html", "theme/index.fr.html"}}, {"Home, no ext or delim", LayoutDescriptor{Kind: "home"}, true, "", noExtDelimFormat, []string{"index.nem", "_default/list.nem"}}, {"Home, no ext", LayoutDescriptor{Kind: "home"}, true, "", noExt,