Add internationalization and localization of campsite types
I am not happy with the localization interface for admins, but it is the easier that i could think of (for me, i guess), with a separate for each language. I am not at all proud of the use of RecordArray, but i did not see the need to create and register a type just to show the translation links. I might change my mind when i need to add more and more translation links, but only it the current interface remains, which i am not that sure at the moment.
This commit is contained in:
parent
7d8cf5439b
commit
f48936f800
|
@ -35,8 +35,18 @@ alter sequence campsite_type_campsite_type_id_seq restart with 72;
|
||||||
insert into campsite_type (company_id, name, media_id, description)
|
insert into campsite_type (company_id, name, media_id, description)
|
||||||
values (52, 'Parceŀles', 62, '')
|
values (52, 'Parceŀles', 62, '')
|
||||||
, (52, 'Safari Tents', 63, '')
|
, (52, 'Safari Tents', 63, '')
|
||||||
, (52, 'Bungalows', 64, '')
|
, (52, 'Bungalous', 64, '')
|
||||||
, (52, 'Cabanes de fusta', 65, '')
|
, (52, 'Cabanes de fusta', 65, '')
|
||||||
;
|
;
|
||||||
|
insert into campsite_type_i18n (campsite_type_id, lang_tag, name, description)
|
||||||
|
values (72, 'en', 'Plots', '')
|
||||||
|
, (72, 'es', 'Parcelas', '')
|
||||||
|
, (73, 'en', 'Safari Tents', '')
|
||||||
|
, (73, 'es', 'Tiendas Safari', '')
|
||||||
|
, (74, 'en', 'Bungalows', '')
|
||||||
|
, (74, 'es', 'Bungalós', '')
|
||||||
|
, (75, 'en', 'Wooden Lodges', '')
|
||||||
|
, (75, 'es', 'Cabañas de madera', '')
|
||||||
|
;
|
||||||
|
|
||||||
commit;
|
commit;
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
-- Deploy camper:campsite_type_i18n to pg
|
||||||
|
-- requires: roles
|
||||||
|
-- requires: schema_camper
|
||||||
|
-- requires: campsite_type
|
||||||
|
-- requires: language
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
set search_path to camper, public;
|
||||||
|
|
||||||
|
create table campsite_type_i18n (
|
||||||
|
campsite_type_id integer not null references campsite_type,
|
||||||
|
lang_tag text not null references language,
|
||||||
|
name text not null,
|
||||||
|
description xml not null,
|
||||||
|
primary key (campsite_type_id, lang_tag)
|
||||||
|
);
|
||||||
|
|
||||||
|
grant select on table campsite_type_i18n to guest;
|
||||||
|
grant select on table campsite_type_i18n to employee;
|
||||||
|
grant select, insert, update, delete on table campsite_type_i18n to admin;
|
||||||
|
|
||||||
|
commit;
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"dev.tandem.ws/tandem/camper/pkg/company"
|
"dev.tandem.ws/tandem/camper/pkg/company"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||||
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/season"
|
"dev.tandem.ws/tandem/camper/pkg/season"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||||
)
|
)
|
||||||
|
@ -23,9 +24,9 @@ type adminHandler struct {
|
||||||
season *season.AdminHandler
|
season *season.AdminHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAdminHandler() *adminHandler {
|
func newAdminHandler(locales locale.Locales) *adminHandler {
|
||||||
return &adminHandler{
|
return &adminHandler{
|
||||||
campsite: campsite.NewAdminHandler(),
|
campsite: campsite.NewAdminHandler(locales),
|
||||||
company: company.NewAdminHandler(),
|
company: company.NewAdminHandler(),
|
||||||
season: season.NewAdminHandler(),
|
season: season.NewAdminHandler(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func New(db *database.DB, avatarsDir string, mediaDir string) (http.Handler, err
|
||||||
db: db,
|
db: db,
|
||||||
fileHandler: static,
|
fileHandler: static,
|
||||||
profile: profile,
|
profile: profile,
|
||||||
admin: newAdminHandler(),
|
admin: newAdminHandler(locales),
|
||||||
public: newPublicHandler(),
|
public: newPublicHandler(),
|
||||||
media: media,
|
media: media,
|
||||||
locales: locales,
|
locales: locales,
|
||||||
|
|
|
@ -65,7 +65,16 @@ type campsiteType struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustCollectCampsiteTypes(ctx context.Context, company *auth.Company, conn *database.Conn, loc *locale.Locale) []*campsiteType {
|
func mustCollectCampsiteTypes(ctx context.Context, company *auth.Company, conn *database.Conn, loc *locale.Locale) []*campsiteType {
|
||||||
rows, err := conn.Query(ctx, "select name, '/campsites/types/' || slug, '/media/' || encode(hash, 'hex') || '/' || original_filename from campsite_type join media using (media_id) where campsite_type.company_id = $1 and campsite_type.active", company.ID)
|
rows, err := conn.Query(ctx, `
|
||||||
|
select coalesce(i18n.name, campsite_type.name) as l10_name
|
||||||
|
, '/campsites/types/' || slug
|
||||||
|
, '/media/' || encode(hash, 'hex') || '/' || original_filename
|
||||||
|
from campsite_type
|
||||||
|
left join campsite_type_i18n as i18n on campsite_type.campsite_type_id = i18n.campsite_type_id and lang_tag = $1
|
||||||
|
join media using (media_id)
|
||||||
|
where campsite_type.company_id = $2
|
||||||
|
and campsite_type.active
|
||||||
|
`, loc.Language, company.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,9 @@ type AdminHandler struct {
|
||||||
types *types.AdminHandler
|
types *types.AdminHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAdminHandler() *AdminHandler {
|
func NewAdminHandler(locales locale.Locales) *AdminHandler {
|
||||||
return &AdminHandler{
|
return &AdminHandler{
|
||||||
types: &types.AdminHandler{},
|
types: types.NewAdminHandler(locales),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/jackc/pgtype"
|
||||||
|
"github.com/jackc/pgx/v4"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"dev.tandem.ws/tandem/camper/pkg/auth"
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/form"
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
||||||
|
@ -20,6 +24,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminHandler struct {
|
type AdminHandler struct {
|
||||||
|
locales locale.Locales
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminHandler(locales locale.Locales) *AdminHandler {
|
||||||
|
return &AdminHandler{locales}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
||||||
|
@ -58,6 +67,18 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
|
||||||
}
|
}
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
h.TypeHandler(user, company, conn, f).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminHandler) TypeHandler(user *auth.User, company *auth.Company, conn *database.Conn, f *typeForm) 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 {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
f.MustRender(w, r, user, company)
|
f.MustRender(w, r, user, company)
|
||||||
|
@ -66,6 +87,29 @@ func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *dat
|
||||||
default:
|
default:
|
||||||
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
tag, err := language.Parse(head)
|
||||||
|
if err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loc, ok := h.locales[tag]
|
||||||
|
if !ok {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l10n := newTypeL10nForm(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:
|
||||||
|
editTypeL10n(w, r, user, company, conn, l10n)
|
||||||
|
default:
|
||||||
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -85,10 +129,32 @@ type typeEntry struct {
|
||||||
Slug string
|
Slug string
|
||||||
Name string
|
Name string
|
||||||
Active bool
|
Active bool
|
||||||
|
Translations []*translation
|
||||||
|
}
|
||||||
|
|
||||||
|
type translation struct {
|
||||||
|
Language string
|
||||||
|
Endonym string
|
||||||
|
Missing bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectTypeEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*typeEntry, error) {
|
func collectTypeEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*typeEntry, error) {
|
||||||
rows, err := conn.Query(ctx, "select slug, name, active from campsite_type where company_id = $1 order by name", company.ID)
|
rows, err := conn.Query(ctx, `
|
||||||
|
select campsite_type.slug
|
||||||
|
, campsite_type.name
|
||||||
|
, campsite_type.active
|
||||||
|
, array_agg((lang_tag, endonym, not exists (select 1 from campsite_type_i18n as i18n where i18n.campsite_type_id = campsite_type.campsite_type_id and i18n.lang_tag = language.lang_tag)) order by endonym)
|
||||||
|
from campsite_type
|
||||||
|
join company using (company_id)
|
||||||
|
, language
|
||||||
|
where lang_tag <> default_lang_tag
|
||||||
|
and language.selectable
|
||||||
|
and campsite_type.company_id = $1
|
||||||
|
group by campsite_type.slug
|
||||||
|
, campsite_type.name
|
||||||
|
, campsite_type.active
|
||||||
|
order by name
|
||||||
|
`, pgx.QueryResultFormats{pgx.BinaryFormatCode}, company.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -97,9 +163,17 @@ func collectTypeEntries(ctx context.Context, company *auth.Company, conn *databa
|
||||||
var types []*typeEntry
|
var types []*typeEntry
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
entry := &typeEntry{}
|
entry := &typeEntry{}
|
||||||
if err = rows.Scan(&entry.Slug, &entry.Name, &entry.Active); err != nil {
|
var translations pgtype.RecordArray
|
||||||
|
if err = rows.Scan(&entry.Slug, &entry.Name, &entry.Active, &translations); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, el := range translations.Elements {
|
||||||
|
entry.Translations = append(entry.Translations, &translation{
|
||||||
|
el.Fields[0].Get().(string),
|
||||||
|
el.Fields[1].Get().(string),
|
||||||
|
el.Fields[2].Get().(bool),
|
||||||
|
})
|
||||||
|
}
|
||||||
types = append(types, entry)
|
types = append(types, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
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 L10nInput struct {
|
||||||
|
form.Input
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeL10nForm struct {
|
||||||
|
Locale *locale.Locale
|
||||||
|
Slug string
|
||||||
|
Name *L10nInput
|
||||||
|
Description *L10nInput
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTypeL10nForm(f *typeForm, loc *locale.Locale) *typeL10nForm {
|
||||||
|
return &typeL10nForm{
|
||||||
|
Locale: loc,
|
||||||
|
Slug: f.Slug,
|
||||||
|
Name: &L10nInput{
|
||||||
|
Input: form.Input{
|
||||||
|
Name: f.Name.Name,
|
||||||
|
},
|
||||||
|
Source: f.Name.Val,
|
||||||
|
},
|
||||||
|
Description: &L10nInput{
|
||||||
|
Input: form.Input{
|
||||||
|
Name: f.Description.Name,
|
||||||
|
},
|
||||||
|
Source: f.Description.Val,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l10n *typeL10nForm) FillFromDatabase(ctx context.Context, conn *database.Conn) error {
|
||||||
|
row := conn.QueryRow(ctx, `
|
||||||
|
select coalesce(i18n.name, '') as l10n_name
|
||||||
|
, coalesce(i18n.description, '') as l10n_description
|
||||||
|
from campsite_type
|
||||||
|
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
|
||||||
|
`, l10n.Locale.Language, l10n.Slug)
|
||||||
|
return row.Scan(&l10n.Name.Val, &l10n.Description.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l10n *typeL10nForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
||||||
|
template.MustRenderAdmin(w, r, user, company, "campsite/type/l10n.gohtml", l10n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editTypeL10n(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, l10n *typeL10nForm) {
|
||||||
|
if err := l10n.Parse(r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := user.VerifyCSRFToken(r); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !l10n.Valid(user.Locale) {
|
||||||
|
if !httplib.IsHTMxRequest(r) {
|
||||||
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||||
|
}
|
||||||
|
l10n.MustRender(w, r, user, company)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.MustExec(r.Context(), `
|
||||||
|
insert into campsite_type_i18n (campsite_type_id, lang_tag, name, description)
|
||||||
|
select campsite_type_id, $1, $2, $3
|
||||||
|
from campsite_type
|
||||||
|
where slug = $4
|
||||||
|
on conflict (campsite_type_id, lang_tag) do update
|
||||||
|
set name = excluded.name
|
||||||
|
, description = excluded.description
|
||||||
|
`, l10n.Locale.Language, l10n.Name, l10n.Description, l10n.Slug)
|
||||||
|
httplib.Redirect(w, r, "/admin/campsites/types", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l10n *typeL10nForm) Parse(r *http.Request) error {
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l10n.Name.FillValue(r)
|
||||||
|
l10n.Description.FillValue(r)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l10n *typeL10nForm) Valid(l *locale.Locale) bool {
|
||||||
|
v := form.NewValidator(l)
|
||||||
|
v.CheckRequired(&l10n.Name.Input, l.GettextNoop("Name can not be empty."))
|
||||||
|
return v.AllOK
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
page, err := newPublicPage(r.Context(), conn, head)
|
page, err := newPublicPage(r.Context(), user, conn, head)
|
||||||
if database.ErrorIsNotFound(err) {
|
if database.ErrorIsNotFound(err) {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
|
@ -51,11 +51,18 @@ type publicPage struct {
|
||||||
Description gotemplate.HTML
|
Description gotemplate.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPublicPage(ctx context.Context, conn *database.Conn, slug string) (*publicPage, error) {
|
func newPublicPage(ctx context.Context, user *auth.User, conn *database.Conn, slug string) (*publicPage, error) {
|
||||||
page := &publicPage{
|
page := &publicPage{
|
||||||
PublicPage: template.NewPublicPage(),
|
PublicPage: template.NewPublicPage(),
|
||||||
}
|
}
|
||||||
row := conn.QueryRow(ctx, "select name, description::text from campsite_type where slug = $1 and active", slug)
|
row := conn.QueryRow(ctx, `
|
||||||
|
select coalesce(i18n.name, campsite_type.name) as l10n_name
|
||||||
|
, coalesce(i18n.description, campsite_type.description)::text as l10n_description
|
||||||
|
from campsite_type
|
||||||
|
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)
|
||||||
if err := row.Scan(&page.Name, &page.Description); err != nil {
|
if err := row.Scan(&page.Name, &page.Description); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,14 @@ func (p *PublicPage) Setup(r *http.Request, user *auth.User, company *auth.Compa
|
||||||
sort.Slice(p.LocalizedAlternates, func(i, j int) bool { return p.LocalizedAlternates[i].Lang < p.LocalizedAlternates[j].Lang })
|
sort.Slice(p.LocalizedAlternates, func(i, j int) bool { return p.LocalizedAlternates[i].Lang < p.LocalizedAlternates[j].Lang })
|
||||||
|
|
||||||
p.Menu = &siteMenu{
|
p.Menu = &siteMenu{
|
||||||
CampsiteTypes: mustCollectMenuItems(r.Context(), conn, user.Locale, "select name, '/campsites/types/' || slug from campsite_type where company_id = $1 and active", company.ID),
|
CampsiteTypes: mustCollectMenuItems(r.Context(), conn, user.Locale, `
|
||||||
|
select coalesce(i18n.name, campsite_type.name) as l10n_name
|
||||||
|
, '/campsites/types/' || slug
|
||||||
|
from campsite_type
|
||||||
|
left join campsite_type_i18n as i18n on campsite_type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1
|
||||||
|
where company_id = $2
|
||||||
|
and active
|
||||||
|
`, user.Locale.Language, company.ID),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
po/ca.po
54
po/ca.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: camper\n"
|
"Project-Id-Version: camper\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-09-11 04:08+0200\n"
|
"POT-Creation-Date: 2023-09-12 20:18+0200\n"
|
||||||
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
|
"PO-Revision-Date: 2023-07-22 23:45+0200\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Catalan <ca@dodds.net>\n"
|
"Language-Team: Catalan <ca@dodds.net>\n"
|
||||||
|
@ -18,7 +18,7 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:39
|
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:28
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Inici"
|
msgstr "Inici"
|
||||||
|
@ -69,7 +69,7 @@ msgid "Come and enjoy!"
|
||||||
msgstr "Vine a gaudir!"
|
msgstr "Vine a gaudir!"
|
||||||
|
|
||||||
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:23
|
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:23
|
||||||
#: web/templates/public/layout.gohtml:60
|
#: web/templates/public/layout.gohtml:58
|
||||||
msgid "Campsite Montagut"
|
msgid "Campsite Montagut"
|
||||||
msgstr "Càmping Montagut"
|
msgstr "Càmping Montagut"
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ msgstr "Càmping Montagut"
|
||||||
msgid "Skip to main content"
|
msgid "Skip to main content"
|
||||||
msgstr "Salta al contingut principal"
|
msgstr "Salta al contingut principal"
|
||||||
|
|
||||||
#: web/templates/public/layout.gohtml:44
|
#: web/templates/public/layout.gohtml:32
|
||||||
msgid "Singular Lodges"
|
msgid "Singular Lodges"
|
||||||
msgstr "Allotjaments singulars"
|
msgstr "Allotjaments singulars"
|
||||||
|
|
||||||
|
@ -150,13 +150,13 @@ msgid "Type"
|
||||||
msgstr "Tipus"
|
msgstr "Tipus"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/index.gohtml:28
|
#: web/templates/admin/campsite/index.gohtml:28
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:26
|
#: web/templates/admin/campsite/type/index.gohtml:36
|
||||||
#: web/templates/admin/season/index.gohtml:32
|
#: web/templates/admin/season/index.gohtml:32
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr "Sí"
|
msgstr "Sí"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/index.gohtml:28
|
#: web/templates/admin/campsite/index.gohtml:28
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:26
|
#: web/templates/admin/campsite/type/index.gohtml:36
|
||||||
#: web/templates/admin/season/index.gohtml:32
|
#: web/templates/admin/season/index.gohtml:32
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr "No"
|
msgstr "No"
|
||||||
|
@ -178,12 +178,13 @@ msgid "New Campsite Type"
|
||||||
msgstr "Nou tipus d’allotjament"
|
msgstr "Nou tipus d’allotjament"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/form.gohtml:39
|
#: web/templates/admin/campsite/type/form.gohtml:39
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:19
|
#: web/templates/admin/campsite/type/index.gohtml:20
|
||||||
msgctxt "campsite type"
|
msgctxt "campsite type"
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr "Actiu"
|
msgstr "Actiu"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/form.gohtml:48
|
#: web/templates/admin/campsite/type/form.gohtml:48
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:21
|
||||||
#: web/templates/admin/season/form.gohtml:47
|
#: web/templates/admin/season/form.gohtml:47
|
||||||
#: web/templates/admin/profile.gohtml:26
|
#: web/templates/admin/profile.gohtml:26
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
|
@ -196,6 +197,7 @@ msgid "Cover image"
|
||||||
msgstr "Imatge de portada"
|
msgstr "Imatge de portada"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/form.gohtml:68
|
#: web/templates/admin/campsite/type/form.gohtml:68
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:33
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
@ -218,10 +220,37 @@ msgctxt "header"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nom"
|
msgstr "Nom"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:32
|
#: web/templates/admin/campsite/type/index.gohtml:19
|
||||||
|
msgctxt "campsite type"
|
||||||
|
msgid "Translations"
|
||||||
|
msgstr "Traduccions"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/index.gohtml:42
|
||||||
msgid "No campsite types added yet."
|
msgid "No campsite types added yet."
|
||||||
msgstr "No s’ha afegit cap tipus d’allotjament encara."
|
msgstr "No s’ha afegit cap tipus d’allotjament encara."
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:7
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:15
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Translate Campsite Type to %s"
|
||||||
|
msgstr "Traducció del tipus d’allotjament a %s"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:22
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:34
|
||||||
|
msgid "Source:"
|
||||||
|
msgstr "Origen:"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:24
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:37
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Translation:"
|
||||||
|
msgstr "Traducció:"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:46
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Translate"
|
||||||
|
msgstr "Tradueix"
|
||||||
|
|
||||||
#: web/templates/admin/season/form.gohtml:8
|
#: web/templates/admin/season/form.gohtml:8
|
||||||
#: web/templates/admin/season/form.gohtml:26
|
#: web/templates/admin/season/form.gohtml:26
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
|
@ -434,7 +463,8 @@ msgctxt "language option"
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr "Automàtic"
|
msgstr "Automàtic"
|
||||||
|
|
||||||
#: pkg/app/user.go:249 pkg/campsite/types/admin.go:227 pkg/season/admin.go:203
|
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:105
|
||||||
|
#: pkg/campsite/types/admin.go:301 pkg/season/admin.go:203
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podeu deixar el nom en blanc."
|
msgstr "No podeu deixar el nom en blanc."
|
||||||
|
|
||||||
|
@ -446,15 +476,15 @@ msgstr "La confirmació no es correspon amb la contrasenya."
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "L’idioma escollit no és vàlid."
|
msgstr "L’idioma escollit no és vàlid."
|
||||||
|
|
||||||
#: pkg/app/user.go:253 pkg/campsite/types/admin.go:229
|
#: pkg/app/user.go:253 pkg/campsite/types/admin.go:303
|
||||||
msgid "File must be a valid PNG or JPEG image."
|
msgid "File must be a valid PNG or JPEG image."
|
||||||
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."
|
msgstr "El fitxer has de ser una imatge PNG o JPEG vàlida."
|
||||||
|
|
||||||
#: pkg/app/admin.go:43
|
#: pkg/app/admin.go:44
|
||||||
msgid "Access forbidden"
|
msgid "Access forbidden"
|
||||||
msgstr "Accés prohibit"
|
msgstr "Accés prohibit"
|
||||||
|
|
||||||
#: pkg/campsite/types/admin.go:231
|
#: pkg/campsite/types/admin.go:305
|
||||||
msgid "Cover image can not be empty."
|
msgid "Cover image can not be empty."
|
||||||
msgstr "No podeu deixar la imatge de portada en blanc."
|
msgstr "No podeu deixar la imatge de portada en blanc."
|
||||||
|
|
||||||
|
|
54
po/es.po
54
po/es.po
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: camper\n"
|
"Project-Id-Version: camper\n"
|
||||||
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
|
||||||
"POT-Creation-Date: 2023-09-11 04:08+0200\n"
|
"POT-Creation-Date: 2023-09-12 20:18+0200\n"
|
||||||
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
|
"PO-Revision-Date: 2023-07-22 23:46+0200\n"
|
||||||
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
|
||||||
"Language-Team: Spanish <es@tp.org.es>\n"
|
"Language-Team: Spanish <es@tp.org.es>\n"
|
||||||
|
@ -18,7 +18,7 @@ msgstr ""
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:39
|
#: web/templates/public/home.gohtml:6 web/templates/public/layout.gohtml:28
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "Inicio"
|
msgstr "Inicio"
|
||||||
|
@ -69,7 +69,7 @@ msgid "Come and enjoy!"
|
||||||
msgstr "¡Ven a disfrutar!"
|
msgstr "¡Ven a disfrutar!"
|
||||||
|
|
||||||
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:23
|
#: web/templates/public/layout.gohtml:11 web/templates/public/layout.gohtml:23
|
||||||
#: web/templates/public/layout.gohtml:60
|
#: web/templates/public/layout.gohtml:58
|
||||||
msgid "Campsite Montagut"
|
msgid "Campsite Montagut"
|
||||||
msgstr "Camping Montagut"
|
msgstr "Camping Montagut"
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ msgstr "Camping Montagut"
|
||||||
msgid "Skip to main content"
|
msgid "Skip to main content"
|
||||||
msgstr "Saltar al contenido principal"
|
msgstr "Saltar al contenido principal"
|
||||||
|
|
||||||
#: web/templates/public/layout.gohtml:44
|
#: web/templates/public/layout.gohtml:32
|
||||||
msgid "Singular Lodges"
|
msgid "Singular Lodges"
|
||||||
msgstr "Alojamientos singulares"
|
msgstr "Alojamientos singulares"
|
||||||
|
|
||||||
|
@ -150,13 +150,13 @@ msgid "Type"
|
||||||
msgstr "Tipo"
|
msgstr "Tipo"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/index.gohtml:28
|
#: web/templates/admin/campsite/index.gohtml:28
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:26
|
#: web/templates/admin/campsite/type/index.gohtml:36
|
||||||
#: web/templates/admin/season/index.gohtml:32
|
#: web/templates/admin/season/index.gohtml:32
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr "Sí"
|
msgstr "Sí"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/index.gohtml:28
|
#: web/templates/admin/campsite/index.gohtml:28
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:26
|
#: web/templates/admin/campsite/type/index.gohtml:36
|
||||||
#: web/templates/admin/season/index.gohtml:32
|
#: web/templates/admin/season/index.gohtml:32
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr "No"
|
msgstr "No"
|
||||||
|
@ -178,12 +178,13 @@ msgid "New Campsite Type"
|
||||||
msgstr "Nuevo tipo de alojamiento"
|
msgstr "Nuevo tipo de alojamiento"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/form.gohtml:39
|
#: web/templates/admin/campsite/type/form.gohtml:39
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:19
|
#: web/templates/admin/campsite/type/index.gohtml:20
|
||||||
msgctxt "campsite type"
|
msgctxt "campsite type"
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr "Activo"
|
msgstr "Activo"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/form.gohtml:48
|
#: web/templates/admin/campsite/type/form.gohtml:48
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:21
|
||||||
#: web/templates/admin/season/form.gohtml:47
|
#: web/templates/admin/season/form.gohtml:47
|
||||||
#: web/templates/admin/profile.gohtml:26
|
#: web/templates/admin/profile.gohtml:26
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
|
@ -196,6 +197,7 @@ msgid "Cover image"
|
||||||
msgstr "Imagen de portada"
|
msgstr "Imagen de portada"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/form.gohtml:68
|
#: web/templates/admin/campsite/type/form.gohtml:68
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:33
|
||||||
msgctxt "input"
|
msgctxt "input"
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripción"
|
msgstr "Descripción"
|
||||||
|
@ -218,10 +220,37 @@ msgctxt "header"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Nombre"
|
msgstr "Nombre"
|
||||||
|
|
||||||
#: web/templates/admin/campsite/type/index.gohtml:32
|
#: web/templates/admin/campsite/type/index.gohtml:19
|
||||||
|
msgctxt "campsite type"
|
||||||
|
msgid "Translations"
|
||||||
|
msgstr "Traducciones"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/index.gohtml:42
|
||||||
msgid "No campsite types added yet."
|
msgid "No campsite types added yet."
|
||||||
msgstr "No se ha añadido ningún tipo de alojamiento todavía."
|
msgstr "No se ha añadido ningún tipo de alojamiento todavía."
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:7
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:15
|
||||||
|
msgctxt "title"
|
||||||
|
msgid "Translate Campsite Type to %s"
|
||||||
|
msgstr "Traducción de tipo de alojamiento a %s"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:22
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:34
|
||||||
|
msgid "Source:"
|
||||||
|
msgstr "Origen:"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:24
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:37
|
||||||
|
msgctxt "input"
|
||||||
|
msgid "Translation:"
|
||||||
|
msgstr "Traducción"
|
||||||
|
|
||||||
|
#: web/templates/admin/campsite/type/l10n.gohtml:46
|
||||||
|
msgctxt "action"
|
||||||
|
msgid "Translate"
|
||||||
|
msgstr "Traducir"
|
||||||
|
|
||||||
#: web/templates/admin/season/form.gohtml:8
|
#: web/templates/admin/season/form.gohtml:8
|
||||||
#: web/templates/admin/season/form.gohtml:26
|
#: web/templates/admin/season/form.gohtml:26
|
||||||
msgctxt "title"
|
msgctxt "title"
|
||||||
|
@ -434,7 +463,8 @@ msgctxt "language option"
|
||||||
msgid "Automatic"
|
msgid "Automatic"
|
||||||
msgstr "Automático"
|
msgstr "Automático"
|
||||||
|
|
||||||
#: pkg/app/user.go:249 pkg/campsite/types/admin.go:227 pkg/season/admin.go:203
|
#: pkg/app/user.go:249 pkg/campsite/types/l10n.go:105
|
||||||
|
#: pkg/campsite/types/admin.go:301 pkg/season/admin.go:203
|
||||||
msgid "Name can not be empty."
|
msgid "Name can not be empty."
|
||||||
msgstr "No podéis dejar el nombre en blanco."
|
msgstr "No podéis dejar el nombre en blanco."
|
||||||
|
|
||||||
|
@ -446,15 +476,15 @@ msgstr "La confirmación no se corresponde con la contraseña."
|
||||||
msgid "Selected language is not valid."
|
msgid "Selected language is not valid."
|
||||||
msgstr "El idioma escogido no es válido."
|
msgstr "El idioma escogido no es válido."
|
||||||
|
|
||||||
#: pkg/app/user.go:253 pkg/campsite/types/admin.go:229
|
#: pkg/app/user.go:253 pkg/campsite/types/admin.go:303
|
||||||
msgid "File must be a valid PNG or JPEG image."
|
msgid "File must be a valid PNG or JPEG image."
|
||||||
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."
|
msgstr "El archivo tiene que ser una imagen PNG o JPEG válida."
|
||||||
|
|
||||||
#: pkg/app/admin.go:43
|
#: pkg/app/admin.go:44
|
||||||
msgid "Access forbidden"
|
msgid "Access forbidden"
|
||||||
msgstr "Acceso prohibido"
|
msgstr "Acceso prohibido"
|
||||||
|
|
||||||
#: pkg/campsite/types/admin.go:231
|
#: pkg/campsite/types/admin.go:305
|
||||||
msgid "Cover image can not be empty."
|
msgid "Cover image can not be empty."
|
||||||
msgstr "No podéis dejar la imagen de portada en blanco."
|
msgstr "No podéis dejar la imagen de portada en blanco."
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- Revert camper:campsite_type_i18n from pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
drop table if exists camper.campsite_type_i18n;
|
||||||
|
|
||||||
|
commit;
|
|
@ -43,6 +43,7 @@ media_type [schema_camper] 2023-09-08T17:17:02Z jordi fita mas <jordi@tandem.blo
|
||||||
media [roles schema_camper company user_profile media_type] 2023-09-08T16:50:55Z jordi fita mas <jordi@tandem.blog> # Add relation of uploaded media
|
media [roles schema_camper company user_profile media_type] 2023-09-08T16:50:55Z jordi fita mas <jordi@tandem.blog> # Add relation of uploaded media
|
||||||
add_media [roles schema_camper media media_type] 2023-09-08T17:40:28Z jordi fita mas <jordi@tandem.blog> # Add function to create media
|
add_media [roles schema_camper media media_type] 2023-09-08T17:40:28Z jordi fita mas <jordi@tandem.blog> # Add function to create media
|
||||||
campsite_type [roles schema_camper company media user_profile] 2023-07-31T11:20:29Z jordi fita mas <jordi@tandem.blog> # Add relation of campsite type
|
campsite_type [roles schema_camper company media user_profile] 2023-07-31T11:20:29Z jordi fita mas <jordi@tandem.blog> # Add relation of campsite type
|
||||||
|
campsite_type_i18n [roles schema_camper campsite_type language] 2023-09-12T10:31:29Z jordi fita mas <jordi@tandem.blog> # Add relation for campsite_type translations
|
||||||
add_campsite_type [roles schema_camper campsite_type company] 2023-08-04T16:14:48Z jordi fita mas <jordi@tandem.blog> # Add function to create campsite types
|
add_campsite_type [roles schema_camper campsite_type company] 2023-08-04T16:14:48Z jordi fita mas <jordi@tandem.blog> # Add function to create campsite types
|
||||||
edit_campsite_type [roles schema_camper campsite_type company] 2023-08-07T22:21:34Z jordi fita mas <jordi@tandem.blog> # Add function to edit campsite types
|
edit_campsite_type [roles schema_camper campsite_type company] 2023-08-07T22:21:34Z jordi fita mas <jordi@tandem.blog> # Add function to edit campsite types
|
||||||
campsite [roles schema_camper company campsite_type user_profile] 2023-08-14T10:11:51Z jordi fita mas <jordi@tandem.blog> # Add campsite relation
|
campsite [roles schema_camper company campsite_type user_profile] 2023-08-14T10:11:51Z jordi fita mas <jordi@tandem.blog> # Add campsite relation
|
||||||
|
|
|
@ -7,7 +7,7 @@ begin;
|
||||||
|
|
||||||
set search_path to camper, public;
|
set search_path to camper, public;
|
||||||
|
|
||||||
select plan(12);
|
select plan(13);
|
||||||
|
|
||||||
select has_function('camper', 'add_campsite_type', array ['integer', 'integer', 'text', 'text']);
|
select has_function('camper', 'add_campsite_type', array ['integer', 'integer', 'text', 'text']);
|
||||||
select function_lang_is('camper', 'add_campsite_type', array ['integer', 'integer', 'text', 'text'], 'sql');
|
select function_lang_is('camper', 'add_campsite_type', array ['integer', 'integer', 'text', 'text'], 'sql');
|
||||||
|
@ -21,6 +21,7 @@ select function_privs_are('camper', 'add_campsite_type', array ['integer', 'inte
|
||||||
|
|
||||||
|
|
||||||
set client_min_messages to warning;
|
set client_min_messages to warning;
|
||||||
|
truncate campsite_type_i18n cascade;
|
||||||
truncate campsite_type cascade;
|
truncate campsite_type cascade;
|
||||||
truncate media cascade;
|
truncate media cascade;
|
||||||
truncate company cascade;
|
truncate company cascade;
|
||||||
|
@ -55,6 +56,11 @@ select bag_eq(
|
||||||
'Should have added all two campsite type'
|
'Should have added all two campsite type'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
select is_empty(
|
||||||
|
$$ select * from campsite_type_i18n $$,
|
||||||
|
'Should not have added any translation for campsite types.'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
select *
|
select *
|
||||||
from finish();
|
from finish();
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
-- Test campsite_type_i18n
|
||||||
|
set client_min_messages to warning;
|
||||||
|
create extension if not exists pgtap;
|
||||||
|
reset client_min_messages;
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select plan(27);
|
||||||
|
|
||||||
|
set search_path to camper, public;
|
||||||
|
|
||||||
|
select has_table('campsite_type_i18n');
|
||||||
|
select has_pk('campsite_type_i18n');
|
||||||
|
select col_is_pk('campsite_type_i18n', array['campsite_type_id', 'lang_tag']);
|
||||||
|
select table_privs_are('campsite_type_i18n', 'guest', array['SELECT']);
|
||||||
|
select table_privs_are('campsite_type_i18n', 'employee', array['SELECT']);
|
||||||
|
select table_privs_are('campsite_type_i18n', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
|
||||||
|
select table_privs_are('campsite_type_i18n', 'authenticator', array[]::text[]);
|
||||||
|
|
||||||
|
select has_column('campsite_type_i18n', 'campsite_type_id');
|
||||||
|
select col_is_fk('campsite_type_i18n', 'campsite_type_id');
|
||||||
|
select fk_ok('campsite_type_i18n', 'campsite_type_id', 'campsite_type', 'campsite_type_id');
|
||||||
|
select col_type_is('campsite_type_i18n', 'campsite_type_id', 'integer');
|
||||||
|
select col_not_null('campsite_type_i18n', 'campsite_type_id');
|
||||||
|
select col_hasnt_default('campsite_type_i18n', 'campsite_type_id');
|
||||||
|
|
||||||
|
select has_column('campsite_type_i18n', 'lang_tag');
|
||||||
|
select col_is_fk('campsite_type_i18n', 'lang_tag');
|
||||||
|
select fk_ok('campsite_type_i18n', 'lang_tag', 'language', 'lang_tag');
|
||||||
|
select col_type_is('campsite_type_i18n', 'lang_tag', 'text');
|
||||||
|
select col_not_null('campsite_type_i18n', 'lang_tag');
|
||||||
|
select col_hasnt_default('campsite_type_i18n', 'lang_tag');
|
||||||
|
|
||||||
|
select has_column('campsite_type_i18n', 'name');
|
||||||
|
select col_type_is('campsite_type_i18n', 'name', 'text');
|
||||||
|
select col_not_null('campsite_type_i18n', 'name');
|
||||||
|
select col_hasnt_default('campsite_type_i18n', 'name');
|
||||||
|
|
||||||
|
select has_column('campsite_type_i18n', 'description');
|
||||||
|
select col_type_is('campsite_type_i18n', 'description', 'xml');
|
||||||
|
select col_not_null('campsite_type_i18n', 'description');
|
||||||
|
select col_hasnt_default('campsite_type_i18n', 'description');
|
||||||
|
|
||||||
|
|
||||||
|
select *
|
||||||
|
from finish();
|
||||||
|
|
||||||
|
rollback;
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- Verify camper:campsite_type_i18n on pg
|
||||||
|
|
||||||
|
begin;
|
||||||
|
|
||||||
|
select campsite_type_id
|
||||||
|
, lang_tag
|
||||||
|
, name
|
||||||
|
, description
|
||||||
|
from camper.campsite_type_i18n
|
||||||
|
where false;
|
||||||
|
|
||||||
|
rollback;
|
|
@ -44,3 +44,7 @@ p, h1, h2, h3, h4, h5, h6 {
|
||||||
:any-link {
|
:any-link {
|
||||||
color: #0000ff;
|
color: #0000ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.missing-translation {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
|
@ -16,13 +16,23 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{{( pgettext "Name" "header" )}}</th>
|
<th scope="col">{{( pgettext "Name" "header" )}}</th>
|
||||||
|
<th scope="col">{{( pgettext "Translations" "campsite type" )}}</th>
|
||||||
<th scope="col">{{( pgettext "Active" "campsite type" )}}</th>
|
<th scope="col">{{( pgettext "Active" "campsite type" )}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .Types -}}
|
{{ range $type := .Types -}}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/admin/campsites/types/{{ .Slug }}">{{ .Name }}</a></td>
|
<td><a href="/admin/campsites/types/{{ .Slug }}">{{ .Name }}</a></td>
|
||||||
|
<td>
|
||||||
|
{{ range .Translations }}
|
||||||
|
<a
|
||||||
|
{{ if .Missing }}
|
||||||
|
class="missing-translation"
|
||||||
|
{{ end }}
|
||||||
|
href="/admin/campsites/types/{{ $type.Slug }}/{{ .Language }}">{{ .Endonym }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
<td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
|
<td>{{ if .Active }}{{( gettext "Yes" )}}{{ else }}{{( gettext "No" )}}{{ end }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!--
|
||||||
|
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/campsite/types.typeL10nForm*/ -}}
|
||||||
|
{{printf (pgettext "Translate Campsite Type to %s" "title") .Locale.Endonym }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{ define "content" -}}
|
||||||
|
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/campsite/types.typeL10nForm*/ -}}
|
||||||
|
{{ template "settings-tabs" "campsiteTypes" }}
|
||||||
|
<form data-hx-put="/admin/campsites/types/{{ .Slug }}/{{ .Locale.Language }}">
|
||||||
|
<h2>
|
||||||
|
{{printf (pgettext "Translate Campsite Type 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 }}
|
||||||
|
{{ with .Description -}}
|
||||||
|
<fieldset>
|
||||||
|
<legend{{( pgettext "Description" "input")}}></legend>
|
||||||
|
{{( gettext "Source:" )}}<br>
|
||||||
|
{{ .Source | raw }}<br>
|
||||||
|
<label>
|
||||||
|
{{( pgettext "Translation:" "input" )}}
|
||||||
|
<textarea class="html"
|
||||||
|
name="{{ .Name }}" {{ template "error-attrs" . }}>{{ .Val }}</textarea><br>
|
||||||
|
</label>
|
||||||
|
{{ template "error-message" . }}
|
||||||
|
</fieldset>
|
||||||
|
{{- end }}
|
||||||
|
</fieldset>
|
||||||
|
<footer>
|
||||||
|
<button type="submit">{{( pgettext "Translate" "action" )}}</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
{{- end }}
|
Loading…
Reference in New Issue