Skip to content

Commit

Permalink
refactor(mimetype/html): 不再区分是否带 localized 参数
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed Nov 25, 2024
1 parent 3438969 commit a7cd7c4
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 69 deletions.
95 changes: 30 additions & 65 deletions mimetype/html/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"maps"
"slices"

"github.com/issue9/sliceutil"
"golang.org/x/text/language"

"github.com/issue9/web"
Expand All @@ -20,91 +21,57 @@ type contextType int
const viewContextKey contextType = 1

type view struct {
localized map[language.Tag]string
tpls map[language.Tag]*template.Template
matcher language.Matcher
}

// Init 初始化 html 模板系统
//
// localized 模板的本地化目录列表。键值为目录名称,键名为该目录对应的本地化 ID。
// 将目录映射到 [language.Und],表示该目录作为默认模板使用。
// 如果 localized 为空,则表示不按目录进行区分,将加载所有内容至一个模板实例中。
func Init(s web.Server, localized map[language.Tag]string) {
if _, ok := s.Vars().Load(viewContextKey); ok {
panic("已经初始化")
}

v := &view{localized: localized}

if len(localized) > 0 {
tags := slices.Collect(maps.Keys(localized))
if slices.Index(tags, language.Und) < 0 {
panic("必须指定 language.Und")
}

v.matcher = language.NewMatcher(tags)
}

s.Vars().Store(viewContextKey, v)
tpls map[language.Tag]*template.Template
matcher language.Matcher
}

// Install 安装模板
//
// 提供了以下两个方法
// funcs 添加到当前模板系统的函数,除此之个,默认提供了以下两个函数
// - t 根据当前的语言([web.Context.LanguageTag])对参数进行翻译;
// - tt 将内容翻译成指定语言,语言 ID 由第一个参数指定;
//
// fsys 表示模板目录。该目录下应该包含 Init 中 localized 参数指定的所有目录
// 否则会 panic。
// localized 本地化 ID 与目录的映射关系,表示这些目录只解析至对应的 ID
// 如果为空则相当于 {language.Und: "."};
//
// 通过此函数安装之后,可以正常输出以下内容:
// - string 直接输出字符串;
// - []byte 直接输出内容;
// - Marshaler 将 [Marshaler.MarshalHTML] 返回内容作为输出内容;
// - 其它结构体,尝试读取 XMLName 字段的 html struct tag 值作为模板名称进行查找;
// fsys 表示模板目录。如果 localized 不为空,则只解析该参数中指定的目录,否则解析整个目录;
//
// NOTE: 可以多次调用,相同名称的模板会覆盖。
func Install(s web.Server, funcs template.FuncMap, glob string, fsys ...fs.FS) {
v, ok := s.Vars().Load(viewContextKey)
if !ok {
panic("未初始化")
func Install(s web.Server, funcs template.FuncMap, localized map[language.Tag]string, glob string, fsys ...fs.FS) {
var v *view
if vv, ok := s.Vars().Load(viewContextKey); !ok {
v = &view{}
s.Vars().Store(viewContextKey, v)
} else {
v = vv.(*view)
}

vv := v.(*view)
for _, f := range fsys {
install(s, vv, funcs, glob, f)
if localized == nil {
localized = map[language.Tag]string{language.Und: "."}
}
}

func install(s web.Server, v *view, funcs template.FuncMap, glob string, fsys fs.FS) {
if len(v.localized) > 0 {
instalLocalizedView(s, v, funcs, glob, fsys)
} else {
if len(v.tpls) == 0 { // 第一次调用
f := getTranslateFuncMap(s)
maps.Copy(f, funcs) // 用户定义的可覆盖系统自带的
funcs = f
}
tpl := template.New(s.Name()).Funcs(funcs)
template.Must(tpl.ParseFS(fsys, glob))
s.Vars().Store(viewContextKey, &view{tpls: map[language.Tag]*template.Template{language.Und: tpl}})
keys := slices.AppendSeq(slices.Collect(maps.Keys(v.tpls)), maps.Keys(localized))
keys = sliceutil.Unique(keys, func(i, j language.Tag) bool { return i == j })
v.matcher = language.NewMatcher(keys)

for _, f := range fsys {
install(s, v, funcs, localized, glob, f)
}
}

func instalLocalizedView(s web.Server, v *view, funcs template.FuncMap, glob string, fsys fs.FS) {
tpls := make(map[language.Tag]*template.Template, len(v.localized))
func install(s web.Server, v *view, funcs template.FuncMap, localized map[language.Tag]string, glob string, fsys fs.FS) {
tpls := make(map[language.Tag]*template.Template, len(localized))

for t, name := range v.localized {
for t, name := range localized {
sub, err := fs.Sub(fsys, name)
if err != nil {
panic(err)
}

tpl, found := v.tpls[t]
if found { // 首次添加
if found {
tpl = template.Must(tpl.Funcs(funcs).ParseFS(sub, glob))
} else {
} else { // 首次添加
f := getTranslateFuncMap(s)
maps.Copy(f, funcs) // 用户定义的可覆盖系统自带的
tpl = template.Must(template.New(name).Funcs(f).ParseFS(sub, glob))
Expand Down Expand Up @@ -135,12 +102,10 @@ func (v *view) buildCurrentTpl(ctx *web.Context) *template.Template {
return nil
}

var tpl *template.Template
if len(v.localized) == 0 {
tag, _, _ := v.matcher.Match(ctx.LanguageTag())
tpl, found := v.tpls[tag]
if !found && tag != language.Und {
tpl = v.tpls[language.Und]
} else {
tag, _, _ := v.matcher.Match(ctx.LanguageTag())
tpl = v.tpls[tag]
}

return tpl.Funcs(template.FuncMap{
Expand Down
7 changes: 3 additions & 4 deletions mimetype/html/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ func newServer(a *assert.Assertion, lang string) web.Server {
func TestInstall(t *testing.T) {
a := assert.New(t, false)
s := newServer(a, "und") // und 被解析为 language.Und,将会尝试读取系统的本地化信息
html.Init(s, nil)
html.Install(s, nil, "*.tpl", os.DirFS("./testdata/view"))
html.Install(s, nil, nil, "*.tpl", os.DirFS("./testdata/view"))

defer servertest.Run(a, s)()
defer s.Close(500 * time.Millisecond)
Expand Down Expand Up @@ -73,10 +72,10 @@ func TestInstall(t *testing.T) {
}

func TestInstall_withLocalized(t *testing.T) {
localized := map[language.Tag]string{language.SimplifiedChinese: "cmn-hans", language.TraditionalChinese: "cmn-hant", language.Und: "cmn-hans"}
a := assert.New(t, false)
s := newServer(a, "cmn-hans")
html.Init(s, map[language.Tag]string{language.SimplifiedChinese: "cmn-hans", language.TraditionalChinese: "cmn-hant", language.Und: "cmn-hans"})
html.Install(s, nil, "*.tpl", os.DirFS("./testdata/dir"))
html.Install(s, nil, localized, "*.tpl", os.DirFS("./testdata/dir"))

defer servertest.Run(a, s)()
defer s.Close(500 * time.Millisecond)
Expand Down

0 comments on commit a7cd7c4

Please sign in to comment.