Add the first draft of the booking and payment forms

The form is based on the one in the current website, but in a single
page instead of split into many pages; possibly each <fieldset> should
be in a separate page/view.  The idea is for Oriol to check the design
and decide how it would be presented to the user, so i needed something
to show him first.

I hardcoded the **test** data for the customer’s Redsys account.  Is
this bad? I hope not, but i am not really, really sure.

The data sent to Redsys is just a placeholder because there are booking
details that i do not know, like what i have to do with the “teenagers”
field or the area preferences, thus i can not yet have a booking
relation.  Nevertheless, had to generate a random order number up to
12-chars in length or Redsys would refuse the payment, claiming that
the order is duplicated.

The Redsys package is based on the PHP code provided by Redsys
themselves, plus some hints at the implementations from various Go
packages that did not know why they were so complicated.

Had to grant select on table country to guest in order to show the
select with the country options.

I have changed the “Postal code” input in taxDetails for “Postcode”
because this is the spell that it is used in the current web, i did not
see a reason to change it—it is an accepted form—, and i did not want to
have inconsistencies between forms.
This commit is contained in:
jordi fita mas 2023-10-19 21:37:34 +02:00
parent 96fb253354
commit 302ce29e4a
16 changed files with 1545 additions and 215 deletions

View File

@ -13,6 +13,7 @@ create table country (
postal_code_regex text not null postal_code_regex text not null
); );
grant select on table country to guest;
grant select on table country to employee; grant select on table country to employee;
grant select on table country to admin; grant select on table country to admin;

View File

@ -16,6 +16,7 @@ create table country_i18n (
primary key (country_code, lang_tag) primary key (country_code, lang_tag)
); );
grant select on table country_i18n to guest;
grant select on table country_i18n to employee; grant select on table country_i18n to employee;
grant select on table country_i18n to admin; grant select on table country_i18n to admin;

View File

@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/booking"
"dev.tandem.ws/tandem/camper/pkg/campsite" "dev.tandem.ws/tandem/camper/pkg/campsite"
"dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/home" "dev.tandem.ws/tandem/camper/pkg/home"
@ -19,6 +20,7 @@ import (
type publicHandler struct { type publicHandler struct {
home *home.PublicHandler home *home.PublicHandler
booking *booking.PublicHandler
campsite *campsite.PublicHandler campsite *campsite.PublicHandler
services *services.PublicHandler services *services.PublicHandler
} }
@ -26,6 +28,7 @@ type publicHandler struct {
func newPublicHandler() *publicHandler { func newPublicHandler() *publicHandler {
return &publicHandler{ return &publicHandler{
home: home.NewPublicHandler(), home: home.NewPublicHandler(),
booking: booking.NewPublicHandler(),
campsite: campsite.NewPublicHandler(), campsite: campsite.NewPublicHandler(),
services: services.NewPublicHandler(), services: services.NewPublicHandler(),
} }
@ -38,6 +41,8 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da
switch head { switch head {
case "": case "":
h.home.Handler(user, company, conn).ServeHTTP(w, r) h.home.Handler(user, company, conn).ServeHTTP(w, r)
case "booking":
h.booking.Handler(user, company, conn).ServeHTTP(w, r)
case "campground": case "campground":
campgroundHandler(user, company, conn).ServeHTTP(w, r) campgroundHandler(user, company, conn).ServeHTTP(w, r)
case "campsites": case "campsites":

99
pkg/booking/payment.go Normal file
View File

@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package booking
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/redsys"
"dev.tandem.ws/tandem/camper/pkg/template"
)
type paymentPage struct {
*template.PublicPage
Request *redsys.SignedRequest
}
func newPaymentPage(request *redsys.SignedRequest) *paymentPage {
return &paymentPage{
PublicPage: template.NewPublicPage(),
Request: request,
}
}
func (p *paymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "payment/request.gohtml", p)
}
func handleSuccessfulPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newSuccessfulPaymentPage()
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
default:
http.NotFound(w, r)
}
}
type successfulPaymentPage struct {
*template.PublicPage
}
func newSuccessfulPaymentPage() *successfulPaymentPage {
return &successfulPaymentPage{
PublicPage: template.NewPublicPage(),
}
}
func (p *successfulPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "payment/success.gohtml", p)
}
func handleFailedPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newFailedPaymentPage()
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
default:
http.NotFound(w, r)
}
}
type failedPaymentPage struct {
*template.PublicPage
}
func newFailedPaymentPage() *failedPaymentPage {
return &failedPaymentPage{
PublicPage: template.NewPublicPage(),
}
}
func (p *failedPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "payment/failure.gohtml", p)
}

345
pkg/booking/public.go Normal file
View File

@ -0,0 +1,345 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package booking
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/form"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/redsys"
"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 "":
switch r.Method {
case http.MethodGet:
page := newPublicPage(r.Context(), company, conn, user.Locale)
page.MustRender(w, r, user, company, conn)
case http.MethodPost:
makeReservation(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
case "success":
handleSuccessfulPayment(w, r, user, company, conn)
case "failure":
handleFailedPayment(w, r, user, company, conn)
default:
http.NotFound(w, r)
}
})
}
func makeReservation(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
f := newBookingForm(r.Context(), company, conn, user.Locale)
if err := f.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
panic(err)
} else if !ok {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
page := newPublicPageWithForm(f)
page.MustRender(w, r, user, company, conn)
return
}
client, err := redsys.New("361716962", "001", "sq7HjrUOBfKmC576ILgskD5srU870gJ7")
if err != nil {
panic(err)
}
schema := httplib.Protocol(r)
authority := httplib.Host(r)
request := &redsys.Request{
TransactionType: redsys.TransactionTypeCharge,
Amount: 1234,
Currency: redsys.CurrencyEUR,
Order: randomOrderNumber(),
Product: "Test Booking",
SuccessURL: fmt.Sprintf("%s://%s/%s/booking/success", schema, authority, user.Locale.Language),
FailureURL: fmt.Sprintf("%s://%s/%s/booking/failure", schema, authority, user.Locale.Language),
ConsumerLanguage: redsys.LanguageCatalan,
}
signed, err := client.SignRequest(request)
if err != nil {
panic(err)
}
page := newPaymentPage(signed)
page.MustRender(w, r, user, company, conn)
}
func randomOrderNumber() string {
bytes := make([]byte, 6)
if _, err := rand.Read(bytes); err != nil {
panic(err)
}
return hex.EncodeToString(bytes)
}
type publicPage struct {
*template.PublicPage
Form *bookingForm
}
func newPublicPage(ctx context.Context, company *auth.Company, conn *database.Conn, l *locale.Locale) *publicPage {
return newPublicPageWithForm(newBookingForm(ctx, company, conn, l))
}
func newPublicPageWithForm(form *bookingForm) *publicPage {
return &publicPage{
PublicPage: template.NewPublicPage(),
Form: form,
}
}
func (p *publicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "booking.gohtml", p)
}
type bookingForm struct {
FullName *form.Input
Address *form.Input
PostalCode *form.Input
City *form.Input
Country *form.Select
Email *form.Input
Phone *form.Input
Adults *form.Input
Teenagers *form.Input
Children *form.Input
Dogs *form.Input
CampsiteType *form.Select
CampsiteTypeOptions map[string][]*campsiteTypeOption
ArrivalDate *form.Input
DepartureDate *form.Input
AreaPreferences *form.Input
ACSICard *form.Checkbox
Agreement *form.Checkbox
}
type campsiteTypeOption struct {
Label string
Min int
Max int
Input *form.Input
}
func newBookingForm(ctx context.Context, company *auth.Company, conn *database.Conn, l *locale.Locale) *bookingForm {
f := &bookingForm{
FullName: &form.Input{
Name: "full_name",
},
Address: &form.Input{
Name: "address",
},
PostalCode: &form.Input{
Name: "postal_code",
},
City: &form.Input{
Name: "city",
},
Country: &form.Select{
Name: "country",
Options: form.MustGetOptions(ctx, conn, "select country.country_code, coalesce(i18n.name, country.name) as l10n_name from country left join country_i18n as i18n on country.country_code = i18n.country_code and i18n.lang_tag = $1 order by l10n_name", l.Language),
},
Email: &form.Input{
Name: "email",
},
Phone: &form.Input{
Name: "phone",
},
Adults: &form.Input{
Name: "adults",
Val: "2",
},
Teenagers: &form.Input{
Name: "teenagers",
Val: "0",
},
Children: &form.Input{
Name: "children",
Val: "0",
},
Dogs: &form.Input{
Name: "dogs",
Val: "0",
},
CampsiteType: &form.Select{
Name: "campsite_type",
Options: form.MustGetOptions(ctx, conn, "select type.slug, coalesce(i18n.name, type.name) as l10n_name from campsite_type as type left join campsite_type_i18n as i18n on type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1 where company_id = $2 order by l10n_name", l.Language, company.ID),
},
CampsiteTypeOptions: make(map[string][]*campsiteTypeOption),
ArrivalDate: &form.Input{
Name: "arrival_date",
},
DepartureDate: &form.Input{
Name: "departure_date",
},
AreaPreferences: &form.Input{
Name: "area_preferences",
},
ACSICard: &form.Checkbox{
Name: "acsi_card",
},
Agreement: &form.Checkbox{
Name: "agreement",
},
}
rows, err := conn.Query(ctx, `
select 'campsite_type_option_' || option.campsite_type_option_id
, slug
, coalesce(option.name, i18n.name) as l10_name
, lower(range)::text
, lower(range)
, upper(range)
from campsite_type_option as option
join campsite_type using (campsite_type_id)
left join campsite_type_option_i18n as i18n on i18n.campsite_type_option_id = option.campsite_type_id and i18n.lang_tag = $1
where company_id = $2
`, l.Language, company.ID)
if err != nil {
panic(err)
}
for rows.Next() {
var slug string
option := &campsiteTypeOption{
Input: &form.Input{},
}
if err := rows.Scan(&option.Input.Name, &slug, &option.Label, &option.Input.Val, &option.Min, &option.Max); err != nil {
panic(err)
}
f.CampsiteTypeOptions[slug] = append(f.CampsiteTypeOptions[slug], option)
}
if rows.Err() != nil {
panic(rows.Err())
}
return f
}
func (f *bookingForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.FullName.FillValue(r)
f.Address.FillValue(r)
f.PostalCode.FillValue(r)
f.City.FillValue(r)
f.Country.FillValue(r)
f.Email.FillValue(r)
f.Phone.FillValue(r)
f.Adults.FillValue(r)
f.Teenagers.FillValue(r)
f.Children.FillValue(r)
f.Dogs.FillValue(r)
f.CampsiteType.FillValue(r)
f.ArrivalDate.FillValue(r)
f.DepartureDate.FillValue(r)
f.AreaPreferences.FillValue(r)
f.ACSICard.FillValue(r)
f.Agreement.FillValue(r)
for _, options := range f.CampsiteTypeOptions {
for _, option := range options {
option.Input.FillValue(r)
}
}
return nil
}
func (f *bookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
v := form.NewValidator(l)
var country string
if v.CheckSelectedOptions(f.Country, l.GettextNoop("Selected country is not valid.")) {
country = f.Country.Selected[0]
}
if v.CheckRequired(f.FullName, l.GettextNoop("Full name can not be empty.")) {
v.CheckMinLength(f.FullName, 1, l.GettextNoop("Full name must have at least one letter."))
}
if f.PostalCode.Val != "" {
if _, err := v.CheckValidPostalCode(ctx, conn, f.PostalCode, country, l.GettextNoop("This postal code is not valid.")); err != nil {
return false, err
}
}
if v.CheckRequired(f.Email, l.GettextNoop("Email can not be empty.")) {
v.CheckValidEmail(f.Email, l.GettextNoop("This email is not valid. It should be like name@domain.com."))
}
if v.CheckRequired(f.Phone, l.GettextNoop("Phone can not be empty.")) {
if _, err := v.CheckValidPhone(ctx, conn, f.Phone, country, l.GettextNoop("This phone number is not valid.")); err != nil {
return false, err
}
}
if v.CheckRequired(f.Adults, l.GettextNoop("Number of adults can not be empty")) {
if v.CheckValidInteger(f.Adults, l.GettextNoop("Number of adults must be an integer.")) {
v.CheckMinInteger(f.Adults, 1, l.GettextNoop("Number of adults must be one or greater."))
}
}
if v.CheckRequired(f.Teenagers, l.GettextNoop("Number of teenagers can not be empty")) {
if v.CheckValidInteger(f.Teenagers, l.GettextNoop("Number of teenagers must be an integer.")) {
v.CheckMinInteger(f.Teenagers, 0, l.GettextNoop("Number of teenagers must be zero or greater."))
}
}
if v.CheckRequired(f.Children, l.GettextNoop("Number of children can not be empty")) {
if v.CheckValidInteger(f.Children, l.GettextNoop("Number of children must be an integer.")) {
v.CheckMinInteger(f.Children, 0, l.GettextNoop("Number of children must be zero or greater."))
}
}
if v.CheckRequired(f.Dogs, l.GettextNoop("Number of dogs can not be empty")) {
if v.CheckValidInteger(f.Dogs, l.GettextNoop("Number of dogs must be an integer.")) {
v.CheckMinInteger(f.Dogs, 0, l.GettextNoop("Number of dogs must be zero or greater."))
}
}
var validBefore bool
v.CheckSelectedOptions(f.CampsiteType, l.GettextNoop("Selected campsite type is not valid."))
if v.CheckRequired(f.ArrivalDate, l.GettextNoop("Arrival date can not be empty")) {
if v.CheckValidDate(f.ArrivalDate, l.GettextNoop("Arrival date must be a valid date.")) {
validBefore = true
}
}
if v.CheckRequired(f.DepartureDate, l.GettextNoop("Departure date can not be empty")) {
if v.CheckValidDate(f.DepartureDate, l.GettextNoop("Departure date must be a valid date.")) && validBefore {
v.CheckDateAfter(f.DepartureDate, f.ArrivalDate, l.GettextNoop("The departure date must be after the arrival date."))
}
}
v.Check(f.Agreement, f.Agreement.Checked, l.GettextNoop("It is mandatory to agree to the reservation conditions."))
for _, options := range f.CampsiteTypeOptions {
for _, option := range options {
if v.CheckRequired(option.Input, fmt.Sprintf(l.Gettext("%s can not be empty"), option.Label)) {
if v.CheckValidInteger(option.Input, fmt.Sprintf(l.Gettext("%s must be an integer."), option.Label)) {
if v.CheckMinInteger(option.Input, option.Min, fmt.Sprintf(l.Gettext("%s must be %d or greater."), option.Label, option.Min)) {
v.CheckMaxInteger(option.Input, option.Max, fmt.Sprintf(l.Gettext("%s must be at most %d."), option.Label, option.Max))
}
}
}
}
}
return v.AllOK, nil
}

View File

@ -43,6 +43,11 @@ func (v *Validator) CheckMinInteger(input *Input, min int, message string) bool
return v.Check(input, i >= min, message) return v.Check(input, i >= min, message)
} }
func (v *Validator) CheckMaxInteger(input *Input, max int, message string) bool {
i, _ := strconv.Atoi(input.Val)
return v.Check(input, i <= max, message)
}
func (v *Validator) CheckMinDecimal(input *Input, min float64, message string) bool { func (v *Validator) CheckMinDecimal(input *Input, min float64, message string) bool {
f, _ := strconv.ParseFloat(input.Val, 64) f, _ := strconv.ParseFloat(input.Val, 64)
return v.Check(input, f >= min, message) return v.Check(input, f >= min, message)
@ -109,6 +114,12 @@ func (v *Validator) CheckValidDate(field *Input, message string) bool {
return v.Check(field, err == nil, message) return v.Check(field, err == nil, message)
} }
func (v *Validator) CheckDateAfter(field *Input, beforeField *Input, message string) bool {
date, _ := time.Parse("2006-01-02", field.Val)
before, _ := time.Parse("2006-01-02", beforeField.Val)
return v.Check(field, date.After(before), message)
}
func (v *Validator) CheckPasswordConfirmation(password *Input, confirm *Input, message string) bool { func (v *Validator) CheckPasswordConfirmation(password *Input, confirm *Input, message string) bool {
return v.Check(confirm, password.Val == confirm.Val, message) return v.Check(confirm, password.Val == confirm.Val, message)
} }

174
pkg/redsys/client.go Normal file
View File

@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
* SPDX-License-Identifier: AGPL-3.0-only
*/
package redsys
import (
"bytes"
"crypto/cipher"
"crypto/des"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
type Language int
const (
LanguageDefault Language = 0
LanguageSpanish Language = 1
LanguageEnglish Language = 2
LanguageCatalan Language = 3
LanguageFrench Language = 4
)
type TransactionType int
const (
TransactionTypeCharge TransactionType = 0
TransactionTypeRefund TransactionType = 3
TransactionTypePreauth TransactionType = 1
TransactionTypePreauthConfirm TransactionType = 2
TransactionTypePreauthVoid TransactionType = 9
TransactionTypeSplitAuth TransactionType = 7
TransactionTypeSplitConfirm TransactionType = 8
)
type Currency int
// Source: https://sis-d.redsys.es/pagosonline/codigos-autorizacion.html#monedas
// The codes seem to be the same as ISO 4217 https://en.wikipedia.org/wiki/ISO_4217
const (
CurrencyEUR Currency = 978
)
type Request struct {
TransactionType TransactionType
Amount int64
Currency Currency
Order string
Product string
CardHolder string
SuccessURL string
FailureURL string
ConsumerLanguage Language
}
type merchantParameters struct {
MerchantCode string `json:"Ds_Merchant_MerchantCode"`
Terminal string `json:"Ds_Merchant_Terminal"`
TransactionType TransactionType `json:"Ds_Merchant_TransactionType,string"`
Amount int64 `json:"Ds_Merchant_Amount,string"`
Currency Currency `json:"Ds_Merchant_Currency,string"`
Order string `json:"Ds_Merchant_Order"`
MerchantURL string `json:"Ds_Merchant_MerchantURL,omitempty"`
Product string `json:"Ds_Merchant_ProductDescription,omitempty"`
CardHolder string `json:"Ds_Merchant_Titular,omitempty"`
SuccessURL string `json:"Ds_Merchant_UrlOK,omitempty"`
FailureURL string `json:"Ds_Merchant_UrlKO,omitempty"`
MerchantName string `json:"Ds_Merchant_MerchantName,omitempty"`
ConsumerLanguage Language `json:"Ds_Merchant_ConsumerLanguage,string,omitempty"`
}
type Client struct {
merchantCode string
terminalCode string
crypto cipher.Block
}
func New(merchantCode string, terminalCode string, key string) (*Client, error) {
b, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return nil, fmt.Errorf("key is invalid base64: %v", err)
}
crypto, err := des.NewTripleDESCipher(b)
if err != nil {
return nil, err
}
return &Client{
merchantCode: merchantCode,
terminalCode: terminalCode,
crypto: crypto,
}, nil
}
func (p *Client) SignRequest(req *Request) (*SignedRequest, error) {
signed := &SignedRequest{
SignatureVersion: "HMAC_SHA256_V1",
}
params := &merchantParameters{
MerchantCode: p.merchantCode,
Terminal: p.terminalCode,
TransactionType: req.TransactionType,
Amount: req.Amount,
Currency: req.Currency,
Order: req.Order,
MerchantURL: "http://localhost/ca/",
Product: req.Product,
CardHolder: req.CardHolder,
SuccessURL: req.SuccessURL,
FailureURL: req.FailureURL,
MerchantName: "Merchant Here",
ConsumerLanguage: req.ConsumerLanguage,
}
var err error
signed.MerchantParameters, err = encodeMerchantParameters(params)
if err != nil {
return nil, err
}
signed.Signature = p.sign(params.Order, signed.MerchantParameters)
return signed, nil
}
func encodeMerchantParameters(params *merchantParameters) (string, error) {
marshalled, err := json.Marshal(params)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(marshalled), err
}
func (p *Client) sign(id string, data string) string {
key := encrypt3DES(id, p.crypto)
return mac256(data, key)
}
var iv = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
func encrypt3DES(str string, crypto cipher.Block) string {
cbc := cipher.NewCBCEncrypter(crypto, iv)
decrypted := []byte(str)
decryptedPadded, _ := zeroPad(decrypted, crypto.BlockSize())
cbc.CryptBlocks(decryptedPadded, decryptedPadded)
return base64.StdEncoding.EncodeToString(decryptedPadded)
}
func zeroPad(data []byte, blockLen int) ([]byte, error) {
padLen := (blockLen - (len(data) % blockLen)) % blockLen
pad := bytes.Repeat([]byte{0x00}, padLen)
return append(data, pad...), nil
}
func mac256(data string, key string) string {
decodedKey, _ := base64.StdEncoding.DecodeString(key)
res := hmac.New(sha256.New, decodedKey)
res.Write([]byte(strings.TrimSpace(data)))
result := res.Sum(nil)
return base64.StdEncoding.EncodeToString(result)
}
type SignedRequest struct {
MerchantParameters string
Signature string
SignatureVersion string
}

448
po/ca.po
View File

@ -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-10-14 21:54+0200\n" "POT-Creation-Date: 2023-10-19 21:23+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,9 +18,32 @@ 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/public/payment/success.gohtml:6
#: web/templates/public/payment/success.gohtml:11
msgctxt "title"
msgid "Payment Successful"
msgstr "Pagament amb èxit"
#: web/templates/public/payment/request.gohtml:6
#: web/templates/public/payment/request.gohtml:11
msgctxt "title"
msgid "Payment"
msgstr "Pagament"
#: web/templates/public/payment/request.gohtml:17
msgctxt "action"
msgid "Pay"
msgstr "Paga"
#: web/templates/public/payment/failure.gohtml:6
#: web/templates/public/payment/failure.gohtml:11
msgctxt "title"
msgid "Payment Failed"
msgstr "Ha fallat el pagament"
#: web/templates/public/services.gohtml:6 #: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15 #: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:43 web/templates/public/layout.gohtml:70 #: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71
#: web/templates/admin/services/index.gohtml:53 #: web/templates/admin/services/index.gohtml:53
msgctxt "title" msgctxt "title"
msgid "Services" msgid "Services"
@ -30,8 +53,8 @@ msgstr "Serveis"
msgid "The campsite offers many different services." msgid "The campsite offers many different services."
msgstr "El càmping disposa de diversos serveis." msgstr "El càmping disposa de diversos serveis."
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:29 #: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:30
#: web/templates/public/layout.gohtml:68 #: web/templates/public/layout.gohtml:69
msgctxt "title" msgctxt "title"
msgid "Home" msgid "Home"
msgstr "Inici" msgstr "Inici"
@ -52,7 +75,7 @@ msgstr "Els nostres serveis"
#: web/templates/public/home.gohtml:34 #: web/templates/public/home.gohtml:34
#: web/templates/public/surroundings.gohtml:6 #: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:10 #: web/templates/public/surroundings.gohtml:10
#: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71 #: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72
msgctxt "title" msgctxt "title"
msgid "Surroundings" msgid "Surroundings"
msgstr "Lentorn" msgstr "Lentorn"
@ -77,60 +100,109 @@ msgstr "Descobreix lentorn"
msgid "Come and enjoy!" msgid "Come and enjoy!"
msgstr "Vine a gaudir!" msgstr "Vine a gaudir!"
#: web/templates/public/campsite/type.gohtml:40 #: web/templates/public/campsite/type.gohtml:41
msgctxt "input" msgctxt "input"
msgid "Check-in Date" msgid "Check-in Date"
msgstr "Data dentrada" msgstr "Data dentrada"
#: web/templates/public/campsite/type.gohtml:46 #: web/templates/public/campsite/type.gohtml:47
msgctxt "input" msgctxt "input"
msgid "Check-out Date" msgid "Check-out Date"
msgstr "Data de sortida" msgstr "Data de sortida"
#: web/templates/public/campsite/type.gohtml:53 #: web/templates/public/campsite/type.gohtml:54
#: web/templates/public/booking.gohtml:175
msgctxt "action" msgctxt "action"
msgid "Book" msgid "Book"
msgstr "Reserva" msgstr "Reserva"
#: web/templates/public/campsite/type.gohtml:59 #: web/templates/public/campsite/type.gohtml:60
#: web/templates/admin/campsite/option/form.gohtml:58 #: web/templates/admin/campsite/option/form.gohtml:58
#: web/templates/admin/campsite/type/form.gohtml:73 #: web/templates/admin/campsite/type/form.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Prices" msgid "Prices"
msgstr "Preus" msgstr "Preus"
#: web/templates/public/campsite/type.gohtml:71 #: web/templates/public/campsite/type.gohtml:72
msgid "Starting from %s €/night" msgid "Starting from %s €/night"
msgstr "A partir de %s €/nit" msgstr "A partir de %s €/nit"
#: web/templates/public/campsite/type.gohtml:73 #: web/templates/public/campsite/type.gohtml:74
msgid "%s €/night" msgid "%s €/night"
msgstr "%s €/nit" msgstr "%s €/nit"
#: web/templates/public/campsite/type.gohtml:76 #: web/templates/public/campsite/type.gohtml:77
msgid "*Minimum %d nights per stay" msgid "*Minimum %d nights per stay"
msgstr "*Mínim %d nits per estada" msgstr "*Mínim %d nits per estada"
#: web/templates/public/campsite/type.gohtml:90 #: web/templates/public/campsite/type.gohtml:89
#: web/templates/admin/season/index.gohtml:48
msgctxt "title"
msgid "Calendar"
msgstr "Calendari"
#: web/templates/public/campsite/type.gohtml:100
msgctxt "title" msgctxt "title"
msgid "Features" msgid "Features"
msgstr "Característiques" msgstr "Característiques"
#: web/templates/public/campsite/type.gohtml:101 #: web/templates/public/campsite/type.gohtml:111
msgctxt "title" msgctxt "title"
msgid "Info" msgid "Info"
msgstr "Informació" msgstr "Informació"
#: web/templates/public/campsite/type.gohtml:105 #: web/templates/public/campsite/type.gohtml:115
msgctxt "title" msgctxt "title"
msgid "Facilities" msgid "Facilities"
msgstr "Equipaments" msgstr "Equipaments"
#: web/templates/public/campsite/type.gohtml:109 #: web/templates/public/campsite/type.gohtml:119
msgctxt "title" msgctxt "title"
msgid "Description" msgid "Description"
msgstr "Descripció" msgstr "Descripció"
#: web/templates/public/campsite/calendar.gohtml:18
#: web/templates/admin/season/calendar.gohtml:16
msgctxt "day"
msgid "Mon"
msgstr "dl"
#: web/templates/public/campsite/calendar.gohtml:19
#: web/templates/admin/season/calendar.gohtml:17
msgctxt "day"
msgid "Tue"
msgstr "dt"
#: web/templates/public/campsite/calendar.gohtml:20
#: web/templates/admin/season/calendar.gohtml:18
msgctxt "day"
msgid "Wed"
msgstr "dc"
#: web/templates/public/campsite/calendar.gohtml:21
#: web/templates/admin/season/calendar.gohtml:19
msgctxt "day"
msgid "Thu"
msgstr "dj"
#: web/templates/public/campsite/calendar.gohtml:22
#: web/templates/admin/season/calendar.gohtml:20
msgctxt "day"
msgid "Fri"
msgstr "dv"
#: web/templates/public/campsite/calendar.gohtml:23
#: web/templates/admin/season/calendar.gohtml:21
msgctxt "day"
msgid "Sat"
msgstr "ds"
#: web/templates/public/campsite/calendar.gohtml:24
#: web/templates/admin/season/calendar.gohtml:22
msgctxt "day"
msgid "Sun"
msgstr "dg"
#: web/templates/public/surroundings.gohtml:13 #: web/templates/public/surroundings.gohtml:13
msgctxt "title" msgctxt "title"
msgid "What to Do Outside the Campsite?" msgid "What to Do Outside the Campsite?"
@ -191,27 +263,140 @@ msgstr "Hi ha diversos punts on poder anar amb caiac, des de trams del riu Ter c
#: web/templates/public/campground.gohtml:6 #: web/templates/public/campground.gohtml:6
#: web/templates/public/campground.gohtml:11 #: web/templates/public/campground.gohtml:11
#: web/templates/public/layout.gohtml:30 web/templates/public/layout.gohtml:69 #: web/templates/public/layout.gohtml:31 web/templates/public/layout.gohtml:70
msgctxt "title" msgctxt "title"
msgid "Campground" msgid "Campground"
msgstr "El càmping" msgstr "El càmping"
#: web/templates/public/contact.gohtml:6 web/templates/public/contact.gohtml:15 #: web/templates/public/contact.gohtml:6 web/templates/public/contact.gohtml:15
#: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72 #: web/templates/public/layout.gohtml:46 web/templates/public/layout.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Contact" msgid "Contact"
msgstr "Contacte" msgstr "Contacte"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:24 #: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:95 msgctxt "title"
msgid "Booking"
msgstr "Reserva"
#: web/templates/public/booking.gohtml:15
msgctxt "title"
msgid "Customer Details"
msgstr "Detalls del client"
#: web/templates/public/booking.gohtml:18
msgctxt "input"
msgid "Full name"
msgstr "Nom i cognoms"
#: web/templates/public/booking.gohtml:26
msgctxt "input"
msgid "Address (optional)"
msgstr "Adreça (opcional)"
#: web/templates/public/booking.gohtml:34
msgctxt "input"
msgid "Postcode (optional)"
msgstr "Codi postal (opcional)"
#: web/templates/public/booking.gohtml:42
msgctxt "input"
msgid "Town or village (optional)"
msgstr "Població (opcional)"
#: web/templates/public/booking.gohtml:50
#: web/templates/admin/taxDetails.gohtml:98
msgctxt "input"
msgid "Country"
msgstr "País"
#: web/templates/public/booking.gohtml:53
msgid "Choose a country"
msgstr "Esculli un país"
#: web/templates/public/booking.gohtml:60 web/templates/admin/login.gohtml:22
#: web/templates/admin/profile.gohtml:35
#: web/templates/admin/taxDetails.gohtml:50
msgctxt "input"
msgid "Email"
msgstr "Correu-e"
#: web/templates/public/booking.gohtml:68
#: web/templates/admin/taxDetails.gohtml:42
msgctxt "input"
msgid "Phone"
msgstr "Telèfon"
#: web/templates/public/booking.gohtml:76
msgctxt "title"
msgid "Party Details"
msgstr "Dades dels visitants"
#: web/templates/public/booking.gohtml:79
msgctxt "input"
msgid "Adults"
msgstr "Adults"
#: web/templates/public/booking.gohtml:87
msgctxt "input"
msgid "Teenagers (from 11 to 16 years old)"
msgstr "Adolescents (entre 11 i 16 anys)"
#: web/templates/public/booking.gohtml:95
msgctxt "input"
msgid "Children (up to 10 years old)"
msgstr "Nens (fins a 10 anys)"
#: web/templates/public/booking.gohtml:103
msgctxt "input"
msgid "Dogs"
msgstr "Gossos"
#: web/templates/public/booking.gohtml:111
msgctxt "title"
msgid "Accomodation"
msgstr "Acomodacions"
#: web/templates/public/booking.gohtml:136
msgctxt "title"
msgid "Booking Period"
msgstr "Període de reserva"
#: web/templates/public/booking.gohtml:139
msgctxt "input"
msgid "Arrival date"
msgstr "Data darribada"
#: web/templates/public/booking.gohtml:147
msgctxt "input"
msgid "Departure date"
msgstr "Data de sortida"
#: web/templates/public/booking.gohtml:155
msgctxt "input"
msgid "Area preferences (optional)"
msgstr "Preferències dàrea (opcional)"
#: web/templates/public/booking.gohtml:165
msgctxt "input"
msgid "ACSI card? (optional)"
msgstr "Targeta ACSI? (opcional)"
#: web/templates/public/booking.gohtml:171
msgctxt "input"
msgid "I have read and I accept the reservation conditions"
msgstr "He llegit i accepto les condicions de reserves"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:25
#: web/templates/public/layout.gohtml:96
msgid "Campsite Montagut" msgid "Campsite Montagut"
msgstr "Càmping Montagut" msgstr "Càmping Montagut"
#: web/templates/public/layout.gohtml:22 web/templates/admin/layout.gohtml:18 #: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:18
msgid "Skip to main content" msgid "Skip to main content"
msgstr "Salta al contingut principal" msgstr "Salta al contingut principal"
#: web/templates/public/layout.gohtml:34 web/templates/public/layout.gohtml:79 #: web/templates/public/layout.gohtml:35 web/templates/public/layout.gohtml:80
#: web/templates/admin/campsite/index.gohtml:6 #: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12 #: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:40 web/templates/admin/layout.gohtml:71 #: web/templates/admin/layout.gohtml:40 web/templates/admin/layout.gohtml:71
@ -219,12 +404,12 @@ msgctxt "title"
msgid "Campsites" msgid "Campsites"
msgstr "Allotjaments" msgstr "Allotjaments"
#: web/templates/public/layout.gohtml:66 #: web/templates/public/layout.gohtml:67
msgctxt "title" msgctxt "title"
msgid "Sections" msgid "Sections"
msgstr "Apartats" msgstr "Apartats"
#: web/templates/public/layout.gohtml:92 #: web/templates/public/layout.gohtml:93
msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s" msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s"
msgstr "<abbr title=\"Número\">Núm.</abbr> <abbr title=\"Registre de Turisme de Catalunya\">RTC</abbr> %s" msgstr "<abbr title=\"Número\">Núm.</abbr> <abbr title=\"Registre de Turisme de Catalunya\">RTC</abbr> %s"
@ -735,52 +920,12 @@ msgstr "Color"
msgid "No seasons added yet." msgid "No seasons added yet."
msgstr "No sha afegit cap temporada encara." msgstr "No sha afegit cap temporada encara."
#: web/templates/admin/season/index.gohtml:48
msgctxt "title"
msgid "Calendar"
msgstr "Calendari"
#: web/templates/admin/season/l10n.gohtml:7 #: web/templates/admin/season/l10n.gohtml:7
#: web/templates/admin/season/l10n.gohtml:14 #: web/templates/admin/season/l10n.gohtml:14
msgctxt "title" msgctxt "title"
msgid "Translate Season to %s" msgid "Translate Season to %s"
msgstr "Traducció de la temporada a %s" msgstr "Traducció de la temporada a %s"
#: web/templates/admin/season/calendar.gohtml:16
msgctxt "day"
msgid "Mon"
msgstr "dl"
#: web/templates/admin/season/calendar.gohtml:17
msgctxt "day"
msgid "Tue"
msgstr "dt"
#: web/templates/admin/season/calendar.gohtml:18
msgctxt "day"
msgid "Wed"
msgstr "dc"
#: web/templates/admin/season/calendar.gohtml:19
msgctxt "day"
msgid "Thu"
msgstr "dj"
#: web/templates/admin/season/calendar.gohtml:20
msgctxt "day"
msgid "Fri"
msgstr "dv"
#: web/templates/admin/season/calendar.gohtml:21
msgctxt "day"
msgid "Sat"
msgstr "ds"
#: web/templates/admin/season/calendar.gohtml:22
msgctxt "day"
msgid "Sun"
msgstr "dg"
#: web/templates/admin/season/calendar.gohtml:49 #: web/templates/admin/season/calendar.gohtml:49
#: web/templates/admin/media/picker.gohtml:61 #: web/templates/admin/media/picker.gohtml:61
msgctxt "action" msgctxt "action"
@ -798,12 +943,6 @@ msgctxt "title"
msgid "Login" msgid "Login"
msgstr "Entrada" msgstr "Entrada"
#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35
#: web/templates/admin/taxDetails.gohtml:50
msgctxt "input"
msgid "Email"
msgstr "Correu-e"
#: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46 #: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46
msgctxt "input" msgctxt "input"
msgid "Password" msgid "Password"
@ -916,11 +1055,6 @@ msgctxt "input"
msgid "Trade Name" msgid "Trade Name"
msgstr "Nom comercial" msgstr "Nom comercial"
#: web/templates/admin/taxDetails.gohtml:42
msgctxt "input"
msgid "Phone"
msgstr "Telèfon"
#: web/templates/admin/taxDetails.gohtml:66 #: web/templates/admin/taxDetails.gohtml:66
msgctxt "input" msgctxt "input"
msgid "Address" msgid "Address"
@ -938,14 +1072,9 @@ msgstr "Província"
#: web/templates/admin/taxDetails.gohtml:90 #: web/templates/admin/taxDetails.gohtml:90
msgctxt "input" msgctxt "input"
msgid "Postal Code" msgid "Postcode"
msgstr "Codi postal" msgstr "Codi postal"
#: web/templates/admin/taxDetails.gohtml:98
msgctxt "input"
msgid "Country"
msgstr "País"
#: web/templates/admin/taxDetails.gohtml:108 #: web/templates/admin/taxDetails.gohtml:108
msgctxt "input" msgctxt "input"
msgid "RTC number" msgid "RTC number"
@ -1082,10 +1211,12 @@ msgid "Slide image must be an image media type."
msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge." msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge."
#: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:210 #: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:210
#: pkg/booking/public.go:292
msgid "Email can not be empty." msgid "Email can not be empty."
msgstr "No podeu deixar el correu-e en blanc." msgstr "No podeu deixar el correu-e en blanc."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:211 #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:211
#: pkg/booking/public.go:293
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."
@ -1106,7 +1237,7 @@ msgstr "Automàtic"
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268 #: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243 #: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243
#: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69 #: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69
#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 #: pkg/season/admin.go:394 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc." msgstr "No podeu deixar el nom en blanc."
@ -1213,7 +1344,12 @@ msgstr "El número mínim de nits ha de ser enter."
msgid "Minimum number of nights must be one or greater." msgid "Minimum number of nights must be one or greater."
msgstr "El número mínim de nits no pot ser zero." msgstr "El número mínim de nits no pot ser zero."
#: pkg/campsite/admin.go:226 #: pkg/campsite/types/public.go:157
msgctxt "season"
msgid "Closed"
msgstr "Tancat"
#: pkg/campsite/admin.go:226 pkg/booking/public.go:321
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "El tipus dallotjament escollit no és vàlid." msgstr "El tipus dallotjament escollit no és vàlid."
@ -1221,96 +1357,96 @@ msgstr "El tipus dallotjament escollit no és vàlid."
msgid "Label can not be empty." msgid "Label can not be empty."
msgstr "No podeu deixar letiqueta en blanc." msgstr "No podeu deixar letiqueta en blanc."
#: pkg/season/admin.go:202 #: pkg/season/admin.go:212
msgctxt "month" msgctxt "month"
msgid "January" msgid "January"
msgstr "gener" msgstr "gener"
#: pkg/season/admin.go:203 #: pkg/season/admin.go:213
msgctxt "month" msgctxt "month"
msgid "February" msgid "February"
msgstr "febrer" msgstr "febrer"
#: pkg/season/admin.go:204 #: pkg/season/admin.go:214
msgctxt "month" msgctxt "month"
msgid "March" msgid "March"
msgstr "març" msgstr "març"
#: pkg/season/admin.go:205 #: pkg/season/admin.go:215
msgctxt "month" msgctxt "month"
msgid "April" msgid "April"
msgstr "abril" msgstr "abril"
#: pkg/season/admin.go:206 #: pkg/season/admin.go:216
msgctxt "month" msgctxt "month"
msgid "May" msgid "May"
msgstr "maig" msgstr "maig"
#: pkg/season/admin.go:207 #: pkg/season/admin.go:217
msgctxt "month" msgctxt "month"
msgid "June" msgid "June"
msgstr "juny" msgstr "juny"
#: pkg/season/admin.go:208 #: pkg/season/admin.go:218
msgctxt "month" msgctxt "month"
msgid "July" msgid "July"
msgstr "juliol" msgstr "juliol"
#: pkg/season/admin.go:209 #: pkg/season/admin.go:219
msgctxt "month" msgctxt "month"
msgid "August" msgid "August"
msgstr "agost" msgstr "agost"
#: pkg/season/admin.go:210 #: pkg/season/admin.go:220
msgctxt "month" msgctxt "month"
msgid "September" msgid "September"
msgstr "setembre" msgstr "setembre"
#: pkg/season/admin.go:211 #: pkg/season/admin.go:221
msgctxt "month" msgctxt "month"
msgid "October" msgid "October"
msgstr "octubre" msgstr "octubre"
#: pkg/season/admin.go:212 #: pkg/season/admin.go:222
msgctxt "month" msgctxt "month"
msgid "November" msgid "November"
msgstr "novembre" msgstr "novembre"
#: pkg/season/admin.go:213 #: pkg/season/admin.go:223
msgctxt "month" msgctxt "month"
msgid "December" msgid "December"
msgstr "desembre" msgstr "desembre"
#: pkg/season/admin.go:383 #: pkg/season/admin.go:395
msgid "Color can not be empty." msgid "Color can not be empty."
msgstr "No podeu deixar el color en blanc." msgstr "No podeu deixar el color en blanc."
#: pkg/season/admin.go:384 #: pkg/season/admin.go:396
msgid "This color is not valid. It must be like #123abc." msgid "This color is not valid. It must be like #123abc."
msgstr "Aquest color no és vàlid. Hauria de ser similar a #123abc." msgstr "Aquest color no és vàlid. Hauria de ser similar a #123abc."
#: pkg/season/admin.go:460 #: pkg/season/admin.go:472
msgctxt "action" msgctxt "action"
msgid "Unset" msgid "Unset"
msgstr "Desassigna" msgstr "Desassigna"
#: pkg/season/admin.go:491 #: pkg/season/admin.go:503
msgid "Start date can not be empty." msgid "Start date can not be empty."
msgstr "No podeu deixar la data dinici en blanc." msgstr "No podeu deixar la data dinici en blanc."
#: pkg/season/admin.go:492 #: pkg/season/admin.go:504
msgid "Start date must be a valid date." msgid "Start date must be a valid date."
msgstr "La data dinici ha de ser una data vàlida." msgstr "La data dinici ha de ser una data vàlida."
#: pkg/season/admin.go:494 #: pkg/season/admin.go:506
msgid "End date can not be empty." msgid "End date can not be empty."
msgstr "No podeu deixar la data de fi en blanc." msgstr "No podeu deixar la data de fi en blanc."
#: pkg/season/admin.go:495 #: pkg/season/admin.go:507
msgid "End date must be a valid date." msgid "End date must be a valid date."
msgstr "La data de fi ha de ser una data vàlida." msgstr "La data de fi ha de ser una data vàlida."
#: pkg/company/admin.go:193 #: pkg/company/admin.go:193 pkg/booking/public.go:279
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "El país escollit no és vàlid." msgstr "El país escollit no és vàlid."
@ -1330,11 +1466,11 @@ msgstr "No podeu deixar el NIF en blanc."
msgid "This VAT number is not valid." msgid "This VAT number is not valid."
msgstr "Aquest NIF no és vàlid." msgstr "Aquest NIF no és vàlid."
#: pkg/company/admin.go:205 #: pkg/company/admin.go:205 pkg/booking/public.go:295
msgid "Phone can not be empty." msgid "Phone can not be empty."
msgstr "No podeu deixar el telèfon en blanc." msgstr "No podeu deixar el telèfon en blanc."
#: pkg/company/admin.go:206 #: pkg/company/admin.go:206 pkg/booking/public.go:296
msgid "This phone number is not valid." msgid "This phone number is not valid."
msgstr "Aquest número de telèfon no és vàlid." msgstr "Aquest número de telèfon no és vàlid."
@ -1358,7 +1494,7 @@ msgstr "No podeu deixar la província en blanc."
msgid "Postal code can not be empty." msgid "Postal code can not be empty."
msgstr "No podeu deixar el codi postal en blanc." msgstr "No podeu deixar el codi postal en blanc."
#: pkg/company/admin.go:220 #: pkg/company/admin.go:220 pkg/booking/public.go:288
msgid "This postal code is not valid." msgid "This postal code is not valid."
msgstr "Aquest codi postal no és vàlid." msgstr "Aquest codi postal no és vàlid."
@ -1386,6 +1522,106 @@ msgstr "No podeu deixar el fitxer del mèdia en blanc."
msgid "Filename can not be empty." msgid "Filename can not be empty."
msgstr "No podeu deixar el nom del fitxer en blanc." msgstr "No podeu deixar el nom del fitxer en blanc."
#: pkg/booking/public.go:283
msgid "Full name can not be empty."
msgstr "No podeu deixar el nom i els cognoms en blanc."
#: pkg/booking/public.go:284
msgid "Full name must have at least one letter."
msgstr "El nom i els cognoms han de tenir com a mínim una lletra."
#: pkg/booking/public.go:300
msgid "Number of adults can not be empty"
msgstr "No podeu deixar el número dadults en blanc."
#: pkg/booking/public.go:301
msgid "Number of adults must be an integer."
msgstr "El número dadults ha de ser enter."
#: pkg/booking/public.go:302
msgid "Number of adults must be one or greater."
msgstr "El número dadults no pot ser zero."
#: pkg/booking/public.go:305
msgid "Number of teenagers can not be empty"
msgstr "No podeu deixar el número dadolescents en blanc."
#: pkg/booking/public.go:306
msgid "Number of teenagers must be an integer."
msgstr "El número dadolescents ha de ser enter."
#: pkg/booking/public.go:307
msgid "Number of teenagers must be zero or greater."
msgstr "El número dadolescents ha de ser com a mínim zero."
#: pkg/booking/public.go:310
msgid "Number of children can not be empty"
msgstr "No podeu deixar el número de nens en blanc."
#: pkg/booking/public.go:311
msgid "Number of children must be an integer."
msgstr "El número de nens ha de ser enter."
#: pkg/booking/public.go:312
msgid "Number of children must be zero or greater."
msgstr "El número de nens ha de ser com a mínim zero."
#: pkg/booking/public.go:315
msgid "Number of dogs can not be empty"
msgstr "No podeu deixar el número de gossos en blanc."
#: pkg/booking/public.go:316
msgid "Number of dogs must be an integer."
msgstr "El número de gossos ha de ser enter."
#: pkg/booking/public.go:317
msgid "Number of dogs must be zero or greater."
msgstr "El número de gossos ha de ser com a mínim zero."
#: pkg/booking/public.go:322
msgid "Arrival date can not be empty"
msgstr "No podeu deixar la data darribada en blanc."
#: pkg/booking/public.go:323
msgid "Arrival date must be a valid date."
msgstr "La data darribada ha de ser una data vàlida."
#: pkg/booking/public.go:327
msgid "Departure date can not be empty"
msgstr "No podeu deixar la data de sortida en blanc."
#: pkg/booking/public.go:328
msgid "Departure date must be a valid date."
msgstr "La data de sortida ha de ser una data vàlida."
#: pkg/booking/public.go:329
msgid "The departure date must be after the arrival date."
msgstr "La data de sortida ha de ser posterior a la darribada."
#: pkg/booking/public.go:332
msgid "It is mandatory to agree to the reservation conditions."
msgstr "És obligatori acceptar les condicions de reserves."
#: pkg/booking/public.go:335
#, c-format
msgid "%s can not be empty"
msgstr "No podeu deixar %s en blanc."
#: pkg/booking/public.go:336
#, c-format
msgid "%s must be an integer."
msgstr "%s ha de ser un número enter."
#: pkg/booking/public.go:337
#, c-format
msgid "%s must be %d or greater."
msgstr "El valor de %s ha de ser com a mínim %d."
#: pkg/booking/public.go:338
#, c-format
msgid "%s must be at most %d."
msgstr "El valor de %s ha de ser com a màxim %d."
#~ msgid "%s: %s €/night" #~ msgid "%s: %s €/night"
#~ msgstr "%s: %s €/nit" #~ msgstr "%s: %s €/nit"

447
po/es.po
View File

@ -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-10-14 21:54+0200\n" "POT-Creation-Date: 2023-10-19 21:23+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,9 +18,32 @@ 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/public/payment/success.gohtml:6
#: web/templates/public/payment/success.gohtml:11
msgctxt "title"
msgid "Payment Successful"
msgstr "Pago con éxito"
#: web/templates/public/payment/request.gohtml:6
#: web/templates/public/payment/request.gohtml:11
msgctxt "title"
msgid "Payment"
msgstr "Pago"
#: web/templates/public/payment/request.gohtml:17
msgctxt "action"
msgid "Pay"
msgstr "Pagar"
#: web/templates/public/payment/failure.gohtml:6
#: web/templates/public/payment/failure.gohtml:11
msgctxt "title"
msgid "Payment Failed"
msgstr "Pago fallido"
#: web/templates/public/services.gohtml:6 #: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15 #: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:43 web/templates/public/layout.gohtml:70 #: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71
#: web/templates/admin/services/index.gohtml:53 #: web/templates/admin/services/index.gohtml:53
msgctxt "title" msgctxt "title"
msgid "Services" msgid "Services"
@ -30,8 +53,8 @@ msgstr "Servicios"
msgid "The campsite offers many different services." msgid "The campsite offers many different services."
msgstr "El camping dispone de varios servicios." msgstr "El camping dispone de varios servicios."
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:29 #: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:30
#: web/templates/public/layout.gohtml:68 #: web/templates/public/layout.gohtml:69
msgctxt "title" msgctxt "title"
msgid "Home" msgid "Home"
msgstr "Inicio" msgstr "Inicio"
@ -52,7 +75,7 @@ msgstr "Nuestros servicios"
#: web/templates/public/home.gohtml:34 #: web/templates/public/home.gohtml:34
#: web/templates/public/surroundings.gohtml:6 #: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:10 #: web/templates/public/surroundings.gohtml:10
#: web/templates/public/layout.gohtml:44 web/templates/public/layout.gohtml:71 #: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72
msgctxt "title" msgctxt "title"
msgid "Surroundings" msgid "Surroundings"
msgstr "El entorno" msgstr "El entorno"
@ -77,60 +100,109 @@ msgstr "Descubre el entorno"
msgid "Come and enjoy!" msgid "Come and enjoy!"
msgstr "¡Ven a disfrutar!" msgstr "¡Ven a disfrutar!"
#: web/templates/public/campsite/type.gohtml:40 #: web/templates/public/campsite/type.gohtml:41
msgctxt "input" msgctxt "input"
msgid "Check-in Date" msgid "Check-in Date"
msgstr "Fecha de entrada" msgstr "Fecha de entrada"
#: web/templates/public/campsite/type.gohtml:46 #: web/templates/public/campsite/type.gohtml:47
msgctxt "input" msgctxt "input"
msgid "Check-out Date" msgid "Check-out Date"
msgstr "Fecha de salida" msgstr "Fecha de salida"
#: web/templates/public/campsite/type.gohtml:53 #: web/templates/public/campsite/type.gohtml:54
#: web/templates/public/booking.gohtml:175
msgctxt "action" msgctxt "action"
msgid "Book" msgid "Book"
msgstr "Reservar" msgstr "Reservar"
#: web/templates/public/campsite/type.gohtml:59 #: web/templates/public/campsite/type.gohtml:60
#: web/templates/admin/campsite/option/form.gohtml:58 #: web/templates/admin/campsite/option/form.gohtml:58
#: web/templates/admin/campsite/type/form.gohtml:73 #: web/templates/admin/campsite/type/form.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Prices" msgid "Prices"
msgstr "Precios" msgstr "Precios"
#: web/templates/public/campsite/type.gohtml:71 #: web/templates/public/campsite/type.gohtml:72
msgid "Starting from %s €/night" msgid "Starting from %s €/night"
msgstr "A partir de %s €/noche" msgstr "A partir de %s €/noche"
#: web/templates/public/campsite/type.gohtml:73 #: web/templates/public/campsite/type.gohtml:74
msgid "%s €/night" msgid "%s €/night"
msgstr "%s €/noche" msgstr "%s €/noche"
#: web/templates/public/campsite/type.gohtml:76 #: web/templates/public/campsite/type.gohtml:77
msgid "*Minimum %d nights per stay" msgid "*Minimum %d nights per stay"
msgstr "*Mínimo %d noches por estancia" msgstr "*Mínimo %d noches por estancia"
#: web/templates/public/campsite/type.gohtml:90 #: web/templates/public/campsite/type.gohtml:89
#: web/templates/admin/season/index.gohtml:48
msgctxt "title"
msgid "Calendar"
msgstr "Calendario"
#: web/templates/public/campsite/type.gohtml:100
msgctxt "title" msgctxt "title"
msgid "Features" msgid "Features"
msgstr "Características" msgstr "Características"
#: web/templates/public/campsite/type.gohtml:101 #: web/templates/public/campsite/type.gohtml:111
msgctxt "title" msgctxt "title"
msgid "Info" msgid "Info"
msgstr "Información" msgstr "Información"
#: web/templates/public/campsite/type.gohtml:105 #: web/templates/public/campsite/type.gohtml:115
msgctxt "title" msgctxt "title"
msgid "Facilities" msgid "Facilities"
msgstr "Equipamento" msgstr "Equipamento"
#: web/templates/public/campsite/type.gohtml:109 #: web/templates/public/campsite/type.gohtml:119
msgctxt "title" msgctxt "title"
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: web/templates/public/campsite/calendar.gohtml:18
#: web/templates/admin/season/calendar.gohtml:16
msgctxt "day"
msgid "Mon"
msgstr "lu"
#: web/templates/public/campsite/calendar.gohtml:19
#: web/templates/admin/season/calendar.gohtml:17
msgctxt "day"
msgid "Tue"
msgstr "ma"
#: web/templates/public/campsite/calendar.gohtml:20
#: web/templates/admin/season/calendar.gohtml:18
msgctxt "day"
msgid "Wed"
msgstr "mi"
#: web/templates/public/campsite/calendar.gohtml:21
#: web/templates/admin/season/calendar.gohtml:19
msgctxt "day"
msgid "Thu"
msgstr "ju"
#: web/templates/public/campsite/calendar.gohtml:22
#: web/templates/admin/season/calendar.gohtml:20
msgctxt "day"
msgid "Fri"
msgstr "vi"
#: web/templates/public/campsite/calendar.gohtml:23
#: web/templates/admin/season/calendar.gohtml:21
msgctxt "day"
msgid "Sat"
msgstr "sá"
#: web/templates/public/campsite/calendar.gohtml:24
#: web/templates/admin/season/calendar.gohtml:22
msgctxt "day"
msgid "Sun"
msgstr "do"
#: web/templates/public/surroundings.gohtml:13 #: web/templates/public/surroundings.gohtml:13
msgctxt "title" msgctxt "title"
msgid "What to Do Outside the Campsite?" msgid "What to Do Outside the Campsite?"
@ -191,27 +263,140 @@ msgstr "Hay diversos puntos dónde podéis ir en kayak, desde tramos del río Te
#: web/templates/public/campground.gohtml:6 #: web/templates/public/campground.gohtml:6
#: web/templates/public/campground.gohtml:11 #: web/templates/public/campground.gohtml:11
#: web/templates/public/layout.gohtml:30 web/templates/public/layout.gohtml:69 #: web/templates/public/layout.gohtml:31 web/templates/public/layout.gohtml:70
msgctxt "title" msgctxt "title"
msgid "Campground" msgid "Campground"
msgstr "El camping" msgstr "El camping"
#: web/templates/public/contact.gohtml:6 web/templates/public/contact.gohtml:15 #: web/templates/public/contact.gohtml:6 web/templates/public/contact.gohtml:15
#: web/templates/public/layout.gohtml:45 web/templates/public/layout.gohtml:72 #: web/templates/public/layout.gohtml:46 web/templates/public/layout.gohtml:73
msgctxt "title" msgctxt "title"
msgid "Contact" msgid "Contact"
msgstr "Contacto" msgstr "Contacto"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:24 #: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:95 msgctxt "title"
msgid "Booking"
msgstr "Reserva"
#: web/templates/public/booking.gohtml:15
msgctxt "title"
msgid "Customer Details"
msgstr "Detalles del cliente"
#: web/templates/public/booking.gohtml:18
msgctxt "input"
msgid "Full name"
msgstr "Nombre y apellidos"
#: web/templates/public/booking.gohtml:26
msgctxt "input"
msgid "Address (optional)"
msgstr "Dirección (opcional)"
#: web/templates/public/booking.gohtml:34
msgctxt "input"
msgid "Postcode (optional)"
msgstr "Código postal (opcional)"
#: web/templates/public/booking.gohtml:42
msgctxt "input"
msgid "Town or village (optional)"
msgstr "Población (opcional)"
#: web/templates/public/booking.gohtml:50
#: web/templates/admin/taxDetails.gohtml:98
msgctxt "input"
msgid "Country"
msgstr "País"
#: web/templates/public/booking.gohtml:53
msgid "Choose a country"
msgstr "Escoja un país"
#: web/templates/public/booking.gohtml:60 web/templates/admin/login.gohtml:22
#: web/templates/admin/profile.gohtml:35
#: web/templates/admin/taxDetails.gohtml:50
msgctxt "input"
msgid "Email"
msgstr "Correo-e"
#: web/templates/public/booking.gohtml:68
#: web/templates/admin/taxDetails.gohtml:42
msgctxt "input"
msgid "Phone"
msgstr "Teléfono"
#: web/templates/public/booking.gohtml:76
msgctxt "title"
msgid "Party Details"
msgstr "Datos de los visitantes"
#: web/templates/public/booking.gohtml:79
msgctxt "input"
msgid "Adults"
msgstr "Adultos"
#: web/templates/public/booking.gohtml:87
msgctxt "input"
msgid "Teenagers (from 11 to 16 years old)"
msgstr "Adolescentes (de 11 a 16 años)"
#: web/templates/public/booking.gohtml:95
msgctxt "input"
msgid "Children (up to 10 years old)"
msgstr "Niños (hasta 10 años)"
#: web/templates/public/booking.gohtml:103
msgctxt "input"
msgid "Dogs"
msgstr "Perros"
#: web/templates/public/booking.gohtml:111
msgctxt "title"
msgid "Accomodation"
msgstr "Acomodaciones"
#: web/templates/public/booking.gohtml:136
msgctxt "title"
msgid "Booking Period"
msgstr "Periodo de reserva"
#: web/templates/public/booking.gohtml:139
msgctxt "input"
msgid "Arrival date"
msgstr "Fecha de llegada"
#: web/templates/public/booking.gohtml:147
msgctxt "input"
msgid "Departure date"
msgstr "Fecha de salida"
#: web/templates/public/booking.gohtml:155
msgctxt "input"
msgid "Area preferences (optional)"
msgstr "Preferencias de área (opcional)"
#: web/templates/public/booking.gohtml:165
msgctxt "input"
msgid "ACSI card? (optional)"
msgstr "¿Tarjeta ACSI? (opcional)"
#: web/templates/public/booking.gohtml:171
msgctxt "input"
msgid "I have read and I accept the reservation conditions"
msgstr "He leído y acepto las condiciones de reserva"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:25
#: web/templates/public/layout.gohtml:96
msgid "Campsite Montagut" msgid "Campsite Montagut"
msgstr "Camping Montagut" msgstr "Camping Montagut"
#: web/templates/public/layout.gohtml:22 web/templates/admin/layout.gohtml:18 #: web/templates/public/layout.gohtml:23 web/templates/admin/layout.gohtml:18
msgid "Skip to main content" msgid "Skip to main content"
msgstr "Saltar al contenido principal" msgstr "Saltar al contenido principal"
#: web/templates/public/layout.gohtml:34 web/templates/public/layout.gohtml:79 #: web/templates/public/layout.gohtml:35 web/templates/public/layout.gohtml:80
#: web/templates/admin/campsite/index.gohtml:6 #: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12 #: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:40 web/templates/admin/layout.gohtml:71 #: web/templates/admin/layout.gohtml:40 web/templates/admin/layout.gohtml:71
@ -219,12 +404,12 @@ msgctxt "title"
msgid "Campsites" msgid "Campsites"
msgstr "Alojamientos" msgstr "Alojamientos"
#: web/templates/public/layout.gohtml:66 #: web/templates/public/layout.gohtml:67
msgctxt "title" msgctxt "title"
msgid "Sections" msgid "Sections"
msgstr "Apartados" msgstr "Apartados"
#: web/templates/public/layout.gohtml:92 #: web/templates/public/layout.gohtml:93
msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s" msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s"
msgstr "<abbr title=\"Número\">Nº</abbr> <abbr title=\"Registro de Turismo de Cataluña\">RTC</abbr> %s" msgstr "<abbr title=\"Número\">Nº</abbr> <abbr title=\"Registro de Turismo de Cataluña\">RTC</abbr> %s"
@ -735,52 +920,12 @@ msgstr "Color"
msgid "No seasons added yet." msgid "No seasons added yet."
msgstr "No se ha añadido ninguna temporada todavía." msgstr "No se ha añadido ninguna temporada todavía."
#: web/templates/admin/season/index.gohtml:48
msgctxt "title"
msgid "Calendar"
msgstr "Calendario"
#: web/templates/admin/season/l10n.gohtml:7 #: web/templates/admin/season/l10n.gohtml:7
#: web/templates/admin/season/l10n.gohtml:14 #: web/templates/admin/season/l10n.gohtml:14
msgctxt "title" msgctxt "title"
msgid "Translate Season to %s" msgid "Translate Season to %s"
msgstr "Traducción de la temporada a %s" msgstr "Traducción de la temporada a %s"
#: web/templates/admin/season/calendar.gohtml:16
msgctxt "day"
msgid "Mon"
msgstr "lu"
#: web/templates/admin/season/calendar.gohtml:17
msgctxt "day"
msgid "Tue"
msgstr "ma"
#: web/templates/admin/season/calendar.gohtml:18
msgctxt "day"
msgid "Wed"
msgstr "mi"
#: web/templates/admin/season/calendar.gohtml:19
msgctxt "day"
msgid "Thu"
msgstr "ju"
#: web/templates/admin/season/calendar.gohtml:20
msgctxt "day"
msgid "Fri"
msgstr "vi"
#: web/templates/admin/season/calendar.gohtml:21
msgctxt "day"
msgid "Sat"
msgstr "sá"
#: web/templates/admin/season/calendar.gohtml:22
msgctxt "day"
msgid "Sun"
msgstr "do"
#: web/templates/admin/season/calendar.gohtml:49 #: web/templates/admin/season/calendar.gohtml:49
#: web/templates/admin/media/picker.gohtml:61 #: web/templates/admin/media/picker.gohtml:61
msgctxt "action" msgctxt "action"
@ -798,12 +943,6 @@ msgctxt "title"
msgid "Login" msgid "Login"
msgstr "Entrada" msgstr "Entrada"
#: web/templates/admin/login.gohtml:22 web/templates/admin/profile.gohtml:35
#: web/templates/admin/taxDetails.gohtml:50
msgctxt "input"
msgid "Email"
msgstr "Correo-e"
#: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46 #: web/templates/admin/login.gohtml:31 web/templates/admin/profile.gohtml:46
msgctxt "input" msgctxt "input"
msgid "Password" msgid "Password"
@ -916,11 +1055,6 @@ msgctxt "input"
msgid "Trade Name" msgid "Trade Name"
msgstr "Nombre comercial" msgstr "Nombre comercial"
#: web/templates/admin/taxDetails.gohtml:42
msgctxt "input"
msgid "Phone"
msgstr "Teléfono"
#: web/templates/admin/taxDetails.gohtml:66 #: web/templates/admin/taxDetails.gohtml:66
msgctxt "input" msgctxt "input"
msgid "Address" msgid "Address"
@ -938,14 +1072,9 @@ msgstr "Provincia"
#: web/templates/admin/taxDetails.gohtml:90 #: web/templates/admin/taxDetails.gohtml:90
msgctxt "input" msgctxt "input"
msgid "Postal Code" msgid "Postcode"
msgstr "Código postal" msgstr "Código postal"
#: web/templates/admin/taxDetails.gohtml:98
msgctxt "input"
msgid "Country"
msgstr "País"
#: web/templates/admin/taxDetails.gohtml:108 #: web/templates/admin/taxDetails.gohtml:108
msgctxt "input" msgctxt "input"
msgid "RTC number" msgid "RTC number"
@ -1082,10 +1211,12 @@ msgid "Slide image must be an image media type."
msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen." msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen."
#: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:210 #: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:210
#: pkg/booking/public.go:292
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:57 pkg/app/user.go:247 pkg/company/admin.go:211 #: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:211
#: pkg/booking/public.go:293
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."
@ -1106,7 +1237,7 @@ msgstr "Automático"
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268 #: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243 #: pkg/campsite/types/option.go:340 pkg/campsite/types/feature.go:243
#: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69 #: pkg/campsite/types/admin.go:415 pkg/season/l10n.go:69
#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266 #: pkg/season/admin.go:394 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty." msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco." msgstr "No podéis dejar el nombre en blanco."
@ -1213,7 +1344,12 @@ msgstr "El número mínimo de noches tiene que ser entero."
msgid "Minimum number of nights must be one or greater." msgid "Minimum number of nights must be one or greater."
msgstr "El número mínimo de noches no puede ser cero." msgstr "El número mínimo de noches no puede ser cero."
#: pkg/campsite/admin.go:226 #: pkg/campsite/types/public.go:157
msgctxt "season"
msgid "Closed"
msgstr "Cerrado"
#: pkg/campsite/admin.go:226 pkg/booking/public.go:321
msgid "Selected campsite type is not valid." msgid "Selected campsite type is not valid."
msgstr "El tipo de alojamiento escogido no es válido." msgstr "El tipo de alojamiento escogido no es válido."
@ -1221,96 +1357,96 @@ msgstr "El tipo de alojamiento escogido no es válido."
msgid "Label can not be empty." msgid "Label can not be empty."
msgstr "No podéis dejar la etiqueta en blanco." msgstr "No podéis dejar la etiqueta en blanco."
#: pkg/season/admin.go:202 #: pkg/season/admin.go:212
msgctxt "month" msgctxt "month"
msgid "January" msgid "January"
msgstr "enero" msgstr "enero"
#: pkg/season/admin.go:203 #: pkg/season/admin.go:213
msgctxt "month" msgctxt "month"
msgid "February" msgid "February"
msgstr "febrero" msgstr "febrero"
#: pkg/season/admin.go:204 #: pkg/season/admin.go:214
msgctxt "month" msgctxt "month"
msgid "March" msgid "March"
msgstr "marzo" msgstr "marzo"
#: pkg/season/admin.go:205 #: pkg/season/admin.go:215
msgctxt "month" msgctxt "month"
msgid "April" msgid "April"
msgstr "abril" msgstr "abril"
#: pkg/season/admin.go:206 #: pkg/season/admin.go:216
msgctxt "month" msgctxt "month"
msgid "May" msgid "May"
msgstr "mayo" msgstr "mayo"
#: pkg/season/admin.go:207 #: pkg/season/admin.go:217
msgctxt "month" msgctxt "month"
msgid "June" msgid "June"
msgstr "junio" msgstr "junio"
#: pkg/season/admin.go:208 #: pkg/season/admin.go:218
msgctxt "month" msgctxt "month"
msgid "July" msgid "July"
msgstr "julio" msgstr "julio"
#: pkg/season/admin.go:209 #: pkg/season/admin.go:219
msgctxt "month" msgctxt "month"
msgid "August" msgid "August"
msgstr "agosto" msgstr "agosto"
#: pkg/season/admin.go:210 #: pkg/season/admin.go:220
msgctxt "month" msgctxt "month"
msgid "September" msgid "September"
msgstr "septiembre" msgstr "septiembre"
#: pkg/season/admin.go:211 #: pkg/season/admin.go:221
msgctxt "month" msgctxt "month"
msgid "October" msgid "October"
msgstr "octubre" msgstr "octubre"
#: pkg/season/admin.go:212 #: pkg/season/admin.go:222
msgctxt "month" msgctxt "month"
msgid "November" msgid "November"
msgstr "noviembre" msgstr "noviembre"
#: pkg/season/admin.go:213 #: pkg/season/admin.go:223
msgctxt "month" msgctxt "month"
msgid "December" msgid "December"
msgstr "diciembre" msgstr "diciembre"
#: pkg/season/admin.go:383 #: pkg/season/admin.go:395
msgid "Color can not be empty." msgid "Color can not be empty."
msgstr "No podéis dejar el color en blanco." msgstr "No podéis dejar el color en blanco."
#: pkg/season/admin.go:384 #: pkg/season/admin.go:396
msgid "This color is not valid. It must be like #123abc." msgid "This color is not valid. It must be like #123abc."
msgstr "Este color no es válido. Tiene que ser parecido a #123abc." msgstr "Este color no es válido. Tiene que ser parecido a #123abc."
#: pkg/season/admin.go:460 #: pkg/season/admin.go:472
msgctxt "action" msgctxt "action"
msgid "Unset" msgid "Unset"
msgstr "Desasignar" msgstr "Desasignar"
#: pkg/season/admin.go:491 #: pkg/season/admin.go:503
msgid "Start date can not be empty." msgid "Start date can not be empty."
msgstr "No podéis dejar la fecha de inicio en blanco." msgstr "No podéis dejar la fecha de inicio en blanco."
#: pkg/season/admin.go:492 #: pkg/season/admin.go:504
msgid "Start date must be a valid date." msgid "Start date must be a valid date."
msgstr "La fecha de inicio tiene que ser una fecha válida." msgstr "La fecha de inicio tiene que ser una fecha válida."
#: pkg/season/admin.go:494 #: pkg/season/admin.go:506
msgid "End date can not be empty." msgid "End date can not be empty."
msgstr "No podéis dejar la fecha final en blanco." msgstr "No podéis dejar la fecha final en blanco."
#: pkg/season/admin.go:495 #: pkg/season/admin.go:507
msgid "End date must be a valid date." msgid "End date must be a valid date."
msgstr "La fecha final tiene que ser una fecha válida." msgstr "La fecha final tiene que ser una fecha válida."
#: pkg/company/admin.go:193 #: pkg/company/admin.go:193 pkg/booking/public.go:279
msgid "Selected country is not valid." msgid "Selected country is not valid."
msgstr "El país escogido no es válido." msgstr "El país escogido no es válido."
@ -1330,11 +1466,11 @@ msgstr "No podéis dejar el NIF en blanco."
msgid "This VAT number is not valid." msgid "This VAT number is not valid."
msgstr "Este NIF no es válido." msgstr "Este NIF no es válido."
#: pkg/company/admin.go:205 #: pkg/company/admin.go:205 pkg/booking/public.go:295
msgid "Phone can not be empty." msgid "Phone can not be empty."
msgstr "No podéis dejar el teléfono en blanco." msgstr "No podéis dejar el teléfono en blanco."
#: pkg/company/admin.go:206 #: pkg/company/admin.go:206 pkg/booking/public.go:296
msgid "This phone number is not valid." msgid "This phone number is not valid."
msgstr "Este teléfono no es válido." msgstr "Este teléfono no es válido."
@ -1358,7 +1494,7 @@ msgstr "No podéis dejar la provincia en blanco."
msgid "Postal code can not be empty." msgid "Postal code can not be empty."
msgstr "No podéis dejar el código postal en blanco." msgstr "No podéis dejar el código postal en blanco."
#: pkg/company/admin.go:220 #: pkg/company/admin.go:220 pkg/booking/public.go:288
msgid "This postal code is not valid." msgid "This postal code is not valid."
msgstr "Este código postal no es válido." msgstr "Este código postal no es válido."
@ -1386,6 +1522,105 @@ msgstr "No podéis dejar el archivo del medio en blanco."
msgid "Filename can not be empty." msgid "Filename can not be empty."
msgstr "No podéis dejar el nombre del archivo en blanco." msgstr "No podéis dejar el nombre del archivo en blanco."
#: pkg/booking/public.go:283
msgid "Full name can not be empty."
msgstr "No podéis dejar el nombre y los apellidos en blanco."
#: pkg/booking/public.go:284
msgid "Full name must have at least one letter."
msgstr "El nombre y los apellidos tienen que tener como mínimo una letra."
#: pkg/booking/public.go:300
msgid "Number of adults can not be empty"
msgstr "No podéis dejar el número de adultos blanco."
#: pkg/booking/public.go:301
msgid "Number of adults must be an integer."
msgstr "El número de adultos tiene que ser entero."
#: pkg/booking/public.go:302
msgid "Number of adults must be one or greater."
msgstr "El número de adultos no puede ser cero."
#: pkg/booking/public.go:305
msgid "Number of teenagers can not be empty"
msgstr "No podéis dejar el número de adolescentes en blanco."
#: pkg/booking/public.go:306
msgid "Number of teenagers must be an integer."
msgstr "El número de adolescentes tiene que ser entero."
#: pkg/booking/public.go:307
msgid "Number of teenagers must be zero or greater."
msgstr "El número de adolescentes tiene que ser como mínimo cero."
#: pkg/booking/public.go:310
msgid "Number of children can not be empty"
msgstr "No podéis dejar el número de niños en blanco."
#: pkg/booking/public.go:311
msgid "Number of children must be an integer."
msgstr "El número de niños tiene que ser entero."
#: pkg/booking/public.go:312
msgid "Number of children must be zero or greater."
msgstr "El número de niños tiene que ser como mínimo cero."
#: pkg/booking/public.go:315
msgid "Number of dogs can not be empty"
msgstr "No podéis dejar el número de perros en blanco."
#: pkg/booking/public.go:316
msgid "Number of dogs must be an integer."
msgstr "El número de perros tiene que ser entero."
#: pkg/booking/public.go:317
msgid "Number of dogs must be zero or greater."
msgstr "El número de perros tiene que ser como mínimo cero."
#: pkg/booking/public.go:322
msgid "Arrival date can not be empty"
msgstr "No podéis dejar la fecha de llegada en blanco."
#: pkg/booking/public.go:323
msgid "Arrival date must be a valid date."
msgstr "La fecha de llegada tiene que ser una fecha válida."
#: pkg/booking/public.go:327
msgid "Departure date can not be empty"
msgstr "No podéis dejar la fecha de partida en blanco."
#: pkg/booking/public.go:328
msgid "Departure date must be a valid date."
msgstr "La fecha de partida tiene que ser una fecha válida."
#: pkg/booking/public.go:329
msgid "The departure date must be after the arrival date."
msgstr "La fecha de partida tiene que ser posterior a la de llegada."
#: pkg/booking/public.go:332
msgid "It is mandatory to agree to the reservation conditions."
msgstr "Es obligatorio aceptar las condiciones de reserva."
#: pkg/booking/public.go:335
msgid "%s can not be empty"
msgstr "No podéis dejar %s en blanco."
#: pkg/booking/public.go:336
#, c-format
msgid "%s must be an integer."
msgstr "%s tiene que ser un número entero."
#: pkg/booking/public.go:337
#, c-format
msgid "%s must be %d or greater."
msgstr "%s tiene que ser como mínimo %d."
#: pkg/booking/public.go:338
#, c-format
msgid "%s must be at most %d."
msgstr "%s tiene que ser como máximo %d"
#~ msgid "%s: %s €/night" #~ msgid "%s: %s €/night"
#~ msgstr "%s: %s €/noche" #~ msgstr "%s: %s €/noche"

View File

@ -11,7 +11,7 @@ set search_path to camper, public;
select has_table('country'); select has_table('country');
select has_pk('country' ); select has_pk('country' );
select table_privs_are('country', 'guest', array []::text[]); select table_privs_are('country', 'guest', array ['SELECT']);
select table_privs_are('country', 'employee', array ['SELECT']); select table_privs_are('country', 'employee', array ['SELECT']);
select table_privs_are('country', 'admin', array ['SELECT']); select table_privs_are('country', 'admin', array ['SELECT']);
select table_privs_are('country', 'authenticator', array []::text[]); select table_privs_are('country', 'authenticator', array []::text[]);

View File

@ -12,7 +12,7 @@ set search_path to camper, public;
select has_table('country_i18n'); select has_table('country_i18n');
select has_pk('country_i18n' ); select has_pk('country_i18n' );
select col_is_pk('country_i18n', array['country_code', 'lang_tag']); select col_is_pk('country_i18n', array['country_code', 'lang_tag']);
select table_privs_are('country_i18n', 'guest', array []::text[]); select table_privs_are('country_i18n', 'guest', array ['SELECT']);
select table_privs_are('country_i18n', 'employee', array ['SELECT']); select table_privs_are('country_i18n', 'employee', array ['SELECT']);
select table_privs_are('country_i18n', 'admin', array ['SELECT']); select table_privs_are('country_i18n', 'admin', array ['SELECT']);
select table_privs_are('country_i18n', 'authenticator', array []::text[]); select table_privs_are('country_i18n', 'authenticator', array []::text[]);

View File

@ -87,7 +87,7 @@
{{- end }} {{- end }}
{{ with .PostalCode -}} {{ with .PostalCode -}}
<label> <label>
{{( pgettext "Postal Code" "input")}}<br> {{( pgettext "Postcode" "input")}}<br>
<input type="text" name="{{ .Name }}" value="{{ .Val }}" <input type="text" name="{{ .Name }}" value="{{ .Val }}"
required autocomplete="postal-code" {{ template "error-attrs" . }}><br> required autocomplete="postal-code" {{ template "error-attrs" . }}><br>
</label> </label>

View File

@ -0,0 +1,179 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Booking" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.publicPage*/ -}}
<h2>{{( pgettext "Booking" "title" )}}</h2>
{{ with .Form -}}
<form action="/{{ currentLocale }}/booking" method="post">
<fieldset>
<legend>{{( pgettext "Customer Details" "title" )}}</legend>
{{ with .FullName -}}
<label>
{{( pgettext "Full name" "input" )}}<br>
<input type="text" required autocomplete="name" minlength="2"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .Address -}}
<label>
{{( pgettext "Address (optional)" "input" )}}<br>
<input type="text" autocomplete="billing street-address"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .PostalCode -}}
<label>
{{( pgettext "Postcode (optional)" "input" )}}<br>
<input type="text" autocomplete="billing postal-code"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .City -}}
<label>
{{( pgettext "Town or village (optional)" "input" )}}<br>
<input type="text"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .Country -}}
<label>
{{( pgettext "Country" "input" )}}<br>
<select name="{{ .Name }}"
required autocomplete="country">
<option>{{( gettext "Choose a country" )}}</option>
{{ template "error-attrs" . }}>{{ template "list-options" . }}
</select><br>
</label>
{{- end }}
{{ with .Email -}}
<label>
{{( pgettext "Email" "input" )}}<br>
<input type="email" required autocomplete="email"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .Phone -}}
<label>
{{( pgettext "Phone" "input" )}}<br>
<input type="tel" required autocomplete="tel"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
</fieldset>
<fieldset>
<legend>{{( pgettext "Party Details" "title" )}}</legend>
{{ with .Adults -}}
<label>
{{( pgettext "Adults" "input" )}}<br>
<input type="number" required min="1"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .Teenagers -}}
<label>
{{( pgettext "Teenagers (from 11 to 16 years old)" "input" )}}<br>
<input type="number" required min="0"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .Children -}}
<label>
{{( pgettext "Children (up to 10 years old)" "input" )}}<br>
<input type="number" required min="0"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .Dogs -}}
<label>
{{( pgettext "Dogs" "input" )}}<br>
<input type="number" required min="0"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
</fieldset>
<fieldset>
<legend>{{( pgettext "Accomodation" "title" )}}</legend>
{{ range .CampsiteType.Options -}}
<label><input type="radio" name="{{ $.Form.CampsiteType.Name }}" value="{{ .Value }}"
> {{ .Label }}</label><br>
{{- end }}
</fieldset>
{{ range .CampsiteType.Options -}}
{{ $options := index $.Form.CampsiteTypeOptions .Value }}
{{ if $options -}}
<fieldset>
<legend>{{ .Label }}</legend>
{{ range $options -}}
<label>
{{ .Label }}<br>
<input type="number" required
name="{{ .Input.Name }}" value="{{ .Input.Val }}"
min="{{ .Min }}" max="{{ .Max }}"
{{ template "error-attrs" .Input }}
><br>
</label>
{{- end }}
</fieldset>
{{- end }}
{{- end }}
<fieldset>
<legend>{{( pgettext "Booking Period" "title" )}}</legend>
{{ with .ArrivalDate -}}
<label>
{{( pgettext "Arrival date" "input" )}}<br>
<input type="date" required
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .DepartureDate -}}
<label>
{{( pgettext "Departure date" "input" )}}<br>
<input type="date" required
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .AreaPreferences -}}
<label>
{{( pgettext "Area preferences (optional)" "input" )}}<br>
<input type="text"
name="{{ .Name }}" value="{{ .Val }}" {{ template "error-attrs" . }}
><br>
</label>
{{- end }}
{{ with .ACSICard -}}
<label>
<input type="checkbox" name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
{{ template "error-attrs" . }}
> {{( pgettext "ACSI card? (optional)" "input" )}}</label><br>
{{- end }}
{{ with .Agreement -}}
<label>
<input type="checkbox" required name="{{ .Name }}" {{ if .Checked}}checked{{ end }}
{{ template "error-attrs" . }}
> {{( pgettext "I have read and I accept the reservation conditions" "input" )}}</label><br>
{{- end }}
</fieldset>
<footer>
<button type="submit">{{( pgettext "Book" "action" )}} <span>→</span></button>
</footer>
</form>
{{- end }}
{{- end }}

View File

@ -0,0 +1,12 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Payment Failed" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}}
<h2>{{( pgettext "Payment Failed" "title" )}}</h2>
{{- end }}

View File

@ -0,0 +1,20 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Payment" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.paymentPage*/ -}}
<h2>{{( pgettext "Payment" "title" )}}</h2>
{{ with .Request -}}
<form action="https://sis-t.redsys.es:25443/sis/realizarPago" method="post">
<input type="hidden" name="Ds_MerchantParameters" value="{{ .MerchantParameters }}"/>
<input type="hidden" name="Ds_Signature" value="{{ .Signature }}"/>
<input type="hidden" name="Ds_SignatureVersion" value="{{ .SignatureVersion }}"/>
<button type="submit">{{( pgettext "Pay" "action" )}}</button>
</form>
{{- end }}
{{- end }}

View File

@ -0,0 +1,12 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Payment Successful" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}}
<h2>{{( pgettext "Payment Successful" "title" )}}</h2>
{{- end }}