diff options
Diffstat (limited to 'internal/web/templates.go')
| -rw-r--r-- | internal/web/templates.go | 50 |
1 files changed, 35 insertions, 15 deletions
diff --git a/internal/web/templates.go b/internal/web/templates.go index e7e35f5..7f71304 100644 --- a/internal/web/templates.go +++ b/internal/web/templates.go @@ -12,23 +12,27 @@ import ( "strings" "sync" "time" + + "github.com/gramanas/blogspace/internal/i18n" + "github.com/gramanas/blogspace/internal/store" ) //go:embed templates static var assets embed.FS -// templates compiles each page together with its layout. In dev mode it -// re-reads from disk on every render so edits show up without a restart. +// templates compiles each page together with its layout, once per language +// (the translating funcs are closed over the language, so templates just +// write {{t "…"}}). In dev mode it re-reads from disk on every render so +// edits show up without a restart. type templates struct { dev bool fs fs.FS - funcs template.FuncMap mu sync.Mutex - cache map[string]*template.Template + cache map[string]*template.Template // "lang/dir/file.html" } -func newTemplates(dev bool, funcs template.FuncMap) *templates { - t := &templates{dev: dev, funcs: funcs, cache: map[string]*template.Template{}} +func newTemplates(dev bool) *templates { + t := &templates{dev: dev, cache: map[string]*template.Template{}} t.fs = assets if dev { // locate the package directory so `go run` from anywhere still finds the files @@ -54,38 +58,55 @@ func layoutFor(name string) string { return "layouts/dashboard.html" } -func (t *templates) get(name string) (*template.Template, error) { +func (t *templates) get(lang, name string) (*template.Template, error) { + key := lang + "/" + name if !t.dev { t.mu.Lock() - if tpl, ok := t.cache[name]; ok { + if tpl, ok := t.cache[key]; ok { t.mu.Unlock() return tpl, nil } t.mu.Unlock() } - tpl, err := template.New("").Funcs(t.funcs).ParseFS(t.fs, + tpl, err := template.New("").Funcs(funcsFor(lang)).ParseFS(t.fs, "templates/"+layoutFor(name), "templates/partials/*.html", "templates/"+name) if err != nil { return nil, fmt.Errorf("parse %s: %w", name, err) } if !t.dev { t.mu.Lock() - t.cache[name] = tpl + t.cache[key] = tpl t.mu.Unlock() } return tpl, nil } -func (t *templates) render(w io.Writer, name string, data any) error { - tpl, err := t.get(name) +func (t *templates) render(w io.Writer, lang, name string, data any) error { + tpl, err := t.get(lang, name) if err != nil { return err } return tpl.ExecuteTemplate(w, "layout", data) } +// funcsFor is the shared funcs plus the ones that speak the page's language. +func funcsFor(lang string) template.FuncMap { + m := make(template.FuncMap, len(funcs)+6) + for k, v := range funcs { + m[k] = v + } + // t translates fixed text; tf also formats: {{tf "Page %d of %d" .N .Last}} + m["t"] = func(key string) string { return i18n.T(lang, key) } + m["tf"] = func(key string, args ...any) string { return i18n.Tf(lang, key, args...) } + // date is for the dashboard; postdate follows the theme's date style + m["date"] = func(t time.Time) string { return i18n.FormatDate(lang, "long", t) } + m["postdate"] = func(style string, t time.Time) string { return i18n.FormatDate(lang, style, t) } + m["month"] = func(mo time.Month) string { return i18n.Month(lang, mo) } + m["modsummary"] = func(mod store.Module) string { return moduleSummary(lang, mod) } + return m +} + var funcs = template.FuncMap{ - "date": func(t time.Time) string { return t.Format("2 January 2006") }, "rfc": func(t time.Time) string { return t.Format(time.RFC1123Z) }, "html": func(s string) template.HTML { return template.HTML(s) }, "css": func(s string) template.CSS { return template.CSS(s) }, @@ -107,8 +128,7 @@ var funcs = template.FuncMap{ // split turns a plain-text field into lines: {{range split .Body "\n"}}<p>{{.}}</p>{{end}} "split": strings.Split, "lower": strings.ToLower, - // layout modules: one-line description and whether the kind has an edit page - "modsummary": moduleSummary, + // layout modules: whether the kind has an edit page (modsummary is per language) "modsettings": moduleHasSettings, // hasPrefix marks the current dashboard tab: {{if hasPrefix .Path "/b/x/posts"}} "hasPrefix": strings.HasPrefix, |
