3
0

Add resource translation UI for page layouts

This commit is contained in:
Tomaž Jerman
2023-04-04 12:50:50 +02:00
committed by Jože Fortun
parent 09ed669ada
commit 02d0c4dcdb
14 changed files with 365 additions and 46 deletions
@@ -31,6 +31,16 @@ export default {
required: true,
},
pageLayouts: {
type: Array,
default: () => [],
},
pageLayout: {
type: compose.PageLayout,
required: false,
},
block: {
type: compose.PageBlock,
required: false,
@@ -79,6 +89,15 @@ export default {
titles[this.resource] = this.$t('title', { handle: handle || pageID })
}
if (this.pageLayout) {
const { namespaceID, pageID, pageLayoutID, handle, meta } = this.pageLayout
titles[`compose:page-layout/${namespaceID}/${pageID}/${pageLayoutID}`] = this.$t('layout.title', { handle: handle || meta.title || pageLayoutID })
} else {
this.pageLayouts.forEach(({ namespaceID, pageID, pageLayoutID, handle, meta }) => {
titles[`compose:page-layout/${namespaceID}/${pageID}/${pageLayoutID}`] = this.$t('layout.title', { handle: handle || meta.title || pageLayoutID })
})
}
return titles
},
@@ -97,6 +116,10 @@ export default {
set = set.filter(({ key }) => key.startsWith(`pageBlock.${this.block.blockID}.`))
}
if (this.pageLayout) {
set = set.filter(({ resource }) => resource.endsWith(`${pageID}`) || resource.endsWith(`/${this.pageLayout.pageLayoutID}`))
}
return set
})
}
@@ -124,6 +147,8 @@ export default {
return translations.find(t => t.key === key && t.lang === this.currentLanguage && t.resource === this.resource)
}
const layoutResource = ({ pageLayoutID, pageID, namespaceID }) => `compose:page-layout/${namespaceID}/${pageID}/${pageLayoutID}`
let tr = find('title')
if (tr !== undefined) {
this.page.title = tr.message
@@ -203,12 +228,71 @@ export default {
return block
}
const updatePageLayoutTranslations = pageLayout => {
if (pageLayout.pageLayoutID === NoID) return pageLayout
const find = (key) => {
return translations.find(t => t.key === key && t.lang === this.currentLanguage && t.resource === layoutResource(pageLayout))
}
let tr = find('title')
if (tr !== undefined) {
pageLayout.meta.title = tr.message
}
tr = find('description')
if (tr !== undefined) {
pageLayout.meta.description = tr.message
}
// Refresh page buttons for record pages
if (pageLayout.moduleID && pageLayout.moduleID !== NoID) {
tr = find('config.buttons.new.label')
if (tr) {
pageLayout.config.buttons.new.label = tr.message
}
tr = find('config.buttons.edit.label')
if (tr) {
pageLayout.config.buttons.edit.label = tr.message
}
tr = find('config.buttons.submit.label')
if (tr) {
pageLayout.config.buttons.submit.label = tr.message
}
tr = find('config.buttons.delete.label')
if (tr) {
pageLayout.config.buttons.delete.label = tr.message
}
tr = find('config.buttons.clone.label')
if (tr) {
pageLayout.config.buttons.clone.label = tr.message
}
tr = find('config.buttons.back.label')
if (tr) {
pageLayout.config.buttons.back.label = tr.message
}
}
return pageLayout
}
if (this.block) {
this.block = updateBlockTranslations(this.block)
} else {
this.page.blocks = this.page.blocks.map(block => updateBlockTranslations(block))
}
if (this.pageLayout) {
this.pageLayout = updatePageLayoutTranslations(this.pageLayout)
} else {
this.pageLayouts = this.pageLayouts.map(pageLayout => updatePageLayoutTranslations(pageLayout))
}
return this.page
})
.then(page => {
@@ -217,6 +301,12 @@ export default {
if (this.block) {
this.$emit('update:block', this.block)
}
if (this.pageLayout) {
this.$emit('update:pageLayout', this.pageLayout)
} else {
this.$emit('update:pageLayouts', this.pageLayouts)
}
})
}
},
@@ -0,0 +1,159 @@
<template>
<c-translator-button
v-if="canManageResourceTranslations && resourceTranslationsEnabled"
v-bind="$props"
:title="$t('tooltip')"
:resource="resource"
:titles="titles"
:fetcher="fetcher"
:updater="updater"
/>
</template>
<script>
import { compose, NoID } from '@cortezaproject/corteza-js'
import { mapGetters } from 'vuex'
import CTranslatorButton from 'corteza-webapp-compose/src/components/Translator/CTranslatorButton'
export default {
components: {
CTranslatorButton,
},
i18nOptions: {
namespaces: 'resource-translator',
keyPrefix: 'resources.page-layout',
},
props: {
pageLayout: {
type: compose.PageLayout,
required: true,
},
buttonVariant: {
type: String,
default: () => 'primary',
},
highlightKey: {
type: String,
default: '',
},
disabled: {
type: Boolean,
default: () => false,
},
},
computed: {
...mapGetters({
can: 'rbac/can',
}),
canManageResourceTranslations () {
return this.can('compose/', 'resource-translations.manage')
},
resource () {
const { pageID, namespaceID, pageLayoutID } = this.pageLayout
return `compose:page-layout/${namespaceID}/${pageID}/${pageLayoutID}`
},
titles () {
const titles = {}
const { pageID, handle, meta } = this.pageLayout
titles[this.resource] = this.$t('title', { handle: handle || meta.title || pageID })
return titles
},
fetcher () {
const { pageLayoutID, pageID, namespaceID } = this.pageLayout
return () => {
return this.$ComposeAPI
.pageLayoutListTranslations({ namespaceID, pageID, pageLayoutID })
.then(set => {
return set
})
}
},
updater () {
const { pageID, namespaceID, pageLayoutID } = this.pageLayout
return translations => {
return this.$ComposeAPI
.pageLayoutUpdateTranslations({ namespaceID, pageID, pageLayoutID, translations })
// re-fetch translations, sanitized and stripped
.then(() => this.fetcher())
.then((translations) => {
// When translations are successfully saved,
// scan changes and apply them back to the passed object
// not the most elegant solution but is saves us from
// handling the resource on multiple places
//
// @todo move this to Namespace* classes
// the logic there needs to be implemented; the idea is to encode
// values from the set of translations back to the resource object
const find = (key) => {
return translations.find(t => t.key === key && t.lang === this.currentLanguage && t.resource === this.resource)
}
let tr = find('title')
if (tr !== undefined) {
this.pageLayout.meta.title = tr.message
}
tr = find('description')
if (tr !== undefined) {
this.pageLayout.meta.description = tr.message
}
// Refresh page buttons for record pages
if (this.pageLayout.moduleID && this.pageLayout.moduleID !== NoID) {
tr = find('config.buttons.new.label')
if (tr) {
this.$set(this.pageLayout.config.buttons.new, 'label', tr.message)
}
tr = find('config.buttons.edit.label')
if (tr) {
this.$set(this.pageLayout.config.buttons.edit, 'label', tr.message)
}
tr = find('config.buttons.submit.label')
if (tr) {
this.$set(this.pageLayout.config.buttons.submit, 'label', tr.message)
}
tr = find('config.buttons.delete.label')
if (tr) {
this.$set(this.pageLayout.config.buttons.delete, 'label', tr.message)
}
tr = find('config.buttons.clone.label')
if (tr) {
this.$set(this.pageLayout.config.buttons.clone, 'label', tr.message)
}
tr = find('config.buttons.back.label')
if (tr) {
this.$set(this.pageLayout.config.buttons.back, 'label', tr.message)
}
}
return this.page
})
.then(page => {
this.$emit('update:pageLayout', page)
})
}
},
},
}
</script>
@@ -41,6 +41,7 @@
</b-button>
<page-translator
:page.sync="trPage"
:page-layout.sync="layout"
style="margin-left:2px;"
/>
<b-button
@@ -27,7 +27,8 @@
<page-translator
v-if="page"
:page="page"
:page.sync="page"
:page-layouts.sync="layouts"
style="margin-left:2px;"
/>
@@ -247,6 +248,7 @@
<b-button
variant="light"
class="d-flex align-items-center px-3"
style="margin-left:2px;"
@click="configureLayout(index)"
>
<font-awesome-icon
@@ -258,12 +260,17 @@
variant="primary"
:disabled="layout.pageLayoutID === '0'"
class="d-flex align-items-center"
style="margin-left:2px;"
:to="{ name: 'admin.pages.builder', query: { layoutID: layout.pageLayoutID} }"
>
<font-awesome-icon
:icon="['far', 'edit']"
/>
</b-button>
<page-layout-translator
:page-layout="layout"
style="margin-left:2px;"
/>
</b-input-group-append>
</b-input-group>
</b-td>
@@ -664,6 +671,7 @@
import { mapGetters, mapActions } from 'vuex'
import EditorToolbar from 'corteza-webapp-compose/src/components/Admin/EditorToolbar'
import PageTranslator from 'corteza-webapp-compose/src/components/Admin/Page/PageTranslator'
import PageLayoutTranslator from 'corteza-webapp-compose/src/components/Admin/PageLayout/PageLayoutTranslator'
import pages from 'corteza-webapp-compose/src/mixins/pages'
import Uploader from 'corteza-webapp-compose/src/components/Public/Page/Attachment/Uploader'
import Draggable from 'vuedraggable'
@@ -681,6 +689,7 @@ export default {
components: {
EditorToolbar,
PageTranslator,
PageLayoutTranslator,
Uploader,
Draggable,
VueSelect,
@@ -29,6 +29,7 @@
v-if="trPage"
data-test-id="button-page-translations"
:page.sync="trPage"
:page-layout.sync="layout"
style="margin-left:2px;"
/>
<b-button
-8
View File
@@ -1018,7 +1018,6 @@ export default class Compose {
pageID,
moduleID,
parentID,
primary,
query,
handle,
labels,
@@ -1040,7 +1039,6 @@ export default class Compose {
pageID,
moduleID,
parentID,
primary,
query,
handle,
labels,
@@ -1066,7 +1064,6 @@ export default class Compose {
pageID,
moduleID,
parentID,
primary,
query,
handle,
labels,
@@ -1090,7 +1087,6 @@ export default class Compose {
cfg.params = {
moduleID,
parentID,
primary,
query,
handle,
labels,
@@ -1119,7 +1115,6 @@ export default class Compose {
weight,
moduleID,
handle,
primary,
meta,
config,
blocks,
@@ -1144,7 +1139,6 @@ export default class Compose {
weight,
moduleID,
handle,
primary,
meta,
config,
blocks,
@@ -1208,7 +1202,6 @@ export default class Compose {
weight,
moduleID,
handle,
primary,
meta,
config,
blocks,
@@ -1236,7 +1229,6 @@ export default class Compose {
weight,
moduleID,
handle,
primary,
meta,
config,
blocks,
@@ -21,3 +21,8 @@ resources:
block:
title: "Page block #{{ blockID }}"
tooltip: Block translations
layout:
title: Page layout "{{ handle }}"
page-layout:
title: Page layout "{{ handle }}"
tooltip: Page layout translations
+1 -1
View File
@@ -163,7 +163,7 @@ pageLayout: {
customHandler: true
}
actionLabel: {
path: ["config", "actions", {part: "actionID", var: true}, "label"]
path: ["config", "actions", {part: "actionID", var: true}, "meta", "label"]
customHandler: true
}
}
+2 -5
View File
@@ -2,7 +2,6 @@ package rest
import (
"context"
"fmt"
"github.com/cortezaproject/corteza/server/compose/rest/request"
"github.com/cortezaproject/corteza/server/compose/service"
@@ -144,13 +143,11 @@ func (ctrl *PageLayout) Read(ctx context.Context, r *request.PageLayoutRead) (in
}
func (ctrl *PageLayout) ListTranslations(ctx context.Context, r *request.PageLayoutListTranslations) (interface{}, error) {
return nil, fmt.Errorf("@todo...")
// return ctrl.locale.PageLayout(ctx, r.NamespaceID, r.PageLayoutID)
return ctrl.locale.PageLayout(ctx, r.NamespaceID, r.PageID, r.PageLayoutID)
}
func (ctrl *PageLayout) UpdateTranslations(ctx context.Context, r *request.PageLayoutUpdateTranslations) (interface{}, error) {
return nil, fmt.Errorf("@todo...")
// return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
return api.OK(), ctrl.locale.Upsert(ctx, r.Translations)
}
func (ctrl *PageLayout) Reorder(ctx context.Context, r *request.PageLayoutReorder) (interface{}, error) {
+63 -1
View File
@@ -179,7 +179,25 @@ func (svc resourceTranslationsManager) pageExtended(ctx context.Context, res *ty
aux []*locale.ResourceTranslation
)
// We need to get page layouts to include them also
// @todo refactor the base logic to simplify this; it's suboptimal
layouts, _, err := store.SearchComposePageLayouts(ctx, svc.store, types.PageLayoutFilter{
NamespaceID: res.NamespaceID,
PageID: res.ID,
})
if err != nil {
return
}
for _, tag := range svc.locale.Tags() {
for _, l := range layouts {
aux, err = svc.PageLayout(ctx, res.NamespaceID, res.ID, l.ID)
if err != nil {
return
}
out = append(out, aux...)
}
for i, block := range res.Blocks {
pbContentID := locale.ContentID(block.BlockID, i)
rpl := strings.NewReplacer(
@@ -240,13 +258,57 @@ func (svc resourceTranslationsManager) pageLayoutExtended(ctx context.Context, r
)
for _, tag := range svc.locale.Tags() {
// Standard buttons
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: types.LocaleKeyPageLayoutConfigButtonsNewLabel.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), types.LocaleKeyPageLayoutConfigButtonsNewLabel.Path),
})
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: types.LocaleKeyPageLayoutConfigButtonsEditLabel.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), types.LocaleKeyPageLayoutConfigButtonsEditLabel.Path),
})
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: types.LocaleKeyPageLayoutConfigButtonsSubmitLabel.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), types.LocaleKeyPageLayoutConfigButtonsSubmitLabel.Path),
})
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: types.LocaleKeyPageLayoutConfigButtonsDeleteLabel.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), types.LocaleKeyPageLayoutConfigButtonsDeleteLabel.Path),
})
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: types.LocaleKeyPageLayoutConfigButtonsCloneLabel.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), types.LocaleKeyPageLayoutConfigButtonsCloneLabel.Path),
})
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
Key: types.LocaleKeyPageLayoutConfigButtonsBackLabel.Path,
Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), types.LocaleKeyPageLayoutConfigButtonsBackLabel.Path),
})
// Actions
for i, action := range res.Config.Actions {
acContentID := locale.ContentID(action.ActionID, i)
rpl := strings.NewReplacer(
"{{actionID}}", strconv.FormatUint(acContentID, 10),
)
k = types.LocaleKeyPageLayoutConfigActionsActionIDLabel
k = types.LocaleKeyPageLayoutConfigActionsActionIDMetaLabel
out = append(out, &locale.ResourceTranslation{
Resource: res.ResourceTranslation(),
Lang: tag.String(),
-8
View File
@@ -464,14 +464,6 @@ func (svc page) updater(ctx context.Context, s store.Storer, ns *types.Namespace
return err
}
// Get max blockID for later use
blockID := uint64(0)
for _, b := range res.Blocks {
if b.BlockID > blockID {
blockID = b.BlockID
}
}
old = res.Clone()
aProps.setNamespace(ns)
+10 -18
View File
@@ -334,14 +334,6 @@ func (svc pageLayout) updater(ctx context.Context, s store.Storer, ns *types.Nam
return err
}
// Get max blockID for later use
blockID := uint64(0)
for _, b := range res.Blocks {
if b.BlockID > blockID {
blockID = b.BlockID
}
}
old = res.Clone()
aProps.setNamespace(ns)
@@ -455,11 +447,11 @@ func (svc pageLayout) handleUpdate(ctx context.Context, upd *types.PageLayout) p
}
}
// Get max blockID for later use
blockID := uint64(0)
for _, b := range res.Blocks {
if b.BlockID > blockID {
blockID = b.BlockID
// Get max actionID for later use
actionID := uint64(0)
for _, a := range res.Config.Actions {
if a.ActionID > actionID {
actionID = a.ActionID
}
}
@@ -494,11 +486,11 @@ func (svc pageLayout) handleUpdate(ctx context.Context, upd *types.PageLayout) p
}
// Assure blockIDs
for i, b := range res.Blocks {
if b.BlockID == 0 {
blockID++
b.BlockID = blockID
res.Blocks[i] = b
for i, a := range res.Config.Actions {
if a.ActionID == 0 {
actionID++
a.ActionID = actionID
res.Config.Actions[i] = a
changes |= pageLayoutChanged
}
+1 -1
View File
@@ -62,7 +62,7 @@ var (
LocaleKeyPageLayoutConfigButtonsDeleteLabel = LocaleKey{Path: "config.buttons.delete.label"}
LocaleKeyPageLayoutConfigButtonsCloneLabel = LocaleKey{Path: "config.buttons.clone.label"}
LocaleKeyPageLayoutConfigButtonsBackLabel = LocaleKey{Path: "config.buttons.back.label"}
LocaleKeyPageLayoutConfigActionsActionIDLabel = LocaleKey{Path: "config.actions.{{actionID}}.label"}
LocaleKeyPageLayoutConfigActionsActionIDMetaLabel = LocaleKey{Path: "config.actions.{{actionID}}.meta.label"}
)
// ResourceTranslation returns string representation of Locale resource for Chart by calling ChartResourceTranslation fn
+22 -3
View File
@@ -145,6 +145,25 @@ func (p *PageLayout) decodeTranslations(tt locale.ResourceTranslationIndex) {
// @note not doing blocks because they are simply copied from the page's index
if aux = tt.FindByKey(LocaleKeyPageLayoutConfigButtonsNewLabel.Path); aux != nil {
p.Config.Buttons.New.Label = aux.Msg
}
if aux = tt.FindByKey(LocaleKeyPageLayoutConfigButtonsEditLabel.Path); aux != nil {
p.Config.Buttons.Edit.Label = aux.Msg
}
if aux = tt.FindByKey(LocaleKeyPageLayoutConfigButtonsSubmitLabel.Path); aux != nil {
p.Config.Buttons.Submit.Label = aux.Msg
}
if aux = tt.FindByKey(LocaleKeyPageLayoutConfigButtonsDeleteLabel.Path); aux != nil {
p.Config.Buttons.Delete.Label = aux.Msg
}
if aux = tt.FindByKey(LocaleKeyPageLayoutConfigButtonsCloneLabel.Path); aux != nil {
p.Config.Buttons.Clone.Label = aux.Msg
}
if aux = tt.FindByKey(LocaleKeyPageLayoutConfigButtonsBackLabel.Path); aux != nil {
p.Config.Buttons.Back.Label = aux.Msg
}
for i, action := range p.Config.Actions {
actionID := locale.ContentID(action.ActionID, i)
rpl := strings.NewReplacer(
@@ -152,7 +171,7 @@ func (p *PageLayout) decodeTranslations(tt locale.ResourceTranslationIndex) {
)
if aux = tt.FindByKey(rpl.Replace(LocaleKeyPagePageBlockBlockIDTitle.Path)); aux != nil {
p.Config.Actions[i].Label = aux.Msg
p.Config.Actions[i].Meta.Label = aux.Msg
}
}
}
@@ -171,8 +190,8 @@ func (p *PageLayout) encodeTranslations() (out locale.ResourceTranslationSet) {
out = append(out, &locale.ResourceTranslation{
Resource: p.ResourceTranslation(),
Key: rpl.Replace(LocaleKeyPageLayoutConfigActionsActionIDLabel.Path),
Msg: action.Label,
Key: rpl.Replace(LocaleKeyPageLayoutConfigActionsActionIDMetaLabel.Path),
Msg: action.Meta.Label,
})
}