Repository: gohugoio/hugo (language: go)

Issue:
Indeterminate template lookup order re. the all template and templates with and without lang
Given this layouts directory:

```text
layouts/
├── all.en.html
├── all.en.html.html
├── all.html
├── all.html.html
├── page.en.html
├── page.en.html.html
├── page.html
├── page.html.html
├── single.en.html
├── single.en.html.html
├── single.html
└── single.html.html
````

And this content:

```text
content/
├── s1/
│   └── p1.md
└── _index.md
```

The template lookup order (from most specific to least specific) for `content/s1/p1.md` is indeterminate:

Run 1|Run 2|Run 3
:--|:--|:--
page.en.html|page.en.html|page.en.html
page.en.html.html|page.en.html.html|page.en.html.html
<mark>page.html</mark>|<mark>all.en.html</mark>|<mark>single.en.html</mark>
<mark>single.en.html</mark>|<mark>page.html</mark>|<mark>single.en.html.html</mark>
<mark>single.en.html.html</mark>|<mark>single.en.html</mark>|<mark>all.en.html</mark>
<mark>page.html.html</mark>|<mark>single.en.html.html</mark>|<mark>all.en.html.html</mark>
<mark>all.en.html</mark>|<mark>page.html.html</mark>|<mark>page.html</mark>
<mark>all.en.html.html</mark>|<mark>all.en.html.html</mark>|<mark>page.html.html</mark>
single.html|single.html|single.html
single.html.html|single.html.html|single.html.html
all.html|all.html|all.html
all.html.html|all.html.html|all.html.html

Tested with hugo v0.147.0-DEV-fd238a1de126fd977407a1cf6f3081bae053574d+extended+withdeploy linux/amd64 




Indeterminate template lookup order re. the all template and templates with and without lang
Given this layouts directory:

```text
layouts/
├── all.en.html
├── all.en.html.html
├── all.html
├── all.html.html
├── page.en.html
├── page.en.html.html
├── page.html
├── page.html.html
├── single.en.html
├── single.en.html.html
├── single.html
└── single.html.html
````

And this content:

```text
content/
├── s1/
│   └── p1.md
└── _index.md
```

The template lookup order (from most specific to least specific) for `content/s1/p1.md` is indeterminate:

Run 1|Run 2|Run 3
:--|:--|:--
page.en.html|page.en.html|page.en.html
page.en.html.html|page.en.html.html|page.en.html.html
<mark>page.html</mark>|<mark>all.en.html</mark>|<mark>single.en.html</mark>
<mark>single.en.html</mark>|<mark>page.html</mark>|<mark>single.en.html.html</mark>
<mark>single.en.html.html</mark>|<mark>single.en.html</mark>|<mark>all.en.html</mark>
<mark>page.html.html</mark>|<mark>single.en.html.html</mark>|<mark>all.en.html.html</mark>
<mark>all.en.html</mark>|<mark>page.html.html</mark>|<mark>page.html</mark>
<mark>all.en.html.html</mark>|<mark>all.en.html.html</mark>|<mark>page.html.html</mark>
single.html|single.html|single.html
single.html.html|single.html.html|single.html.html
all.html|all.html|all.html
all.html.html|all.html.html|all.html.html

Tested with hugo v0.147.0-DEV-fd238a1de126fd977407a1cf6f3081bae053574d+extended+withdeploy linux/amd64 






Discussion / hints:

Fix patch:
diff --git a/hugolib/alias.go b/hugolib/alias.go
index 0bb3165c733..7b252c6131a 100644
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -56,6 +56,7 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err
 	templateDesc.LayoutFromUser = ""
 	templateDesc.Kind = ""
 	templateDesc.OutputFormat = output.AliasHTMLFormat.Name
+	templateDesc.MediaType = output.AliasHTMLFormat.MediaType.Type
 
 	q := tplimpl.TemplateQuery{
 		Path:     base,
diff --git a/tpl/tplimpl/templatedescriptor.go b/tpl/tplimpl/templatedescriptor.go
index 8b50605ac8f..f65ad394310 100644
--- a/tpl/tplimpl/templatedescriptor.go
+++ b/tpl/tplimpl/templatedescriptor.go
@@ -64,7 +64,7 @@ func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool
 		return weightNoMatch
 	}
 
-	w := this.doCompare(category, isEmbedded, other)
+	w := this.doCompare(category, isEmbedded, s.opts.DefaultContentLanguage, other)
 
 	if w.w1 <= 0 {
 		if category == CategoryMarkup && (this.Variant1 == other.Variant1) && (this.Variant2 == other.Variant2 || this.Variant2 != "" && other.Variant2 == "") {
@@ -82,7 +82,7 @@ func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool
 }
 
 //lint:ignore ST1006 this vs other makes it easier to reason about.
-func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, other TemplateDescriptor) weight {
+func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, defaultContentLanguage string, other TemplateDescriptor) weight {
 	w := weightNoMatch
 
 	// HTML in plain text is OK, but not the other way around.
@@ -124,6 +124,10 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 		// Continue.
 	}
 
+	if other.MediaType != this.MediaType {
+		return w
+	}
+
 	// One example of variant1 and 2 is for render codeblocks:
 	// variant1=codeblock, variant2=go (language).
 	if other.Variant1 != "" && other.Variant1 != this.Variant1 {
@@ -142,14 +146,15 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 	}
 
 	const (
-		weightKind           = 3 // page, home, section, taxonomy, term (and only those)
-		weightcustomLayout   = 4 // custom layout (mylayout, set in e.g. front matter)
-		weightLayoutStandard = 2 // standard layouts (single,list,all)
-		weightOutputFormat   = 2 // a configured output format (e.g. rss, html, json)
+		weightKind           = 5 // page, home, section, taxonomy, term (and only those)
+		weightcustomLayout   = 6 // custom layout (mylayout, set in e.g. front matter)
+		weightLayoutStandard = 4 // standard layouts (single,list)
+		weightLayoutAll      = 2 // the "all" layout
+		weightOutputFormat   = 4 // a configured output format (e.g. rss, html, json)
 		weightMediaType      = 1 // a configured media type (e.g. text/html, text/plain)
 		weightLang           = 1 // a configured language (e.g. en, nn, fr, ...)
-		weightVariant1       = 4 // currently used for render hooks, e.g. "link", "image"
-		weightVariant2       = 2 // currently used for render hooks, e.g. the language "go" in code blocks.
+		weightVariant1       = 6 // currently used for render hooks, e.g. "link", "image"
+		weightVariant2       = 4 // currently used for render hooks, e.g. the language "go" in code blocks.
 
 		// We will use the values for group 2 and 3
 		// if the distance up to the template is shorter than
@@ -173,10 +178,12 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 		w.w2 = weight2Group1
 	}
 
-	if other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate || other.LayoutFromTemplate == layoutAll) {
+	if other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate) {
 		w.w1 += weightLayoutStandard
 		w.w2 = weight2Group1
-
+	} else if other.LayoutFromTemplate == layoutAll {
+		w.w1 += weightLayoutAll
+		w.w2 = weight2Group1
 	}
 
 	// LayoutCustom is only set in this (usually from Page.Layout).
@@ -185,7 +192,7 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 		w.w2 = weight2Group2
 	}
 
-	if other.Lang != "" && other.Lang == this.Lang {
+	if (other.Lang != "" && other.Lang == this.Lang) || (other.Lang == "" && this.Lang == defaultContentLanguage) {
 		w.w1 += weightLang
 		w.w3 += weight3
 	}


Test patch:
diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go
index 1c2642468a2..922ecbc12e3 100644
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -139,7 +139,7 @@ weight = 1
 languageName = "English"
 [languages.nn]
 weight = 2
--- layouts/_default/list.xml --
+-- layouts/list.xml --
 Site: {{ .Site.Title }}|
 -- layouts/home --
 Home.
diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
index 7770d053bf2..5be65f874f2 100644
--- a/tpl/tplimpl/templatestore.go
+++ b/tpl/tplimpl/templatestore.go
@@ -1842,7 +1842,9 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
 		// Anything is better than nothing.
 		return true
 	}
+
 	if w.w1 <= 0 {
+
 		if best.w.w1 <= 0 {
 			return ti.PathInfo.Path() < best.templ.PathInfo.Path()
 		}
@@ -1885,11 +1887,7 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
 			return true
 		}
 
-		if ti.D.LayoutFromTemplate != "" && best.desc.LayoutFromTemplate != "" {
-			return ti.D.LayoutFromTemplate != layoutAll
-		}
-
-		return w.distance < best.w.distance || ti.PathInfo.Path() < best.templ.PathInfo.Path()
+		return ti.PathInfo.Path() < best.templ.PathInfo.Path()
 	}
 
 	return true
diff --git a/tpl/tplimpl/templatestore_integration_test.go b/tpl/tplimpl/templatestore_integration_test.go
index 51b28754fa0..06cb00212b1 100644
--- a/tpl/tplimpl/templatestore_integration_test.go
+++ b/tpl/tplimpl/templatestore_integration_test.go
@@ -3,6 +3,7 @@ package tplimpl_test
 import (
 	"context"
 	"io"
+	"strings"
 	"testing"
 
 	qt "github.com/frankban/quicktest"
@@ -684,6 +685,102 @@ layout: mylayout
 	b.AssertFileContent("public/en/foo/index.xml", "layouts/list.xml")
 }
 
+func TestLookupOrderIssue13636(t *testing.T) {
+	t.Parallel()
+
+	filesTemplate := `
+-- hugo.toml --
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.nn]
+weight = 2
+-- content/s1/p1.en.md --
+---
+outputs: ["html", "amp", "json"]
+---
+-- content/s1/p1.nn.md --
+---
+outputs: ["html", "amp", "json"]
+---
+-- layouts/L1 --
+L1
+-- layouts/L2 --
+L2
+-- layouts/L3 --
+L3
+
+`
+
+	tests := []struct {
+		Lang       string
+		L1         string
+		L2         string
+		L3         string
+		ExpectHTML string
+		ExpectAmp  string
+		ExpectJSON string
+	}{
+		{"en", "all.en.html", "all.html", "single.html", "single.html", "single.html", ""},
+		{"en", "all.amp.html", "all.html", "page.html", "page.html", "all.amp.html", ""},
+		{"en", "all.amp.html", "all.html", "list.html", "all.html", "all.amp.html", ""},
+		{"en", "all.en.html", "all.json", "single.html", "single.html", "single.html", "all.json"},
+		{"en", "all.en.html", "single.json", "single.html", "single.html", "single.html", "single.json"},
+		{"en", "all.en.html", "all.html", "list.html", "all.en.html", "all.en.html", ""},
+		{"en", "list.en.html", "list.html", "list.en.html", "", "", ""},
+		{"nn", "all.en.html", "all.html", "single.html", "single.html", "single.html", ""},
+		{"nn", "all.en.html", "all.nn.html", "single.html", "single.html", "single.html", ""},
+		{"nn", "all.en.html", "all.nn.html", "single.nn.html", "single.nn.html", "single.nn.html", ""},
+		{"nn", "single.json", "single.nn.json", "all.json", "", "", "single.nn.json"},
+		{"nn", "single.json", "single.en.json", "all.nn.json", "", "", "single.json"},
+	}
+
+	for i, test := range tests {
+		if i != 8 {
+			// continue
+		}
+		files := strings.ReplaceAll(filesTemplate, "L1", test.L1)
+		files = strings.ReplaceAll(files, "L2", test.L2)
+		files = strings.ReplaceAll(files, "L3", test.L3)
+		t.Logf("Test %d: %s %s %s %s", i, test.Lang, test.L1, test.L2, test.L3)
+
+		for range 3 {
+			b := hugolib.Test(t, files)
+			b.Assert(len(b.H.Sites), qt.Equals, 2)
+
+			var (
+				pubhHTML = "public/LANG/s1/p1/index.html"
+				pubhAmp  = "public/LANG/amp/s1/p1/index.html"
+				pubhJSON = "public/LANG/s1/p1/index.json"
+			)
+
+			pubhHTML = strings.ReplaceAll(pubhHTML, "LANG", test.Lang)
+			pubhAmp = strings.ReplaceAll(pubhAmp, "LANG", test.Lang)
+			pubhJSON = strings.ReplaceAll(pubhJSON, "LANG", test.Lang)
+
+			if test.ExpectHTML != "" {
+				b.AssertFileContent(pubhHTML, test.ExpectHTML)
+			} else {
+				b.AssertFileExists(pubhHTML, false)
+			}
+
+			if test.ExpectAmp != "" {
+				b.AssertFileContent(pubhAmp, test.ExpectAmp)
+			} else {
+				b.AssertFileExists(pubhAmp, false)
+			}
+
+			if test.ExpectJSON != "" {
+				b.AssertFileContent(pubhJSON, test.ExpectJSON)
+			} else {
+				b.AssertFileExists(pubhJSON, false)
+			}
+		}
+	}
+}
+
 func TestLookupShortcodeDepth(t *testing.T) {
 	t.Parallel()