Extracted strings and fixes on localization support

Language chooser in the UI
This commit is contained in:
Tomas Bures 2018-11-18 21:31:22 +01:00
parent 9f449c0a2f
commit dc7789c17b
126 changed files with 2919 additions and 2028 deletions

View file

@ -2,7 +2,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {translate} from 'react-i18next';
import { withTranslation } from '../lib/i18n';
import {
NavButton,
requiresAuthenticatedUser,
@ -38,7 +38,7 @@ import {getUrl} from "../lib/urls";
import {TestSendModalDialog} from "./TestSendModalDialog";
@translate()
@withTranslation()
@withForm
@withPageHelpers
@withErrorHandling
@ -103,12 +103,12 @@ export default class CUD extends Component {
}
if (!state.getIn(['name', 'value'])) {
state.setIn(['name', 'error'], t('Name must not be empty'));
state.setIn(['name', 'error'], t('nameMustNotBeEmpty'));
}
const typeKey = state.getIn(['type', 'value']);
if (!typeKey) {
state.setIn(['type', 'error'], t('Type must be selected'));
state.setIn(['type', 'error'], t('typeMustBeSelected'));
}
validateNamespace(t, state);
@ -137,7 +137,7 @@ export default class CUD extends Component {
}
this.disableForm();
this.setFormStatusMessage('info', t('Saving ...'));
this.setFormStatusMessage('info', t('saving'));
const submitResponse = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
Object.assign(data, exportedData);
@ -146,13 +146,13 @@ export default class CUD extends Component {
if (submitResponse) {
if (this.props.entity) {
this.navigateToWithFlashMessage('/templates', 'success', t('Template saved'));
this.navigateToWithFlashMessage('/templates', 'success', t('templateSaved'));
} else {
this.navigateToWithFlashMessage(`/templates/${submitResponse}/edit`, 'success', t('Template saved'));
this.navigateToWithFlashMessage(`/templates/${submitResponse}/edit`, 'success', t('templateSaved'));
}
} else {
this.enableForm();
this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.'));
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
}
}
@ -244,23 +244,23 @@ export default class CUD extends Component {
deleteUrl={`rest/templates/${this.props.entity.id}`}
backUrl={`/templates/${this.props.entity.id}/edit`}
successUrl="/templates"
deletingMsg={t('Deleting template ...')}
deletedMsg={t('Template deleted')}/>
deletingMsg={t('deletingTemplate')}
deletedMsg={t('templateDeleted')}/>
}
<Title>{isEdit ? t('Edit Template') : t('Create Template')}</Title>
<Title>{isEdit ? t('editTemplate') : t('createTemplate')}</Title>
<Form stateOwner={this} onSubmitAsync={::this.submitHandler}>
<InputField id="name" label={t('Name')}/>
<TextArea id="description" label={t('Description')}/>
<InputField id="name" label={t('name')}/>
<TextArea id="description" label={t('description')}/>
{isEdit
?
<StaticField id="type" className={styles.formDisabled} label={t('Type')}>
<StaticField id="type" className={styles.formDisabled} label={t('type')}>
{typeKey && this.templateTypes[typeKey].typeName}
</StaticField>
:
<Dropdown id="type" label={t('Type')} options={typeOptions}/>
<Dropdown id="type" label={t('type')} options={typeOptions}/>
}
{typeForm}
@ -270,9 +270,9 @@ export default class CUD extends Component {
{editForm}
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={isEdit ? t('Save') : t('Save and edit template')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/templates/${this.props.entity.id}/delete`}/> }
{isEdit && <Button className="btn-danger" icon="send" label={t('Test send')} onClickAsync={async () => this.setState({showTestSendModal: true})}/> }
<Button type="submit" className="btn-primary" icon="ok" label={isEdit ? t('save') : t('saveAndEditTemplate')}/>
{canDelete && <NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/templates/${this.props.entity.id}/delete`}/> }
{isEdit && <Button className="btn-danger" icon="send" label={t('testSend')} onClickAsync={async () => this.setState({showTestSendModal: true})}/> }
</ButtonRow>
</Form>
</div>

View file

@ -1,7 +1,7 @@
'use strict';
import React, {Component} from 'react';
import {translate} from 'react-i18next';
import { withTranslation } from '../lib/i18n';
import {Icon} from '../lib/bootstrap-components';
import {
NavButton,
@ -24,7 +24,7 @@ import {
tableDeleteDialogRender
} from "../lib/modals";
@translate()
@withTranslation()
@withPageHelpers
@withErrorHandling
@requiresAuthenticatedUser
@ -70,11 +70,11 @@ export default class List extends Component {
const t = this.props.t;
const columns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('Description') },
{ data: 3, title: t('Type'), render: data => this.templateTypes[data].typeName },
{ data: 4, title: t('Created'), render: data => moment(data).fromNow() },
{ data: 5, title: t('Namespace') },
{ data: 1, title: t('name') },
{ data: 2, title: t('description') },
{ data: 3, title: t('type'), render: data => this.templateTypes[data].typeName },
{ data: 4, title: t('created'), render: data => moment(data).fromNow() },
{ data: 5, title: t('namespace') },
{
actions: data => {
const actions = [];
@ -82,21 +82,21 @@ export default class List extends Component {
if (perms.includes('edit')) {
actions.push({
label: <Icon icon="edit" title={t('Edit')}/>,
label: <Icon icon="edit" title={t('edit')}/>,
link: `/templates/${data[0]}/edit`
});
}
if (perms.includes('viewFiles')) {
actions.push({
label: <Icon icon="hdd" title={t('Files')}/>,
label: <Icon icon="hdd" title={t('files')}/>,
link: `/templates/${data[0]}/files`
});
}
if (perms.includes('share')) {
actions.push({
label: <Icon icon="share-alt" title={t('Share')}/>,
label: <Icon icon="share-alt" title={t('share')}/>,
link: `/templates/${data[0]}/share`
});
}
@ -110,17 +110,17 @@ export default class List extends Component {
return (
<div>
{tableDeleteDialogRender(this, `rest/templates`, t('Deleting template ...'), t('Template deleted'))}
{tableDeleteDialogRender(this, `rest/templates`, t('deletingTemplate'), t('templateDeleted'))}
<Toolbar>
{this.state.createPermitted &&
<NavButton linkTo="/templates/create" className="btn-primary" icon="plus" label={t('Create Template')}/>
<NavButton linkTo="/templates/create" className="btn-primary" icon="plus" label={t('createTemplate')}/>
}
{this.state.mosaicoTemplatesPermitted &&
<NavButton linkTo="/templates/mosaico" className="btn-primary" label={t('Mosaico Templates')}/>
<NavButton linkTo="/templates/mosaico" className="btn-primary" label={t('mosaicoTemplates')}/>
}
</Toolbar>
<Title>{t('Templates')}</Title>
<Title>{t('templates')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/templates-table" columns={columns} />
</div>

View file

@ -1,7 +1,7 @@
'use strict';
import React, {Component} from 'react';
import {translate} from 'react-i18next';
import { withTranslation } from '../lib/i18n';
import PropTypes
from 'prop-types';
import {ModalDialog} from "../lib/bootstrap-components";
@ -22,7 +22,7 @@ import axios from '../lib/axios';
import {getUrl} from "../lib/urls";
@translate()
@withTranslation()
@withForm
@withPageHelpers
@withErrorHandling
@ -64,7 +64,7 @@ export class TestSendModalDialog extends Component {
try {
this.hideFormValidation();
this.disableForm();
this.setFormStatusMessage('info', t('Sending test email'));
this.setFormStatusMessage('info', t('sendingTestEmail'));
const data = await this.props.getDataAsync();
data.listCid = this.getFormValue('list');
@ -90,19 +90,19 @@ export class TestSendModalDialog extends Component {
const t = this.props.t;
if (!state.getIn(['sendConfiguration', 'value'])) {
state.setIn(['sendConfiguration', 'error'], t('Send configuration has to be selected.'))
state.setIn(['sendConfiguration', 'error'], t('sendConfigurationHasToBeSelected'))
} else {
state.setIn(['sendConfiguration', 'error'], null);
}
if (!state.getIn(['list', 'value'])) {
state.setIn(['list', 'error'], t('List has to be selected.'))
state.setIn(['list', 'error'], t('listHasToBeSelected'))
} else {
state.setIn(['list', 'error'], null);
}
if (!state.getIn(['testUser', 'value'])) {
state.setIn(['testUser', 'error'], t('Subscription has to be selected.'))
state.setIn(['testUser', 'error'], t('subscriptionHasToBeSelected'))
} else {
state.setIn(['testUser', 'error'], null);
}
@ -114,37 +114,37 @@ export class TestSendModalDialog extends Component {
const listId = this.getFormValue('list');
const testUsersColumns = [
{ data: 1, title: t('Subscription ID'), render: data => <code>{data}</code> },
{ data: 2, title: t('Email') }
{ data: 1, title: t('subscriptionId'), render: data => <code>{data}</code> },
{ data: 2, title: t('email') }
];
const listsColumns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
{ data: 3, title: t('Subscribers') },
{ data: 4, title: t('Description') },
{ data: 5, title: t('Namespace') }
{ data: 1, title: t('name') },
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
{ data: 3, title: t('subscribers') },
{ data: 4, title: t('description') },
{ data: 5, title: t('namespace') }
];
const sendConfigurationsColumns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('ID'), render: data => <code>{data}</code> },
{ data: 3, title: t('Description') },
{ data: 4, title: t('Type'), render: data => this.mailerTypes[data].typeName },
{ data: 5, title: t('Created'), render: data => moment(data).fromNow() },
{ data: 6, title: t('Namespace') }
{ data: 1, title: t('name') },
{ data: 2, title: t('id'), render: data => <code>{data}</code> },
{ data: 3, title: t('description') },
{ data: 4, title: t('type'), render: data => this.mailerTypes[data].typeName },
{ data: 5, title: t('created'), render: data => moment(data).fromNow() },
{ data: 6, title: t('namespace') }
];
return (
<ModalDialog hidden={!this.props.visible} title={t('Send Test Email')} onCloseAsync={() => this.hideModal()} buttons={[
{ label: t('Send'), className: 'btn-danger', onClickAsync: ::this.performAction },
{ label: t('Cancel'), className: 'btn-primary', onClickAsync: ::this.hideModal }
<ModalDialog hidden={!this.props.visible} title={t('sendTestEmail')} onCloseAsync={() => this.hideModal()} buttons={[
{ label: t('send'), className: 'btn-danger', onClickAsync: ::this.performAction },
{ label: t('cancel'), className: 'btn-primary', onClickAsync: ::this.hideModal }
]}>
<Form stateOwner={this} format="wide">
<TableSelect id="sendConfiguration" format="wide" label={t('Send configuration')} withHeader dropdown dataUrl='rest/send-configurations-with-send-permission-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
<TableSelect id="list" format="wide" label={t('List')} withHeader dropdown dataUrl={`rest/lists-table`} columns={listsColumns} selectionKeyIndex={2} selectionLabelIndex={1} />
<TableSelect id="sendConfiguration" format="wide" label={t('sendConfiguration')} withHeader dropdown dataUrl='rest/send-configurations-with-send-permission-table' columns={sendConfigurationsColumns} selectionLabelIndex={1} />
<TableSelect id="list" format="wide" label={t('list')} withHeader dropdown dataUrl={`rest/lists-table`} columns={listsColumns} selectionKeyIndex={2} selectionLabelIndex={1} />
{ listId &&
<TableSelect id="testUser" format="wide" label={t('Subscription')} withHeader dropdown dataUrl={`rest/subscriptions-test-user-table/${listId}`} columns={testUsersColumns} selectionKeyIndex={1} selectionLabelIndex={2} />
<TableSelect id="testUser" format="wide" label={t('subscription')} withHeader dropdown dataUrl={`rest/subscriptions-test-user-table/${listId}`} columns={testUsersColumns} selectionKeyIndex={1} selectionLabelIndex={2} />
}
</Form>
</ModalDialog>

View file

@ -1,10 +1,10 @@
'use strict';
import React from "react";
import React
from "react";
import {
ACEEditor,
AlignedRow,
CheckBox,
CKEditor,
Dropdown,
StaticField,
@ -13,10 +13,10 @@ import {
import 'brace/mode/text';
import 'brace/mode/html';
import { MosaicoHost } from "../lib/sandboxed-mosaico";
import { CKEditorHost } from "../lib/sandboxed-ckeditor";
import { GrapesJSHost } from "../lib/sandboxed-grapesjs";
import { CodeEditorHost } from "../lib/sandboxed-codeeditor";
import {MosaicoHost} from "../lib/sandboxed-mosaico";
import {CKEditorHost} from "../lib/sandboxed-ckeditor";
import {GrapesJSHost} from "../lib/sandboxed-grapesjs";
import {CodeEditorHost} from "../lib/sandboxed-codeeditor";
import {
getGrapesJSSourceTypeOptions,
@ -24,8 +24,8 @@ import {
} from "../lib/sandboxed-grapesjs-shared";
import {
getCodeEditorSourceTypeOptions,
CodeEditorSourceType
CodeEditorSourceType,
getCodeEditorSourceTypeOptions
} from "../lib/sandboxed-codeeditor-shared";
import {getTemplateTypes as getMosaicoTemplateTypes} from './mosaico/helpers';
@ -34,14 +34,16 @@ import {
getSandboxUrl,
getTrustedUrl
} from "../lib/urls";
import mailtrainConfig from 'mailtrainConfig';
import mailtrainConfig
from 'mailtrainConfig';
import {
ActionLink,
Button
} from "../lib/bootstrap-components";
import {Trans} from "react-i18next";
import styles from "../lib/styles.scss";
import styles
from "../lib/styles.scss";
import {
base,
unbase
@ -78,18 +80,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
const mosaicoTemplateTypes = getMosaicoTemplateTypes(t);
const mosaicoTemplatesColumns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('Description') },
{ data: 3, title: t('Type'), render: data => mosaicoTemplateTypes[data].typeName },
{ data: 5, title: t('Namespace') },
{ data: 1, title: t('name') },
{ data: 2, title: t('description') },
{ data: 3, title: t('type'), render: data => mosaicoTemplateTypes[data].typeName },
{ data: 5, title: t('namespace') },
];
templateTypes.mosaico = {
typeName: t('Mosaico'),
typeName: t('mosaico'),
getTypeForm: (owner, isEdit) =>
<TableSelect id={prefix + 'mosaicoTemplate'} label={t('Mosaico template')} withHeader dropdown dataUrl='rest/mosaico-templates-table' columns={mosaicoTemplatesColumns} selectionLabelIndex={1} disabled={isEdit} />,
<TableSelect id={prefix + 'mosaicoTemplate'} label={t('mosaicoTemplate')} withHeader dropdown dataUrl='rest/mosaico-templates-table' columns={mosaicoTemplatesColumns} selectionLabelIndex={1} disabled={isEdit} />,
getHTMLEditor: owner =>
<AlignedRow label={t('Template content (HTML)')}>
<AlignedRow label={t('templateContentHtml')}>
<MosaicoHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
@ -97,7 +99,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
templateId={owner.getFormValue(prefix + 'mosaicoTemplate')}
entityTypeId={entityTypeId}
title={t('Mosaico Template Designer')}
title={t('mosaicoTemplateDesigner')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
@ -137,7 +139,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
validate: state => {
const mosaicoTemplate = state.getIn([prefix + 'mosaicoTemplate', 'value']);
if (!mosaicoTemplate) {
state.setIn([prefix + 'mosaicoTemplate', 'error'], t('Mosaico template must be selected'));
state.setIn([prefix + 'mosaicoTemplate', 'error'], t('mosaicoTemplateMustBeSelected'));
}
}
};
@ -146,16 +148,16 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
const mosaicoFsTemplatesLabels = new Map(mailtrainConfig.mosaico.fsTemplates.map(({key, label}) => ([key, label])));
templateTypes.mosaicoWithFsTemplate = {
typeName: t('Mosaico with predefined templates'),
typeName: t('mosaicoWithPredefinedTemplates'),
getTypeForm: (owner, isEdit) => {
if (isEdit) {
return <StaticField id={prefix + 'mosaicoFsTemplate'} className={styles.formDisabled} label={t('Mosaico Template')}>{mosaicoFsTemplatesLabels.get(owner.getFormValue(prefix + 'mosaicoFsTemplate'))}</StaticField>;
return <StaticField id={prefix + 'mosaicoFsTemplate'} className={styles.formDisabled} label={t('mosaicoTemplate-1')}>{mosaicoFsTemplatesLabels.get(owner.getFormValue(prefix + 'mosaicoFsTemplate'))}</StaticField>;
} else {
return <Dropdown id={prefix + 'mosaicoFsTemplate'} label={t('Mosaico Template')} options={mosaicoFsTemplatesOptions}/>;
return <Dropdown id={prefix + 'mosaicoFsTemplate'} label={t('mosaicoTemplate-1')} options={mosaicoFsTemplatesOptions}/>;
}
},
getHTMLEditor: owner =>
<AlignedRow label={t('Template content (HTML)')}>
<AlignedRow label={t('templateContentHtml')}>
<MosaicoHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
@ -163,7 +165,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
initialMetadata={owner.getFormValue(prefix + 'mosaicoData').metadata}
templatePath={getSandboxUrl(`static/mosaico/templates/${owner.getFormValue(prefix + 'mosaicoFsTemplate')}/index.html`)}
entityTypeId={entityTypeId}
title={t('Mosaico Template Designer')}
title={t('mosaicoTemplateDesigner')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
@ -211,16 +213,16 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
}
templateTypes.grapesjs = {
typeName: t('GrapesJS'),
typeName: t('grapesJs'),
getTypeForm: (owner, isEdit) => {
if (isEdit) {
return <StaticField id={prefix + 'grapesJSSourceType'} className={styles.formDisabled} label={t('Type')}>{grapesJSSourceTypeLabels[owner.getFormValue(prefix + 'grapesJSSourceType')]}</StaticField>;
return <StaticField id={prefix + 'grapesJSSourceType'} className={styles.formDisabled} label={t('type')}>{grapesJSSourceTypeLabels[owner.getFormValue(prefix + 'grapesJSSourceType')]}</StaticField>;
} else {
return <Dropdown id={prefix + 'grapesJSSourceType'} label={t('Type')} options={grapesJSSourceTypes}/>;
return <Dropdown id={prefix + 'grapesJSSourceType'} label={t('type')} options={grapesJSSourceTypes}/>;
}
},
getHTMLEditor: owner =>
<AlignedRow label={t('Template content (HTML)')}>
<AlignedRow label={t('templateContentHtml')}>
<GrapesJSHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
@ -228,7 +230,7 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
initialSource={owner.getFormValue(prefix + 'grapesJSData').source}
initialStyle={owner.getFormValue(prefix + 'grapesJSData').style}
sourceType={owner.getFormValue(prefix + 'grapesJSSourceType')}
title={t('GrapesJS Template Designer')}
title={t('grapesJsTemplateDesigner')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
@ -269,16 +271,16 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
};
templateTypes.ckeditor4 = {
typeName: t('CKEditor 4'),
typeName: t('ckEditor4'),
getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner =>
<AlignedRow label={t('Template content (HTML)')}>
<AlignedRow label={t('templateContentHtml')}>
<CKEditorHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
initialSource={owner.getFormValue(prefix + 'ckeditor4Data').source}
entityTypeId={entityTypeId}
title={t('CKEditor 4 Template Designer')}
title={t('ckEditor4TemplateDesigner')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
@ -313,9 +315,9 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
};
templateTypes.ckeditor5 = {
typeName: t('CKEditor 5'),
typeName: t('ckEditor5'),
getTypeForm: (owner, isEdit) => null,
getHTMLEditor: owner => <CKEditor id={prefix + 'ckeditor5Source'} height="600px" mode="html" label={t('Template content (HTML)')}/>,
getHTMLEditor: owner => <CKEditor id={prefix + 'ckeditor5Source'} height="600px" mode="html" label={t('templateContentHtml')}/>,
exportHTMLEditorData: async owner => {
const preHtml = '<!doctype html><html><head><meta charset="utf-8"><title></title></head><body>';
const postHtml = '</body></html>';
@ -362,24 +364,24 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
}
templateTypes.codeeditor = {
typeName: t('Code Editor'),
typeName: t('codeEditor'),
getTypeForm: (owner, isEdit) => {
const sourceType = owner.getFormValue(prefix + 'codeEditorSourceType');
if (isEdit) {
return <StaticField id={prefix + 'codeEditorSourceType'} className={styles.formDisabled} label={t('Type')}>{codeEditorSourceTypeLabels[sourceType]}</StaticField>;
return <StaticField id={prefix + 'codeEditorSourceType'} className={styles.formDisabled} label={t('type')}>{codeEditorSourceTypeLabels[sourceType]}</StaticField>;
} else {
return <Dropdown id={prefix + 'codeEditorSourceType'} label={t('Type')} options={codeEditorSourceTypes}/>;
return <Dropdown id={prefix + 'codeEditorSourceType'} label={t('type')} options={codeEditorSourceTypes}/>;
}
},
getHTMLEditor: owner =>
<AlignedRow label={t('Template content (HTML)')}>
<AlignedRow label={t('templateContentHtml')}>
<CodeEditorHost
ref={node => owner.editorNode = node}
entity={owner.props.entity}
entityTypeId={entityTypeId}
initialSource={owner.getFormValue(prefix + 'codeEditorData').source}
sourceType={owner.getFormValue(prefix + 'codeEditorSourceType')}
title={t('Code Editor Template Designer')}
title={t('codeEditorTemplateDesigner')}
onTestSend={::owner.showTestSendModal}
onFullscreenAsync={::owner.setElementInFullscreen}
/>
@ -426,19 +428,19 @@ export function getEditForm(owner, typeKey, prefix = '') {
return <div>
<AlignedRow>
<Button className="btn-default" onClickAsync={::owner.toggleMergeTagReference} label={t('Merge tag reference')}/>
<Button className="btn-default" onClickAsync={::owner.toggleMergeTagReference} label={t('mergeTagReference')}/>
{owner.state.showMergeTagReference &&
<div style={{marginTop: '15px'}}>
<Trans><p>Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: <code>[TAG_NAME]</code> or <code>[TAG_NAME/fallback]</code> where <code>fallback</code> is an optional text value used when <code>TAG_NAME</code> is empty.</p></Trans>
<Trans><p>You can use any of the standard merge tags below. In addition to that every custom field has its own merge tag. Check the fields of the list you are going to send to.</p></Trans>
<Trans i18nKey="mergeTagsAreTagsThatAreReplacedBefore"><p>Merge tags are tags that are replaced before sending out the message. The format of the merge tag is the following: <code>[TAG_NAME]</code> or <code>[TAG_NAME/fallback]</code> where <code>fallback</code> is an optional text value used when <code>TAG_NAME</code> is empty.</p></Trans>
<Trans i18nKey="youCanUseAnyOfTheStandardMergeTagsBelow"><p>You can use any of the standard merge tags below. In addition to that every custom field has its own merge tag. Check the fields of the list you are going to send to.</p></Trans>
<table className="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>
<Trans>Merge tag</Trans>
<Trans i18nKey="mergeTag-1">Merge tag</Trans>
</th>
<th>
<Trans>Description</Trans>
<Trans i18nKey="description">Description</Trans>
</th>
</tr>
</thead>
@ -448,7 +450,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[LINK_UNSUBSCRIBE]
</th>
<td>
<Trans>URL that points to the unsubscribe page</Trans>
<Trans i18nKey="urlThatPointsToTheUnsubscribePage">URL that points to the unsubscribe page</Trans>
</td>
</tr>
<tr>
@ -456,7 +458,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[LINK_PREFERENCES]
</th>
<td>
<Trans>URL that points to the preferences page of the subscriber</Trans>
<Trans i18nKey="urlThatPointsToThePreferencesPageOfThe">URL that points to the preferences page of the subscriber</Trans>
</td>
</tr>
<tr>
@ -464,7 +466,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[LINK_BROWSER]
</th>
<td>
<Trans>URL to preview the message in a browser</Trans>
<Trans i18nKey="urlToPreviewTheMessageInABrowser">URL to preview the message in a browser</Trans>
</td>
</tr>
<tr>
@ -472,7 +474,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[EMAIL]
</th>
<td>
<Trans>Email address</Trans>
<Trans i18nKey="emailAddress-1">Email address</Trans>
</td>
</tr>
<tr>
@ -480,7 +482,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[TO_NAME]
</th>
<td>
<Trans>Recipient name as it appears in email's 'To' header</Trans>
<Trans i18nKey="recipientNameAsItAppearsInEmailsToHeader">Recipient name as it appears in email's 'To' header</Trans>
</td>
</tr>
<tr>
@ -488,7 +490,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[SUBSCRIPTION_ID]
</th>
<td>
<Trans>Unique ID that identifies the recipient</Trans>
<Trans i18nKey="uniqueIdThatIdentifiesTheRecipient">Unique ID that identifies the recipient</Trans>
</td>
</tr>
<tr>
@ -496,7 +498,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[LIST_ID]
</th>
<td>
<Trans>Unique ID that identifies the list used for this campaign</Trans>
<Trans i18nKey="uniqueIdThatIdentifiesTheListUsedForThis">Unique ID that identifies the list used for this campaign</Trans>
</td>
</tr>
<tr>
@ -504,20 +506,20 @@ export function getEditForm(owner, typeKey, prefix = '') {
[CAMPAIGN_ID]
</th>
<td>
<Trans>Unique ID that identifies current campaign</Trans>
<Trans i18nKey="uniqueIdThatIdentifiesCurrentCampaign">Unique ID that identifies current campaign</Trans>
</td>
</tr>
</tbody>
</table>
<Trans><p>For RSS campaigns, the following further tags can be used.</p></Trans>
<Trans i18nKey="forRssCampaignsTheFollowingFurtherTags"><p>For RSS campaigns, the following further tags can be used.</p></Trans>
<table className="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>
<Trans>Merge tag</Trans>
<Trans i18nKey="mergeTag-1">Merge tag</Trans>
</th>
<th>
<Trans>Description</Trans>
<Trans i18nKey="description">Description</Trans>
</th>
</tr>
</thead>
@ -527,7 +529,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[RSS_ENTRY_TITLE]
</th>
<td>
<Trans>RSS entry title</Trans>
<Trans i18nKey="rssEntryTitle">RSS entry title</Trans>
</td>
</tr>
<tr>
@ -535,7 +537,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[RSS_ENTRY_DATE]
</th>
<td>
<Trans>RSS entry date</Trans>
<Trans i18nKey="rssEntryDate">RSS entry date</Trans>
</td>
</tr>
<tr>
@ -543,7 +545,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[RSS_ENTRY_LINK]
</th>
<td>
<Trans>RSS entry link</Trans>
<Trans i18nKey="rssEntryLink">RSS entry link</Trans>
</td>
</tr>
<tr>
@ -551,7 +553,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[RSS_ENTRY_CONTENT]
</th>
<td>
<Trans>Content of an RSS entry</Trans>
<Trans i18nKey="contentOfAnRssEntry">Content of an RSS entry</Trans>
</td>
</tr>
<tr>
@ -559,7 +561,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[RSS_ENTRY_SUMMARY]
</th>
<td>
<Trans>RSS entry summary</Trans>
<Trans i18nKey="rssEntrySummary">RSS entry summary</Trans>
</td>
</tr>
<tr>
@ -567,7 +569,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
[RSS_ENTRY_IMAGE_URL]
</th>
<td>
<Trans>RSS entry image URL</Trans>
<Trans i18nKey="rssEntryImageUrl">RSS entry image URL</Trans>
</td>
</tr>
</tbody>
@ -577,7 +579,7 @@ export function getEditForm(owner, typeKey, prefix = '') {
{owner.templateTypes[typeKey].getHTMLEditor(owner)}
<ACEEditor id={prefix + 'text'} height="400px" mode="text" label={t('Template content (plain text)')} help={<Trans>To extract the text from HTML click <ActionLink onClickAsync={::owner.extractPlainText}>here</ActionLink>. Please note that your existing plaintext in the field above will be overwritten. This feature uses the <a href="http://premailer.dialect.ca/api">Premailer API</a>, a third party service. Their Terms of Service and Privacy Policy apply.</Trans>}/>
<ACEEditor id={prefix + 'text'} height="400px" mode="text" label={t('templateContentPlainText')} help={<Trans i18nKey="toExtractTheTextFromHtmlClickHerePlease">To extract the text from HTML click <ActionLink onClickAsync={::owner.extractPlainText}>here</ActionLink>. Please note that your existing plaintext in the field above will be overwritten. This feature uses the <a href="http://premailer.dialect.ca/api">Premailer API</a>, a third party service. Their Terms of Service and Privacy Policy apply.</Trans>}/>
</div>;
}

View file

@ -2,7 +2,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {translate} from 'react-i18next';
import { withTranslation } from '../../lib/i18n';
import {
NavButton,
requiresAuthenticatedUser,
@ -32,7 +32,7 @@ import {
getTemplateTypesOrder
} from "./helpers";
@translate()
@withTranslation()
@withForm
@withPageHelpers
@withErrorHandling
@ -96,13 +96,13 @@ export default class CUD extends Component {
const t = this.props.t;
if (!state.getIn(['name', 'value'])) {
state.setIn(['name', 'error'], t('Name must not be empty'));
state.setIn(['name', 'error'], t('nameMustNotBeEmpty'));
} else {
state.setIn(['name', 'error'], null);
}
if (!state.getIn(['type', 'value'])) {
state.setIn(['type', 'error'], t('Type must be selected'));
state.setIn(['type', 'error'], t('typeMustBeSelected'));
} else {
state.setIn(['type', 'error'], null);
}
@ -131,7 +131,7 @@ export default class CUD extends Component {
}
this.disableForm();
this.setFormStatusMessage('info', t('Saving ...'));
this.setFormStatusMessage('info', t('saving'));
const submitSuccessful = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
this.templateTypes[data.type].beforeSave(data);
@ -143,13 +143,13 @@ export default class CUD extends Component {
this.templateTypes[data.type].afterLoad(data);
});
this.enableForm();
this.setFormStatusMessage('success', t('Mosaico template saved'));
this.setFormStatusMessage('success', t('mosaicoTemplateSaved'));
} else {
this.navigateToWithFlashMessage('/templates/mosaico', 'success', t('Mosaico template saved'));
this.navigateToWithFlashMessage('/templates/mosaico', 'success', t('mosaicoTemplateSaved'));
}
} else {
this.enableForm();
this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.'));
this.setFormStatusMessage('warning', t('thereAreErrorsInTheFormPleaseFixThemAnd'));
}
}
@ -173,31 +173,31 @@ export default class CUD extends Component {
deleteUrl={`rest/mosaico-templates/${this.props.entity.id}`}
backUrl={`/templates/mosaico/${this.props.entity.id}/edit`}
successUrl="/templates/mosaico"
deletingMsg={t('Deleting Mosaico template ...')}
deletedMsg={t('Mosaico template deleted')}/>
deletingMsg={t('deletingMosaicoTemplate')}
deletedMsg={t('mosaicoTemplateDeleted')}/>
}
<Title>{isEdit ? t('Edit Mosaico Template') : t('Create Mosaico Template')}</Title>
<Title>{isEdit ? t('editMosaicoTemplate') : t('createMosaicoTemplate')}</Title>
<Form stateOwner={this} onSubmitAsync={::this.submitAndLeave}>
<InputField id="name" label={t('Name')}/>
<TextArea id="description" label={t('Description')}/>
<Dropdown id="type" label={t('Type')} options={this.typeOptions}/>
<InputField id="name" label={t('name')}/>
<TextArea id="description" label={t('description')}/>
<Dropdown id="type" label={t('type')} options={this.typeOptions}/>
<NamespaceSelect/>
{form}
{isEdit ?
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save and Stay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save and Leave')}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('saveAndStay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('saveAndLeave')}/>
{canDelete &&
<NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/templates/mosaico/${this.props.entity.id}/delete`}/>
<NavButton className="btn-danger" icon="remove" label={t('delete')} linkTo={`/templates/mosaico/${this.props.entity.id}/delete`}/>
}
</ButtonRow>
:
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save')}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('save')}/>
</ButtonRow>
}
</Form>

View file

@ -1,7 +1,7 @@
'use strict';
import React, { Component } from 'react';
import { translate } from 'react-i18next';
import { withTranslation } from '../../lib/i18n';
import {DropdownMenu, Icon} from '../../lib/bootstrap-components';
import { requiresAuthenticatedUser, withPageHelpers, Title, Toolbar, MenuLink } from '../../lib/page';
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling';
@ -17,7 +17,7 @@ import {
} from "../../lib/modals";
@translate()
@withTranslation()
@withPageHelpers
@withErrorHandling
@requiresAuthenticatedUser
@ -54,11 +54,11 @@ export default class List extends Component {
const t = this.props.t;
const columns = [
{ data: 1, title: t('Name') },
{ data: 2, title: t('Description') },
{ data: 3, title: t('Type'), render: data => this.templateTypes[data].typeName },
{ data: 4, title: t('Created'), render: data => moment(data).fromNow() },
{ data: 5, title: t('Namespace') },
{ data: 1, title: t('name') },
{ data: 2, title: t('description') },
{ data: 3, title: t('type'), render: data => this.templateTypes[data].typeName },
{ data: 4, title: t('created'), render: data => moment(data).fromNow() },
{ data: 5, title: t('namespace') },
{
actions: data => {
const actions = [];
@ -66,28 +66,28 @@ export default class List extends Component {
if (perms.includes('edit')) {
actions.push({
label: <Icon icon="edit" title={t('Edit')}/>,
label: <Icon icon="edit" title={t('edit')}/>,
link: `/templates/mosaico/${data[0]}/edit`
});
}
if (perms.includes('viewFiles')) {
actions.push({
label: <Icon icon="hdd" title={t('Files')}/>,
label: <Icon icon="hdd" title={t('files')}/>,
link: `/templates/mosaico/${data[0]}/files`
});
}
if (perms.includes('viewFiles')) {
actions.push({
label: <Icon icon="th-large" title={t('Block thumbnails')}/>,
label: <Icon icon="th-large" title={t('blockThumbnails')}/>,
link: `/templates/mosaico/${data[0]}/blocks`
});
}
if (perms.includes('share')) {
actions.push({
label: <Icon icon="share-alt" title={t('Share')}/>,
label: <Icon icon="share-alt" title={t('share')}/>,
link: `/templates/mosaico/${data[0]}/share`
});
}
@ -101,17 +101,17 @@ export default class List extends Component {
return (
<div>
{tableDeleteDialogRender(this, `rest/mosaico-templates`, t('Deleting Mosaico template ...'), t('Mosaico template deleted'))}
{tableDeleteDialogRender(this, `rest/mosaico-templates`, t('deletingMosaicoTemplate'), t('mosaicoTemplateDeleted'))}
{this.state.createPermitted &&
<Toolbar>
<DropdownMenu className="btn-primary" label={t('Create Mosaico Template')}>
<MenuLink to="/templates/mosaico/create">{t('Blank')}</MenuLink>
<MenuLink to="/templates/mosaico/create/versafix">{t('Versafix One')}</MenuLink>
<DropdownMenu className="btn-primary" label={t('createMosaicoTemplate')}>
<MenuLink to="/templates/mosaico/create">{t('blank')}</MenuLink>
<MenuLink to="/templates/mosaico/create/versafix">{t('versafixOne')}</MenuLink>
</DropdownMenu>
</Toolbar>
}
<Title>{t('Mosaico Templates')}</Title>
<Title>{t('mosaicoTemplates')}</Title>
<Table ref={node => this.table = node} withHeader dataUrl="rest/mosaico-templates-table" columns={columns} />
</div>

View file

@ -18,8 +18,8 @@ export function getTemplateTypes(t) {
}
templateTypes.html = {
typeName: t('HTML'),
getForm: owner => <ACEEditor id="html" height="700px" mode="html" label={t('Template content')}/>,
typeName: t('html'),
getForm: owner => <ACEEditor id="html" height="700px" mode="html" label={t('templateContent')}/>,
afterLoad: data => {
data.html = data.data.html;
},
@ -33,8 +33,8 @@ export function getTemplateTypes(t) {
};
templateTypes.mjml = {
typeName: t('MJML'),
getForm: owner => <ACEEditor id="html" height="700px" mode="xml" label={t('Template content')}/>,
typeName: t('mjml'),
getForm: owner => <ACEEditor id="html" height="700px" mode="xml" label={t('templateContent')}/>,
afterLoad: data => {
data.mjml = data.data.mjml;
},

View file

@ -13,81 +13,81 @@ import MosaicoList from './mosaico/List';
function getMenus(t) {
return {
'templates': {
title: t('Templates'),
title: t('templates'),
link: '/templates',
panelComponent: TemplatesList,
children: {
':templateId([0-9]+)': {
title: resolved => t('Template "{{name}}"', {name: resolved.template.name}),
title: resolved => t('templateName', {name: resolved.template.name}),
resolve: {
template: params => `rest/templates/${params.templateId}`
},
link: params => `/templates/${params.templateId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
title: t('edit'),
link: params => `/templates/${params.templateId}/edit`,
visible: resolved => resolved.template.permissions.includes('edit'),
panelRender: props => <TemplatesCUD action={props.match.params.action} entity={props.resolved.template} />
},
files: {
title: t('Files'),
title: t('files'),
link: params => `/templates/${params.templateId}/files`,
visible: resolved => resolved.template.permissions.includes('viewFiles'),
panelRender: props => <Files title={t('Files')} help={t('These files are publicly available via HTTP so that they can be linked to from the content of the campaign.')} entity={props.resolved.template} entityTypeId="template" entitySubTypeId="file" managePermission="manageFiles"/>
panelRender: props => <Files title={t('files')} help={t('theseFilesArePubliclyAvailableViaHttpSo')} entity={props.resolved.template} entityTypeId="template" entitySubTypeId="file" managePermission="manageFiles"/>
},
share: {
title: t('Share'),
title: t('share'),
link: params => `/templates/${params.templateId}/share`,
visible: resolved => resolved.template.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.template} entityTypeId="template" />
panelRender: props => <Share title={t('share')} entity={props.resolved.template} entityTypeId="template" />
}
}
},
create: {
title: t('Create'),
title: t('create'),
panelRender: props => <TemplatesCUD action="create" />
},
mosaico: {
title: t('Mosaico Templates'),
title: t('mosaicoTemplates'),
link: '/templates/mosaico',
panelComponent: MosaicoList,
children: {
':mosaiceTemplateId([0-9]+)': {
title: resolved => t('Mosaico Template "{{name}}"', {name: resolved.mosaicoTemplate.name}),
title: resolved => t('mosaicoTemplateName', {name: resolved.mosaicoTemplate.name}),
resolve: {
mosaicoTemplate: params => `rest/mosaico-templates/${params.mosaiceTemplateId}`
},
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/edit`,
navs: {
':action(edit|delete)': {
title: t('Edit'),
title: t('edit'),
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/edit`,
visible: resolved => resolved.mosaicoTemplate.permissions.includes('edit'),
panelRender: props => <MosaicoCUD action={props.match.params.action} entity={props.resolved.mosaicoTemplate} />
},
files: {
title: t('Files'),
title: t('files'),
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/files`,
visible: resolved => resolved.mosaicoTemplate.permissions.includes('viewFiles'),
panelRender: props => <Files title={t('Files')} help={t('These files are publicly available via HTTP so that they can be linked to from the Mosaico template.')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="file" managePermission="manageFiles" />
panelRender: props => <Files title={t('files')} help={t('theseFilesArePubliclyAvailableViaHttpSo-1')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="file" managePermission="manageFiles" />
},
blocks: {
title: t('Block thumbnails'),
title: t('blockThumbnails'),
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/blocks`,
visible: resolved => resolved.mosaicoTemplate.permissions.includes('viewFiles'),
panelRender: props => <Files title={t('Block thumbnails')} help={t('These files will be used by Mosaico to search for block thumbnails (the "edres" directory). Place here one file per block type that you have defined in the Mosaico template. Each file must have the same name as the block id. The file will be used as the thumbnail of the corresponding block.')}entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="block" managePermission="manageFiles" />
panelRender: props => <Files title={t('blockThumbnails')} help={t('theseFilesWillBeUsedByMosaicoToSearchFor')}entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" entitySubTypeId="block" managePermission="manageFiles" />
},
share: {
title: t('Share'),
title: t('share'),
link: params => `/templates/mosaico/${params.mosaiceTemplateId}/share`,
visible: resolved => resolved.mosaicoTemplate.permissions.includes('share'),
panelRender: props => <Share title={t('Share')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" />
panelRender: props => <Share title={t('share')} entity={props.resolved.mosaicoTemplate} entityTypeId="mosaicoTemplate" />
}
}
},
create: {
title: t('Create'),
title: t('create'),
extraParams: [':wizard?'],
panelRender: props => <MosaicoCUD action="create" wizard={props.match.params.wizard} />
}