diff --git a/compose/rest.yaml b/compose/rest.yaml index 80cc3062b..1aa25d258 100644 --- a/compose/rest.yaml +++ b/compose/rest.yaml @@ -12,6 +12,7 @@ endpoints: authentication: [] imports: - github.com/cortezaproject/corteza-server/pkg/label + - github.com/cortezaproject/corteza-server/pkg/locale - sqlxTypes github.com/jmoiron/sqlx/types - time apis: @@ -148,6 +149,32 @@ endpoints: type: string title: Script to execute required: true + - name: list resource + method: GET + title: List resource translations + path: "/{namespaceID}/locale" + parameters: + path: + - type: uint64 + name: namespaceID + required: true + title: ID + - name: update resource + method: PATCH + title: Update resource translations + path: "/{namespaceID}/locale" + parameters: + path: + - type: uint64 + name: namespaceID + required: true + title: ID + post: + - name: locale + type: locale.ResourceTranslationSet + title: Resource translations to upsert + required: true + - title: Pages description: Compose pages entrypoint: page @@ -155,6 +182,7 @@ endpoints: authentication: [] imports: - sqlxTypes github.com/jmoiron/sqlx/types + - github.com/cortezaproject/corteza-server/pkg/locale - github.com/cortezaproject/corteza-server/pkg/label parameters: path: @@ -357,6 +385,33 @@ endpoints: type: string title: Script to execute required: true + + - name: list resource + method: GET + title: List resource translations + path: "/{pageID}/locale" + parameters: + path: + - type: uint64 + name: pageID + required: true + title: ID + - name: update resource + method: PATCH + title: Update resource translations + path: "/{pageID}/locale" + parameters: + path: + - type: uint64 + name: pageID + required: true + title: ID + post: + - name: locale + type: locale.ResourceTranslationSet + title: Resource translations to upsert + required: true + - title: Modules description: Compose module definitions entrypoint: module @@ -371,6 +426,7 @@ endpoints: imports: - sqlxTypes github.com/jmoiron/sqlx/types - github.com/cortezaproject/corteza-server/compose/types + - github.com/cortezaproject/corteza-server/pkg/locale - github.com/cortezaproject/corteza-server/pkg/label - time apis: @@ -501,6 +557,32 @@ endpoints: type: string title: Script to execute required: true + - name: list resource + method: GET + title: List resource translations + path: "/{moduleID}/locale" + parameters: + path: + - type: uint64 + name: moduleID + required: true + title: ID + - name: update resource + method: PATCH + title: Update resource translations + path: "/{moduleID}/locale" + parameters: + path: + - type: uint64 + name: moduleID + required: true + title: ID + post: + - name: locale + type: locale.ResourceTranslationSet + title: Resource translations to upsert + required: true + - title: Records description: Compose records entrypoint: record diff --git a/compose/rest/handlers/module.go b/compose/rest/handlers/module.go index 0dcc5ecdf..86d499cf7 100644 --- a/compose/rest/handlers/module.go +++ b/compose/rest/handlers/module.go @@ -25,6 +25,8 @@ type ( Update(context.Context, *request.ModuleUpdate) (interface{}, error) Delete(context.Context, *request.ModuleDelete) (interface{}, error) TriggerScript(context.Context, *request.ModuleTriggerScript) (interface{}, error) + ListLocale(context.Context, *request.ModuleListLocale) (interface{}, error) + UpdateLocale(context.Context, *request.ModuleUpdateLocale) (interface{}, error) } // HTTP API interface @@ -35,6 +37,8 @@ type ( Update func(http.ResponseWriter, *http.Request) Delete func(http.ResponseWriter, *http.Request) TriggerScript func(http.ResponseWriter, *http.Request) + ListLocale func(http.ResponseWriter, *http.Request) + UpdateLocale func(http.ResponseWriter, *http.Request) } ) @@ -134,6 +138,38 @@ func NewModule(h ModuleAPI) *Module { return } + api.Send(w, r, value) + }, + ListLocale: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewModuleListLocale() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.ListLocale(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + UpdateLocale: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewModuleUpdateLocale() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.UpdateLocale(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + api.Send(w, r, value) }, } @@ -148,5 +184,7 @@ func (h Module) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http r.Post("/namespace/{namespaceID}/module/{moduleID}", h.Update) r.Delete("/namespace/{namespaceID}/module/{moduleID}", h.Delete) r.Post("/namespace/{namespaceID}/module/{moduleID}/trigger", h.TriggerScript) + r.Get("/namespace/{namespaceID}/module/{moduleID}/locale", h.ListLocale) + r.Patch("/namespace/{namespaceID}/module/{moduleID}/locale", h.UpdateLocale) }) } diff --git a/compose/rest/handlers/namespace.go b/compose/rest/handlers/namespace.go index 70bd78946..4e438d4e5 100644 --- a/compose/rest/handlers/namespace.go +++ b/compose/rest/handlers/namespace.go @@ -26,6 +26,8 @@ type ( Delete(context.Context, *request.NamespaceDelete) (interface{}, error) Upload(context.Context, *request.NamespaceUpload) (interface{}, error) TriggerScript(context.Context, *request.NamespaceTriggerScript) (interface{}, error) + ListLocale(context.Context, *request.NamespaceListLocale) (interface{}, error) + UpdateLocale(context.Context, *request.NamespaceUpdateLocale) (interface{}, error) } // HTTP API interface @@ -37,6 +39,8 @@ type ( Delete func(http.ResponseWriter, *http.Request) Upload func(http.ResponseWriter, *http.Request) TriggerScript func(http.ResponseWriter, *http.Request) + ListLocale func(http.ResponseWriter, *http.Request) + UpdateLocale func(http.ResponseWriter, *http.Request) } ) @@ -152,6 +156,38 @@ func NewNamespace(h NamespaceAPI) *Namespace { return } + api.Send(w, r, value) + }, + ListLocale: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceListLocale() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.ListLocale(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + UpdateLocale: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewNamespaceUpdateLocale() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.UpdateLocale(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + api.Send(w, r, value) }, } @@ -167,5 +203,7 @@ func (h Namespace) MountRoutes(r chi.Router, middlewares ...func(http.Handler) h r.Delete("/namespace/{namespaceID}", h.Delete) r.Post("/namespace/upload", h.Upload) r.Post("/namespace/{namespaceID}/trigger", h.TriggerScript) + r.Get("/namespace/{namespaceID}/locale", h.ListLocale) + r.Patch("/namespace/{namespaceID}/locale", h.UpdateLocale) }) } diff --git a/compose/rest/handlers/page.go b/compose/rest/handlers/page.go index a806ecdcb..73ecacb11 100644 --- a/compose/rest/handlers/page.go +++ b/compose/rest/handlers/page.go @@ -28,6 +28,8 @@ type ( Delete(context.Context, *request.PageDelete) (interface{}, error) Upload(context.Context, *request.PageUpload) (interface{}, error) TriggerScript(context.Context, *request.PageTriggerScript) (interface{}, error) + ListLocale(context.Context, *request.PageListLocale) (interface{}, error) + UpdateLocale(context.Context, *request.PageUpdateLocale) (interface{}, error) } // HTTP API interface @@ -41,6 +43,8 @@ type ( Delete func(http.ResponseWriter, *http.Request) Upload func(http.ResponseWriter, *http.Request) TriggerScript func(http.ResponseWriter, *http.Request) + ListLocale func(http.ResponseWriter, *http.Request) + UpdateLocale func(http.ResponseWriter, *http.Request) } ) @@ -188,6 +192,38 @@ func NewPage(h PageAPI) *Page { return } + api.Send(w, r, value) + }, + ListLocale: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPageListLocale() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.ListLocale(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + + api.Send(w, r, value) + }, + UpdateLocale: func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + params := request.NewPageUpdateLocale() + if err := params.Fill(r); err != nil { + api.Send(w, r, err) + return + } + + value, err := h.UpdateLocale(r.Context(), params) + if err != nil { + api.Send(w, r, err) + return + } + api.Send(w, r, value) }, } @@ -205,5 +241,7 @@ func (h Page) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.H r.Delete("/namespace/{namespaceID}/page/{pageID}", h.Delete) r.Post("/namespace/{namespaceID}/page/{pageID}/attachment", h.Upload) r.Post("/namespace/{namespaceID}/page/{pageID}/trigger", h.TriggerScript) + r.Get("/namespace/{namespaceID}/page/{pageID}/locale", h.ListLocale) + r.Patch("/namespace/{namespaceID}/page/{pageID}/locale", h.UpdateLocale) }) } diff --git a/compose/rest/module.go b/compose/rest/module.go index 367eaad5a..a7e548e9d 100644 --- a/compose/rest/module.go +++ b/compose/rest/module.go @@ -38,6 +38,7 @@ type ( Module struct { module service.ModuleService + locale service.ResourceTranslationService namespace service.NamespaceService ac moduleAccessController } @@ -60,6 +61,7 @@ func (Module) New() *Module { module: service.DefaultModule, namespace: service.DefaultNamespace, ac: service.DefaultAccessControl, + locale: service.DefaultResourceTranslation, } } @@ -92,6 +94,14 @@ func (ctrl *Module) Read(ctx context.Context, r *request.ModuleRead) (interface{ return ctrl.makePayload(ctx, mod, err) } +func (ctrl *Module) ListLocale(ctx context.Context, r *request.ModuleListLocale) (interface{}, error) { + return ctrl.locale.Module(ctx, r.NamespaceID, r.ModuleID) +} + +func (ctrl *Module) UpdateLocale(ctx context.Context, r *request.ModuleUpdateLocale) (interface{}, error) { + return api.OK(), ctrl.locale.Upsert(ctx, r.Locale) +} + func (ctrl *Module) Create(ctx context.Context, r *request.ModuleCreate) (interface{}, error) { var ( err error diff --git a/compose/rest/namespace.go b/compose/rest/namespace.go index a2a143387..5c7462eea 100644 --- a/compose/rest/namespace.go +++ b/compose/rest/namespace.go @@ -32,6 +32,7 @@ type ( Namespace struct { namespace service.NamespaceService + locale service.ResourceTranslationService attachment service.AttachmentService ac namespaceAccessController } @@ -52,6 +53,7 @@ type ( func (Namespace) New() *Namespace { return &Namespace{ namespace: service.DefaultNamespace, + locale: service.DefaultResourceTranslation, attachment: service.DefaultAttachment, ac: service.DefaultAccessControl, } @@ -103,6 +105,14 @@ func (ctrl Namespace) Read(ctx context.Context, r *request.NamespaceRead) (inter return ctrl.makePayload(ctx, ns, err) } +func (ctrl Namespace) ListLocale(ctx context.Context, r *request.NamespaceListLocale) (interface{}, error) { + return ctrl.locale.Namespace(ctx, r.NamespaceID) +} + +func (ctrl Namespace) UpdateLocale(ctx context.Context, r *request.NamespaceUpdateLocale) (interface{}, error) { + return api.OK(), ctrl.locale.Upsert(ctx, r.Locale) +} + func (ctrl Namespace) Update(ctx context.Context, r *request.NamespaceUpdate) (interface{}, error) { var ( err error diff --git a/compose/rest/page.go b/compose/rest/page.go index d6ced6a73..08a6e464f 100644 --- a/compose/rest/page.go +++ b/compose/rest/page.go @@ -44,6 +44,7 @@ type ( Reorder(ctx context.Context, namespaceID, selfID uint64, pageIDs []uint64) error } + locale service.ResourceTranslationService namespace service.NamespaceService attachment service.AttachmentService ac pageAccessController @@ -60,6 +61,7 @@ type ( func (Page) New() *Page { return &Page{ page: service.DefaultPage, + locale: service.DefaultResourceTranslation, namespace: service.DefaultNamespace, attachment: service.DefaultAttachment, ac: service.DefaultAccessControl, @@ -125,7 +127,14 @@ func (ctrl *Page) Create(ctx context.Context, r *request.PageCreate) (interface{ func (ctrl *Page) Read(ctx context.Context, r *request.PageRead) (interface{}, error) { mod, err := ctrl.page.FindByID(ctx, r.NamespaceID, r.PageID) return ctrl.makePayload(ctx, mod, err) +} +func (ctrl *Page) ListLocale(ctx context.Context, r *request.PageListLocale) (interface{}, error) { + return ctrl.locale.Page(ctx, r.NamespaceID, r.PageID) +} + +func (ctrl *Page) UpdateLocale(ctx context.Context, r *request.PageUpdateLocale) (interface{}, error) { + return api.OK(), ctrl.locale.Upsert(ctx, r.Locale) } func (ctrl *Page) Reorder(ctx context.Context, r *request.PageReorder) (interface{}, error) { diff --git a/compose/rest/request/module.go b/compose/rest/request/module.go index ad2a60c43..cfa313818 100644 --- a/compose/rest/request/module.go +++ b/compose/rest/request/module.go @@ -13,6 +13,7 @@ import ( "fmt" "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/go-chi/chi" sqlxTypes "github.com/jmoiron/sqlx/types" @@ -193,6 +194,35 @@ type ( // Script to execute Script string } + + ModuleListLocale struct { + // NamespaceID PATH parameter + // + // Namespace ID + NamespaceID uint64 `json:",string"` + + // ModuleID PATH parameter + // + // ID + ModuleID uint64 `json:",string"` + } + + ModuleUpdateLocale struct { + // NamespaceID PATH parameter + // + // Namespace ID + NamespaceID uint64 `json:",string"` + + // ModuleID PATH parameter + // + // ID + ModuleID uint64 `json:",string"` + + // Locale POST parameter + // + // ... + Locale locale.ResourceTranslationSet + } ) // NewModuleList request @@ -770,3 +800,129 @@ func (r *ModuleTriggerScript) Fill(req *http.Request) (err error) { return err } + +// NewModuleListLocale request +func NewModuleListLocale() *ModuleListLocale { + return &ModuleListLocale{} +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleListLocale) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "moduleID": r.ModuleID, + } +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleListLocale) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleListLocale) GetModuleID() uint64 { + return r.ModuleID +} + +// Fill processes request and fills internal variables +func (r *ModuleListLocale) Fill(req *http.Request) (err error) { + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "moduleID") + r.ModuleID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} + +// NewModuleUpdateLocale request +func NewModuleUpdateLocale() *ModuleUpdateLocale { + return &ModuleUpdateLocale{} +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleUpdateLocale) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "moduleID": r.ModuleID, + "locale": r.Locale, + } +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleUpdateLocale) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleUpdateLocale) GetModuleID() uint64 { + return r.ModuleID +} + +// Auditable returns all auditable/loggable parameters +func (r ModuleUpdateLocale) GetLocale() locale.ResourceTranslationSet { + return r.Locale +} + +// Fill processes request and fills internal variables +func (r *ModuleUpdateLocale) Fill(req *http.Request) (err error) { + + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + //if val, ok := req.Form["locale[]"]; ok && len(val) > 0 { + // r.Locale, err = locale.ResourceTranslationSet(val), nil + // if err != nil { + // return err + // } + //} + } + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "moduleID") + r.ModuleID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} diff --git a/compose/rest/request/namespace.go b/compose/rest/request/namespace.go index 21123b6dc..a5e059c93 100644 --- a/compose/rest/request/namespace.go +++ b/compose/rest/request/namespace.go @@ -12,6 +12,7 @@ import ( "encoding/json" "fmt" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/go-chi/chi" sqlxTypes "github.com/jmoiron/sqlx/types" @@ -164,6 +165,25 @@ type ( // Script to execute Script string } + + NamespaceListLocale struct { + // NamespaceID PATH parameter + // + // ID + NamespaceID uint64 `json:",string"` + } + + NamespaceUpdateLocale struct { + // NamespaceID PATH parameter + // + // ID + NamespaceID uint64 `json:",string"` + + // Locale POST parameter + // + // ... + Locale locale.ResourceTranslationSet + } ) // NewNamespaceList request @@ -692,3 +712,105 @@ func (r *NamespaceTriggerScript) Fill(req *http.Request) (err error) { return err } + +// NewNamespaceListLocale request +func NewNamespaceListLocale() *NamespaceListLocale { + return &NamespaceListLocale{} +} + +// Auditable returns all auditable/loggable parameters +func (r NamespaceListLocale) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + } +} + +// Auditable returns all auditable/loggable parameters +func (r NamespaceListLocale) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Fill processes request and fills internal variables +func (r *NamespaceListLocale) Fill(req *http.Request) (err error) { + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} + +// NewNamespaceUpdateLocale request +func NewNamespaceUpdateLocale() *NamespaceUpdateLocale { + return &NamespaceUpdateLocale{} +} + +// Auditable returns all auditable/loggable parameters +func (r NamespaceUpdateLocale) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "locale": r.Locale, + } +} + +// Auditable returns all auditable/loggable parameters +func (r NamespaceUpdateLocale) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r NamespaceUpdateLocale) GetLocale() locale.ResourceTranslationSet { + return r.Locale +} + +// Fill processes request and fills internal variables +func (r *NamespaceUpdateLocale) Fill(req *http.Request) (err error) { + + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + //if val, ok := req.Form["locale[]"]; ok && len(val) > 0 { + // r.Locale, err = locale.ResourceTranslationSet(val), nil + // if err != nil { + // return err + // } + //} + } + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} diff --git a/compose/rest/request/page.go b/compose/rest/request/page.go index f6c6fff2a..d817dabfd 100644 --- a/compose/rest/request/page.go +++ b/compose/rest/request/page.go @@ -12,6 +12,7 @@ import ( "encoding/json" "fmt" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/go-chi/chi" sqlxTypes "github.com/jmoiron/sqlx/types" @@ -272,6 +273,35 @@ type ( // Script to execute Script string } + + PageListLocale struct { + // NamespaceID PATH parameter + // + // Namespace ID + NamespaceID uint64 `json:",string"` + + // PageID PATH parameter + // + // ID + PageID uint64 `json:",string"` + } + + PageUpdateLocale struct { + // NamespaceID PATH parameter + // + // Namespace ID + NamespaceID uint64 `json:",string"` + + // PageID PATH parameter + // + // ID + PageID uint64 `json:",string"` + + // Locale POST parameter + // + // ... + Locale locale.ResourceTranslationSet + } ) // NewPageList request @@ -1143,3 +1173,129 @@ func (r *PageTriggerScript) Fill(req *http.Request) (err error) { return err } + +// NewPageListLocale request +func NewPageListLocale() *PageListLocale { + return &PageListLocale{} +} + +// Auditable returns all auditable/loggable parameters +func (r PageListLocale) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "pageID": r.PageID, + } +} + +// Auditable returns all auditable/loggable parameters +func (r PageListLocale) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r PageListLocale) GetPageID() uint64 { + return r.PageID +} + +// Fill processes request and fills internal variables +func (r *PageListLocale) Fill(req *http.Request) (err error) { + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "pageID") + r.PageID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} + +// NewPageUpdateLocale request +func NewPageUpdateLocale() *PageUpdateLocale { + return &PageUpdateLocale{} +} + +// Auditable returns all auditable/loggable parameters +func (r PageUpdateLocale) Auditable() map[string]interface{} { + return map[string]interface{}{ + "namespaceID": r.NamespaceID, + "pageID": r.PageID, + "locale": r.Locale, + } +} + +// Auditable returns all auditable/loggable parameters +func (r PageUpdateLocale) GetNamespaceID() uint64 { + return r.NamespaceID +} + +// Auditable returns all auditable/loggable parameters +func (r PageUpdateLocale) GetPageID() uint64 { + return r.PageID +} + +// Auditable returns all auditable/loggable parameters +func (r PageUpdateLocale) GetLocale() locale.ResourceTranslationSet { + return r.Locale +} + +// Fill processes request and fills internal variables +func (r *PageUpdateLocale) Fill(req *http.Request) (err error) { + + if strings.ToLower(req.Header.Get("content-type")) == "application/json" { + err = json.NewDecoder(req.Body).Decode(r) + + switch { + case err == io.EOF: + err = nil + case err != nil: + return fmt.Errorf("error parsing http request body: %w", err) + } + } + + { + if err = req.ParseForm(); err != nil { + return err + } + + // POST params + + //if val, ok := req.Form["locale[]"]; ok && len(val) > 0 { + // r.Locale, err = locale.ResourceTranslationSet(val), nil + // if err != nil { + // return err + // } + //} + } + + { + var val string + // path params + + val = chi.URLParam(req, "namespaceID") + r.NamespaceID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + val = chi.URLParam(req, "pageID") + r.PageID, err = payload.ParseUint64(val), nil + if err != nil { + return err + } + + } + + return err +} diff --git a/compose/service/locale.gen.go b/compose/service/locale.gen.go new file mode 100644 index 000000000..bb914c10c --- /dev/null +++ b/compose/service/locale.gen.go @@ -0,0 +1,213 @@ +package service + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +// Definitions file that controls how this file is generated: +// - compose.module.yaml +// - compose.namespace.yaml +// - compose.page.yaml + +import ( + "context" + + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/cortezaproject/corteza-server/store" + systemTypes "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + resourceTranslation struct { + actionlog actionlog.Recorder + locale locale.Resource + store store.Storer + ac localeAccessController + } + + localeAccessController interface { + // CanManageResourceTranslation(context.Context) bool + } + + ResourceTranslationService interface { + Module(ctx context.Context, namespaceID uint64, ID uint64) (locale.ResourceTranslationSet, error) + Namespace(ctx context.Context, ID uint64) (locale.ResourceTranslationSet, error) + Page(ctx context.Context, namespaceID uint64, ID uint64) (locale.ResourceTranslationSet, error) + + Upsert(context.Context, locale.ResourceTranslationSet) error + Locale() locale.Resource + } +) + +func ResourceTranslation(ls locale.Resource) *resourceTranslation { + return &resourceTranslation{ + actionlog: DefaultActionlog, + store: DefaultStore, + locale: ls, + } +} + +func (svc resourceTranslation) Upsert(ctx context.Context, rr locale.ResourceTranslationSet) (err error) { + // @todo AC + // @todo validation + + defer locale.Global().ReloadResourceTranslations(ctx) + me := auth.GetIdentityFromContext(ctx) + + // - group by resource + localeByRes := make(map[string]locale.ResourceTranslationSet) + for _, r := range rr { + localeByRes[r.Resource] = append(localeByRes[r.Resource], r) + } + + // - for each resource, fetch the current state + sysLocale := make(systemTypes.ResourceTranslationSet, 0, len(rr)) + for res, rr := range localeByRes { + current, _, err := store.SearchResourceTranslations(ctx, svc.store, systemTypes.ResourceTranslationFilter{ + Resource: res, + }) + if err != nil { + return err + } + + // get deltas and prepare upsert accordingly + aux := current.New(rr) + aux.Walk(func(cc *systemTypes.ResourceTranslation) error { + cc.ID = nextID() + cc.CreatedAt = *now() + cc.CreatedBy = me.Identity() + + return nil + }) + sysLocale = append(sysLocale, aux...) + + aux = current.Old(rr) + aux.Walk(func(cc *systemTypes.ResourceTranslation) error { + cc.UpdatedAt = now() + cc.UpdatedBy = me.Identity() + return nil + }) + sysLocale = append(sysLocale, aux...) + } + + err = store.UpsertResourceTranslation(ctx, svc.store, sysLocale...) + if err != nil { + return err + } + return nil +} + +func (svc resourceTranslation) Locale() locale.Resource { + return svc.locale +} + +func (svc resourceTranslation) Module(ctx context.Context, namespaceID uint64, ID uint64) (locale.ResourceTranslationSet, error) { + var ( + err error + out locale.ResourceTranslationSet + res *types.Module + k types.LocaleKey + ) + + res, err = svc.loadModule(ctx, svc.store, namespaceID, ID) + if err != nil { + return nil, err + } + + for _, tag := range svc.locale.Tags() { + k = types.LocaleKeyModuleName + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + } + + tmp, err := svc.moduleExtended(ctx, res) + return append(out, tmp...), err +} + +func (svc resourceTranslation) Namespace(ctx context.Context, ID uint64) (locale.ResourceTranslationSet, error) { + var ( + err error + out locale.ResourceTranslationSet + res *types.Namespace + k types.LocaleKey + ) + + res, err = svc.loadNamespace(ctx, svc.store, ID) + if err != nil { + return nil, err + } + + for _, tag := range svc.locale.Tags() { + k = types.LocaleKeyNamespaceName + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + k = types.LocaleKeyNamespaceSubtitle + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + k = types.LocaleKeyNamespaceDescription + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + } + return out, nil +} + +func (svc resourceTranslation) Page(ctx context.Context, namespaceID uint64, ID uint64) (locale.ResourceTranslationSet, error) { + var ( + err error + out locale.ResourceTranslationSet + res *types.Page + k types.LocaleKey + ) + + res, err = svc.loadPage(ctx, svc.store, namespaceID, ID) + if err != nil { + return nil, err + } + + for _, tag := range svc.locale.Tags() { + k = types.LocaleKeyPageTitle + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + k = types.LocaleKeyPageDescription + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + } + + tmp, err := svc.pageExtended(ctx, res) + return append(out, tmp...), err +} diff --git a/compose/service/locale.go b/compose/service/locale.go new file mode 100644 index 000000000..aa730f8dc --- /dev/null +++ b/compose/service/locale.go @@ -0,0 +1,150 @@ +package service + +import ( + "context" + "strconv" + "strings" + + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/cortezaproject/corteza-server/store" + "github.com/spf13/cast" + "golang.org/x/text/language" +) + +func (svc resourceTranslation) moduleExtended(ctx context.Context, res *types.Module) (out locale.ResourceTranslationSet, err error) { + var ( + k types.LocaleKey + ) + + for _, tag := range svc.locale.Tags() { + for _, f := range res.Fields { + k = types.LocaleKeyModuleFieldLabel + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: k.Path, + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), k.Path), + }) + + // Extra field bits + converted, err := svc.moduleFieldValidatorErrorHandler(ctx, tag, f, k.Path) + if err != nil { + return nil, err + } + out = append(out, converted...) + } + } + + return out, nil +} + +func (svc resourceTranslation) moduleFieldValidatorErrorHandler(ctx context.Context, tag language.Tag, f *types.ModuleField, k string) (locale.ResourceTranslationSet, error) { + out := make(locale.ResourceTranslationSet, 0, 10) + + for i, v := range f.Expressions.Validators { + vContentID := locale.ContentID(v.ValidatorID, i) + rpl := strings.NewReplacer( + "{{validatorID}}", strconv.FormatUint(vContentID, 10), + ) + tKey := rpl.Replace(k) + + out = append(out, &locale.ResourceTranslation{ + Resource: f.ResourceTranslation(), + Lang: tag.String(), + Key: tKey, + Msg: svc.locale.TRFor(tag, f.ResourceTranslation(), tKey), + }) + } + + return out, nil +} + +func (svc resourceTranslation) pageExtended(ctx context.Context, res *types.Page) (out locale.ResourceTranslationSet, err error) { + var ( + k types.LocaleKey + ) + + for _, tag := range svc.locale.Tags() { + for i, block := range res.Blocks { + pbContentID := locale.ContentID(block.BlockID, i) + rpl := strings.NewReplacer( + "{{blockID}}", strconv.FormatUint(pbContentID, 10), + ) + + // base stuff + k = types.LocaleKeyPageBlockTitle + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: rpl.Replace(k.Path), + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), rpl.Replace(k.Path)), + }) + + k = types.LocaleKeyPageBlockDescription + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: rpl.Replace(k.Path), + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), rpl.Replace(k.Path)), + }) + + switch block.Kind { + case "Automation": + aux, err := svc.pageExtendedAutomatinBlock(tag, res, block, pbContentID, k) + if err != nil { + return nil, err + } + + out = append(out, aux...) + } + } + } + + return +} + +func (svc resourceTranslation) pageExtendedAutomatinBlock(tag language.Tag, res *types.Page, block types.PageBlock, blockID uint64, k types.LocaleKey) (locale.ResourceTranslationSet, error) { + out := make(locale.ResourceTranslationSet, 0, 10) + + bb, _ := block.Options["buttons"].([]interface{}) + for j, auxBtn := range bb { + btn := auxBtn.(map[string]interface{}) + + bContentID := uint64(0) + if aux, ok := btn["buttonID"]; ok { + bContentID = cast.ToUint64(aux) + } + + bContentID = locale.ContentID(bContentID, j) + + rpl := strings.NewReplacer( + "{{blockID}}", strconv.FormatUint(blockID, 10), + "{{buttonID}}", strconv.FormatUint(bContentID, 10), + ) + + out = append(out, &locale.ResourceTranslation{ + Resource: res.ResourceTranslation(), + Lang: tag.String(), + Key: rpl.Replace(k.Path), + Msg: svc.locale.TRFor(tag, res.ResourceTranslation(), rpl.Replace(k.Path)), + }) + } + + return out, nil +} + +// Helper loaders + +func (svc resourceTranslation) loadModule(ctx context.Context, s store.Storer, namespaceID, moduleID uint64) (m *types.Module, err error) { + return loadModule(ctx, s, moduleID) +} + +func (svc resourceTranslation) loadNamespace(ctx context.Context, s store.Storer, namespaceID uint64) (m *types.Namespace, err error) { + return loadNamespace(ctx, s, namespaceID) +} + +func (svc resourceTranslation) loadPage(ctx context.Context, s store.Storer, namespaceID, pageID uint64) (m *types.Page, err error) { + _, m, err = loadPage(ctx, s, namespaceID, pageID) + return m, err +} diff --git a/compose/service/module.go b/compose/service/module.go index 4c92c591f..74956fc58 100644 --- a/compose/service/module.go +++ b/compose/service/module.go @@ -16,6 +16,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/store" ) @@ -25,6 +26,7 @@ type ( ac moduleAccessController eventbus eventDispatcher store store.Storer + locale ResourceTranslationService } moduleAccessController interface { @@ -66,6 +68,7 @@ func Module() *module { eventbus: eventbus.Service(), actionlog: DefaultActionlog, store: DefaultStore, + locale: DefaultResourceTranslation, } } @@ -122,7 +125,23 @@ func (svc module) Find(ctx context.Context, filter types.ModuleFilter) (set type return err } - return loadModuleFields(ctx, svc.store, set...) + err = loadModuleFields(ctx, svc.store, set...) + if err != nil { + return err + } + + // i18n + tag := locale.GetLanguageFromContext(ctx) + set.Walk(func(m *types.Module) error { + m.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, m.ResourceTranslation())) + + m.Fields.Walk(func(mf *types.ModuleField) error { + mf.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, mf.ResourceTranslation())) + return nil + }) + return nil + }) + return nil }() return set, f, svc.recordAction(ctx, aProps, ModuleActionSearch, err) @@ -328,6 +347,18 @@ func (svc module) updater(ctx context.Context, namespaceID, moduleID uint64, act } } + // i18n + tt := m.EncodeTranslations() + for _, f := range m.Fields { + tt = append(tt, f.EncodeTranslations()...) + } + + tt.SetLanguage(locale.GetLanguageFromContext(ctx)) + err = svc.locale.Upsert(ctx, tt) + if err != nil { + return err + } + if changes&moduleLabelsChanged > 0 { if err = label.Update(ctx, s, m); err != nil { return diff --git a/compose/service/namespace.go b/compose/service/namespace.go index f61fb53bb..6db47a33a 100644 --- a/compose/service/namespace.go +++ b/compose/service/namespace.go @@ -12,6 +12,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/eventbus" "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/rbac" "github.com/cortezaproject/corteza-server/store" ) @@ -22,6 +23,7 @@ type ( ac namespaceAccessController eventbus eventDispatcher store store.Storer + locale ResourceTranslationService } namespaceAccessController interface { @@ -61,6 +63,7 @@ func Namespace() *namespace { eventbus: eventbus.Service(), actionlog: DefaultActionlog, store: DefaultStore, + locale: DefaultResourceTranslation, } } @@ -106,6 +109,13 @@ func (svc namespace) Find(ctx context.Context, filter types.NamespaceFilter) (se return err } + // i18n + tag := locale.GetLanguageFromContext(ctx) + set.Walk(func(n *types.Namespace) error { + n.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, n.ResourceTranslation())) + return nil + }) + if err = label.Load(ctx, svc.store, toLabeledNamespaces(set)...); err != nil { return err } @@ -265,6 +275,14 @@ func (svc namespace) updater(ctx context.Context, namespaceID uint64, action fun } } + // i18n + tt := ns.EncodeTranslations() + tt.SetLanguage(locale.GetLanguageFromContext(ctx)) + err = DefaultResourceTranslation.Upsert(ctx, tt) + if err != nil { + return err + } + if changes&namespaceLabelsChanged > 0 { if err = label.Update(ctx, s, ns); err != nil { return diff --git a/compose/service/page.go b/compose/service/page.go index 41549b40b..1550667ef 100644 --- a/compose/service/page.go +++ b/compose/service/page.go @@ -11,6 +11,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/eventbus" "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/label" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/store" ) @@ -20,6 +21,7 @@ type ( ac pageAccessController eventbus eventDispatcher store store.Storer + locale ResourceTranslationService } pageAccessController interface { @@ -47,6 +49,7 @@ func Page() *page { ac: DefaultAccessControl, eventbus: eventbus.Service(), store: DefaultStore, + locale: DefaultResourceTranslation, } } @@ -140,6 +143,13 @@ func (svc page) search(ctx context.Context, filter types.PageFilter) (set types. return err } + // i18n + tag := locale.GetLanguageFromContext(ctx) + set.Walk(func(p *types.Page) error { + p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(tag, p.ResourceTranslation())) + return nil + }) + return nil }() @@ -350,6 +360,14 @@ func (svc page) updater(ctx context.Context, namespaceID, pageID uint64, action } } + // i18n + tt := p.EncodeTranslations() + tt.SetLanguage(locale.GetLanguageFromContext(ctx)) + err = svc.locale.Upsert(ctx, tt) + if err != nil { + return err + } + if changes&pageLabelsChanged > 0 { if err = label.Update(ctx, s, p); err != nil { return @@ -385,6 +403,8 @@ func (svc page) lookup(ctx context.Context, namespaceID uint64, lookup func(*pag return err } + p.DecodeTranslations(svc.locale.Locale().ResourceTranslations(locale.GetLanguageFromContext(ctx), p.ResourceTranslation())) + aProps.setPage(p) if !svc.ac.CanReadPage(ctx, p) { diff --git a/compose/service/service.go b/compose/service/service.go index 87eafe9c7..57dda2e1a 100644 --- a/compose/service/service.go +++ b/compose/service/service.go @@ -15,6 +15,7 @@ import ( "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/pkg/healthcheck" "github.com/cortezaproject/corteza-server/pkg/id" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/logger" "github.com/cortezaproject/corteza-server/pkg/objstore" "github.com/cortezaproject/corteza-server/pkg/objstore/minio" @@ -52,14 +53,15 @@ var ( // DefaultAccessControl Access control checking DefaultAccessControl *accessControl - DefaultNamespace NamespaceService - DefaultImportSession ImportSessionService - DefaultRecord RecordService - DefaultModule ModuleService - DefaultChart *chart - DefaultPage *page - DefaultAttachment AttachmentService - DefaultNotification *notification + DefaultNamespace NamespaceService + DefaultImportSession ImportSessionService + DefaultRecord RecordService + DefaultModule ModuleService + DefaultChart *chart + DefaultPage *page + DefaultAttachment AttachmentService + DefaultNotification *notification + DefaultResourceTranslation ResourceTranslationService // wrapper around time.Now() that will aid service testing now = func() *time.Time { @@ -97,6 +99,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config) DefaultActionlog = actionlog.NewService(DefaultStore, log, tee, policy) } + DefaultResourceTranslation = ResourceTranslation(locale.Global()) DefaultAccessControl = AccessControl() if DefaultObjectStore == nil { diff --git a/compose/types/locale.gen.go b/compose/types/locale.gen.go new file mode 100644 index 000000000..e80f3c15f --- /dev/null +++ b/compose/types/locale.gen.go @@ -0,0 +1,310 @@ +package types + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// + +// Definitions file that controls how this file is generated: +// - compose.module-field.yaml +// - compose.module.yaml +// - compose.namespace.yaml +// - compose.page.yaml + +import ( + "fmt" + "github.com/cortezaproject/corteza-server/pkg/locale" + "strconv" +) + +type ( + LocaleKey struct { + Name string + Resource string + Path string + CustomHandler string + } +) + +// Types and stuff +const ( + ModuleFieldResourceTranslationType = "compose:module-field" + ModuleResourceTranslationType = "compose:module" + NamespaceResourceTranslationType = "compose:namespace" + PageResourceTranslationType = "compose:page" +) + +var ( + LocaleKeyModuleFieldLabel = LocaleKey{ + Name: "label", + Resource: ModuleFieldResourceTranslationType, + Path: "label", + } + LocaleKeyModuleFieldValidatorError = LocaleKey{ + Name: "validatorError", + Resource: ModuleFieldResourceTranslationType, + Path: "expression.validator.{{validatorID}}.error", + CustomHandler: "validatorError", + } + LocaleKeyModuleName = LocaleKey{ + Name: "name", + Resource: ModuleResourceTranslationType, + Path: "name", + } + LocaleKeyNamespaceName = LocaleKey{ + Name: "name", + Resource: NamespaceResourceTranslationType, + Path: "name", + } + LocaleKeyNamespaceSubtitle = LocaleKey{ + Name: "subtitle", + Resource: NamespaceResourceTranslationType, + Path: "subtitle", + } + LocaleKeyNamespaceDescription = LocaleKey{ + Name: "description", + Resource: NamespaceResourceTranslationType, + Path: "description", + } + LocaleKeyPageTitle = LocaleKey{ + Name: "title", + Resource: PageResourceTranslationType, + Path: "title", + } + LocaleKeyPageDescription = LocaleKey{ + Name: "description", + Resource: PageResourceTranslationType, + Path: "description", + } + LocaleKeyPageBlockTitle = LocaleKey{ + Name: "blockTitle", + Resource: PageResourceTranslationType, + Path: "pageBlock.{{blockID}}.title", + } + LocaleKeyPageBlockDescription = LocaleKey{ + Name: "blockDescription", + Resource: PageResourceTranslationType, + Path: "pageBlock.{{blockID}}.description", + } + LocaleKeyPageBlockAutomationButtonlabel = LocaleKey{ + Name: "blockAutomationButtonlabel", + Resource: PageResourceTranslationType, + Path: "pageBlock.{{blockID}}.automation.{{buttonID}}.label", + } +) + +// ResourceTranslation returns string representation of Locale resource for ModuleField by calling ModuleFieldResourceTranslation fn +// +// Locale resource is in the compose:module-field/... format +// +// This function is auto-generated +func (r ModuleField) ResourceTranslation() string { + return ModuleFieldResourceTranslation(r.NamespaceID, r.ModuleID, r.ID) +} + +// ModuleFieldResourceTranslation returns string representation of Locale resource for ModuleField +// +// Locale resource is in the compose:module-field/... format +// +// This function is auto-generated +func ModuleFieldResourceTranslation(namespaceID uint64, moduleID uint64, id uint64) string { + cpts := []interface{}{ModuleFieldResourceTranslationType} + cpts = append(cpts, strconv.FormatUint(namespaceID, 10), strconv.FormatUint(moduleID, 10), strconv.FormatUint(id, 10)) + + return fmt.Sprintf(ModuleFieldResourceTranslationTpl(), cpts...) +} + +// @todo template +func ModuleFieldResourceTranslationTpl() string { + return "%s/%s/%s/%s" +} + +func (r *ModuleField) DecodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + if aux = tt.FindByKey(LocaleKeyModuleFieldLabel.Path); aux != nil { + r.Label = aux.Msg + } + r.decodeTranslationsValidatorError(tt) +} + +func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) { + out = locale.ResourceTranslationSet{ + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyModuleFieldLabel.Path, + Msg: r.Label, + }, + } + + out = append(out, r.encodeTranslationsValidatorError()...) + + return out +} + +// ResourceTranslation returns string representation of Locale resource for Module by calling ModuleResourceTranslation fn +// +// Locale resource is in the compose:module/... format +// +// This function is auto-generated +func (r Module) ResourceTranslation() string { + return ModuleResourceTranslation(r.NamespaceID, r.ID) +} + +// ModuleResourceTranslation returns string representation of Locale resource for Module +// +// Locale resource is in the compose:module/... format +// +// This function is auto-generated +func ModuleResourceTranslation(namespaceID uint64, id uint64) string { + cpts := []interface{}{ModuleResourceTranslationType} + cpts = append(cpts, strconv.FormatUint(namespaceID, 10), strconv.FormatUint(id, 10)) + + return fmt.Sprintf(ModuleResourceTranslationTpl(), cpts...) +} + +// @todo template +func ModuleResourceTranslationTpl() string { + return "%s/%s/%s" +} + +func (r *Module) DecodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + if aux = tt.FindByKey(LocaleKeyModuleName.Path); aux != nil { + r.Name = aux.Msg + } + + r.decodeTranslations(tt) +} + +func (r *Module) EncodeTranslations() (out locale.ResourceTranslationSet) { + out = locale.ResourceTranslationSet{ + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyModuleName.Path, + Msg: r.Name, + }, + } + + out = append(out, r.encodeTranslations()...) + + return out +} + +// ResourceTranslation returns string representation of Locale resource for Namespace by calling NamespaceResourceTranslation fn +// +// Locale resource is in the compose:namespace/... format +// +// This function is auto-generated +func (r Namespace) ResourceTranslation() string { + return NamespaceResourceTranslation(r.ID) +} + +// NamespaceResourceTranslation returns string representation of Locale resource for Namespace +// +// Locale resource is in the compose:namespace/... format +// +// This function is auto-generated +func NamespaceResourceTranslation(id uint64) string { + cpts := []interface{}{NamespaceResourceTranslationType} + cpts = append(cpts, strconv.FormatUint(id, 10)) + + return fmt.Sprintf(NamespaceResourceTranslationTpl(), cpts...) +} + +// @todo template +func NamespaceResourceTranslationTpl() string { + return "%s/%s" +} + +func (r *Namespace) DecodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + if aux = tt.FindByKey(LocaleKeyNamespaceName.Path); aux != nil { + r.Name = aux.Msg + } + if aux = tt.FindByKey(LocaleKeyNamespaceSubtitle.Path); aux != nil { + r.Meta.Subtitle = aux.Msg + } + if aux = tt.FindByKey(LocaleKeyNamespaceDescription.Path); aux != nil { + r.Meta.Description = aux.Msg + } +} + +func (r *Namespace) EncodeTranslations() (out locale.ResourceTranslationSet) { + out = locale.ResourceTranslationSet{ + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyNamespaceName.Path, + Msg: r.Name, + }, + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyNamespaceSubtitle.Path, + Msg: r.Meta.Subtitle, + }, + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyNamespaceDescription.Path, + Msg: r.Meta.Description, + }, + } + + return out +} + +// ResourceTranslation returns string representation of Locale resource for Page by calling PageResourceTranslation fn +// +// Locale resource is in the compose:page/... format +// +// This function is auto-generated +func (r Page) ResourceTranslation() string { + return PageResourceTranslation(r.NamespaceID, r.ID) +} + +// PageResourceTranslation returns string representation of Locale resource for Page +// +// Locale resource is in the compose:page/... format +// +// This function is auto-generated +func PageResourceTranslation(namespaceID uint64, id uint64) string { + cpts := []interface{}{PageResourceTranslationType} + cpts = append(cpts, strconv.FormatUint(namespaceID, 10), strconv.FormatUint(id, 10)) + + return fmt.Sprintf(PageResourceTranslationTpl(), cpts...) +} + +// @todo template +func PageResourceTranslationTpl() string { + return "%s/%s/%s" +} + +func (r *Page) DecodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + if aux = tt.FindByKey(LocaleKeyPageTitle.Path); aux != nil { + r.Title = aux.Msg + } + if aux = tt.FindByKey(LocaleKeyPageDescription.Path); aux != nil { + r.Description = aux.Msg + } + + r.decodeTranslations(tt) +} + +func (r *Page) EncodeTranslations() (out locale.ResourceTranslationSet) { + out = locale.ResourceTranslationSet{ + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyPageTitle.Path, + Msg: r.Title, + }, + { + Resource: r.ResourceTranslation(), + Key: LocaleKeyPageDescription.Path, + Msg: r.Description, + }, + } + + out = append(out, r.encodeTranslations()...) + + return out +} diff --git a/compose/types/module.go b/compose/types/module.go index 5e2a78b7a..c2ed9b9a4 100644 --- a/compose/types/module.go +++ b/compose/types/module.go @@ -4,6 +4,7 @@ import ( "time" "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/jmoiron/sqlx/types" ) @@ -54,6 +55,16 @@ func (m Module) Clone() *Module { return c } +// We won't worry about fields at this point +func (m *Module) decodeTranslations(tt locale.ResourceTranslationIndex) { + return +} + +// We won't worry about fields at this point +func (m *Module) encodeTranslations() (out locale.ResourceTranslationSet) { + return +} + // FindByHandle finds module by it's handle func (set ModuleSet) FindByHandle(handle string) *Module { for i := range set { diff --git a/compose/types/module_field.go b/compose/types/module_field.go index 0d11e4e32..510fbc08f 100644 --- a/compose/types/module_field.go +++ b/compose/types/module_field.go @@ -3,9 +3,13 @@ package types import ( "database/sql/driver" "encoding/json" - "github.com/cortezaproject/corteza-server/pkg/filter" "sort" + "strconv" + "strings" "time" + + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/locale" ) type ( @@ -47,6 +51,41 @@ var ( _ sort.Interface = &ModuleFieldSet{} ) +func (f *ModuleField) decodeTranslationsValidatorError(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + + for i, e := range f.Expressions.Validators { + validatorID := locale.ContentID(e.ValidatorID, i) + rpl := strings.NewReplacer( + "{{validatorID}}", strconv.FormatUint(validatorID, 10), + ) + + if aux = tt.FindByKey(rpl.Replace(LocaleKeyModuleFieldValidatorError.Path)); aux != nil { + f.Expressions.Validators[i].Error = aux.Msg + } + } +} + +func (m *ModuleField) encodeTranslationsValidatorError() (out locale.ResourceTranslationSet) { + out = make(locale.ResourceTranslationSet, 0, 3) + + // Module field expressions + for i, e := range m.Expressions.Validators { + validatorID := locale.ContentID(e.ValidatorID, i) + rpl := strings.NewReplacer( + "{{validatorID}}", strconv.FormatUint(validatorID, 10), + ) + + out = append(out, &locale.ResourceTranslation{ + Resource: m.ResourceTranslation(), + Key: rpl.Replace(LocaleKeyModuleFieldValidatorError.Path), + Msg: e.Error, + }) + } + + return +} + func (m ModuleField) Clone() *ModuleField { return &m } diff --git a/compose/types/module_field_expr.go b/compose/types/module_field_expr.go index c0867d30a..2a98e7fff 100644 --- a/compose/types/module_field_expr.go +++ b/compose/types/module_field_expr.go @@ -20,8 +20,9 @@ type ( } ModuleFieldValidator struct { - Test string `json:"test,omitempty"` - Error string `json:"error,omitempty"` + ValidatorID uint64 `json:"validatorID,string,omitempty"` + Test string `json:"test,omitempty"` + Error string `json:"error,omitempty"` } ) diff --git a/compose/types/page.go b/compose/types/page.go index 115e71afd..97419ff80 100644 --- a/compose/types/page.go +++ b/compose/types/page.go @@ -3,9 +3,13 @@ package types import ( "database/sql/driver" "encoding/json" + "strconv" + "strings" "time" "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/spf13/cast" "github.com/pkg/errors" ) @@ -41,6 +45,7 @@ type ( PageBlocks []PageBlock PageBlock struct { + BlockID uint64 `json:"blockID,string,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` Options map[string]interface{} `json:"options,omitempty"` @@ -84,6 +89,104 @@ func (m Page) Clone() *Page { return c } +func (p *Page) decodeTranslations(tt locale.ResourceTranslationIndex) { + var aux *locale.ResourceTranslation + + for i, block := range p.Blocks { + blockID := locale.ContentID(block.BlockID, i) + rpl := strings.NewReplacer( + "{{blockID}}", strconv.FormatUint(blockID, 10), + ) + + // - generic page block stuff + if aux = tt.FindByKey(rpl.Replace(LocaleKeyPageBlockTitle.Path)); aux != nil { + p.Blocks[i].Title = aux.Msg + } + if aux = tt.FindByKey(rpl.Replace(LocaleKeyPageBlockDescription.Path)); aux != nil { + p.Blocks[i].Description = aux.Msg + } + + // - automation page block stuff + if block.Kind == "Automation" { + bb, _ := block.Options["buttons"].([]interface{}) + for j, auxBtn := range bb { + btn := auxBtn.(map[string]interface{}) + + buttonID := uint64(0) + if aux, ok := btn["buttonID"]; ok { + buttonID = cast.ToUint64(aux) + } + buttonID = locale.ContentID(buttonID, j) + + rpl := strings.NewReplacer( + "{{blockID}}", strconv.FormatUint(blockID, 10), + "{{buttonID}}", strconv.FormatUint(buttonID, 10), + ) + + if aux = tt.FindByKey(rpl.Replace(LocaleKeyPageBlockAutomationButtonlabel.Path)); aux != nil { + btn["label"] = aux.Msg + } + } + } + } +} + +func (p *Page) encodeTranslations() (out locale.ResourceTranslationSet) { + out = make(locale.ResourceTranslationSet, 0, 3) + + // Page blocks + for i, block := range p.Blocks { + blockID := locale.ContentID(block.BlockID, i) + rpl := strings.NewReplacer( + "{{blockID}}", strconv.FormatUint(uint64(blockID), 10), + ) + + // - generic page block stuff + out = append(out, &locale.ResourceTranslation{ + Resource: p.ResourceTranslation(), + Key: rpl.Replace(LocaleKeyPageBlockTitle.Path), + Msg: block.Title, + }) + + out = append(out, &locale.ResourceTranslation{ + Resource: p.ResourceTranslation(), + Key: rpl.Replace(LocaleKeyPageBlockDescription.Path), + Msg: block.Description, + }) + + // - automation page block stuff + if block.Kind == "Automation" { + bb, _ := block.Options["buttons"].([]interface{}) + for j, auxBtn := range bb { + btn := auxBtn.(map[string]interface{}) + + if _, ok := btn["label"]; !ok { + continue + } + + buttonID := uint64(0) + if aux, ok := btn["buttonID"]; ok { + buttonID = cast.ToUint64(aux) + } + buttonID = locale.ContentID(buttonID, j) + + rpl := strings.NewReplacer( + "{{blockID}}", strconv.FormatUint(blockID, 10), + "{{buttonID}}", strconv.FormatUint(buttonID, 10), + ) + + out = append(out, &locale.ResourceTranslation{ + Resource: p.ResourceTranslation(), + Key: rpl.Replace(LocaleKeyPageBlockAutomationButtonlabel.Path), + Msg: btn["label"].(string), + }) + } + } + } + + return +} + // FindByHandle finds page by it's handle func (set PageSet) FindByHandle(handle string) *Page { for i := range set {