diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go
index c2134f702a..2eb85cd8a7 100644
--- a/models/migrations/base/tests.go
+++ b/models/migrations/base/tests.go
@@ -13,9 +13,9 @@ import (
 	"testing"
 
 	"code.gitea.io/gitea/models/unittest"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/testlogger"
 
 	"github.com/stretchr/testify/require"
@@ -92,10 +92,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
 func MainTest(m *testing.M) {
 	testlogger.Init()
 
-	giteaRoot := base.SetupGiteaRoot()
-	if giteaRoot == "" {
-		testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
-	}
+	giteaRoot := test.SetupGiteaRoot()
 	giteaBinary := "gitea"
 	if runtime.GOOS == "windows" {
 		giteaBinary += ".exe"
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index 5a1c27dbea..5794d5109e 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -14,13 +14,13 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/system"
 	"code.gitea.io/gitea/modules/auth/password/hash"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/setting/config"
 	"code.gitea.io/gitea/modules/storage"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/stretchr/testify/assert"
@@ -235,5 +235,5 @@ func PrepareTestEnv(t testing.TB) {
 	assert.NoError(t, PrepareTestDatabase())
 	metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
 	assert.NoError(t, SyncDirs(metaPath, setting.RepoRootPath))
-	base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
+	test.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
 }
diff --git a/modules/base/base.go b/modules/base/base.go
deleted file mode 100644
index dddce202da..0000000000
--- a/modules/base/base.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2014 The Gogs Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package base
-
-type (
-	// TplName template relative path type
-	TplName string
-)
diff --git a/modules/base/tool.go b/modules/base/tool.go
index 928c80700b..2303e64a68 100644
--- a/modules/base/tool.go
+++ b/modules/base/tool.go
@@ -13,9 +13,6 @@ import (
 	"errors"
 	"fmt"
 	"hash"
-	"os"
-	"path/filepath"
-	"runtime"
 	"strconv"
 	"strings"
 	"time"
@@ -189,49 +186,3 @@ func EntryIcon(entry *git.TreeEntry) string {
 
 	return "file"
 }
-
-// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
-func SetupGiteaRoot() string {
-	giteaRoot := os.Getenv("GITEA_ROOT")
-	if giteaRoot == "" {
-		_, filename, _, _ := runtime.Caller(0)
-		giteaRoot = strings.TrimSuffix(filename, "modules/base/tool.go")
-		wd, err := os.Getwd()
-		if err != nil {
-			rel, err := filepath.Rel(giteaRoot, wd)
-			if err != nil && strings.HasPrefix(filepath.ToSlash(rel), "../") {
-				giteaRoot = wd
-			}
-		}
-		if _, err := os.Stat(filepath.Join(giteaRoot, "gitea")); os.IsNotExist(err) {
-			giteaRoot = ""
-		} else if err := os.Setenv("GITEA_ROOT", giteaRoot); err != nil {
-			giteaRoot = ""
-		}
-	}
-	return giteaRoot
-}
-
-// FormatNumberSI format a number
-func FormatNumberSI(data any) string {
-	var num int64
-	if num1, ok := data.(int64); ok {
-		num = num1
-	} else if num1, ok := data.(int); ok {
-		num = int64(num1)
-	} else {
-		return ""
-	}
-
-	if num < 1000 {
-		return fmt.Sprintf("%d", num)
-	} else if num < 1000000 {
-		num2 := float32(num) / float32(1000.0)
-		return fmt.Sprintf("%.1fk", num2)
-	} else if num < 1000000000 {
-		num2 := float32(num) / float32(1000000.0)
-		return fmt.Sprintf("%.1fM", num2)
-	}
-	num2 := float32(num) / float32(1000000000.0)
-	return fmt.Sprintf("%.1fG", num2)
-}
diff --git a/modules/base/tool_test.go b/modules/base/tool_test.go
index f63679048e..de6c311856 100644
--- a/modules/base/tool_test.go
+++ b/modules/base/tool_test.go
@@ -169,18 +169,3 @@ func TestInt64sToStrings(t *testing.T) {
 }
 
 // TODO: Test EntryIcon
-
-func TestSetupGiteaRoot(t *testing.T) {
-	t.Setenv("GITEA_ROOT", "test")
-	assert.Equal(t, "test", SetupGiteaRoot())
-	t.Setenv("GITEA_ROOT", "")
-	assert.NotEqual(t, "test", SetupGiteaRoot())
-}
-
-func TestFormatNumberSI(t *testing.T) {
-	assert.Equal(t, "125", FormatNumberSI(int(125)))
-	assert.Equal(t, "1.3k", FormatNumberSI(int64(1317)))
-	assert.Equal(t, "21.3M", FormatNumberSI(21317675))
-	assert.Equal(t, "45.7G", FormatNumberSI(45721317675))
-	assert.Equal(t, "", FormatNumberSI("test"))
-}
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index ff9673ccef..880769dc65 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -9,7 +9,6 @@ import (
 	"html"
 	"html/template"
 	"net/url"
-	"reflect"
 	"strings"
 	"time"
 
@@ -69,7 +68,7 @@ func NewFuncMap() template.FuncMap {
 		// -----------------------------------------------------------------
 		// time / number / format
 		"FileSize": base.FileSize,
-		"CountFmt": base.FormatNumberSI,
+		"CountFmt": countFmt,
 		"Sec2Time": util.SecToTime,
 
 		"TimeEstimateString": timeEstimateString,
@@ -239,29 +238,8 @@ func iif(condition any, vals ...any) any {
 }
 
 func isTemplateTruthy(v any) bool {
-	if v == nil {
-		return false
-	}
-
-	rv := reflect.ValueOf(v)
-	switch rv.Kind() {
-	case reflect.Bool:
-		return rv.Bool()
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return rv.Int() != 0
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		return rv.Uint() != 0
-	case reflect.Float32, reflect.Float64:
-		return rv.Float() != 0
-	case reflect.Complex64, reflect.Complex128:
-		return rv.Complex() != 0
-	case reflect.String, reflect.Slice, reflect.Array, reflect.Map:
-		return rv.Len() > 0
-	case reflect.Struct:
-		return true
-	default:
-		return !rv.IsNil()
-	}
+	truth, _ := template.IsTrue(v)
+	return truth
 }
 
 // evalTokens evaluates the expression by tokens and returns the result, see the comment of eval.Expr for details.
@@ -286,14 +264,6 @@ func userThemeName(user *user_model.User) string {
 	return setting.UI.DefaultTheme
 }
 
-func timeEstimateString(timeSec any) string {
-	v, _ := util.ToInt64(timeSec)
-	if v == 0 {
-		return ""
-	}
-	return util.TimeEstimateString(v)
-}
-
 // QueryBuild builds a query string from a list of key-value pairs.
 // It omits the nil and empty strings, but it doesn't omit other zero values,
 // because the zero value of number types may have a meaning.
diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go
index 3e17e86c66..a530d484bc 100644
--- a/modules/templates/helper_test.go
+++ b/modules/templates/helper_test.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 	"testing"
 
+	"code.gitea.io/gitea/modules/htmlutil"
 	"code.gitea.io/gitea/modules/util"
 
 	"github.com/stretchr/testify/assert"
@@ -65,31 +66,12 @@ func TestSanitizeHTML(t *testing.T) {
 	assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))
 }
 
-func TestTemplateTruthy(t *testing.T) {
+func TestTemplateIif(t *testing.T) {
 	tmpl := template.New("test")
 	tmpl.Funcs(template.FuncMap{"Iif": iif})
 	template.Must(tmpl.Parse(`{{if .Value}}true{{else}}false{{end}}:{{Iif .Value "true" "false"}}`))
 
-	cases := []any{
-		nil, false, true, "", "string", 0, 1,
-		byte(0), byte(1), int64(0), int64(1), float64(0), float64(1),
-		complex(0, 0), complex(1, 0),
-		(chan int)(nil), make(chan int),
-		(func())(nil), func() {},
-		util.ToPointer(0), util.ToPointer(util.ToPointer(0)),
-		util.ToPointer(1), util.ToPointer(util.ToPointer(1)),
-		[0]int{},
-		[1]int{0},
-		[]int(nil),
-		[]int{},
-		[]int{0},
-		map[any]any(nil),
-		map[any]any{},
-		map[any]any{"k": "v"},
-		(*struct{})(nil),
-		struct{}{},
-		util.ToPointer(struct{}{}),
-	}
+	cases := []any{nil, false, true, "", "string", 0, 1}
 	w := &strings.Builder{}
 	truthyCount := 0
 	for i, v := range cases {
@@ -102,3 +84,37 @@ func TestTemplateTruthy(t *testing.T) {
 	}
 	assert.True(t, truthyCount != 0 && truthyCount != len(cases))
 }
+
+func TestTemplateEscape(t *testing.T) {
+	execTmpl := func(code string) string {
+		tmpl := template.New("test")
+		tmpl.Funcs(template.FuncMap{"QueryBuild": QueryBuild, "HTMLFormat": htmlutil.HTMLFormat})
+		template.Must(tmpl.Parse(code))
+		w := &strings.Builder{}
+		assert.NoError(t, tmpl.Execute(w, nil))
+		return w.String()
+	}
+
+	t.Run("Golang URL Escape", func(t *testing.T) {
+		// Golang template considers "href", "*src*", "*uri*", "*url*" (and more) ... attributes as contentTypeURL and does auto-escaping
+		actual := execTmpl(`<a href="?a={{"%"}}"></a>`)
+		assert.Equal(t, `<a href="?a=%25"></a>`, actual)
+		actual = execTmpl(`<a data-xxx-url="?a={{"%"}}"></a>`)
+		assert.Equal(t, `<a data-xxx-url="?a=%25"></a>`, actual)
+	})
+	t.Run("Golang URL No-escape", func(t *testing.T) {
+		// non-URL content isn't auto-escaped
+		actual := execTmpl(`<a data-link="?a={{"%"}}"></a>`)
+		assert.Equal(t, `<a data-link="?a=%"></a>`, actual)
+	})
+	t.Run("QueryBuild", func(t *testing.T) {
+		actual := execTmpl(`<a href="{{QueryBuild "?" "a" "%"}}"></a>`)
+		assert.Equal(t, `<a href="?a=%25"></a>`, actual)
+		actual = execTmpl(`<a href="?{{QueryBuild "a" "%"}}"></a>`)
+		assert.Equal(t, `<a href="?a=%25"></a>`, actual)
+	})
+	t.Run("HTMLFormat", func(t *testing.T) {
+		actual := execTmpl("{{HTMLFormat `<a k=\"%s\">%s</a>` `\"` `<>`}}")
+		assert.Equal(t, `<a k="&#34;">&lt;&gt;</a>`, actual)
+	})
+}
diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go
index e7e805ed30..529284f7e8 100644
--- a/modules/templates/htmlrenderer.go
+++ b/modules/templates/htmlrenderer.go
@@ -29,6 +29,8 @@ import (
 
 type TemplateExecutor scopedtmpl.TemplateExecutor
 
+type TplName string
+
 type HTMLRender struct {
 	templates atomic.Pointer[scopedtmpl.ScopedTemplate]
 }
@@ -40,7 +42,8 @@ var (
 
 var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")
 
-func (h *HTMLRender) HTML(w io.Writer, status int, name string, data any, ctx context.Context) error { //nolint:revive
+func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ctx context.Context) error { //nolint:revive
+	name := string(tplName)
 	if respWriter, ok := w.(http.ResponseWriter); ok {
 		if respWriter.Header().Get("Content-Type") == "" {
 			respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
diff --git a/modules/templates/util_format.go b/modules/templates/util_format.go
new file mode 100644
index 0000000000..bee6fb7b75
--- /dev/null
+++ b/modules/templates/util_format.go
@@ -0,0 +1,37 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+	"fmt"
+
+	"code.gitea.io/gitea/modules/util"
+)
+
+func timeEstimateString(timeSec any) string {
+	v, _ := util.ToInt64(timeSec)
+	if v == 0 {
+		return ""
+	}
+	return util.TimeEstimateString(v)
+}
+
+func countFmt(data any) string {
+	// legacy code, not ideal, still used in some places
+	num, err := util.ToInt64(data)
+	if err != nil {
+		return ""
+	}
+	if num < 1000 {
+		return fmt.Sprintf("%d", num)
+	} else if num < 1_000_000 {
+		num2 := float32(num) / 1000.0
+		return fmt.Sprintf("%.1fk", num2)
+	} else if num < 1_000_000_000 {
+		num2 := float32(num) / 1_000_000.0
+		return fmt.Sprintf("%.1fM", num2)
+	}
+	num2 := float32(num) / 1_000_000_000.0
+	return fmt.Sprintf("%.1fG", num2)
+}
diff --git a/modules/templates/util_format_test.go b/modules/templates/util_format_test.go
new file mode 100644
index 0000000000..8d466faff0
--- /dev/null
+++ b/modules/templates/util_format_test.go
@@ -0,0 +1,18 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package templates
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCountFmt(t *testing.T) {
+	assert.Equal(t, "125", countFmt(125))
+	assert.Equal(t, "1.3k", countFmt(int64(1317)))
+	assert.Equal(t, "21.3M", countFmt(21317675))
+	assert.Equal(t, "45.7G", countFmt(45721317675))
+	assert.Equal(t, "", countFmt("test"))
+}
diff --git a/modules/test/utils.go b/modules/test/utils.go
index 8dee92fbce..ec4c976388 100644
--- a/modules/test/utils.go
+++ b/modules/test/utils.go
@@ -4,11 +4,16 @@
 package test
 
 import (
+	"fmt"
 	"net/http"
 	"net/http/httptest"
+	"os"
+	"path/filepath"
+	"runtime"
 	"strings"
 
 	"code.gitea.io/gitea/modules/json"
+	"code.gitea.io/gitea/modules/util"
 )
 
 // RedirectURL returns the redirect URL of a http response.
@@ -41,3 +46,19 @@ func MockVariableValue[T any](p *T, v ...T) (reset func()) {
 	}
 	return func() { *p = old }
 }
+
+// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
+func SetupGiteaRoot() string {
+	giteaRoot := os.Getenv("GITEA_ROOT")
+	if giteaRoot != "" {
+		return giteaRoot
+	}
+	_, filename, _, _ := runtime.Caller(0)
+	giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename)))
+	fixturesDir := filepath.Join(giteaRoot, "models", "fixtures")
+	if exist, _ := util.IsDir(fixturesDir); !exist {
+		panic(fmt.Sprintf("fixtures directory not found: %s", fixturesDir))
+	}
+	_ = os.Setenv("GITEA_ROOT", giteaRoot)
+	return giteaRoot
+}
diff --git a/modules/test/utils_test.go b/modules/test/utils_test.go
new file mode 100644
index 0000000000..0469ce97f2
--- /dev/null
+++ b/modules/test/utils_test.go
@@ -0,0 +1,17 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestSetupGiteaRoot(t *testing.T) {
+	t.Setenv("GITEA_ROOT", "test")
+	assert.Equal(t, "test", SetupGiteaRoot())
+	t.Setenv("GITEA_ROOT", "")
+	assert.NotEqual(t, "test", SetupGiteaRoot())
+}
diff --git a/routers/common/errpage.go b/routers/common/errpage.go
index 402ca44c12..c0b16dbdde 100644
--- a/routers/common/errpage.go
+++ b/routers/common/errpage.go
@@ -8,7 +8,6 @@ import (
 	"net/http"
 
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/httpcache"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -18,7 +17,7 @@ import (
 	"code.gitea.io/gitea/services/context"
 )
 
-const tplStatus500 base.TplName = "status/500"
+const tplStatus500 templates.TplName = "status/500"
 
 // RenderPanicErrorPage renders a 500 page, and it never panics
 func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
@@ -47,7 +46,7 @@ func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
 		ctxData["ErrorMsg"] = "PANIC: " + combinedErr
 	}
 
-	err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, string(tplStatus500), ctxData, tmplCtx)
+	err = templates.HTMLRenderer().HTML(w, http.StatusInternalServerError, tplStatus500, ctxData, tmplCtx)
 	if err != nil {
 		log.Error("Error occurs again when rendering error page: %v", err)
 		w.WriteHeader(http.StatusInternalServerError)
diff --git a/routers/install/install.go b/routers/install/install.go
index e420d36da5..1819bafc62 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -21,7 +21,6 @@ import (
 	system_model "code.gitea.io/gitea/models/system"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/password/hash"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/generate"
 	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/log"
@@ -43,8 +42,8 @@ import (
 
 const (
 	// tplInstall template for installation page
-	tplInstall     base.TplName = "install"
-	tplPostInstall base.TplName = "post-install"
+	tplInstall     templates.TplName = "install"
+	tplPostInstall templates.TplName = "post-install"
 )
 
 // getSupportedDbTypeNames returns a slice for supported database types and names. The slice is used to keep the order
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 37c54b5362..3902a1efb1 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -21,6 +21,7 @@ import (
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/updatechecker"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
@@ -31,14 +32,14 @@ import (
 )
 
 const (
-	tplDashboard    base.TplName = "admin/dashboard"
-	tplSystemStatus base.TplName = "admin/system_status"
-	tplSelfCheck    base.TplName = "admin/self_check"
-	tplCron         base.TplName = "admin/cron"
-	tplQueue        base.TplName = "admin/queue"
-	tplStacktrace   base.TplName = "admin/stacktrace"
-	tplQueueManage  base.TplName = "admin/queue_manage"
-	tplStats        base.TplName = "admin/stats"
+	tplDashboard    templates.TplName = "admin/dashboard"
+	tplSystemStatus templates.TplName = "admin/system_status"
+	tplSelfCheck    templates.TplName = "admin/self_check"
+	tplCron         templates.TplName = "admin/cron"
+	tplQueue        templates.TplName = "admin/queue"
+	tplStacktrace   templates.TplName = "admin/stacktrace"
+	tplQueueManage  templates.TplName = "admin/queue_manage"
+	tplStats        templates.TplName = "admin/stats"
 )
 
 var sysStatus struct {
diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go
index 9b48f21eca..aec6349f21 100644
--- a/routers/web/admin/applications.go
+++ b/routers/web/admin/applications.go
@@ -9,15 +9,15 @@ import (
 
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	user_setting "code.gitea.io/gitea/routers/web/user/setting"
 	"code.gitea.io/gitea/services/context"
 )
 
 var (
-	tplSettingsApplications          base.TplName = "admin/applications/list"
-	tplSettingsOauth2ApplicationEdit base.TplName = "admin/applications/oauth2_edit"
+	tplSettingsApplications          templates.TplName = "admin/applications/list"
+	tplSettingsOauth2ApplicationEdit templates.TplName = "admin/applications/oauth2_edit"
 )
 
 func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers {
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 60e2b7c86f..6a65cfa697 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -15,9 +15,9 @@ import (
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/modules/auth/pam"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	auth_service "code.gitea.io/gitea/services/auth"
@@ -33,9 +33,9 @@ import (
 )
 
 const (
-	tplAuths    base.TplName = "admin/auth/list"
-	tplAuthNew  base.TplName = "admin/auth/new"
-	tplAuthEdit base.TplName = "admin/auth/edit"
+	tplAuths    templates.TplName = "admin/auth/list"
+	tplAuthNew  templates.TplName = "admin/auth/new"
+	tplAuthEdit templates.TplName = "admin/auth/edit"
 )
 
 var (
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index d067250a5b..520f14e89f 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	system_model "code.gitea.io/gitea/models/system"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/setting/config"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/mailer"
@@ -26,8 +26,8 @@ import (
 )
 
 const (
-	tplConfig         base.TplName = "admin/config"
-	tplConfigSettings base.TplName = "admin/config_settings"
+	tplConfig         templates.TplName = "admin/config"
+	tplConfigSettings templates.TplName = "admin/config_settings"
 )
 
 // SendTestMail send test mail to confirm mail service is OK
diff --git a/routers/web/admin/emails.go b/routers/web/admin/emails.go
index e9c97d8b8f..e925de8937 100644
--- a/routers/web/admin/emails.go
+++ b/routers/web/admin/emails.go
@@ -10,16 +10,16 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/user"
 )
 
 const (
-	tplEmails base.TplName = "admin/emails/list"
+	tplEmails templates.TplName = "admin/emails/list"
 )
 
 // Emails show all emails
diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go
index 91ca6e3fa7..34dc0fc9b0 100644
--- a/routers/web/admin/hooks.go
+++ b/routers/web/admin/hooks.go
@@ -7,15 +7,15 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models/webhook"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
 	// tplAdminHooks template path to render hook settings
-	tplAdminHooks base.TplName = "admin/hooks"
+	tplAdminHooks templates.TplName = "admin/hooks"
 )
 
 // DefaultOrSystemWebhooks renders both admin default and system webhook list pages
diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go
index 5f7432e629..21a8ab0d17 100644
--- a/routers/web/admin/notice.go
+++ b/routers/web/admin/notice.go
@@ -10,14 +10,14 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	system_model "code.gitea.io/gitea/models/system"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplNotices base.TplName = "admin/notice"
+	tplNotices templates.TplName = "admin/notice"
 )
 
 // Notices show notices for admin
diff --git a/routers/web/admin/orgs.go b/routers/web/admin/orgs.go
index cea28f8220..35e61efa17 100644
--- a/routers/web/admin/orgs.go
+++ b/routers/web/admin/orgs.go
@@ -7,15 +7,15 @@ package admin
 import (
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/routers/web/explore"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplOrgs base.TplName = "admin/org/list"
+	tplOrgs templates.TplName = "admin/org/list"
 )
 
 // Organizations show all the organizations
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 2b9edc622d..da345f2f89 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -10,16 +10,16 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 	packages_service "code.gitea.io/gitea/services/packages"
 	packages_cleanup_service "code.gitea.io/gitea/services/packages/cleanup"
 )
 
 const (
-	tplPackagesList base.TplName = "admin/packages/list"
+	tplPackagesList templates.TplName = "admin/packages/list"
 )
 
 // Packages shows all packages
diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go
index 75e5ee5d86..27d39dcf4b 100644
--- a/routers/web/admin/repos.go
+++ b/routers/web/admin/repos.go
@@ -12,9 +12,9 @@ import (
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/web/explore"
 	"code.gitea.io/gitea/services/context"
@@ -22,8 +22,8 @@ import (
 )
 
 const (
-	tplRepos          base.TplName = "admin/repo/list"
-	tplUnadoptedRepos base.TplName = "admin/repo/unadopted"
+	tplRepos          templates.TplName = "admin/repo/list"
+	tplUnadoptedRepos templates.TplName = "admin/repo/unadopted"
 )
 
 // Repos show all the repositories
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index 1eaa37bfbd..cf158f7aa9 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -18,10 +18,10 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/password"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/web/explore"
@@ -33,10 +33,10 @@ import (
 )
 
 const (
-	tplUsers    base.TplName = "admin/user/list"
-	tplUserNew  base.TplName = "admin/user/new"
-	tplUserView base.TplName = "admin/user/view"
-	tplUserEdit base.TplName = "admin/user/edit"
+	tplUsers    templates.TplName = "admin/user/list"
+	tplUserNew  templates.TplName = "admin/user/new"
+	tplUserView templates.TplName = "admin/user/view"
+	tplUserEdit templates.TplName = "admin/user/edit"
 )
 
 // UserSearchDefaultAdminSort is the default sort type for admin view
diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go
index f93177bf96..fe363fe90a 100644
--- a/routers/web/auth/2fa.go
+++ b/routers/web/auth/2fa.go
@@ -9,8 +9,8 @@ import (
 
 	"code.gitea.io/gitea/models/auth"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/externalaccount"
@@ -18,8 +18,8 @@ import (
 )
 
 var (
-	tplTwofa        base.TplName = "user/auth/twofa"
-	tplTwofaScratch base.TplName = "user/auth/twofa_scratch"
+	tplTwofa        templates.TplName = "user/auth/twofa"
+	tplTwofaScratch templates.TplName = "user/auth/twofa_scratch"
 )
 
 // TwoFactor shows the user a two-factor authentication page.
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 3f16da3cdd..42736a423f 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -15,13 +15,13 @@ import (
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/password"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/eventsource"
 	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/session"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
@@ -38,10 +38,10 @@ import (
 )
 
 const (
-	tplSignIn         base.TplName = "user/auth/signin"          // for sign in page
-	tplSignUp         base.TplName = "user/auth/signup"          // for sign up page
-	TplActivate       base.TplName = "user/auth/activate"        // for activate user
-	TplActivatePrompt base.TplName = "user/auth/activate_prompt" // for showing a message for user activation
+	tplSignIn         templates.TplName = "user/auth/signin"          // for sign in page
+	tplSignUp         templates.TplName = "user/auth/signup"          // for sign up page
+	TplActivate       templates.TplName = "user/auth/activate"        // for activate user
+	TplActivatePrompt templates.TplName = "user/auth/activate_prompt" // for showing a message for user activation
 )
 
 // autoSignIn reads cookie and try to auto-login.
@@ -517,7 +517,7 @@ func SignUpPost(ctx *context.Context) {
 
 // createAndHandleCreatedUser calls createUserInContext and
 // then handleUserCreated.
-func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
+func createAndHandleCreatedUser(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
 	if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) {
 		return false
 	}
@@ -526,7 +526,7 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any
 
 // createUserInContext creates a user and handles errors within a given context.
 // Optionally a template can be specified.
-func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
+func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
 	meta := &user_model.Meta{
 		InitialIP:        ctx.RemoteAddr(),
 		InitialUserAgent: ctx.Req.UserAgent(),
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index 519431d92b..147d8d3802 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -11,9 +11,9 @@ import (
 
 	"code.gitea.io/gitea/models/auth"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	auth_service "code.gitea.io/gitea/services/auth"
@@ -25,7 +25,7 @@ import (
 	"github.com/markbates/goth"
 )
 
-var tplLinkAccount base.TplName = "user/auth/link_account"
+var tplLinkAccount templates.TplName = "user/auth/link_account"
 
 // LinkAccount shows the page where the user can decide to login or create a new account
 func LinkAccount(ctx *context.Context) {
@@ -92,7 +92,7 @@ func LinkAccount(ctx *context.Context) {
 	ctx.HTML(http.StatusOK, tplLinkAccount)
 }
 
-func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl base.TplName, invoker string, err error) {
+func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl templates.TplName, invoker string, err error) {
 	if errors.Is(err, util.ErrNotExist) {
 		ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm)
 	} else if errors.Is(err, util.ErrInvalidArgument) {
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 75f94de0ed..32c30c71e8 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -15,11 +15,11 @@ import (
 	"code.gitea.io/gitea/models/auth"
 	user_model "code.gitea.io/gitea/models/user"
 	auth_module "code.gitea.io/gitea/modules/auth"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web/middleware"
 	source_service "code.gitea.io/gitea/services/auth/source"
 	"code.gitea.io/gitea/services/auth/source/oauth2"
@@ -194,7 +194,7 @@ func SignInOAuthCallback(ctx *context.Context) {
 			u.IsAdmin = isAdmin.ValueOrDefault(false)
 			u.IsRestricted = isRestricted.ValueOrDefault(false)
 
-			if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
+			if !createAndHandleCreatedUser(ctx, templates.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
 				// error already handled
 				return
 			}
diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go
index 1aebc047bd..6262ad8a6d 100644
--- a/routers/web/auth/oauth2_provider.go
+++ b/routers/web/auth/oauth2_provider.go
@@ -18,6 +18,7 @@ import (
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	auth_service "code.gitea.io/gitea/services/auth"
 	"code.gitea.io/gitea/services/context"
@@ -29,8 +30,8 @@ import (
 )
 
 const (
-	tplGrantAccess base.TplName = "user/auth/grant"
-	tplGrantError  base.TplName = "user/auth/grant_error"
+	tplGrantAccess templates.TplName = "user/auth/grant"
+	tplGrantError  templates.TplName = "user/auth/grant_error"
 )
 
 // TODO move error and responses to SDK or models
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 83268faacb..41d37ecb8b 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -10,9 +10,9 @@ import (
 
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/openid"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/auth"
@@ -21,9 +21,9 @@ import (
 )
 
 const (
-	tplSignInOpenID base.TplName = "user/auth/signin_openid"
-	tplConnectOID   base.TplName = "user/auth/signup_openid_connect"
-	tplSignUpOID    base.TplName = "user/auth/signup_openid_register"
+	tplSignInOpenID templates.TplName = "user/auth/signin_openid"
+	tplConnectOID   templates.TplName = "user/auth/signup_openid_connect"
+	tplSignUpOID    templates.TplName = "user/auth/signup_openid_register"
 )
 
 // SignInOpenID render sign in page
diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go
index 334d864c6a..3812d582e5 100644
--- a/routers/web/auth/password.go
+++ b/routers/web/auth/password.go
@@ -11,10 +11,10 @@ import (
 	"code.gitea.io/gitea/models/auth"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/password"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/modules/web/middleware"
@@ -26,9 +26,9 @@ import (
 
 var (
 	// tplMustChangePassword template for updating a user's password
-	tplMustChangePassword base.TplName = "user/auth/change_passwd"
-	tplForgotPassword     base.TplName = "user/auth/forgot_passwd"
-	tplResetPassword      base.TplName = "user/auth/reset_passwd"
+	tplMustChangePassword templates.TplName = "user/auth/change_passwd"
+	tplForgotPassword     templates.TplName = "user/auth/forgot_passwd"
+	tplResetPassword      templates.TplName = "user/auth/reset_passwd"
 )
 
 // ForgotPasswd render the forget password page
diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go
index ba25d45070..69031adeaa 100644
--- a/routers/web/auth/webauthn.go
+++ b/routers/web/auth/webauthn.go
@@ -11,9 +11,9 @@ import (
 	"code.gitea.io/gitea/models/auth"
 	user_model "code.gitea.io/gitea/models/user"
 	wa "code.gitea.io/gitea/modules/auth/webauthn"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/externalaccount"
 
@@ -21,7 +21,7 @@ import (
 	"github.com/go-webauthn/webauthn/webauthn"
 )
 
-var tplWebAuthn base.TplName = "user/auth/webauthn"
+var tplWebAuthn templates.TplName = "user/auth/webauthn"
 
 // WebAuthn shows the WebAuthn login page
 func WebAuthn(ctx *context.Context) {
diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go
index 0068c9fe88..0bc84d2d1e 100644
--- a/routers/web/devtest/devtest.go
+++ b/routers/web/devtest/devtest.go
@@ -9,7 +9,6 @@ import (
 	"strings"
 	"time"
 
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
@@ -62,5 +61,5 @@ func Tmpl(ctx *context.Context) {
 		time.Sleep(2 * time.Second)
 	}
 
-	ctx.HTML(http.StatusOK, base.TplName("devtest"+path.Clean("/"+ctx.PathParam("sub"))))
+	ctx.HTML(http.StatusOK, templates.TplName("devtest"+path.Clean("/"+ctx.PathParam("sub"))))
 }
diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go
index 48f890332b..4df89253b4 100644
--- a/routers/web/explore/code.go
+++ b/routers/web/explore/code.go
@@ -8,15 +8,15 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/modules/base"
 	code_indexer "code.gitea.io/gitea/modules/indexer/code"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
 	// tplExploreCode explore code page template
-	tplExploreCode base.TplName = "explore/code"
+	tplExploreCode templates.TplName = "explore/code"
 )
 
 // Code render explore code page
diff --git a/routers/web/explore/repo.go b/routers/web/explore/repo.go
index 5b6f612e72..c421aea715 100644
--- a/routers/web/explore/repo.go
+++ b/routers/web/explore/repo.go
@@ -9,17 +9,17 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/sitemap"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
 	// tplExploreRepos explore repositories page template
-	tplExploreRepos        base.TplName = "explore/repos"
-	relevantReposOnlyParam string       = "only_show_relevant"
+	tplExploreRepos        templates.TplName = "explore/repos"
+	relevantReposOnlyParam string            = "only_show_relevant"
 )
 
 // RepoSearchOptions when calling search repositories
@@ -29,7 +29,7 @@ type RepoSearchOptions struct {
 	Restricted       bool
 	PageSize         int
 	OnlyShowRelevant bool
-	TplName          base.TplName
+	TplName          templates.TplName
 }
 
 // RenderRepoSearch render repositories search page
diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go
index c009982d42..ef103af8cf 100644
--- a/routers/web/explore/user.go
+++ b/routers/web/explore/user.go
@@ -9,20 +9,20 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/sitemap"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
 	// tplExploreUsers explore users page template
-	tplExploreUsers base.TplName = "explore/users"
+	tplExploreUsers templates.TplName = "explore/users"
 )
 
 var nullByte = []byte{0x00}
@@ -32,7 +32,7 @@ func isKeywordValid(keyword string) bool {
 }
 
 // RenderUserSearch render user search page
-func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName base.TplName) {
+func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, tplName templates.TplName) {
 	// Sitemap index for sitemap paths
 	opts.Page = int(ctx.PathParamInt64("idx"))
 	isSitemap := ctx.PathParam("idx") != ""
diff --git a/routers/web/home.go b/routers/web/home.go
index d4be0931e8..9ad495d54f 100644
--- a/routers/web/home.go
+++ b/routers/web/home.go
@@ -11,12 +11,12 @@ import (
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/sitemap"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web/middleware"
 	"code.gitea.io/gitea/routers/web/auth"
 	"code.gitea.io/gitea/routers/web/user"
@@ -25,7 +25,7 @@ import (
 
 const (
 	// tplHome home page template
-	tplHome base.TplName = "home"
+	tplHome templates.TplName = "home"
 )
 
 // Home render home page
diff --git a/routers/web/misc/swagger.go b/routers/web/misc/swagger.go
index 5fddfa8885..1ca347551c 100644
--- a/routers/web/misc/swagger.go
+++ b/routers/web/misc/swagger.go
@@ -6,12 +6,12 @@ package misc
 import (
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 // tplSwagger swagger page template
-const tplSwagger base.TplName = "swagger/ui"
+const tplSwagger templates.TplName = "swagger/ui"
 
 // Swagger render swagger-ui page with v1 json
 func Swagger(ctx *context.Context) {
diff --git a/routers/web/org/block.go b/routers/web/org/block.go
index d40458e250..aeb4bd51a8 100644
--- a/routers/web/org/block.go
+++ b/routers/web/org/block.go
@@ -6,13 +6,13 @@ package org
 import (
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsBlockedUsers base.TplName = "org/settings/blocked_users"
+	tplSettingsBlockedUsers templates.TplName = "org/settings/blocked_users"
 )
 
 func BlockedUsers(ctx *context.Context) {
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index f02c08ae76..7122aff6bd 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -13,17 +13,17 @@ import (
 	"code.gitea.io/gitea/models/organization"
 	"code.gitea.io/gitea/models/renderhelper"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplOrgHome base.TplName = "org/home"
+	tplOrgHome templates.TplName = "org/home"
 )
 
 // Home show organization home page
diff --git a/routers/web/org/members.go b/routers/web/org/members.go
index 7af087c4df..f91062957e 100644
--- a/routers/web/org/members.go
+++ b/routers/web/org/members.go
@@ -9,9 +9,9 @@ import (
 
 	"code.gitea.io/gitea/models/organization"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 	org_service "code.gitea.io/gitea/services/org"
@@ -19,7 +19,7 @@ import (
 
 const (
 	// tplMembers template for organization members page
-	tplMembers base.TplName = "org/member/members"
+	tplMembers templates.TplName = "org/member/members"
 )
 
 // Members render organization users page
diff --git a/routers/web/org/org.go b/routers/web/org/org.go
index f94dd16eae..856a605764 100644
--- a/routers/web/org/org.go
+++ b/routers/web/org/org.go
@@ -11,9 +11,9 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
@@ -21,7 +21,7 @@ import (
 
 const (
 	// tplCreateOrg template path for create organization
-	tplCreateOrg base.TplName = "org/create"
+	tplCreateOrg templates.TplName = "org/create"
 )
 
 // Create render the page for create organization
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 3b9ec2a7b8..08201e5eaa 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -15,7 +15,6 @@ import (
 	project_model "code.gitea.io/gitea/models/project"
 	attachment_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
@@ -29,9 +28,9 @@ import (
 )
 
 const (
-	tplProjects     base.TplName = "org/projects/list"
-	tplProjectsNew  base.TplName = "org/projects/new"
-	tplProjectsView base.TplName = "org/projects/view"
+	tplProjects     templates.TplName = "org/projects/list"
+	tplProjectsNew  templates.TplName = "org/projects/new"
+	tplProjectsView templates.TplName = "org/projects/view"
 )
 
 // MustEnableProjects check if projects are enabled in settings
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index 551019c717..cb1c4213c9 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -13,11 +13,11 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/models/webhook"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	user_setting "code.gitea.io/gitea/routers/web/user/setting"
@@ -30,13 +30,13 @@ import (
 
 const (
 	// tplSettingsOptions template path for render settings
-	tplSettingsOptions base.TplName = "org/settings/options"
+	tplSettingsOptions templates.TplName = "org/settings/options"
 	// tplSettingsDelete template path for render delete repository
-	tplSettingsDelete base.TplName = "org/settings/delete"
+	tplSettingsDelete templates.TplName = "org/settings/delete"
 	// tplSettingsHooks template path for render hook settings
-	tplSettingsHooks base.TplName = "org/settings/hooks"
+	tplSettingsHooks templates.TplName = "org/settings/hooks"
 	// tplSettingsLabels template path for render labels settings
-	tplSettingsLabels base.TplName = "org/settings/labels"
+	tplSettingsLabels templates.TplName = "org/settings/labels"
 )
 
 // Settings render the main settings page
diff --git a/routers/web/org/setting_oauth2.go b/routers/web/org/setting_oauth2.go
index 7f855795d3..c93058477e 100644
--- a/routers/web/org/setting_oauth2.go
+++ b/routers/web/org/setting_oauth2.go
@@ -9,16 +9,16 @@ import (
 
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	user_setting "code.gitea.io/gitea/routers/web/user/setting"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsApplications         base.TplName = "org/settings/applications"
-	tplSettingsOAuthApplicationEdit base.TplName = "org/settings/applications_oauth2_edit"
+	tplSettingsApplications         templates.TplName = "org/settings/applications"
+	tplSettingsOAuthApplicationEdit templates.TplName = "org/settings/applications_oauth2_edit"
 )
 
 func newOAuth2CommonHandlers(org *context.Organization) *user_setting.OAuth2CommonHandlers {
diff --git a/routers/web/org/setting_packages.go b/routers/web/org/setting_packages.go
index af9836e42c..0912a9e0fd 100644
--- a/routers/web/org/setting_packages.go
+++ b/routers/web/org/setting_packages.go
@@ -7,17 +7,17 @@ import (
 	"fmt"
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared "code.gitea.io/gitea/routers/web/shared/packages"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsPackages            base.TplName = "org/settings/packages"
-	tplSettingsPackagesRuleEdit    base.TplName = "org/settings/packages_cleanup_rules_edit"
-	tplSettingsPackagesRulePreview base.TplName = "org/settings/packages_cleanup_rules_preview"
+	tplSettingsPackages            templates.TplName = "org/settings/packages"
+	tplSettingsPackagesRuleEdit    templates.TplName = "org/settings/packages_cleanup_rules_edit"
+	tplSettingsPackagesRulePreview templates.TplName = "org/settings/packages_cleanup_rules_preview"
 )
 
 func Packages(ctx *context.Context) {
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index b03b18bd9c..7414a11308 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -19,9 +19,9 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
@@ -33,15 +33,15 @@ import (
 
 const (
 	// tplTeams template path for teams list page
-	tplTeams base.TplName = "org/team/teams"
+	tplTeams templates.TplName = "org/team/teams"
 	// tplTeamNew template path for create new team page
-	tplTeamNew base.TplName = "org/team/new"
+	tplTeamNew templates.TplName = "org/team/new"
 	// tplTeamMembers template path for showing team members page
-	tplTeamMembers base.TplName = "org/team/members"
+	tplTeamMembers templates.TplName = "org/team/members"
 	// tplTeamRepositories template path for showing team repositories page
-	tplTeamRepositories base.TplName = "org/team/repositories"
+	tplTeamRepositories templates.TplName = "org/team/repositories"
 	// tplTeamInvite template path for team invites page
-	tplTeamInvite base.TplName = "org/team/invite"
+	tplTeamInvite templates.TplName = "org/team/invite"
 )
 
 // Teams render teams list page
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index 1de1835936..099593bff0 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -17,12 +17,12 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	"code.gitea.io/gitea/modules/actions"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
@@ -33,8 +33,8 @@ import (
 )
 
 const (
-	tplListActions base.TplName = "repo/actions/list"
-	tplViewActions base.TplName = "repo/actions/view"
+	tplListActions templates.TplName = "repo/actions/list"
+	tplViewActions templates.TplName = "repo/actions/view"
 )
 
 type Workflow struct {
diff --git a/routers/web/repo/activity.go b/routers/web/repo/activity.go
index 65dd9e392f..1d809ad8e9 100644
--- a/routers/web/repo/activity.go
+++ b/routers/web/repo/activity.go
@@ -9,12 +9,12 @@ import (
 
 	activities_model "code.gitea.io/gitea/models/activities"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplActivity base.TplName = "repo/activity"
+	tplActivity templates.TplName = "repo/activity"
 )
 
 // Activity render the page to show repository latest changes
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 3e1c6fc61c..72fd958e28 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -14,12 +14,12 @@ import (
 	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/utils"
@@ -31,7 +31,7 @@ import (
 )
 
 const (
-	tplBranch base.TplName = "repo/branch/list"
+	tplBranch templates.TplName = "repo/branch/list"
 )
 
 // Branches render repository branch page
diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go
index 71671ec628..30f4c8a90e 100644
--- a/routers/web/repo/cherry_pick.go
+++ b/routers/web/repo/cherry_pick.go
@@ -10,9 +10,9 @@ import (
 
 	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
@@ -20,7 +20,7 @@ import (
 	"code.gitea.io/gitea/services/repository/files"
 )
 
-var tplCherryPick base.TplName = "repo/editor/cherry_pick"
+var tplCherryPick templates.TplName = "repo/editor/cherry_pick"
 
 // CherryPick handles cherrypick GETs
 func CherryPick(ctx *context.Context) {
diff --git a/routers/web/repo/code_frequency.go b/routers/web/repo/code_frequency.go
index c76f492da0..6572adce74 100644
--- a/routers/web/repo/code_frequency.go
+++ b/routers/web/repo/code_frequency.go
@@ -7,13 +7,13 @@ import (
 	"errors"
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 	contributors_service "code.gitea.io/gitea/services/repository"
 )
 
 const (
-	tplCodeFrequency base.TplName = "repo/activity"
+	tplCodeFrequency templates.TplName = "repo/activity"
 )
 
 // CodeFrequency renders the page to show repository code frequency
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index 6d53df7c10..1447b17a36 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -27,6 +27,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/gitdiff"
@@ -34,10 +35,10 @@ import (
 )
 
 const (
-	tplCommits    base.TplName = "repo/commits"
-	tplGraph      base.TplName = "repo/graph"
-	tplGraphDiv   base.TplName = "repo/graph/div"
-	tplCommitPage base.TplName = "repo/commit_page"
+	tplCommits    templates.TplName = "repo/commits"
+	tplGraph      templates.TplName = "repo/graph"
+	tplGraphDiv   templates.TplName = "repo/graph/div"
+	tplCommitPage templates.TplName = "repo/commit_page"
 )
 
 // RefCommits render commits page
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index 278974bec3..6c59421bda 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -33,6 +33,7 @@ import (
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/common"
@@ -42,9 +43,9 @@ import (
 )
 
 const (
-	tplCompare     base.TplName = "repo/diff/compare"
-	tplBlobExcerpt base.TplName = "repo/diff/blob_excerpt"
-	tplDiffBox     base.TplName = "repo/diff/box"
+	tplCompare     templates.TplName = "repo/diff/compare"
+	tplBlobExcerpt templates.TplName = "repo/diff/blob_excerpt"
+	tplDiffBox     templates.TplName = "repo/diff/box"
 )
 
 // setCompareContext sets context data.
diff --git a/routers/web/repo/contributors.go b/routers/web/repo/contributors.go
index 762fbf9379..e9c0919955 100644
--- a/routers/web/repo/contributors.go
+++ b/routers/web/repo/contributors.go
@@ -7,13 +7,13 @@ import (
 	"errors"
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 	contributors_service "code.gitea.io/gitea/services/repository"
 )
 
 const (
-	tplContributors base.TplName = "repo/activity"
+	tplContributors templates.TplName = "repo/activity"
 )
 
 // Contributors render the page to show repository contributors graph
diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go
index afc69bffdf..5fbdeee27e 100644
--- a/routers/web/repo/editor.go
+++ b/routers/web/repo/editor.go
@@ -13,13 +13,13 @@ import (
 	git_model "code.gitea.io/gitea/models/git"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
@@ -31,10 +31,10 @@ import (
 )
 
 const (
-	tplEditFile        base.TplName = "repo/editor/edit"
-	tplEditDiffPreview base.TplName = "repo/editor/diff_preview"
-	tplDeleteFile      base.TplName = "repo/editor/delete"
-	tplUploadFile      base.TplName = "repo/editor/upload"
+	tplEditFile        templates.TplName = "repo/editor/edit"
+	tplEditDiffPreview templates.TplName = "repo/editor/diff_preview"
+	tplDeleteFile      templates.TplName = "repo/editor/delete"
+	tplUploadFile      templates.TplName = "repo/editor/upload"
 
 	frmCommitChoiceDirect    string = "direct"
 	frmCommitChoiceNewBranch string = "commit-to-new-branch"
diff --git a/routers/web/repo/find.go b/routers/web/repo/find.go
index 2c44552f9c..3a3a7610e7 100644
--- a/routers/web/repo/find.go
+++ b/routers/web/repo/find.go
@@ -6,13 +6,13 @@ package repo
 import (
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplFindFiles base.TplName = "repo/find/files"
+	tplFindFiles templates.TplName = "repo/find/files"
 )
 
 // FindFiles render the page to find repository files
diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go
index 86af705617..786b5d7e43 100644
--- a/routers/web/repo/fork.go
+++ b/routers/web/repo/fork.go
@@ -13,12 +13,12 @@ import (
 	"code.gitea.io/gitea/models/organization"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
@@ -26,7 +26,7 @@ import (
 )
 
 const (
-	tplFork base.TplName = "repo/pulls/fork"
+	tplFork templates.TplName = "repo/pulls/fork"
 )
 
 func getForkRepository(ctx *context.Context) *repo_model.Repository {
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 5397411b59..23012dda3d 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -21,7 +21,6 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/optional"
@@ -37,14 +36,14 @@ import (
 )
 
 const (
-	tplAttachment base.TplName = "repo/issue/view_content/attachments"
+	tplAttachment templates.TplName = "repo/issue/view_content/attachments"
 
-	tplIssues      base.TplName = "repo/issue/list"
-	tplIssueNew    base.TplName = "repo/issue/new"
-	tplIssueChoose base.TplName = "repo/issue/choose"
-	tplIssueView   base.TplName = "repo/issue/view"
+	tplIssues      templates.TplName = "repo/issue/list"
+	tplIssueNew    templates.TplName = "repo/issue/new"
+	tplIssueChoose templates.TplName = "repo/issue/choose"
+	tplIssueView   templates.TplName = "repo/issue/view"
 
-	tplReactions base.TplName = "repo/issue/view_content/reactions"
+	tplReactions templates.TplName = "repo/issue/view_content/reactions"
 
 	issueTemplateKey      = "IssueTemplate"
 	issueTemplateTitleKey = "IssueTemplateTitle"
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index 4874baaa54..5ef6b09faa 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -9,10 +9,10 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/organization"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/label"
 	"code.gitea.io/gitea/modules/log"
 	repo_module "code.gitea.io/gitea/modules/repository"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
@@ -20,7 +20,7 @@ import (
 )
 
 const (
-	tplLabels base.TplName = "repo/issue/labels"
+	tplLabels templates.TplName = "repo/issue/labels"
 )
 
 // Labels render issue's labels page
diff --git a/routers/web/repo/issue_watch.go b/routers/web/repo/issue_watch.go
index 8b033f3b17..a2a4be1758 100644
--- a/routers/web/repo/issue_watch.go
+++ b/routers/web/repo/issue_watch.go
@@ -8,13 +8,13 @@ import (
 	"strconv"
 
 	issues_model "code.gitea.io/gitea/models/issues"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplWatching base.TplName = "repo/issue/view_content/watching"
+	tplWatching templates.TplName = "repo/issue/view_content/watching"
 )
 
 // IssueWatch sets issue watching
diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go
index a2c257940e..3a7dc29466 100644
--- a/routers/web/repo/migrate.go
+++ b/routers/web/repo/migrate.go
@@ -13,13 +13,13 @@ import (
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
@@ -29,7 +29,7 @@ import (
 )
 
 const (
-	tplMigrate base.TplName = "repo/migrate/migrate"
+	tplMigrate templates.TplName = "repo/migrate/migrate"
 )
 
 // Migrate render migration of repository page
@@ -67,10 +67,10 @@ func Migrate(ctx *context.Context) {
 	}
 	ctx.Data["ContextUser"] = ctxUser
 
-	ctx.HTML(http.StatusOK, base.TplName("repo/migrate/"+serviceType.Name()))
+	ctx.HTML(http.StatusOK, templates.TplName("repo/migrate/"+serviceType.Name()))
 }
 
-func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form *forms.MigrateRepoForm) {
+func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl templates.TplName, form *forms.MigrateRepoForm) {
 	if setting.Repository.DisableMigrations {
 		ctx.Error(http.StatusForbidden, "MigrateError: the site administrator has disabled migrations")
 		return
@@ -122,7 +122,7 @@ func handleMigrateError(ctx *context.Context, owner *user_model.User, err error,
 	}
 }
 
-func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl base.TplName, form *forms.MigrateRepoForm) {
+func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl templates.TplName, form *forms.MigrateRepoForm) {
 	if git.IsErrInvalidCloneAddr(err) {
 		addrErr := err.(*git.ErrInvalidCloneAddr)
 		switch {
@@ -169,7 +169,7 @@ func MigratePost(ctx *context.Context) {
 	}
 	ctx.Data["ContextUser"] = ctxUser
 
-	tpl := base.TplName("repo/migrate/" + form.Service.Name())
+	tpl := templates.TplName("repo/migrate/" + form.Service.Name())
 
 	if ctx.HasError() {
 		ctx.HTML(http.StatusOK, tpl)
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index 33c15e7767..d6e41a89b2 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -11,10 +11,10 @@ import (
 	"code.gitea.io/gitea/models/db"
 	issues_model "code.gitea.io/gitea/models/issues"
 	"code.gitea.io/gitea/models/renderhelper"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/common"
 	"code.gitea.io/gitea/services/context"
@@ -25,9 +25,9 @@ import (
 )
 
 const (
-	tplMilestone       base.TplName = "repo/issue/milestones"
-	tplMilestoneNew    base.TplName = "repo/issue/milestone_new"
-	tplMilestoneIssues base.TplName = "repo/issue/milestone_issues"
+	tplMilestone       templates.TplName = "repo/issue/milestones"
+	tplMilestoneNew    templates.TplName = "repo/issue/milestone_new"
+	tplMilestoneIssues templates.TplName = "repo/issue/milestone_issues"
 )
 
 // Milestones render milestones page
diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go
index 57e578da37..c8d3719bc0 100644
--- a/routers/web/repo/packages.go
+++ b/routers/web/repo/packages.go
@@ -9,14 +9,14 @@ import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplPackagesList base.TplName = "repo/packages"
+	tplPackagesList templates.TplName = "repo/packages"
 )
 
 // Packages displays a list of all packages in the repository
diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go
index 5906ec6f3d..1807cf31a1 100644
--- a/routers/web/repo/patch.go
+++ b/routers/web/repo/patch.go
@@ -8,8 +8,8 @@ import (
 
 	git_model "code.gitea.io/gitea/models/git"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
@@ -18,7 +18,7 @@ import (
 )
 
 const (
-	tplPatchFile base.TplName = "repo/editor/patch"
+	tplPatchFile templates.TplName = "repo/editor/patch"
 )
 
 // NewDiffPatch render create patch page
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 3be9578670..92227e3f3e 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -16,11 +16,11 @@ import (
 	"code.gitea.io/gitea/models/renderhelper"
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/web/shared/issue"
@@ -31,9 +31,9 @@ import (
 )
 
 const (
-	tplProjects     base.TplName = "repo/projects/list"
-	tplProjectsNew  base.TplName = "repo/projects/new"
-	tplProjectsView base.TplName = "repo/projects/view"
+	tplProjects     templates.TplName = "repo/projects/list"
+	tplProjectsNew  templates.TplName = "repo/projects/new"
+	tplProjectsView templates.TplName = "repo/projects/view"
 )
 
 // MustEnableRepoProjects check if repo projects are enabled in settings
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 7cc7bf58d2..0948282ca2 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -23,13 +23,13 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/emoji"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/gitrepo"
 	issue_template "code.gitea.io/gitea/modules/issue/template"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/utils"
@@ -49,9 +49,9 @@ import (
 )
 
 const (
-	tplCompareDiff base.TplName = "repo/diff/compare"
-	tplPullCommits base.TplName = "repo/pulls/commits"
-	tplPullFiles   base.TplName = "repo/pulls/files"
+	tplCompareDiff templates.TplName = "repo/diff/compare"
+	tplPullCommits templates.TplName = "repo/pulls/commits"
+	tplPullFiles   templates.TplName = "repo/pulls/files"
 
 	pullRequestTemplateKey = "PullRequestTemplate"
 )
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index aa2e689e42..3e9e615b15 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -12,10 +12,10 @@ import (
 	"code.gitea.io/gitea/models/organization"
 	pull_model "code.gitea.io/gitea/models/pull"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/context/upload"
@@ -26,10 +26,10 @@ import (
 )
 
 const (
-	tplDiffConversation     base.TplName = "repo/diff/conversation"
-	tplConversationOutdated base.TplName = "repo/diff/conversation_outdated"
-	tplTimelineConversation base.TplName = "repo/issue/view_content/conversation"
-	tplNewComment           base.TplName = "repo/diff/new_comment"
+	tplDiffConversation     templates.TplName = "repo/diff/conversation"
+	tplConversationOutdated templates.TplName = "repo/diff/conversation_outdated"
+	tplTimelineConversation templates.TplName = "repo/issue/view_content/conversation"
+	tplNewComment           templates.TplName = "repo/diff/new_comment"
 )
 
 // RenderNewCodeCommentForm will render the form for creating a new review comment
diff --git a/routers/web/repo/recent_commits.go b/routers/web/repo/recent_commits.go
index c158fb30b6..dc72081900 100644
--- a/routers/web/repo/recent_commits.go
+++ b/routers/web/repo/recent_commits.go
@@ -7,13 +7,13 @@ import (
 	"errors"
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 	contributors_service "code.gitea.io/gitea/services/repository"
 )
 
 const (
-	tplRecentCommits base.TplName = "repo/activity"
+	tplRecentCommits templates.TplName = "repo/activity"
 )
 
 // RecentCommits renders the page to show recent commit frequency on repository
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 5c5191fc3b..b8176cb70b 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -16,12 +16,12 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/web/feed"
@@ -33,9 +33,9 @@ import (
 )
 
 const (
-	tplReleasesList base.TplName = "repo/release/list"
-	tplReleaseNew   base.TplName = "repo/release/new"
-	tplTagsList     base.TplName = "repo/tag/list"
+	tplReleasesList templates.TplName = "repo/release/list"
+	tplReleaseNew   templates.TplName = "repo/release/new"
+	tplTagsList     templates.TplName = "repo/tag/list"
 )
 
 // calReleaseNumCommitsBehind calculates given release has how many commits behind release target.
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index 4b017f61c4..6df7d78f7a 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -18,7 +18,6 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	"code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
@@ -27,6 +26,7 @@ import (
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
@@ -38,8 +38,8 @@ import (
 )
 
 const (
-	tplCreate       base.TplName = "repo/create"
-	tplAlertDetails base.TplName = "base/alert_details"
+	tplCreate       templates.TplName = "repo/create"
+	tplAlertDetails templates.TplName = "base/alert_details"
 )
 
 // MustBeNotEmpty render when a repo is a empty git dir
@@ -185,7 +185,7 @@ func Create(ctx *context.Context) {
 	ctx.HTML(http.StatusOK, tplCreate)
 }
 
-func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl base.TplName, form any) {
+func handleCreateError(ctx *context.Context, owner *user_model.User, err error, name string, tpl templates.TplName, form any) {
 	switch {
 	case repo_model.IsErrReachLimitOfRepo(err):
 		maxCreationLimit := owner.MaxCreationLimit()
@@ -304,8 +304,8 @@ func CreatePost(ctx *context.Context) {
 }
 
 const (
-	tplWatchUnwatch base.TplName = "repo/watch_unwatch"
-	tplStarUnstar   base.TplName = "repo/star_unstar"
+	tplWatchUnwatch templates.TplName = "repo/watch_unwatch"
+	tplStarUnstar   templates.TplName = "repo/star_unstar"
 )
 
 // Action response for actions to a repository
diff --git a/routers/web/repo/search.go b/routers/web/repo/search.go
index 920a865555..a037a34833 100644
--- a/routers/web/repo/search.go
+++ b/routers/web/repo/search.go
@@ -8,14 +8,14 @@ import (
 	"strings"
 
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	code_indexer "code.gitea.io/gitea/modules/indexer/code"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
-const tplSearch base.TplName = "repo/search"
+const tplSearch templates.TplName = "repo/search"
 
 func indexSettingToGitGrepPathspecList() (list []string) {
 	for _, expr := range setting.Indexer.IncludePatterns {
diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go
index fad6359668..2df483fa34 100644
--- a/routers/web/repo/setting/lfs.go
+++ b/routers/web/repo/setting/lfs.go
@@ -15,7 +15,6 @@ import (
 	"strings"
 
 	git_model "code.gitea.io/gitea/models/git"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/git"
@@ -25,17 +24,18 @@ import (
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsLFS         base.TplName = "repo/settings/lfs"
-	tplSettingsLFSLocks    base.TplName = "repo/settings/lfs_locks"
-	tplSettingsLFSFile     base.TplName = "repo/settings/lfs_file"
-	tplSettingsLFSFileFind base.TplName = "repo/settings/lfs_file_find"
-	tplSettingsLFSPointers base.TplName = "repo/settings/lfs_pointers"
+	tplSettingsLFS         templates.TplName = "repo/settings/lfs"
+	tplSettingsLFSLocks    templates.TplName = "repo/settings/lfs_locks"
+	tplSettingsLFSFile     templates.TplName = "repo/settings/lfs_file"
+	tplSettingsLFSFileFind templates.TplName = "repo/settings/lfs_file_find"
+	tplSettingsLFSPointers templates.TplName = "repo/settings/lfs_pointers"
 )
 
 // LFSFiles shows a repository's LFS files
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index f651d8f318..022a24a9ad 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -15,6 +15,7 @@ import (
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers/web/repo"
 	"code.gitea.io/gitea/services/context"
@@ -26,7 +27,7 @@ import (
 )
 
 const (
-	tplProtectedBranch base.TplName = "repo/settings/protected_branch"
+	tplProtectedBranch templates.TplName = "repo/settings/protected_branch"
 )
 
 // ProtectedBranchRules render the page to protect the repository
diff --git a/routers/web/repo/setting/protected_tag.go b/routers/web/repo/setting/protected_tag.go
index fcfa77aa8c..1c24c8a7ba 100644
--- a/routers/web/repo/setting/protected_tag.go
+++ b/routers/web/repo/setting/protected_tag.go
@@ -14,13 +14,14 @@ import (
 	access_model "code.gitea.io/gitea/models/perm/access"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
 )
 
 const (
-	tplTags base.TplName = "repo/settings/tags"
+	tplTags templates.TplName = "repo/settings/tags"
 )
 
 // Tags render the page to protect tags
diff --git a/routers/web/repo/setting/runners.go b/routers/web/repo/setting/runners.go
index 3141d8f42a..ec037baec1 100644
--- a/routers/web/repo/setting/runners.go
+++ b/routers/web/repo/setting/runners.go
@@ -10,8 +10,8 @@ import (
 
 	actions_model "code.gitea.io/gitea/models/actions"
 	"code.gitea.io/gitea/models/db"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	actions_shared "code.gitea.io/gitea/routers/web/shared/actions"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
@@ -19,14 +19,14 @@ import (
 
 const (
 	// TODO: Separate secrets from runners when layout is ready
-	tplRepoRunners     base.TplName = "repo/settings/actions"
-	tplOrgRunners      base.TplName = "org/settings/actions"
-	tplAdminRunners    base.TplName = "admin/actions"
-	tplUserRunners     base.TplName = "user/settings/actions"
-	tplRepoRunnerEdit  base.TplName = "repo/settings/runner_edit"
-	tplOrgRunnerEdit   base.TplName = "org/settings/runners_edit"
-	tplAdminRunnerEdit base.TplName = "admin/runners/edit"
-	tplUserRunnerEdit  base.TplName = "user/settings/runner_edit"
+	tplRepoRunners     templates.TplName = "repo/settings/actions"
+	tplOrgRunners      templates.TplName = "org/settings/actions"
+	tplAdminRunners    templates.TplName = "admin/actions"
+	tplUserRunners     templates.TplName = "user/settings/actions"
+	tplRepoRunnerEdit  templates.TplName = "repo/settings/runner_edit"
+	tplOrgRunnerEdit   templates.TplName = "org/settings/runners_edit"
+	tplAdminRunnerEdit templates.TplName = "admin/runners/edit"
+	tplUserRunnerEdit  templates.TplName = "user/settings/runner_edit"
 )
 
 type runnersCtx struct {
@@ -36,8 +36,8 @@ type runnersCtx struct {
 	IsOrg              bool
 	IsAdmin            bool
 	IsUser             bool
-	RunnersTemplate    base.TplName
-	RunnerEditTemplate base.TplName
+	RunnersTemplate    templates.TplName
+	RunnerEditTemplate templates.TplName
 	RedirectLink       string
 }
 
diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go
index df11729344..46cb875f9b 100644
--- a/routers/web/repo/setting/secrets.go
+++ b/routers/web/repo/setting/secrets.go
@@ -8,8 +8,8 @@ import (
 	"net/http"
 
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared "code.gitea.io/gitea/routers/web/shared/secrets"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
@@ -17,9 +17,9 @@ import (
 
 const (
 	// TODO: Separate secrets from runners when layout is ready
-	tplRepoSecrets base.TplName = "repo/settings/actions"
-	tplOrgSecrets  base.TplName = "org/settings/actions"
-	tplUserSecrets base.TplName = "user/settings/actions"
+	tplRepoSecrets templates.TplName = "repo/settings/actions"
+	tplOrgSecrets  templates.TplName = "org/settings/actions"
+	tplUserSecrets templates.TplName = "user/settings/actions"
 )
 
 type secretsCtx struct {
@@ -28,7 +28,7 @@ type secretsCtx struct {
 	IsRepo          bool
 	IsOrg           bool
 	IsUser          bool
-	SecretsTemplate base.TplName
+	SecretsTemplate templates.TplName
 	RedirectLink    string
 }
 
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 1c7f9441f8..7399c681e2 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -18,7 +18,6 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/indexer/code"
 	issue_indexer "code.gitea.io/gitea/modules/indexer/issues"
@@ -27,6 +26,7 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/validation"
 	"code.gitea.io/gitea/modules/web"
@@ -41,12 +41,12 @@ import (
 )
 
 const (
-	tplSettingsOptions base.TplName = "repo/settings/options"
-	tplCollaboration   base.TplName = "repo/settings/collaboration"
-	tplBranches        base.TplName = "repo/settings/branches"
-	tplGithooks        base.TplName = "repo/settings/githooks"
-	tplGithookEdit     base.TplName = "repo/settings/githook_edit"
-	tplDeployKeys      base.TplName = "repo/settings/deploy_keys"
+	tplSettingsOptions templates.TplName = "repo/settings/options"
+	tplCollaboration   templates.TplName = "repo/settings/collaboration"
+	tplBranches        templates.TplName = "repo/settings/branches"
+	tplGithooks        templates.TplName = "repo/settings/githooks"
+	tplGithookEdit     templates.TplName = "repo/settings/githook_edit"
+	tplDeployKeys      templates.TplName = "repo/settings/deploy_keys"
 )
 
 // SettingsCtxData is a middleware that sets all the general context data for the
diff --git a/routers/web/repo/setting/variables.go b/routers/web/repo/setting/variables.go
index cc2e619f66..9b5453f043 100644
--- a/routers/web/repo/setting/variables.go
+++ b/routers/web/repo/setting/variables.go
@@ -7,18 +7,18 @@ import (
 	"errors"
 	"net/http"
 
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared "code.gitea.io/gitea/routers/web/shared/actions"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplRepoVariables  base.TplName = "repo/settings/actions"
-	tplOrgVariables   base.TplName = "org/settings/actions"
-	tplUserVariables  base.TplName = "user/settings/actions"
-	tplAdminVariables base.TplName = "admin/actions"
+	tplRepoVariables  templates.TplName = "repo/settings/actions"
+	tplOrgVariables   templates.TplName = "org/settings/actions"
+	tplUserVariables  templates.TplName = "user/settings/actions"
+	tplAdminVariables templates.TplName = "admin/actions"
 )
 
 type variablesCtx struct {
@@ -28,7 +28,7 @@ type variablesCtx struct {
 	IsOrg             bool
 	IsUser            bool
 	IsGlobal          bool
-	VariablesTemplate base.TplName
+	VariablesTemplate templates.TplName
 	RedirectLink      string
 }
 
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index 8d548c4e3d..bcf8a7eac0 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -17,11 +17,11 @@ import (
 	access_model "code.gitea.io/gitea/models/perm/access"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/models/webhook"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/json"
 	"code.gitea.io/gitea/modules/setting"
 	api "code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	webhook_module "code.gitea.io/gitea/modules/webhook"
@@ -32,11 +32,11 @@ import (
 )
 
 const (
-	tplHooks        base.TplName = "repo/settings/webhook/base"
-	tplHookNew      base.TplName = "repo/settings/webhook/new"
-	tplOrgHookNew   base.TplName = "org/settings/hook_new"
-	tplUserHookNew  base.TplName = "user/settings/hook_new"
-	tplAdminHookNew base.TplName = "admin/hook_new"
+	tplHooks        templates.TplName = "repo/settings/webhook/base"
+	tplHookNew      templates.TplName = "repo/settings/webhook/new"
+	tplOrgHookNew   templates.TplName = "org/settings/hook_new"
+	tplUserHookNew  templates.TplName = "user/settings/hook_new"
+	tplAdminHookNew templates.TplName = "admin/hook_new"
 )
 
 // Webhooks render web hooks list page
@@ -64,7 +64,7 @@ type ownerRepoCtx struct {
 	IsSystemWebhook bool
 	Link            string
 	LinkNew         string
-	NewTemplate     base.TplName
+	NewTemplate     templates.TplName
 }
 
 // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context.
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index e43841acd3..14fc9038f3 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -35,6 +35,7 @@ import (
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
@@ -45,12 +46,12 @@ import (
 )
 
 const (
-	tplRepoEMPTY    base.TplName = "repo/empty"
-	tplRepoHome     base.TplName = "repo/home"
-	tplRepoViewList base.TplName = "repo/view_list"
-	tplWatchers     base.TplName = "repo/watchers"
-	tplForks        base.TplName = "repo/forks"
-	tplMigrating    base.TplName = "repo/migrate/migrating"
+	tplRepoEMPTY    templates.TplName = "repo/empty"
+	tplRepoHome     templates.TplName = "repo/home"
+	tplRepoViewList templates.TplName = "repo/view_list"
+	tplWatchers     templates.TplName = "repo/watchers"
+	tplForks        templates.TplName = "repo/forks"
+	tplMigrating    templates.TplName = "repo/migrate/migrating"
 )
 
 type fileInfo struct {
@@ -314,7 +315,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 }
 
 // RenderUserCards render a page show users according the input template
-func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) {
+func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl templates.TplName) {
 	page := ctx.FormInt("page")
 	if page <= 0 {
 		page = 1
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index fec72c9253..3fca7cebea 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -26,6 +26,7 @@ import (
 	"code.gitea.io/gitea/modules/markup"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
@@ -37,11 +38,11 @@ import (
 )
 
 const (
-	tplWikiStart    base.TplName = "repo/wiki/start"
-	tplWikiView     base.TplName = "repo/wiki/view"
-	tplWikiRevision base.TplName = "repo/wiki/revision"
-	tplWikiNew      base.TplName = "repo/wiki/new"
-	tplWikiPages    base.TplName = "repo/wiki/pages"
+	tplWikiStart    templates.TplName = "repo/wiki/start"
+	tplWikiView     templates.TplName = "repo/wiki/view"
+	tplWikiRevision templates.TplName = "repo/wiki/revision"
+	tplWikiNew      templates.TplName = "repo/wiki/new"
+	tplWikiPages    templates.TplName = "repo/wiki/pages"
 )
 
 // MustEnableWiki check if wiki is enabled, if external then redirect
diff --git a/routers/web/shared/packages/packages.go b/routers/web/shared/packages/packages.go
index 1d3cabf71b..a1bcb09850 100644
--- a/routers/web/shared/packages/packages.go
+++ b/routers/web/shared/packages/packages.go
@@ -11,9 +11,9 @@ import (
 	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
@@ -54,11 +54,11 @@ func setRuleEditContext(ctx *context.Context, pcr *packages_model.PackageCleanup
 	ctx.Data["AvailableTypes"] = packages_model.TypeList
 }
 
-func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
+func PerformRuleAddPost(ctx *context.Context, owner *user_model.User, redirectURL string, template templates.TplName) {
 	performRuleEditPost(ctx, owner, nil, redirectURL, template)
 }
 
-func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template base.TplName) {
+func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectURL string, template templates.TplName) {
 	pcr := getCleanupRuleByContext(ctx, owner)
 	if pcr == nil {
 		return
@@ -79,7 +79,7 @@ func PerformRuleEditPost(ctx *context.Context, owner *user_model.User, redirectU
 	}
 }
 
-func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template base.TplName) {
+func performRuleEditPost(ctx *context.Context, owner *user_model.User, pcr *packages_model.PackageCleanupRule, redirectURL string, template templates.TplName) {
 	isEditRule := pcr != nil
 
 	if pcr == nil {
diff --git a/routers/web/user/code.go b/routers/web/user/code.go
index 785c37b124..f805f2b028 100644
--- a/routers/web/user/code.go
+++ b/routers/web/user/code.go
@@ -8,15 +8,15 @@ import (
 
 	"code.gitea.io/gitea/models/db"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/modules/base"
 	code_indexer "code.gitea.io/gitea/modules/indexer/code"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplUserCode base.TplName = "user/code"
+	tplUserCode templates.TplName = "user/code"
 )
 
 // CodeSearch render user/organization code search page
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index befa33b0c0..e118aa051e 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -31,6 +31,7 @@ import (
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/web/feed"
 	"code.gitea.io/gitea/routers/web/shared/issue"
@@ -46,10 +47,10 @@ import (
 )
 
 const (
-	tplDashboard  base.TplName = "user/dashboard/dashboard"
-	tplIssues     base.TplName = "user/dashboard/issues"
-	tplMilestones base.TplName = "user/dashboard/milestones"
-	tplProfile    base.TplName = "user/profile"
+	tplDashboard  templates.TplName = "user/dashboard/dashboard"
+	tplIssues     templates.TplName = "user/dashboard/issues"
+	tplMilestones templates.TplName = "user/dashboard/milestones"
+	tplProfile    templates.TplName = "user/profile"
 )
 
 // getDashboardContextUser finds out which context user dashboard is being viewed as .
diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go
index 414cb0be49..732fac2595 100644
--- a/routers/web/user/notification.go
+++ b/routers/web/user/notification.go
@@ -22,6 +22,7 @@ import (
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 	issue_service "code.gitea.io/gitea/services/issue"
@@ -29,9 +30,9 @@ import (
 )
 
 const (
-	tplNotification              base.TplName = "user/notification/notification"
-	tplNotificationDiv           base.TplName = "user/notification/notification_div"
-	tplNotificationSubscriptions base.TplName = "user/notification/notification_subscriptions"
+	tplNotification              templates.TplName = "user/notification/notification"
+	tplNotificationDiv           templates.TplName = "user/notification/notification_div"
+	tplNotificationSubscriptions templates.TplName = "user/notification/notification_subscriptions"
 )
 
 // GetNotificationCount is the middleware that sets the notification count in the context
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index c6f85ac734..d5aac513b6 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -14,7 +14,6 @@ import (
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/log"
@@ -24,6 +23,7 @@ import (
 	debian_module "code.gitea.io/gitea/modules/packages/debian"
 	rpm_module "code.gitea.io/gitea/modules/packages/rpm"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	packages_helper "code.gitea.io/gitea/routers/api/packages/helper"
@@ -34,10 +34,10 @@ import (
 )
 
 const (
-	tplPackagesList       base.TplName = "user/overview/packages"
-	tplPackagesView       base.TplName = "package/view"
-	tplPackageVersionList base.TplName = "user/overview/package_versions"
-	tplPackagesSettings   base.TplName = "package/settings"
+	tplPackagesList       templates.TplName = "user/overview/packages"
+	tplPackagesView       templates.TplName = "package/view"
+	tplPackageVersionList templates.TplName = "user/overview/package_versions"
+	tplPackagesSettings   templates.TplName = "package/settings"
 )
 
 // ListPackages displays a list of all packages of the context user
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index c41030a5e2..44824de752 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -15,12 +15,12 @@ import (
 	"code.gitea.io/gitea/models/renderhelper"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/web/feed"
 	"code.gitea.io/gitea/routers/web/org"
@@ -30,8 +30,8 @@ import (
 )
 
 const (
-	tplProfileBigAvatar base.TplName = "shared/user/profile_big_avatar"
-	tplFollowUnfollow   base.TplName = "org/follow_unfollow"
+	tplProfileBigAvatar templates.TplName = "shared/user/profile_big_avatar"
+	tplFollowUnfollow   templates.TplName = "org/follow_unfollow"
 )
 
 // OwnerProfile render profile page for a user or a organization (aka, repo owner)
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 00fe02c886..3acc3c7a54 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -15,10 +15,10 @@ import (
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/auth/password"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/auth"
@@ -31,7 +31,7 @@ import (
 )
 
 const (
-	tplSettingsAccount base.TplName = "user/settings/account"
+	tplSettingsAccount templates.TplName = "user/settings/account"
 )
 
 // Account renders change user's password, user's email and user suicide page
diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go
index 356c2ea5de..cf71d01dc1 100644
--- a/routers/web/user/setting/applications.go
+++ b/routers/web/user/setting/applications.go
@@ -10,15 +10,15 @@ import (
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/context"
 	"code.gitea.io/gitea/services/forms"
 )
 
 const (
-	tplSettingsApplications base.TplName = "user/settings/applications"
+	tplSettingsApplications templates.TplName = "user/settings/applications"
 )
 
 // Applications render manage access token page
diff --git a/routers/web/user/setting/block.go b/routers/web/user/setting/block.go
index d419fb321b..3756495fd2 100644
--- a/routers/web/user/setting/block.go
+++ b/routers/web/user/setting/block.go
@@ -7,14 +7,14 @@ import (
 	"net/http"
 
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsBlockedUsers base.TplName = "user/settings/blocked_users"
+	tplSettingsBlockedUsers templates.TplName = "user/settings/blocked_users"
 )
 
 func BlockedUsers(ctx *context.Context) {
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index c492715fb5..127aed9845 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -11,8 +11,8 @@ import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web"
 	asymkey_service "code.gitea.io/gitea/services/asymkey"
 	"code.gitea.io/gitea/services/context"
@@ -20,7 +20,7 @@ import (
 )
 
 const (
-	tplSettingsKeys base.TplName = "user/settings/keys"
+	tplSettingsKeys templates.TplName = "user/settings/keys"
 )
 
 // Keys render user's SSH/GPG public keys page
diff --git a/routers/web/user/setting/oauth2.go b/routers/web/user/setting/oauth2.go
index 1f485e06c8..d50728c24e 100644
--- a/routers/web/user/setting/oauth2.go
+++ b/routers/web/user/setting/oauth2.go
@@ -4,13 +4,13 @@
 package setting
 
 import (
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsOAuthApplicationEdit base.TplName = "user/settings/applications_oauth2_edit"
+	tplSettingsOAuthApplicationEdit templates.TplName = "user/settings/applications_oauth2_edit"
 )
 
 func newOAuth2CommonHandlers(userID int64) *OAuth2CommonHandlers {
diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go
index e93e9e1954..783deca710 100644
--- a/routers/web/user/setting/oauth2_common.go
+++ b/routers/web/user/setting/oauth2_common.go
@@ -8,7 +8,7 @@ import (
 	"net/http"
 
 	"code.gitea.io/gitea/models/auth"
-	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	shared_user "code.gitea.io/gitea/routers/web/shared/user"
@@ -17,10 +17,10 @@ import (
 )
 
 type OAuth2CommonHandlers struct {
-	OwnerID            int64        // 0 for instance-wide, otherwise OrgID or UserID
-	BasePathList       string       // the base URL for the application list page, eg: "/user/setting/applications"
-	BasePathEditPrefix string       // the base URL for the application edit page, will be appended with app id, eg: "/user/setting/applications/oauth2"
-	TplAppEdit         base.TplName // the template for the application edit page
+	OwnerID            int64             // 0 for instance-wide, otherwise OrgID or UserID
+	BasePathList       string            // the base URL for the application list page, eg: "/user/setting/applications"
+	BasePathEditPrefix string            // the base URL for the application edit page, will be appended with app id, eg: "/user/setting/applications/oauth2"
+	TplAppEdit         templates.TplName // the template for the application edit page
 }
 
 func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) {
diff --git a/routers/web/user/setting/packages.go b/routers/web/user/setting/packages.go
index 50521c11c0..9692cd9db0 100644
--- a/routers/web/user/setting/packages.go
+++ b/routers/web/user/setting/packages.go
@@ -8,18 +8,18 @@ import (
 	"strings"
 
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	chef_module "code.gitea.io/gitea/modules/packages/chef"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/util"
 	shared "code.gitea.io/gitea/routers/web/shared/packages"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsPackages            base.TplName = "user/settings/packages"
-	tplSettingsPackagesRuleEdit    base.TplName = "user/settings/packages_cleanup_rules_edit"
-	tplSettingsPackagesRulePreview base.TplName = "user/settings/packages_cleanup_rules_preview"
+	tplSettingsPackages            templates.TplName = "user/settings/packages"
+	tplSettingsPackagesRuleEdit    templates.TplName = "user/settings/packages_cleanup_rules_edit"
+	tplSettingsPackagesRulePreview templates.TplName = "user/settings/packages_cleanup_rules_preview"
 )
 
 func Packages(ctx *context.Context) {
diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go
index 3b051c9b5f..4b3c214096 100644
--- a/routers/web/user/setting/profile.go
+++ b/routers/web/user/setting/profile.go
@@ -19,10 +19,10 @@ import (
 	"code.gitea.io/gitea/models/organization"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/translation"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
@@ -35,10 +35,10 @@ import (
 )
 
 const (
-	tplSettingsProfile      base.TplName = "user/settings/profile"
-	tplSettingsAppearance   base.TplName = "user/settings/appearance"
-	tplSettingsOrganization base.TplName = "user/settings/organization"
-	tplSettingsRepositories base.TplName = "user/settings/repos"
+	tplSettingsProfile      templates.TplName = "user/settings/profile"
+	tplSettingsAppearance   templates.TplName = "user/settings/appearance"
+	tplSettingsOrganization templates.TplName = "user/settings/organization"
+	tplSettingsRepositories templates.TplName = "user/settings/repos"
 )
 
 // Profile render user's profile page
diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go
index b44cb4dd49..38d1910e2d 100644
--- a/routers/web/user/setting/security/security.go
+++ b/routers/web/user/setting/security/security.go
@@ -11,16 +11,16 @@ import (
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/auth/source/oauth2"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsSecurity    base.TplName = "user/settings/security/security"
-	tplSettingsTwofaEnroll base.TplName = "user/settings/security/twofa_enroll"
+	tplSettingsSecurity    templates.TplName = "user/settings/security/security"
+	tplSettingsTwofaEnroll templates.TplName = "user/settings/security/twofa_enroll"
 )
 
 // Security render change user's password page and 2FA
diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go
index 3732ca27c0..ca7c7ec1ac 100644
--- a/routers/web/user/setting/webhooks.go
+++ b/routers/web/user/setting/webhooks.go
@@ -9,13 +9,13 @@ import (
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/models/webhook"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/services/context"
 )
 
 const (
-	tplSettingsHooks base.TplName = "user/settings/hooks"
+	tplSettingsHooks templates.TplName = "user/settings/hooks"
 )
 
 // Webhooks render webhook list page
diff --git a/services/auth/sspi.go b/services/auth/sspi.go
index 7f8a03a4c6..8ac83dcb04 100644
--- a/services/auth/sspi.go
+++ b/services/auth/sspi.go
@@ -13,10 +13,10 @@ import (
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/optional"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/web/middleware"
 	"code.gitea.io/gitea/services/auth/source/sspi"
 	gitea_context "code.gitea.io/gitea/services/context"
@@ -25,7 +25,7 @@ import (
 )
 
 const (
-	tplSignIn base.TplName = "user/auth/signin"
+	tplSignIn templates.TplName = "user/auth/signin"
 )
 
 type SSPIAuth interface {
diff --git a/services/context/captcha.go b/services/context/captcha.go
index 41afe0e7d2..9272e7a65a 100644
--- a/services/context/captcha.go
+++ b/services/context/captcha.go
@@ -7,13 +7,13 @@ import (
 	"fmt"
 	"sync"
 
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/cache"
 	"code.gitea.io/gitea/modules/hcaptcha"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/mcaptcha"
 	"code.gitea.io/gitea/modules/recaptcha"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/turnstile"
 
 	"gitea.com/go-chi/captcha"
@@ -60,7 +60,7 @@ const (
 
 // VerifyCaptcha verifies Captcha data
 // No-op if captchas are not enabled
-func VerifyCaptcha(ctx *Context, tpl base.TplName, form any) {
+func VerifyCaptcha(ctx *Context, tpl templates.TplName, form any) {
 	if !setting.Service.EnableCaptcha {
 		return
 	}
diff --git a/services/context/context.go b/services/context/context.go
index 812a8c27ee..0d5429e366 100644
--- a/services/context/context.go
+++ b/services/context/context.go
@@ -32,7 +32,7 @@ import (
 // Render represents a template render
 type Render interface {
 	TemplateLookup(tmpl string, templateCtx context.Context) (templates.TemplateExecutor, error)
-	HTML(w io.Writer, status int, name string, data any, templateCtx context.Context) error
+	HTML(w io.Writer, status int, name templates.TplName, data any, templateCtx context.Context) error
 }
 
 // Context represents context of a request.
diff --git a/services/context/context_response.go b/services/context/context_response.go
index c43a649b49..4c086ea9f5 100644
--- a/services/context/context_response.go
+++ b/services/context/context_response.go
@@ -17,7 +17,6 @@ import (
 	"time"
 
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -63,10 +62,10 @@ func (ctx *Context) RedirectToCurrentSite(location ...string) {
 	ctx.Redirect(setting.AppSubURL + "/")
 }
 
-const tplStatus500 base.TplName = "status/500"
+const tplStatus500 templates.TplName = "status/500"
 
 // HTML calls Context.HTML and renders the template to HTTP response
-func (ctx *Context) HTML(status int, name base.TplName) {
+func (ctx *Context) HTML(status int, name templates.TplName) {
 	log.Debug("Template: %s", name)
 
 	tmplStartTime := time.Now()
@@ -77,7 +76,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
 		return strconv.FormatInt(time.Since(tmplStartTime).Nanoseconds()/1e6, 10) + "ms"
 	}
 
-	err := ctx.Render.HTML(ctx.Resp, status, string(name), ctx.Data, ctx.TemplateContext)
+	err := ctx.Render.HTML(ctx.Resp, status, name, ctx.Data, ctx.TemplateContext)
 	if err == nil || errors.Is(err, syscall.EPIPE) {
 		return
 	}
@@ -94,7 +93,7 @@ func (ctx *Context) HTML(status int, name base.TplName) {
 
 // JSONTemplate renders the template as JSON response
 // keep in mind that the template is processed in HTML context, so JSON-things should be handled carefully, eg: by JSEscape
-func (ctx *Context) JSONTemplate(tmpl base.TplName) {
+func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
 	t, err := ctx.Render.TemplateLookup(string(tmpl), nil)
 	if err != nil {
 		ctx.ServerError("unable to find template", err)
@@ -107,14 +106,14 @@ func (ctx *Context) JSONTemplate(tmpl base.TplName) {
 }
 
 // RenderToHTML renders the template content to a HTML string
-func (ctx *Context) RenderToHTML(name base.TplName, data map[string]any) (template.HTML, error) {
+func (ctx *Context) RenderToHTML(name templates.TplName, data map[string]any) (template.HTML, error) {
 	var buf strings.Builder
-	err := ctx.Render.HTML(&buf, 0, string(name), data, ctx.TemplateContext)
+	err := ctx.Render.HTML(&buf, 0, name, data, ctx.TemplateContext)
 	return template.HTML(buf.String()), err
 }
 
 // RenderWithErr used for page has form validation but need to prompt error to users.
-func (ctx *Context) RenderWithErr(msg any, tpl base.TplName, form any) {
+func (ctx *Context) RenderWithErr(msg any, tpl templates.TplName, form any) {
 	if form != nil {
 		middleware.AssignForm(form, ctx.Data)
 	}
@@ -151,7 +150,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
 
 	ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
 	ctx.Data["Title"] = "Page Not Found"
-	ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
+	ctx.HTML(http.StatusNotFound, templates.TplName("status/404"))
 }
 
 // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go
index 3c3fa76e3c..39ad5a362f 100644
--- a/services/contexttest/context_tests.go
+++ b/services/contexttest/context_tests.go
@@ -183,7 +183,7 @@ func (tr *MockRender) TemplateLookup(tmpl string, _ gocontext.Context) (template
 	return nil, nil
 }
 
-func (tr *MockRender) HTML(w io.Writer, status int, _ string, _ any, _ gocontext.Context) error {
+func (tr *MockRender) HTML(w io.Writer, status int, _ templates.TplName, _ any, _ gocontext.Context) error {
 	if resp, ok := w.(http.ResponseWriter); ok {
 		resp.WriteHeader(status)
 	}
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index ee2c8c0963..a6763e4f03 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -21,11 +21,11 @@ import (
 	"code.gitea.io/gitea/models/renderhelper"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/emoji"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/translation"
 	incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
@@ -34,14 +34,14 @@ import (
 )
 
 const (
-	mailAuthActivate       base.TplName = "auth/activate"
-	mailAuthActivateEmail  base.TplName = "auth/activate_email"
-	mailAuthResetPassword  base.TplName = "auth/reset_passwd"
-	mailAuthRegisterNotify base.TplName = "auth/register_notify"
+	mailAuthActivate       templates.TplName = "auth/activate"
+	mailAuthActivateEmail  templates.TplName = "auth/activate_email"
+	mailAuthResetPassword  templates.TplName = "auth/reset_passwd"
+	mailAuthRegisterNotify templates.TplName = "auth/register_notify"
 
-	mailNotifyCollaborator base.TplName = "notify/collaborator"
+	mailNotifyCollaborator templates.TplName = "notify/collaborator"
 
-	mailRepoTransferNotify base.TplName = "notify/repo_transfer"
+	mailRepoTransferNotify templates.TplName = "notify/repo_transfer"
 
 	// There's no actual limit for subject in RFC 5322
 	mailMaxSubjectRunes = 256
@@ -63,7 +63,7 @@ func SendTestMail(email string) error {
 }
 
 // sendUserMail sends a mail to the user
-func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, subject, info string) {
+func sendUserMail(language string, u *user_model.User, tpl templates.TplName, code, subject, info string) {
 	locale := translation.NewLocale(language)
 	data := map[string]any{
 		"locale":            locale,
diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go
index 1d73d77612..796d63d27a 100644
--- a/services/mailer/mail_release.go
+++ b/services/mailer/mail_release.go
@@ -10,16 +10,16 @@ import (
 	"code.gitea.io/gitea/models/renderhelper"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup/markdown"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/translation"
 	sender_service "code.gitea.io/gitea/services/mailer/sender"
 )
 
 const (
-	tplNewReleaseMail base.TplName = "release"
+	tplNewReleaseMail templates.TplName = "release"
 )
 
 // MailNewRelease send new release notify to all repo watchers.
diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go
index 4f2d5e4ca7..5ca44442f3 100644
--- a/services/mailer/mail_team_invite.go
+++ b/services/mailer/mail_team_invite.go
@@ -11,15 +11,15 @@ import (
 
 	org_model "code.gitea.io/gitea/models/organization"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/templates"
 	"code.gitea.io/gitea/modules/translation"
 	sender_service "code.gitea.io/gitea/services/mailer/sender"
 )
 
 const (
-	tplTeamInviteMail base.TplName = "team_invite"
+	tplTeamInviteMail templates.TplName = "team_invite"
 )
 
 // MailTeamInvite sends team invites
diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go
index 462cb73eee..616ccf291a 100644
--- a/tests/integration/migration-test/migration_test.go
+++ b/tests/integration/migration-test/migration_test.go
@@ -21,11 +21,11 @@ import (
 	"code.gitea.io/gitea/models/migrations"
 	migrate_base "code.gitea.io/gitea/models/migrations/base"
 	"code.gitea.io/gitea/models/unittest"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/testlogger"
 	"code.gitea.io/gitea/modules/util"
 	"github.com/stretchr/testify/assert"
@@ -37,12 +37,7 @@ var currentEngine *xorm.Engine
 
 func initMigrationTest(t *testing.T) func() {
 	testlogger.Init()
-
-	deferFn := testlogger.PrintCurrentTest(t, 2)
-	giteaRoot := base.SetupGiteaRoot()
-	if giteaRoot == "" {
-		testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
-	}
+	giteaRoot := test.SetupGiteaRoot()
 	setting.AppPath = path.Join(giteaRoot, "gitea")
 	if _, err := os.Stat(setting.AppPath); err != nil {
 		testlogger.Fatalf(fmt.Sprintf("Could not find gitea binary at %s\n", setting.AppPath))
@@ -64,7 +59,8 @@ func initMigrationTest(t *testing.T) func() {
 	assert.NoError(t, git.InitFull(context.Background()))
 	setting.LoadDBSetting()
 	setting.InitLoggersForTest()
-	return deferFn
+
+	return testlogger.PrintCurrentTest(t, 2)
 }
 
 func availableVersions() ([]string, error) {
diff --git a/tests/test_utils.go b/tests/test_utils.go
index deefdd43c5..0fe0200ea7 100644
--- a/tests/test_utils.go
+++ b/tests/test_utils.go
@@ -15,13 +15,13 @@ import (
 	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/models/unittest"
-	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/git"
 	"code.gitea.io/gitea/modules/graceful"
 	"code.gitea.io/gitea/modules/log"
 	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/storage"
+	"code.gitea.io/gitea/modules/test"
 	"code.gitea.io/gitea/modules/testlogger"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers"
@@ -32,10 +32,7 @@ import (
 func InitTest(requireGitea bool) {
 	testlogger.Init()
 
-	giteaRoot := base.SetupGiteaRoot()
-	if giteaRoot == "" {
-		testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
-	}
+	giteaRoot := test.SetupGiteaRoot()
 
 	// TODO: Speedup tests that rely on the event source ticker, confirm whether there is any bug or failure.
 	// setting.UI.Notification.EventSourceUpdateTime = time.Second