Split templates and handlers into admin and public
I need to check that the user is an employee (or admin) in administration handlers, but i do not want to do it for each handler, because i am bound to forget it. Thus, i added the /admin sub-path for these resources. The public-facing web is the rest of the resources outside /admin, but for now there is only home, to test whether it works as expected or not. The public-facing web can not relay on the user’s language settings, as the guest user has no way to set that. I would be happy to just use the Accept-Language header for that, but apparently Google does not use that header[0], and they give four alternatives: a country-specific domain, a subdomain with a generic top-level domain (gTLD), subdirectories with a gTLD, or URL parameters (e.g., site.com?loc=de). Of the four, Google does not recommend URL parameters, and the customer is already using subdirectories with the current site, therefor that’s what i have chosen. Google also tells me that it is a very good idea to have links between localized version of the same resources, either with <link> elements, Link HTTP response headers, or a sitemap file[1]; they are all equivalent in the eyes of Google. I have choosen the Link response headers way, because for that i can simply “augment” ResponseHeader to automatically add these headers when the response status is 2xx, otherwise i would need to pass down the original URL path until it reaches the template. Even though Camper is supposed to be a “generic”, multi-company application, i think i will stick to the easiest route and write the templates for just the “first” customer. [0]: https://developers.google.com/search/docs/specialty/international/managing-multi-regional-sites [1]: https://developers.google.com/search/docs/specialty/international/localized-versions
This commit is contained in:
parent
9349cda5f6
commit
e128680e9a
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||
"dev.tandem.ws/tandem/camper/pkg/campsite"
|
||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
)
|
||||
|
||||
type adminHandler struct {
|
||||
campsite *campsite.Handler
|
||||
}
|
||||
|
||||
func newAdminHandler() *adminHandler {
|
||||
return &adminHandler{
|
||||
campsite: campsite.NewHandler(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *adminHandler) Handle(user *auth.User, company *auth.Company, conn *database.Conn, requestPath string) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !user.LoggedIn {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
serveLoginForm(w, r, user, company, requestPath)
|
||||
return
|
||||
}
|
||||
|
||||
if !user.IsEmployee() {
|
||||
http.Error(w, user.Locale.Gettext("Access forbidden"), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var head string
|
||||
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
||||
switch head {
|
||||
case "campsites":
|
||||
h.campsite.Handler(user, company, conn).ServeHTTP(w, r)
|
||||
case "":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
serveDashboard(w, r, user, company)
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func serveDashboard(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRenderAdmin(w, r, user, company, "dashboard.gohtml", nil)
|
||||
}
|
|
@ -11,18 +11,17 @@ import (
|
|||
"golang.org/x/text/language"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||
"dev.tandem.ws/tandem/camper/pkg/campsite"
|
||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
db *database.DB
|
||||
fileHandler http.Handler
|
||||
profile *profileHandler
|
||||
campsite *campsite.Handler
|
||||
admin *adminHandler
|
||||
public *publicHandler
|
||||
locales locale.Locales
|
||||
defaultLocale *locale.Locale
|
||||
languageMatcher language.Matcher
|
||||
|
@ -39,7 +38,8 @@ func New(db *database.DB, avatarsDir string) (http.Handler, error) {
|
|||
db: db,
|
||||
fileHandler: static,
|
||||
profile: profile,
|
||||
campsite: campsite.NewHandler(),
|
||||
admin: newAdminHandler(),
|
||||
public: newPublicHandler(),
|
||||
locales: locales,
|
||||
defaultLocale: locales[language.Catalan],
|
||||
languageMatcher: language.NewMatcher(locales.Tags()),
|
||||
|
@ -81,41 +81,38 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
panic(err)
|
||||
}
|
||||
|
||||
if head == "login" {
|
||||
switch head {
|
||||
case "":
|
||||
http.Redirect(w, r, "/"+user.Locale.Language.String()+"/", http.StatusFound)
|
||||
case "admin":
|
||||
h.admin.Handle(user, company, conn, requestPath).ServeHTTP(w, r)
|
||||
case "login":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
serveLoginForm(w, r, user, company, "/")
|
||||
serveLoginForm(w, r, user, company, "/admin")
|
||||
case http.MethodPost:
|
||||
handleLogin(w, r, user, company, conn)
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodPost, http.MethodGet)
|
||||
}
|
||||
} else {
|
||||
if !user.LoggedIn {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
serveLoginForm(w, r, user, company, requestPath)
|
||||
case "me":
|
||||
h.profile.Handler(user, company, conn, requestPath).ServeHTTP(w, r)
|
||||
default:
|
||||
langTag, err := language.Parse(head)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch head {
|
||||
case "me":
|
||||
h.profile.Handler(user, company, conn).ServeHTTP(w, r)
|
||||
case "campsites":
|
||||
h.campsite.Handler(user, company, conn).ServeHTTP(w, r)
|
||||
case "":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
serveDashboard(w, r, user, company)
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
||||
}
|
||||
default:
|
||||
urlLocale := h.locales[langTag]
|
||||
if urlLocale == nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
user.Locale = urlLocale
|
||||
if r.Method == http.MethodGet || r.Method == http.MethodHead {
|
||||
w = httplib.LanguageLinks(w, false, r.Host, r.URL.Path, h.locales)
|
||||
}
|
||||
h.public.Handler(user, company, conn).ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveDashboard(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRender(w, r, user, company, "dashboard.gohtml", nil)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ func (f *loginForm) Parse(r *http.Request) error {
|
|||
f.Password.FillValue(r)
|
||||
f.Redirect.FillValue(r)
|
||||
if f.Redirect.Val == "" {
|
||||
f.Redirect.Val = "/"
|
||||
f.Redirect.Val = "/admin/"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ func (f *loginForm) Valid(l *locale.Locale) bool {
|
|||
}
|
||||
|
||||
func (f *loginForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRender(w, r, user, company, "login.gohtml", f)
|
||||
template.MustRenderAdmin(w, r, user, company, "login.gohtml", f)
|
||||
}
|
||||
|
||||
func serveLoginForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, redirectPath string) {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
)
|
||||
|
||||
type publicHandler struct{}
|
||||
|
||||
func newPublicHandler() *publicHandler {
|
||||
return &publicHandler{}
|
||||
}
|
||||
|
||||
func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var head string
|
||||
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
||||
switch head {
|
||||
case "":
|
||||
template.MustRenderPublic(w, r, user, company, "home.gohtml", nil)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -80,8 +80,14 @@ func newProfileHandler(static http.Handler, avatarsDir string) (*profileHandler,
|
|||
return handler, nil
|
||||
}
|
||||
|
||||
func (h *profileHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.HandlerFunc {
|
||||
func (h *profileHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn, requestPath string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !user.LoggedIn {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
serveLoginForm(w, r, user, company, requestPath)
|
||||
return
|
||||
}
|
||||
|
||||
var head string
|
||||
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
||||
|
||||
|
@ -249,7 +255,7 @@ func (f *profileForm) Valid(l *locale.Locale) bool {
|
|||
}
|
||||
|
||||
func (f *profileForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRender(w, r, user, company, "profile.gohtml", f)
|
||||
template.MustRenderAdmin(w, r, user, company, "profile.gohtml", f)
|
||||
}
|
||||
|
||||
func (f *profileForm) HasAvatarFile() bool {
|
||||
|
|
|
@ -39,3 +39,8 @@ func (user *User) VerifyCSRFToken(r *http.Request) error {
|
|||
}
|
||||
return errors.New(user.Locale.Gettext("Cross-site request forgery detected."))
|
||||
}
|
||||
|
||||
func (user *User) IsEmployee() bool {
|
||||
role := user.Role[0]
|
||||
return role == 'e' || role == 'a'
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ type typeIndex struct {
|
|||
}
|
||||
|
||||
func (page *typeIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRender(w, r, user, company, "campsite/type/index.gohtml", page)
|
||||
template.MustRenderAdmin(w, r, user, company, "campsite/type/index.gohtml", page)
|
||||
}
|
||||
|
||||
func addType(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
||||
|
@ -145,5 +145,5 @@ func (f *typeForm) Valid(l *locale.Locale) bool {
|
|||
}
|
||||
|
||||
func (f *typeForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRender(w, r, user, company, "campsite/type/new.gohtml", f)
|
||||
template.MustRenderAdmin(w, r, user, company, "campsite/type/new.gohtml", f)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||
)
|
||||
|
||||
type languageLinks struct {
|
||||
http.ResponseWriter
|
||||
schemaAuthority string
|
||||
path string
|
||||
locales locale.Locales
|
||||
wroteHeader bool
|
||||
}
|
||||
|
||||
func (w *languageLinks) WriteHeader(statusCode int) {
|
||||
if statusCode >= 200 && statusCode < 300 {
|
||||
for k := range w.locales {
|
||||
tag := k.String()
|
||||
w.Header().Add("Link", fmt.Sprintf(`<%[1]s/%[2]s%[3]s>; rel="alternate"; hreflang="%[2]s"`, w.schemaAuthority, tag, w.path))
|
||||
}
|
||||
}
|
||||
w.wroteHeader = true
|
||||
w.ResponseWriter.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func (w *languageLinks) Write(data []byte) (int, error) {
|
||||
if !w.wroteHeader {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func LanguageLinks(w http.ResponseWriter, https bool, authority string, path string, locales locale.Locales) http.ResponseWriter {
|
||||
var schema string
|
||||
if https {
|
||||
schema = "https"
|
||||
} else {
|
||||
schema = "http"
|
||||
}
|
||||
return &languageLinks{w, schema + "://" + authority, path, locales, false}
|
||||
}
|
|
@ -15,19 +15,28 @@ import (
|
|||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
)
|
||||
|
||||
func templateFile(name string) string {
|
||||
return "web/templates/" + name
|
||||
func adminTemplateFile(name string) string {
|
||||
return "web/templates/admin/" + name
|
||||
}
|
||||
|
||||
func MustRender(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
|
||||
func publicTemplateFile(name string) string {
|
||||
return "web/templates/public/" + name
|
||||
}
|
||||
|
||||
func MustRenderAdmin(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
|
||||
layout := "layout.gohtml"
|
||||
if httplib.IsHTMxRequest(r) {
|
||||
layout = "htmx.gohtml"
|
||||
}
|
||||
mustRenderLayout(w, user, company, layout, filename, data)
|
||||
mustRenderLayout(w, user, company, adminTemplateFile, layout, filename, data)
|
||||
}
|
||||
|
||||
func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, layout string, filename string, data interface{}) {
|
||||
func MustRenderPublic(w io.Writer, r *http.Request, user *auth.User, company *auth.Company, filename string, data interface{}) {
|
||||
layout := "layout.gohtml"
|
||||
mustRenderLayout(w, user, company, publicTemplateFile, layout, filename, data)
|
||||
}
|
||||
|
||||
func mustRenderLayout(w io.Writer, user *auth.User, company *auth.Company, templateFile func(string) string, layout string, filename string, data interface{}) {
|
||||
t := template.New(filename)
|
||||
t.Funcs(template.FuncMap{
|
||||
"gettext": user.Locale.Get,
|
||||
|
|
88
po/ca.po
88
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-08-04 19:51+0200\n"
|
||||
"POT-Creation-Date: 2023-08-05 03:23+0200\n"
|
||||
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -18,123 +18,133 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:14
|
||||
#: web/templates/campsite/type/new.gohtml:20
|
||||
#: web/templates/public/home.gohtml:6
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inici"
|
||||
|
||||
#: web/templates/public/layout.gohtml:10 web/templates/public/layout.gohtml:17
|
||||
msgid "Campsite Montagut"
|
||||
msgstr "Càmping Montagut"
|
||||
|
||||
#: web/templates/public/layout.gohtml:16 web/templates/admin/layout.gohtml:18
|
||||
msgid "Skip to main content"
|
||||
msgstr "Salta al contingut principal"
|
||||
|
||||
#: web/templates/admin/campsite/type/new.gohtml:14
|
||||
#: web/templates/admin/campsite/type/new.gohtml:20
|
||||
msgctxt "title"
|
||||
msgid "New Campsite Type"
|
||||
msgstr "Nou tipus d’allotjament"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:25 web/templates/profile.gohtml:26
|
||||
#: web/templates/admin/campsite/type/new.gohtml:25
|
||||
#: web/templates/admin/profile.gohtml:26
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:33
|
||||
#: web/templates/admin/campsite/type/new.gohtml:33
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:40
|
||||
#: web/templates/admin/campsite/type/new.gohtml:40
|
||||
msgctxt "action"
|
||||
msgid "Add"
|
||||
msgstr "Afegeix"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:6
|
||||
#: web/templates/campsite/type/index.gohtml:12
|
||||
#: web/templates/admin/campsite/type/index.gohtml:6
|
||||
#: web/templates/admin/campsite/type/index.gohtml:12
|
||||
msgctxt "title"
|
||||
msgid "Campsite Types"
|
||||
msgstr "Tipus d’allotjaments"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:11
|
||||
#: web/templates/admin/campsite/type/index.gohtml:11
|
||||
msgctxt "action"
|
||||
msgid "Add Type"
|
||||
msgstr "Afegeix tipus"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:17
|
||||
#: web/templates/admin/campsite/type/index.gohtml:17
|
||||
msgctxt "header"
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:29
|
||||
#: web/templates/admin/campsite/type/index.gohtml:29
|
||||
msgid "No campsite types added yet."
|
||||
msgstr "No s’ha afegit cap tipus d’allotjament encara."
|
||||
|
||||
#: web/templates/dashboard.gohtml:6 web/templates/dashboard.gohtml:10
|
||||
#: web/templates/layout.gohtml:44
|
||||
#: web/templates/admin/dashboard.gohtml:6
|
||||
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:44
|
||||
msgctxt "title"
|
||||
msgid "Dashboard"
|
||||
msgstr "Tauler"
|
||||
|
||||
#: web/templates/login.gohtml:6 web/templates/login.gohtml:13
|
||||
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:13
|
||||
msgctxt "title"
|
||||
msgid "Login"
|
||||
msgstr "Entrada"
|
||||
|
||||
#: web/templates/login.gohtml:22 web/templates/profile.gohtml:35
|
||||
#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35
|
||||
msgctxt "input"
|
||||
msgid "Email"
|
||||
msgstr "Correu-e"
|
||||
|
||||
#: web/templates/login.gohtml:31 web/templates/profile.gohtml:46
|
||||
#: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46
|
||||
msgctxt "input"
|
||||
msgid "Password"
|
||||
msgstr "Contrasenya"
|
||||
|
||||
#: web/templates/login.gohtml:40
|
||||
#: web/templates/admin/login.gohtml:40
|
||||
msgctxt "action"
|
||||
msgid "Login"
|
||||
msgstr "Entra"
|
||||
|
||||
#: web/templates/profile.gohtml:6 web/templates/profile.gohtml:12
|
||||
#: web/templates/layout.gohtml:29
|
||||
#: web/templates/admin/profile.gohtml:6 web/templates/admin/profile.gohtml:12
|
||||
#: web/templates/admin/layout.gohtml:29
|
||||
msgctxt "title"
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
#: web/templates/profile.gohtml:17
|
||||
#: web/templates/admin/profile.gohtml:17
|
||||
msgctxt "inut"
|
||||
msgid "Profile Image"
|
||||
msgstr "Imatge del perfil"
|
||||
|
||||
#: web/templates/profile.gohtml:43
|
||||
#: web/templates/admin/profile.gohtml:43
|
||||
msgctxt "legend"
|
||||
msgid "Change password"
|
||||
msgstr "Canvi de contrasenya"
|
||||
|
||||
#: web/templates/profile.gohtml:55
|
||||
#: web/templates/admin/profile.gohtml:55
|
||||
msgctxt "input"
|
||||
msgid "Password Confirmation"
|
||||
msgstr "Confirmació de la contrasenya"
|
||||
|
||||
#: web/templates/profile.gohtml:65
|
||||
#: web/templates/admin/profile.gohtml:65
|
||||
msgctxt "input"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: web/templates/profile.gohtml:75
|
||||
#: web/templates/admin/profile.gohtml:75
|
||||
msgctxt "action"
|
||||
msgid "Save changes"
|
||||
msgstr "Desa els canvis"
|
||||
|
||||
#: web/templates/layout.gohtml:18
|
||||
msgid "Skip to main content"
|
||||
msgstr "Salta al contingut principal"
|
||||
|
||||
#: web/templates/layout.gohtml:25
|
||||
#: web/templates/admin/layout.gohtml:25
|
||||
msgctxt "title"
|
||||
msgid "User Menu"
|
||||
msgstr "Menú d’usuari"
|
||||
|
||||
#: web/templates/layout.gohtml:33
|
||||
#: web/templates/admin/layout.gohtml:33
|
||||
msgctxt "action"
|
||||
msgid "Logout"
|
||||
msgstr "Surt"
|
||||
|
||||
#: pkg/app/login.go:56 pkg/app/user.go:239
|
||||
#: pkg/app/login.go:56 pkg/app/user.go:245
|
||||
msgid "Email can not be empty."
|
||||
msgstr "No podeu deixar el correu en blanc."
|
||||
|
||||
#: pkg/app/login.go:57 pkg/app/user.go:240
|
||||
#: pkg/app/login.go:57 pkg/app/user.go:246
|
||||
msgid "This email is not valid. It should be like name@domain.com."
|
||||
msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com."
|
||||
|
||||
|
@ -146,27 +156,31 @@ msgstr "No podeu deixar la contrasenya en blanc."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||
|
||||
#: pkg/app/user.go:190
|
||||
#: pkg/app/user.go:196
|
||||
msgctxt "language option"
|
||||
msgid "Automatic"
|
||||
msgstr "Automàtic"
|
||||
|
||||
#: pkg/app/user.go:242 pkg/campsite/type.go:143
|
||||
#: pkg/app/user.go:248 pkg/campsite/type.go:143
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podeu deixar el nom en blanc."
|
||||
|
||||
#: pkg/app/user.go:243
|
||||
#: pkg/app/user.go:249
|
||||
msgid "Confirmation does not match password."
|
||||
msgstr "La confirmació no es correspon amb la contrasenya."
|
||||
|
||||
#: pkg/app/user.go:244
|
||||
#: pkg/app/user.go:250
|
||||
msgid "Selected language is not valid."
|
||||
msgstr "L’idioma escollit no és vàlid."
|
||||
|
||||
#: pkg/app/user.go:246
|
||||
#: pkg/app/user.go:252
|
||||
msgid "File must be a valid PNG or JPEG image."
|
||||
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."
|
||||
|
||||
#: pkg/app/admin.go:37
|
||||
msgid "Access forbidden"
|
||||
msgstr "Accés prohibit"
|
||||
|
||||
#: pkg/auth/user.go:40
|
||||
msgid "Cross-site request forgery detected."
|
||||
msgstr "S’ha detectat un intent de falsificació de petició a llocs creuats."
|
||||
|
|
88
po/es.po
88
po/es.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-08-04 19:51+0200\n"
|
||||
"POT-Creation-Date: 2023-08-05 03:23+0200\n"
|
||||
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -18,123 +18,133 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:14
|
||||
#: web/templates/campsite/type/new.gohtml:20
|
||||
#: web/templates/public/home.gohtml:6
|
||||
msgctxt "title"
|
||||
msgid "Home"
|
||||
msgstr "Inicio"
|
||||
|
||||
#: web/templates/public/layout.gohtml:10 web/templates/public/layout.gohtml:17
|
||||
msgid "Campsite Montagut"
|
||||
msgstr "Camping Montagut"
|
||||
|
||||
#: web/templates/public/layout.gohtml:16 web/templates/admin/layout.gohtml:18
|
||||
msgid "Skip to main content"
|
||||
msgstr "Saltar al contenido principal"
|
||||
|
||||
#: web/templates/admin/campsite/type/new.gohtml:14
|
||||
#: web/templates/admin/campsite/type/new.gohtml:20
|
||||
msgctxt "title"
|
||||
msgid "New Campsite Type"
|
||||
msgstr "Nuevo tipo de alojamiento"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:25 web/templates/profile.gohtml:26
|
||||
#: web/templates/admin/campsite/type/new.gohtml:25
|
||||
#: web/templates/admin/profile.gohtml:26
|
||||
msgctxt "input"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:33
|
||||
#: web/templates/admin/campsite/type/new.gohtml:33
|
||||
msgctxt "input"
|
||||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: web/templates/campsite/type/new.gohtml:40
|
||||
#: web/templates/admin/campsite/type/new.gohtml:40
|
||||
msgctxt "action"
|
||||
msgid "Add"
|
||||
msgstr "Añadir"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:6
|
||||
#: web/templates/campsite/type/index.gohtml:12
|
||||
#: web/templates/admin/campsite/type/index.gohtml:6
|
||||
#: web/templates/admin/campsite/type/index.gohtml:12
|
||||
msgctxt "title"
|
||||
msgid "Campsite Types"
|
||||
msgstr "Tipos de alojamientos"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:11
|
||||
#: web/templates/admin/campsite/type/index.gohtml:11
|
||||
msgctxt "action"
|
||||
msgid "Add Type"
|
||||
msgstr "Añadir tipo"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:17
|
||||
#: web/templates/admin/campsite/type/index.gohtml:17
|
||||
msgctxt "header"
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: web/templates/campsite/type/index.gohtml:29
|
||||
#: web/templates/admin/campsite/type/index.gohtml:29
|
||||
msgid "No campsite types added yet."
|
||||
msgstr "No se ha añadido ningún tipo de alojamiento todavía."
|
||||
|
||||
#: web/templates/dashboard.gohtml:6 web/templates/dashboard.gohtml:10
|
||||
#: web/templates/layout.gohtml:44
|
||||
#: web/templates/admin/dashboard.gohtml:6
|
||||
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:44
|
||||
msgctxt "title"
|
||||
msgid "Dashboard"
|
||||
msgstr "Panel"
|
||||
|
||||
#: web/templates/login.gohtml:6 web/templates/login.gohtml:13
|
||||
#: web/templates/admin/login.gohtml:6 web/templates/admin/login.gohtml:13
|
||||
msgctxt "title"
|
||||
msgid "Login"
|
||||
msgstr "Entrada"
|
||||
|
||||
#: web/templates/login.gohtml:22 web/templates/profile.gohtml:35
|
||||
#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35
|
||||
msgctxt "input"
|
||||
msgid "Email"
|
||||
msgstr "Correo-e"
|
||||
|
||||
#: web/templates/login.gohtml:31 web/templates/profile.gohtml:46
|
||||
#: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46
|
||||
msgctxt "input"
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: web/templates/login.gohtml:40
|
||||
#: web/templates/admin/login.gohtml:40
|
||||
msgctxt "action"
|
||||
msgid "Login"
|
||||
msgstr "Entrar"
|
||||
|
||||
#: web/templates/profile.gohtml:6 web/templates/profile.gohtml:12
|
||||
#: web/templates/layout.gohtml:29
|
||||
#: web/templates/admin/profile.gohtml:6 web/templates/admin/profile.gohtml:12
|
||||
#: web/templates/admin/layout.gohtml:29
|
||||
msgctxt "title"
|
||||
msgid "Profile"
|
||||
msgstr "Perfil"
|
||||
|
||||
#: web/templates/profile.gohtml:17
|
||||
#: web/templates/admin/profile.gohtml:17
|
||||
msgctxt "inut"
|
||||
msgid "Profile Image"
|
||||
msgstr "Imagen del perfil"
|
||||
|
||||
#: web/templates/profile.gohtml:43
|
||||
#: web/templates/admin/profile.gohtml:43
|
||||
msgctxt "legend"
|
||||
msgid "Change password"
|
||||
msgstr "Cambio de contraseña"
|
||||
|
||||
#: web/templates/profile.gohtml:55
|
||||
#: web/templates/admin/profile.gohtml:55
|
||||
msgctxt "input"
|
||||
msgid "Password Confirmation"
|
||||
msgstr "Confirmación de la contraseña"
|
||||
|
||||
#: web/templates/profile.gohtml:65
|
||||
#: web/templates/admin/profile.gohtml:65
|
||||
msgctxt "input"
|
||||
msgid "Language"
|
||||
msgstr "Idioma"
|
||||
|
||||
#: web/templates/profile.gohtml:75
|
||||
#: web/templates/admin/profile.gohtml:75
|
||||
msgctxt "action"
|
||||
msgid "Save changes"
|
||||
msgstr "Guardar los cambios"
|
||||
|
||||
#: web/templates/layout.gohtml:18
|
||||
msgid "Skip to main content"
|
||||
msgstr "Saltar al contenido principal"
|
||||
|
||||
#: web/templates/layout.gohtml:25
|
||||
#: web/templates/admin/layout.gohtml:25
|
||||
msgctxt "title"
|
||||
msgid "User Menu"
|
||||
msgstr "Menú de usuario"
|
||||
|
||||
#: web/templates/layout.gohtml:33
|
||||
#: web/templates/admin/layout.gohtml:33
|
||||
msgctxt "action"
|
||||
msgid "Logout"
|
||||
msgstr "Salir"
|
||||
|
||||
#: pkg/app/login.go:56 pkg/app/user.go:239
|
||||
#: pkg/app/login.go:56 pkg/app/user.go:245
|
||||
msgid "Email can not be empty."
|
||||
msgstr "No podéis dejar el correo-e en blanco."
|
||||
|
||||
#: pkg/app/login.go:57 pkg/app/user.go:240
|
||||
#: pkg/app/login.go:57 pkg/app/user.go:246
|
||||
msgid "This email is not valid. It should be like name@domain.com."
|
||||
msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com."
|
||||
|
||||
|
@ -146,27 +156,31 @@ msgstr "No podéis dejar la contraseña en blanco."
|
|||
msgid "Invalid user or password."
|
||||
msgstr "Usuario o contraseña incorrectos."
|
||||
|
||||
#: pkg/app/user.go:190
|
||||
#: pkg/app/user.go:196
|
||||
msgctxt "language option"
|
||||
msgid "Automatic"
|
||||
msgstr "Automático"
|
||||
|
||||
#: pkg/app/user.go:242 pkg/campsite/type.go:143
|
||||
#: pkg/app/user.go:248 pkg/campsite/type.go:143
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podéis dejar el nombre en blanco."
|
||||
|
||||
#: pkg/app/user.go:243
|
||||
#: pkg/app/user.go:249
|
||||
msgid "Confirmation does not match password."
|
||||
msgstr "La confirmación no se corresponde con la contraseña."
|
||||
|
||||
#: pkg/app/user.go:244
|
||||
#: pkg/app/user.go:250
|
||||
msgid "Selected language is not valid."
|
||||
msgstr "El idioma escogido no es válido."
|
||||
|
||||
#: pkg/app/user.go:246
|
||||
#: pkg/app/user.go:252
|
||||
msgid "File must be a valid PNG or JPEG image."
|
||||
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."
|
||||
|
||||
#: pkg/app/admin.go:37
|
||||
msgid "Access forbidden"
|
||||
msgstr "Acceso prohibido"
|
||||
|
||||
#: pkg/auth/user.go:40
|
||||
msgid "Cross-site request forgery detected."
|
||||
msgstr "Se ha detectado un intento de falsificación de petición en sitios cruzados."
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 62.5%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 1.6rem;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: white;
|
||||
color: #3f3b37;
|
||||
}
|
||||
|
||||
img, picture, video, canvas, svg {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
input, button, textarea, select {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6 {
|
||||
overflow-wrap: break-word;
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
{{ define "content" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.typeIndex*/ -}}
|
||||
<a href="/campsites/types/new">{{( pgettext "Add Type" "action" )}}</a>
|
||||
<a href="/admin/campsites/types/new">{{( pgettext "Add Type" "action" )}}</a>
|
||||
<h2>{{( pgettext "Campsite Types" "title" )}}</h2>
|
||||
{{ if .Types -}}
|
||||
<table>
|
||||
|
@ -20,7 +20,7 @@
|
|||
<tbody>
|
||||
{{ range .Types -}}
|
||||
<tr>
|
||||
<td><a href="/campsites/type/{{ .Slug }}">{{ .Name }}</a></td>
|
||||
<td><a href="/admin/campsites/type/{{ .Slug }}">{{ .Name }}</a></td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
</tbody>
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
{{ define "content" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite.typeForm*/ -}}
|
||||
<form action="/campsites/types" method="post">
|
||||
<form action="/admin/campsites/types" method="post">
|
||||
<h2>{{( pgettext "New Campsite Type" "title" )}}</h2>
|
||||
{{ CSRFInput }}
|
||||
<fieldset>
|
|
@ -41,7 +41,7 @@
|
|||
<nav>
|
||||
<ul role="menu">
|
||||
<li role="presentation">
|
||||
<a role="menuitem" href="/">{{( pgettext "Dashboard" "title" )}}</a>
|
||||
<a role="menuitem" href="/admin/">{{( pgettext "Dashboard" "title" )}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
|
@ -0,0 +1 @@
|
|||
../admin/form.gohtml
|
|
@ -0,0 +1,10 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
{{ define "title" -}}
|
||||
{{( pgettext "Home" "title" )}}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" -}}
|
||||
{{- end }}
|
|
@ -0,0 +1,23 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
<!doctype html>
|
||||
<html lang="{{ currentLocale }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ template "title" . }} — {{( gettext "Campsite Montagut" )}}</title>
|
||||
<link rel="stylesheet" media="screen" href="/static/public.css">
|
||||
{{ block "head" . }}{{ end }}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="#content">{{( gettext "Skip to main content" )}}</a>
|
||||
<h1>{{( gettext "Campsite Montagut" )}}</h1>
|
||||
</header>
|
||||
<main id="content">
|
||||
{{- template "content" . }}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue