aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/templates.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/web/templates.go')
-rw-r--r--internal/web/templates.go50
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,