Use “layouts” for the common HTML between pages

Had to call xgettext on Go source files because now the title comes from
there, as i assume i will have titles like "Invoice #INVxxxx" that have
to come from the database that the template does not know.
This commit is contained in:
jordi fita mas 2023-01-22 21:41:50 +01:00
parent fa6ddc70b3
commit 5505fa41c3
13 changed files with 115 additions and 110 deletions

View File

@ -1,8 +1,10 @@
INPUT_FILES := $(shell find web -name *.html) HTML_FILES := $(shell find web -name *.html)
GO_FILES := $(shell find . -name *.go)
DEFAULT_DOMAIN = numerus DEFAULT_DOMAIN = numerus
POT_FILE = po/$(DEFAULT_DOMAIN).pot POT_FILE = po/$(DEFAULT_DOMAIN).pot
LINGUAS = ca es LINGUAS = ca es
MO_FILES = $(patsubst %,locales/%/LC_MESSAGES/$(DEFAULT_DOMAIN).mo,$(LINGUAS)) MO_FILES = $(patsubst %,locales/%/LC_MESSAGES/$(DEFAULT_DOMAIN).mo,$(LINGUAS))
XGETTEXTFLAGS = --no-wrap --from-code=UTF-8 --package-name=numerus --msgid-bugs-address=jordi@tandem.blog
locales: $(MO_FILES) locales: $(MO_FILES)
@ -13,8 +15,9 @@ locales/%/LC_MESSAGES/numerus.mo: po/%.po
po/%.po: $(POT_FILE) po/%.po: $(POT_FILE)
msgmerge --no-wrap --update --backup=off $@ $< msgmerge --no-wrap --update --backup=off $@ $<
$(POT_FILE): $(INPUT_FILES) $(POT_FILE): $(HTML_FILES) $(GO_FILES)
xgettext --no-wrap --language=Scheme --from-code=UTF-8 --output=$@ --keyword=pgettext:1,2c --package-name=numerus --msgid-bugs-address=jordi@tandem.blog $^ xgettext $(XGETTEXTFLAGS) --language=Scheme --output=$@ --keyword=pgettext:1,2c $(HTML_FILES)
xgettext $(XGETTEXTFLAGS) --language=C --output=$@ --join-existing $(GO_FILES)
test-deploy: test-deploy:
sqitch deploy --db-name $(PGDATABASE) sqitch deploy --db-name $(PGDATABASE)

View File

@ -46,6 +46,10 @@ func getLocale(r *http.Request) *gotext.Locale {
return r.Context().Value(contextLocaleKey).(*gotext.Locale) return r.Context().Value(contextLocaleKey).(*gotext.Locale)
} }
func pgettext(context string, str string, locale *gotext.Locale) string {
return locale.GetC(str, context)
}
func mustGetAvailableLanguages(db *Db) []language.Tag { func mustGetAvailableLanguages(db *Db) []language.Tag {
rows, err := db.Query(context.Background(), "select lang_tag from language where selectable") rows, err := db.Query(context.Background(), "select lang_tag from language where selectable")
if err != nil { if err != nil {

View File

@ -52,7 +52,7 @@ func LoginHandler() http.Handler {
} else { } else {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
mustRenderTemplate(w, r, "login.html", page) mustRenderWebTemplate(w, r, "login.html", page)
}) })
} }

View File

@ -11,6 +11,7 @@ type LanguageOption struct {
} }
type ProfilePage struct { type ProfilePage struct {
Title string
Name string Name string
Email string Email string
Password string Password string
@ -27,7 +28,9 @@ func ProfileHandler() http.Handler {
return return
} }
conn := getConn(r) conn := getConn(r)
locale := getLocale(r)
page := ProfilePage{ page := ProfilePage{
Title: pgettext("title", "User Settings", locale),
Email: user.Email, Email: user.Email,
Languages: mustGetLanguageOptions(r.Context(), conn), Languages: mustGetLanguageOptions(r.Context(), conn),
} }
@ -44,7 +47,7 @@ func ProfileHandler() http.Handler {
panic(nil) panic(nil)
} }
} }
mustRenderTemplate(w, r, "profile.html", page) mustRenderAppTemplate(w, r, "profile.html", page)
}) })
} }

View File

@ -13,7 +13,7 @@ func NewRouter(db *Db) http.Handler {
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {
mustRenderTemplate(w, r, "index.html", nil) mustRenderAppTemplate(w, r, "dashboard.html", nil)
} else { } else {
http.Redirect(w, r, "/login", http.StatusSeeOther) http.Redirect(w, r, "/login", http.StatusSeeOther)
} }

View File

@ -6,17 +6,29 @@ import (
"net/http" "net/http"
) )
func mustRenderTemplate(wr io.Writer, r *http.Request, filename string, data interface{}) { func templateFile (name string) string {
return "web/template/" + name
}
func mustRenderTemplate(wr io.Writer, r *http.Request, layout string, filename string, data interface{}) {
locale := getLocale(r) locale := getLocale(r)
t := template.New(filename) t := template.New(filename)
t.Funcs(template.FuncMap{ t.Funcs(template.FuncMap{
"gettext": locale.Get, "gettext": locale.Get,
"pgettext": locale.GetC, "pgettext": locale.GetC,
}) })
if _, err := t.ParseFiles("web/template/" + filename); err != nil { if _, err := t.ParseFiles(templateFile(filename), templateFile(layout)); err != nil {
panic(err) panic(err)
} }
if err := t.Execute(wr, data); err != nil { if err := t.ExecuteTemplate(wr, layout, data); err != nil {
panic(err) panic(err)
} }
} }
func mustRenderAppTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
mustRenderTemplate(w, r, "app.html", filename, data);
}
func mustRenderWebTemplate(w io.Writer, r *http.Request, filename string, data interface{}) {
mustRenderTemplate(w, r, "web.html", filename, data);
}

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-01-22 02:20+0100\n" "POT-Creation-Date: 2023-01-22 21:38+0100\n"
"PO-Revision-Date: 2023-01-18 17:08+0100\n" "PO-Revision-Date: 2023-01-18 17:08+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n" "Language-Team: Catalan <ca@dodds.net>\n"
@ -17,75 +17,75 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: web/template/login.html:6 web/template/login.html:19 #: web/template/web.html:6 web/template/login.html:9
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
msgstr "Entrada" msgstr "Entrada"
#: web/template/login.html:14 #: web/template/login.html:5
msgid "Invalid user or password" msgid "Invalid user or password"
msgstr "Nom dusuari o contrasenya incorrectes" msgstr "Nom dusuari o contrasenya incorrectes"
#: web/template/login.html:21 web/template/profile.html:29 #: web/template/login.html:11 web/template/profile.html:10
msgctxt "input" msgctxt "input"
msgid "Email" msgid "Email"
msgstr "Correu electrònic" msgstr "Correu electrònic"
#: web/template/login.html:24 web/template/profile.html:35 #: web/template/login.html:14 web/template/profile.html:16
msgctxt "input" msgctxt "input"
msgid "Password" msgid "Password"
msgstr "Contrasenya" msgstr "Contrasenya"
#: web/template/login.html:27 #: web/template/login.html:17
msgctxt "action" msgctxt "action"
msgid "Login" msgid "Login"
msgstr "Entra" msgstr "Entra"
#: web/template/profile.html:6 web/template/profile.html:21 #: web/template/profile.html:2 pkg/profile.go:33
msgctxt "title" msgctxt "title"
msgid "User Settings" msgid "User Settings"
msgstr "Configuració usuari" msgstr "Configuració usuari"
#: web/template/profile.html:15 web/template/index.html:15 #: web/template/profile.html:5
msgid "Account"
msgstr "Compte"
#: web/template/profile.html:16 web/template/index.html:16
msgctxt "action"
msgid "Logout"
msgstr "Surt"
#: web/template/profile.html:24
msgctxt "title" msgctxt "title"
msgid "User Access Data" msgid "User Access Data"
msgstr "Dades accés usuari" msgstr "Dades accés usuari"
#: web/template/profile.html:26 #: web/template/profile.html:7
msgctxt "input" msgctxt "input"
msgid "User name" msgid "User name"
msgstr "Nom dusuari" msgstr "Nom dusuari"
#: web/template/profile.html:33 #: web/template/profile.html:14
msgctxt "title" msgctxt "title"
msgid "Password Change" msgid "Password Change"
msgstr "Canvi contrasenya" msgstr "Canvi contrasenya"
#: web/template/profile.html:38 #: web/template/profile.html:19
msgctxt "input" msgctxt "input"
msgid "Password Confirmation" msgid "Password Confirmation"
msgstr "Confirmació contrasenya" msgstr "Confirmació contrasenya"
#: web/template/profile.html:42 #: web/template/profile.html:23
msgctxt "input" msgctxt "input"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: web/template/profile.html:44 #: web/template/profile.html:25
msgctxt "language option" msgctxt "language option"
msgid "Automatic" msgid "Automatic"
msgstr "Automàtic" msgstr "Automàtic"
#: web/template/profile.html:49 #: web/template/profile.html:30
msgctxt "action" msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Desa canvis" msgstr "Desa canvis"
#: web/template/app.html:15
msgid "Account"
msgstr "Compte"
#: web/template/app.html:16
msgctxt "action"
msgid "Logout"
msgstr "Surt"

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: numerus\n" "Project-Id-Version: numerus\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-01-22 02:20+0100\n" "POT-Creation-Date: 2023-01-22 21:38+0100\n"
"PO-Revision-Date: 2023-01-18 17:45+0100\n" "PO-Revision-Date: 2023-01-18 17:45+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n" "Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n" "Language-Team: Spanish <es@tp.org.es>\n"
@ -17,75 +17,75 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: web/template/login.html:6 web/template/login.html:19 #: web/template/web.html:6 web/template/login.html:9
msgctxt "title" msgctxt "title"
msgid "Login" msgid "Login"
msgstr "Entrada" msgstr "Entrada"
#: web/template/login.html:14 #: web/template/login.html:5
msgid "Invalid user or password" msgid "Invalid user or password"
msgstr "Nombre de usuario o contraseña inválido" msgstr "Nombre de usuario o contraseña inválido"
#: web/template/login.html:21 web/template/profile.html:29 #: web/template/login.html:11 web/template/profile.html:10
msgctxt "input" msgctxt "input"
msgid "Email" msgid "Email"
msgstr "Correo electrónico" msgstr "Correo electrónico"
#: web/template/login.html:24 web/template/profile.html:35 #: web/template/login.html:14 web/template/profile.html:16
msgctxt "input" msgctxt "input"
msgid "Password" msgid "Password"
msgstr "Contraseña" msgstr "Contraseña"
#: web/template/login.html:27 #: web/template/login.html:17
msgctxt "action" msgctxt "action"
msgid "Login" msgid "Login"
msgstr "Entrar" msgstr "Entrar"
#: web/template/profile.html:6 web/template/profile.html:21 #: web/template/profile.html:2 pkg/profile.go:33
msgctxt "title" msgctxt "title"
msgid "User Settings" msgid "User Settings"
msgstr "Configuración usuario" msgstr "Configuración usuario"
#: web/template/profile.html:15 web/template/index.html:15 #: web/template/profile.html:5
msgid "Account"
msgstr "Cuenta"
#: web/template/profile.html:16 web/template/index.html:16
msgctxt "action"
msgid "Logout"
msgstr "Salir"
#: web/template/profile.html:24
msgctxt "title" msgctxt "title"
msgid "User Access Data" msgid "User Access Data"
msgstr "Datos acceso usuario" msgstr "Datos acceso usuario"
#: web/template/profile.html:26 #: web/template/profile.html:7
msgctxt "input" msgctxt "input"
msgid "User name" msgid "User name"
msgstr "Nombre de usuario" msgstr "Nombre de usuario"
#: web/template/profile.html:33 #: web/template/profile.html:14
msgctxt "title" msgctxt "title"
msgid "Password Change" msgid "Password Change"
msgstr "Cambio de contraseña" msgstr "Cambio de contraseña"
#: web/template/profile.html:38 #: web/template/profile.html:19
msgctxt "input" msgctxt "input"
msgid "Password Confirmation" msgid "Password Confirmation"
msgstr "Confirmación contrasenya" msgstr "Confirmación contrasenya"
#: web/template/profile.html:42 #: web/template/profile.html:23
msgctxt "input" msgctxt "input"
msgid "Language" msgid "Language"
msgstr "Idioma" msgstr "Idioma"
#: web/template/profile.html:44 #: web/template/profile.html:25
msgctxt "language option" msgctxt "language option"
msgid "Automatic" msgid "Automatic"
msgstr "Automático" msgstr "Automático"
#: web/template/profile.html:49 #: web/template/profile.html:30
msgctxt "action" msgctxt "action"
msgid "Save changes" msgid "Save changes"
msgstr "Guardar cambios" msgstr "Guardar cambios"
#: web/template/app.html:15
msgid "Account"
msgstr "Cuenta"
#: web/template/app.html:16
msgctxt "action"
msgid "Logout"
msgstr "Salir"

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Numerus</title> <title>{{ .Title }} — Numerus</title>
<link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css"> <link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css">
</head> </head>
<body> <body>
@ -18,6 +18,7 @@
</nav> </nav>
</header> </header>
<main> <main>
{{- template "content" . }}
</main> </main>
</body> </body>
</html> </html>

View File

@ -0,0 +1,2 @@
{{ define "content" }}
{{- end }}

View File

@ -1,31 +1,20 @@
<!doctype html> {{ define "content" }}
<html lang="en"> <h1><img src="/static/numerus.svg" alt="Numerus" width="620" height="77"></h1>
<head> {{ if .LoginError -}}
<meta charset="utf-8"> <div class="error" role="alert">
<meta name="viewport" content="width=device-width, initial-scale=1"> <p>{{( gettext "Invalid user or password" )}}</p>
<title>{{( pgettext "Login" "title" )}} — Numerus</title> </div>
<link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css"> {{- end }}
</head> <section id="login">
<body class="web"> <h2>{{( pgettext "Login" "title" )}}</h2>
<h1><img src="/static/numerus.svg" alt="Numerus" width="620" height="77"></h1> <form method="POST" action="/login">
<label for="user_email">{{( pgettext "Email" "input" )}}</label>
<input id="user_email" type="email" required autofocus name="email" autocapitalize="none" value="{{ .Email }}">
{{ if .LoginError }} <label for="user_password">{{( pgettext "Password" "input" )}}</label>
<div class="error" role="alert"> <input id="user_password" type="password" required name="password" autocomplete="current-password" value="{{ .Password }}">
<p>{{( gettext "Invalid user or password" )}}</p>
</div>
{{ end }}
<section id="login"> <button type="submit">{{( pgettext "Login" "action" )}}</button>
<h2>{{( pgettext "Login" "title" )}}</h2> </form>
<form method="POST" action="/login"> </section>
<label for="user_email">{{( pgettext "Email" "input" )}}</label> {{- end }}
<input id="user_email" type="email" required autofocus name="email" autocapitalize="none" value="{{ .Email }}">
<label for="user_password">{{( pgettext "Password" "input" )}}</label>
<input id="user_password" type="password" required name="password" autocomplete="current-password" value="{{ .Password }}">
<button type="submit">{{( pgettext "Login" "action" )}}</button>
</form>
</section>
</body>
</html>

View File

@ -1,23 +1,4 @@
<!doctype html> {{ define "content" }}
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{(pgettext "User Settings" "title")}} — Numerus</title>
<link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css">
</head>
<body>
<header>
<h1><img src="/static/numerus.svg" alt="Numerus" width="261" height="33"></h1>
<nav role="navigation">
<button aria-haspopup="true"><i class="ri-eye-close-line ri-3x"></i></button>
<ul>
<li><a href="/profile"><i class="ri-account-circle-line"></i> {{( gettext "Account" )}}</a></li>
<li><form method="POST" action="/logout"><button type="submit"><i class="ri-logout-circle-line"></i> {{( pgettext "Logout" "action" )}}</button></form></li>
</ul>
</nav>
</header>
<main>
<h2>{{(pgettext "User Settings" "title")}}</h2> <h2>{{(pgettext "User Settings" "title")}}</h2>
<form method="POST" action="/profile"> <form method="POST" action="/profile">
<fieldset> <fieldset>
@ -42,12 +23,10 @@
<label for="language">{{( pgettext "Language" "input" )}}</label> <label for="language">{{( pgettext "Language" "input" )}}</label>
<select id="language" name="language"> <select id="language" name="language">
<option value="und">{{( pgettext "Automatic" "language option" )}}</option> <option value="und">{{( pgettext "Automatic" "language option" )}}</option>
{{ range $language := .Languages }} {{- range $language := .Languages }}
<option value="{{ .Tag }}" {{ if eq .Tag $.Language }}selected="selected"{{ end }}>{{ .Name }}</option> <option value="{{ .Tag }}" {{ if eq .Tag $.Language }}selected="selected"{{ end }}>{{ .Name }}</option>
{{ end }} {{- end }}
</select> </select>
<button type="submit">{{( pgettext "Save changes" "action" )}}</button> <button type="submit">{{( pgettext "Save changes" "action" )}}</button>
</form> </form>
</main> {{- end }}
</body>
</html>

12
web/template/web.html Normal file
View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{( pgettext "Login" "title" )}} — Numerus</title>
<link rel="stylesheet" type="text/css" media="screen" href="/static/numerus.css">
</head>
<body class="web">
{{- template "content" . }}
</body>
</html>