Skip to content

Commit 5408a2f

Browse files
committed
v2.8.0
1 parent c8d71ea commit 5408a2f

5 files changed

Lines changed: 93 additions & 44 deletions

File tree

config/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.7.0
1+
2.8.0

sub/sub.go

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ package sub
33
import (
44
"context"
55
"crypto/tls"
6+
"html/template"
67
"io"
8+
"io/fs"
79
"net"
810
"net/http"
911
"os"
1012
"path/filepath"
1113
"strconv"
1214

13-
"x-ui/config"
1415
"x-ui/logger"
1516
"x-ui/util/common"
17+
webpkg "x-ui/web"
1618
"x-ui/web/locale"
1719
"x-ui/web/middleware"
1820
"x-ui/web/network"
@@ -21,6 +23,21 @@ import (
2123
"github.com/gin-gonic/gin"
2224
)
2325

26+
// setEmbeddedTemplates parses and sets embedded templates on the engine
27+
func setEmbeddedTemplates(engine *gin.Engine) error {
28+
t, err := template.New("").Funcs(engine.FuncMap).ParseFS(
29+
webpkg.EmbeddedHTML(),
30+
"html/common/page.html",
31+
"html/component/aThemeSwitch.html",
32+
"html/subscription.html",
33+
)
34+
if err != nil {
35+
return err
36+
}
37+
engine.SetHTMLTemplate(t)
38+
return nil
39+
}
40+
2441
type Server struct {
2542
httpServer *http.Server
2643
listener net.Listener
@@ -41,13 +58,10 @@ func NewServer() *Server {
4158
}
4259

4360
func (s *Server) initRouter() (*gin.Engine, error) {
44-
if config.IsDebug() {
45-
gin.SetMode(gin.DebugMode)
46-
} else {
47-
gin.DefaultWriter = io.Discard
48-
gin.DefaultErrorWriter = io.Discard
49-
gin.SetMode(gin.ReleaseMode)
50-
}
61+
// Always run in release mode for the subscription server
62+
gin.DefaultWriter = io.Discard
63+
gin.DefaultErrorWriter = io.Discard
64+
gin.SetMode(gin.ReleaseMode)
5165

5266
engine := gin.Default()
5367

@@ -120,28 +134,35 @@ func (s *Server) initRouter() (*gin.Engine, error) {
120134
SubTitle = ""
121135
}
122136

123-
// init i18n for sub server using disk FS so templates can use {{ i18n }}
124-
// Root FS is project root; translation files are under web/translation
125-
if err := locale.InitLocalizerFS(os.DirFS("web"), &s.settingService); err != nil {
126-
logger.Warning("sub: i18n init failed:", err)
127-
}
128137
// set per-request localizer from headers/cookies
129138
engine.Use(locale.LocalizerMiddleware())
130139

131-
// load HTML templates needed for subscription page (common layout + page + component + subscription)
132-
if files, err := s.getHtmlFiles(); err != nil {
133-
logger.Warning("sub: getHtmlFiles failed:", err)
134-
} else {
135-
// register i18n function similar to web server
136-
i18nWebFunc := func(key string, params ...string) string {
137-
return locale.I18n(locale.Web, key, params...)
140+
// register i18n function similar to web server
141+
i18nWebFunc := func(key string, params ...string) string {
142+
return locale.I18n(locale.Web, key, params...)
143+
}
144+
engine.SetFuncMap(map[string]any{"i18n": i18nWebFunc})
145+
146+
// Templates: prefer embedded; fallback to disk if necessary
147+
if err := setEmbeddedTemplates(engine); err != nil {
148+
logger.Warning("sub: failed to parse embedded templates:", err)
149+
if files, derr := s.getHtmlFiles(); derr == nil {
150+
engine.LoadHTMLFiles(files...)
151+
} else {
152+
logger.Error("sub: no templates available (embedded parse and disk load failed)", err, derr)
138153
}
139-
engine.SetFuncMap(map[string]any{"i18n": i18nWebFunc})
140-
engine.LoadHTMLFiles(files...)
141154
}
142155

143-
// serve assets from web/assets to use shared JS/CSS like other pages
144-
engine.StaticFS("/assets", http.FS(os.DirFS("web/assets")))
156+
// Assets: use disk if present, fallback to embedded
157+
if _, err := os.Stat("web/assets"); err == nil {
158+
engine.StaticFS("/assets", http.FS(os.DirFS("web/assets")))
159+
} else {
160+
if subFS, err := fs.Sub(webpkg.EmbeddedAssets(), "assets"); err == nil {
161+
engine.StaticFS("/assets", http.FS(subFS))
162+
} else {
163+
logger.Error("sub: failed to mount embedded assets:", err)
164+
}
165+
}
145166

146167
g := engine.Group("/")
147168

web/html/subscription.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<script src="{{ .base_path }}assets/vue/vue.min.js?{{ .cur_ver }}"></script>
55
<script src="{{ .base_path }}assets/ant-design-vue/antd.min.js"></script>
66
<script src="{{ .base_path }}assets/js/util/index.js?{{ .cur_ver }}"></script>
7+
<script src="{{ .base_path }}assets/js/util/date-util.js?{{ .cur_ver }}"></script>
78
<script src="{{ .base_path }}assets/qrcode/qrious2.min.js?{{ .cur_ver }}"></script>
89
{{ template "page/head_end" .}}
910

web/locale/locale.go

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package locale
33
import (
44
"embed"
55
"io/fs"
6+
"os"
67
"strings"
78

89
"x-ui/logger"
@@ -48,22 +49,6 @@ func InitLocalizer(i18nFS embed.FS, settingService SettingService) error {
4849
return nil
4950
}
5051

51-
// InitLocalizerFS allows initializing i18n from any fs.FS (e.g., disk), rooted at a directory containing a "translation" folder
52-
func InitLocalizerFS(fsys fs.FS, settingService SettingService) error {
53-
// set default bundle to english
54-
i18nBundle = i18n.NewBundle(language.MustParse("en-US"))
55-
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
56-
57-
if err := parseTranslationFiles(fsys, i18nBundle); err != nil {
58-
return err
59-
}
60-
61-
if err := initTGBotLocalizer(settingService); err != nil {
62-
return err
63-
}
64-
return nil
65-
}
66-
6752
func createTemplateData(params []string, seperator ...string) map[string]any {
6853
var sep string = "=="
6954
if len(seperator) > 0 {
@@ -94,6 +79,11 @@ func I18n(i18nType I18nType, key string, params ...string) string {
9479

9580
templateData := createTemplateData(params)
9681

82+
if localizer == nil {
83+
// Fallback to key if localizer not ready; prevents nil panic on pages like sub
84+
return key
85+
}
86+
9787
msg, err := localizer.Localize(&i18n.LocalizeConfig{
9888
MessageID: key,
9989
TemplateData: templateData,
@@ -118,6 +108,15 @@ func initTGBotLocalizer(settingService SettingService) error {
118108

119109
func LocalizerMiddleware() gin.HandlerFunc {
120110
return func(c *gin.Context) {
111+
// Ensure bundle is initialized so creating a Localizer won't panic
112+
if i18nBundle == nil {
113+
i18nBundle = i18n.NewBundle(language.MustParse("en-US"))
114+
i18nBundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
115+
// Try lazy-load from disk when running sub server without InitLocalizer
116+
if err := loadTranslationsFromDisk(i18nBundle); err != nil {
117+
logger.Warning("i18n lazy load failed:", err)
118+
}
119+
}
121120
var lang string
122121

123122
if cookie, err := c.Request.Cookie("lang"); err == nil {
@@ -134,8 +133,27 @@ func LocalizerMiddleware() gin.HandlerFunc {
134133
}
135134
}
136135

137-
func parseTranslationFiles(fsys fs.FS, i18nBundle *i18n.Bundle) error {
138-
err := fs.WalkDir(fsys, "translation",
136+
// loadTranslationsFromDisk attempts to load translation files from "web/translation" using the local filesystem.
137+
func loadTranslationsFromDisk(bundle *i18n.Bundle) error {
138+
root := os.DirFS("web")
139+
return fs.WalkDir(root, "translation", func(path string, d fs.DirEntry, err error) error {
140+
if err != nil {
141+
return err
142+
}
143+
if d.IsDir() {
144+
return nil
145+
}
146+
data, err := fs.ReadFile(root, path)
147+
if err != nil {
148+
return err
149+
}
150+
_, err = bundle.ParseMessageFileBytes(data, path)
151+
return err
152+
})
153+
}
154+
155+
func parseTranslationFiles(i18nFS embed.FS, i18nBundle *i18n.Bundle) error {
156+
err := fs.WalkDir(i18nFS, "translation",
139157
func(path string, d fs.DirEntry, err error) error {
140158
if err != nil {
141159
return err
@@ -145,7 +163,7 @@ func parseTranslationFiles(fsys fs.FS, i18nBundle *i18n.Bundle) error {
145163
return nil
146164
}
147165

148-
data, err := fs.ReadFile(fsys, path)
166+
data, err := i18nFS.ReadFile(path)
149167
if err != nil {
150168
return err
151169
}

web/web.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,15 @@ func (f *wrapAssetsFileInfo) ModTime() time.Time {
7878
return startTime
7979
}
8080

81+
// Expose embedded resources for reuse by other servers (e.g., sub server)
82+
func EmbeddedHTML() embed.FS {
83+
return htmlFS
84+
}
85+
86+
func EmbeddedAssets() embed.FS {
87+
return assetsFS
88+
}
89+
8190
type Server struct {
8291
httpServer *http.Server
8392
listener net.Listener

0 commit comments

Comments
 (0)