Add internationalization and localization to seasons
This commit is contained in:
parent
0ce1b4bd64
commit
5f38ab8fd3
|
@ -242,6 +242,15 @@ select add_season(52, 'Temporada alta', '#ff926c');
|
|||
select add_season(52, 'Temporada mitjana', '#ffe37f');
|
||||
select add_season(52, 'Temporada baixa', '#00aa7d');
|
||||
|
||||
insert into season_i18n (season_id, lang_tag, name)
|
||||
values (92, 'en', 'Peak season')
|
||||
, (92, 'es', 'Temporada alta')
|
||||
, (93, 'en', 'Shoulder season')
|
||||
, (93, 'es', 'Temporada media')
|
||||
, (94, 'en', 'Offseason')
|
||||
, (94, 'es', 'Temporada baja')
|
||||
;
|
||||
|
||||
select set_season_range(92, '[2023-04-06, 2023-04-10]');
|
||||
select set_season_range(94, '[2023-04-11, 2023-04-27]');
|
||||
select set_season_range(93, '[2023-04-28, 2023-04-30]');
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
-- Deploy camper:season_i18n to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_camper
|
||||
-- requires: season
|
||||
-- requires: language
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to camper, public;
|
||||
|
||||
create table season_i18n (
|
||||
season_id integer not null references season,
|
||||
lang_tag text not null references language,
|
||||
name text not null,
|
||||
primary key (season_id, lang_tag)
|
||||
);
|
||||
|
||||
grant select on table season_i18n to guest;
|
||||
grant select on table season_i18n to employee;
|
||||
grant select, insert, update, delete on table season_i18n to admin;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,27 @@
|
|||
-- Deploy camper:translate_season to pg
|
||||
-- requires: roles
|
||||
-- requires: schema_camper
|
||||
-- requires: season
|
||||
-- requires: season_i18n
|
||||
|
||||
begin;
|
||||
|
||||
set search_path to camper, public;
|
||||
|
||||
create or replace function translate_season(slug uuid, lang_tag text, name text) returns void as
|
||||
$$
|
||||
insert into season_i18n (season_id, lang_tag, name)
|
||||
select season_id, translate_season.lang_tag, translate_season.name
|
||||
from season
|
||||
where slug = translate_season.slug
|
||||
on conflict (season_id, lang_tag) do update
|
||||
set name = excluded.name
|
||||
;
|
||||
$$
|
||||
language sql
|
||||
;
|
||||
|
||||
revoke execute on function translate_season(uuid, text, text) from public;
|
||||
grant execute on function translate_season(uuid, text, text) to admin;
|
||||
|
||||
commit;
|
|
@ -36,7 +36,7 @@ func newAdminHandler(locales locale.Locales, mediaDir string) *adminHandler {
|
|||
company: company.NewAdminHandler(),
|
||||
home: home.NewAdminHandler(locales),
|
||||
media: media.NewAdminHandler(mediaDir),
|
||||
season: season.NewAdminHandler(),
|
||||
season: season.NewAdminHandler(locales),
|
||||
services: services.NewAdminHandler(locales),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"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/locale"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
||||
)
|
||||
|
@ -31,7 +32,7 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
|
|||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
page, err := newPublicPage(r.Context(), user, conn, head)
|
||||
page, err := newPublicPage(r.Context(), conn, user.Locale, head)
|
||||
if database.ErrorIsNotFound(err) {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
|
@ -59,7 +60,7 @@ type typePrice struct {
|
|||
PricePerNight string
|
||||
}
|
||||
|
||||
func newPublicPage(ctx context.Context, user *auth.User, conn *database.Conn, slug string) (*publicPage, error) {
|
||||
func newPublicPage(ctx context.Context, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) {
|
||||
page := &publicPage{
|
||||
PublicPage: template.NewPublicPage(),
|
||||
}
|
||||
|
@ -70,23 +71,23 @@ func newPublicPage(ctx context.Context, user *auth.User, conn *database.Conn, sl
|
|||
left join campsite_type_i18n as i18n on campsite_type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1
|
||||
where slug = $2
|
||||
and active
|
||||
`, user.Locale.Language, slug)
|
||||
`, loc.Language, slug)
|
||||
if err := row.Scan(&page.Name, &page.Description); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := conn.Query(ctx, `
|
||||
select season.name --coalesce(i18n.name, season.name) as l10n_name
|
||||
select coalesce(i18n.name, season.name) as l10n_name
|
||||
, to_color(season.color)
|
||||
, coalesce(min_nights, 1)
|
||||
, to_price(coalesce(cost_per_night, 0))::text
|
||||
from season
|
||||
--left join season_i18n as i18n on season.season_id = i18n.season_id and i18n.lang_tag = $1
|
||||
left join season_i18n as i18n on season.season_id = i18n.season_id and i18n.lang_tag = $1
|
||||
left join (
|
||||
campsite_type_cost as cost join campsite_type as type on cost.campsite_type_id = type.campsite_type_id and type.slug = $1
|
||||
campsite_type_cost as cost join campsite_type as type on cost.campsite_type_id = type.campsite_type_id and type.slug = $2
|
||||
) as cost on cost.season_id = season.season_id
|
||||
where season.active
|
||||
`, slug)
|
||||
`, loc.Language, slug)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
"github.com/jackc/pgx/v4"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
|
@ -28,10 +29,13 @@ import (
|
|||
const unsetColor = 13750495
|
||||
|
||||
type AdminHandler struct {
|
||||
locales locale.Locales
|
||||
}
|
||||
|
||||
func NewAdminHandler() *AdminHandler {
|
||||
return &AdminHandler{}
|
||||
func NewAdminHandler(locales locale.Locales) *AdminHandler {
|
||||
return &AdminHandler{
|
||||
locales: locales,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.HandlerFunc {
|
||||
|
@ -79,13 +83,37 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
|
|||
}
|
||||
panic(err)
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
f.MustRender(w, r, user, company)
|
||||
case http.MethodPut:
|
||||
editSeason(w, r, user, company, conn, f)
|
||||
var langTag string
|
||||
langTag, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
||||
|
||||
switch langTag {
|
||||
case "":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
f.MustRender(w, r, user, company)
|
||||
case http.MethodPut:
|
||||
editSeason(w, r, user, company, conn, f)
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
||||
}
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
||||
loc, ok := h.locales.Get(langTag)
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
l10n := newSeasonL10nForm(f, loc)
|
||||
if err := l10n.FillFromDatabase(r.Context(), conn); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
l10n.MustRender(w, r, user, company)
|
||||
case http.MethodPut:
|
||||
editSeasonL10n(w, r, user, company, conn, l10n)
|
||||
default:
|
||||
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,13 +148,22 @@ func getCalendarYear(query url.Values) int {
|
|||
|
||||
func collectSeasonEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*seasonEntry, error) {
|
||||
rows, err := conn.Query(ctx, `
|
||||
select slug
|
||||
, name
|
||||
select '/admin/seasons/' || season.slug
|
||||
, season.name
|
||||
, to_color(color)::text
|
||||
, active
|
||||
, array_agg((lang_tag, endonym, not exists (select 1 from season_i18n as i18n where i18n.season_id = season.season_id and i18n.lang_tag = language.lang_tag)) order by endonym)
|
||||
from season
|
||||
where company_id = $1
|
||||
order by name`, company.ID)
|
||||
join company using (company_id)
|
||||
, language
|
||||
where lang_tag <> default_lang_tag
|
||||
and language.selectable
|
||||
and season.company_id = $1
|
||||
group by season.slug
|
||||
, season.name
|
||||
, to_color(color)::text
|
||||
, active
|
||||
order by name`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, company.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -135,9 +172,17 @@ func collectSeasonEntries(ctx context.Context, company *auth.Company, conn *data
|
|||
var seasons []*seasonEntry
|
||||
for rows.Next() {
|
||||
entry := &seasonEntry{}
|
||||
if err = rows.Scan(&entry.Slug, &entry.Name, &entry.Color, &entry.Active); err != nil {
|
||||
var translations database.RecordArray
|
||||
if err = rows.Scan(&entry.URL, &entry.Name, &entry.Color, &entry.Active, &translations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, el := range translations.Elements {
|
||||
entry.Translations = append(entry.Translations, &locale.Translation{
|
||||
URL: entry.URL + "/" + el.Fields[0].Get().(string),
|
||||
Endonym: el.Fields[1].Get().(string),
|
||||
Missing: el.Fields[2].Get().(bool),
|
||||
})
|
||||
}
|
||||
seasons = append(seasons, entry)
|
||||
}
|
||||
|
||||
|
@ -145,10 +190,12 @@ func collectSeasonEntries(ctx context.Context, company *auth.Company, conn *data
|
|||
}
|
||||
|
||||
type seasonEntry struct {
|
||||
Slug string
|
||||
Name string
|
||||
Color string
|
||||
Active bool
|
||||
ID int
|
||||
URL string
|
||||
Name string
|
||||
Color string
|
||||
Active bool
|
||||
Translations []*locale.Translation
|
||||
}
|
||||
|
||||
var longMonthNames = []string{
|
||||
|
@ -396,13 +443,13 @@ func newCalendarForm(ctx context.Context, company *auth.Company, conn *database.
|
|||
|
||||
func mustCollectCalendarSeasons(ctx context.Context, company *auth.Company, conn *database.Conn) []*seasonEntry {
|
||||
rows, err := conn.Query(ctx, `
|
||||
select '0' as slug
|
||||
select 0 as season_id
|
||||
, $1 as name
|
||||
, to_color($2)::text
|
||||
, true
|
||||
, 0 as sort
|
||||
union all
|
||||
select season_id::text
|
||||
select season_id
|
||||
, name
|
||||
, to_color(color)::text
|
||||
, active
|
||||
|
@ -420,7 +467,7 @@ func mustCollectCalendarSeasons(ctx context.Context, company *auth.Company, conn
|
|||
for rows.Next() {
|
||||
entry := &seasonEntry{}
|
||||
var sort int
|
||||
if err = rows.Scan(&entry.Slug, &entry.Name, &entry.Color, &entry.Active, &sort); err != nil {
|
||||
if err = rows.Scan(&entry.ID, &entry.Name, &entry.Color, &entry.Active, &sort); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
seasons = append(seasons, entry)
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package season
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/template"
|
||||
)
|
||||
|
||||
type seasonL10nForm struct {
|
||||
Locale *locale.Locale
|
||||
Slug string
|
||||
Name *form.L10nInput
|
||||
}
|
||||
|
||||
func newSeasonL10nForm(f *seasonForm, loc *locale.Locale) *seasonL10nForm {
|
||||
return &seasonL10nForm{
|
||||
Locale: loc,
|
||||
Slug: f.Slug,
|
||||
Name: f.Name.L10nInput(),
|
||||
}
|
||||
}
|
||||
|
||||
func (l10n *seasonL10nForm) FillFromDatabase(ctx context.Context, conn *database.Conn) error {
|
||||
row := conn.QueryRow(ctx, `
|
||||
select coalesce(i18n.name, '') as l10n_name
|
||||
from season
|
||||
left join season_i18n as i18n on season.season_id = i18n.season_id and i18n.lang_tag = $1
|
||||
where slug = $2
|
||||
`, l10n.Locale.Language, l10n.Slug)
|
||||
return row.Scan(&l10n.Name.Val)
|
||||
}
|
||||
|
||||
func (l10n *seasonL10nForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||
template.MustRenderAdmin(w, r, user, company, "season/l10n.gohtml", l10n)
|
||||
}
|
||||
|
||||
func editSeasonL10n(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, l10n *seasonL10nForm) {
|
||||
if ok, err := form.Handle(l10n, w, r, user); err != nil {
|
||||
return
|
||||
} else if !ok {
|
||||
l10n.MustRender(w, r, user, company)
|
||||
return
|
||||
}
|
||||
conn.MustExec(r.Context(), "select translate_season($1, $2, $3)", l10n.Slug, l10n.Locale.Language, l10n.Name)
|
||||
httplib.Redirect(w, r, "/admin/seasons", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (l10n *seasonL10nForm) Parse(r *http.Request) error {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
l10n.Name.FillValue(r)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l10n *seasonL10nForm) Valid(l *locale.Locale) bool {
|
||||
v := form.NewValidator(l)
|
||||
v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty."))
|
||||
return v.AllOK
|
||||
}
|
65
po/ca.po
65
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-10-01 21:09+0200\n"
|
||||
"POT-Creation-Date: 2023-10-03 21:05+0200\n"
|
||||
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||
|
@ -205,6 +205,7 @@ msgstr "Traducció de la diapositiva del carrusel a %s"
|
|||
#: web/templates/admin/carousel/l10n.gohtml:21
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:21
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:33
|
||||
#: web/templates/admin/season/l10n.gohtml:21
|
||||
#: web/templates/admin/services/l10n.gohtml:21
|
||||
#: web/templates/admin/services/l10n.gohtml:33
|
||||
msgid "Source:"
|
||||
|
@ -213,6 +214,7 @@ msgstr "Origen:"
|
|||
#: web/templates/admin/carousel/l10n.gohtml:23
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:23
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:36
|
||||
#: web/templates/admin/season/l10n.gohtml:23
|
||||
#: web/templates/admin/services/l10n.gohtml:23
|
||||
#: web/templates/admin/services/l10n.gohtml:36
|
||||
msgctxt "input"
|
||||
|
@ -221,6 +223,7 @@ msgstr "Traducció:"
|
|||
|
||||
#: web/templates/admin/carousel/l10n.gohtml:32
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:45
|
||||
#: web/templates/admin/season/l10n.gohtml:32
|
||||
#: web/templates/admin/services/l10n.gohtml:45
|
||||
msgctxt "action"
|
||||
msgid "Translate"
|
||||
|
@ -282,13 +285,13 @@ msgstr "Tipus"
|
|||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:35
|
||||
#: web/templates/admin/season/index.gohtml:31
|
||||
#: web/templates/admin/season/index.gohtml:39
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:35
|
||||
#: web/templates/admin/season/index.gohtml:31
|
||||
#: web/templates/admin/season/index.gohtml:39
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
|
@ -317,6 +320,7 @@ msgstr "Actiu"
|
|||
#: 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
|
||||
|
@ -371,6 +375,7 @@ msgid "Name"
|
|||
msgstr "Nom"
|
||||
|
||||
#: web/templates/admin/campsite/type/index.gohtml:18
|
||||
#: web/templates/admin/season/index.gohtml:19
|
||||
#: web/templates/admin/services/index.gohtml:19
|
||||
#: web/templates/admin/services/index.gohtml:60
|
||||
#: web/templates/admin/home/index.gohtml:19
|
||||
|
@ -401,7 +406,7 @@ msgid "New Season"
|
|||
msgstr "Nova temporada"
|
||||
|
||||
#: web/templates/admin/season/form.gohtml:37
|
||||
#: web/templates/admin/season/index.gohtml:19
|
||||
#: web/templates/admin/season/index.gohtml:20
|
||||
msgctxt "season"
|
||||
msgid "Active"
|
||||
msgstr "Activa"
|
||||
|
@ -428,15 +433,21 @@ msgctxt "header"
|
|||
msgid "Color"
|
||||
msgstr "Color"
|
||||
|
||||
#: web/templates/admin/season/index.gohtml:37
|
||||
#: web/templates/admin/season/index.gohtml:45
|
||||
msgid "No seasons added yet."
|
||||
msgstr "No s’ha afegit cap temporada encara."
|
||||
|
||||
#: web/templates/admin/season/index.gohtml:40
|
||||
#: 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:14
|
||||
msgctxt "title"
|
||||
msgid "Translate Season to %s"
|
||||
msgstr "Traducció de la temporada a %s"
|
||||
|
||||
#: web/templates/admin/season/calendar.gohtml:16
|
||||
msgctxt "day"
|
||||
msgid "Mon"
|
||||
|
@ -836,8 +847,8 @@ msgid "Automatic"
|
|||
msgstr "Automàtic"
|
||||
|
||||
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:82
|
||||
#: pkg/campsite/types/admin.go:387 pkg/season/admin.go:335
|
||||
#: pkg/services/l10n.go:73 pkg/services/admin.go:266
|
||||
#: pkg/campsite/types/admin.go:387 pkg/season/l10n.go:69
|
||||
#: pkg/season/admin.go:382 pkg/services/l10n.go:73 pkg/services/admin.go:266
|
||||
msgid "Name can not be empty."
|
||||
msgstr "No podeu deixar el nom en blanc."
|
||||
|
||||
|
@ -923,92 +934,92 @@ msgstr "El tipus d’allotjament escollit no és vàlid."
|
|||
msgid "Label can not be empty."
|
||||
msgstr "No podeu deixar l’etiqueta en blanc."
|
||||
|
||||
#: pkg/season/admin.go:155
|
||||
#: pkg/season/admin.go:202
|
||||
msgctxt "month"
|
||||
msgid "January"
|
||||
msgstr "gener"
|
||||
|
||||
#: pkg/season/admin.go:156
|
||||
#: pkg/season/admin.go:203
|
||||
msgctxt "month"
|
||||
msgid "February"
|
||||
msgstr "febrer"
|
||||
|
||||
#: pkg/season/admin.go:157
|
||||
#: pkg/season/admin.go:204
|
||||
msgctxt "month"
|
||||
msgid "March"
|
||||
msgstr "març"
|
||||
|
||||
#: pkg/season/admin.go:158
|
||||
#: pkg/season/admin.go:205
|
||||
msgctxt "month"
|
||||
msgid "April"
|
||||
msgstr "abril"
|
||||
|
||||
#: pkg/season/admin.go:159
|
||||
#: pkg/season/admin.go:206
|
||||
msgctxt "month"
|
||||
msgid "May"
|
||||
msgstr "maig"
|
||||
|
||||
#: pkg/season/admin.go:160
|
||||
#: pkg/season/admin.go:207
|
||||
msgctxt "month"
|
||||
msgid "June"
|
||||
msgstr "juny"
|
||||
|
||||
#: pkg/season/admin.go:161
|
||||
#: pkg/season/admin.go:208
|
||||
msgctxt "month"
|
||||
msgid "July"
|
||||
msgstr "juliol"
|
||||
|
||||
#: pkg/season/admin.go:162
|
||||
#: pkg/season/admin.go:209
|
||||
msgctxt "month"
|
||||
msgid "August"
|
||||
msgstr "agost"
|
||||
|
||||
#: pkg/season/admin.go:163
|
||||
#: pkg/season/admin.go:210
|
||||
msgctxt "month"
|
||||
msgid "September"
|
||||
msgstr "setembre"
|
||||
|
||||
#: pkg/season/admin.go:164
|
||||
#: pkg/season/admin.go:211
|
||||
msgctxt "month"
|
||||
msgid "October"
|
||||
msgstr "octubre"
|
||||
|
||||
#: pkg/season/admin.go:165
|
||||
#: pkg/season/admin.go:212
|
||||
msgctxt "month"
|
||||
msgid "November"
|
||||
msgstr "novembre"
|
||||
|
||||
#: pkg/season/admin.go:166
|
||||
#: pkg/season/admin.go:213
|
||||
msgctxt "month"
|
||||
msgid "December"
|
||||
msgstr "desembre"
|
||||
|
||||
#: pkg/season/admin.go:336
|
||||
#: pkg/season/admin.go:383
|
||||
msgid "Color can not be empty."
|
||||
msgstr "No podeu deixar el color en blanc."
|
||||
|
||||
#: pkg/season/admin.go:337
|
||||
#: pkg/season/admin.go:384
|
||||
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:413
|
||||
#: pkg/season/admin.go:460
|
||||
msgctxt "action"
|
||||
msgid "Unset"
|
||||
msgstr "Desassigna"
|
||||
|
||||
#: pkg/season/admin.go:444
|
||||
#: pkg/season/admin.go:491
|
||||
msgid "Start date can not be empty."
|
||||
msgstr "No podeu deixar la data d’inici en blanc."
|
||||
|
||||
#: pkg/season/admin.go:445
|
||||
#: pkg/season/admin.go:492
|
||||
msgid "Start date must be a valid date."
|
||||
msgstr "La data d’inici ha de ser una data vàlida."
|
||||
|
||||
#: pkg/season/admin.go:447
|
||||
#: pkg/season/admin.go:494
|
||||
msgid "End date can not be empty."
|
||||
msgstr "No podeu deixar la data de fi en blanc."
|
||||
|
||||
#: pkg/season/admin.go:448
|
||||
#: pkg/season/admin.go:495
|
||||
msgid "End date must be a valid date."
|
||||
msgstr "La data de fi ha de ser una data vàlida."
|
||||
|
||||
|
|
65
po/es.po
65
po/es.po
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: camper\n"
|
||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||
"POT-Creation-Date: 2023-10-01 21:09+0200\n"
|
||||
"POT-Creation-Date: 2023-10-03 21:05+0200\n"
|
||||
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
|
||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||
|
@ -205,6 +205,7 @@ msgstr "Traducción de la diapositiva de carrusel a %s"
|
|||
#: web/templates/admin/carousel/l10n.gohtml:21
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:21
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:33
|
||||
#: web/templates/admin/season/l10n.gohtml:21
|
||||
#: web/templates/admin/services/l10n.gohtml:21
|
||||
#: web/templates/admin/services/l10n.gohtml:33
|
||||
msgid "Source:"
|
||||
|
@ -213,6 +214,7 @@ msgstr "Origen:"
|
|||
#: web/templates/admin/carousel/l10n.gohtml:23
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:23
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:36
|
||||
#: web/templates/admin/season/l10n.gohtml:23
|
||||
#: web/templates/admin/services/l10n.gohtml:23
|
||||
#: web/templates/admin/services/l10n.gohtml:36
|
||||
msgctxt "input"
|
||||
|
@ -221,6 +223,7 @@ msgstr "Traducción"
|
|||
|
||||
#: web/templates/admin/carousel/l10n.gohtml:32
|
||||
#: web/templates/admin/campsite/type/l10n.gohtml:45
|
||||
#: web/templates/admin/season/l10n.gohtml:32
|
||||
#: web/templates/admin/services/l10n.gohtml:45
|
||||
msgctxt "action"
|
||||
msgid "Translate"
|
||||
|
@ -282,13 +285,13 @@ msgstr "Tipo"
|
|||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:35
|
||||
#: web/templates/admin/season/index.gohtml:31
|
||||
#: web/templates/admin/season/index.gohtml:39
|
||||
msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: web/templates/admin/campsite/index.gohtml:28
|
||||
#: web/templates/admin/campsite/type/index.gohtml:35
|
||||
#: web/templates/admin/season/index.gohtml:31
|
||||
#: web/templates/admin/season/index.gohtml:39
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
|
@ -317,6 +320,7 @@ msgstr "Activo"
|
|||
#: 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
|
||||
|
@ -371,6 +375,7 @@ msgid "Name"
|
|||
msgstr "Nombre"
|
||||
|
||||
#: web/templates/admin/campsite/type/index.gohtml:18
|
||||
#: web/templates/admin/season/index.gohtml:19
|
||||
#: web/templates/admin/services/index.gohtml:19
|
||||
#: web/templates/admin/services/index.gohtml:60
|
||||
#: web/templates/admin/home/index.gohtml:19
|
||||
|
@ -401,7 +406,7 @@ msgid "New Season"
|
|||
msgstr "Nueva temporada"
|
||||
|
||||
#: web/templates/admin/season/form.gohtml:37
|
||||
#: web/templates/admin/season/index.gohtml:19
|
||||
#: web/templates/admin/season/index.gohtml:20
|
||||
msgctxt "season"
|
||||
msgid "Active"
|
||||
msgstr "Activa"
|
||||
|
@ -428,15 +433,21 @@ msgctxt "header"
|
|||
msgid "Color"
|
||||
msgstr "Color"
|
||||
|
||||
#: web/templates/admin/season/index.gohtml:37
|
||||
#: web/templates/admin/season/index.gohtml:45
|
||||
msgid "No seasons added yet."
|
||||
msgstr "No se ha añadido ninguna temporada todavía."
|
||||
|
||||
#: web/templates/admin/season/index.gohtml:40
|
||||
#: 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:14
|
||||
msgctxt "title"
|
||||
msgid "Translate Season to %s"
|
||||
msgstr "Traducción de la temporada a %s"
|
||||
|
||||
#: web/templates/admin/season/calendar.gohtml:16
|
||||
msgctxt "day"
|
||||
msgid "Mon"
|
||||
|
@ -836,8 +847,8 @@ msgid "Automatic"
|
|||
msgstr "Automático"
|
||||
|
||||
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:82
|
||||
#: pkg/campsite/types/admin.go:387 pkg/season/admin.go:335
|
||||
#: pkg/services/l10n.go:73 pkg/services/admin.go:266
|
||||
#: pkg/campsite/types/admin.go:387 pkg/season/l10n.go:69
|
||||
#: pkg/season/admin.go:382 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."
|
||||
|
||||
|
@ -923,92 +934,92 @@ msgstr "El tipo de alojamiento escogido no es válido."
|
|||
msgid "Label can not be empty."
|
||||
msgstr "No podéis dejar la etiqueta en blanco."
|
||||
|
||||
#: pkg/season/admin.go:155
|
||||
#: pkg/season/admin.go:202
|
||||
msgctxt "month"
|
||||
msgid "January"
|
||||
msgstr "enero"
|
||||
|
||||
#: pkg/season/admin.go:156
|
||||
#: pkg/season/admin.go:203
|
||||
msgctxt "month"
|
||||
msgid "February"
|
||||
msgstr "febrero"
|
||||
|
||||
#: pkg/season/admin.go:157
|
||||
#: pkg/season/admin.go:204
|
||||
msgctxt "month"
|
||||
msgid "March"
|
||||
msgstr "marzo"
|
||||
|
||||
#: pkg/season/admin.go:158
|
||||
#: pkg/season/admin.go:205
|
||||
msgctxt "month"
|
||||
msgid "April"
|
||||
msgstr "abril"
|
||||
|
||||
#: pkg/season/admin.go:159
|
||||
#: pkg/season/admin.go:206
|
||||
msgctxt "month"
|
||||
msgid "May"
|
||||
msgstr "mayo"
|
||||
|
||||
#: pkg/season/admin.go:160
|
||||
#: pkg/season/admin.go:207
|
||||
msgctxt "month"
|
||||
msgid "June"
|
||||
msgstr "junio"
|
||||
|
||||
#: pkg/season/admin.go:161
|
||||
#: pkg/season/admin.go:208
|
||||
msgctxt "month"
|
||||
msgid "July"
|
||||
msgstr "julio"
|
||||
|
||||
#: pkg/season/admin.go:162
|
||||
#: pkg/season/admin.go:209
|
||||
msgctxt "month"
|
||||
msgid "August"
|
||||
msgstr "agosto"
|
||||
|
||||
#: pkg/season/admin.go:163
|
||||
#: pkg/season/admin.go:210
|
||||
msgctxt "month"
|
||||
msgid "September"
|
||||
msgstr "septiembre"
|
||||
|
||||
#: pkg/season/admin.go:164
|
||||
#: pkg/season/admin.go:211
|
||||
msgctxt "month"
|
||||
msgid "October"
|
||||
msgstr "octubre"
|
||||
|
||||
#: pkg/season/admin.go:165
|
||||
#: pkg/season/admin.go:212
|
||||
msgctxt "month"
|
||||
msgid "November"
|
||||
msgstr "noviembre"
|
||||
|
||||
#: pkg/season/admin.go:166
|
||||
#: pkg/season/admin.go:213
|
||||
msgctxt "month"
|
||||
msgid "December"
|
||||
msgstr "diciembre"
|
||||
|
||||
#: pkg/season/admin.go:336
|
||||
#: pkg/season/admin.go:383
|
||||
msgid "Color can not be empty."
|
||||
msgstr "No podéis dejar el color en blanco."
|
||||
|
||||
#: pkg/season/admin.go:337
|
||||
#: pkg/season/admin.go:384
|
||||
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:413
|
||||
#: pkg/season/admin.go:460
|
||||
msgctxt "action"
|
||||
msgid "Unset"
|
||||
msgstr "Desasignar"
|
||||
|
||||
#: pkg/season/admin.go:444
|
||||
#: pkg/season/admin.go:491
|
||||
msgid "Start date can not be empty."
|
||||
msgstr "No podéis dejar la fecha de inicio en blanco."
|
||||
|
||||
#: pkg/season/admin.go:445
|
||||
#: pkg/season/admin.go:492
|
||||
msgid "Start date must be a valid date."
|
||||
msgstr "La fecha de inicio tiene que ser una fecha válida."
|
||||
|
||||
#: pkg/season/admin.go:447
|
||||
#: pkg/season/admin.go:494
|
||||
msgid "End date can not be empty."
|
||||
msgstr "No podéis dejar la fecha final en blanco."
|
||||
|
||||
#: pkg/season/admin.go:448
|
||||
#: pkg/season/admin.go:495
|
||||
msgid "End date must be a valid date."
|
||||
msgstr "La fecha final tiene que ser una fecha válida."
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert camper:season_i18n from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop table if exists camper.season_i18n;
|
||||
|
||||
commit;
|
|
@ -0,0 +1,7 @@
|
|||
-- Revert camper:translate_season from pg
|
||||
|
||||
begin;
|
||||
|
||||
drop function if exists camper.translate_season(uuid, text, text);
|
||||
|
||||
commit;
|
|
@ -88,3 +88,5 @@ campsite_type_cost [roles schema_camper campsite_type season user_profile] 2023-
|
|||
parse_price [roles schema_camper] 2023-10-01T16:27:50Z jordi fita mas <jordi@tandem.blog> # Add function to format cents to prices
|
||||
to_price [roles schema_camper] 2023-10-01T16:30:40Z jordi fita mas <jordi@tandem.blog> # Add function to format cents to prices
|
||||
set_campsite_type_cost [roles schema_camper campsite_type_cost parse_price] 2023-10-01T17:51:23Z jordi fita mas <jordi@tandem.blog> # Add function to set the cost of a campsite type for a given season
|
||||
season_i18n [roles schema_camper season language] 2023-10-03T18:30:42Z jordi fita mas <jordi@tandem.blog> # Add relation for season translations
|
||||
translate_season [roles schema_camper season season_i18n] 2023-10-03T18:37:19Z jordi fita mas <jordi@tandem.blog> # Add function to translate seasons
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
-- Test season_i18n
|
||||
set client_min_messages to warning;
|
||||
create extension if not exists pgtap;
|
||||
reset client_min_messages;
|
||||
|
||||
begin;
|
||||
|
||||
select plan(23);
|
||||
|
||||
set search_path to camper, public;
|
||||
|
||||
select has_table('season_i18n');
|
||||
select has_pk('season_i18n');
|
||||
select col_is_pk('season_i18n', array['season_id', 'lang_tag']);
|
||||
select table_privs_are('season_i18n', 'guest', array['SELECT']);
|
||||
select table_privs_are('season_i18n', 'employee', array['SELECT']);
|
||||
select table_privs_are('season_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||
select table_privs_are('season_i18n', 'authenticator', array[]::text[]);
|
||||
|
||||
select has_column('season_i18n', 'season_id');
|
||||
select col_is_fk('season_i18n', 'season_id');
|
||||
select fk_ok('season_i18n', 'season_id', 'season', 'season_id');
|
||||
select col_type_is('season_i18n', 'season_id', 'integer');
|
||||
select col_not_null('season_i18n', 'season_id');
|
||||
select col_hasnt_default('season_i18n', 'season_id');
|
||||
|
||||
select has_column('season_i18n', 'lang_tag');
|
||||
select col_is_fk('season_i18n', 'lang_tag');
|
||||
select fk_ok('season_i18n', 'lang_tag', 'language', 'lang_tag');
|
||||
select col_type_is('season_i18n', 'lang_tag', 'text');
|
||||
select col_not_null('season_i18n', 'lang_tag');
|
||||
select col_hasnt_default('season_i18n', 'lang_tag');
|
||||
|
||||
select has_column('season_i18n', 'name');
|
||||
select col_type_is('season_i18n', 'name', 'text');
|
||||
select col_not_null('season_i18n', 'name');
|
||||
select col_hasnt_default('season_i18n', 'name');
|
||||
|
||||
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
-- Test translate_season
|
||||
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_season', array['uuid', 'text', 'text']);
|
||||
select function_lang_is('camper', 'translate_season', array['uuid', 'text', 'text'], 'sql');
|
||||
select function_returns('camper', 'translate_season', array['uuid', 'text', 'text'], 'void');
|
||||
select isnt_definer('camper', 'translate_season', array['uuid', 'text', 'text']);
|
||||
select volatility_is('camper', 'translate_season', array['uuid', 'text', 'text'], 'volatile');
|
||||
select function_privs_are('camper', 'translate_season', array['uuid', 'text', 'text'], 'guest', array[]::text[]);
|
||||
select function_privs_are('camper', 'translate_season', array['uuid', 'text', 'text'], 'employee', array[]::text[]);
|
||||
select function_privs_are('camper', 'translate_season', array['uuid', 'text', 'text'], 'admin', array['EXECUTE']);
|
||||
select function_privs_are('camper', 'translate_season', array['uuid', 'text', 'text'], 'authenticator', array[]::text[]);
|
||||
|
||||
set client_min_messages to warning;
|
||||
truncate season_i18n cascade;
|
||||
truncate season 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, country_code, currency_code, default_lang_tag)
|
||||
values (1, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', 'ES', 'EUR', 'ca')
|
||||
;
|
||||
|
||||
insert into season (season_id, company_id, slug, name, color, active)
|
||||
values (2, 1, '87452b88-b48f-48d3-bb6c-0296de64164e', 'High', to_integer('#232323'), true)
|
||||
, (3, 1, '9b6370f7-f941-46f2-bc6e-de455675bd0a', 'Low', to_integer('#323232'), false)
|
||||
;
|
||||
|
||||
insert into season_i18n (season_id, lang_tag, name)
|
||||
values (3, 'ca', 'baixa')
|
||||
;
|
||||
|
||||
|
||||
select lives_ok(
|
||||
$$ select translate_season('87452b88-b48f-48d3-bb6c-0296de64164e', 'ca', 'Alta') $$,
|
||||
'Should be able to translate the first season'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select translate_season('9b6370f7-f941-46f2-bc6e-de455675bd0a', 'es', 'Baja') $$,
|
||||
'Should be able to translate the second season'
|
||||
);
|
||||
|
||||
select lives_ok(
|
||||
$$ select translate_season('9b6370f7-f941-46f2-bc6e-de455675bd0a', 'ca', 'Baixa') $$,
|
||||
'Should be able to overwrite the catalan translation of the second season'
|
||||
);
|
||||
|
||||
select bag_eq(
|
||||
$$ select slug::text, lang_tag, i18n.name from season_i18n as i18n join season using (season_id) $$,
|
||||
$$ values ('87452b88-b48f-48d3-bb6c-0296de64164e', 'ca', 'Alta')
|
||||
, ('9b6370f7-f941-46f2-bc6e-de455675bd0a', 'ca', 'Baixa')
|
||||
, ('9b6370f7-f941-46f2-bc6e-de455675bd0a', 'es', 'Baja')
|
||||
$$,
|
||||
'Should have added and updated all translations.'
|
||||
);
|
||||
select *
|
||||
from finish();
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,11 @@
|
|||
-- Verify camper:season_i18n on pg
|
||||
|
||||
begin;
|
||||
|
||||
select season_id
|
||||
, lang_tag
|
||||
, name
|
||||
from camper.season_i18n
|
||||
where false;
|
||||
|
||||
rollback;
|
|
@ -0,0 +1,7 @@
|
|||
-- Verify camper:translate_season on pg
|
||||
|
||||
begin;
|
||||
|
||||
select has_function_privilege('camper.translate_season(uuid, text, text)', 'execute');
|
||||
|
||||
rollback;
|
|
@ -48,7 +48,7 @@
|
|||
<footer>
|
||||
<button type="submit"><span class="sr-only">{{( pgettext "Cancel" "action" )}}</span></button>
|
||||
{{ range .Seasons -}}
|
||||
<button type="submit" name="season_id" value="{{ .Slug }}">
|
||||
<button type="submit" name="season_id" value="{{ .ID }}">
|
||||
<svg width="20px" height="20px">
|
||||
<circle cx="50%" cy="50%" r="49%" fill="{{ .Color }}" stroke="#000"
|
||||
stroke-width=".5"/>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<tr>
|
||||
<th scope="col">{{( pgettext "Color" "header" )}}</th>
|
||||
<th scope="col">{{( pgettext "Name" "header" )}}</th>
|
||||
<th scope="col">{{( pgettext "Translations" "campsite type" )}}</th>
|
||||
<th scope="col">{{( pgettext "Active" "season" )}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -27,7 +28,14 @@
|
|||
<circle cx="50%" cy="50%" r="49%" fill="{{ .Color }}" stroke="#000" stroke-width=".5"/>
|
||||
</svg>
|
||||
</td>
|
||||
<td><a href="/admin/seasons/{{ .Slug }}">{{ .Name }}</a></td>
|
||||
<td><a href="{{ .URL }}">{{ .Name }}</a></td>
|
||||
<td>
|
||||
{{ range .Translations }}
|
||||
<a
|
||||
{{ if .Missing }}class="missing-translation"{{ end }}
|
||||
href="{{ .URL }}">{{ .Endonym }}</a>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
|
||||
</tr>
|
||||
{{- end }}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<!--
|
||||
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/season.seasonL10nForm*/ -}}
|
||||
{{printf (pgettext "Translate Season to %s" "title") .Locale.Endonym }}
|
||||
{{- end }}
|
||||
|
||||
{{ define "content" -}}
|
||||
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/season.seasonL10nForm*/ -}}
|
||||
<form data-hx-put="/admin/seasons/{{ .Slug }}/{{ .Locale.Language }}">
|
||||
<h2>
|
||||
{{printf (pgettext "Translate Season to %s" "title") .Locale.Endonym }}
|
||||
</h2>
|
||||
{{ CSRFInput }}
|
||||
<fieldset>
|
||||
{{ with .Name -}}
|
||||
<fieldset>
|
||||
<legend>{{( pgettext "Name" "input")}}</legend>
|
||||
{{( gettext "Source:" )}} {{ .Source }}<br>
|
||||
<label>
|
||||
{{( pgettext "Translation:" "input" )}}
|
||||
<input type="text" name="{{ .Name }}" value="{{ .Val }}"
|
||||
required {{ template "error-attrs" . }}><br>
|
||||
</label>
|
||||
{{ template "error-message" . }}
|
||||
</fieldset>
|
||||
{{- end }}
|
||||
</fieldset>
|
||||
<footer>
|
||||
<button type="submit">{{( pgettext "Translate" "action" )}}</button>
|
||||
</footer>
|
||||
</form>
|
||||
{{- end }}
|
Loading…
Reference in New Issue