Repository: gohugoio/hugo (language: go)

Issue:
Page resources duplication for translations (even in 0.123)
There is an issue #11453 already dealing with somewhat similar problem. It is marked as solved with 0.123.0, but that solution only works with md files. In our case, all translations are in html format, and all the resources are duplicated to all the languages and there is no way to prevent this from happening. In our case it takes dozens of gigabytes of extra space, creating all sorts of problems. We even tried avoiding this by loading the resources only from the base language, but it doesn't matter as hugo still duplicates all the resources to all the languages.

### What version of Hugo are you using (`hugo version`)?

<pre>
$ hugo version
hugo v0.123.0-DEV+extended linux/amd64 BuildDate=unknown
</pre>

### Does this issue reproduce with the latest release?

Yes.



Discussion / hints:
I'm not sure I follow.

* #11453 talks about bundled non-content files (e.g. images) 
* Content files used as resources (in leaf bundles), has never (as I remember) been duplicated. They're assigned to a language.

I may be misunderstanding this, but then you need to provide me with a full and running example project.
We have something like this:

```
content/
└── news/
    └── 2023/
        └── 01/
            └── 1/
                ├── image1.jpg
                ├── image2.jpg
                ├── index.md
                ├── index.es.html
                └── index.fr.html
```

We get this built:

```
news/
└── 2023/
    └── 01/
        ├── image1.jpg
        ├── image2.jpg
        └── 1.html
es/
└── news/
    └── 2023/
        └── 01/
            ├── image1.jpg
            ├── image2.jpg
            └── 1.html
fr/
└── news/
    └── 2023/
        └── 01/
            ├── image1.jpg
            ├── image2.jpg
            └── 1.html
```

All images are duplicated in all languages.
Is this a multihost setup? As in: Do you have multiple baseURLs?
No, we have only one baseURL
BTW, you tagged it as regression, but it is not - resources were always duplicated for us. We hoped that 0.123 would fix this, but it does not.

Fix patch:
diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
index 842d9307b06..897edb9b73d 100644
--- a/common/paths/pathparser.go
+++ b/common/paths/pathparser.go
@@ -29,6 +29,9 @@ var defaultPathParser PathParser
 type PathParser struct {
 	// Maps the language code to its index in the languages/sites slice.
 	LanguageIndex map[string]int
+
+	// Reports whether the given language is disabled.
+	IsLangDisabled func(string) bool
 }
 
 // Parse parses component c with path s into Path using the default path parser.
@@ -134,7 +137,16 @@ func (pp *PathParser) doParse(component, s string) (*Path, error) {
 					s := p.s[id.Low:id.High]
 
 					if hasLang {
-						if _, found := pp.LanguageIndex[s]; found {
+						var disabled bool
+						_, langFound := pp.LanguageIndex[s]
+						if !langFound {
+							disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(s)
+							if disabled {
+								p.disabled = true
+								langFound = true
+							}
+						}
+						if langFound {
 							p.posIdentifierLanguage = 1
 							p.identifiers = append(p.identifiers, id)
 						}
@@ -220,6 +232,7 @@ type Path struct {
 	identifiers []types.LowHigh
 
 	posIdentifierLanguage int
+	disabled              bool
 
 	trimLeadingSlash bool
 
@@ -435,6 +448,10 @@ func (p *Path) Identifier(i int) string {
 	return p.identifierAsString(i)
 }
 
+func (p *Path) Disabled() bool {
+	return p.disabled
+}
+
 func (p *Path) Identifiers() []string {
 	ids := make([]string, len(p.identifiers))
 	for i, id := range p.identifiers {
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index 742a281504c..413baebd12f 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -733,7 +733,8 @@ func (c *Configs) Init() error {
 
 	c.Languages = languages
 	c.LanguagesDefaultFirst = languagesDefaultFirst
-	c.ContentPathParser = paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet()}
+
+	c.ContentPathParser = paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
 
 	c.configLangs = make([]config.AllProvider, len(c.Languages))
 	for i, l := range c.LanguagesDefaultFirst {
diff --git a/hugofs/component_fs.go b/hugofs/component_fs.go
index 62d977ae889..e75e23f8438 100644
--- a/hugofs/component_fs.go
+++ b/hugofs/component_fs.go
@@ -94,11 +94,15 @@ func (f *componentFsDir) ReadDir(count int) ([]iofs.DirEntry, error) {
 
 	fis = fis[:n]
 
+	n = 0
 	for _, fi := range fis {
 		s := path.Join(f.name, fi.Name())
-		_ = f.fs.applyMeta(fi, s)
-
+		if _, ok := f.fs.applyMeta(fi, s); ok {
+			fis[n] = fi
+			n++
+		}
 	}
+	fis = fis[:n]
 
 	sort.Slice(fis, func(i, j int) bool {
 		fimi, fimj := fis[i].(FileMetaInfo), fis[j].(FileMetaInfo)
@@ -180,7 +184,8 @@ func (f *componentFsDir) Stat() (iofs.FileInfo, error) {
 	if err != nil {
 		return nil, err
 	}
-	return f.fs.applyMeta(fi, f.name), nil
+	fim, _ := f.fs.applyMeta(fi, f.name)
+	return fim, nil
 }
 
 func (fs *componentFs) Stat(name string) (os.FileInfo, error) {
@@ -188,16 +193,26 @@ func (fs *componentFs) Stat(name string) (os.FileInfo, error) {
 	if err != nil {
 		return nil, err
 	}
-	return fs.applyMeta(fi, name), nil
+	fim, _ := fs.applyMeta(fi, name)
+	return fim, nil
 }
 
-func (fs *componentFs) applyMeta(fi FileNameIsDir, name string) FileMetaInfo {
+func (fs *componentFs) applyMeta(fi FileNameIsDir, name string) (FileMetaInfo, bool) {
 	if runtime.GOOS == "darwin" {
 		name = norm.NFC.String(name)
 	}
 	fim := fi.(FileMetaInfo)
 	meta := fim.Meta()
-	meta.PathInfo = fs.opts.PathParser.Parse(fs.opts.Component, name)
+	pi := fs.opts.PathParser.Parse(fs.opts.Component, name)
+	if pi.Disabled() {
+		return fim, false
+	}
+	if meta.Lang != "" {
+		if isLangDisabled := fs.opts.PathParser.IsLangDisabled; isLangDisabled != nil && isLangDisabled(meta.Lang) {
+			return fim, false
+		}
+	}
+	meta.PathInfo = pi
 	if !fim.IsDir() {
 		if fileLang := meta.PathInfo.Lang(); fileLang != "" {
 			// A valid lang set in filename.
@@ -223,7 +238,7 @@ func (fs *componentFs) applyMeta(fi FileNameIsDir, name string) FileMetaInfo {
 		}
 	}
 
-	return fim
+	return fim, true
 }
 
 func (f *componentFsDir) Readdir(count int) ([]os.FileInfo, error) {
diff --git a/hugolib/content_map.go b/hugolib/content_map.go
index 96013c4ed8c..85300e3db21 100644
--- a/hugolib/content_map.go
+++ b/hugolib/content_map.go
@@ -166,11 +166,6 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
 		return nil
 	}
 
-	meta := fi.Meta()
-	if m.s.conf.IsLangDisabled(meta.Lang) {
-		return nil
-	}
-
 	insertResource := func(fim hugofs.FileMetaInfo) error {
 		pi := fi.Meta().PathInfo
 		key := pi.Base()
@@ -209,6 +204,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
 		return nil
 	}
 
+	meta := fi.Meta()
 	pi := meta.PathInfo
 
 	switch pi.BundleType() {
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
index 3bef55f43fd..d846fe03c25 100644
--- a/hugolib/page__new.go
+++ b/hugolib/page__new.go
@@ -60,6 +60,11 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
 		return nil, nil, m.wrapError(err, h.BaseFs.SourceFs)
 	}
 	pcfg := m.pageConfig
+	if pcfg.Lang != "" {
+		if h.Conf.IsLangDisabled(pcfg.Lang) {
+			return nil, nil, nil
+		}
+	}
 
 	if pcfg.Path != "" {
 		s := m.pageConfig.Path


Test patch:
diff --git a/hugolib/disableKinds_test.go b/hugolib/disableKinds_test.go
index 00652058010..8262f562a08 100644
--- a/hugolib/disableKinds_test.go
+++ b/hugolib/disableKinds_test.go
@@ -416,3 +416,50 @@ Section: MySection|RelPermalink: |Outputs: 0
 	b.Assert(b.CheckExists("public/sect/no-render/index.html"), qt.Equals, false)
 	b.Assert(b.CheckExists("public/sect-no-render/index.html"), qt.Equals, false)
 }
+
+func TestDisableOneOfThreeLanguages(t *testing.T) {
+	files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+title = "English"
+[languages.nn]
+weight = 2
+title = "Nynorsk"
+disabled = true
+[languages.nb]
+weight = 3
+title = "Bokmål"
+-- content/p1.nn.md --
+---
+title: "Page 1 nn"
+---
+-- content/p1.nb.md --
+---
+title: "Page 1 nb"
+---
+-- content/p1.en.md --
+---
+title: "Page 1 en"
+---
+-- content/p2.nn.md --
+---
+title: "Page 2 nn"
+---
+-- layouts/_default/single.html --
+{{ .Title }}
+`
+	b := Test(t, files)
+
+	b.Assert(len(b.H.Sites), qt.Equals, 2)
+	b.AssertFileContent("public/en/p1/index.html", "Page 1 en")
+	b.AssertFileContent("public/nb/p1/index.html", "Page 1 nb")
+
+	b.AssertFileExists("public/en/p2/index.html", false)
+	b.AssertFileExists("public/nn/p1/index.html", false)
+	b.AssertFileExists("public/nn/p2/index.html", false)
+}
diff --git a/hugolib/pagebundler_test.go b/hugolib/pagebundler_test.go
index 54ea04c7a8b..c6f9155ea1e 100644
--- a/hugolib/pagebundler_test.go
+++ b/hugolib/pagebundler_test.go
@@ -188,30 +188,43 @@ baseURL = "https://example.com"
 disableKinds = ["taxonomy", "term"]
 defaultContentLanguage = "en"
 defaultContentLanguageInSubdir = true
-disableLanguages = ["nn"]
 [languages]
 [languages.en]
 weight = 1
 [languages.nn]
 weight = 2
--- content/p1.md --
+disabled = true
+-- content/mysect/_index.md --
+---
+title: "My Sect En"
+---
+-- content/mysect/p1/index.md --
 ---
 title: "P1"
 ---
 P1
--- content/p1.nn.md --
+-- content/mysect/_index.nn.md --
+---
+title: "My Sect Nn"
+---
+-- content/mysect/p1/index.nn.md --
 ---
 title: "P1nn"
 ---
 P1nn
+-- layouts/index.html --
+Len RegularPages: {{ len .Site.RegularPages }}|RegularPages: {{ range site.RegularPages }}{{ .RelPermalink }}: {{ .Title }}|{{ end }}|
+Len Pages: {{ len .Site.Pages }}|
+Len Sites: {{ len .Site.Sites }}|
 -- layouts/_default/single.html --
 {{ .Title }}|{{ .Content }}|{{ .Lang }}|
 
 `
 	b := Test(t, files)
 
-	b.AssertFileContent("public/en/p1/index.html", "P1|<p>P1</p>\n|en|")
-	b.AssertFileExists("public/public/nn/p1/index.html", false)
+	b.AssertFileContent("public/en/index.html", "Len RegularPages: 1|")
+	b.AssertFileContent("public/en/mysect/p1/index.html", "P1|<p>P1</p>\n|en|")
+	b.AssertFileExists("public/public/nn/mysect/p1/index.html", false)
 	b.Assert(len(b.H.Sites), qt.Equals, 1)
 }