Skip to content

Commit

Permalink
output: Support templates per site/language
Browse files Browse the repository at this point in the history
This applies to both regular templates and shortcodes. So, if the site language is French and the output format is AMP, this is the (start) of the lookup order for the home page:

1. index.fr.amp.html
2. index.amp.html
3. index.fr.html
4. index.html
5. ...

Fixes #3360
  • Loading branch information
bep committed Jul 4, 2017
1 parent a1d260b commit aa6b1b9
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 40 deletions.
36 changes: 31 additions & 5 deletions hugolib/hugo_sites_build_test.go
Expand Up @@ -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) require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect)


// check home page content (including data files rendering) // check home page content (including data files rendering)
th.assertFileContent("public/en/index.html", "Home Page 1", "Hello", "Hugo Rocks!") th.assertFileContent("public/en/index.html", "Default Home Page 1", "Hello", "Hugo Rocks!")
th.assertFileContent("public/fr/index.html", "Home Page 1", "Bonjour", "Hugo Rocks!") th.assertFileContent("public/fr/index.html", "French Home Page 1", "Bonjour", "Hugo Rocks!")


// check single page content // check single page content
th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour") th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour", "LingoFrench")
th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello") th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello", "LingoDefault")


// Check node translations // Check node translations
homeEn := enSite.getPage(KindHome) homeEn := enSite.getPage(KindHome)
Expand Down Expand Up @@ -1042,7 +1042,14 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf


if err := afero.WriteFile(mf, if err := afero.WriteFile(mf,
filepath.Join("layouts", "index.html"), 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 { 0755); err != nil {
t.Fatalf("Failed to write layout file: %s", err) t.Fatalf("Failed to write layout file: %s", err)
} }
Expand All @@ -1055,6 +1062,21 @@ func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, conf
t.Fatalf("Failed to write layout file: %s", err) 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 // Add some language files
if err := afero.WriteFile(mf, if err := afero.WriteFile(mf,
filepath.Join("i18n", "en.yaml"), filepath.Join("i18n", "en.yaml"),
Expand Down Expand Up @@ -1098,6 +1120,8 @@ publishdate: "2000-01-01"
{{< shortcode >}} {{< shortcode >}}
{{< lingo >}}
NOTE: slug should be used as URL NOTE: slug should be used as URL
`)}, `)},
{Name: filepath.FromSlash("sect/doc1.fr.md"), Content: []byte(`--- {Name: filepath.FromSlash("sect/doc1.fr.md"), Content: []byte(`---
Expand All @@ -1113,6 +1137,8 @@ publishdate: "2000-01-04"
{{< shortcode >}} {{< shortcode >}}
{{< lingo >}}
NOTE: should be in the 'en' Page's 'Translations' field. NOTE: should be in the 'en' Page's 'Translations' field.
NOTE: date is after "doc3" NOTE: date is after "doc3"
`)}, `)},
Expand Down
1 change: 1 addition & 0 deletions hugolib/page.go
Expand Up @@ -250,6 +250,7 @@ func (p *Page) createLayoutDescriptor() output.LayoutDescriptor {
return output.LayoutDescriptor{ return output.LayoutDescriptor{
Kind: p.Kind, Kind: p.Kind,
Type: p.Type(), Type: p.Type(),
Lang: p.Lang(),
Layout: p.Layout, Layout: p.Layout,
Section: section, Section: section,
} }
Expand Down
21 changes: 16 additions & 5 deletions hugolib/shortcode.go
Expand Up @@ -157,6 +157,7 @@ func (sc shortcode) String() string {
// Note that in the below, OutputFormat may be empty. // Note that in the below, OutputFormat may be empty.
// We will try to look for the most specific shortcode template available. // We will try to look for the most specific shortcode template available.
type scKey struct { type scKey struct {
Lang string
OutputFormat string OutputFormat string
Suffix string Suffix string
ShortcodePlaceholder string ShortcodePlaceholder string
Expand All @@ -166,8 +167,8 @@ func newScKey(m media.Type, shortcodeplaceholder string) scKey {
return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder} return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder}
} }


func newScKeyFromOutputFormat(o output.Format, shortcodeplaceholder string) scKey { func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey {
return scKey{Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder} return scKey{Lang: lang, Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder}
} }


func newDefaultScKey(shortcodeplaceholder string) scKey { func newDefaultScKey(shortcodeplaceholder string) scKey {
Expand Down Expand Up @@ -251,10 +252,11 @@ const innerCleanupExpand = "$1"
func prepareShortcodeForPage(placeholder string, sc shortcode, parent *ShortcodeWithPage, p *Page) map[scKey]func() (string, error) { func prepareShortcodeForPage(placeholder string, sc shortcode, parent *ShortcodeWithPage, p *Page) map[scKey]func() (string, error) {


m := make(map[scKey]func() (string, error)) m := make(map[scKey]func() (string, error))
lang := p.Lang()


for _, f := range p.outputFormats { for _, f := range p.outputFormats {
// The most specific template will win. // The most specific template will win.
key := newScKeyFromOutputFormat(f, placeholder) key := newScKeyFromLangAndOutputFormat(lang, f, placeholder)
m[key] = func() (string, error) { m[key] = func() (string, error) {
return renderShortcode(key, sc, nil, p), nil return renderShortcode(key, sc, nil, p), nil
} }
Expand Down Expand Up @@ -371,9 +373,11 @@ func (s *shortcodeHandler) updateDelta() bool {


func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map[scKey]func() (string, error) { func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map[scKey]func() (string, error) {
contentShortcodesForOuputFormat := make(map[scKey]func() (string, error)) contentShortcodesForOuputFormat := make(map[scKey]func() (string, error))
lang := s.p.Lang()

for shortcodePlaceholder := range s.shortcodes { for shortcodePlaceholder := range s.shortcodes {


key := newScKeyFromOutputFormat(f, shortcodePlaceholder) key := newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder)
renderFn, found := s.contentShortcodes[key] renderFn, found := s.contentShortcodes[key]


if !found { if !found {
Expand All @@ -390,7 +394,7 @@ func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map
if !found { if !found {
panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder)) panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder))
} }
contentShortcodesForOuputFormat[newScKeyFromOutputFormat(f, shortcodePlaceholder)] = renderFn contentShortcodesForOuputFormat[newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder)] = renderFn
} }


return contentShortcodesForOuputFormat return contentShortcodesForOuputFormat
Expand Down Expand Up @@ -676,12 +680,19 @@ func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.T


suffix := strings.ToLower(key.Suffix) suffix := strings.ToLower(key.Suffix)
outFormat := strings.ToLower(key.OutputFormat) outFormat := strings.ToLower(key.OutputFormat)
lang := strings.ToLower(key.Lang)


if outFormat != "" && suffix != "" { 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)) names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, outFormat, suffix))
} }


if 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)) names = append(names, fmt.Sprintf("%s.%s", shortcodeName, suffix))
} }


Expand Down
4 changes: 2 additions & 2 deletions hugolib/shortcode_test.go
Expand Up @@ -837,8 +837,8 @@ func TestReplaceShortcodeTokens(t *testing.T) {
func TestScKey(t *testing.T) { func TestScKey(t *testing.T) {
require.Equal(t, scKey{Suffix: "xml", ShortcodePlaceholder: "ABCD"}, require.Equal(t, scKey{Suffix: "xml", ShortcodePlaceholder: "ABCD"},
newScKey(media.XMLType, "ABCD")) newScKey(media.XMLType, "ABCD"))
require.Equal(t, scKey{Suffix: "html", OutputFormat: "AMP", ShortcodePlaceholder: "EFGH"}, require.Equal(t, scKey{Lang: "en", Suffix: "html", OutputFormat: "AMP", ShortcodePlaceholder: "EFGH"},
newScKeyFromOutputFormat(output.AMPFormat, "EFGH")) newScKeyFromLangAndOutputFormat("en", output.AMPFormat, "EFGH"))
require.Equal(t, scKey{Suffix: "html", ShortcodePlaceholder: "IJKL"}, require.Equal(t, scKey{Suffix: "html", ShortcodePlaceholder: "IJKL"},
newDefaultScKey("IJKL")) newDefaultScKey("IJKL"))


Expand Down
1 change: 1 addition & 0 deletions output/docshelper.go
Expand Up @@ -44,6 +44,7 @@ func createLayoutExamples() interface{} {
f Format f Format
}{ }{
{`AMP home, with theme "demoTheme".`, LayoutDescriptor{Kind: "home"}, true, "", AMPFormat}, {`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}, {"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(`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}, {fmt.Sprintf(`JSON regular, "type: %s" in front matter.`, demoType), LayoutDescriptor{Kind: "page", Type: demoType}, false, "", JSONFormat},
Expand Down
84 changes: 56 additions & 28 deletions output/layout.go
Expand Up @@ -26,6 +26,7 @@ type LayoutDescriptor struct {
Type string Type string
Section string Section string
Kind string Kind string
Lang string
Layout string Layout string
} }


Expand Down Expand Up @@ -55,31 +56,33 @@ func NewLayoutHandler(hasTheme bool) *LayoutHandler {


const ( const (


// TODO(bep) variations reduce to 1 "."

// The RSS templates doesn't map easily into the regular pages. // The RSS templates doesn't map easily into the regular pages.
layoutsRSSHome = `NAME.SUFFIX _default/NAME.SUFFIX _internal/_default/rss.xml` layoutsRSSHome = `VARIATIONS _default/VARIATIONS _internal/_default/rss.xml`
layoutsRSSSection = `section/SECTION.NAME.SUFFIX _default/NAME.SUFFIX NAME.SUFFIX _internal/_default/rss.xml` layoutsRSSSection = `section/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml`
layoutsRSSTaxonomy = `taxonomy/SECTION.NAME.SUFFIX _default/NAME.SUFFIX NAME.SUFFIX _internal/_default/rss.xml` layoutsRSSTaxonomy = `taxonomy/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml`
layoutsRSSTaxonomyTerm = `taxonomy/SECTION.terms.NAME.SUFFIX _default/NAME.SUFFIX NAME.SUFFIX _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 = ` layoutsSection = `
section/SECTION.NAME.SUFFIX section/SECTION.SUFFIX section/SECTION.VARIATIONS
SECTION/list.NAME.SUFFIX SECTION/list.SUFFIX SECTION/list.VARIATIONS
_default/section.NAME.SUFFIX _default/section.SUFFIX _default/section.VARIATIONS
_default/list.NAME.SUFFIX _default/list.SUFFIX _default/list.VARIATIONS
indexes/SECTION.NAME.SUFFIX indexes/SECTION.SUFFIX indexes/SECTION.VARIATIONS
_default/indexes.NAME.SUFFIX _default/indexes.SUFFIX _default/indexes.VARIATIONS
` `
layoutsTaxonomy = ` layoutsTaxonomy = `
taxonomy/SECTION.NAME.SUFFIX taxonomy/SECTION.SUFFIX taxonomy/SECTION.VARIATIONS
indexes/SECTION.NAME.SUFFIX indexes/SECTION.SUFFIX indexes/SECTION.VARIATIONS
_default/taxonomy.NAME.SUFFIX _default/taxonomy.SUFFIX _default/taxonomy.VARIATIONS
_default/list.NAME.SUFFIX _default/list.SUFFIX _default/list.VARIATIONS
` `
layoutsTaxonomyTerm = ` layoutsTaxonomyTerm = `
taxonomy/SECTION.terms.NAME.SUFFIX taxonomy/SECTION.terms.SUFFIX taxonomy/SECTION.terms.VARIATIONS
_default/terms.NAME.SUFFIX _default/terms.SUFFIX _default/terms.VARIATIONS
indexes/indexes.NAME.SUFFIX indexes/indexes.SUFFIX indexes/indexes.VARIATIONS
` `
) )


Expand Down Expand Up @@ -185,14 +188,41 @@ func resolveListTemplate(d LayoutDescriptor, f Format,
} }


func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string { func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
delim := "."
if f.MediaType.Delimiter == "" { // VARIATIONS will be replaced with
delim = "" // .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) return filterDotLess(layouts)
} }
Expand All @@ -201,9 +231,7 @@ func filterDotLess(layouts []string) []string {
var filteredLayouts []string var filteredLayouts []string


for _, l := range layouts { for _, l := range layouts {
// This may be constructed, but media types can be suffix-less, but can contain l = strings.Trim(l, ".")
// a delimiter.
l = strings.TrimSuffix(l, ".")
// If media type has no suffix, we have "index" type of layouts in this list, which // If media type has no suffix, we have "index" type of layouts in this list, which
// doesn't make much sense. // doesn't make much sense.
if strings.Contains(l, ".") { if strings.Contains(l, ".") {
Expand Down
2 changes: 2 additions & 0 deletions output/layout_test.go
Expand Up @@ -59,6 +59,8 @@ func TestLayout(t *testing.T) {
}{ }{
{"Home", LayoutDescriptor{Kind: "home"}, true, "", ampType, {"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"}}, []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, {"Home, no ext or delim", LayoutDescriptor{Kind: "home"}, true, "", noExtDelimFormat,
[]string{"index.nem", "_default/list.nem"}}, []string{"index.nem", "_default/list.nem"}},
{"Home, no ext", LayoutDescriptor{Kind: "home"}, true, "", noExt, {"Home, no ext", LayoutDescriptor{Kind: "home"}, true, "", noExt,
Expand Down

0 comments on commit aa6b1b9

Please sign in to comment.