Repository: gohugoio/hugo (language: go)

Issue:
Hugo gets confused when multihost and all but one language is disabled
See https://discourse.gohugo.io/t/scss-broken-after-0-123/48937


Discussion / hints:
🙇 thank you for creating this issue. I'm the originator of the post on _discourse_ - and my goal is to have a single codebase that builds several (different-language) websites (under different domains). I thought to build one at a time I had to disable all languages but one, but I see that I can build them all and simply go to `public/en` for _English_ (full working website) and `public/de` for _German_ - and everything seems to work 🥳 

My build scripts did work better when I could build one language at a time by `disableLanguages = ["es", "de"]` - since all the files were in `/public` and I didn't have to move them from `/public/en` during the build (I'm also generating PDFs and static search via _Pagefind_) - so it _is_ more convenient to build one language at a time (both PDF and Search tooling is easier to work with when there is a single website -- though I can complicate my script by changing ports manually and deleting subdirectories manually/with script).

_tl;dr_ 🙇  thank you - please feel free to prioritize this _issue_ as you see fit ❤️ 

p.s - my codebase: https://github.com/whyboris/utilitarianism.net 
@whyboris it's an obvious bug and the fix should be simple.

Fix patch:
diff --git a/cache/dynacache/dynacache.go b/cache/dynacache/dynacache.go
index e79de5a5bb9..87ef68f5be2 100644
--- a/cache/dynacache/dynacache.go
+++ b/cache/dynacache/dynacache.go
@@ -67,7 +67,7 @@ func New(opts Options) *Cache {
 	evictedIdentities := collections.NewStack[identity.Identity]()
 
 	onEvict := func(k, v any) {
-		if !opts.Running {
+		if !opts.Watching {
 			return
 		}
 		identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool {
@@ -97,7 +97,7 @@ type Options struct {
 	CheckInterval time.Duration
 	MaxSize       int
 	MinMaxSize    int
-	Running       bool
+	Watching      bool
 }
 
 // Options for a partition.
diff --git a/config/allconfig/configlanguage.go b/config/allconfig/configlanguage.go
index 900db75ce6a..c7f1c276ae8 100644
--- a/config/allconfig/configlanguage.go
+++ b/config/allconfig/configlanguage.go
@@ -71,6 +71,9 @@ func (c ConfigLanguage) Environment() string {
 }
 
 func (c ConfigLanguage) IsMultihost() bool {
+	if len(c.m.Languages)-len(c.config.C.DisabledLanguages) <= 1 {
+		return false
+	}
 	return c.m.IsMultihost
 }
 
diff --git a/deps/deps.go b/deps/deps.go
index 78a5330b7e9..41a8ecb3e73 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -155,7 +155,7 @@ func (d *Deps) Init() error {
 	}
 
 	if d.MemCache == nil {
-		d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
+		d.MemCache = dynacache.New(dynacache.Options{Watching: d.Conf.Watching(), Log: d.Log})
 	}
 
 	if d.PathSpec == nil {
diff --git a/hugolib/site_new.go b/hugolib/site_new.go
index 59b97e887eb..496889295d2 100644
--- a/hugolib/site_new.go
+++ b/hugolib/site_new.go
@@ -123,14 +123,14 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
 			HandlerPost:        logHookLast,
 			Stdout:             cfg.LogOut,
 			Stderr:             cfg.LogOut,
-			StoreErrors:        conf.Running(),
+			StoreErrors:        conf.Watching(),
 			SuppressStatements: conf.IgnoredLogs(),
 		}
 		logger = loggers.New(logOpts)
 
 	}
 
-	memCache := dynacache.New(dynacache.Options{Running: conf.Running(), Log: logger})
+	memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger})
 
 	firstSiteDeps := &deps.Deps{
 		Fs:                  cfg.Fs,
diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
index 9d14b9e56b2..e5ee4d54f80 100644
--- a/tpl/tplimpl/template_funcs.go
+++ b/tpl/tplimpl/template_funcs.go
@@ -71,7 +71,7 @@ var (
 )
 
 type templateExecHelper struct {
-	running    bool // whether we're in server mode.
+	watching   bool // whether we're in server/watch mode.
 	site       reflect.Value
 	siteParams reflect.Value
 	funcs      map[string]reflect.Value
@@ -95,7 +95,7 @@ func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Prep
 }
 
 func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Preparer) {
-	if t.running {
+	if t.watching {
 		_, ok := tmpl.(identity.IdentityProvider)
 		if ok {
 			t.trackDependencies(ctx, tmpl, "", reflect.Value{})
@@ -129,7 +129,7 @@ func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Pr
 		name = "MainSections"
 	}
 
-	if t.running {
+	if t.watching {
 		ctx = t.trackDependencies(ctx, tmpl, name, receiver)
 	}
 
@@ -151,7 +151,7 @@ func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Pr
 }
 
 func (t *templateExecHelper) OnCalled(ctx context.Context, tmpl texttemplate.Preparer, name string, args []reflect.Value, result reflect.Value) {
-	if !t.running {
+	if !t.watching {
 		return
 	}
 
@@ -238,7 +238,7 @@ func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflec
 	}
 
 	exeHelper := &templateExecHelper{
-		running:    d.Conf.Running(),
+		watching:   d.Conf.Watching(),
 		funcs:      funcsv,
 		site:       reflect.ValueOf(d.Site),
 		siteParams: reflect.ValueOf(d.Site.Params()),


Test patch:
diff --git a/hugolib/hugo_sites_multihost_test.go b/hugolib/hugo_sites_multihost_test.go
index 937166146fc..004da351dbc 100644
--- a/hugolib/hugo_sites_multihost_test.go
+++ b/hugolib/hugo_sites_multihost_test.go
@@ -252,3 +252,31 @@ Files: {{ range $files }}{{ .Permalink }}|{{ end }}$
 	b.AssertFileContent("public/en/enpages/mybundle-en/file2.txt", "File 2 en.")
 	b.AssertFileContent("public/fr/section/mybundle/file2.txt", "File 2 en.")
 }
+
+func TestMultihostAllButOneLanguageDisabledIssue12288(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+defaultContentLanguage = "en"
+disableLanguages = ["fr"]
+#baseURL = "https://example.com"
+[languages]
+[languages.en]
+baseURL = "https://example.en"
+weight = 1
+[languages.fr]
+baseURL = "https://example.fr"
+weight = 2
+--  assets/css/main.css --
+body { color: red; }
+-- layouts/index.html --
+{{ $css := resources.Get "css/main.css" | minify }}
+CSS: {{ $css.Permalink }}|{{ $css.RelPermalink }}|
+`
+
+	b := Test(t, files)
+
+	b.AssertFileContent("public/css/main.min.css", "body{color:red}")
+	b.AssertFileContent("public/index.html", "CSS: https://example.en/css/main.min.css|/css/main.min.css|")
+}
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 8daf7b1caff..7495d33417f 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -38,12 +38,20 @@ import (
 
 type TestOpt func(*IntegrationTestConfig)
 
+// TestOptRunning will enable running in integration tests.
 func TestOptRunning() TestOpt {
 	return func(c *IntegrationTestConfig) {
 		c.Running = true
 	}
 }
 
+// TestOptWatching will enable watching in integration tests.
+func TestOptWatching() TestOpt {
+	return func(c *IntegrationTestConfig) {
+		c.Watching = true
+	}
+}
+
 // Enable tracing in integration tests.
 // THis should only be used during development and not committed to the repo.
 func TestOptTrace() TestOpt {
@@ -570,6 +578,10 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 				"running": s.Cfg.Running,
 				"watch":   s.Cfg.Running,
 			})
+		} else if s.Cfg.Watching {
+			flags.Set("internal", maps.Params{
+				"watch": s.Cfg.Watching,
+			})
 		}
 
 		if s.Cfg.WorkingDir != "" {
@@ -817,6 +829,11 @@ type IntegrationTestConfig struct {
 	// Whether to simulate server mode.
 	Running bool
 
+	// Watch for changes.
+	// This is (currently) always set to true when Running is set.
+	// Note that the CLI for the server does allow for --watch=false, but that is not used in these test.
+	Watching bool
+
 	// Will print the log buffer after the build
 	Verbose bool
 
diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go
index ea7efcb6fa6..4336d8b6105 100644
--- a/hugolib/rebuild_test.go
+++ b/hugolib/rebuild_test.go
@@ -121,14 +121,23 @@ func TestRebuildEditTextFileInBranchBundle(t *testing.T) {
 	b.AssertRenderCountContent(1)
 }
 
+func testRebuildBothWatchingAndRunning(t *testing.T, files string, withB func(b *IntegrationTestBuilder)) {
+	t.Helper()
+	for _, opt := range []TestOpt{TestOptWatching(), TestOptRunning()} {
+		b := Test(t, files, opt)
+		withB(b)
+	}
+}
+
 func TestRebuildRenameTextFileInLeafBundle(t *testing.T) {
-	b := TestRunning(t, rebuildFilesSimple)
-	b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
+	testRebuildBothWatchingAndRunning(t, rebuildFilesSimple, func(b *IntegrationTestBuilder) {
+		b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
 
-	b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
-	b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
-	b.AssertRenderCountPage(3)
-	b.AssertRenderCountContent(3)
+		b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
+		b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
+		b.AssertRenderCountPage(3)
+		b.AssertRenderCountContent(3)
+	})
 }
 
 func TestRebuilEditContentFileInLeafBundle(t *testing.T) {
@@ -367,8 +376,6 @@ My short.
 }
 
 func TestRebuildBaseof(t *testing.T) {
-	t.Parallel()
-
 	files := `
 -- hugo.toml --
 title = "Hugo Site"
@@ -383,12 +390,13 @@ Baseof: {{ .Title }}|
 Home: {{ .Title }}|{{ .Content }}|
 {{ end }}
 `
-	b := Test(t, files, TestOptRunning())
-	b.AssertFileContent("public/index.html", "Baseof: Hugo Site|", "Home: Hugo Site||")
-	b.EditFileReplaceFunc("layouts/_default/baseof.html", func(s string) string {
-		return strings.Replace(s, "Baseof", "Baseof Edited", 1)
-	}).Build()
-	b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Site|", "Home: Hugo Site||")
+	testRebuildBothWatchingAndRunning(t, files, func(b *IntegrationTestBuilder) {
+		b.AssertFileContent("public/index.html", "Baseof: Hugo Site|", "Home: Hugo Site||")
+		b.EditFileReplaceFunc("layouts/_default/baseof.html", func(s string) string {
+			return strings.Replace(s, "Baseof", "Baseof Edited", 1)
+		}).Build()
+		b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Site|", "Home: Hugo Site||")
+	})
 }
 
 func TestRebuildSingleWithBaseof(t *testing.T) {