Include the locale inside the User struct
The locale is completely dependent on the user, as much as its email or CSRF token, so it does not make much sense to have it in a separate variable: for different users we might have to use different locales. Also, this means one less variable to pass to handlers, that most of them will need the user at some point or another (i.e., to render its profile icon). The thing is that i can not import `app.User` from the template package because it would create an import cycle. I created the `auth` package just because of that. I thought that the login code would be better moved to the auth package as well, but of course then i would reintroduce the import cycle between auth and template.
This commit is contained in:
parent
1ef6dcc4cf
commit
2f3fc8812d
|
@ -6,13 +6,13 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||||
|
@ -70,23 +70,21 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
defer conn.Release()
|
defer conn.Release()
|
||||||
|
|
||||||
cookie := getSessionCookie(r)
|
user, err := h.getUser(r, conn)
|
||||||
user, err := h.getUser(r.Context(), conn, cookie)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
l := h.matchLocale(r, user)
|
|
||||||
|
|
||||||
if head == "login" {
|
if head == "login" {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
h.handleLogin(w, r, l, conn)
|
handleLogin(w, r, user, conn)
|
||||||
default:
|
default:
|
||||||
methodNotAllowed(w, r, http.MethodPost)
|
methodNotAllowed(w, r, http.MethodPost)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !user.LoggedIn {
|
if !user.LoggedIn {
|
||||||
h.serveLoginForm(w, r, l, requestURL)
|
serveLoginForm(w, r, user, requestURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +92,7 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
case "":
|
case "":
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
h.serveDashboard(w, r, l)
|
h.serveDashboard(w, r, user)
|
||||||
default:
|
default:
|
||||||
methodNotAllowed(w, r, http.MethodGet)
|
methodNotAllowed(w, r, http.MethodGet)
|
||||||
}
|
}
|
||||||
|
@ -105,50 +103,6 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
func (h *App) serveDashboard(w http.ResponseWriter, _ *http.Request, user *auth.User) {
|
||||||
Email string
|
template.MustRender(w, user, "dashboard.gohtml", nil)
|
||||||
LoggedIn bool
|
|
||||||
Role string
|
|
||||||
Language language.Tag
|
|
||||||
CsrfToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *App) getUser(ctx context.Context, conn *database.Conn, cookie string) (*User, error) {
|
|
||||||
if _, err := conn.Exec(ctx, "select set_cookie($1)", cookie); err != nil {
|
|
||||||
conn.Release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user := &User{
|
|
||||||
Email: "",
|
|
||||||
LoggedIn: false,
|
|
||||||
Role: "guest",
|
|
||||||
}
|
|
||||||
row := conn.QueryRow(ctx, "select coalesce(email, ''), email is not null, role, lang_tag, csrf_token from user_profile")
|
|
||||||
var langTag string
|
|
||||||
if err := row.Scan(&user.Email, &user.LoggedIn, &user.Role, &langTag, &user.CsrfToken); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if lang, err := language.Parse(langTag); err == nil {
|
|
||||||
user.Language = lang
|
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *App) matchLocale(r *http.Request, user *User) *locale.Locale {
|
|
||||||
l := h.locales[user.Language]
|
|
||||||
if l == nil {
|
|
||||||
l = locale.Match(r.Header.Get("Accept-Language"), h.locales, h.languageMatcher)
|
|
||||||
if l == nil {
|
|
||||||
l = h.defaultLocale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *App) serveDashboard(w http.ResponseWriter, _ *http.Request, l *locale.Locale) {
|
|
||||||
template.MustRender(w, l, "dashboard.gohtml", nil)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ package app
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/form"
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
||||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||||
|
@ -17,10 +17,6 @@ import (
|
||||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
sessionCookie = "camper-session"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loginForm struct {
|
type loginForm struct {
|
||||||
Email *form.Input
|
Email *form.Input
|
||||||
Password *form.Input
|
Password *form.Input
|
||||||
|
@ -64,52 +60,30 @@ func (f *loginForm) Valid(l *locale.Locale) bool {
|
||||||
return v.AllOK
|
return v.AllOK
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *App) serveLoginForm(w http.ResponseWriter, _ *http.Request, l *locale.Locale, requestURL string) {
|
func serveLoginForm(w http.ResponseWriter, _ *http.Request, user *auth.User, requestURL string) {
|
||||||
login := newLoginForm()
|
login := newLoginForm()
|
||||||
login.Redirect.Val = requestURL
|
login.Redirect.Val = requestURL
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
template.MustRender(w, l, "login.gohtml", login)
|
template.MustRender(w, user, "login.gohtml", login)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *App) handleLogin(w http.ResponseWriter, r *http.Request, l *locale.Locale, conn *database.Conn) {
|
func handleLogin(w http.ResponseWriter, r *http.Request, user *auth.User, conn *database.Conn) {
|
||||||
login := newLoginForm()
|
login := newLoginForm()
|
||||||
if err := login.Parse(r); err != nil {
|
if err := login.Parse(r); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if login.Valid(l) {
|
if login.Valid(user.Locale) {
|
||||||
cookie := conn.MustGetText(r.Context(), "select login($1, $2, $3)", login.Email, login.Password, httplib.RemoteAddr(r))
|
cookie := conn.MustGetText(r.Context(), "select login($1, $2, $3)", login.Email, login.Password, httplib.RemoteAddr(r))
|
||||||
if cookie != "" {
|
if cookie != "" {
|
||||||
setSessionCookie(w, cookie)
|
auth.SetSessionCookie(w, cookie)
|
||||||
http.Redirect(w, r, login.Redirect.Val, http.StatusSeeOther)
|
http.Redirect(w, r, login.Redirect.Val, http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
login.Error = errors.New(l.Gettext("Invalid user or password."))
|
login.Error = errors.New(user.Locale.Gettext("Invalid user or password."))
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
}
|
}
|
||||||
template.MustRender(w, l, "login.gohtml", login)
|
template.MustRender(w, user, "login.gohtml", login)
|
||||||
}
|
|
||||||
|
|
||||||
func setSessionCookie(w http.ResponseWriter, cookie string) {
|
|
||||||
http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSessionCookie(r *http.Request) string {
|
|
||||||
if cookie, err := r.Cookie(sessionCookie); err == nil {
|
|
||||||
return cookie.Value
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSessionCookie(value string, duration time.Duration) *http.Cookie {
|
|
||||||
return &http.Cookie{
|
|
||||||
Name: sessionCookie,
|
|
||||||
Value: value,
|
|
||||||
Path: "/",
|
|
||||||
Expires: time.Now().Add(duration),
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteLaxMode,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *App) getUser(r *http.Request, conn *database.Conn) (*auth.User, error) {
|
||||||
|
cookie := auth.GetSessionCookie(r)
|
||||||
|
if _, err := conn.Exec(r.Context(), "select set_cookie($1)", cookie); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &auth.User{
|
||||||
|
Email: "",
|
||||||
|
LoggedIn: false,
|
||||||
|
Role: "guest",
|
||||||
|
}
|
||||||
|
row := conn.QueryRow(r.Context(), "select coalesce(email, ''), email is not null, role, lang_tag, csrf_token from user_profile")
|
||||||
|
var langTag string
|
||||||
|
if err := row.Scan(&user.Email, &user.LoggedIn, &user.Role, &langTag, &user.CsrfToken); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if lang, err := language.Parse(langTag); err == nil {
|
||||||
|
user.Language = lang
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Locale = h.locales[user.Language]
|
||||||
|
if user.Locale == nil {
|
||||||
|
user.Locale = h.matchLocale(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *App) matchLocale(r *http.Request) *locale.Locale {
|
||||||
|
l := locale.Match(r.Header.Get("Accept-Language"), h.locales, h.languageMatcher)
|
||||||
|
if l == nil {
|
||||||
|
l = h.defaultLocale
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionCookie = "camper-session"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string
|
||||||
|
LoggedIn bool
|
||||||
|
Role string
|
||||||
|
Language language.Tag
|
||||||
|
CsrfToken string
|
||||||
|
Locale *locale.Locale
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetSessionCookie(w http.ResponseWriter, cookie string) {
|
||||||
|
http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSessionCookie(r *http.Request) string {
|
||||||
|
if cookie, err := r.Cookie(sessionCookie); err == nil {
|
||||||
|
return cookie.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSessionCookie(value string, duration time.Duration) *http.Cookie {
|
||||||
|
return &http.Cookie{
|
||||||
|
Name: sessionCookie,
|
||||||
|
Value: value,
|
||||||
|
Path: "/",
|
||||||
|
Expires: time.Now().Add(duration),
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,25 +10,25 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
func templateFile(name string) string {
|
func templateFile(name string) string {
|
||||||
return "web/templates/" + name
|
return "web/templates/" + name
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustRender(w io.Writer, locale *locale.Locale, filename string, data interface{}) {
|
func MustRender(w io.Writer, user *auth.User, filename string, data interface{}) {
|
||||||
layout := "layout.gohtml"
|
layout := "layout.gohtml"
|
||||||
mustRenderLayout(w, locale, layout, filename, data)
|
mustRenderLayout(w, user, layout, filename, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRenderLayout(w io.Writer, locale *locale.Locale, layout string, filename string, data interface{}) {
|
func mustRenderLayout(w io.Writer, user *auth.User, layout string, filename string, data interface{}) {
|
||||||
t := template.New(filename)
|
t := template.New(filename)
|
||||||
t.Funcs(template.FuncMap{
|
t.Funcs(template.FuncMap{
|
||||||
"gettext": locale.Get,
|
"gettext": user.Locale.Get,
|
||||||
"pgettext": locale.GetC,
|
"pgettext": user.Locale.GetC,
|
||||||
"currentLocale": func() string {
|
"currentLocale": func() string {
|
||||||
return locale.Language.String()
|
return user.Locale.Language.String()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if _, err := t.ParseFiles(templateFile(layout), templateFile(filename)); err != nil {
|
if _, err := t.ParseFiles(templateFile(layout), templateFile(filename)); err != nil {
|
||||||
|
|
20
po/ca.po
20
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: camper\n"
|
"Project-Id-Version: camper\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-07-26 01:33+0200\n"
|
"POT-Creation-Date: 2023-07-26 11:57+0200\n"
|
||||||
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
|
"PO-Revision-Date: 2023-07-22 23:45+0200\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"
|
||||||
|
@ -18,27 +18,27 @@ 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/templates/dashboard.gohtml:2 web/templates/dashboard.gohtml:6
|
#: web/templates/dashboard.gohtml:6 web/templates/dashboard.gohtml:10
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Tauler"
|
msgstr "Tauler"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:2 web/templates/login.gohtml:9
|
#: web/templates/login.gohtml:6 web/templates/login.gohtml:13
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Entrada"
|
msgstr "Entrada"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:18
|
#: web/templates/login.gohtml:22
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correu-e"
|
msgstr "Correu-e"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:27
|
#: web/templates/login.gohtml:31
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contrasenya"
|
msgstr "Contrasenya"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:36
|
#: web/templates/login.gohtml:40
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Entra"
|
msgstr "Entra"
|
||||||
|
@ -47,18 +47,18 @@ msgstr "Entra"
|
||||||
msgid "Skip to main content"
|
msgid "Skip to main content"
|
||||||
msgstr "Salta al contingut principal"
|
msgstr "Salta al contingut principal"
|
||||||
|
|
||||||
#: pkg/app/login.go:60
|
#: pkg/app/login.go:58
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podeu deixar el correu en blanc."
|
msgstr "No podeu deixar el correu en blanc."
|
||||||
|
|
||||||
#: pkg/app/login.go:61
|
#: pkg/app/login.go:59
|
||||||
msgid "This email is not valid. It should be like name@domain.com."
|
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."
|
msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com."
|
||||||
|
|
||||||
#: pkg/app/login.go:63
|
#: pkg/app/login.go:61
|
||||||
msgid "Password can not be empty."
|
msgid "Password can not be empty."
|
||||||
msgstr "No podeu deixar la contrasenya en blanc."
|
msgstr "No podeu deixar la contrasenya en blanc."
|
||||||
|
|
||||||
#: pkg/app/login.go:86
|
#: pkg/app/login.go:85
|
||||||
msgid "Invalid user or password."
|
msgid "Invalid user or password."
|
||||||
msgstr "Nom d’usuari o contrasenya incorrectes."
|
msgstr "Nom d’usuari o contrasenya incorrectes."
|
||||||
|
|
20
po/es.po
20
po/es.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: camper\n"
|
"Project-Id-Version: camper\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-07-26 01:33+0200\n"
|
"POT-Creation-Date: 2023-07-26 11:57+0200\n"
|
||||||
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
|
"PO-Revision-Date: 2023-07-22 23:46+0200\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"
|
||||||
|
@ -18,27 +18,27 @@ 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/templates/dashboard.gohtml:2 web/templates/dashboard.gohtml:6
|
#: web/templates/dashboard.gohtml:6 web/templates/dashboard.gohtml:10
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Panel"
|
msgstr "Panel"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:2 web/templates/login.gohtml:9
|
#: web/templates/login.gohtml:6 web/templates/login.gohtml:13
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Entrada"
|
msgstr "Entrada"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:18
|
#: web/templates/login.gohtml:22
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Correo-e"
|
msgstr "Correo-e"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:27
|
#: web/templates/login.gohtml:31
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Contraseña"
|
msgstr "Contraseña"
|
||||||
|
|
||||||
#: web/templates/login.gohtml:36
|
#: web/templates/login.gohtml:40
|
||||||
msgctxt "action"
|
msgctxt "action"
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Entrar"
|
msgstr "Entrar"
|
||||||
|
@ -47,18 +47,18 @@ msgstr "Entrar"
|
||||||
msgid "Skip to main content"
|
msgid "Skip to main content"
|
||||||
msgstr "Saltar al contenido principal"
|
msgstr "Saltar al contenido principal"
|
||||||
|
|
||||||
#: pkg/app/login.go:60
|
#: pkg/app/login.go:58
|
||||||
msgid "Email can not be empty."
|
msgid "Email can not be empty."
|
||||||
msgstr "No podéis dejar el correo-e en blanco."
|
msgstr "No podéis dejar el correo-e en blanco."
|
||||||
|
|
||||||
#: pkg/app/login.go:61
|
#: pkg/app/login.go:59
|
||||||
msgid "This email is not valid. It should be like name@domain.com."
|
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."
|
msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com."
|
||||||
|
|
||||||
#: pkg/app/login.go:63
|
#: pkg/app/login.go:61
|
||||||
msgid "Password can not be empty."
|
msgid "Password can not be empty."
|
||||||
msgstr "No podéis dejar la contraseña en blanco."
|
msgstr "No podéis dejar la contraseña en blanco."
|
||||||
|
|
||||||
#: pkg/app/login.go:86
|
#: pkg/app/login.go:85
|
||||||
msgid "Invalid user or password."
|
msgid "Invalid user or password."
|
||||||
msgstr "Usuario o contraseña incorrectos."
|
msgstr "Usuario o contraseña incorrectos."
|
||||||
|
|
Loading…
Reference in New Issue