Repository: gin-gonic/gin
Pull request #4208 (2025-03-31 22:53:10)

chore(deps): bump github.com/go-playground/validator/v10 from 10.22.1 to 10.26.0
Bumps [github.com/go-playground/validator/v10](https://github.com/go-playground/validator) from 10.22.1 to 10.26.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/go-playground/validator/releases">github.com/go-playground/validator/v10's releases</a>.</em></p>
<blockquote>
<h2>v10.26.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Use correct pointer in errors.As(). Fix &quot;panic: errors: *target must be interface or implement error&quot; in examples. by <a href="https://github.com/antonsoroko"><code>@​antonsoroko</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1378">go-playground/validator#1378</a></li>
<li>Create dependabot by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1373">go-playground/validator#1373</a></li>
<li>Bump golangci/golangci-lint-action from 4 to 6 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1381">go-playground/validator#1381</a></li>
<li>Bump golang.org/x/text from 0.21.0 to 0.22.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1383">go-playground/validator#1383</a></li>
<li>Bump golang.org/x/crypto from 0.32.0 to 0.33.0 by <a href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1382">go-playground/validator#1382</a></li>
<li>feat(translations): improve Indonesian translations and add tests by <a href="https://github.com/fathiraz"><code>@​fathiraz</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1376">go-playground/validator#1376</a></li>
<li>Fix time.Duration translation error by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1154">go-playground/validator#1154</a></li>
<li>Update Project Status button by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1380">go-playground/validator#1380</a></li>
<li>Remove gitter.im link from README.md by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1366">go-playground/validator#1366</a></li>
<li>Docs: fix <code>Base64RawURL</code> usage by <a href="https://github.com/196Ikuchil"><code>@​196Ikuchil</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1336">go-playground/validator#1336</a></li>
<li>Fix length check on dns_rfc1035_label tag by <a href="https://github.com/KimNorgaard"><code>@​KimNorgaard</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1214">go-playground/validator#1214</a></li>
<li>Add Korean by <a href="https://github.com/jkh0kr"><code>@​jkh0kr</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1338">go-playground/validator#1338</a></li>
<li>add german translations by <a href="https://github.com/max-weis"><code>@​max-weis</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1322">go-playground/validator#1322</a></li>
<li>Update workflow to support the last three Go versions by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1393">go-playground/validator#1393</a></li>
<li>Fix: Nil pointer dereference in Arabic translations by <a href="https://github.com/BlackSud0"><code>@​BlackSud0</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1391">go-playground/validator#1391</a></li>
<li>Translate to thai by <a href="https://github.com/maetad"><code>@​maetad</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1202">go-playground/validator#1202</a></li>
<li>Feat: add EIN validation by <a href="https://github.com/henrriusdev"><code>@​henrriusdev</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1384">go-playground/validator#1384</a></li>
<li>Fix reference to parameter name in docs by <a href="https://github.com/yegvla"><code>@​yegvla</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1400">go-playground/validator#1400</a></li>
<li>use mail.ParseAddress to cover missing email validations by <a href="https://github.com/eladb2011"><code>@​eladb2011</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1395">go-playground/validator#1395</a></li>
<li>Update linter to v2.0.2 by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1405">go-playground/validator#1405</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/antonsoroko"><code>@​antonsoroko</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1378">go-playground/validator#1378</a></li>
<li><a href="https://github.com/dependabot"><code>@​dependabot</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1381">go-playground/validator#1381</a></li>
<li><a href="https://github.com/fathiraz"><code>@​fathiraz</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1376">go-playground/validator#1376</a></li>
<li><a href="https://github.com/196Ikuchil"><code>@​196Ikuchil</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1336">go-playground/validator#1336</a></li>
<li><a href="https://github.com/KimNorgaard"><code>@​KimNorgaard</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1214">go-playground/validator#1214</a></li>
<li><a href="https://github.com/jkh0kr"><code>@​jkh0kr</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1338">go-playground/validator#1338</a></li>
<li><a href="https://github.com/max-weis"><code>@​max-weis</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1322">go-playground/validator#1322</a></li>
<li><a href="https://github.com/BlackSud0"><code>@​BlackSud0</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1391">go-playground/validator#1391</a></li>
<li><a href="https://github.com/maetad"><code>@​maetad</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1202">go-playground/validator#1202</a></li>
<li><a href="https://github.com/henrriusdev"><code>@​henrriusdev</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1384">go-playground/validator#1384</a></li>
<li><a href="https://github.com/yegvla"><code>@​yegvla</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1400">go-playground/validator#1400</a></li>
<li><a href="https://github.com/eladb2011"><code>@​eladb2011</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1395">go-playground/validator#1395</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/go-playground/validator/compare/v10.25.0...v10.26.0">https://github.com/go-playground/validator/compare/v10.25.0...v10.26.0</a></p>
<h2>Release 10.25.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix postcode_iso3166_alpha2_field validation by <a href="https://github.com/ddevcap"><code>@​ddevcap</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1359">go-playground/validator#1359</a></li>
<li>Update README to replace the Travis CI badge with a GitHub Actions badge by <a href="https://github.com/nodivbyzero"><code>@​nodivbyzero</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1362">go-playground/validator#1362</a></li>
<li>chore: using errors.As instead of type assertion by <a href="https://github.com/fatelei"><code>@​fatelei</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1346">go-playground/validator#1346</a></li>
<li>Fix/remove issue template md by <a href="https://github.com/ganeshdipdumbare"><code>@​ganeshdipdumbare</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1375">go-playground/validator#1375</a></li>
<li>feat: Add support for omitting empty and zero values in validation (including nil pointer and empty content of pointer) by <a href="https://github.com/zeewell"><code>@​zeewell</code></a> in <a href="https://redirect.github.com/go-playground/validator/pull/1289">go-playground/validator#1289</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/ddevcap"><code>@​ddevcap</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1359">go-playground/validator#1359</a></li>
<li><a href="https://github.com/fatelei"><code>@​fatelei</code></a> made their first contribution in <a href="https://redirect.github.com/go-playground/validator/pull/1346">go-playground/validator#1346</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/go-playground/validator/commit/433b08283c14b1def28e8f05e36c2bed2f13b3f4"><code>433b082</code></a> Update linter to v2.0.2 (<a href="https://redirect.github.com/go-playground/validator/issues/1405">#1405</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/859202275556dac82d4460234867c5c5988d06fd"><code>8592022</code></a> use mail.ParseAddress to cover missing email validations (<a href="https://redirect.github.com/go-playground/validator/issues/1395">#1395</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/271abb312cd958fd1c61b58983ecab31026eb252"><code>271abb3</code></a> Fix reference to parameter name in docs (<a href="https://redirect.github.com/go-playground/validator/issues/1400">#1400</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/79c42ad73c1fdebee04c93ac6a0e132353662dc6"><code>79c42ad</code></a> Feat: add EIN validation (<a href="https://redirect.github.com/go-playground/validator/issues/1384">#1384</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/2cadaff20364eef00f8711e1b6a0dfcc2aceee14"><code>2cadaff</code></a> Translate to thai (<a href="https://redirect.github.com/go-playground/validator/issues/1202">#1202</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/bae7f6d3cb253fbd5dddcdf339f8460fa8d091c3"><code>bae7f6d</code></a> Fix: Nil pointer dereference in Arabic translations (<a href="https://redirect.github.com/go-playground/validator/issues/1391">#1391</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/909c504042f17c83dc76e8a2c11efd3dd41bd212"><code>909c504</code></a> Update workflow to support the last three Go versions (<a href="https://redirect.github.com/go-playground/validator/issues/1393">#1393</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/09c1323276ffada71562c1b8bf0554a0ed5ddd1d"><code>09c1323</code></a> add german translations (<a href="https://redirect.github.com/go-playground/validator/issues/1322">#1322</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/45dd6e3bd61a7659b16d9c624e3e0174223a5fd6"><code>45dd6e3</code></a> Add Korean (<a href="https://redirect.github.com/go-playground/validator/issues/1338">#1338</a>)</li>
<li><a href="https://github.com/go-playground/validator/commit/97d2bf07aad31b97b64fcd8213f922f159e62361"><code>97d2bf0</code></a> Fix length check on dns_rfc1035_label tag (<a href="https://redirect.github.com/go-playground/validator/issues/1214">#1214</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/go-playground/validator/compare/v10.22.1...v10.26.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/go-playground/validator/v10&package-manager=go_modules&previous-version=10.22.1&new-version=10.26.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/docs/doc.md b/docs/doc.md
index cd65139..ce46665 100644
--- a/docs/doc.md
+++ b/docs/doc.md
@@ -1393,13 +1393,19 @@ func main() {
 
 ### HTML rendering
 
-Using LoadHTMLGlob() or LoadHTMLFiles()
+Using LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS()
 
 ```go
+//go:embed templates/*
+var templates embed.FS
+
 func main() {
   router := gin.Default()
   router.LoadHTMLGlob("templates/*")
   //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
+  //router.LoadHTMLFS(http.Dir("templates"), "template1.html", "template2.html")
+  //or
+  //router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html")
   router.GET("/index", func(c *gin.Context) {
     c.HTML(http.StatusOK, "index.tmpl", gin.H{
       "title": "Main website",
diff --git a/gin.go b/gin.go
index 0761c14..f9813e1 100644
--- a/gin.go
+++ b/gin.go
@@ -16,6 +16,7 @@ import (
 	"sync"
 
 	"github.com/gin-gonic/gin/internal/bytesconv"
+	filesystem "github.com/gin-gonic/gin/internal/fs"
 	"github.com/gin-gonic/gin/render"
 
 	"github.com/quic-go/quic-go/http3"
@@ -285,6 +286,19 @@ func (engine *Engine) LoadHTMLFiles(files ...string) {
 	engine.SetHTMLTemplate(templ)
 }
 
+// LoadHTMLFS loads an http.FileSystem and a slice of patterns
+// and associates the result with HTML renderer.
+func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
+	if IsDebugging() {
+		engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
+		return
+	}
+
+	templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(
+		filesystem.FileSystem{FileSystem: fs}, patterns...))
+	engine.SetHTMLTemplate(templ)
+}
+
 // SetHTMLTemplate associate a template with HTML renderer.
 func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
 	if len(engine.trees) > 0 {
diff --git a/ginS/gins.go b/ginS/gins.go
index 1dcb191..3e6a92e 100644
--- a/ginS/gins.go
+++ b/ginS/gins.go
@@ -32,6 +32,11 @@ func LoadHTMLFiles(files ...string) {
 	engine().LoadHTMLFiles(files...)
 }
 
+// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS.
+func LoadHTMLFS(fs http.FileSystem, patterns ...string) {
+	engine().LoadHTMLFS(fs, patterns...)
+}
+
 // SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
 func SetHTMLTemplate(templ *template.Template) {
 	engine().SetHTMLTemplate(templ)
diff --git a/go.mod b/go.mod
index b993ca5..397db4a 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.23.0
 require (
 	github.com/bytedance/sonic v1.13.1
 	github.com/gin-contrib/sse v1.1.0
-	github.com/go-playground/validator/v10 v10.22.1
+	github.com/go-playground/validator/v10 v10.26.0
 	github.com/goccy/go-json v0.10.2
 	github.com/json-iterator/go v1.1.12
 	github.com/mattn/go-isatty v0.0.20
@@ -22,7 +22,7 @@ require (
 	github.com/bytedance/sonic/loader v0.2.4 // indirect
 	github.com/cloudwego/base64x v0.1.5 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
diff --git a/go.sum b/go.sum
index c107dc0..d2fc985 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
 github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
 github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -24,8 +24,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
-github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
+github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
diff --git a/internal/fs/fs.go b/internal/fs/fs.go
new file mode 100644
index 0000000..524ac08
--- /dev/null
+++ b/internal/fs/fs.go
@@ -0,0 +1,22 @@
+package fs
+
+import (
+	"io/fs"
+	"net/http"
+)
+
+// FileSystem implements an [fs.FS].
+type FileSystem struct {
+	http.FileSystem
+}
+
+// Open passes `Open` to the upstream implementation and return an [fs.File].
+func (o FileSystem) Open(name string) (fs.File, error) {
+	f, err := o.FileSystem.Open(name)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return fs.File(f), nil
+}
diff --git a/render/html.go b/render/html.go
index c308408..f5e7455 100644
--- a/render/html.go
+++ b/render/html.go
@@ -7,6 +7,8 @@ package render
 import (
 	"html/template"
 	"net/http"
+
+	"github.com/gin-gonic/gin/internal/fs"
 )
 
 // Delims represents a set of Left and Right delimiters for HTML template rendering.
@@ -31,10 +33,12 @@ type HTMLProduction struct {
 
 // HTMLDebug contains template delims and pattern and function with file list.
 type HTMLDebug struct {
-	Files   []string
-	Glob    string
-	Delims  Delims
-	FuncMap template.FuncMap
+	Files      []string
+	Glob       string
+	FileSystem http.FileSystem
+	Patterns   []string
+	Delims     Delims
+	FuncMap    template.FuncMap
 }
 
 // HTML contains template reference and its name with given interface object.
@@ -73,7 +77,11 @@ func (r HTMLDebug) loadTemplate() *template.Template {
 	if r.Glob != "" {
 		return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
 	}
-	panic("the HTML debug render was created without files or glob pattern")
+	if r.FileSystem != nil && len(r.Patterns) > 0 {
+		return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(
+			fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...))
+	}
+	panic("the HTML debug render was created without files or glob pattern or file system with patterns")
 }
 
 // Render (HTML) executes template and writes its result with custom ContentType for response.
diff --git a/render/json.go b/render/json.go
index fc8dea4..a6b54dc 100644
--- a/render/json.go
+++ b/render/json.go
@@ -151,7 +151,7 @@ func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
 }
 
 // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
-func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
+func (r AsciiJSON) Render(w http.ResponseWriter) error {
 	r.WriteContentType(w)
 	ret, err := json.Marshal(r.Data)
 	if err != nil {
@@ -159,12 +159,15 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
 	}
 
 	var buffer bytes.Buffer
+	escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences
+
 	for _, r := range bytesconv.BytesToString(ret) {
-		cvt := string(r)
 		if r >= 128 {
-			cvt = fmt.Sprintf("\\u%04x", int64(r))
+			escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf
+			buffer.Write(escapeBuf)
+		} else {
+			buffer.WriteByte(byte(r))
 		}
-		buffer.WriteString(cvt)
 	}
 
 	_, err = w.Write(buffer.Bytes())


Test patch:
diff --git a/gin_test.go b/gin_test.go
index 850ae09..a80b690 100644
--- a/gin_test.go
+++ b/gin_test.go
@@ -325,6 +325,115 @@ func TestLoadHTMLFilesFuncMap(t *testing.T) {
 	assert.Equal(t, "Date: 2017/07/01", string(resp))
 }
 
+var tmplFS = http.Dir("testdata/template")
+
+func TestLoadHTMLFSTestMode(t *testing.T) {
+	ts := setupHTMLFiles(
+		t,
+		TestMode,
+		false,
+		func(router *Engine) {
+			router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
+		},
+	)
+	defer ts.Close()
+
+	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
+	if err != nil {
+		t.Error(err)
+	}
+
+	resp, _ := io.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp))
+}
+
+func TestLoadHTMLFSDebugMode(t *testing.T) {
+	ts := setupHTMLFiles(
+		t,
+		DebugMode,
+		false,
+		func(router *Engine) {
+			router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
+		},
+	)
+	defer ts.Close()
+
+	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
+	if err != nil {
+		t.Error(err)
+	}
+
+	resp, _ := io.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp))
+}
+
+func TestLoadHTMLFSReleaseMode(t *testing.T) {
+	ts := setupHTMLFiles(
+		t,
+		ReleaseMode,
+		false,
+		func(router *Engine) {
+			router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
+		},
+	)
+	defer ts.Close()
+
+	res, err := http.Get(fmt.Sprintf("%s/test", ts.URL))
+	if err != nil {
+		t.Error(err)
+	}
+
+	resp, _ := io.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp))
+}
+
+func TestLoadHTMLFSUsingTLS(t *testing.T) {
+	ts := setupHTMLFiles(
+		t,
+		TestMode,
+		true,
+		func(router *Engine) {
+			router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
+		},
+	)
+	defer ts.Close()
+
+	// Use InsecureSkipVerify for avoiding `x509: certificate signed by unknown authority` error
+	tr := &http.Transport{
+		TLSClientConfig: &tls.Config{
+			InsecureSkipVerify: true,
+		},
+	}
+	client := &http.Client{Transport: tr}
+	res, err := client.Get(fmt.Sprintf("%s/test", ts.URL))
+	if err != nil {
+		t.Error(err)
+	}
+
+	resp, _ := io.ReadAll(res.Body)
+	assert.Equal(t, "<h1>Hello world</h1>", string(resp))
+}
+
+func TestLoadHTMLFSFuncMap(t *testing.T) {
+	ts := setupHTMLFiles(
+		t,
+		TestMode,
+		false,
+		func(router *Engine) {
+			router.LoadHTMLFS(tmplFS, "hello.tmpl", "raw.tmpl")
+		},
+	)
+	defer ts.Close()
+
+	res, err := http.Get(fmt.Sprintf("%s/raw", ts.URL))
+	if err != nil {
+		t.Error(err)
+	}
+
+	resp, _ := io.ReadAll(res.Body)
+	assert.Equal(t, "Date: 2017/07/01", string(resp))
+}
+
 func TestAddRoute(t *testing.T) {
 	router := New()
 	router.addRoute(http.MethodGet, "/", HandlersChain{func(_ *Context) {}})
diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go
new file mode 100644
index 0000000..113e92b
--- /dev/null
+++ b/internal/fs/fs_test.go
@@ -0,0 +1,49 @@
+package fs
+
+import (
+	"errors"
+	"net/http"
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+type mockFileSystem struct {
+	open func(name string) (http.File, error)
+}
+
+func (m *mockFileSystem) Open(name string) (http.File, error) {
+	return m.open(name)
+}
+
+func TesFileSystem_Open(t *testing.T) {
+	var testFile *os.File
+	mockFS := &mockFileSystem{
+		open: func(name string) (http.File, error) {
+			return testFile, nil
+		},
+	}
+	fs := &FileSystem{mockFS}
+
+	file, err := fs.Open("foo")
+
+	require.NoError(t, err)
+	assert.Equal(t, testFile, file)
+}
+
+func TestFileSystem_Open_err(t *testing.T) {
+	testError := errors.New("mock")
+	mockFS := &mockFileSystem{
+		open: func(_ string) (http.File, error) {
+			return nil, testError
+		},
+	}
+	fs := &FileSystem{mockFS}
+
+	file, err := fs.Open("foo")
+
+	require.ErrorIs(t, err, testError)
+	assert.Nil(t, file)
+}
diff --git a/render/render_test.go b/render/render_test.go
index ad633b0..4dd2a3a 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -489,10 +489,12 @@ func TestRenderHTMLTemplateEmptyName(t *testing.T) {
 func TestRenderHTMLDebugFiles(t *testing.T) {
 	w := httptest.NewRecorder()
 	htmlRender := HTMLDebug{
-		Files:   []string{"../testdata/template/hello.tmpl"},
-		Glob:    "",
-		Delims:  Delims{Left: "{[{", Right: "}]}"},
-		FuncMap: nil,
+		Files:      []string{"../testdata/template/hello.tmpl"},
+		Glob:       "",
+		FileSystem: nil,
+		Patterns:   nil,
+		Delims:     Delims{Left: "{[{", Right: "}]}"},
+		FuncMap:    nil,
 	}
 	instance := htmlRender.Instance("hello.tmpl", map[string]any{
 		"name": "thinkerou",
@@ -508,10 +510,33 @@ func TestRenderHTMLDebugFiles(t *testing.T) {
 func TestRenderHTMLDebugGlob(t *testing.T) {
 	w := httptest.NewRecorder()
 	htmlRender := HTMLDebug{
-		Files:   nil,
-		Glob:    "../testdata/template/hello*",
-		Delims:  Delims{Left: "{[{", Right: "}]}"},
-		FuncMap: nil,
+		Files:      nil,
+		Glob:       "../testdata/template/hello*",
+		FileSystem: nil,
+		Patterns:   nil,
+		Delims:     Delims{Left: "{[{", Right: "}]}"},
+		FuncMap:    nil,
+	}
+	instance := htmlRender.Instance("hello.tmpl", map[string]any{
+		"name": "thinkerou",
+	})
+
+	err := instance.Render(w)
+
+	require.NoError(t, err)
+	assert.Equal(t, "<h1>Hello thinkerou</h1>", w.Body.String())
+	assert.Equal(t, "text/html; charset=utf-8", w.Header().Get("Content-Type"))
+}
+
+func TestRenderHTMLDebugFS(t *testing.T) {
+	w := httptest.NewRecorder()
+	htmlRender := HTMLDebug{
+		Files:      nil,
+		Glob:       "",
+		FileSystem: http.Dir("../testdata/template"),
+		Patterns:   []string{"hello.tmpl"},
+		Delims:     Delims{Left: "{[{", Right: "}]}"},
+		FuncMap:    nil,
 	}
 	instance := htmlRender.Instance("hello.tmpl", map[string]any{
 		"name": "thinkerou",
@@ -526,10 +551,12 @@ func TestRenderHTMLDebugGlob(t *testing.T) {
 
 func TestRenderHTMLDebugPanics(t *testing.T) {
 	htmlRender := HTMLDebug{
-		Files:   nil,
-		Glob:    "",
-		Delims:  Delims{"{{", "}}"},
-		FuncMap: nil,
+		Files:      nil,
+		Glob:       "",
+		FileSystem: nil,
+		Patterns:   nil,
+		Delims:     Delims{"{{", "}}"},
+		FuncMap:    nil,
 	}
 	assert.Panics(t, func() { htmlRender.Instance("", nil) })
 }