Repository: gohugoio/hugo (language: go)

Issue:
Dot file in i18n directory throws error
```text
i18n/
├── .gitkeep
├── de.toml
└── en.toml
```

> Error: error building site: "art-x-": language: tag is not well-formed

No error with v0.122.0. Found this building site from a recent forum topic.




Discussion / hints:

Fix patch:
diff --git a/commands/server.go b/commands/server.go
index bdf6e905a26..e1a4b5cf5b4 100644
--- a/commands/server.go
+++ b/commands/server.go
@@ -497,9 +497,7 @@ func (c *serverCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
 
 	err := func() error {
 		defer c.r.timeTrack(time.Now(), "Built")
-		var err error
-		err = c.build()
-		return err
+		return c.build()
 	}()
 	if err != nil {
 		return err
diff --git a/hugofs/walk.go b/hugofs/walk.go
index 18667a5fce0..391f70a6588 100644
--- a/hugofs/walk.go
+++ b/hugofs/walk.go
@@ -53,8 +53,9 @@ type WalkwayConfig struct {
 	Logger loggers.Logger
 
 	// One or both of these may be pre-set.
-	Info       FileMetaInfo   // The start info.
-	DirEntries []FileMetaInfo // The start info's dir entries.
+	Info       FileMetaInfo               // The start info.
+	DirEntries []FileMetaInfo             // The start info's dir entries.
+	IgnoreFile func(filename string) bool // Optional
 
 	// Will be called in order.
 	HookPre  WalkHook // Optional.
@@ -172,6 +173,17 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo
 
 	}
 
+	if w.cfg.IgnoreFile != nil {
+		n := 0
+		for _, fi := range dirEntries {
+			if !w.cfg.IgnoreFile(fi.Meta().Filename) {
+				dirEntries[n] = fi
+				n++
+			}
+		}
+		dirEntries = dirEntries[:n]
+	}
+
 	if w.cfg.HookPre != nil {
 		var err error
 		dirEntries, err = w.cfg.HookPre(info, path, dirEntries)
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 24ff1077f8b..99dc88b10a1 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -473,7 +473,8 @@ func (h *HugoSites) loadData() error {
 	h.data = make(map[string]any)
 	w := hugofs.NewWalkway(
 		hugofs.WalkwayConfig{
-			Fs: h.PathSpec.BaseFs.Data.Fs,
+			Fs:         h.PathSpec.BaseFs.Data.Fs,
+			IgnoreFile: h.SourceSpec.IgnoreFile,
 			WalkFn: func(path string, fi hugofs.FileMetaInfo) error {
 				if fi.IsDir() {
 					return nil
diff --git a/hugolib/pages_capture.go b/hugolib/pages_capture.go
index acdc674e63d..b7da065fdc5 100644
--- a/hugolib/pages_capture.go
+++ b/hugolib/pages_capture.go
@@ -249,9 +249,6 @@ func (c *pagesCollector) collectDir(dirPath *paths.Path, isDir bool, inFilter fu
 
 func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, inFilter func(fim hugofs.FileMetaInfo) bool) error {
 	filter := func(fim hugofs.FileMetaInfo) bool {
-		if c.sp.IgnoreFile(fim.Meta().Filename) {
-			return false
-		}
 		if inFilter != nil {
 			return inFilter(fim)
 		}
@@ -330,13 +327,14 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
 
 	w := hugofs.NewWalkway(
 		hugofs.WalkwayConfig{
-			Logger:   c.logger,
-			Root:     path,
-			Info:     root,
-			Fs:       c.fs,
-			HookPre:  preHook,
-			HookPost: postHook,
-			WalkFn:   wfn,
+			Logger:     c.logger,
+			Root:       path,
+			Info:       root,
+			Fs:         c.fs,
+			IgnoreFile: c.h.SourceSpec.IgnoreFile,
+			HookPre:    preHook,
+			HookPost:   postHook,
+			WalkFn:     wfn,
 		})
 
 	return w.Walk()
@@ -371,6 +369,7 @@ func (c *pagesCollector) handleBundleLeaf(dir, bundle hugofs.FileMetaInfo, inPat
 			Logger:     c.logger,
 			Info:       dir,
 			DirEntries: readdir,
+			IgnoreFile: c.h.SourceSpec.IgnoreFile,
 			WalkFn:     walk,
 		})
 
diff --git a/langs/i18n/translationProvider.go b/langs/i18n/translationProvider.go
index ab524741300..9bbd5d9f6b3 100644
--- a/langs/i18n/translationProvider.go
+++ b/langs/i18n/translationProvider.go
@@ -59,7 +59,8 @@ func (tp *TranslationProvider) NewResource(dst *deps.Deps) error {
 
 	w := hugofs.NewWalkway(
 		hugofs.WalkwayConfig{
-			Fs: dst.BaseFs.I18n.Fs,
+			Fs:         dst.BaseFs.I18n.Fs,
+			IgnoreFile: dst.SourceSpec.IgnoreFile,
 			WalkFn: func(path string, info hugofs.FileMetaInfo) error {
 				if info.IsDir() {
 					return nil
diff --git a/source/filesystem.go b/source/filesystem.go
deleted file mode 100644
index 208f5036f5d..00000000000
--- a/source/filesystem.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2016 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package source
-
-import (
-	"path/filepath"
-
-	"github.com/gohugoio/hugo/hugofs"
-	"github.com/spf13/afero"
-)
-
-// Filesystem represents a source filesystem.
-type Filesystem struct {
-	Base string
-	fs   afero.Fs
-	fi   hugofs.FileMetaInfo
-	SourceSpec
-}
-
-// NewFilesystem returns a new filesytem for a given source spec.
-func (sp SourceSpec) NewFilesystem(base string) *Filesystem {
-	return &Filesystem{SourceSpec: sp, Base: base, fs: sp.Fs.Source}
-}
-
-func (f *Filesystem) Walk(addFile func(*File) error) error {
-	walker := func(path string, fi hugofs.FileMetaInfo) error {
-		if fi.IsDir() {
-			return nil
-		}
-
-		meta := fi.Meta()
-		filename := meta.Filename
-
-		b, err := f.shouldRead(filename, fi)
-		if err != nil {
-			return err
-		}
-
-		if b {
-			if err = addFile(NewFileInfo(fi)); err != nil {
-				return err
-			}
-		}
-
-		return err
-	}
-
-	w := hugofs.NewWalkway(hugofs.WalkwayConfig{
-		Fs:     f.fs,
-		Info:   f.fi,
-		Root:   f.Base,
-		WalkFn: walker,
-	})
-
-	return w.Walk()
-}
-
-func (f *Filesystem) shouldRead(filename string, fi hugofs.FileMetaInfo) (bool, error) {
-	ignore := f.SourceSpec.IgnoreFile(fi.Meta().Filename)
-
-	if fi.IsDir() {
-		if ignore {
-			return false, filepath.SkipDir
-		}
-		return false, nil
-	}
-
-	if ignore {
-		return false, nil
-	}
-
-	return true, nil
-}


Test patch:
diff --git a/hugolib/language_test.go b/hugolib/language_test.go
index c9368770e45..582d3985fdb 100644
--- a/hugolib/language_test.go
+++ b/hugolib/language_test.go
@@ -135,3 +135,14 @@ FormatNumberCustom: 12,345.68
 NumFmt: -98,765.43
 `)
 }
+
+// Issue 11993.
+func TestI18nDotFile(t *testing.T) {
+	files := `
+-- hugo.toml --{}
+baseURL = "https://example.com"
+-- i18n/.keep --
+-- data/.keep --
+`
+	Test(t, files)
+}
diff --git a/source/filesystem_test.go b/source/filesystem_test.go
deleted file mode 100644
index 9118285dab2..00000000000
--- a/source/filesystem_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package source_test
-
-import (
-	"runtime"
-	"testing"
-
-	qt "github.com/frankban/quicktest"
-	"github.com/gohugoio/hugo/hugolib"
-	"golang.org/x/text/unicode/norm"
-)
-
-func TestUnicodeNorm(t *testing.T) {
-	if runtime.GOOS != "darwin" {
-		t.Skip("Skipping test on non-Darwin OS")
-	}
-	t.Parallel()
-	files := `
--- hugo.toml --
--- content/å.md --
--- content/é.md --
--- content/å/å.md --
--- content/é/é.md --
--- layouts/_default/single.html --
-Title: {{ .Title }}|File: {{ .File.Path}}
-`
-	b := hugolib.Test(t, files, hugolib.TestOptWithNFDOnDarwin())
-
-	for _, p := range b.H.Sites[0].RegularPages() {
-		f := p.File()
-		b.Assert(norm.NFC.IsNormalString(f.Path()), qt.IsTrue)
-		b.Assert(norm.NFC.IsNormalString(f.Dir()), qt.IsTrue)
-		b.Assert(norm.NFC.IsNormalString(f.Filename()), qt.IsTrue)
-		b.Assert(norm.NFC.IsNormalString(f.BaseFileName()), qt.IsTrue)
-	}
-}