Add management of legal texts

This commit is contained in:
jordi fita mas 2023-12-22 02:23:18 +01:00
parent ee86b34e93
commit 03c20fec88
27 changed files with 1395 additions and 286 deletions

56
deploy/legal_text.sql Normal file
View File

@ -0,0 +1,56 @@
-- Deploy camper:legal_text to pg
-- requires: roles
-- requires: schema_camper
-- requires: company
-- requires: user_profile
begin;
set search_path to camper, public;
create table legal_text (
company_id integer not null references company,
slug text constraint valid_slug check(slug ~ '^[a-z0-9]+(-[a-z0-9])*$'),
name text not null constraint name_not_empty check(length(trim(name)) > 0),
content xml not null,
primary key (company_id, slug)
);
grant select on table legal_text to guest;
grant select on table legal_text to employee;
grant select, insert, update, delete on table legal_text to admin;
alter table legal_text enable row level security;
create policy guest_ok
on legal_text
for select
using (true)
;
create policy insert_to_company
on legal_text
for insert
with check (
company_id in (select company_id from user_profile)
)
;
create policy update_company
on legal_text
for update
using (
company_id in (select company_id from user_profile)
)
;
create policy delete_from_company
on legal_text
for delete
using (
company_id in (select company_id from user_profile)
)
;
commit;

View File

@ -0,0 +1,25 @@
-- Deploy camper:legal_text_i18n to pg
-- requires: roles
-- requires: schema_camper
-- requires: legal_text
-- requires: language
begin;
set search_path to camper, public;
create table legal_text_i18n (
company_id integer not null,
slug text not null,
lang_tag text not null references language,
name text not null,
content xml not null,
foreign key (company_id, slug) references legal_text,
primary key (company_id, slug, lang_tag)
);
grant select on table legal_text_i18n to guest;
grant select on table legal_text_i18n to employee;
grant select, insert, update, delete on table legal_text_i18n to admin;
commit;

View File

@ -0,0 +1,25 @@
-- Deploy camper:translate_legal_text to pg
-- requires: roles
-- requires: schema_camper
-- requires: legal_text_i18n
begin;
set search_path to camper, public;
create or replace function translate_legal_text(company_id integer, slug text, lang_tag text, name text, content text) returns void as
$$
insert into legal_text_i18n (company_id, slug, lang_tag, name, content)
values (company_id, slug, lang_tag, coalesce(name, ''), xmlparse(content coalesce(content, '')))
on conflict (company_id, slug, lang_tag) do update
set name = excluded.name
, content = excluded.content
;
$$
language sql
;
revoke execute on function translate_legal_text(integer, text, text, text, text) from public;
grant execute on function translate_legal_text(integer, text, text, text, text) to admin;
commit;

View File

@ -6,7 +6,6 @@
package app
import (
"dev.tandem.ws/tandem/camper/pkg/location"
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
@ -16,7 +15,9 @@ import (
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/home"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/legal"
"dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/location"
"dev.tandem.ws/tandem/camper/pkg/media"
"dev.tandem.ws/tandem/camper/pkg/season"
"dev.tandem.ws/tandem/camper/pkg/services"
@ -27,6 +28,7 @@ type adminHandler struct {
campsite *campsite.AdminHandler
company *company.AdminHandler
home *home.AdminHandler
legal *legal.AdminHandler
location *location.AdminHandler
media *media.AdminHandler
payment *booking.AdminHandler
@ -39,6 +41,7 @@ func newAdminHandler(locales locale.Locales, mediaDir string) *adminHandler {
campsite: campsite.NewAdminHandler(locales),
company: company.NewAdminHandler(),
home: home.NewAdminHandler(locales),
legal: legal.NewAdminHandler(),
location: location.NewAdminHandler(),
media: media.NewAdminHandler(mediaDir),
payment: booking.NewAdminHandler(),
@ -69,6 +72,8 @@ func (h *adminHandler) Handle(user *auth.User, company *auth.Company, conn *data
h.company.Handler(user, company, conn).ServeHTTP(w, r)
case "home":
h.home.Handler(user, company, conn).ServeHTTP(w, r)
case "legal":
h.legal.Handler(user, company, conn).ServeHTTP(w, r)
case "location":
h.location.Handler(user, company, conn).ServeHTTP(w, r)
case "media":

View File

@ -6,6 +6,7 @@
package app
import (
"dev.tandem.ws/tandem/camper/pkg/legal"
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
@ -23,6 +24,7 @@ type publicHandler struct {
home *home.PublicHandler
booking *booking.PublicHandler
campsite *campsite.PublicHandler
legal *legal.PublicHandler
location *location.PublicHandler
services *services.PublicHandler
}
@ -32,6 +34,7 @@ func newPublicHandler() *publicHandler {
home: home.NewPublicHandler(),
booking: booking.NewPublicHandler(),
campsite: campsite.NewPublicHandler(),
legal: legal.NewPublicHandler(),
location: location.NewPublicHandler(),
services: services.NewPublicHandler(),
}
@ -50,6 +53,8 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da
campgroundHandler(user, company, conn).ServeHTTP(w, r)
case "campsites":
h.campsite.Handler(user, company, conn).ServeHTTP(w, r)
case "legal":
h.legal.Handler(user, company, conn).ServeHTTP(w, r)
case "location":
h.location.Handler(user, company, conn).ServeHTTP(w, r)
case "services":

View File

@ -102,3 +102,8 @@ func (tx *Tx) TranslateLocation(ctx context.Context, companyID int, langTag lang
_, err := tx.Exec(ctx, "select translate_location($1, $2, $3, $4)", companyID, langTag, directions, openingHours)
return err
}
func (tx *Tx) TranslateLegalText(ctx context.Context, companyID int, slug string, langTag language.Tag, name string, content string) error {
_, err := tx.Exec(ctx, "select translate_legal_text($1, $2, $3, $4, $5)", companyID, slug, langTag, name, content)
return err
}

View File

@ -7,10 +7,14 @@ package form
import (
"database/sql/driver"
"dev.tandem.ws/tandem/camper/pkg/locale"
"net/http"
"strconv"
"strings"
"golang.org/x/text/language"
"dev.tandem.ws/tandem/camper/pkg/database"
"dev.tandem.ws/tandem/camper/pkg/locale"
)
type Input struct {
@ -69,3 +73,14 @@ func (input I18nInput) FillValue(r *http.Request) {
inner.FillValue(r)
}
}
func (input I18nInput) FillArray(array database.RecordArray) error {
for _, el := range array.Elements {
tag, err := language.Parse(el.Fields[0].Get().(string))
if err != nil {
return err
}
input[tag.String()].Val = el.Fields[1].Get().(string)
}
return nil
}

263
pkg/legal/admin.go Normal file
View File

@ -0,0 +1,263 @@
package legal
import (
"context"
"net/http"
"github.com/jackc/pgx/v4"
"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/template"
)
type AdminHandler struct {
}
func NewAdminHandler() *AdminHandler {
return &AdminHandler{}
}
func (h *AdminHandler) 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:
serveLegalIndex(w, r, user, company, conn)
case http.MethodPost:
addLegal(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
case "new":
switch r.Method {
case http.MethodGet:
f := newLegalForm(company)
f.MustRender(w, r, user, company)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
f := newLegalForm(company)
if err := f.FillFromDatabase(r.Context(), conn, head); err != nil {
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
return
}
panic(err)
}
h.legalHandler(user, company, conn, f).ServeHTTP(w, r)
}
})
}
func (h *AdminHandler) legalHandler(user *auth.User, company *auth.Company, conn *database.Conn, f *legalForm) 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:
f.MustRender(w, r, user, company)
case http.MethodPut:
editLegal(w, r, user, company, conn, f)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
}
default:
http.NotFound(w, r)
}
})
}
func serveLegalIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
legals, err := collectLegalEntries(r.Context(), conn, company)
if err != nil {
panic(err)
}
page := &legalIndex{
Texts: legals,
}
page.MustRender(w, r, user, company)
}
func collectLegalEntries(ctx context.Context, conn *database.Conn, company *auth.Company) ([]*legalEntry, error) {
rows, err := conn.Query(ctx, `
select '/admin/legal/' || slug
, name
from legal_text
where company_id = $1
order by name
`, company.ID)
if err != nil {
return nil, err
}
defer rows.Close()
var legals []*legalEntry
for rows.Next() {
legal := &legalEntry{}
if err = rows.Scan(&legal.URL, &legal.Name); err != nil {
return nil, err
}
legals = append(legals, legal)
}
return legals, nil
}
type legalEntry struct {
URL string
Name string
}
type legalIndex struct {
Texts []*legalEntry
}
func (page *legalIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "legal/index.gohtml", page)
}
func addLegal(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
f := newLegalForm(company)
processLegalForm(w, r, user, company, conn, f, func(ctx context.Context, tx *database.Tx) error {
_, err := tx.Exec(ctx, `
insert into legal_text (company_id, slug, name, content)
values ($1, $2, $3, xmlparse(content $4))
`, company.ID, f.Slug.Val, f.Name[company.DefaultLanguage.String()].Val, f.Content[company.DefaultLanguage.String()])
if err != nil {
return err
}
for lang := range company.Locales {
if err := tx.TranslateLegalText(ctx, company.ID, f.Slug.Val, lang, f.Name[lang.String()].Val, f.Content[lang.String()].Val); err != nil {
return err
}
}
return nil
})
}
func editLegal(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *legalForm) {
processLegalForm(w, r, user, company, conn, f, func(ctx context.Context, tx *database.Tx) error {
_, err := tx.Exec(ctx, `
update legal_text
set name = $3
, content = xmlparse(content $4)
where company_id = $1
and slug = $2
`, company.ID, f.Slug.Val, f.Name[company.DefaultLanguage.String()].Val, f.Content[company.DefaultLanguage.String()])
if err != nil {
return err
}
for lang := range company.Locales {
if err := tx.TranslateLegalText(ctx, company.ID, f.Slug.Val, lang, f.Name[lang.String()].Val, f.Content[lang.String()].Val); err != nil {
return err
}
}
return nil
})
}
func processLegalForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, f *legalForm, act func(ctx context.Context, tx *database.Tx) error) {
if ok, err := form.Handle(f, w, r, user); err != nil {
return
} else if !ok {
f.MustRender(w, r, user, company)
return
}
tx := conn.MustBegin(r.Context())
if err := act(r.Context(), tx); err == nil {
if err := tx.Commit(r.Context()); err != nil {
panic(err)
}
} else {
if err := tx.Rollback(r.Context()); err != nil {
panic(err)
}
panic(err)
}
httplib.Redirect(w, r, "/admin/legal/"+f.Slug.Val, http.StatusSeeOther)
}
type legalForm struct {
company *auth.Company
URL string
Slug *form.Input
Name form.I18nInput
Content form.I18nInput
}
func newLegalForm(company *auth.Company) *legalForm {
f := &legalForm{
company: company,
Slug: &form.Input{
Name: "slug",
},
Name: form.NewI18nInput(company.Locales, "name"),
Content: form.NewI18nInput(company.Locales, "content"),
}
return f
}
func (f *legalForm) FillFromDatabase(ctx context.Context, conn *database.Conn, slug string) error {
var name database.RecordArray
var content database.RecordArray
row := conn.QueryRow(ctx, `
select '/admin/legal/' || text.slug
, text.slug
, text.name
, text.content::text
, array_agg((lang_tag, i18n.name))
, array_agg((lang_tag, i18n.content::text))
from legal_text as text
left join legal_text_i18n as i18n using (company_id, slug)
where text.company_id = $1
and text.slug = $2
group by text.slug
, text.name
, text.content::text
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, f.company.ID, slug)
if err := row.Scan(&f.URL, &f.Slug.Val, &f.Name[f.company.DefaultLanguage.String()].Val, &f.Content[f.company.DefaultLanguage.String()].Val, &name, &content); err != nil {
return err
}
if err := f.Name.FillArray(name); err != nil {
return err
}
if err := f.Content.FillArray(content); err != nil {
return err
}
return nil
}
func (f *legalForm) Parse(r *http.Request) error {
if err := r.ParseForm(); err != nil {
return err
}
f.Slug.FillValue(r)
f.Name.FillValue(r)
f.Content.FillValue(r)
return nil
}
func (f *legalForm) Valid(l *locale.Locale) bool {
v := form.NewValidator(l)
if v.CheckRequired(f.Name[f.company.DefaultLanguage.String()], l.GettextNoop("Name can not be empty.")) {
v.CheckMinLength(f.Name[f.company.DefaultLanguage.String()], 1, l.GettextNoop("Name must have at least one letter."))
}
return v.AllOK
}
func (f *legalForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "legal/form.gohtml", f)
}

78
pkg/legal/public.go Normal file
View File

@ -0,0 +1,78 @@
package legal
import (
"context"
"dev.tandem.ws/tandem/camper/pkg/locale"
gotemplate "html/template"
"net/http"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/template"
)
type PublicHandler struct {
}
func NewPublicHandler() *PublicHandler {
return &PublicHandler{}
}
func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var slug string
slug, r.URL.Path = httplib.ShiftPath(r.URL.Path)
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page, err := newLegalPage(r.Context(), company, conn, user.Locale, slug)
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
return
} else if err != nil {
panic(err)
}
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
http.NotFound(w, r)
}
})
}
type legalPage struct {
*template.PublicPage
Name string
Content gotemplate.HTML
}
func newLegalPage(ctx context.Context, company *auth.Company, conn *database.Conn, loc *locale.Locale, slug string) (*legalPage, error) {
page := &legalPage{
PublicPage: template.NewPublicPage(),
}
row := conn.QueryRow(ctx, `
select coalesce(i18n.name, text.name) as l10n_name
, coalesce(i18n.content, text.content)::text as l10n_description
from legal_text as text
left join legal_text_i18n as i18n on text.company_id = i18n.company_id and text.slug = i18n.slug and i18n.lang_tag = $1
where text.company_id = $2
and text.slug = $3
`, loc.Language, company.ID, slug)
if err := row.Scan(&page.Name, &page.Content); err != nil {
return nil, err
}
return page, nil
}
func (p *legalPage) 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, "legal.gohtml", p)
}

View File

@ -2,7 +2,6 @@ package location
import (
"context"
"golang.org/x/text/language"
"net/http"
"github.com/jackc/pgx/v4"
@ -116,26 +115,15 @@ func (f *locationForm) FillFromDatabase(ctx context.Context, company *auth.Compa
if err != nil {
return err
}
if err := fillI18nInput(f.Directions, directions); err != nil {
if err := f.Directions.FillArray(directions); err != nil {
return err
}
if err := fillI18nInput(f.OpeningDates, openingDates); err != nil {
if err := f.OpeningDates.FillArray(openingDates); err != nil {
return err
}
return nil
}
func fillI18nInput(input form.I18nInput, array database.RecordArray) error {
for _, el := range array.Elements {
tag, err := language.Parse(el.Fields[0].Get().(string))
if err != nil {
return err
}
input[tag.String()].Val = el.Fields[1].Get().(string)
}
return nil
}
func (f *locationForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
template.MustRenderAdmin(w, r, user, company, "location.gohtml", f)
}

218
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-12-21 21:08+0100\n"
"POT-Creation-Date: 2023-12-22 02:19+0100\n"
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -43,7 +43,7 @@ msgstr "Ha fallat el pagament"
#: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:51 web/templates/public/layout.gohtml:79
#: web/templates/public/layout.gohtml:66 web/templates/public/layout.gohtml:94
#: web/templates/admin/services/index.gohtml:66
msgctxt "title"
msgid "Services"
@ -55,14 +55,14 @@ msgstr "El càmping disposa de diversos serveis."
#: web/templates/public/location.gohtml:6
#: web/templates/public/location.gohtml:12
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:81
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/admin/layout.gohtml:60
msgctxt "title"
msgid "Location"
msgstr "Com arribar"
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:37
#: web/templates/public/layout.gohtml:77
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:52
#: web/templates/public/layout.gohtml:92
msgctxt "title"
msgid "Home"
msgstr "Inici"
@ -87,7 +87,7 @@ msgstr "Vine a gaudir!"
#: web/templates/public/home.gohtml:35
#: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:10
#: web/templates/public/layout.gohtml:52 web/templates/public/layout.gohtml:80
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
msgctxt "title"
msgid "Surroundings"
msgstr "Lentorn"
@ -271,13 +271,13 @@ 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:11
#: web/templates/public/layout.gohtml:38 web/templates/public/layout.gohtml:78
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:93
msgctxt "title"
msgid "Campground"
msgstr "El càmping"
#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:54
#: web/templates/public/layout.gohtml:69
msgctxt "title"
msgid "Booking"
msgstr "Reserva"
@ -365,8 +365,8 @@ 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:32
#: web/templates/public/layout.gohtml:109
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:47
#: web/templates/public/layout.gohtml:130
msgid "Campsite Montagut"
msgstr "Càmping Montagut"
@ -374,28 +374,118 @@ msgstr "Càmping Montagut"
msgid "Skip to main content"
msgstr "Salta al contingut principal"
#: web/templates/public/layout.gohtml:42 web/templates/public/layout.gohtml:88
#: web/templates/public/layout.gohtml:57 web/templates/public/layout.gohtml:103
#: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:45 web/templates/admin/layout.gohtml:79
#: web/templates/admin/layout.gohtml:45 web/templates/admin/layout.gohtml:82
msgctxt "title"
msgid "Campsites"
msgstr "Allotjaments"
#: web/templates/public/layout.gohtml:75
#: web/templates/public/layout.gohtml:90
msgctxt "title"
msgid "Sections"
msgstr "Apartats"
#: web/templates/public/layout.gohtml:99
#: web/templates/public/layout.gohtml:114
msgctxt "title"
msgid "Opening"
msgstr "Obertura"
#: web/templates/public/layout.gohtml:106
#: web/templates/public/layout.gohtml:121
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"
#: web/templates/admin/legal/form.gohtml:8
#: web/templates/admin/legal/form.gohtml:25
msgctxt "title"
msgid "Edit Legal Text"
msgstr "Edició del text legal"
#: web/templates/admin/legal/form.gohtml:10
#: web/templates/admin/legal/form.gohtml:27
msgctxt "title"
msgid "New Legal Text"
msgstr "Nou text legal"
#: web/templates/admin/legal/form.gohtml:37
msgctxt "input"
msgid "Slug"
msgstr "Àlies"
#: web/templates/admin/legal/form.gohtml:46
#: web/templates/admin/campsite/feature/form.gohtml:52
#: web/templates/admin/campsite/feature/l10n.gohtml:20
#: web/templates/admin/campsite/option/form.gohtml:34
#: web/templates/admin/campsite/option/l10n.gohtml:20
#: web/templates/admin/campsite/type/form.gohtml:46
#: web/templates/admin/campsite/type/l10n.gohtml:20
#: web/templates/admin/season/form.gohtml:46
#: web/templates/admin/season/l10n.gohtml:20
#: web/templates/admin/services/form.gohtml:52
#: web/templates/admin/services/l10n.gohtml:20
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/legal/form.gohtml:64
msgctxt "input"
msgid "Content"
msgstr "Contingut"
#: web/templates/admin/legal/form.gohtml:84
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/feature/form.gohtml:62
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:129
#: web/templates/admin/season/form.gohtml:64
#: web/templates/admin/services/form.gohtml:69
#: web/templates/admin/media/form.gohtml:35
msgctxt "action"
msgid "Update"
msgstr "Actualitza"
#: web/templates/admin/legal/form.gohtml:86
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/feature/form.gohtml:64
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:131
#: web/templates/admin/season/form.gohtml:66
#: web/templates/admin/services/form.gohtml:71
msgctxt "action"
msgid "Add"
msgstr "Afegeix"
#: web/templates/admin/legal/index.gohtml:6
#: web/templates/admin/legal/index.gohtml:12
#: web/templates/admin/layout.gohtml:63
msgctxt "title"
msgid "Legal Texts"
msgstr "Texts legals"
#: web/templates/admin/legal/index.gohtml:11
msgctxt "action"
msgid "Add Legal Text"
msgstr "Afegeix text legal"
#: web/templates/admin/legal/index.gohtml:17
#: web/templates/admin/campsite/feature/index.gohtml:26
#: web/templates/admin/campsite/option/index.gohtml:25
#: web/templates/admin/campsite/type/index.gohtml:25
#: web/templates/admin/season/index.gohtml:26
msgctxt "header"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/legal/index.gohtml:29
msgid "No legal texts added yet."
msgstr "No sha afegit cap text legal encara."
#: web/templates/admin/carousel/form.gohtml:8
#: web/templates/admin/carousel/form.gohtml:25
msgctxt "title"
@ -416,31 +506,6 @@ msgctxt "input"
msgid "Caption"
msgstr "Llegenda"
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/feature/form.gohtml:62
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:129
#: web/templates/admin/season/form.gohtml:64
#: web/templates/admin/services/form.gohtml:69
#: web/templates/admin/media/form.gohtml:35
msgctxt "action"
msgid "Update"
msgstr "Actualitza"
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/feature/form.gohtml:64
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:131
#: web/templates/admin/season/form.gohtml:66
#: web/templates/admin/services/form.gohtml:71
msgctxt "action"
msgid "Add"
msgstr "Afegeix"
#: web/templates/admin/carousel/l10n.gohtml:7
#: web/templates/admin/carousel/l10n.gohtml:14
msgctxt "title"
@ -534,21 +599,6 @@ msgctxt "input"
msgid "Icon"
msgstr "Icona"
#: web/templates/admin/campsite/feature/form.gohtml:52
#: web/templates/admin/campsite/feature/l10n.gohtml:20
#: web/templates/admin/campsite/option/form.gohtml:34
#: web/templates/admin/campsite/option/l10n.gohtml:20
#: web/templates/admin/campsite/type/form.gohtml:46
#: web/templates/admin/campsite/type/l10n.gohtml:20
#: web/templates/admin/season/form.gohtml:46
#: web/templates/admin/season/l10n.gohtml:20
#: web/templates/admin/services/form.gohtml:52
#: web/templates/admin/services/l10n.gohtml:20
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/campsite/feature/index.gohtml:6
#: web/templates/admin/campsite/feature/index.gohtml:12
msgctxt "title"
@ -560,14 +610,6 @@ msgctxt "action"
msgid "Add Feature"
msgstr "Afegeix característica"
#: web/templates/admin/campsite/feature/index.gohtml:26
#: web/templates/admin/campsite/option/index.gohtml:25
#: web/templates/admin/campsite/type/index.gohtml:25
#: web/templates/admin/season/index.gohtml:26
msgctxt "header"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/campsite/feature/index.gohtml:27
#: web/templates/admin/campsite/carousel/index.gohtml:27
#: web/templates/admin/campsite/option/index.gohtml:26
@ -979,7 +1021,7 @@ msgid "Integration"
msgstr "Integració"
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:76
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:79
msgctxt "title"
msgid "Dashboard"
msgstr "Tauler"
@ -1162,7 +1204,7 @@ msgctxt "title"
msgid "Home Page"
msgstr "Pàgina dinici"
#: web/templates/admin/layout.gohtml:65
#: web/templates/admin/layout.gohtml:68
msgctxt "action"
msgid "Logout"
msgstr "Surt"
@ -1232,6 +1274,19 @@ msgctxt "title"
msgid "Upload Media"
msgstr "Pujada de mèdia"
#: pkg/legal/admin.go:255 pkg/app/user.go:249 pkg/campsite/types/l10n.go:87
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:350 pkg/campsite/types/feature.go:253
#: pkg/campsite/types/admin.go:447 pkg/season/l10n.go:69
#: pkg/season/admin.go:405 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/legal/admin.go:256 pkg/campsite/types/option.go:351
#: pkg/campsite/types/feature.go:254 pkg/campsite/types/admin.go:448
msgid "Name must have at least one letter."
msgstr "El nom ha de tenir com a mínim una lletra."
#: pkg/carousel/admin.go:285 pkg/campsite/types/carousel.go:242
msgctxt "input"
msgid "Slide image"
@ -1273,14 +1328,6 @@ msgctxt "language option"
msgid "Automatic"
msgstr "Automàtic"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:87
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:350 pkg/campsite/types/feature.go:253
#: pkg/campsite/types/admin.go:447 pkg/season/l10n.go:69
#: pkg/season/admin.go:404 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "No podeu deixar el nom en blanc."
#: pkg/app/user.go:250
msgid "Confirmation does not match password."
msgstr "La confirmació no es correspon amb la contrasenya."
@ -1293,15 +1340,10 @@ msgstr "Lidioma escollit no és vàlid."
msgid "File must be a valid PNG or JPEG image."
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."
#: pkg/app/admin.go:59
#: pkg/app/admin.go:62
msgid "Access forbidden"
msgstr "Accés prohibit"
#: pkg/campsite/types/option.go:351 pkg/campsite/types/feature.go:254
#: pkg/campsite/types/admin.go:448
msgid "Name must have at least one letter."
msgstr "El nom ha de tenir com a mínim una lletra."
#: pkg/campsite/types/option.go:354
msgid "Minimum can not be empty."
msgstr "No podeu deixar el mínim en blanc."
@ -1457,32 +1499,32 @@ msgctxt "month"
msgid "December"
msgstr "desembre"
#: pkg/season/admin.go:405
#: pkg/season/admin.go:406
msgid "Color can not be empty."
msgstr "No podeu deixar el color en blanc."
#: pkg/season/admin.go:406
#: pkg/season/admin.go:407
msgid "This color is not valid. It must be like #123abc."
msgstr "Aquest color no és vàlid. Hauria de ser similar a #123abc."
#: pkg/season/admin.go:506
#: pkg/season/admin.go:507
msgctxt "action"
msgid "Unset"
msgstr "Desassigna"
#: pkg/season/admin.go:537
#: pkg/season/admin.go:538
msgid "Start date can not be empty."
msgstr "No podeu deixar la data dinici en blanc."
#: pkg/season/admin.go:538
#: pkg/season/admin.go:539
msgid "Start date must be a valid date."
msgstr "La data dinici ha de ser una data vàlida."
#: pkg/season/admin.go:540
#: pkg/season/admin.go:541
msgid "End date can not be empty."
msgstr "No podeu deixar la data de fi en blanc."
#: pkg/season/admin.go:541
#: pkg/season/admin.go:542
msgid "End date must be a valid date."
msgstr "La data de fi ha de ser una data vàlida."
@ -1666,10 +1708,6 @@ msgstr "El valor de %s ha de ser com a mínim %d."
msgid "%s must be at most %d."
msgstr "El valor de %s ha de ser com a màxim %d."
#~ msgctxt "title"
#~ msgid "Contact"
#~ msgstr "Contacte"
#~ msgctxt "title"
#~ msgid "Party Details"
#~ msgstr "Dades dels visitants"

218
po/es.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-12-21 21:08+0100\n"
"POT-Creation-Date: 2023-12-22 02:19+0100\n"
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -43,7 +43,7 @@ msgstr "Pago fallido"
#: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:51 web/templates/public/layout.gohtml:79
#: web/templates/public/layout.gohtml:66 web/templates/public/layout.gohtml:94
#: web/templates/admin/services/index.gohtml:66
msgctxt "title"
msgid "Services"
@ -55,14 +55,14 @@ msgstr "El camping dispone de varios servicios."
#: web/templates/public/location.gohtml:6
#: web/templates/public/location.gohtml:12
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:81
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/admin/layout.gohtml:60
msgctxt "title"
msgid "Location"
msgstr "Cómo llegar"
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:37
#: web/templates/public/layout.gohtml:77
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:52
#: web/templates/public/layout.gohtml:92
msgctxt "title"
msgid "Home"
msgstr "Inicio"
@ -87,7 +87,7 @@ msgstr "¡Ven a disfrutar!"
#: web/templates/public/home.gohtml:35
#: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:10
#: web/templates/public/layout.gohtml:52 web/templates/public/layout.gohtml:80
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
msgctxt "title"
msgid "Surroundings"
msgstr "El entorno"
@ -271,13 +271,13 @@ 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:11
#: web/templates/public/layout.gohtml:38 web/templates/public/layout.gohtml:78
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:93
msgctxt "title"
msgid "Campground"
msgstr "El camping"
#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:54
#: web/templates/public/layout.gohtml:69
msgctxt "title"
msgid "Booking"
msgstr "Reserva"
@ -365,8 +365,8 @@ 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:32
#: web/templates/public/layout.gohtml:109
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:47
#: web/templates/public/layout.gohtml:130
msgid "Campsite Montagut"
msgstr "Camping Montagut"
@ -374,28 +374,118 @@ msgstr "Camping Montagut"
msgid "Skip to main content"
msgstr "Saltar al contenido principal"
#: web/templates/public/layout.gohtml:42 web/templates/public/layout.gohtml:88
#: web/templates/public/layout.gohtml:57 web/templates/public/layout.gohtml:103
#: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:45 web/templates/admin/layout.gohtml:79
#: web/templates/admin/layout.gohtml:45 web/templates/admin/layout.gohtml:82
msgctxt "title"
msgid "Campsites"
msgstr "Alojamientos"
#: web/templates/public/layout.gohtml:75
#: web/templates/public/layout.gohtml:90
msgctxt "title"
msgid "Sections"
msgstr "Apartados"
#: web/templates/public/layout.gohtml:99
#: web/templates/public/layout.gohtml:114
msgctxt "title"
msgid "Opening"
msgstr "Apertura"
#: web/templates/public/layout.gohtml:106
#: web/templates/public/layout.gohtml:121
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"
#: web/templates/admin/legal/form.gohtml:8
#: web/templates/admin/legal/form.gohtml:25
msgctxt "title"
msgid "Edit Legal Text"
msgstr "Edición del texto legal"
#: web/templates/admin/legal/form.gohtml:10
#: web/templates/admin/legal/form.gohtml:27
msgctxt "title"
msgid "New Legal Text"
msgstr "Nuevo texto legal"
#: web/templates/admin/legal/form.gohtml:37
msgctxt "input"
msgid "Slug"
msgstr "Álias"
#: web/templates/admin/legal/form.gohtml:46
#: web/templates/admin/campsite/feature/form.gohtml:52
#: web/templates/admin/campsite/feature/l10n.gohtml:20
#: web/templates/admin/campsite/option/form.gohtml:34
#: web/templates/admin/campsite/option/l10n.gohtml:20
#: web/templates/admin/campsite/type/form.gohtml:46
#: web/templates/admin/campsite/type/l10n.gohtml:20
#: web/templates/admin/season/form.gohtml:46
#: web/templates/admin/season/l10n.gohtml:20
#: web/templates/admin/services/form.gohtml:52
#: web/templates/admin/services/l10n.gohtml:20
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: web/templates/admin/legal/form.gohtml:64
msgctxt "input"
msgid "Content"
msgstr "Contenido"
#: web/templates/admin/legal/form.gohtml:84
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/feature/form.gohtml:62
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:129
#: web/templates/admin/season/form.gohtml:64
#: web/templates/admin/services/form.gohtml:69
#: web/templates/admin/media/form.gohtml:35
msgctxt "action"
msgid "Update"
msgstr "Actualizar"
#: web/templates/admin/legal/form.gohtml:86
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/feature/form.gohtml:64
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:131
#: web/templates/admin/season/form.gohtml:66
#: web/templates/admin/services/form.gohtml:71
msgctxt "action"
msgid "Add"
msgstr "Añadir"
#: web/templates/admin/legal/index.gohtml:6
#: web/templates/admin/legal/index.gohtml:12
#: web/templates/admin/layout.gohtml:63
msgctxt "title"
msgid "Legal Texts"
msgstr "Textos legales"
#: web/templates/admin/legal/index.gohtml:11
msgctxt "action"
msgid "Add Legal Text"
msgstr "Añadir texto legal"
#: web/templates/admin/legal/index.gohtml:17
#: web/templates/admin/campsite/feature/index.gohtml:26
#: web/templates/admin/campsite/option/index.gohtml:25
#: web/templates/admin/campsite/type/index.gohtml:25
#: web/templates/admin/season/index.gohtml:26
msgctxt "header"
msgid "Name"
msgstr "Nombre"
#: web/templates/admin/legal/index.gohtml:29
msgid "No legal texts added yet."
msgstr "No se ha añadido ningún texto legal todavía."
#: web/templates/admin/carousel/form.gohtml:8
#: web/templates/admin/carousel/form.gohtml:25
msgctxt "title"
@ -416,31 +506,6 @@ msgctxt "input"
msgid "Caption"
msgstr "Leyenda"
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/feature/form.gohtml:62
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:129
#: web/templates/admin/season/form.gohtml:64
#: web/templates/admin/services/form.gohtml:69
#: web/templates/admin/media/form.gohtml:35
msgctxt "action"
msgid "Update"
msgstr "Actualizar"
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/feature/form.gohtml:64
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:131
#: web/templates/admin/season/form.gohtml:66
#: web/templates/admin/services/form.gohtml:71
msgctxt "action"
msgid "Add"
msgstr "Añadir"
#: web/templates/admin/carousel/l10n.gohtml:7
#: web/templates/admin/carousel/l10n.gohtml:14
msgctxt "title"
@ -534,21 +599,6 @@ msgctxt "input"
msgid "Icon"
msgstr "Icono"
#: web/templates/admin/campsite/feature/form.gohtml:52
#: web/templates/admin/campsite/feature/l10n.gohtml:20
#: web/templates/admin/campsite/option/form.gohtml:34
#: web/templates/admin/campsite/option/l10n.gohtml:20
#: web/templates/admin/campsite/type/form.gohtml:46
#: web/templates/admin/campsite/type/l10n.gohtml:20
#: web/templates/admin/season/form.gohtml:46
#: web/templates/admin/season/l10n.gohtml:20
#: web/templates/admin/services/form.gohtml:52
#: web/templates/admin/services/l10n.gohtml:20
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nombre"
#: web/templates/admin/campsite/feature/index.gohtml:6
#: web/templates/admin/campsite/feature/index.gohtml:12
msgctxt "title"
@ -560,14 +610,6 @@ msgctxt "action"
msgid "Add Feature"
msgstr "Añadir características"
#: web/templates/admin/campsite/feature/index.gohtml:26
#: web/templates/admin/campsite/option/index.gohtml:25
#: web/templates/admin/campsite/type/index.gohtml:25
#: web/templates/admin/season/index.gohtml:26
msgctxt "header"
msgid "Name"
msgstr "Nombre"
#: web/templates/admin/campsite/feature/index.gohtml:27
#: web/templates/admin/campsite/carousel/index.gohtml:27
#: web/templates/admin/campsite/option/index.gohtml:26
@ -979,7 +1021,7 @@ msgid "Integration"
msgstr "Integración"
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:76
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:79
msgctxt "title"
msgid "Dashboard"
msgstr "Panel"
@ -1162,7 +1204,7 @@ msgctxt "title"
msgid "Home Page"
msgstr "Página de inicio"
#: web/templates/admin/layout.gohtml:65
#: web/templates/admin/layout.gohtml:68
msgctxt "action"
msgid "Logout"
msgstr "Salir"
@ -1232,6 +1274,19 @@ msgctxt "title"
msgid "Upload Media"
msgstr "Subida de medio"
#: pkg/legal/admin.go:255 pkg/app/user.go:249 pkg/campsite/types/l10n.go:87
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:350 pkg/campsite/types/feature.go:253
#: pkg/campsite/types/admin.go:447 pkg/season/l10n.go:69
#: pkg/season/admin.go:405 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/legal/admin.go:256 pkg/campsite/types/option.go:351
#: pkg/campsite/types/feature.go:254 pkg/campsite/types/admin.go:448
msgid "Name must have at least one letter."
msgstr "El nombre tiene que tener como mínimo una letra."
#: pkg/carousel/admin.go:285 pkg/campsite/types/carousel.go:242
msgctxt "input"
msgid "Slide image"
@ -1273,14 +1328,6 @@ msgctxt "language option"
msgid "Automatic"
msgstr "Automático"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:87
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:350 pkg/campsite/types/feature.go:253
#: pkg/campsite/types/admin.go:447 pkg/season/l10n.go:69
#: pkg/season/admin.go:404 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "No podéis dejar el nombre en blanco."
#: pkg/app/user.go:250
msgid "Confirmation does not match password."
msgstr "La confirmación no se corresponde con la contraseña."
@ -1293,15 +1340,10 @@ msgstr "El idioma escogido no es válido."
msgid "File must be a valid PNG or JPEG image."
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."
#: pkg/app/admin.go:59
#: pkg/app/admin.go:62
msgid "Access forbidden"
msgstr "Acceso prohibido"
#: pkg/campsite/types/option.go:351 pkg/campsite/types/feature.go:254
#: pkg/campsite/types/admin.go:448
msgid "Name must have at least one letter."
msgstr "El nombre tiene que tener como mínimo una letra."
#: pkg/campsite/types/option.go:354
msgid "Minimum can not be empty."
msgstr "No podéis dejar el mínimo en blanco."
@ -1457,32 +1499,32 @@ msgctxt "month"
msgid "December"
msgstr "diciembre"
#: pkg/season/admin.go:405
#: pkg/season/admin.go:406
msgid "Color can not be empty."
msgstr "No podéis dejar el color en blanco."
#: pkg/season/admin.go:406
#: pkg/season/admin.go:407
msgid "This color is not valid. It must be like #123abc."
msgstr "Este color no es válido. Tiene que ser parecido a #123abc."
#: pkg/season/admin.go:506
#: pkg/season/admin.go:507
msgctxt "action"
msgid "Unset"
msgstr "Desasignar"
#: pkg/season/admin.go:537
#: pkg/season/admin.go:538
msgid "Start date can not be empty."
msgstr "No podéis dejar la fecha de inicio en blanco."
#: pkg/season/admin.go:538
#: pkg/season/admin.go:539
msgid "Start date must be a valid date."
msgstr "La fecha de inicio tiene que ser una fecha válida."
#: pkg/season/admin.go:540
#: pkg/season/admin.go:541
msgid "End date can not be empty."
msgstr "No podéis dejar la fecha final en blanco."
#: pkg/season/admin.go:541
#: pkg/season/admin.go:542
msgid "End date must be a valid date."
msgstr "La fecha final tiene que ser una fecha válida."
@ -1666,10 +1708,6 @@ msgstr "%s tiene que ser como mínimo %d."
msgid "%s must be at most %d."
msgstr "%s tiene que ser como máximo %d"
#~ msgctxt "title"
#~ msgid "Contact"
#~ msgstr "Contacto"
#~ msgctxt "title"
#~ msgid "Party Details"
#~ msgstr "Datos de los visitantes"

220
po/fr.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2023-12-21 21:08+0100\n"
"POT-Creation-Date: 2023-12-22 02:19+0100\n"
"PO-Revision-Date: 2023-12-20 10:13+0100\n"
"Last-Translator: Oriol Carbonell <info@oriolcarbonell.cat>\n"
"Language-Team: French <traduc@traduc.org>\n"
@ -44,7 +44,7 @@ msgstr "Le paiement a échoué"
#: web/templates/public/services.gohtml:6
#: web/templates/public/services.gohtml:15
#: web/templates/public/layout.gohtml:51 web/templates/public/layout.gohtml:79
#: web/templates/public/layout.gohtml:66 web/templates/public/layout.gohtml:94
#: web/templates/admin/services/index.gohtml:66
msgctxt "title"
msgid "Services"
@ -56,14 +56,14 @@ msgstr "Le camping propose de nombreux services différents."
#: web/templates/public/location.gohtml:6
#: web/templates/public/location.gohtml:12
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:81
#: web/templates/public/layout.gohtml:68 web/templates/public/layout.gohtml:96
#: web/templates/admin/layout.gohtml:60
msgctxt "title"
msgid "Location"
msgstr "Comment nous rejoindre"
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:37
#: web/templates/public/layout.gohtml:77
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:52
#: web/templates/public/layout.gohtml:92
msgctxt "title"
msgid "Home"
msgstr "Accueil"
@ -88,7 +88,7 @@ msgstr "Venez et profitez-en !"
#: web/templates/public/home.gohtml:35
#: web/templates/public/surroundings.gohtml:6
#: web/templates/public/surroundings.gohtml:10
#: web/templates/public/layout.gohtml:52 web/templates/public/layout.gohtml:80
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
msgctxt "title"
msgid "Surroundings"
msgstr "Entourage"
@ -272,13 +272,13 @@ msgstr "Il y a plusieurs points où vous pouvez aller en kayak, à partir de sec
#: web/templates/public/campground.gohtml:6
#: web/templates/public/campground.gohtml:11
#: web/templates/public/layout.gohtml:38 web/templates/public/layout.gohtml:78
#: web/templates/public/layout.gohtml:53 web/templates/public/layout.gohtml:93
msgctxt "title"
msgid "Campground"
msgstr "Camping"
#: web/templates/public/booking.gohtml:6 web/templates/public/booking.gohtml:11
#: web/templates/public/layout.gohtml:54
#: web/templates/public/layout.gohtml:69
msgctxt "title"
msgid "Booking"
msgstr "Reservation"
@ -366,8 +366,8 @@ msgctxt "input"
msgid "I have read and I accept the reservation conditions"
msgstr "Jai lu et jaccepte les conditions de réservation"
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:32
#: web/templates/public/layout.gohtml:109
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:47
#: web/templates/public/layout.gohtml:130
msgid "Campsite Montagut"
msgstr "Camping Montagut"
@ -375,28 +375,120 @@ msgstr "Camping Montagut"
msgid "Skip to main content"
msgstr "Passer au contenu principal"
#: web/templates/public/layout.gohtml:42 web/templates/public/layout.gohtml:88
#: web/templates/public/layout.gohtml:57 web/templates/public/layout.gohtml:103
#: web/templates/admin/campsite/index.gohtml:6
#: web/templates/admin/campsite/index.gohtml:12
#: web/templates/admin/layout.gohtml:45 web/templates/admin/layout.gohtml:79
#: web/templates/admin/layout.gohtml:45 web/templates/admin/layout.gohtml:82
msgctxt "title"
msgid "Campsites"
msgstr "Locatifs"
#: web/templates/public/layout.gohtml:75
#: web/templates/public/layout.gohtml:90
msgctxt "title"
msgid "Sections"
msgstr "Sections"
#: web/templates/public/layout.gohtml:99
#: web/templates/public/layout.gohtml:114
msgctxt "title"
msgid "Opening"
msgstr "Ouverture"
#: web/templates/public/layout.gohtml:106
#: web/templates/public/layout.gohtml:121
msgid "<abbr title=\"Catalonia Tourism Registry\">RTC</abbr> <abbr title=\"Number\">#</abbr>%s"
msgstr "<abbr title=\"Registre du tourisme de Catalogne\"># RTC</abbr> %s"
#: web/templates/admin/legal/form.gohtml:8
#: web/templates/admin/legal/form.gohtml:25
msgctxt "title"
msgid "Edit Legal Text"
msgstr ""
#: web/templates/admin/legal/form.gohtml:10
#: web/templates/admin/legal/form.gohtml:27
msgctxt "title"
msgid "New Legal Text"
msgstr ""
#: web/templates/admin/legal/form.gohtml:37
msgctxt "input"
msgid "Slug"
msgstr ""
#: web/templates/admin/legal/form.gohtml:46
#: web/templates/admin/campsite/feature/form.gohtml:52
#: web/templates/admin/campsite/feature/l10n.gohtml:20
#: web/templates/admin/campsite/option/form.gohtml:34
#: web/templates/admin/campsite/option/l10n.gohtml:20
#: web/templates/admin/campsite/type/form.gohtml:46
#: web/templates/admin/campsite/type/l10n.gohtml:20
#: web/templates/admin/season/form.gohtml:46
#: web/templates/admin/season/l10n.gohtml:20
#: web/templates/admin/services/form.gohtml:52
#: web/templates/admin/services/l10n.gohtml:20
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/legal/form.gohtml:64
#, fuzzy
msgctxt "input"
msgid "Content"
msgstr "Contact"
#: web/templates/admin/legal/form.gohtml:84
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/feature/form.gohtml:62
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:129
#: web/templates/admin/season/form.gohtml:64
#: web/templates/admin/services/form.gohtml:69
#: web/templates/admin/media/form.gohtml:35
msgctxt "action"
msgid "Update"
msgstr "Mettre à jour"
#: web/templates/admin/legal/form.gohtml:86
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/feature/form.gohtml:64
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:131
#: web/templates/admin/season/form.gohtml:66
#: web/templates/admin/services/form.gohtml:71
msgctxt "action"
msgid "Add"
msgstr "Ajouter"
#: web/templates/admin/legal/index.gohtml:6
#: web/templates/admin/legal/index.gohtml:12
#: web/templates/admin/layout.gohtml:63
msgctxt "title"
msgid "Legal Texts"
msgstr ""
#: web/templates/admin/legal/index.gohtml:11
msgctxt "action"
msgid "Add Legal Text"
msgstr ""
#: web/templates/admin/legal/index.gohtml:17
#: web/templates/admin/campsite/feature/index.gohtml:26
#: web/templates/admin/campsite/option/index.gohtml:25
#: web/templates/admin/campsite/type/index.gohtml:25
#: web/templates/admin/season/index.gohtml:26
msgctxt "header"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/legal/index.gohtml:29
#, fuzzy
msgid "No legal texts added yet."
msgstr "Aucune diapositive na encore été ajoutée."
#: web/templates/admin/carousel/form.gohtml:8
#: web/templates/admin/carousel/form.gohtml:25
msgctxt "title"
@ -417,31 +509,6 @@ msgctxt "input"
msgid "Caption"
msgstr "Légende"
#: web/templates/admin/carousel/form.gohtml:47
#: web/templates/admin/campsite/feature/form.gohtml:62
#: web/templates/admin/campsite/carousel/form.gohtml:47
#: web/templates/admin/campsite/form.gohtml:70
#: web/templates/admin/campsite/option/form.gohtml:78
#: web/templates/admin/campsite/type/form.gohtml:129
#: web/templates/admin/season/form.gohtml:64
#: web/templates/admin/services/form.gohtml:69
#: web/templates/admin/media/form.gohtml:35
msgctxt "action"
msgid "Update"
msgstr "Mettre à jour"
#: web/templates/admin/carousel/form.gohtml:49
#: web/templates/admin/campsite/feature/form.gohtml:64
#: web/templates/admin/campsite/carousel/form.gohtml:49
#: web/templates/admin/campsite/form.gohtml:72
#: web/templates/admin/campsite/option/form.gohtml:80
#: web/templates/admin/campsite/type/form.gohtml:131
#: web/templates/admin/season/form.gohtml:66
#: web/templates/admin/services/form.gohtml:71
msgctxt "action"
msgid "Add"
msgstr "Ajouter"
#: web/templates/admin/carousel/l10n.gohtml:7
#: web/templates/admin/carousel/l10n.gohtml:14
msgctxt "title"
@ -535,21 +602,6 @@ msgctxt "input"
msgid "Icon"
msgstr "Icône"
#: web/templates/admin/campsite/feature/form.gohtml:52
#: web/templates/admin/campsite/feature/l10n.gohtml:20
#: web/templates/admin/campsite/option/form.gohtml:34
#: web/templates/admin/campsite/option/l10n.gohtml:20
#: web/templates/admin/campsite/type/form.gohtml:46
#: web/templates/admin/campsite/type/l10n.gohtml:20
#: web/templates/admin/season/form.gohtml:46
#: web/templates/admin/season/l10n.gohtml:20
#: web/templates/admin/services/form.gohtml:52
#: web/templates/admin/services/l10n.gohtml:20
#: web/templates/admin/profile.gohtml:26
msgctxt "input"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/campsite/feature/index.gohtml:6
#: web/templates/admin/campsite/feature/index.gohtml:12
msgctxt "title"
@ -561,14 +613,6 @@ msgctxt "action"
msgid "Add Feature"
msgstr "Ajouter une fonctionnalité"
#: web/templates/admin/campsite/feature/index.gohtml:26
#: web/templates/admin/campsite/option/index.gohtml:25
#: web/templates/admin/campsite/type/index.gohtml:25
#: web/templates/admin/season/index.gohtml:26
msgctxt "header"
msgid "Name"
msgstr "Nom"
#: web/templates/admin/campsite/feature/index.gohtml:27
#: web/templates/admin/campsite/carousel/index.gohtml:27
#: web/templates/admin/campsite/option/index.gohtml:26
@ -980,7 +1024,7 @@ msgid "Integration"
msgstr "Intégration"
#: web/templates/admin/dashboard.gohtml:6
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:76
#: web/templates/admin/dashboard.gohtml:10 web/templates/admin/layout.gohtml:79
msgctxt "title"
msgid "Dashboard"
msgstr "Tableau de bord"
@ -1163,7 +1207,7 @@ msgctxt "title"
msgid "Home Page"
msgstr "Page d'accueil"
#: web/templates/admin/layout.gohtml:65
#: web/templates/admin/layout.gohtml:68
msgctxt "action"
msgid "Logout"
msgstr "Déconnexion"
@ -1233,6 +1277,19 @@ msgctxt "title"
msgid "Upload Media"
msgstr "Envoyer un fichier"
#: pkg/legal/admin.go:255 pkg/app/user.go:249 pkg/campsite/types/l10n.go:87
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:350 pkg/campsite/types/feature.go:253
#: pkg/campsite/types/admin.go:447 pkg/season/l10n.go:69
#: pkg/season/admin.go:405 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "Le nom ne peut pas être laissé vide."
#: pkg/legal/admin.go:256 pkg/campsite/types/option.go:351
#: pkg/campsite/types/feature.go:254 pkg/campsite/types/admin.go:448
msgid "Name must have at least one letter."
msgstr "Le nom doit comporter au moins une lettre."
#: pkg/carousel/admin.go:285 pkg/campsite/types/carousel.go:242
msgctxt "input"
msgid "Slide image"
@ -1274,14 +1331,6 @@ msgctxt "language option"
msgid "Automatic"
msgstr "Automatique"
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:87
#: pkg/campsite/types/l10n.go:144 pkg/campsite/types/l10n.go:268
#: pkg/campsite/types/option.go:350 pkg/campsite/types/feature.go:253
#: pkg/campsite/types/admin.go:447 pkg/season/l10n.go:69
#: pkg/season/admin.go:404 pkg/services/l10n.go:73 pkg/services/admin.go:266
msgid "Name can not be empty."
msgstr "Le nom ne peut pas être laissé vide."
#: pkg/app/user.go:250
msgid "Confirmation does not match password."
msgstr "La confirmation ne correspond pas au mot de passe."
@ -1294,15 +1343,10 @@ msgstr "La langue sélectionnée nest pas valide."
msgid "File must be a valid PNG or JPEG image."
msgstr "Le fichier doit être une image PNG ou JPEG valide."
#: pkg/app/admin.go:59
#: pkg/app/admin.go:62
msgid "Access forbidden"
msgstr "Accès interdit"
#: pkg/campsite/types/option.go:351 pkg/campsite/types/feature.go:254
#: pkg/campsite/types/admin.go:448
msgid "Name must have at least one letter."
msgstr "Le nom doit comporter au moins une lettre."
#: pkg/campsite/types/option.go:354
msgid "Minimum can not be empty."
msgstr "Le minimum ne peut pas être vide."
@ -1458,32 +1502,32 @@ msgctxt "month"
msgid "December"
msgstr "Décembre"
#: pkg/season/admin.go:405
#: pkg/season/admin.go:406
msgid "Color can not be empty."
msgstr "La couleur ne peut pas être vide."
#: pkg/season/admin.go:406
#: pkg/season/admin.go:407
msgid "This color is not valid. It must be like #123abc."
msgstr "Cette couleur nest pas valide. Il doit être comme #123abc."
#: pkg/season/admin.go:506
#: pkg/season/admin.go:507
msgctxt "action"
msgid "Unset"
msgstr "Unset"
#: pkg/season/admin.go:537
#: pkg/season/admin.go:538
msgid "Start date can not be empty."
msgstr "La date de début ne peut pas être vide."
#: pkg/season/admin.go:538
#: pkg/season/admin.go:539
msgid "Start date must be a valid date."
msgstr "La date de début doit être une date valide."
#: pkg/season/admin.go:540
#: pkg/season/admin.go:541
msgid "End date can not be empty."
msgstr "La date de fin ne peut pas être vide."
#: pkg/season/admin.go:541
#: pkg/season/admin.go:542
msgid "End date must be a valid date."
msgstr "La date de fin doit être une date valide."
@ -1666,7 +1710,3 @@ msgstr "%s doit être %d ou plus."
#, c-format
msgid "%s must be at most %d."
msgstr "%s doit être tout au plus %d."
#~ msgctxt "title"
#~ msgid "Contact"
#~ msgstr "Contact"

7
revert/legal_text.sql Normal file
View File

@ -0,0 +1,7 @@
-- Revert camper:legal_text from pg
begin;
drop table if exists camper.legal_text;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:legal_text_i18n from pg
begin;
drop table if exists camper.legal_text_i18n;
commit;

View File

@ -0,0 +1,7 @@
-- Revert camper:translate_legal_text from pg
begin;
drop function if exists camper.translate_legal_text(integer, text, text, text, text);
commit;

View File

@ -128,3 +128,6 @@ location [schema_camper roles company user_profile] 2023-12-21T17:01:28Z jordi f
location_i18n [roles schema_camper location language] 2023-12-21T17:32:50Z jordi fita mas <jordi@tandem.blog> # Add relation for location internationalization
translate_location [roles schema_camper location_i18n] 2023-12-21T17:37:47Z jordi fita mas <jordi@tandem.blog> # Add function to translate location
setup_location [roles schema_camper location] 2023-12-21T19:26:53Z jordi fita mas <jordi@tandem.blog> # Add function to setup location settings
legal_text [roles schema_camper company user_profile] 2023-12-21T23:29:28Z jordi fita mas <jordi@tandem.blog> # Add relation for legal documents
legal_text_i18n [roles schema_camper legal_text language] 2023-12-21T23:51:09Z jordi fita mas <jordi@tandem.blog> # Add relation for legal text internationalization
translate_legal_text [roles schema_camper legal_text_i18n] 2023-12-22T00:10:05Z jordi fita mas <jordi@tandem.blog> # Add function to translate legal texts

198
test/legal_text.sql Normal file
View File

@ -0,0 +1,198 @@
-- Test legal_text
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(41);
set search_path to camper, public;
select has_table('legal_text');
select has_pk('legal_text');
select col_is_pk('legal_text', array['company_id', 'slug']);
select table_privs_are('legal_text', 'guest', array['SELECT']);
select table_privs_are('legal_text', 'employee', array['SELECT']);
select table_privs_are('legal_text', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('legal_text', 'authenticator', array[]::text[]);
select has_column('legal_text', 'slug');
select col_type_is('legal_text', 'slug', 'text');
select col_not_null('legal_text', 'slug');
select col_hasnt_default('legal_text', 'slug');
select has_column('legal_text', 'company_id');
select col_is_fk('legal_text', 'company_id');
select fk_ok('legal_text', 'company_id', 'company', 'company_id');
select col_type_is('legal_text', 'company_id', 'integer');
select col_not_null('legal_text', 'company_id');
select col_hasnt_default('legal_text', 'company_id');
select has_column('legal_text', 'name');
select col_type_is('legal_text', 'name', 'text');
select col_not_null('legal_text', 'name');
select col_hasnt_default('legal_text', 'name');
select has_column('legal_text', 'content');
select col_type_is('legal_text', 'content', 'xml');
select col_not_null('legal_text', 'content');
select col_hasnt_default('legal_text', 'content');
set client_min_messages to warning;
truncate legal_text cascade;
truncate company_host cascade;
truncate company_user cascade;
truncate company cascade;
truncate auth."user" cascade;
reset client_min_messages;
insert into auth."user" (user_id, email, name, password, cookie, cookie_expires_at)
values (1, 'demo@tandem.blog', 'Demo', 'test', '44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e', current_timestamp + interval '1 month')
, (5, 'admin@tandem.blog', 'Demo', 'test', '12af4c88b528c2ad4222e3740496ecbc58e76e26f087657524', current_timestamp + interval '1 month')
;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, country_code, currency_code, default_lang_tag)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 'ES', 'EUR', 'ca')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', '', 'FR', 'USD', 'ca')
;
insert into company_user (company_id, user_id, role)
values (2, 1, 'admin')
, (4, 5, 'admin')
;
insert into company_host (company_id, host)
values (2, 'co2')
, (4, 'co4')
;
insert into legal_text (company_id, slug, name, content)
values (2, 'reservation', 'Reservation', '')
, (4, 'cookies', 'Cookies', '')
;
prepare legal_data as
select company_id, slug
from legal_text
order by company_id, slug;
set role guest;
select bag_eq(
'legal_data',
$$ values (2, 'reservation')
, (4, 'cookies')
$$,
'Everyone should be able to list all legal texts across all companies'
);
reset role;
select set_cookie('44facbb30d8a419dfd4bfbc44a4b5539d4970148dfc84bed0e/demo@tandem.blog', 'co2');
select lives_ok(
$$ insert into legal_text(company_id, slug, name, content) values (2, 'tos', 'Terms of Service', '') $$,
'Admin from company 2 should be able to insert a new legal text to that company.'
);
select bag_eq(
'legal_data',
$$ values (2, 'tos')
, (2, 'reservation')
, (4, 'cookies')
$$,
'The new row should have been added'
);
select lives_ok(
$$ update legal_text set slug = 'terms' where company_id = 2 and slug = 'tos' $$,
'Admin from company 2 should be able to update legal text of that company.'
);
select bag_eq(
'legal_data',
$$ values (2, 'terms')
, (2, 'reservation')
, (4, 'cookies')
$$,
'The row should have been updated.'
);
select lives_ok(
$$ delete from legal_text where company_id = 2 and slug = 'terms' $$,
'Admin from company 2 should be able to delete legal text from that company.'
);
select bag_eq(
'legal_data',
$$ values (2, 'reservation')
, (4, 'cookies')
$$,
'The row should have been deleted.'
);
select throws_ok(
$$ insert into legal_text (company_id, slug, name, content) values (4, 'terms', 'Terms', '') $$,
'42501', 'new row violates row-level security policy for table "legal_text"',
'Admin from company 2 should NOT be able to insert new legal texts to company 4.'
);
select lives_ok(
$$ update legal_text set slug = 'nope' where company_id = 4 $$,
'Admin from company 2 should not be able to update new legal texts of company 4, but no error if company_id is not changed.'
);
select bag_eq(
'legal_data',
$$ values (2, 'reservation')
, (4, 'cookies')
$$,
'No row should have been changed.'
);
select throws_ok(
$$ update legal_text set company_id = 4 where company_id = 2 $$,
'42501', 'new row violates row-level security policy for table "legal_text"',
'Admin from company 2 should NOT be able to move legal texts to company 4'
);
select lives_ok(
$$ delete from legal_text where company_id = 4 $$,
'Admin from company 2 should NOT be able to delete legal texts from company 4, but not error is thrown'
);
select bag_eq(
'legal_data',
$$ values (2, 'reservation')
, (4, 'cookies')
$$,
'No row should have been changed'
);
select throws_ok(
$$ insert into legal_text (company_id, slug, name, content) values (2, 'ToS', 'Term of Services', '') $$,
'23514', 'new row for relation "legal_text" violates check constraint "valid_slug"',
'Should not be able to insert legal texts with a invalid slug.'
);
select throws_ok(
$$ insert into legal_text (company_id, slug, name, content) values (2, 'terms of service', 'Term of Services', '') $$,
'23514', 'new row for relation "legal_text" violates check constraint "valid_slug"',
'Should not be able to insert legal texts with a spaces in the slug.'
);
select throws_ok(
$$ insert into legal_text (company_id, slug, name, content) values (2, 'tos', ' ', '') $$,
'23514', 'new row for relation "legal_text" violates check constraint "name_not_empty"',
'Should not be able to insert legal texts with a blank name.'
);
reset role;
select *
from finish();
rollback;

54
test/legal_text_i18n.sql Normal file
View File

@ -0,0 +1,54 @@
-- Test legal_text_i18n
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(31);
set search_path to camper, public;
select has_table('legal_text_i18n');
select has_pk('legal_text_i18n');
select col_is_pk('legal_text_i18n', array['company_id', 'slug', 'lang_tag']);
select col_is_fk('legal_text_i18n', array['company_id', 'slug']);
select fk_ok('legal_text_i18n', array['company_id', 'slug'], 'legal_text', array['company_id', 'slug']);
select table_privs_are('legal_text_i18n', 'guest', array['SELECT']);
select table_privs_are('legal_text_i18n', 'employee', array['SELECT']);
select table_privs_are('legal_text_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('legal_text_i18n', 'authenticator', array[]::text[]);
select has_column('legal_text_i18n', 'company_id');
select col_type_is('legal_text_i18n', 'company_id', 'integer');
select col_not_null('legal_text_i18n', 'company_id');
select col_hasnt_default('legal_text_i18n', 'company_id');
select has_column('legal_text_i18n', 'slug');
select col_type_is('legal_text_i18n', 'slug', 'text');
select col_not_null('legal_text_i18n', 'slug');
select col_hasnt_default('legal_text_i18n', 'slug');
select has_column('legal_text_i18n', 'lang_tag');
select col_is_fk('legal_text_i18n', 'lang_tag');
select fk_ok('legal_text_i18n', 'lang_tag', 'language', 'lang_tag');
select col_type_is('legal_text_i18n', 'lang_tag', 'text');
select col_not_null('legal_text_i18n', 'lang_tag');
select col_hasnt_default('legal_text_i18n', 'lang_tag');
select has_column('legal_text_i18n', 'name');
select col_type_is('legal_text_i18n', 'name', 'text');
select col_not_null('legal_text_i18n', 'name');
select col_hasnt_default('legal_text_i18n', 'name');
select has_column('legal_text_i18n', 'content');
select col_type_is('legal_text_i18n', 'content', 'xml');
select col_not_null('legal_text_i18n', 'content');
select col_hasnt_default('legal_text_i18n', 'content');
select *
from finish();
rollback;

View File

@ -0,0 +1,72 @@
-- Test translate_legal_text
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(13);
set search_path to camper, public;
select has_function('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text']);
select function_lang_is('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'sql');
select function_returns('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'void');
select isnt_definer('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text']);
select volatility_is('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'volatile');
select function_privs_are('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'guest', array[]::text[]);
select function_privs_are('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'employee', array[]::text[]);
select function_privs_are('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'translate_legal_text', array['integer', 'text', 'text', 'text', 'text'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate legal_text_i18n cascade;
truncate legal_text cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, country_code, currency_code, default_lang_tag)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 'ES', 'EUR', 'ca')
, (4, 'Company 4', 'XX234', '', '666-666-666', 'b@b', '', '', '', '', '', '', 'FR', 'USD', 'ca')
;
insert into legal_text (company_id, slug, name, content)
values (2, 'tos', 'Terms of Service', '<p>Go away</p>')
, (4, 'cookies', 'Cookies', '<p>Yummy</p>')
;
insert into legal_text_i18n (company_id, slug, lang_tag, name, content)
values (2, 'tos', 'ca', '<p>Termes i condicions</p>', '<p>Uh?</p>')
, (4, 'cookies', 'ca', '<p>Galetes</p>', '<p>Uh?</p>')
;
select lives_ok(
$$ select translate_legal_text(2, 'tos', 'es', 'Términos', '<p>Adiós</p>') $$,
'Should be able to translate the legal text of the first company to a new language'
);
select lives_ok(
$$ select translate_legal_text(2, 'tos', 'ca', 'Termes', '<p>Adéu</p>') $$,
'Should be able to overwrite a legal texts translation'
);
select lives_ok(
$$ select translate_legal_text(4, 'cookies', 'ca', null, null) $$,
'Should be able to “translate” a legal_text to empty strings'
);
select bag_eq(
$$ select company_id, slug, lang_tag, name, content::text from legal_text_i18n $$,
$$ values (2, 'tos', 'ca', 'Termes', '<p>Adéu</p>')
, (2, 'tos', 'es', 'Términos', '<p>Adiós</p>')
, (4, 'cookies', 'ca', '', '')
$$,
'Should have translated all legal texts'
);
select *
from finish();
rollback;

18
verify/legal_text.sql Normal file
View File

@ -0,0 +1,18 @@
-- Verify camper:legal_text on pg
begin;
select company_id
, slug
, name
, content
from camper.legal_text
where false;
select 1 / count(*) from pg_class where oid = 'camper.legal_text'::regclass and relrowsecurity;
select 1 / count(*) from pg_policy where polname = 'guest_ok' and polrelid = 'camper.legal_text'::regclass;
select 1 / count(*) from pg_policy where polname = 'insert_to_company' and polrelid = 'camper.legal_text'::regclass;
select 1 / count(*) from pg_policy where polname = 'update_company' and polrelid = 'camper.legal_text'::regclass;
select 1 / count(*) from pg_policy where polname = 'delete_from_company' and polrelid = 'camper.legal_text'::regclass;
rollback;

View File

@ -0,0 +1,13 @@
-- Verify camper:legal_text_i18n on pg
begin;
select company_id
, slug
, lang_tag
, name
, content
from camper.legal_text_i18n
where false;
rollback;

View File

@ -0,0 +1,7 @@
-- Verify camper:translate_legal_text on pg
begin;
select has_function_privilege('camper.translate_legal_text(integer, text, text, text, text)', 'execute');
rollback;

View File

@ -59,6 +59,9 @@
<li>
<a href="/admin/location">{{( pgettext "Location" "title" )}}</a>
</li>
<li>
<a href="/admin/legal">{{( pgettext "Legal Texts" "title" )}}</a>
</li>
{{- end }}
<li class="icon_logout">
<button data-hx-delete="/me/session" data-hx-headers='{ {{ CSRFHeader }} }'

View File

@ -0,0 +1,91 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/legal.legalForm*/ -}}
{{ if .URL }}
{{( pgettext "Edit Legal Text" "title" )}}
{{ else }}
{{( pgettext "New Legal Text" "title" )}}
{{ end }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/legal.legalForm*/ -}}
<form
{{ if .URL }}
data-hx-put="{{ .URL }}"
{{ else }}
action="/admin/legal" method="post"
{{ end }}
>
<h2>
{{ if .URL }}
{{( pgettext "Edit Legal Text" "title" )}}
{{ else }}
{{( pgettext "New Legal Text" "title" )}}
{{ end }}
</h2>
{{ CSRFInput }}
<fieldset x-data="{ lang: 'ca' }">
{{ if .URL }}
<input type="hidden" name="{{ .Slug.Name }}" value="{{ .Slug.Val }}">
{{ else }}
{{ with .Slug -}}
<label>
{{( pgettext "Slug" "input")}}<br>
<input type="text" name="{{ .Name }}" value="{{ .Val }}"
required {{ template "error-attrs" . }}><br>
</label>
{{ template "error-message" . }}
{{- end }}
{{ end }}
{{ with .Name -}}
<fieldset>
<legend>{{( pgettext "Name" "input" )}}</legend>
<div class="lang-selector" role="toolbar">
{{ range $lang, $input := . -}}
<button :aria-pressed="lang === '{{ $lang }}'"
@click.prevent="lang = '{{ $lang }}'">{{ $lang }}</button>
{{- end }}
</div>
{{ range $lang, $input := . -}}
<label x-cloak x-show="lang === '{{ $lang }}'"><span>{{ $lang }}</span><br>
<input type="text" name="{{ $input.Name }}" value="{{ $input.Val }}"
{{ template "error-attrs" . }}><br>
</label>
{{- end }}
{{ template "error-message" . }}
</fieldset>
{{- end }}
{{ with .Content -}}
<fieldset>
<legend>{{( pgettext "Content" "input" )}}</legend>
<div class="lang-selector" role="toolbar">
{{ range $lang, $input := . -}}
<button :aria-pressed="lang === '{{ $lang }}'"
@click.prevent="lang = '{{ $lang }}'">{{ $lang }}</button>
{{- end }}
</div>
{{ range $lang, $input := . -}}
<label x-cloak x-show="lang === '{{ $lang }}'"><span>{{ $lang }}</span><br>
<textarea class="html"
name="{{ $input.Name }}" {{ template "error-attrs" . }}>{{ $input.Val }}</textarea><br>
</label>
{{- end }}
{{ template "error-message" . }}
</fieldset>
{{- end }}
</fieldset>
<footer>
<button type="submit">
{{ if .URL }}
{{( pgettext "Update" "action" )}}
{{ else }}
{{( pgettext "Add" "action" )}}
{{ end }}
</button>
</footer>
</form>
{{- end }}

View File

@ -0,0 +1,31 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Legal Texts" "title" )}}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/legal.legalIndex*/ -}}
<a href="/admin/legal/new">{{( pgettext "Add Legal Text" "action" )}}</a>
<h2>{{( pgettext "Legal Texts" "title" )}}</h2>
{{ if .Texts -}}
<table>
<thead>
<tr>
<th scope="col">{{( pgettext "Name" "header" )}}</th>
</tr>
</thead>
<tbody>
{{ range $text := .Texts -}}
<tr>
<td><a href="{{ .URL }}">{{ .Name }}</a></td>
</tr>
{{- end }}
</tbody>
</table>
{{ else -}}
<p>{{( gettext "No legal texts added yet." )}}</p>
{{- end }}
{{- end }}

View File

@ -0,0 +1,17 @@
<!--
SPDX-FileCopyrightText: 2023 jordi fita mas <jordi@tandem.blog>
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/legal.legalPage*/ -}}
{{ .Name }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/legal.legalPage*/ -}}
<h2>{{ .Name }}</h2>
<div class="legal-page">
{{ .Content }}
</div>
{{- end }}