Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions example/integration/pagebuilder_category_i18n_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package integration_test

import (
"net/http"
"testing"

. "github.com/qor5/web/v3/multipartestutils"

"github.com/qor5/admin/v3/example/admin"
"github.com/qor5/admin/v3/presets/actions"
)

// TestPageCategoryI18n covers KGM-3903:
// In the Japanese UI, the Page Categories "New" form rendered its field labels
// (Name / Path / Description) in English, because the field-level translations
// (PageCategoriesName / PageCategoriesPath / PageCategoriesDescription) were not
// registered under presets.ModelsI18nModuleKey, so i18n.PT fell back to the
// English humanized field names.
func TestPageCategoryI18n(t *testing.T) {
h := admin.TestHandler(TestDB, nil)
dbr, _ := TestDB.DB()

cases := []TestCase{
{
Name: "Category New form in Japanese shows Japanese field labels",
Debug: true,
ReqFunc: func() *http.Request {
pageBuilderData.TruncatePut(dbr)
req := NewMultipartBuilder().
PageURL("/page_categories").
EventFunc(actions.New).
BuildEventFuncRequest()
req.Header.Set("Accept-Language", "ja")
return req
},
// Field labels must be the Japanese translations, in editing field order.
ExpectPortalUpdate0ContainsInOrder: []string{"名前", "パス", "説明"},
},
{
Name: "Category New form in English shows English field labels",
Debug: true,
ReqFunc: func() *http.Request {
pageBuilderData.TruncatePut(dbr)
req := NewMultipartBuilder().
PageURL("/page_categories").
EventFunc(actions.New).
BuildEventFuncRequest()
req.Header.Set("Accept-Language", "en")
return req
},
ExpectPortalUpdate0ContainsInOrder: []string{"Name", "Path", "Description"},
},
}

for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
RunCase(t, c, h)
})
}
}
19 changes: 19 additions & 0 deletions example/integration/pagebuilder_perm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ func TestPageBuilderPerm(t *testing.T) {
},
ExpectPageBodyContainsInOrder: []string{"permission denied"},
},
{
// KGM-4190: the permission-denied message must be localized, not the
// raw English perm.PermissionDenied.Error() string.
Name: "Container Header Update permission denied in Japanese",
Debug: true,
ReqFunc: func() *http.Request {
pageBuilderContainerTestData.TruncatePut(dbr)
req := NewMultipartBuilder().
PageURL("/headers").
EventFunc(actions.Update).
Query(presets.ParamID, "10").
AddField("Color", "white").
BuildEventFuncRequest()
req.Header.Set("Accept-Language", "ja")
return req
},
ExpectPageBodyContainsInOrder: []string{"権限がありません"},
ExpectPageBodyNotContains: []string{"permission denied"},
},
}
for _, c := range cases {
t.Run(c.Name, func(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions pagebuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,8 +683,25 @@ func (b *Builder) defaultCategoryInstall(pb *presets.Builder, pm *presets.ModelB
})

eb := pm.Editing("Name", "Path", "Description")
// Translate the editing form field labels from the page builder's own i18n
// messages, mirroring the listing column labels above. Without this the labels
// fall back to the English humanized field names in non-English locales, because
// the default i18n.PT lookup goes through presets.ModelsI18nModuleKey, which only
// the host app can register (KGM-3903).
categoryFieldLabel := presets.WrapperFieldLabel(func(evCtx *web.EventContext, obj interface{}, field *presets.FieldContext) (map[string]string, error) {
msgr := i18n.MustGetModuleMessages(evCtx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
return map[string]string{
"Name": msgr.ListHeaderName,
"Path": msgr.ListHeaderPath,
"Description": msgr.ListHeaderDescription,
}, nil
})
eb.Field("Name").LazyWrapComponentFunc(categoryFieldLabel)
eb.Field("Description").LazyWrapComponentFunc(categoryFieldLabel)
eb.Field("Path").LazyWrapComponentFunc(func(in presets.FieldComponentFunc) presets.FieldComponentFunc {
return func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) h.HTMLComponent {
msgr := i18n.MustGetModuleMessages(ctx.R, I18nPageBuilderKey, Messages_en_US).(*Messages)
field.Label = msgr.ListHeaderPath
comp := in(obj, field, ctx)
if p, ok := comp.(*vx.VXFieldBuilder); ok {
p.Attr(presets.VFieldError(field.Name, strings.TrimPrefix(field.Value(obj).(string), "/"), field.Errors)...).
Expand Down
5 changes: 2 additions & 3 deletions presets/detailing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"

"github.com/qor5/web/v3"
"github.com/qor5/x/v3/perm"
v "github.com/qor5/x/v3/ui/vuetify"
h "github.com/theplant/htmlgo"

Expand Down Expand Up @@ -195,7 +194,7 @@ func (b *DetailingBuilder) defaultPageFunc(ctx *web.EventContext) (r web.PageRes
}

if b.mb.Info().Verifier().Do(PermGet).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
r.Body = h.Div(h.Text(perm.PermissionDenied.Error()))
r.Body = h.Div(h.Text(MustGetMessages(ctx.R).PermissionDenied))
return
}

Expand Down Expand Up @@ -300,7 +299,7 @@ func (b *DetailingBuilder) WrapIdCurrentActive(w func(IdCurrentActiveProcessor)

func (b *DetailingBuilder) showInDrawer(ctx *web.EventContext) (r web.EventResponse, err error) {
if b.mb.Info().Verifier().Do(PermGet).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}
onChangeEvent := fmt.Sprintf("if (vars.%s) { vars.%s.detailing=true };", VarsPresetsDataChanged, VarsPresetsDataChanged)
Expand Down
14 changes: 9 additions & 5 deletions presets/editing.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ func (b *EditingBuilder) WrapIdCurrentActive(w func(in IdCurrentActiveProcessor)

func (b *EditingBuilder) formNew(ctx *web.EventContext) (r web.EventResponse, err error) {
if b.mb.Info().Verifier().Do(PermCreate).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand All @@ -215,7 +215,7 @@ func (b *EditingBuilder) formNew(ctx *web.EventContext) (r web.EventResponse, er

func (b *EditingBuilder) formEdit(ctx *web.EventContext) (r web.EventResponse, err error) {
if b.mb.Info().Verifier().Do(PermGet).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}
if b.idCurrentActiveProcessor != nil {
Expand Down Expand Up @@ -468,7 +468,7 @@ func (b *EditingBuilder) doValidate(ctx *web.EventContext) (r web.EventResponse,
}
vErrSetter := vErr
if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
vErr.GlobalError(perm.PermissionDenied.Error())
vErr.GlobalError(MustGetMessages(ctx.R).PermissionDenied)
return
}
if usingB.Validator != nil {
Expand All @@ -481,7 +481,7 @@ func (b *EditingBuilder) doValidate(ctx *web.EventContext) (r web.EventResponse,

func (b *EditingBuilder) doDelete(ctx *web.EventContext) (r web.EventResponse, err1 error) {
if b.mb.Info().Verifier().Do(PermDelete).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand Down Expand Up @@ -671,7 +671,11 @@ func (b *EditingBuilder) UpdateOverlayContent(
if err != nil {
if _, ok := err.(*web.ValidationErrors); !ok {
vErr := &web.ValidationErrors{}
vErr.GlobalError(err.Error())
msg := err.Error()
if errors.Is(err, perm.PermissionDenied) {
msg = MustGetMessages(ctx.R).PermissionDenied
}
vErr.GlobalError(msg)
ctx.Flash = vErr
}
}
Expand Down
7 changes: 3 additions & 4 deletions presets/listeditor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"

"github.com/qor5/web/v3"
"github.com/qor5/x/v3/perm"
. "github.com/qor5/x/v3/ui/vuetify"
"github.com/sunfmin/reflectutils"
h "github.com/theplant/htmlgo"
Expand Down Expand Up @@ -285,7 +284,7 @@ func addListItemRow(mb *ModelBuilder) web.EventFunc {
obj, _ := me.FetchAndUnmarshal(id, false, ctx)

if mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), ColorError)
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, ColorError)
return r, nil
}

Expand All @@ -309,7 +308,7 @@ func removeListItemRow(mb *ModelBuilder) web.EventFunc {
obj, _ := me.FetchAndUnmarshal(id, false, ctx)

if mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), ColorError)
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, ColorError)
return r, nil
}

Expand Down Expand Up @@ -337,7 +336,7 @@ func sortListItems(mb *ModelBuilder) web.EventFunc {
obj, _ := me.FetchAndUnmarshal(id, false, ctx)

if mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), ColorError)
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, ColorError)
return r, nil
}

Expand Down
12 changes: 8 additions & 4 deletions presets/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ type Messages struct {

LeaveBeforeUnsubmit string

RecordNotFound string
RecordNotFound string
PermissionDenied string
}

func (msgr *Messages) CreatingObjectTitle(modelName string) string {
Expand Down Expand Up @@ -252,7 +253,8 @@ var Messages_en_US = &Messages{

LeaveBeforeUnsubmit: "If you leave before submitting the form, you will lose all the unsaved input.",

RecordNotFound: "record not found",
RecordNotFound: "record not found",
PermissionDenied: "permission denied",
}

var Messages_zh_CN = &Messages{
Expand Down Expand Up @@ -350,7 +352,8 @@ var Messages_zh_CN = &Messages{

LeaveBeforeUnsubmit: "如果您在提交表单之前离开,您将丢失所有未保存的输入。",

RecordNotFound: "记录未找到",
RecordNotFound: "记录未找到",
PermissionDenied: "没有权限",
}

var Messages_ja_JP = &Messages{
Expand Down Expand Up @@ -448,5 +451,6 @@ var Messages_ja_JP = &Messages{

LeaveBeforeUnsubmit: "フォームを送信する前に離れると、すべての未保存の入力が失われます。",

RecordNotFound: "レコードが見つかりません",
RecordNotFound: "レコードが見つかりません",
PermissionDenied: "権限がありません",
}
4 changes: 2 additions & 2 deletions presets/presets.go
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ func (b *Builder) defaultLayout(in web.PageFunc, cfg *LayoutConfig) (out web.Pag
var innerPr web.PageResponse
innerPr, err = in(ctx)
if errors.Is(err, perm.PermissionDenied) {
pr.Body = h.Text(perm.PermissionDenied.Error())
pr.Body = h.Text(MustGetMessages(ctx.R).PermissionDenied)
return pr, nil
}
if err != nil {
Expand Down Expand Up @@ -1005,7 +1005,7 @@ func (b *Builder) PlainLayout(in web.PageFunc) (out web.PageFunc) {
var innerPr web.PageResponse
innerPr, err = in(ctx)
if err == perm.PermissionDenied {
pr.Body = h.Text(perm.PermissionDenied.Error())
pr.Body = h.Text(MustGetMessages(ctx.R).PermissionDenied)
return pr, nil
}
if err != nil {
Expand Down
15 changes: 7 additions & 8 deletions presets/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/qor5/web/v3"
"github.com/qor5/x/v3/i18n"
"github.com/qor5/x/v3/perm"
. "github.com/qor5/x/v3/ui/vuetify"
"github.com/sunfmin/reflectutils"
h "github.com/theplant/htmlgo"
Expand Down Expand Up @@ -1008,7 +1007,7 @@ func (b *SectionBuilder) EditDetailField(ctx *web.EventContext) (r web.EventResp
}

if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand Down Expand Up @@ -1056,7 +1055,7 @@ func (b *SectionBuilder) SaveDetailField(ctx *web.EventContext) (r web.EventResp
}

if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}
vErrSetter := b.editingFB.Unmarshal(obj, b.mb.Info(), true, ctx)
Expand Down Expand Up @@ -1175,7 +1174,7 @@ func (b *SectionBuilder) ValidateDetailField(ctx *web.EventContext) (r web.Event
}
vErrSetter := vErr
if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
vErr.GlobalError(perm.PermissionDenied.Error())
vErr.GlobalError(MustGetMessages(ctx.R).PermissionDenied)
return
}
if b.validator != nil {
Expand Down Expand Up @@ -1215,7 +1214,7 @@ func (b *SectionBuilder) EditDetailListField(ctx *web.EventContext) (r web.Event
}

if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand Down Expand Up @@ -1269,7 +1268,7 @@ func (b *SectionBuilder) SaveDetailListField(ctx *web.EventContext) (r web.Event
}

if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand Down Expand Up @@ -1384,7 +1383,7 @@ func (b *SectionBuilder) DeleteDetailListField(ctx *web.EventContext) (r web.Eve
}

if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand Down Expand Up @@ -1439,7 +1438,7 @@ func (b *SectionBuilder) CreateDetailListField(ctx *web.EventContext) (r web.Eve
}

if b.mb.Info().Verifier().Do(PermUpdate).ObjectOn(obj).WithReq(ctx.R).IsAllowed() != nil {
ShowMessage(&r, perm.PermissionDenied.Error(), "warning")
ShowMessage(&r, MustGetMessages(ctx.R).PermissionDenied, "warning")
return
}

Expand Down
Loading