Updated translation support

This commit is contained in:
Andris Reinman 2017-03-07 16:30:56 +02:00
parent b1e8cd68cd
commit d25565b6f8
114 changed files with 42095 additions and 1902 deletions

3
.eslintrc Normal file
View file

@ -0,0 +1,3 @@
{
"extends": "nodemailer"
}

View file

@ -1,70 +0,0 @@
'use strict';
module.exports = {
rules: {
indent: [2, 4, {
SwitchCase: 1
}],
quotes: [2, 'single'],
'linebreak-style': [2, 'unix'],
semi: [2, 'always'],
strict: [2, 'global'],
eqeqeq: 2,
'dot-notation': 2,
curly: 2,
'no-fallthrough': 2,
'quote-props': [2, 'as-needed'],
'no-unused-expressions': [2, {
allowShortCircuit: true
}],
'no-unused-vars': 2,
'no-undefined': 2,
'handle-callback-err': 2,
'no-new': 2,
'new-cap': 2,
'no-eval': 2,
'no-invalid-this': 2,
radix: [2, 'always'],
'no-use-before-define': [2, 'nofunc'],
'callback-return': [2, ['callback', 'cb', 'done']],
'comma-dangle': [2, 'never'],
'comma-style': [2, 'last'],
'no-regex-spaces': 2,
'no-empty': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-redeclare': [2, {
builtinGlobals: true
}],
'block-scoped-var': 2,
'no-sequences': 2,
'no-throw-literal': 2,
'no-useless-call': 2,
'no-useless-concat': 2,
'no-void': 2,
yoda: 2,
'no-undef': 2,
'global-require': 2,
'no-var': 2,
'no-bitwise': 2,
'no-lonely-if': 2,
'no-mixed-spaces-and-tabs': 2,
'arrow-body-style': [2, 'as-needed'],
'arrow-parens': [2, 'as-needed'],
'prefer-arrow-callback': 2,
'object-shorthand': 2,
'prefer-spread': 2
},
env: {
es6: true,
node: true
},
extends: 'eslint:recommended',
globals: {
it: true,
describe: true,
beforeEach: true,
afterEach: true
},
fix: true
};

View file

@ -180,7 +180,15 @@ This command generates a CSV file with 100 000 subscriber accounts
## Translations
Mailtrain is currently not translated but it supports translations. To add translations you first need to add translation support for the translatable strings
Mailtrain is currently not translated but it supports translations. To add translations you first need to add translation support for the translatable strings. To test if strings are translatable or not, use a fake language with code "zz"
```toml
language="zz"
```
This would modify all input strings. If a string is not modified then it does not support translations.
![](https://cldup.com/qXxAbaq2F1.png)
### Translating JavaScript files

12
app.js
View file

@ -3,7 +3,7 @@
let config = require('config');
let log = require('npmlog');
let translate = require('./lib/translate');
let _ = require('./lib/translate')._;
let util = require('util');
let express = require('express');
@ -96,14 +96,14 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer
);
});
// {{#translate}}abc{{/translate}}
// {{#translate}}abc{{/translate}}
hbs.registerHelper('translate', function (context, options) { // eslint-disable-line prefer-arrow-callback
if (typeof options === 'undefined' && context) {
options = context;
context = false;
}
let result = translate._(options.fn(this)); // eslint-disable-line no-invalid-this
let result = _(options.fn(this)); // eslint-disable-line no-invalid-this
if (Array.isArray(context)) {
result = util.format(result, ...context);
@ -137,7 +137,7 @@ app.use(session({
app.use(flash());
app.use((req, res, next) => {
req._ = str => translate._(str);
req._ = str => _(str);
next();
});
@ -166,7 +166,7 @@ app.use((req, res, next) => {
};
let menu = [{
title: 'Home',
title: _('Home'),
url: '/',
selected: true
}];
@ -208,7 +208,7 @@ app.use('/api', api);
// catch 404 and forward to error handler
app.use((req, res, next) => {
let err = new Error('Not Found');
let err = new Error(_('Not Found'));
err.status = 404;
next(err);
});

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

26
lib/fakelang.js Normal file
View file

@ -0,0 +1,26 @@
'use strict';
/* lloyd|2012|http://wtfpl.org */
/* eslint-disable */
module.exports = str => {
let from = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+\\|`~[{]};:'\",<.>/?";
let to = "ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅX⅄Z0123456789¡@#$%ᵥ⅋⁎()-_=+\\|,~[{]};:,„´<.>/¿";
return str.replace(/(\{\{[^\}]+\}\}|%s)/g, '\x00\x04$1\x00').split('\x00').map(c => {
if (c.charAt(0) === '\x04') {
return c;
}
let r = '';
for (let i = 0, len = c.length; i < len; i++) {
let pos = from.indexOf(c.charAt(i));
if (pos < 0) {
r += c.charAt(i);
} else {
r += to.charAt(pos);
}
}
return r;
}).join('\x00').replace(/[\x00\x04]/g, '');
}

View file

@ -2,6 +2,8 @@
let FeedParser = require('feedparser');
let request = require('request');
let _ = require('./translate')._;
let util = require('util');
module.exports.fetch = (url, callback) => {
let req = request(url);
@ -26,7 +28,7 @@ module.exports.fetch = (url, callback) => {
}
if (res.statusCode !== 200) {
return req.emit('error', new Error('Bad status code'));
return req.emit('error', new Error(util.format(_('Bad status code %s'), res.statusCode)));
}
req.pipe(feedparser);

View file

@ -2,6 +2,7 @@
let lists = require('./models/lists');
let fields = require('./models/fields');
let _ = require('./translate')._;
module.exports = {
getDefaultMergeTags,
@ -13,34 +14,34 @@ function getDefaultMergeTags(callback) {
callback(null, [
{
key: 'LINK_UNSUBSCRIBE',
value: 'URL that points to the unsubscribe page'
value: _('URL that points to the unsubscribe page')
}, {
key: 'LINK_PREFERENCES',
value: 'URL that points to the preferences page of the subscriber'
value: _('URL that points to the preferences page of the subscriber')
}, {
key: 'LINK_BROWSER',
value: 'URL to preview the message in a browser'
value: _('URL to preview the message in a browser')
}, {
key: 'EMAIL',
value: 'Email address'
value: _('Email address')
}, {
key: 'FIRST_NAME',
value: 'First name'
value: _('First name')
}, {
key: 'LAST_NAME',
value: 'Last name'
value: _('Last name')
}, {
key: 'FULL_NAME',
value: 'Full name (first and last name combined)'
value: _('Full name (first and last name combined)')
}, {
key: 'SUBSCRIPTION_ID',
value: 'Unique ID that identifies the recipient'
value: _('Unique ID that identifies the recipient')
}, {
key: 'LIST_ID',
value: 'Unique ID that identifies the list used for this campaign'
value: _('Unique ID that identifies the list used for this campaign')
}, {
key: 'CAMPAIGN_ID',
value: 'Unique ID that identifies current campaign'
value: _('Unique ID that identifies current campaign')
}
]);
}

View file

@ -14,6 +14,23 @@ let templates = new Map();
let htmlToText = require('html-to-text');
let aws = require('aws-sdk');
let _ = require('./translate')._;
let util = require('util');
Handlebars.registerHelper('translate', function (context, options) { // eslint-disable-line prefer-arrow-callback
if (typeof options === 'undefined' && context) {
options = context;
context = false;
}
let result = _(options.fn(this)); // eslint-disable-line no-invalid-this
if (Array.isArray(context)) {
result = util.format(result, ...context);
}
return new Handlebars.SafeString(result);
});
module.exports.transport = false;
module.exports.update = () => {
@ -195,7 +212,7 @@ function createMailer(callback) {
}
};
} else {
return callback(new Error('Invalid mail transport'));
return callback(new Error(_('Invalid mail transport')));
}
module.exports.transport = nodemailer.createTransport(transportOptions, config.nodemailer);

View file

@ -12,6 +12,7 @@ let feed = require('../feed');
let log = require('npmlog');
let mailer = require('../mailer');
let humanize = require('humanize');
let _ = require('../translate')._;
let allowedKeys = ['description', 'from', 'address', 'reply_to', 'subject', 'editor_name', 'editor_data', 'template', 'source_url', 'list', 'segment', 'html', 'text', 'tracking_disabled'];
@ -267,7 +268,7 @@ module.exports.filterStatusSubscribers = (campaign, status, request, columns, ca
module.exports.getByCid = (cid, callback) => {
cid = (cid || '').toString().trim();
if (!cid) {
return callback(new Error('Missing Campaign ID'));
return callback(new Error(_('Missing Campaign ID')));
}
db.getConnection((err, connection) => {
if (err) {
@ -294,7 +295,7 @@ module.exports.get = (id, withSegment, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Campaign ID'));
return callback(new Error(_('Missing Campaign ID')));
}
db.getConnection((err, connection) => {
@ -367,7 +368,7 @@ module.exports.getAttachments = (campaign, callback) => {
campaign = Number(campaign) || 0;
if (campaign < 1) {
return callback(new Error('Missing Campaign ID'));
return callback(new Error(_('Missing Campaign ID')));
}
db.getConnection((err, connection) => {
@ -403,7 +404,7 @@ module.exports.addAttachment = (id, attachment, callback) => {
let size = attachment.content ? attachment.content.length : 0;
if (!size) {
return callback(new Error('Emtpy or too large attahcment'));
return callback(new Error(_('Emtpy or too large attahcment')));
}
db.getConnection((err, connection) => {
if (err) {
@ -490,7 +491,7 @@ module.exports.getLinks = (id, linkId, callback) => {
linkId = Number(linkId) || 0;
if (id < 1) {
return callback(new Error('Missing Campaign ID'));
return callback(new Error(_('Missing Campaign ID')));
}
db.getConnection((err, connection) => {
@ -569,11 +570,11 @@ module.exports.create = (campaign, opts, callback) => {
campaign.template = Number(campaign.template) || 0;
if (!name) {
return callback(new Error('Campaign Name must be set'));
return callback(new Error(_('Campaign Name must be set')));
}
if (campaign.type === 2 && (!campaign.sourceUrl || !isUrl(campaign.sourceUrl))) {
return callback(new Error('RSS URL must be set and needs to be a valid URL'));
return callback(new Error(_('RSS URL must be set and needs to be a valid URL')));
}
let getList = (listId, callback) => {
@ -726,7 +727,7 @@ module.exports.create = (campaign, opts, callback) => {
return callback(err);
}
if (!template) {
return callback(new Error('Selected template not found'));
return callback(new Error(_('Selected template not found')));
}
campaign.editorName = template.editorName;
@ -748,7 +749,7 @@ module.exports.update = (id, updates, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Campaign ID'));
return callback(new Error(_('Missing Campaign ID')));
}
let campaign = tools.convertKeys(updates);
@ -757,7 +758,7 @@ module.exports.update = (id, updates, callback) => {
campaign.trackingDisabled = campaign.trackingDisabled ? 1 : 0;
if (!name) {
return callback(new Error('Campaign Name must be set'));
return callback(new Error(_('Campaign Name must be set')));
}
if (/^\d+:\d+$/.test(campaign.list)) {
@ -877,7 +878,7 @@ module.exports.delete = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Campaign ID'));
return callback(new Error(_('Missing Campaign ID')));
}
db.getConnection((err, connection) => {
@ -1078,7 +1079,7 @@ module.exports.getMail = (campaignId, listId, subscriptionId, callback) => {
subscriptionId = Number(subscriptionId) || 0;
if (campaignId < 1 || listId < 1 || subscriptionId < 1) {
return callback(new Error('Invalid or missing message ID'));
return callback(new Error(_('Invalid or missing message ID')));
}
db.getConnection((err, connection) => {

View file

@ -6,26 +6,28 @@ let slugify = require('slugify');
let lists = require('./lists');
let shortid = require('shortid');
let Handlebars = require('handlebars');
let _ = require('../translate')._;
let util = require('util');
let allowedKeys = ['name', 'key', 'default_value', 'group', 'group_template', 'visible'];
let allowedTypes;
module.exports.grouped = ['radio', 'checkbox', 'dropdown'];
module.exports.types = {
text: 'Text',
website: 'Website',
longtext: 'Multi-line text',
gpg: 'GPG Public Key',
number: 'Number',
radio: 'Radio Buttons',
checkbox: 'Checkboxes',
dropdown: 'Drop Down',
'date-us': 'Date (MM/DD/YYY)',
'date-eur': 'Date (DD/MM/YYYY)',
'birthday-us': 'Birthday (MM/DD)',
'birthday-eur': 'Birthday (DD/MM)',
json: 'JSON value for custom rendering',
option: 'Option'
text: _('Text'),
website: _('Website'),
longtext: _('Multi-line text'),
gpg: _('GPG Public Key'),
number: _('Number'),
radio: _('Radio Buttons'),
checkbox: _('Checkboxes'),
dropdown: _('Drop Down'),
'date-us': _('Date (MM/DD/YYY)'),
'date-eur': _('Date (DD/MM/YYYY)'),
'birthday-us': _('Birthday (MM/DD)'),
'birthday-eur': _('Birthday (DD/MM)'),
json: _('JSON value for custom rendering'),
option: _('Option')
};
module.exports.allowedTypes = allowedTypes = Object.keys(module.exports.types);
@ -48,7 +50,7 @@ module.exports.list = (listId, callback) => {
listId = Number(listId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
db.getConnection((err, connection) => {
@ -93,7 +95,7 @@ module.exports.get = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
db.getConnection((err, connection) => {
@ -118,13 +120,13 @@ module.exports.create = (listId, field, callback) => {
listId = Number(listId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
field = tools.convertKeys(field);
if (field.type === 'option' && !field.group) {
return callback(new Error('Option field requires a group to be selected'));
return callback(new Error(_('Option field requires a group to be selected')));
}
if (field.type !== 'option') {
@ -144,11 +146,11 @@ module.exports.update = (id, updates, callback) => {
updates = tools.convertKeys(updates);
if (id < 1) {
return callback(new Error('Missing Field ID'));
return callback(new Error(_('Missing Field ID')));
}
if (!(updates.name || '').toString().trim()) {
return callback(new Error('Field Name must be set'));
return callback(new Error(_('Field Name must be set')));
}
if (updates.key) {
@ -194,7 +196,7 @@ module.exports.delete = (fieldId, callback) => {
fieldId = Number(fieldId) || 0;
if (fieldId < 1) {
return callback(new Error('Missing Field ID'));
return callback(new Error(_('Missing Field ID')));
}
db.getConnection((err, connection) => {
@ -211,7 +213,7 @@ module.exports.delete = (fieldId, callback) => {
if (!rows || !rows.length) {
connection.release();
return callback(new Error('Custom field not found'));
return callback(new Error(_('Custom field not found')));
}
let field = tools.convertKeys(rows[0]);
@ -284,15 +286,15 @@ function addCustomField(listId, name, defaultValue, type, group, groupTemplate,
let key = slugify('merge ' + name, '_').toUpperCase();
if (allowedTypes.indexOf(type) < 0) {
return callback(new Error('Unknown column type ' + type));
return callback(new Error(util.format(_('Unknown column type %s'), type)));
}
if (!name) {
return callback(new Error('Missing column name'));
return callback(new Error(_('Missing column name')));
}
if (listId <= 0) {
return callback(new Error('Missing list ID'));
return callback(new Error(_('Missing list ID')));
}
lists.get(listId, (err, list) => {
@ -300,7 +302,7 @@ function addCustomField(listId, name, defaultValue, type, group, groupTemplate,
return callback(err);
}
if (!list) {
return callback('Provided List ID not found');
return callback(_('Provided List ID not found'));
}
db.getConnection((err, connection) => {

View file

@ -3,6 +3,7 @@
let db = require('../db');
let shortid = require('shortid');
let util = require('util');
let _ = require('../translate')._;
let geoip = require('geoip-ultralight');
let campaigns = require('./campaigns');
@ -324,7 +325,7 @@ function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) {
return callback(err);
}
if (!campaign) {
return callback(new Error('Campaign not found'));
return callback(new Error(_('Campaign not found')));
}
lists.getByCid(listCid, (err, list) => {
@ -332,7 +333,7 @@ function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) {
return callback(err);
}
if (!list) {
return callback(new Error('Campaign not found'));
return callback(new Error(_('List not found')));
}
subscriptions.get(list.id, subscriptionCid, (err, subscription) => {
@ -340,7 +341,7 @@ function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) {
return callback(err);
}
if (!subscription) {
return callback(new Error('Subscription not found'));
return callback(new Error(_('Subscription not found')));
}
return callback(null, {

View file

@ -4,6 +4,7 @@ let db = require('../db');
let tools = require('../tools');
let shortid = require('shortid');
let segments = require('./segments');
let _ = require('../translate')._;
let allowedKeys = ['description'];
@ -77,7 +78,7 @@ module.exports.get = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
db.getConnection((err, connection) => {
@ -113,7 +114,7 @@ module.exports.create = (list, callback) => {
let name = (data.name || '').toString().trim();
if (!data) {
return callback(new Error('List Name must be set'));
return callback(new Error(_('List Name must be set')));
}
let keys = ['name'];
@ -171,11 +172,11 @@ module.exports.update = (id, updates, callback) => {
let values = [name];
if (id < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (!name) {
return callback(new Error('List Name must be set'));
return callback(new Error(_('List Name must be set')));
}
Object.keys(updates).forEach(key => {
@ -208,7 +209,7 @@ module.exports.delete = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
db.getConnection((err, connection) => {
@ -237,7 +238,7 @@ module.exports.delete = (id, callback) => {
function resolveCid(cid, callback) {
cid = (cid || '').toString().trim();
if (!cid) {
return callback(new Error('Missing List CID'));
return callback(new Error(_('Missing List CID')));
}
db.getConnection((err, connection) => {

View file

@ -3,34 +3,36 @@
let tools = require('../tools');
let db = require('../db');
let fields = require('./fields');
let util = require('util');
let _ = require('../translate')._;
module.exports.defaultColumns = [{
column: 'email',
name: 'Email address',
name: _('Email address'),
type: 'string'
}, {
column: 'opt_in_country',
name: 'Signup country',
name: _('Signup country'),
type: 'string'
}, {
column: 'created',
name: 'Sign up date',
name: _('Sign up date'),
type: 'date'
}, {
column: 'latest_open',
name: 'Latest open',
name: _('Latest open'),
type: 'date'
}, {
column: 'latest_click',
name: 'Latest click',
name: _('Latest click'),
type: 'date'
}, {
column: 'first_name',
name: 'First name',
name: _('First name'),
type: 'string'
}, {
column: 'last_name',
name: 'Last name',
name: _('Last name'),
type: 'string'
}];
@ -38,7 +40,7 @@ module.exports.list = (listId, callback) => {
listId = Number(listId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
@ -64,7 +66,7 @@ module.exports.get = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Segment ID'));
return callback(new Error(_('Missing Segment ID')));
}
db.getConnection((err, connection) => {
@ -80,7 +82,7 @@ module.exports.get = (id, callback) => {
}
if (!rows || !rows.length) {
connection.release();
return callback(new Error('Segment not found'));
return callback(new Error(_('Segment not found')));
}
let segment = tools.convertKeys(rows[0]);
@ -141,7 +143,9 @@ module.exports.get = (id, callback) => {
case 'date':
case 'birthday':
if (rule.value.relativeRange) {
rule.formatted = (rule.value.start ? rule.value.start + ' days ' + (rule.value.startDirection ? 'after' : 'before') + ' today' : 'today') + ' … ' + (rule.value.end ? rule.value.end + ' days ' + (rule.value.endDirection ? 'after' : 'before') + ' today' : 'today');
let startString = rule.value.startDirection ? util.format(_('%s days after today'), rule.value.start) : util.format(_('%s days before today'), rule.value.start);
let endString = rule.value.endDirection ? util.format(_('%s days after today'), rule.value.end) : util.format(_('%s days before today'), rule.value.end);
rule.formatted = (rule.value.start ? startString : _('today')) + ' … ' + (rule.value.end ? endString : _('today'));
} else if (rule.value.range) {
rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || '');
} else {
@ -149,7 +153,7 @@ module.exports.get = (id, callback) => {
}
break;
case 'boolean':
rule.formatted = rule.value.value ? 'Selected' : 'Not selected';
rule.formatted = rule.value.value ? _('Selected') : _('Not selected');
break;
default:
rule.formatted = rule.value.value || '';
@ -169,7 +173,7 @@ module.exports.create = (listId, segment, callback) => {
listId = Number(listId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
segment = tools.convertKeys(segment);
@ -178,11 +182,11 @@ module.exports.create = (listId, segment, callback) => {
segment.type = Number(segment.type) || 0;
if (!segment.name) {
return callback(new Error('Field Name must be set'));
return callback(new Error(_('Field Name must be set')));
}
if (segment.type <= 0) {
return callback(new Error('Invalid segment rule type'));
return callback(new Error(_('Invalid segment rule type')));
}
let keys = ['list', 'name', 'type'];
@ -209,7 +213,7 @@ module.exports.update = (id, updates, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Segment ID'));
return callback(new Error(_('Missing Segment ID')));
}
let segment = tools.convertKeys(updates);
@ -218,11 +222,11 @@ module.exports.update = (id, updates, callback) => {
segment.type = Number(segment.type) || 0;
if (!segment.name) {
return callback(new Error('Field Name must be set'));
return callback(new Error(_('Field Name must be set')));
}
if (segment.type <= 0) {
return callback(new Error('Invalid segment rule type'));
return callback(new Error(_('Invalid segment rule type')));
}
let keys = ['name', 'type'];
@ -249,7 +253,7 @@ module.exports.delete = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Segment ID'));
return callback(new Error(_('Missing Segment ID')));
}
db.getConnection((err, connection) => {
@ -271,7 +275,7 @@ module.exports.createRule = (segmentId, rule, callback) => {
segmentId = Number(segmentId) || 0;
if (segmentId < 1) {
return callback(new Error('Missing Segment ID'));
return callback(new Error(_('Missing Segment ID')));
}
rule = tools.convertKeys(rule);
@ -282,12 +286,12 @@ module.exports.createRule = (segmentId, rule, callback) => {
}
if (!segment) {
return callback(new Error('Selected segment not found'));
return callback(new Error(_('Selected segment not found')));
}
let column = segment.columns.filter(column => column.column === rule.column).pop();
if (!column) {
return callback(new Error('Invalid rule type'));
return callback(new Error(_('Invalid rule type')));
}
let value;
@ -351,7 +355,7 @@ module.exports.getRule = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Rule ID'));
return callback(new Error(_('Missing Rule ID')));
}
db.getConnection((err, connection) => {
@ -367,7 +371,7 @@ module.exports.getRule = (id, callback) => {
}
if (!rows || !rows.length) {
return callback(new Error('Specified rule not found'));
return callback(new Error(_('Specified rule not found')));
}
let rule = tools.convertKeys(rows[0]);
@ -378,7 +382,7 @@ module.exports.getRule = (id, callback) => {
}
if (!segment) {
return callback(new Error('Specified segment not found'));
return callback(new Error(_('Specified segment not found')));
}
if (rule.value) {
@ -400,7 +404,10 @@ module.exports.getRule = (id, callback) => {
case 'date':
case 'birthday':
if (rule.value.relativeRange) {
rule.formatted = (rule.value.start ? rule.value.start + ' days ' + (rule.value.startDirection ? 'after' : 'before') + ' today' : 'today') + ' … ' + (rule.value.end ? rule.value.end + ' days ' + (rule.value.endDirection ? 'after' : 'before') + ' today' : 'today');
let startString = rule.value.startDirection ? util.format(_('%s days after today'), rule.value.start) : util.format(_('%s days before today'), rule.value.start);
let endString = rule.value.endDirection ? util.format(_('%s days after today'), rule.value.end) : util.format(_('%s days before today'), rule.value.end);
rule.formatted = (rule.value.start ? startString : _('today')) + ' … ' + (rule.value.end ? endString : _('today'));
} else if (rule.value.range) {
rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || '');
} else {
@ -408,7 +415,7 @@ module.exports.getRule = (id, callback) => {
}
break;
case 'boolean':
rule.formatted = rule.value.value ? 'Selected' : 'Not selected';
rule.formatted = rule.value.value ? _('Selected') : _('Not selected');
break;
default:
rule.formatted = rule.value.value || '';
@ -424,7 +431,7 @@ module.exports.updateRule = (id, rule, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Rule ID'));
return callback(new Error(_('Missing Rule ID')));
}
rule = tools.convertKeys(rule);
@ -435,7 +442,7 @@ module.exports.updateRule = (id, rule, callback) => {
}
if (!existingRule) {
return callback(new Error('Selected rule not found'));
return callback(new Error(_('Selected rule not found')));
}
module.exports.get(existingRule.segment, (err, segment) => {
@ -444,12 +451,12 @@ module.exports.updateRule = (id, rule, callback) => {
}
if (!segment) {
return callback(new Error('Selected segment not found'));
return callback(new Error(_('Selected segment not found')));
}
let column = segment.columns.filter(column => column.column === existingRule.column).pop();
if (!column) {
return callback(new Error('Invalid rule type'));
return callback(new Error(_('Invalid rule type')));
}
let value;
@ -514,7 +521,7 @@ module.exports.deleteRule = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Rule ID'));
return callback(new Error(_('Missing Rule ID')));
}
db.getConnection((err, connection) => {
@ -539,7 +546,7 @@ module.exports.getQuery = (id, prefix, callback) => {
}
if (!segment) {
return callback(new Error('Segment not found'));
return callback(new Error(_('Segment not found')));
}
prefix = prefix ? prefix + '.' : '';
@ -648,7 +655,7 @@ module.exports.subscribers = (id, onlySubscribed, callback) => {
return callback(err);
}
if (!segment) {
return callback(new Error('Segment not found'));
return callback(new Error(_('Segment not found')));
}
module.exports.getQuery(id, false, (err, queryData) => {
if (err) {

View file

@ -10,6 +10,8 @@ let settings = require('./settings');
let mailer = require('../mailer');
let urllib = require('url');
let log = require('npmlog');
let _ = require('../translate')._;
let util = require('util');
module.exports.list = (listId, start, limit, callback) => {
listId = Number(listId) || 0;
@ -83,7 +85,7 @@ module.exports.filter = (listId, request, columns, segmentId, callback) => {
segmentId = Number(segmentId) || 0;
if (!listId) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
let processQuery = queryData => {
@ -228,7 +230,7 @@ module.exports.addConfirmation = (list, email, optInIp, data, callback) => {
name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '),
address: email
},
subject: list.name + ': Please Confirm Subscription',
subject: util.format(_('%s: Please Confirm Subscription'),list.name),
encryptionKeys
}, {
html: 'emails/confirm-html.hbs',
@ -319,7 +321,7 @@ module.exports.subscribe = (cid, optInIp, callback) => {
}
if (!result.entryId) {
return callback(new Error('Could not save subscription'));
return callback(new Error(_('Could not save subscription')));
}
db.getConnection((err, connection) => {
@ -502,7 +504,7 @@ module.exports.get = (listId, cid, callback) => {
cid = (cid || '').toString().trim();
if (!cid) {
return callback(new Error('Missing Subbscription ID'));
return callback(new Error(_('Missing Subbscription ID')));
}
db.getConnection((err, connection) => {
@ -532,7 +534,7 @@ module.exports.getById = (listId, id, callback) => {
id = Number(id) || 0;
if (!id) {
return callback(new Error('Missing Subbscription ID'));
return callback(new Error(_('Missing Subbscription ID')));
}
db.getConnection((err, connection) => {
@ -560,7 +562,7 @@ module.exports.getById = (listId, id, callback) => {
module.exports.getByEmail = (listId, email, callback) => {
if (!email) {
return callback(new Error('Missing Subbscription email address'));
return callback(new Error(_('Missing Subbscription email address')));
}
db.getConnection((err, connection) => {
@ -635,11 +637,11 @@ module.exports.update = (listId, cid, updates, allowEmail, callback) => {
let values = [];
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (!cid) {
return callback(new Error('Missing subscription ID'));
return callback(new Error(_('Missing subscription ID')));
}
fields.list(listId, (err, fieldList) => {
@ -698,11 +700,11 @@ module.exports.unsubscribe = (listId, email, campaignId, callback) => {
campaignId = (campaignId || '').toString().trim() || false;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (!email) {
return callback(new Error('Missing email address'));
return callback(new Error(_('Missing email address')));
}
db.getConnection((err, connection) => {
@ -884,11 +886,11 @@ module.exports.delete = (listId, cid, callback) => {
cid = (cid || '').toString().trim();
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (!cid) {
return callback(new Error('Missing subscription ID'));
return callback(new Error(_('Missing subscription ID')));
}
db.getConnection((err, connection) => {
@ -987,11 +989,11 @@ module.exports.updateImport = (listId, importId, data, callback) => {
importId = Number(importId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (importId < 1) {
return callback(new Error('Missing Import ID'));
return callback(new Error(_('Missing Import ID')));
}
let keys = [];
@ -1041,11 +1043,11 @@ module.exports.getImport = (listId, importId, callback) => {
importId = Number(importId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (importId < 1) {
return callback(new Error('Missing Import ID'));
return callback(new Error(_('Missing Import ID')));
}
db.getConnection((err, connection) => {
@ -1081,7 +1083,7 @@ module.exports.getFailedImports = (importId, callback) => {
importId = Number(importId) || 0;
if (importId < 1) {
return callback(new Error('Missing Import ID'));
return callback(new Error(_('Missing Import ID')));
}
db.getConnection((err, connection) => {
@ -1104,7 +1106,7 @@ module.exports.listImports = (listId, callback) => {
listId = Number(listId) || 0;
if (listId < 1) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
db.getConnection((err, connection) => {
@ -1147,11 +1149,11 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
let emailNew = (updates.emailNew || '').toString().trim();
if (!list || !list.id) {
return callback(new Error('Missing List ID'));
return callback(new Error(_('Missing List ID')));
}
if (!cid) {
return callback(new Error('Missing subscription ID'));
return callback(new Error(_('Missing subscription ID')));
}
tools.validateEmail(emailNew, false, err => {
@ -1173,12 +1175,12 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
}
if (!rows || !rows.length) {
connection.release();
return callback(new Error('Unknown subscription ID'));
return callback(new Error(_('Unknown subscription ID')));
}
if (rows[0].email === emailNew) {
connection.release();
return callback(new Error('Nothing seems to be changed'));
return callback(new Error(_('Nothing seems to be changed')));
}
let old = rows[0];
@ -1192,7 +1194,7 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
}
if (rows && rows[0] && rows[0].id) {
return callback(new Error('This address is already registered by someone else'));
return callback(new Error(_('This address is already registered by someone else')));
}
module.exports.addConfirmation(list, emailNew, optInIp, {

View file

@ -2,6 +2,7 @@
let db = require('../db');
let tools = require('../tools');
let _ = require('../translate')._;
let allowedKeys = ['description', 'editor_name', 'editor_data', 'html', 'text'];
@ -47,7 +48,7 @@ module.exports.get = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Template ID'));
return callback(new Error(_('Missing Template ID')));
}
db.getConnection((err, connection) => {
@ -76,7 +77,7 @@ module.exports.create = (template, callback) => {
let data = tools.convertKeys(template);
if (!(data.name || '').toString().trim()) {
return callback(new Error('Template Name must be set'));
return callback(new Error(_('Template Name must be set')));
}
let name = (template.name || '').toString().trim();
@ -118,11 +119,11 @@ module.exports.update = (id, updates, callback) => {
let data = tools.convertKeys(updates);
if (id < 1) {
return callback(new Error('Missing Template ID'));
return callback(new Error(_('Missing Template ID')));
}
if (!(data.name || '').toString().trim()) {
return callback(new Error('Template Name must be set'));
return callback(new Error(_('Template Name must be set')));
}
let name = (updates.name || '').toString().trim();
@ -159,7 +160,7 @@ module.exports.delete = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Template ID'));
return callback(new Error(_('Missing Template ID')));
}
db.getConnection((err, connection) => {

View file

@ -4,36 +4,37 @@ let tools = require('../tools');
let db = require('../db');
let lists = require('./lists');
let util = require('util');
let _ = require('../translate')._;
module.exports.defaultColumns = [{
column: 'created',
name: 'Sign up date',
name: _('Sign up date'),
type: 'date'
}, {
column: 'latest_open',
name: 'Latest open',
name: _('Latest open'),
type: 'date'
}, {
column: 'latest_click',
name: 'Latest click',
name: _('Latest click'),
type: 'date'
}];
module.exports.defaultCampaignEvents = [{
option: 'delivered',
name: 'Delivered'
name: _('Delivered')
}, {
option: 'opened',
name: 'Has Opened'
name: _('Has Opened')
}, {
option: 'clicked',
name: 'Has Clicked'
name: _('Has Clicked')
}, {
option: 'not_opened',
name: 'Not Opened'
name: _('Not Opened')
}, {
option: 'not_clicked',
name: 'Not Clicked'
name: _('Not Clicked')
}];
let defaultColumnMap = {};
@ -170,36 +171,36 @@ module.exports.create = (trigger, callback) => {
let column;
if (!listId) {
return callback(new Error('Missing or invalid list ID'));
return callback(new Error(_('Missing or invalid list ID')));
}
if (seconds < 0) {
return callback(new Error('Days in the past are not allowed'));
return callback(new Error(_('Days in the past are not allowed')));
}
if (!rule || ['campaign', 'subscription'].indexOf(rule) < 0) {
return callback(new Error('Missing or invalid trigger rule'));
return callback(new Error(_('Missing or invalid trigger rule')));
}
switch (rule) {
case 'subscription':
column = (trigger.column || '').toString().toLowerCase().trim();
if (!column) {
return callback(new Error('Invalid subscription configuration'));
return callback(new Error(_('Invalid subscription configuration')));
}
break;
case 'campaign':
column = (trigger.campaignOption || '').toString().toLowerCase().trim();
sourceCampaign = Number(trigger.sourceCampaign) || 0;
if (!column || !sourceCampaign) {
return callback(new Error('Invalid campaign configuration'));
return callback(new Error(_('Invalid campaign configuration')));
}
if (sourceCampaign === destCampaign) {
return callback(new Error('A campaing can not be a target for itself'));
return callback(new Error(_('A campaing can not be a target for itself')));
}
break;
default:
return callback(new Error('Missing or invalid trigger rule'));
return callback(new Error(_('Missing or invalid trigger rule')));
}
lists.get(listId, (err, list) => {
@ -207,7 +208,7 @@ module.exports.create = (trigger, callback) => {
return callback(err);
}
if (!list) {
return callback(new Error('Missing or invalid list ID'));
return callback(new Error(_('Missing or invalid list ID')));
}
db.getConnection((err, connection) => {
@ -228,7 +229,7 @@ module.exports.create = (trigger, callback) => {
let id = result && result.insertId;
if (!id) {
return callback(new Error('Could not store trigger row'));
return callback(new Error(_('Could not store trigger row')));
}
createTriggerTable(id, err => {
@ -245,7 +246,7 @@ module.exports.create = (trigger, callback) => {
module.exports.update = (id, trigger, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing or invalid Trigger ID'));
return callback(new Error(_('Missing or invalid Trigger ID')));
}
trigger = tools.convertKeys(trigger);
@ -259,32 +260,32 @@ module.exports.update = (id, trigger, callback) => {
let column;
if (seconds < 0) {
return callback(new Error('Days in the past are not allowed'));
return callback(new Error(_('Days in the past are not allowed')));
}
if (!rule || ['campaign', 'subscription'].indexOf(rule) < 0) {
return callback(new Error('Missing or invalid trigger rule'));
return callback(new Error(_('Missing or invalid trigger rule')));
}
switch (rule) {
case 'subscription':
column = (trigger.column || '').toString().toLowerCase().trim();
if (!column) {
return callback(new Error('Invalid subscription configuration'));
return callback(new Error(_('Invalid subscription configuration')));
}
break;
case 'campaign':
column = (trigger.campaignOption || '').toString().toLowerCase().trim();
sourceCampaign = Number(trigger.sourceCampaign) || 0;
if (!column || !sourceCampaign) {
return callback(new Error('Invalid campaign configuration'));
return callback(new Error(_('Invalid campaign configuration')));
}
if (sourceCampaign === destCampaign) {
return callback(new Error('A campaing can not be a target for itself'));
return callback(new Error(_('A campaing can not be a target for itself')));
}
break;
default:
return callback(new Error('Missing or invalid trigger rule'));
return callback(new Error(_('Missing or invalid trigger rule')));
}
db.getConnection((err, connection) => {
@ -312,7 +313,7 @@ module.exports.delete = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
return callback(new Error('Missing Trigger ID'));
return callback(new Error(_('Missing Trigger ID')));
}
db.getConnection((err, connection) => {

View file

@ -9,6 +9,7 @@ let mailer = require('../mailer');
let settings = require('./settings');
let crypto = require('crypto');
let urllib = require('url');
let _ = require('../translate')._;
/**
* Fetches user by ID value
@ -99,7 +100,7 @@ module.exports.add = (username, password, email, callback) => {
let id = result && result.insertId;
if (!id) {
return callback(new Error('Could not store user row'));
return callback(new Error(_('Could not store user row')));
}
return callback(null, id);
@ -169,7 +170,7 @@ module.exports.authenticate = (username, password, callback) => {
module.exports.update = (id, updates, callback) => {
if (!updates.email) {
return callback(new Error('Email Address must be set'));
return callback(new Error(_('Email Address must be set')));
}
let update = (connection, callback) => {
@ -180,7 +181,7 @@ module.exports.update = (id, updates, callback) => {
}
if (!rows.length) {
return callback('Failed to check user data');
return callback(_('Failed to check user data'));
}
let keys = ['email'];
@ -191,7 +192,7 @@ module.exports.update = (id, updates, callback) => {
connection.query('UPDATE users SET ' + keys.map(key => key + '=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
if (err) {
if (err.code === 'ER_DUP_ENTRY') {
err = new Error('Can\'t change email as another user with the same email address already exists');
err = new Error(_('Can\'t change email as another user with the same email address already exists'));
}
return callback(err);
}
@ -208,15 +209,15 @@ module.exports.update = (id, updates, callback) => {
return callback(err);
}
if (!result) {
return callback('Incorrect current password');
return callback(_('Incorrect current password'));
}
if (!updates.password) {
return callback(new Error('New password not set'));
return callback(new Error(_('New password not set')));
}
if (updates.password !== updates.password2) {
return callback(new Error('Passwords do not match'));
return callback(new Error(_('Passwords do not match')));
}
bcrypt.hash(updates.password, null, null, (err, hash) => {
@ -254,7 +255,7 @@ module.exports.resetToken = (id, callback) => {
id = Number(id) || 0;
if (!id) {
return callback(new Error('User ID not set'));
return callback(new Error(_('User ID not set')));
}
db.getConnection((err, connection) => {
@ -282,7 +283,7 @@ module.exports.sendReset = (username, callback) => {
username = (username || '').toString().trim();
if (!username) {
return callback(new Error('Username must be set'));
return callback(new Error(_('Username must be set')));
}
db.getConnection((err, connection) => {
@ -319,7 +320,7 @@ module.exports.sendReset = (username, callback) => {
to: {
address: rows[0].email
},
subject: 'Mailer password change request'
subject: _('Mailer password change request')
}, {
html: 'emails/password-reset-html.hbs',
text: 'emails/password-reset-text.hbs',
@ -343,7 +344,7 @@ module.exports.sendReset = (username, callback) => {
module.exports.checkResetToken = (username, resetToken, callback) => {
if (!username || !resetToken) {
return callback(new Error('Missing username or reset token'));
return callback(new Error(_('Missing username or reset token')));
}
db.getConnection((err, connection) => {
if (err) {
@ -363,11 +364,11 @@ module.exports.resetPassword = (data, callback) => {
let updates = tools.convertKeys(data);
if (!updates.username || !updates.resetToken) {
return callback(new Error('Missing username or reset token'));
return callback(new Error(_('Missing username or reset token')));
}
if (!updates.password || !updates.password2 || updates.password !== updates.password2) {
return callback(new Error('Invalid new password'));
return callback(new Error(_('Invalid new password')));
}
bcrypt.hash(updates.password, null, null, (err, hash) => {

View file

@ -2,6 +2,8 @@
let config = require('config');
let log = require('npmlog');
let _ = require('./translate')._;
let util = require('util');
let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;
@ -33,7 +35,7 @@ module.exports.setup = app => {
module.exports.logout = (req, res) => {
if (req.user) {
req.flash('info', req.user.username + ' logged out');
req.flash('info', util.format(_('%s logged out'), req.user.username));
req.logout();
}
res.redirect('/');
@ -46,7 +48,7 @@ module.exports.login = (req, res, next) => {
return next(err);
}
if (!user) {
req.flash('danger', info && info.message || 'Failed to authenticate user');
req.flash('danger', info && info.message || _('Failed to authenticate user'));
return res.redirect('/users/login' + (req.body.next ? '?next=' + encodeURIComponent(req.body.next) : ''));
}
req.logIn(user, err => {
@ -62,7 +64,7 @@ module.exports.login = (req, res, next) => {
req.session.cookie.expires = false;
}
req.flash('success', 'Logged in as ' + user.username);
req.flash('success', util.format(_('Logged in as %s'), user.username));
return res.redirect(req.body.next || '/');
});
})(req, res, next);
@ -120,7 +122,7 @@ if (config.ldap.enabled && LdapStrategy) {
if (!user) {
return done(null, false, {
message: 'Incorrect username or password'
message: _('Incorrect username or password')
});
}

View file

@ -6,6 +6,8 @@ let Isemail = require('isemail');
let urllib = require('url');
let juice = require('juice');
let jsdom = require('jsdom');
let _ = require('./translate')._;
let util = require('util');
let blockedUsers = ['abuse', 'admin', 'billing', 'compliance', 'devnull', 'dns', 'ftp', 'hostmaster', 'inoc', 'ispfeedback', 'ispsupport', 'listrequest', 'list', 'maildaemon', 'noc', 'noreply', 'noreply', 'null', 'phish', 'phishing', 'postmaster', 'privacy', 'registrar', 'root', 'security', 'spam', 'support', 'sysadmin', 'tech', 'undisclosedrecipients', 'unsubscribe', 'usenet', 'uucp', 'webmaster', 'www'];
@ -106,19 +108,19 @@ function updateMenu(res) {
}
res.locals.menu.push({
title: 'Lists',
title: _('Lists'),
url: '/lists',
key: 'lists'
}, {
title: 'Templates',
title: _('Templates'),
url: '/templates',
key: 'templates'
}, {
title: 'Campaigns',
title: _('Campaigns'),
url: '/campaigns',
key: 'campaigns'
}, {
title: 'Automation',
title: _('Automation'),
url: '/triggers',
key: 'triggers'
});
@ -128,7 +130,7 @@ function validateEmail(address, checkBlocked, callback) {
let user = (address || '').toString().split('@').shift().toLowerCase().replace(/[^a-z0-9]/g, '');
if (checkBlocked && blockedUsers.indexOf(user) >= 0) {
return callback(new Error('Blocked email address "' + address + '"'));
return callback(new Error(util.format(_('Blocked email address "%s"'), address)));
}
Isemail.validate(address, {
@ -137,16 +139,16 @@ function validateEmail(address, checkBlocked, callback) {
}, result => {
if (result !== 0) {
let message = 'Invalid email address "' + address + '"';
let message = util.format(_('Invalid email address "%s".'), address);
switch (result) {
case 5:
message += '. MX record not found for domain';
message += ' ' + _('MX record not found for domain');
break;
case 6:
message += '. Address domain not found';
message += ' ' + _('Address domain not found');
break;
case 12:
message += '. Address domain name is required';
message += ' ' + _('Address domain name is required');
break;
}
return callback(new Error(message));

View file

@ -8,6 +8,7 @@ const fs = require('fs');
const path = require('path');
const log = require('npmlog');
const gettextParser = require('gettext-parser');
const fakelang = require('./fakelang');
const language = config.language || 'en';
@ -31,5 +32,10 @@ module.exports._ = str => {
if (typeof str !== 'string') {
str = String(str);
}
if (language === 'zz') {
return fakelang(str);
}
return gt.dgettext(language, str);
};

View file

@ -26,6 +26,7 @@
"node": ">=5.0.0"
},
"devDependencies": {
"eslint-config-nodemailer": "^1.0.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0",
@ -33,9 +34,9 @@
"jsxgettext-andris": "^0.9.0-patch.1"
},
"dependencies": {
"aws-sdk": "^2.22.0",
"aws-sdk": "^2.23.0",
"bcrypt-nodejs": "0.0.3",
"body-parser": "^1.17.0",
"body-parser": "^1.17.1",
"bounce-handler": "^7.3.2-fork.2",
"compression": "^1.6.2",
"config": "^1.25.1",
@ -46,7 +47,7 @@
"csv-generate": "^1.0.0",
"csv-parse": "^1.2.0",
"escape-html": "^1.0.3",
"express": "^4.15.0",
"express": "^4.15.2",
"express-session": "^1.15.1",
"faker": "^4.1.0",
"feedparser": "^2.1.0",
@ -71,14 +72,14 @@
"nodemailer": "^3.1.4",
"nodemailer-openpgp": "^1.0.2",
"npmlog": "^4.0.2",
"openpgp": "^2.3.8",
"openpgp": "^2.4.0",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"redfour": "^1.0.0",
"redis": "^2.6.5",
"request": "^2.80.0",
"serve-favicon": "^2.4.1",
"shortid": "^2.2.6",
"shortid": "^2.2.8",
"slugify": "^1.1.0",
"smtp-server": "^2.0.2",
"striptags": "^3.0.1",

View file

@ -11,6 +11,8 @@ let request = require('request');
let router = new express.Router();
let passport = require('../lib/passport');
let marked = require('marked');
let _ = require('../lib/translate')._;
let util = require('util');
router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, next) => {
settings.get('serviceUrl', (err, serviceUrl) => {
@ -26,7 +28,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res,
}
if (!campaign) {
err = new Error('Not Found');
err = new Error(_('Not Found'));
err.status = 404;
return next(err);
}
@ -38,7 +40,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res,
}
if (!list) {
err = new Error('Not Found');
err = new Error(_('Not Found'));
err.status = 404;
return next(err);
}
@ -50,7 +52,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res,
}
if (!subscription) {
err = new Error('Not Found');
err = new Error(_('Not Found'));
err.status = 404;
return next(err);
}
@ -105,7 +107,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res,
return next(err);
}
if (httpResponse.statusCode !== 200) {
return next(new Error('Received status code ' + httpResponse.statusCode + ' from ' + campaign.sourceUrl));
return next(new Error(util.format(_('Received status code %s from %s'), httpResponse.statusCode, campaign.sourceUrl)));
}
renderAndShow(body && body.toString(), false);
});
@ -129,7 +131,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection,
let url = '/archive/' + encodeURIComponent(req.body.campaign || '') + '/' + encodeURIComponent(req.body.list || '') + '/' + encodeURIComponent(req.body.subscription || '');
campaigns.getByCid(req.body.campaign, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect(url);
}
campaigns.getAttachment(campaign.id, Number(req.body.attachment), (err, attachment) => {
@ -137,7 +139,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection,
req.flash('danger', err && err.message || err);
return res.redirect(url);
} else if (!attachment) {
req.flash('warning', 'Attachment not found');
req.flash('warning', _('Attachment not found'));
return res.redirect(url);
}

View file

@ -14,6 +14,8 @@ let striptags = require('striptags');
let passport = require('../lib/passport');
let htmlescape = require('escape-html');
let multer = require('multer');
let _ = require('../lib/translate')._;
let util = require('util');
let uploadStorage = multer.memoryStorage();
let uploads = multer({
storage: uploadStorage
@ -21,7 +23,7 @@ let uploads = multer({
router.all('/*', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
res.setSelectedMenu('campaigns');
@ -30,7 +32,7 @@ router.all('/*', (req, res, next) => {
router.get('/', (req, res) => {
res.render('campaigns/campaigns', {
title: 'Campaigns'
title: _('Campaigns')
});
});
@ -112,13 +114,13 @@ router.get('/create', passport.csrfProtection, (req, res) => {
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
campaigns.create(req.body, false, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create campaign');
req.flash('danger', err && err.message || err || _('Could not create campaign'));
return res.redirect('/campaigns/create?' + tools.queryParams(req.body));
}
req.flash('success', 'Campaign “' + req.body.name + '” created');
res.redirect((req.body.type === 'rss')
? '/campaigns/edit/' + id
: '/campaigns/edit/' + id + '?tab=template'
req.flash('success', util.format(_('Campaign “%s” created'), req.body.name));
res.redirect((req.body.type === 'rss') ?
'/campaigns/edit/' + id :
'/campaigns/edit/' + id + '?tab=template'
);
});
});
@ -126,7 +128,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) =
router.get('/edit/:id', passport.csrfProtection, (req, res, next) => {
campaigns.get(req.params.id, false, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
@ -199,7 +201,7 @@ router.get('/edit/:id', passport.csrfProtection, (req, res, next) => {
campaign.mergeTags = defaultMergeTags.concat(listMergeTags);
campaign.type === 2 && campaign.mergeTags.push({
key: 'RSS_ENTRY',
value: 'content from an RSS entry'
value: _('content from an RSS entry')
});
res.render(view, campaign);
});
@ -215,9 +217,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) =>
if (err) {
req.flash('danger', err.message || err);
} else if (updated) {
req.flash('success', 'Campaign settings updated');
req.flash('success', _('Campaign settings updated'));
} else {
req.flash('info', 'Campaign settings not updated');
req.flash('info', _('Campaign settings not updated'));
}
if (req.body.id) {
@ -233,9 +235,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) =
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Campaign deleted');
req.flash('success', _('Campaign deleted'));
} else {
req.flash('info', 'Could not delete specified campaign');
req.flash('info', _('Could not delete specified campaign'));
}
return res.redirect('/campaigns');
@ -254,22 +256,22 @@ router.post('/ajax', (req, res) => {
let getStatusText = data => {
switch (data.status) {
case 1:
return 'Idling';
return _('Idling');
case 2:
if (data.scheduled && data.scheduled > new Date()) {
return 'Scheduled';
return _('Scheduled');
}
return '<span class="glyphicon glyphicon-refresh spinning"></span> Sending…';
return '<span class="glyphicon glyphicon-refresh spinning"></span> ' + _('Sending') + '…';
case 3:
return 'Finished';
return _('Finished');
case 4:
return 'Paused';
return _('Paused');
case 5:
return 'Inactive';
return _('Inactive');
case 6:
return 'Active';
return _('Active');
}
return 'Other';
return _('Other');
};
res.json({
@ -282,7 +284,7 @@ router.post('/ajax', (req, res) => {
htmlescape(striptags(row.description) || ''),
getStatusText(row),
'<span class="datestring" data-date="' + row.created.toISOString() + '" title="' + row.created.toISOString() + '">' + row.created.toISOString() + '</span>'
].concat('<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/campaigns/edit/' + row.id + '">Edit</a>'))
].concat('<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/campaigns/edit/' + row.id + '">' + _('Edit') + '</a>'))
});
});
});
@ -290,7 +292,7 @@ router.post('/ajax', (req, res) => {
router.get('/view/:id', passport.csrfProtection, (req, res) => {
campaigns.get(req.params.id, true, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
@ -385,7 +387,7 @@ router.post('/preview/:id', passport.parseForm, passport.csrfProtection, (req, r
router.get('/opened/:id', passport.csrfProtection, (req, res) => {
campaigns.get(req.params.id, true, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
@ -424,13 +426,13 @@ router.get('/status/:id/:status', passport.csrfProtection, (req, res) => {
status = 4;
break;
default:
req.flash('danger', 'Unknown status selector');
req.flash('danger', _('Unknown status selector'));
return res.redirect('/campaigns');
}
campaigns.get(id, true, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
@ -470,7 +472,7 @@ router.get('/status/:id/:status', passport.csrfProtection, (req, res) => {
router.get('/clicked/:id/:linkId', passport.csrfProtection, (req, res) => {
campaigns.get(req.params.id, true, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
@ -536,7 +538,7 @@ router.post('/clicked/ajax/:id/:linkId', (req, res) => {
campaigns.get(req.params.id, true, (err, campaign) => {
if (err || !campaign) {
return res.json({
error: err && err.message || err || 'Campaign not found',
error: err && err.message || err || _('Campaign not found'),
data: []
});
}
@ -571,7 +573,7 @@ router.post('/clicked/ajax/:id/:linkId', (req, res) => {
htmlescape(row.lastName || ''),
row.created && row.created.toISOString ? '<span class="datestring" data-date="' + row.created.toISOString() + '" title="' + row.created.toISOString() + '">' + row.created.toISOString() + '</span>' : 'N/A',
row.count,
'<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + campaign.list + '/edit/' + row.cid + '">Edit</a>'
'<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + campaign.list + '/edit/' + row.cid + '">' + _('Edit') + '</a>'
])
});
});
@ -585,7 +587,7 @@ router.post('/status/ajax/:id/:status', (req, res) => {
campaigns.get(req.params.id, true, (err, campaign) => {
if (err || !campaign) {
return res.json({
error: err && err.message || err || 'Campaign not found',
error: err && err.message || err || _('Campaign not found'),
data: []
});
}
@ -621,7 +623,7 @@ router.post('/status/ajax/:id/:status', (req, res) => {
htmlescape(row.lastName || ''),
htmlescape(row.response || ''),
row.updated && row.created.toISOString ? '<span class="datestring" data-date="' + row.updated.toISOString() + '" title="' + row.updated.toISOString() + '">' + row.updated.toISOString() + '</span>' : 'N/A',
'<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + campaign.list + '/edit/' + row.cid + '">Edit</a>'
'<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + campaign.list + '/edit/' + row.cid + '">' + _('Edit') + '</a>'
])
});
});
@ -634,9 +636,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) =
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Campaign deleted');
req.flash('success', _('Campaign deleted'));
} else {
req.flash('info', 'Could not delete specified campaign');
req.flash('info', _('Could not delete specified campaign'));
}
return res.redirect('/campaigns');
@ -652,9 +654,9 @@ router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) =>
if (err) {
req.flash('danger', err && err.message || err);
} else if (scheduled) {
req.flash('success', 'Scheduled sending');
req.flash('success', _('Scheduled sending'));
} else {
req.flash('info', 'Could not schedule sending');
req.flash('info', _('Could not schedule sending'));
}
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
@ -666,9 +668,9 @@ router.post('/resume', passport.parseForm, passport.csrfProtection, (req, res) =
if (err) {
req.flash('danger', err && err.message || err);
} else if (scheduled) {
req.flash('success', 'Sending resumed');
req.flash('success', _('Sending resumed'));
} else {
req.flash('info', 'Could not resume sending');
req.flash('info', _('Could not resume sending'));
}
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
@ -680,9 +682,9 @@ router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) =>
if (err) {
req.flash('danger', err && err.message || err);
} else if (reset) {
req.flash('success', 'Sending reset');
req.flash('success', _('Sending reset'));
} else {
req.flash('info', 'Could not reset sending');
req.flash('info', _('Could not reset sending'));
}
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
@ -694,9 +696,9 @@ router.post('/pause', passport.parseForm, passport.csrfProtection, (req, res) =>
if (err) {
req.flash('danger', err && err.message || err);
} else if (reset) {
req.flash('success', 'Sending paused');
req.flash('success', _('Sending paused'));
} else {
req.flash('info', 'Could not pause sending');
req.flash('info', _('Could not pause sending'));
}
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
@ -708,9 +710,9 @@ router.post('/activate', passport.parseForm, passport.csrfProtection, (req, res)
if (err) {
req.flash('danger', err && err.message || err);
} else if (reset) {
req.flash('success', 'Sending activated');
req.flash('success', _('Sending activated'));
} else {
req.flash('info', 'Could not activate sending');
req.flash('info', _('Could not activate sending'));
}
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
@ -722,9 +724,9 @@ router.post('/inactivate', passport.parseForm, passport.csrfProtection, (req, re
if (err) {
req.flash('danger', err && err.message || err);
} else if (reset) {
req.flash('success', 'Sending paused');
req.flash('success', _('Sending paused'));
} else {
req.flash('info', 'Could not pause sending');
req.flash('info', _('Could not pause sending'));
}
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
@ -734,7 +736,7 @@ router.post('/inactivate', passport.parseForm, passport.csrfProtection, (req, re
router.post('/attachment', uploads.single('attachment'), passport.parseForm, passport.csrfProtection, (req, res) => {
campaigns.get(req.body.id, false, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
campaigns.addAttachment(campaign.id, {
@ -745,9 +747,9 @@ router.post('/attachment', uploads.single('attachment'), passport.parseForm, pas
if (err) {
req.flash('danger', err && err.message || err);
} else if (attachmentId) {
req.flash('success', 'Attachment uploaded');
req.flash('success', _('Attachment uploaded'));
} else {
req.flash('info', 'Could not store attachment');
req.flash('info', _('Could not store attachment'));
}
return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments');
});
@ -757,16 +759,16 @@ router.post('/attachment', uploads.single('attachment'), passport.parseForm, pas
router.post('/attachment/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
campaigns.get(req.body.id, false, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
campaigns.deleteAttachment(campaign.id, Number(req.body.attachment), (err, deleted) => {
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Attachment deleted');
req.flash('success', _('Attachment deleted'));
} else {
req.flash('info', 'Could not delete attachment');
req.flash('info', _('Could not delete attachment'));
}
return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments');
});
@ -776,7 +778,7 @@ router.post('/attachment/delete', passport.parseForm, passport.csrfProtection, (
router.post('/attachment/download', passport.parseForm, passport.csrfProtection, (req, res) => {
campaigns.get(req.body.id, false, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
campaigns.getAttachment(campaign.id, Number(req.body.attachment), (err, attachment) => {
@ -784,7 +786,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection,
req.flash('danger', err && err.message || err);
return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments');
} else if (!attachment) {
req.flash('warning', 'Attachment not found');
req.flash('warning', _('Attachment not found'));
return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments');
}
@ -798,7 +800,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection,
router.get('/attachment/:campaign', passport.csrfProtection, (req, res) => {
campaigns.get(req.params.campaign, false, (err, campaign) => {
if (err || !campaign) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
campaign.csrfToken = req.csrfToken();

View file

@ -6,10 +6,11 @@ let lists = require('../lib/models/lists');
let fields = require('../lib/models/fields');
let tools = require('../lib/tools');
let passport = require('../lib/passport');
let _ = require('../lib/translate')._;
router.all('/*', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
res.setSelectedMenu('lists');
@ -24,7 +25,7 @@ router.get('/:list', (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -60,7 +61,7 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -98,7 +99,7 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => {
router.post('/:list/create', passport.parseForm, passport.csrfProtection, (req, res) => {
fields.create(req.params.list, req.body, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create custom field');
req.flash('danger', err && err.message || err || _('Could not create custom field'));
return res.redirect('/fields/' + encodeURIComponent(req.params.list) + '/create?' + tools.queryParams(req.body));
}
req.flash('success', 'Custom field created');
@ -114,7 +115,7 @@ router.get('/:list/edit/:field', passport.csrfProtection, (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -125,7 +126,7 @@ router.get('/:list/edit/:field', passport.csrfProtection, (req, res) => {
}
if (!field) {
req.flash('danger', 'Selected field not found');
req.flash('danger', _('Selected field not found'));
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
}
@ -161,9 +162,9 @@ router.post('/:list/edit', passport.parseForm, passport.csrfProtection, (req, re
if (err) {
req.flash('danger', err.message || err);
} else if (updated) {
req.flash('success', 'Field settings updated');
req.flash('success', _('Field settings updated'));
} else {
req.flash('info', 'Field settings not updated');
req.flash('info', _('Field settings not updated'));
}
if (req.body.id) {
@ -179,9 +180,9 @@ router.post('/:list/delete', passport.parseForm, passport.csrfProtection, (req,
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Custom field deleted');
req.flash('success', _('Custom field deleted'));
} else {
req.flash('info', 'Could not delete specified field');
req.flash('info', _('Could not delete specified field'));
}
return res.redirect('/fields/' + encodeURIComponent(req.params.list));

View file

@ -5,6 +5,7 @@ let settings = require('../lib/models/settings');
let lists = require('../lib/models/lists');
let subscriptions = require('../lib/models/subscriptions');
let tools = require('../lib/tools');
let _ = require('../lib/translate')._;
let log = require('npmlog');
let express = require('express');
@ -36,7 +37,7 @@ router.get('/:campaign/:list/:subscription/:link', (req, res) => {
res.status(404);
return res.render('archive/view', {
layout: 'archive/layout',
message: 'Oops, we couldn\'t find a link for the URL you clicked',
message: _('Oops, we couldn\'t find a link for the URL you clicked'),
campaign: {
subject: 'Error 404'
}

View file

@ -17,6 +17,8 @@ let humanize = require('humanize');
let mkdirp = require('mkdirp');
let pathlib = require('path');
let log = require('npmlog');
let _ = require('../lib/translate')._;
let util = require('util');
let uploadStorage = multer.diskStorage({
destination: (req, file, callback) => {
@ -44,7 +46,7 @@ let moment = require('moment-timezone');
router.all('/*', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
res.setSelectedMenu('lists');
@ -85,10 +87,10 @@ router.get('/create', passport.csrfProtection, (req, res) => {
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
lists.create(req.body, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create list');
req.flash('danger', err && err.message || err || _('Could not create list'));
return res.redirect('/lists/create?' + tools.queryParams(req.body));
}
req.flash('success', 'List created');
req.flash('success', _('List created'));
res.redirect('/lists/view/' + id);
});
});
@ -96,7 +98,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) =
router.get('/edit/:id', passport.csrfProtection, (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
list.csrfToken = req.csrfToken();
@ -110,9 +112,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) =>
if (err) {
req.flash('danger', err.message || err);
} else if (updated) {
req.flash('success', 'List settings updated');
req.flash('success', _('List settings updated'));
} else {
req.flash('info', 'List settings not updated');
req.flash('info', _('List settings not updated'));
}
if (req.body.id) {
@ -128,9 +130,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) =
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'List deleted');
req.flash('success', _('List deleted'));
} else {
req.flash('info', 'Could not delete specified list');
req.flash('info', _('Could not delete specified list'));
}
return res.redirect('/lists');
@ -141,7 +143,7 @@ router.post('/ajax/:id', (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
return res.json({
error: err && err.message || err || 'List not found',
error: err && err.message || err || _('List not found'),
data: []
});
}
@ -166,7 +168,7 @@ router.post('/ajax/:id', (req, res) => {
row.customFields = fields.getRow(fieldList, row);
});
let statuses = ['Unknown', 'Subscribed', 'Unsubscribed', 'Bounced', 'Complained'];
let statuses = [_('Unknown'), _('Subscribed'), _('Unsubscribed'), _('Bounced'), _('Complained')];
res.json({
draw: req.body.draw,
@ -197,11 +199,11 @@ router.post('/ajax/:id', (req, res) => {
let key = keys[i];
switch (key.verifyPrimaryKey()) {
case 0:
return 'Invalid key';
return _('Invalid key');
case 1:
return 'Expired key';
return _('Expired key');
case 2:
return 'Revoked key';
return _('Revoked key');
}
}
@ -217,7 +219,7 @@ router.post('/ajax/:id', (req, res) => {
} else {
return htmlescape(cRow.value || '');
}
})).concat(statuses[row.status]).concat(row.created && row.created.toISOString ? '<span class="datestring" data-date="' + row.created.toISOString() + '" title="' + row.created.toISOString() + '">' + row.created.toISOString() + '</span>' : 'N/A').concat('<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + list.id + '/edit/' + row.cid + '">Edit</a>'))
})).concat(statuses[row.status]).concat(row.created && row.created.toISOString ? '<span class="datestring" data-date="' + row.created.toISOString() + '" title="' + row.created.toISOString() + '">' + row.created.toISOString() + '</span>' : 'N/A').concat('<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + list.id + '/edit/' + row.cid + '">' + _('Edit') + '</a>'))
});
});
});
@ -231,7 +233,7 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
@ -248,22 +250,22 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => {
list.imports = imports.map((entry, i) => {
entry.index = i + 1;
entry.importType = entry.type === 1 ? 'Subscribe' : 'Unsubscribe';
entry.importType = entry.type === 1 ? _('Subscribe') : _('Unsubscribe');
switch (entry.status) {
case 0:
entry.importStatus = 'Initializing';
entry.importStatus = _('Initializing');
break;
case 1:
entry.importStatus = 'Initialized';
entry.importStatus = _('Initialized');
break;
case 2:
entry.importStatus = 'Importing...';
entry.importStatus = _('Importing') + '…';
break;
case 3:
entry.importStatus = 'Finished';
entry.importStatus = _('Finished');
break;
default:
entry.importStatus = 'Errored' + (entry.error ? ' (' + entry.error + ')' : '');
entry.importStatus = _('Errored') + (entry.error ? ' (' + entry.error + ')' : '');
entry.error = true;
}
entry.created = entry.created && entry.created.toISOString();
@ -296,7 +298,7 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => {
router.get('/subscription/:id/add', passport.csrfProtection, (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
@ -335,13 +337,13 @@ router.get('/subscription/:id/add', passport.csrfProtection, (req, res) => {
router.get('/subscription/:id/edit/:cid', passport.csrfProtection, (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
subscriptions.get(list.id, req.params.cid, (err, subscription) => {
if (err || !subscription) {
req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID');
req.flash('danger', err && err.message || err || _('Could not find subscriber with specified ID'));
return res.redirect('/lists/view/' + req.params.id);
}
@ -387,14 +389,14 @@ router.get('/subscription/:id/edit/:cid', passport.csrfProtection, (req, res) =>
router.post('/subscription/add', passport.parseForm, passport.csrfProtection, (req, res) => {
subscriptions.insert(req.body.list, false, req.body, (err, response) => {
if (err) {
req.flash('danger', err && err.message || err || 'Could not add subscription');
req.flash('danger', err && err.message || err || _('Could not add subscription'));
return res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/add?' + tools.queryParams(req.body));
}
if (response.entryId) {
req.flash('success', req.body.email + ' was successfully added to your list');
req.flash('success', util.format(_('%s was successfully added to your list'), req.body.email));
} else {
req.flash('warning', req.body.email + ' was not added to your list');
req.flash('warning', util.format(_('%s was not added to your list'), req.body.email));
}
res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/add');
@ -404,22 +406,22 @@ router.post('/subscription/add', passport.parseForm, passport.csrfProtection, (r
router.post('/subscription/unsubscribe', passport.parseForm, passport.csrfProtection, (req, res) => {
lists.get(req.body.list, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
subscriptions.get(list.id, req.body.cid, (err, subscription) => {
if (err || !subscription) {
req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID');
req.flash('danger', err && err.message || err || _('Could not find subscriber with specified ID'));
return res.redirect('/lists/view/' + list.id);
}
subscriptions.unsubscribe(list.id, subscription.email, false, err => {
if (err) {
req.flash('danger', err && err.message || err || 'Could not unsubscribe user');
req.flash('danger', err && err.message || err || _('Could not unsubscribe user'));
return res.redirect('/lists/subscription/' + list.id + '/edit/' + subscription.cid);
}
req.flash('success', subscription.email + ' was successfully unsubscribed from your list');
req.flash('success', util.format(_('%s was successfully unsubscribed from your list'), subscription.email));
res.redirect('/lists/view/' + list.id);
});
});
@ -429,17 +431,17 @@ router.post('/subscription/unsubscribe', passport.parseForm, passport.csrfProtec
router.post('/subscription/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
lists.get(req.body.list, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
subscriptions.delete(list.id, req.body.cid, (err, email) => {
if (err || !email) {
req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID');
req.flash('danger', err && err.message || err || _('Could not find subscriber with specified ID'));
return res.redirect('/lists/view/' + list.id);
}
req.flash('success', email + ' was successfully removed from your list');
req.flash('success', util.format(_('%s was successfully removed from your list'), email));
res.redirect('/lists/view/' + list.id);
});
});
@ -451,16 +453,16 @@ router.post('/subscription/edit', passport.parseForm, passport.csrfProtection, (
if (err) {
if (err.code === 'ER_DUP_ENTRY') {
req.flash('danger', 'Another subscriber with email address ' + req.body.email + ' already exists');
req.flash('danger', util.format(_('Another subscriber with email address %s already exists'), req.body.email));
return res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/edit/' + req.body.cid);
} else {
req.flash('danger', err.message || err);
}
} else if (updated) {
req.flash('success', 'Subscription settings updated');
req.flash('success', _('Subscription settings updated'));
} else {
req.flash('info', 'Subscription settings not updated');
req.flash('info', _('Subscription settings not updated'));
}
if (req.body.list) {
@ -474,7 +476,7 @@ router.post('/subscription/edit', passport.parseForm, passport.csrfProtection, (
router.get('/subscription/:id/import', passport.csrfProtection, (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
@ -496,13 +498,13 @@ router.get('/subscription/:id/import', passport.csrfProtection, (req, res) => {
router.get('/subscription/:id/import/:importId', passport.csrfProtection, (req, res) => {
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
subscriptions.getImport(req.params.id, req.params.importId, (err, data) => {
if (err || !data) {
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
req.flash('danger', err && err.message || err || _('Could not find import data with specified ID'));
return res.redirect('/lists');
}
@ -525,7 +527,7 @@ router.get('/subscription/:id/import/:importId', passport.csrfProtection, (req,
router.post('/subscription/import', uploads.single('listimport'), passport.parseForm, passport.csrfProtection, (req, res) => {
lists.get(req.body.list, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
@ -533,7 +535,7 @@ router.post('/subscription/import', uploads.single('listimport'), passport.parse
getPreview(req.file.path, req.file.size, delimiter, (err, rows) => {
if (err) {
req.flash('danger', err && err.message || err || 'Could not process CSV');
req.flash('danger', err && err.message || err || _('Could not process CSV'));
return res.redirect('/lists');
} else {
@ -542,7 +544,7 @@ router.post('/subscription/import', uploads.single('listimport'), passport.parse
example: rows[1] || []
}, (err, importId) => {
if (err) {
req.flash('danger', err && err.message || err || 'Could not create importer');
req.flash('danger', err && err.message || err || _('Could not create importer'));
return res.redirect('/lists');
}
@ -593,7 +595,7 @@ function getPreview(path, size, delimiter, callback) {
// just ignore
});
if (!data || !data.length) {
return callback(null, new Error('Empty file'));
return callback(null, new Error(_('Empty file')));
}
callback(err, data);
});
@ -604,13 +606,13 @@ function getPreview(path, size, delimiter, callback) {
router.post('/subscription/import-confirm', passport.parseForm, passport.csrfProtection, (req, res) => {
lists.get(req.body.list, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
subscriptions.getImport(list.id, req.body.import, (err, data) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
req.flash('danger', err && err.message || err || _('Could not find import data with specified ID'));
return res.redirect('/lists');
}
@ -646,11 +648,11 @@ router.post('/subscription/import-confirm', passport.parseForm, passport.csrfPro
mapping: JSON.stringify(data.mapping)
}, (err, importer) => {
if (err || !importer) {
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
req.flash('danger', err && err.message || err || _('Could not find import data with specified ID'));
return res.redirect('/lists');
}
req.flash('success', 'Import started');
req.flash('success', _('Import started'));
res.redirect('/lists/view/' + list.id + '?tab=imports');
});
});
@ -661,7 +663,7 @@ router.post('/subscription/import-confirm', passport.parseForm, passport.csrfPro
router.post('/subscription/import-restart', passport.parseForm, passport.csrfProtection, (req, res) => {
lists.get(req.body.list, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
@ -674,11 +676,11 @@ router.post('/subscription/import-restart', passport.parseForm, passport.csrfPro
failed: 0
}, (err, importer) => {
if (err || !importer) {
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
req.flash('danger', err && err.message || err || _('Could not find import data with specified ID'));
return res.redirect('/lists');
}
req.flash('success', 'Import restarted');
req.flash('success', _('Import restarted'));
res.redirect('/lists/view/' + list.id + '?tab=imports');
});
});
@ -688,13 +690,13 @@ router.get('/subscription/:id/import/:importId/failed', (req, res) => {
let start = 0;
lists.get(req.params.id, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
req.flash('danger', err && err.message || err || _('Could not find list with specified ID'));
return res.redirect('/lists');
}
subscriptions.getImport(req.params.id, req.params.importId, (err, data) => {
if (err || !data) {
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
req.flash('danger', err && err.message || err || _('Could not find import data with specified ID'));
return res.redirect('/lists');
}
subscriptions.getFailedImports(req.params.importId, (err, rows) => {

View file

@ -6,10 +6,11 @@ let passport = require('../lib/passport');
let lists = require('../lib/models/lists');
let segments = require('../lib/models/segments');
let tools = require('../lib/tools');
let _ = require('../lib/translate')._;
router.all('/*', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
res.setSelectedMenu('lists');
@ -24,7 +25,7 @@ router.get('/:list', (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -55,7 +56,7 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -82,10 +83,10 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => {
router.post('/:list/create', passport.parseForm, passport.csrfProtection, (req, res) => {
segments.create(req.params.list, req.body, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create segment');
req.flash('danger', err && err.message || err || _('Could not create segment'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/create?' + tools.queryParams(req.body));
}
req.flash('success', 'Segment created');
req.flash('success', _('Segment created'));
res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + id);
});
});
@ -98,7 +99,7 @@ router.get('/:list/view/:id', (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -109,7 +110,7 @@ router.get('/:list/view/:id', (req, res) => {
}
if (!segment) {
req.flash('danger', 'Selected segment ID not found');
req.flash('danger', _('Selected segment ID not found'));
return res.redirect('/');
}
@ -147,7 +148,7 @@ router.get('/:list/edit/:segment', passport.csrfProtection, (req, res) => {
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -184,9 +185,9 @@ router.post('/:list/edit', passport.parseForm, passport.csrfProtection, (req, re
if (err) {
req.flash('danger', err.message || err);
} else if (updated) {
req.flash('success', 'Segment settings updated');
req.flash('success', _('Segment settings updated'));
} else {
req.flash('info', 'Segment settings not updated');
req.flash('info', _('Segment settings not updated'));
}
if (req.body.id) {
@ -202,9 +203,9 @@ router.post('/:list/delete', passport.parseForm, passport.csrfProtection, (req,
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Segment deleted');
req.flash('success', _('Segment deleted'));
} else {
req.flash('info', 'Could not delete specified segment');
req.flash('info', _('Could not delete specified segment'));
}
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
@ -219,7 +220,7 @@ router.get('/:list/rules/:segment/create', passport.csrfProtection, (req, res) =
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -251,7 +252,7 @@ router.post('/:list/rules/:segment/next', passport.parseForm, passport.csrfProte
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -262,13 +263,13 @@ router.post('/:list/rules/:segment/next', passport.parseForm, passport.csrfProte
}
if (!segment) {
req.flash('danger', 'Selected segment not found');
req.flash('danger', _('Selected segment not found'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
}
let column = segment.columns.filter(column => column.column === req.body.column).pop();
if (!column) {
req.flash('danger', 'Invalid rule type');
req.flash('danger', _('Invalid rule type'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/create?' + tools.queryParams(req.body));
}
@ -285,7 +286,7 @@ router.get('/:list/rules/:segment/configure', passport.csrfProtection, (req, res
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -296,13 +297,13 @@ router.get('/:list/rules/:segment/configure', passport.csrfProtection, (req, res
}
if (!segment) {
req.flash('danger', 'Selected segment not found');
req.flash('danger', _('Selected segment not found'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
}
let column = segment.columns.filter(column => column.column === req.query.column).pop();
if (!column) {
req.flash('danger', 'Invalid rule type');
req.flash('danger', _('Invalid rule type'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/create?' + tools.queryParams(req.body));
}
@ -332,16 +333,16 @@ router.post('/:list/rules/:segment/create', passport.parseForm, passport.csrfPro
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
segments.createRule(req.params.segment, req.body, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create rule');
req.flash('danger', err && err.message || err || _('Could not create rule'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + encodeURIComponent(req.params.segment) + '/configure?' + tools.queryParams(req.body));
}
req.flash('success', 'Rule created');
req.flash('success', _('Rule created'));
res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment));
});
});
@ -355,7 +356,7 @@ router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, re
}
if (!list) {
req.flash('danger', 'Selected list ID not found');
req.flash('danger', _('Selected list ID not found'));
return res.redirect('/');
}
@ -366,7 +367,7 @@ router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, re
}
if (!segment) {
req.flash('danger', 'Selected segment not found');
req.flash('danger', _('Selected segment not found'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
}
@ -377,13 +378,13 @@ router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, re
}
if (!segment) {
req.flash('danger', 'Selected segment not found');
req.flash('danger', _('Selected segment not found'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
}
let column = segment.columns.filter(column => column.column === rule.column).pop();
if (!column) {
req.flash('danger', 'Invalid rule type');
req.flash('danger', _('Invalid rule type'));
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + segment.id);
}
@ -406,9 +407,9 @@ router.post('/:list/rules/:segment/edit', passport.parseForm, passport.csrfProte
if (err) {
req.flash('danger', err.message || err);
} else if (updated) {
req.flash('success', 'Rule settings updated');
req.flash('success', _('Rule settings updated'));
} else {
req.flash('info', 'Rule settings not updated');
req.flash('info', _('Rule settings not updated'));
}
if (req.params.segment) {
@ -424,9 +425,9 @@ router.post('/:list/rules/:segment/delete', passport.parseForm, passport.csrfPro
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Rule deleted');
req.flash('success', _('Rule deleted'));
} else {
req.flash('info', 'Could not delete specified rule');
req.flash('info', _('Could not delete specified rule'));
}
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment));

View file

@ -13,11 +13,13 @@ let fields = require('../lib/models/fields');
let subscriptions = require('../lib/models/subscriptions');
let settings = require('../lib/models/settings');
let openpgp = require('openpgp');
let _ = require('../lib/translate')._;
let util = require('util');
router.get('/subscribe/:cid', (req, res, next) => {
subscriptions.subscribe(req.params.cid, req.ip, (err, subscription) => {
if (!err && !subscription) {
err = new Error('Selected subscription not found');
err = new Error(_('Selected subscription not found'));
err.status = 404;
}
@ -27,7 +29,7 @@ router.get('/subscribe/:cid', (req, res, next) => {
lists.get(subscription.list, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -73,7 +75,7 @@ router.get('/subscribe/:cid', (req, res, next) => {
name: [].concat(subscription.firstName || []).concat(subscription.lastName || []).join(' '),
address: subscription.email
},
subject: list.name + ': Subscription Confirmed',
subject: util.format(_('%s: Subscription Confirmed'), list.name),
encryptionKeys
}, {
html: 'emails/subscription-confirmed-html.hbs',
@ -98,7 +100,7 @@ router.get('/subscribe/:cid', (req, res, next) => {
router.get('/:cid', passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.cid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -136,7 +138,7 @@ router.get('/:cid', passport.csrfProtection, (req, res, next) => {
router.get('/:cid/confirm-notice', (req, res, next) => {
lists.getByCid(req.params.cid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -161,7 +163,7 @@ router.get('/:cid/confirm-notice', (req, res, next) => {
router.get('/:cid/updated-notice', (req, res, next) => {
lists.getByCid(req.params.cid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -186,7 +188,7 @@ router.get('/:cid/updated-notice', (req, res, next) => {
router.get('/:cid/unsubscribe-notice', (req, res, next) => {
lists.getByCid(req.params.cid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -212,7 +214,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req
let email = (req.body.email || '').toString().trim();
if (!email) {
req.flash('danger', 'Email address not set');
req.flash('danger', _('Email address not set'));
return res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '?' + tools.queryParams(req.body));
}
@ -227,7 +229,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req
lists.getByCid(req.params.cid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -250,7 +252,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req
subscriptions.addConfirmation(list, email, req.ip, data, (err, confirmCid) => {
if (!err && !confirmCid) {
err = new Error('Could not store confirmation data');
err = new Error(_('Could not store confirmation data'));
}
if (err) {
req.flash('danger', err.message || err);
@ -265,7 +267,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req
router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.lcid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -279,7 +281,7 @@ router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => {
}
subscriptions.get(list.id, req.params.ucid, (err, subscription) => {
if (!err && !subscription) {
err = new Error('Subscription not found from this list');
err = new Error(_('Subscription not found from this list'));
err.status = 404;
}
@ -312,7 +314,7 @@ router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => {
router.post('/:lcid/manage', passport.parseForm, passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.lcid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -334,7 +336,7 @@ router.post('/:lcid/manage', passport.parseForm, passport.csrfProtection, (req,
router.get('/:lcid/manage-address/:ucid', passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.lcid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -344,7 +346,7 @@ router.get('/:lcid/manage-address/:ucid', passport.csrfProtection, (req, res, ne
subscriptions.get(list.id, req.params.ucid, (err, subscription) => {
if (!err && !subscription) {
err = new Error('Subscription not found from this list');
err = new Error(_('Subscription not found from this list'));
err.status = 404;
}
@ -363,7 +365,7 @@ router.get('/:lcid/manage-address/:ucid', passport.csrfProtection, (req, res, ne
router.post('/:lcid/manage-address', passport.parseForm, passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.lcid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -378,7 +380,7 @@ router.post('/:lcid/manage-address', passport.parseForm, passport.csrfProtection
return res.redirect('/subscription/' + encodeURIComponent(req.params.lcid) + '/manage-address/' + encodeURIComponent(req.body.cid) + '?' + tools.queryParams(req.body));
}
req.flash('info', 'Email address updated, check your mailbox for verification instructions');
req.flash('info', _('Email address updated, check your mailbox for verification instructions'));
res.redirect('/subscription/' + req.params.lcid + '/manage/' + req.body.cid);
});
});
@ -387,7 +389,7 @@ router.post('/:lcid/manage-address', passport.parseForm, passport.csrfProtection
router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.lcid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -397,7 +399,7 @@ router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next)
subscriptions.get(list.id, req.params.ucid, (err, subscription) => {
if (!err && !subscription) {
err = new Error('Subscription not found from this list');
err = new Error(_('Subscription not found from this list'));
err.status = 404;
}
@ -419,7 +421,7 @@ router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next)
router.post('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtection, (req, res, next) => {
lists.getByCid(req.params.lcid, (err, list) => {
if (!err && !list) {
err = new Error('Selected list not found');
err = new Error(_('Selected list not found'));
err.status = 404;
}
@ -467,7 +469,7 @@ router.post('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtection, (
name: [].concat(subscription.firstName || []).concat(subscription.lastName || []).join(' '),
address: subscription.email
},
subject: list.name + ': Subscription Confirmed',
subject: util.format(_('%s: Subscription Confirmed'), list.name),
encryptionKeys
}, {
html: 'emails/unsubscribe-confirmed-html.hbs',
@ -494,7 +496,7 @@ router.post('/publickey', passport.parseForm, passport.csrfProtection, (req, res
return next(err);
}
if (!configItems.pgpPrivateKey) {
err = new Error('Public key is not set');
err = new Error(_('Public key is not set'));
err.status = 404;
return next(err);
}
@ -510,7 +512,7 @@ router.post('/publickey', passport.parseForm, passport.csrfProtection, (req, res
}
if (!privKey) {
err = new Error('Public key is not set');
err = new Error(_('Public key is not set'));
err.status = 404;
return next(err);
}

View file

@ -10,10 +10,11 @@ let helpers = require('../lib/helpers');
let striptags = require('striptags');
let passport = require('../lib/passport');
let mailer = require('../lib/mailer');
let _ = require('../lib/translate')._;
router.all('/*', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
res.setSelectedMenu('templates');
@ -68,19 +69,19 @@ router.get('/create', passport.csrfProtection, (req, res, next) => {
data.text = data.text || rendererText(configItems);
data.disableWysiwyg = configItems.disableWysiwyg;
data.editors = config.editors || [['summernote', 'Summernote']];
data.editors = config.editors || [
['summernote', 'Summernote']
];
data.editors = data.editors.map(ed => {
let editor = {
name: ed[0],
label: ed[1],
label: ed[1]
};
if (config[editor.name] && config[editor.name].templates) {
editor.templates = config[editor.name].templates.map(tmpl => {
return {
name: tmpl[0],
label: tmpl[1],
}
});
editor.templates = config[editor.name].templates.map(tmpl => ({
name: tmpl[0],
label: tmpl[1]
}));
}
return editor;
});
@ -94,10 +95,10 @@ router.get('/create', passport.csrfProtection, (req, res, next) => {
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
templates.create(req.body, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create template');
req.flash('danger', err && err.message || err || _('Could not create template'));
return res.redirect('/templates/create?' + tools.queryParams(req.body));
}
req.flash('success', 'Template created');
req.flash('success', _('Template created'));
res.redirect('/templates/edit/' + id);
});
});
@ -105,7 +106,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) =
router.get('/edit/:id', passport.csrfProtection, (req, res, next) => {
templates.get(req.params.id, (err, template) => {
if (err || !template) {
req.flash('danger', err && err.message || err || 'Could not find template with specified ID');
req.flash('danger', err && err.message || err || _('Could not find template with specified ID'));
return res.redirect('/templates');
}
settings.list(['disableWysiwyg'], (err, configItems) => {
@ -136,9 +137,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) =>
if (err) {
req.flash('danger', err.message || err);
} else if (updated) {
req.flash('success', 'Template settings updated');
req.flash('success', _('Template settings updated'));
} else {
req.flash('info', 'Template settings not updated');
req.flash('info', _('Template settings not updated'));
}
if (req.body.id) {
@ -154,9 +155,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) =
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Template deleted');
req.flash('success', _('Template deleted'));
} else {
req.flash('info', 'Could not delete specified template');
req.flash('info', _('Could not delete specified template'));
}
return res.redirect('/templates');

View file

@ -10,10 +10,12 @@ let striptags = require('striptags');
let passport = require('../lib/passport');
let tools = require('../lib/tools');
let htmlescape = require('escape-html');
let _ = require('../lib/translate')._;
let util = require('util');
router.all('/*', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
res.setSelectedMenu('triggers');
@ -57,7 +59,7 @@ router.get('/create-select', passport.csrfProtection, (req, res, next) => {
router.post('/create-select', passport.parseForm, passport.csrfProtection, (req, res) => {
if (!req.body.list) {
req.flash('danger', 'Could not find selected list');
req.flash('danger', _('Could not find selected list'));
return res.redirect('/triggers/create-select');
}
res.redirect('/triggers/' + encodeURIComponent(req.body.list) + '/create');
@ -74,7 +76,7 @@ router.get('/:listId/create', passport.csrfProtection, (req, res, next) => {
lists.get(req.params.listId, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find selected list');
req.flash('danger', err && err.message || err || _('Could not find selected list'));
return res.redirect('/triggers/create-select');
}
fields.list(list.id, (err, fieldList) => {
@ -126,14 +128,14 @@ router.get('/:listId/create', passport.csrfProtection, (req, res, next) => {
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
triggers.create(req.body, (err, id) => {
if (err || !id) {
req.flash('danger', err && err.message || err || 'Could not create trigger');
req.flash('danger', err && err.message || err || _('Could not create trigger'));
if (req.body.list) {
return res.redirect('/triggers/' + encodeURIComponent(req.body.list) + '/create?' + tools.queryParams(req.body));
} else {
return res.redirect('/triggers');
}
}
req.flash('success', 'Trigger “' + req.body.name + '” created');
req.flash('success', util.format(_('Trigger “%s” created'), req.body.name));
res.redirect('/triggers');
});
});
@ -141,7 +143,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) =
router.get('/edit/:id', passport.csrfProtection, (req, res, next) => {
triggers.get(req.params.id, (err, trigger) => {
if (err || !trigger) {
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID'));
return res.redirect('/campaigns');
}
trigger.csrfToken = req.csrfToken();
@ -149,7 +151,7 @@ router.get('/edit/:id', passport.csrfProtection, (req, res, next) => {
lists.get(trigger.list, (err, list) => {
if (err || !list) {
req.flash('danger', err && err.message || err || 'Could not find selected list');
req.flash('danger', err && err.message || err || _('Could not find selected list'));
return res.redirect('/triggers');
}
fields.list(list.id, (err, fieldList) => {
@ -209,9 +211,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) =>
req.flash('danger', err.message || err);
return res.redirect('/triggers/edit/' + encodeURIComponent(req.body.id));
} else if (updated) {
req.flash('success', 'Trigger settings updated');
req.flash('success', _('Trigger settings updated'));
} else {
req.flash('info', 'Trigger settings not updated');
req.flash('info', _('Trigger settings not updated'));
}
return res.redirect('/triggers');
@ -223,9 +225,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) =
if (err) {
req.flash('danger', err && err.message || err);
} else if (deleted) {
req.flash('success', 'Trigger deleted');
req.flash('success', _('Trigger deleted'));
} else {
req.flash('info', 'Could not delete specified trigger');
req.flash('info', _('Could not delete specified trigger'));
}
return res.redirect('/triggers');
@ -237,7 +239,7 @@ router.get('/status/:id', passport.csrfProtection, (req, res) => {
triggers.get(id, (err, trigger) => {
if (err || !trigger) {
req.flash('danger', err && err.message || err || 'Could not find trigger with specified ID');
req.flash('danger', err && err.message || err || _('Could not find trigger with specified ID'));
return res.redirect('/triggers');
}
@ -250,7 +252,7 @@ router.post('/status/ajax/:id', (req, res) => {
triggers.get(req.params.id, (err, trigger) => {
if (err || !trigger) {
return res.json({
error: err && err.message || err || 'Trigger not found',
error: err && err.message || err || _('Trigger not found'),
data: []
});
}
@ -292,7 +294,7 @@ router.post('/status/ajax/:id', (req, res) => {
htmlescape(row.firstName || ''),
htmlescape(row.lastName || ''),
'<span class="datestring" data-date="' + row.created.toISOString() + '" title="' + row.created.toISOString() + '">' + row.created.toISOString() + '</span>',
'<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + trigger.list + '/edit/' + row.cid + '">Edit</a>'
'<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + trigger.list + '/edit/' + row.cid + '">' + _('Edit') + '</a>'
])
});
});

View file

@ -6,6 +6,7 @@ let router = new express.Router();
let users = require('../lib/models/users');
let fields = require('../lib/models/fields');
let settings = require('../lib/models/settings');
let _ = require('../lib/translate')._;
router.get('/logout', (req, res) => passport.logout(req, res));
@ -28,7 +29,7 @@ router.post('/forgot', passport.parseForm, passport.csrfProtection, (req, res) =
req.flash('danger', err.message || err);
return res.redirect('/users/forgot');
} else {
req.flash('success', 'An email with password reset instructions has been sent to your email address, if it exists on our system.');
req.flash('success', _('An email with password reset instructions has been sent to your email address, if it exists on our system.'));
}
return res.redirect('/users/login');
});
@ -42,7 +43,7 @@ router.get('/reset', passport.csrfProtection, (req, res) => {
}
if (!status) {
req.flash('danger', 'Unknown or expired reset token');
req.flash('danger', _('Unknown or expired reset token'));
return res.redirect('/users/login');
}
@ -60,9 +61,9 @@ router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) =>
req.flash('danger', err.message || err);
return res.redirect('/users/reset?username=' + encodeURIComponent(req.body.username) + '&token=' + encodeURIComponent(req.body['reset-token']));
} else if (!status) {
req.flash('danger', 'Unknown or expired reset token');
req.flash('danger', _('Unknown or expired reset token'));
} else {
req.flash('success', 'Your password has been changed successfully');
req.flash('success', _('Your password has been changed successfully'));
}
return res.redirect('/users/login');
@ -71,7 +72,7 @@ router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) =>
router.all('/api', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
next();
@ -83,7 +84,7 @@ router.get('/api', passport.csrfProtection, (req, res, next) => {
return next(err);
}
if (!user) {
return next(new Error('User data not found'));
return next(new Error(_('User data not found')));
}
settings.list(['serviceUrl'], (err, configItems) => {
if (err) {
@ -106,9 +107,9 @@ router.post('/api/reset-token', passport.parseForm, passport.csrfProtection, (re
if (err) {
req.flash('danger', err.message || err);
} else if (success) {
req.flash('success', 'Access token updated');
req.flash('success', _('Access token updated'));
} else {
req.flash('info', 'Access token not updated');
req.flash('info', _('Access token not updated'));
}
return res.redirect('/users/api');
});
@ -116,7 +117,7 @@ router.post('/api/reset-token', passport.parseForm, passport.csrfProtection, (re
router.all('/account', (req, res, next) => {
if (!req.user) {
req.flash('danger', 'Need to be logged in to access restricted content');
req.flash('danger', _('Need to be logged in to access restricted content'));
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
}
next();
@ -135,9 +136,9 @@ router.post('/account', passport.parseForm, passport.csrfProtection, (req, res)
if (err) {
req.flash('danger', err.message || err);
} else if (success) {
req.flash('success', 'Account information updated');
req.flash('success', _('Account information updated'));
} else {
req.flash('info', 'Account information not updated');
req.flash('info', _('Account information not updated'));
}
return res.redirect('/users/account');
});

View file

@ -6,6 +6,8 @@ let db = require('../lib/db');
let tools = require('../lib/tools');
let feed = require('../lib/feed');
let campaigns = require('../lib/models/campaigns');
let _ = require('../lib/translate')._;
let util = require('util');
const feed_timeout = 15 * 1000;
const rss_timeout = 1 * 1000;
@ -46,12 +48,12 @@ function feedLoop() {
let message;
if (err) {
log.error('Feed', err);
message = 'Feed error: ' + err.message;
message = util.format(_('Feed error: %s'), err.message);
} else if (result) {
log.verbose('Feed', 'Added %s new campaigns for %s', result, parent.id);
message = 'Found ' + result + ' new campaign messages from feed';
message = util.format(_('Found %s new campaign messages from feed'), result);
} else {
message = 'Found nothing new from the feed';
message = _('Found nothing new from the feed');
}
return updateRssInfo(parent.id, false, message, () => {
setTimeout(feedLoop, rss_timeout);
@ -138,7 +140,7 @@ function checkEntries(parent, entries, callback) {
let campaign = {
type: 'entry',
name: entry.title || 'RSS entry ' + (entry.guid.substr(0, 67)),
name: entry.title || util.format(_('RSS entry %s'), entry.guid.substr(0, 67)),
from: parent.from,
address: parent.address,
subject: entry.title || parent.subject,

View file

@ -4,6 +4,7 @@ let log = require('npmlog');
let db = require('../lib/db');
let tools = require('../lib/tools');
let _ = require('../lib/translate')._;
let fields = require('../lib/models/fields');
let subscriptions = require('../lib/models/subscriptions');
@ -239,7 +240,7 @@ let importLoop = () => {
let failed = null;
if (err) {
if (err.code === 'ENOENT') {
failed = 'Could not access import file';
failed = _('Could not access import file');
} else {
failed = err.message || err;
}

View file

@ -16,6 +16,8 @@ let url = require('url');
let htmlToText = require('html-to-text');
let request = require('request');
let libmime = require('libmime');
let _ = require('../lib/translate')._;
let util = require('util');
let attachmentCache = new Map();
let attachmentCacheSize = 0;
@ -299,14 +301,14 @@ function formatMessage(message, callback) {
return callback(err);
}
if (!campaign) {
return callback(new Error('Campaign not found'));
return callback(new Error(_('Campaign not found')));
}
lists.get(message.listId, (err, list) => {
if (err) {
return callback(err);
}
if (!list) {
return callback(new Error('List not found'));
return callback(new Error(_('List not found')));
}
settings.list(['serviceUrl', 'verpUse', 'verpHostname'], (err, configItems) => {
@ -442,7 +444,7 @@ function formatMessage(message, callback) {
return callback(err);
}
if (httpResponse.statusCode !== 200) {
return callback(new Error('Received status code ' + httpResponse.statusCode + ' from ' + campaign.sourceUrl));
return callback(new Error(util.format(_('Received status code %s from %s'), httpResponse.statusCode, campaign.sourceUrl)));
}
renderAndSend(body && body.toString(), '', false);
});

View file

@ -4,6 +4,8 @@ let log = require('npmlog');
let db = require('../lib/db');
let tools = require('../lib/tools');
let triggers = require('../lib/models/triggers');
let _ = require('../lib/translate')._;
let util = require('util');
function triggerLoop() {
checkTrigger((err, triggerId) => {
@ -46,7 +48,7 @@ function checkTrigger(callback) {
return callback(err);
}
if (!query) {
return callback(new Error('Unknown trigger type ' + trigger.id));
return callback(new Error(util.format(_('Unknown trigger type %s'), trigger.id)));
}
trigger.query = query;
fireTrigger(trigger, callback);

View file

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="Self hosted email newsletter app">
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
<meta name="author" content="Andris Reinman">
<link rel="icon" href="/favicon.ico">

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Bounced info</li>
<li class="active">{{#translate}}Bounced info{{/translate}}</li>
</ol>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>Bounced info</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>{{#translate}}Bounced info{{/translate}}</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -20,7 +20,7 @@
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">Subscribers who bounced and were unsubscribed:</div>
<div class="panel-heading">{{#translate}}Subscribers who bounced and were unsubscribed:{{/translate}}</div>
<div class="panel-body">
<div class="table-responsive">
<table data-topic-url="/campaigns/status" data-topic-id="{{id}}/3" data-sort-column="1" data-sort-order="asc" class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1,0,1,0">
@ -30,19 +30,19 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
<th>
SMTP response
{{#translate}}SMTP response{{/translate}}
</th>
<th>
Bounced
{{#translate}}Bounce time{{/translate}}
</th>
<th></th>
</tr>

View file

@ -1,22 +1,22 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li class="active">Campaigns</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li class="active">{{#translate}}Campaigns{{/translate}}</li>
</ol>
<div class="pull-right">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Create Campaign <span class="caret"></span>
{{#translate}}Create Campaign{{/translate}} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="/campaigns/create"><i class="glyphicon glyphicon-plus"></i> Normal Campaign</a></li>
<li><a href="/campaigns/create?type=rss"><i class="glyphicon glyphicon-signal"></i> RSS Campaign</a></li>
<li><a href="/campaigns/create?type=triggered"><i class="glyphicon glyphicon-console"></i> Triggered Campaign</a></li>
<li><a href="/campaigns/create"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Regular Campaign{{/translate}}</a></li>
<li><a href="/campaigns/create?type=rss"><i class="glyphicon glyphicon-signal"></i> {{#translate}}RSS Campaign{{/translate}}</a></li>
<li><a href="/campaigns/create?type=triggered"><i class="glyphicon glyphicon-console"></i> {{#translate}}Triggered Campaign{{/translate}}</a></li>
</ul>
</div>
</div>
<h2>Campaigns</h2>
<h2>{{#translate}}Campaigns{{/translate}}</h2>
<hr>
@ -27,16 +27,16 @@
#
</th>
<th>
Name
{{#translate}}Name{{/translate}}
</th>
<th>
Description
{{#translate}}Description{{/translate}}
</th>
<th>
Status
{{#translate}}Status{{/translate}}
</th>
<th>
Created
{{#translate}}Created{{/translate}}
</th>
<th class="col-md-1">
&nbsp;

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Link info</li>
<li class="active">{{#translate}}Link info{{/translate}}</li>
</ol>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>Link info</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=links" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>{{#translate}}Link info{{/translate}}</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=links" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -20,23 +20,23 @@
<table class="table table-bordered table-hover">
<thead>
<th>
URL
{{#translate}}URL{{/translate}}
</th>
<th class="col-md-1">
Clicks
{{#translate}}Clicks{{/translate}}
</th>
<th class="col-md-1">
% of clicks
{{#translate}}% of clicks{{/translate}}
</th>
<th class="col-md-1">
% of messages
{{#translate}}% of messages{{/translate}}
</th>
</thead>
<tbody>
<tr class="success">
{{#if aggregated}}
<th>
Aggregated clicks
{{#translate}}Aggregated clicks{{/translate}}
</th>
<th>
{{clicks}}
@ -67,7 +67,7 @@
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">{{#if aggregated}}Subscribers who clicked on a link:{{else}}Subscribers who clicked on this link:{{/if}}</div>
<div class="panel-heading">{{#if aggregated}}{{#translate}}Subscribers who clicked on a link:{{/translate}}{{else}}{{#translate}}Subscribers who clicked on this link:{{/translate}}{{/if}}</div>
<div class="panel-body">
<div class="table-responsive">
<table data-topic-url="/campaigns/clicked" data-topic-id="{{id}}/{{link.id}}" data-sort-column="1" data-sort-order="asc" class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1,1,1,0">
@ -77,19 +77,19 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
<th>
First click
{{#translate}}First click time{{/translate}}
</th>
<th>
Click count
{{#translate}}Click count{{/translate}}
</th>
<th></th>
</tr>

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Complained info</li>
<li class="active">{{#translate}}Complained info{{/translate}}</li>
</ol>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>Complained info</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>{{#translate}}Complained info{{/translate}}</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -20,7 +20,7 @@
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">Subscribers who complained and were unsubscribed:</div>
<div class="panel-heading">{{#translate}}Subscribers who complained and were unsubscribed:{{/translate}}</div>
<div class="panel-body">
<div class="table-responsive">
<table data-topic-url="/campaigns/status" data-topic-id="{{id}}/4" data-sort-column="1" data-sort-order="asc" class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1,0,1,0">
@ -30,19 +30,19 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
<th>
SMTP response
{{#translate}}SMTP response{{/translate}}
</th>
<th>
Complained
{{#translate}}Complain time{{/translate}}
</th>
<th></th>
</tr>

View file

@ -1,16 +1,16 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li class="active">Create RSS Campaign</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
<li class="active">{{#translate}}Create RSS Campaign{{/translate}}</li>
</ol>
<h2>Create RSS Campaign</h2>
<h2>{{#translate}}Create RSS Campaign{{/translate}}</h2>
<hr>
<div class="panel panel-default">
<div class="panel-body">
RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message.
{{#translate}}RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message.{{/translate}}
</div>
</div>
@ -19,28 +19,28 @@
<input type="hidden" name="type" value="rss">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Campaign Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Campaign Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="list" class="col-sm-2 control-label">List</label>
<label for="list" class="col-sm-2 control-label">{{#translate}}List{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="list" name="list" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each listItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} subscribers</span>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} {{#translate}}subscribers{{/translate}}</span>
</option>
{{#if segments}}
@ -58,33 +58,33 @@
</div>
<div class="form-group">
<label for="source-url" class="col-sm-2 control-label">RSS Feed Url</label>
<label for="source-url" class="col-sm-2 control-label">{{#translate}}RSS Feed Url{{/translate}}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="source-url" id="source-url" value="{{sourceUrl}}" placeholder="http://example.com/rss.php" required>
<span class="help-block">New entries from this RSS URL are sent out to list subscribers as email messages</span>
<span class="help-block">{{#translate}}New entries from this RSS URL are sent out to list subscribers as email messages{{/translate}}</span>
</div>
</div>
<hr />
<div class="form-group">
<label for="from" class="col-sm-2 control-label">Email "from name"</label>
<label for="from" class="col-sm-2 control-label">{{#translate}}Email "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="{{#translate}}This is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "from" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "from" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="This is the address people will send replies to" required>
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="{{#translate}}This is the address people will send replies to{{/translate}}" required>
</div>
</div>
<div class="col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> Disable clicked/opened tracking
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> {{#translate}}Disable clicked/opened tracking{{/translate}}
</label>
</div>
</div>
@ -93,7 +93,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create RSS Campaign</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create RSS Campaign{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,10 +1,10 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li class="active">Create Triggered Campaign</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
<li class="active">{{#translate}}Create Triggered Campaign{{/translate}}</li>
</ol>
<h2>Create Triggered Campaign</h2>
<h2>{{#translate}}Create Triggered Campaign{{/translate}}</h2>
<hr>
@ -13,28 +13,28 @@
<input type="hidden" name="type" value="triggered">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Campaign Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Campaign Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="list" class="col-sm-2 control-label">List</label>
<label for="list" class="col-sm-2 control-label">{{#translate}}List{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="list" name="list" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each listItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} subscribers</span>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} {{#translate}}subscribers{{/translate}}</span>
</option>
{{#if segments}}
@ -52,7 +52,7 @@
</div>
<div class="form-group">
<label for="template" class="col-sm-2 control-label">Template</label>
<label for="template" class="col-sm-2 control-label">{{#translate}}Template{{/translate}}</label>
<div class="col-sm-10">
<p class="form-control-static">
@ -60,21 +60,21 @@
</p>
<div>
<select class="form-control" id="template" name="template">
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each templateItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}}
</option>
{{/each}}
</select>
<span class="help-block">Selecting a template creates a campaign specific copy from it</span>
<span class="help-block">{{#translate}}Selecting a template creates a campaign specific copy from it{{/translate}}</span>
</div>
<p class="form-control-static">
Or alternatively use an URL as the message content source:
{{#translate}}Or alternatively use an URL as the message content source:{{/translate}}
</p>
<div>
<input type="url" class="form-control" name="source-url" id="source-url" value="{{sourceUrl}}" placeholder="http://example.com/message-render.php">
<span class="help-block">If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself</span>
<span class="help-block">{{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}</span>
</div>
</div>
@ -83,28 +83,28 @@
<hr />
<div class="form-group">
<label for="from" class="col-sm-2 control-label">Email "from name"</label>
<label for="from" class="col-sm-2 control-label">{{#translate}}Email "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="{{#translate}}This is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "from" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "from" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="This is the address people will send replies to" required>
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="{{#translate}}This is the address people will send replies to{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="subject" class="col-sm-2 control-label">Email "subject line"</label>
<label for="subject" class="col-sm-2 control-label">{{#translate}}Email "subject line"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="Keep it relevant and non-spammy" required>
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="{{#translate}}Keep it relevant and non-spammy{{/translate}}" required>
</div>
</div>
<div class="col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> Disable clicked/opened tracking
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> {{#translate}}Disable clicked/opened tracking{{/translate}}
</label>
</div>
</div>
@ -113,7 +113,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create Campaign</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Campaign{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,10 +1,10 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li class="active">Create Campaign</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
<li class="active">{{#translate}}Create Campaign{{/translate}}</li>
</ol>
<h2>Create Campaign</h2>
<h2>{{#translate}}Create Campaign{{/translate}}</h2>
<hr>
@ -13,28 +13,28 @@
<input type="hidden" name="type" value="normal">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Campaign Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Campaign Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="list" class="col-sm-2 control-label">List</label>
<label for="list" class="col-sm-2 control-label">{{#translate}}List{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="list" name="list" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each listItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} subscribers</span>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} {{#translate}}subscribers{{/translate}}</span>
</option>
{{#if segments}}
@ -52,29 +52,29 @@
</div>
<div class="form-group">
<label for="template" class="col-sm-2 control-label">Template</label>
<label for="template" class="col-sm-2 control-label">{{#translate}}Template{{/translate}}</label>
<div class="col-sm-10">
<p class="form-control-static">
Select a template:
{{#translate}}Select a template:{{/translate}}
</p>
<div>
<select class="form-control" id="template" name="template">
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each templateItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}}
</option>
{{/each}}
</select>
<span class="help-block">Selecting a template creates a campaign specific copy from it</span>
<span class="help-block">{{#translate}}Selecting a template creates a campaign specific copy from it{{/translate}}</span>
</div>
<p class="form-control-static">
Or alternatively use an URL as the message content source:
{{#translate}}Or alternatively use an URL as the message content source:{{/translate}}
</p>
<div>
<input type="url" class="form-control" name="source-url" id="source-url" value="{{sourceUrl}}" placeholder="http://example.com/message-render.php">
<span class="help-block">If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself</span>
<span class="help-block">{{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}</span>
</div>
</div>
@ -83,34 +83,34 @@
<hr />
<div class="form-group">
<label for="from" class="col-sm-2 control-label">Email "from name"</label>
<label for="from" class="col-sm-2 control-label">{{#translate}}Email "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="{{#translate}}This is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "from" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "from" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="This is the address people will send replies to unless reply-to address is set" required>
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="{{#translate}}This is the address people will send replies to unless reply-to address is set{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "reply-to" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "reply-to" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="reply-to" id="reply-to" value="{{replyTo}}" placeholder="If set, this is the address people will send replies to">
<input type="email" class="form-control" name="reply-to" id="reply-to" value="{{replyTo}}" placeholder="{{#translate}}If set, this is the address people will send replies to{{/translate}}">
</div>
</div>
<div class="form-group">
<label for="subject" class="col-sm-2 control-label">Email "subject line"</label>
<label for="subject" class="col-sm-2 control-label">{{#translate}}Email "subject line"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="Keep it relevant and non-spammy" required>
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="{{#translate}}Keep it relevant and non-spammy{{/translate}}" required>
</div>
</div>
<div class="col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> Disable clicked/opened tracking
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> {{#translate}}Disable clicked/opened tracking{{/translate}}
</label>
</div>
</div>
@ -119,7 +119,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create Campaign</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Campaign{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Delivered info</li>
<li class="active">{{#translate}}Delivered info{{/translate}}</li>
</ol>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>Delivered info</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>{{#translate}}Delivered info{{/translate}}</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -20,7 +20,7 @@
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">Subscribers who received the message and did not bounce/unsubscribe:</div>
<div class="panel-heading">{{#translate}}Subscribers who received the message and did not bounce/unsubscribe:{{/translate}}</div>
<div class="panel-body">
<div class="table-responsive">
<table data-topic-url="/campaigns/status" data-topic-id="{{id}}/1" data-sort-column="1" data-sort-order="asc" class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1,0,1,0">
@ -30,19 +30,19 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
<th>
SMTP response
{{#translate}}SMTP response{{/translate}}
</th>
<th>
Delivered
{{#translate}}Delivery time{{/translate}}
</th>
<th></th>
</tr>

View file

@ -1,20 +1,20 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Edit RSS Campaign</li>
<li class="active">{{#translate}}Edit RSS Campaign{{/translate}}</li>
</ol>
<h2>Edit RSS Campaign <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2>{{#translate}}Edit RSS Campaign{{/translate}} <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
<div class="panel panel-default">
<div class="panel-body">
RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message.
{{#translate}}RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message.{{/translate}}
</div>
</div>
@ -29,32 +29,32 @@
<fieldset>
<legend>
General Settings
{{#translate}}General Settings{{/translate}}
</legend>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Campaign Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Campaign Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="list" class="col-sm-2 control-label">List</label>
<label for="list" class="col-sm-2 control-label">{{#translate}}List{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="list" name="list" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each listItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} subscribers</span>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} {{#translate}}subscribers{{/translate}}</span>
</option>
{{#if segments}}
@ -74,10 +74,10 @@
{{> merge_tag_reference}}
<div class="form-group">
<label for="template" class="col-sm-2 control-label">RSS Feed Url</label>
<label for="template" class="col-sm-2 control-label">{{#translate}}RSS Feed Url{{/translate}}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="source-url" id="source-url" value="{{sourceUrl}}" placeholder="http://example.com/rss.php" required>
<span class="help-block">New entries from this RSS URL are sent out to list subscribers as email messages</span>
<span class="help-block">{{#translate}}New entries from this RSS URL are sent out to list subscribers as email messages{{/translate}}</span>
</div>
</div>
@ -89,29 +89,29 @@
<div class="form-group" style="margin-top: -15px;">
<div class="col-sm-offset-2 col-sm-10">
<span class="help-block">Use special merge tag <code>[RSS_ENTRY]</code> to mark the position for the RSS post content. Additionally you can use any valid merge tag as well.</span>
<span class="help-block">{{#translate}}Use special merge tag [RSS_ENTRY] to mark the position for the RSS post content. Additionally you can use any valid merge tag as well.{{/translate}}</span>
</div>
</div>
<hr />
<div class="form-group">
<label for="from" class="col-sm-2 control-label">Email "from name"</label>
<label for="from" class="col-sm-2 control-label">{{#translate}}Email "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="{{#translate}}This is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "from" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "from" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="This is the address people will send replies to" required>
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="{{#translate}}This is the address people will send replies to{{/translate}}" required>
</div>
</div>
<div class="col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> Disable clicked/opened tracking
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> {{#translate}}Disable clicked/opened tracking{{/translate}}
</label>
</div>
</div>
@ -123,9 +123,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="campaigns-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Campaign</button>
<button type="submit" form="campaigns-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Campaign{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Edit Triggered Campaign</li>
<li class="active">{{#translate}}Edit Triggered Campaign{{/translate}}</li>
</ol>
<h2>Edit Triggered Campaign <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2>{{#translate}}Edit Triggered Campaign{{/translate}} <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -26,8 +26,8 @@
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{#if showGeneral}}active{{/if}}"><a href="#general" aria-controls="general" role="tab" data-toggle="tab">General</a></li>
<li role="presentation" class="{{#if showTemplate}}active{{/if}}"><a href="#template" aria-controls="template" role="tab" data-toggle="tab">Template</a></li>
<li role="presentation" class="{{#if showGeneral}}active{{/if}}"><a href="#general" aria-controls="general" role="tab" data-toggle="tab">{{#translate}}General{{/translate}}</a></li>
<li role="presentation" class="{{#if showTemplate}}active{{/if}}"><a href="#template" aria-controls="template" role="tab" data-toggle="tab">{{#translate}}Template{{/translate}}</a></li>
</ul>
<div class="tab-content">
@ -37,32 +37,32 @@
<fieldset>
<legend>
General Settings
{{#translate}}General Settings{{/translate}}
</legend>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Campaign Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Campaign Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="list" class="col-sm-2 control-label">List</label>
<label for="list" class="col-sm-2 control-label">{{#translate}}List{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="list" name="list" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each listItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} subscribers</span>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} {{#translate}}subscribers{{/translate}}</span>
</option>
{{#if segments}}
@ -82,28 +82,28 @@
<hr />
<div class="form-group">
<label for="from" class="col-sm-2 control-label">Email "from name"</label>
<label for="from" class="col-sm-2 control-label">{{#translate}}Email "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="T{{#translate}}his is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "from" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "from" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="This is the address people will send replies to" required>
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="{{#translate}}This is the address people will send replies to{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="subject" class="col-sm-2 control-label">Email "subject line"</label>
<label for="subject" class="col-sm-2 control-label">{{#translate}}Email "subject line"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="Keep it relevant and non-spammy" required>
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="{{#translate}}Keep it relevant and non-spammy{{/translate}}" required>
</div>
</div>
<div class="col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> Disable clicked/opened tracking
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> {{#translate}}Disable clicked/opened tracking{{/translate}}
</label>
</div>
</div>
@ -116,15 +116,15 @@
<fieldset>
<legend>
Template Settings
{{#translate}}Template Settings{{/translate}}
</legend>
{{#if sourceUrl}}
<div class="form-group">
<label for="source-url" class="col-sm-2 control-label">Template URL</label>
<label for="source-url" class="col-sm-2 control-label">{{#translate}}Template URL{{/translate}}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="source-url" id="source-url" value="{{sourceUrl}}" placeholder="http://example.com/message-render.php">
<span class="help-block">If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself</span>
<span class="help-block">{{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}</span>
</div>
</div>
{{else}}
@ -150,9 +150,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="campaigns-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Campaign</button>
<button type="submit" form="campaigns-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Campaign{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Edit Campaign</li>
<li class="active">{{#translate}}Edit Campaign{{/translate}}</li>
</ol>
<h2>Edit Campaign <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2>{{#translate}}Edit Campaign{{/translate}} <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -37,9 +37,9 @@
<div>
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{#if showGeneral}}active{{/if}}"><a href="#general" aria-controls="general" role="tab" data-toggle="tab">General</a></li>
<li role="presentation" class="{{#if showTemplate}}active{{/if}}"><a href="#template" aria-controls="template" role="tab" data-toggle="tab">Template</a></li>
<li role="presentation" class="{{#if showAttachments}}active{{/if}}"><a href="#attachments" aria-controls="attachments" role="tab" data-toggle="tab">Attachments{{#if attachments}} <span class="badge">{{attachments.length}}</span>{{/if}}</a></li>
<li role="presentation" class="{{#if showGeneral}}active{{/if}}"><a href="#general" aria-controls="general" role="tab" data-toggle="tab">{{#translate}}General{{/translate}}</a></li>
<li role="presentation" class="{{#if showTemplate}}active{{/if}}"><a href="#template" aria-controls="template" role="tab" data-toggle="tab">{{#translate}}Template{{/translate}}</a></li>
<li role="presentation" class="{{#if showAttachments}}active{{/if}}"><a href="#attachments" aria-controls="attachments" role="tab" data-toggle="tab">{{#translate}}Attachments{{/translate}}{{#if attachments}} <span class="badge">{{attachments.length}}</span>{{/if}}</a></li>
</ul>
<div class="tab-content">
@ -49,32 +49,32 @@
<fieldset>
<legend>
General Settings
{{#translate}}General Settings{{/translate}}
</legend>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Campaign Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Campaign Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="list" class="col-sm-2 control-label">List</label>
<label for="list" class="col-sm-2 control-label">{{#translate}}List{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="list" name="list" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each listItems}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} subscribers</span>
{{name}} <span class="text-muted"> &mdash; {{subscribers}} {{#translate}}subscribers{{/translate}}</span>
</option>
{{#if segments}}
@ -94,34 +94,34 @@
<hr />
<div class="form-group">
<label for="from" class="col-sm-2 control-label">Email "from name"</label>
<label for="from" class="col-sm-2 control-label">{{#translate}}Email "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="from" id="from" value="{{from}}" placeholder="{{#translate}}This is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="address" class="col-sm-2 control-label">Email "from" address</label>
<label for="address" class="col-sm-2 control-label">{{#translate}}Email "from" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="This is the address people will send replies to unless reply-to address is set" required>
<input type="email" class="form-control" name="address" id="address" value="{{address}}" placeholder="{{#translate}}This is the address people will send replies to unless reply-to address is set{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="reply-to" class="col-sm-2 control-label">Email "reply-to" address</label>
<label for="reply-to" class="col-sm-2 control-label">{{#translate}}Email "reply-to" address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="reply-to" id="reply-to" value="{{replyTo}}" placeholder="If set, this is the address people will send replies to">
<input type="email" class="form-control" name="reply-to" id="reply-to" value="{{replyTo}}" placeholder="{{#translate}}If set, this is the address people will send replies to{{/translate}}">
</div>
</div>
<div class="form-group">
<label for="subject" class="col-sm-2 control-label">Email "subject line"</label>
<label for="subject" class="col-sm-2 control-label">{{#translate}}Email "subject line"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="Keep it relevant and non-spammy" required>
<input type="text" class="form-control" name="subject" id="subject" value="{{subject}}" placeholder="{{#translate}}Keep it relevant and non-spammy{{/translate}}" required>
</div>
</div>
<div class="col-sm-offset-2">
<div class="checkbox">
<label>
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> Disable clicked/opened tracking
<input type="checkbox" name="tracking-disabled" value="1" {{#if trackingDisabled}} checked {{/if}}> {{#translate}}Disable clicked/opened tracking{{/translate}}
</label>
</div>
</div>
@ -132,15 +132,15 @@
<fieldset>
<legend>
Template Settings
{{#translate}}Template Settings{{/translate}}
</legend>
{{#if sourceUrl}}
<div class="form-group">
<label for="source-url" class="col-sm-2 control-label">Template URL</label>
<label for="source-url" class="col-sm-2 control-label">{{#translate}}Template URL{{/translate}}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="source-url" id="source-url" value="{{sourceUrl}}" placeholder="http://example.com/message-render.php">
<span class="help-block">If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself</span>
<span class="help-block">{{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}</span>
</div>
</div>
{{else}}
@ -166,7 +166,7 @@
<fieldset>
<legend>
Attachments
{{#translate}}Attachments{{/translate}}
</legend>
<div class="table-responsive">
@ -176,10 +176,10 @@
#
</th>
<th>
File
{{#translate}}File{{/translate}}
</th>
<th class="col-md-1">
Size
{{#translate}}Size{{/translate}}
</th>
<th class="col-md-1">
&nbsp;
@ -206,7 +206,7 @@
{{else}}
<tr>
<td colspan="4">
No data available in table
{{#translate}}No data available in table{{/translate}}
</td>
</tr>
{{/if}}
@ -214,7 +214,7 @@
</table>
</div>
<div class="pull-right">
<a class="btn btn-info btn-sm" href="/campaigns/attachment/{{id}}" role="button"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Add Attachment</a>
<a class="btn btn-info btn-sm" href="/campaigns/attachment/{{id}}" role="button"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> {{#translate}}Add Attachment{{/translate}}</a>
</div>
</fieldset>
</div>
@ -226,9 +226,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="campaigns-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Campaign</button>
<button type="submit" form="campaigns-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Campaign{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</div>

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Opened info</li>
<li class="active">{{#translate}}Opened info{{/translate}}</li>
</ol>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>Opened info</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>{{#translate}}Opened info{{/translate}}</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -20,7 +20,7 @@
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">Subscribers who opened this message:</div>
<div class="panel-heading">{{#translate}}Subscribers who opened this message:{{/translate}}</div>
<div class="panel-body">
<div class="table-responsive">
<table data-topic-url="/campaigns/clicked" data-topic-id="{{id}}/-1" data-sort-column="1" data-sort-order="asc" class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1,1,1,0">
@ -30,19 +30,19 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
<th>
First open
{{#translate}}First open{{/translate}}
</th>
<th>
Opened count
{{#translate}}Opened count{{/translate}}
</th>
<th></th>
</tr>

View file

@ -1,14 +1,14 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li class="active">Unsubscribed info</li>
<li class="active">{{#translate}}Unsubscribed info{{/translate}}</li>
</ol>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>Unsubscribed info</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}} <small>{{#translate}}Unsubscribed info{{/translate}}</small> <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}?tab=overview" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -20,7 +20,7 @@
<div class="panel panel-info">
<!-- Default panel contents -->
<div class="panel-heading">Subscribers who unsubscribed:</div>
<div class="panel-heading">{{#translate}}Subscribers who unsubscribed:{{/translate}}</div>
<div class="panel-body">
<div class="table-responsive">
<table data-topic-url="/campaigns/status" data-topic-id="{{id}}/2" data-sort-column="1" data-sort-order="asc" class="table table-bordered table-hover data-table-ajax display nowrap" width="100%" data-row-sort="0,1,1,1,0,1,0">
@ -30,19 +30,19 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
<th>
SMTP response
{{#translate}}SMTP response{{/translate}}
</th>
<th>
Unsubscribed
{{#translate}}Unsubscribed{{/translate}}
</th>
<th></th>
</tr>

View file

@ -1,15 +1,15 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
<li><a href="/campaigns/view/{{id}}">{{name}}</a></li>
<li><a href="/campaigns/edit/{{id}}?tab=attachments">Edit Campaign</a></li>
<li class="active">Add Attachment</li>
<li><a href="/campaigns/edit/{{id}}?tab=attachments">{{#translate}}Edit Campaign{{/translate}}</a></li>
<li class="active">{{#translate}}Add Attachment{{/translate}}</li>
</ol>
<h2>Edit Campaign <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View campaign</a></h2>
<h2>{{#translate}}Edit Campaign{{/translate}} <a class="btn btn-default btn-xs" href="/campaigns/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View campaign{{/translate}}</a></h2>
<hr>
@ -25,7 +25,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-cloud-upload"></i> Upload</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-cloud-upload"></i> {{#translate}}Upload{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,6 +1,6 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/campaigns">Campaigns</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/campaigns">{{#translate}}Campaigns{{/translate}}</a></li>
{{#if parent}}
<li><a href="/campaigns/view/{{parent.id}}">{{parent.name}}</a></li>
{{/if}}
@ -8,7 +8,7 @@
</ol>
<div class="pull-right">
<a class="btn btn-primary" href="/campaigns/edit/{{id}}" role="button"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Edit Campaign</a>
<a class="btn btn-primary" href="/campaigns/edit/{{id}}" role="button"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> {{#translate}}Edit Campaign{{/translate}}</a>
</div>
<h2><span class="glyphicon glyphicon-inbox" aria-hidden="true"></span> {{name}}</h2>
@ -21,9 +21,9 @@
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{#if showOverview}}active{{/if}}"><a href="#overview" aria-controls="overview" role="tab" data-toggle="tab">Overview</a></li>
<li role="presentation" class="{{#if showOverview}}active{{/if}}"><a href="#overview" aria-controls="overview" role="tab" data-toggle="tab">{{#translate}}Overview{{/translate}}</a></li>
{{#if links}}
<li role="presentation" class="{{#if showLinks}}active{{/if}}"><a href="#links" aria-controls="links" role="tab" data-toggle="tab">Links</a></li>
<li role="presentation" class="{{#if showLinks}}active{{/if}}"><a href="#links" aria-controls="links" role="tab" data-toggle="tab">{{#translate}}Links{{/translate}}</a></li>
{{/if}}
</ul>
@ -34,7 +34,7 @@
<dl class="dl-horizontal">
{{#if list}}
<dt>List</dt>
<dt>{{#translate}}List{{/translate}}</dt>
<dd>
{{#if segment}}
<a href="/lists/view/{{list.id}}?segment={{segment.id}}">{{list.name}}: {{segment.name}}</a>
@ -54,43 +54,43 @@
{{/if}}
{{#if isRss}}
<dt>Feed URL</dt>
<dt>{{#translate}}Feed URL{{/translate}}</dt>
<dd><a href="{{sourceUrl}}">{{sourceUrl}}</a></dd>
<dt>Last check</dt>
<dt>{{#translate}}Last check{{/translate}}</dt>
<dd>
{{#if lastCheck}}<span class="datestring" data-date="{{lastCheck}}" title="{{lastCheck}}">{{lastCheck}}</span>{{else}}
Not yet checked{{/if}}
{{#unless isActive}}<span class="text-muted">(activate campaign to start checking feed for new messages)</span>{{/unless}}
{{#translate}}Not yet checked{{/translate}}{{/if}}
{{#unless isActive}}<span class="text-muted">({{#translate}}activate campaign to start checking feed for new messages{{/translate}})</span>{{/unless}}
</dd>
{{#if checkStatus}}
<dt>RSS status</dt>
<dt>{{#translate}}RSS status{{/translate}}</dt>
<dd>{{checkStatus}}</dd>
{{/if}}
{{/if}}
{{#if from}}
<dt>Email "from name"</dt>
<dt>{{#translate}}Email "from name"{{/translate}}</dt>
<dd>{{from}}</dd>
{{/if}}
{{#if address}}
<dt>Email "from" address</dt>
<dt>{{#translate}}Email "from" address{{/translate}}</dt>
<dd>{{address}}</dd>
{{/if}}
{{#if replyTo}}
<dt>Email "reply-to" address</dt>
<dt>{{#translate}}Email "reply-to" address{{/translate}}</dt>
<dd>{{replyTo}}</dd>
{{/if}}
{{#if subject}}
<dt>Email "subject line"</dt>
<dt>{{#translate}}Email "subject line"{{/translate}}</dt>
<dd>{{subject}}</dd>
{{/if}}
{{#unless isRss}}
<dt>Preview campaign as</dt>
<dt>{{#translate}}Preview campaign as{{/translate}}</dt>
<dd>
<form method="post" action="/campaigns/preview/{{id}}" class="form-inline">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
@ -104,26 +104,26 @@
{{/each}}
{{#if testUsers}}
<optgroup label="Actions">
<option value="_create">Add new test user ...</option>
<option value="_create">{{#translate}}Add new test user{{/translate}}</option>
</optgroup>
{{else}}
<option value="_create">No test users yet, create one here ...</option>
<option value="_create">{{#translate}}No test users yet, create one here{{/translate}}</option>
{{/if}}
</select>
</div>
<button type="submit" class="btn btn-default">Go</button>
<button type="submit" class="btn btn-default">{{#translate}}Go{{/translate}}</button>
</form>
</dd>
{{#unless isIdling}}
<dt>Delivered <a href="/campaigns/status/{{id}}/delivered" title="List subscribers who received this message"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dt>{{#translate}}Delivered{{/translate}} <a href="/campaigns/status/{{id}}/delivered" title="{{#translate}}List subscribers who received this message{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dd>{{delivered}}</dd>
</dl>
<hr />
<dl class="dl-horizontal">
<dt>Bounced <a href="/campaigns/status/{{id}}/bounced" title="List subscribers who bounced"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dt>{{#translate}}Bounced{{/translate}} <a href="/campaigns/status/{{id}}/bounced" title="{{#translate}}List subscribers who bounced{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="{{bounceRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 6em; width: {{bounceRate}}%;">
@ -132,7 +132,7 @@
</div>
</dd>
<dt>Complaints <a href="/campaigns/status/{{id}}/complained" title="List subscribers who complained for this message"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dt>{{#translate}}Complaints{{/translate}} <a href="/campaigns/status/{{id}}/complained" title="{{#translate}}List subscribers who complained for this message{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="{{complaintRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 6em; width: {{complaintRate}}%;">
@ -141,7 +141,7 @@
</div>
</dd>
<dt>Unsubscribed <a href="/campaigns/status/{{id}}/unsubscribed" title="List subscribers who unsubscribed after this message"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dt>{{#translate}}Unsubscribed{{/translate}} <a href="/campaigns/status/{{id}}/unsubscribed" title="{{#translate}}List subscribers who unsubscribed after this message{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="{{unsubscribeRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 6em; width: {{unsubscribeRate}}%;">
@ -152,7 +152,7 @@
{{#unless trackingDisabled}}
<dt>Opened <a href="/campaigns/opened/{{id}}" title="List subscribers who opened this message"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dt>{{#translate}}Opened{{/translate}} <a href="/campaigns/opened/{{id}}" title="{{#translate}}List subscribers who opened this message{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{openRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 6em; width: {{openRate}}%;">
@ -161,7 +161,7 @@
</div>
</dd>
<dt>Clicked <a href="/campaigns/clicked/{{id}}/all" title="List subscribers who clicked on a link"> <span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dt>{{#translate}}Clicked{{/translate}} <a href="/campaigns/clicked/{{id}}/all" title="{{#translate}}List subscribers who clicked on a link{{/translate}}"> <span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a></dt>
<dd>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{clicksRate}}" aria-valuemin="0" aria-valuemax="100" style="min-width: 6em; width: {{clicksRate}}%;">
@ -180,34 +180,34 @@
<div class="panel panel-default">
<div class="panel-body">
{{#if isIdling}}
<form class="form-inline confirm-submit" data-confirm-message="Are you sure? This action would start sending messages to the selected list" method="post" action="/campaigns/send">
<form class="form-inline confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would start sending messages to the selected list{{/translate}}" method="post" action="/campaigns/send">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<div class="pull-right">
<div class="form-group">
<p class="form-control-static">Delay sending</p>
<p class="form-control-static">{{#translate}}Delay sending{{/translate}}</p>
</div>
<div class="form-group">
<div class="input-group">
<input type="number" class="form-control" name="delay-hours" id="delay-hours" placeholder="0">
<div class="input-group-addon"> hours</div>
<div class="input-group-addon"> {{#translate}}hours{{/translate}}</div>
</div>
</div>
<div class="form-group">
<div class="input-group">
<input type="number" class="form-control" name="delay-minutes" id="delay-minutes" placeholder="0">
<div class="input-group-addon"> minutes</div>
<div class="input-group-addon"> {{#translate}}minutes{{/translate}}</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> Send to
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> {{#translate}}Send to subscribers:{{/translate}}
{{#if segment}}
{{segment.subscribers}}
{{else}}
{{list.subscribers}}
{{/if}} subscribers</button>
{{/if}}</button>
</form>
{{/if}}
@ -216,45 +216,45 @@
<div class="page-refresh" data-interval="20"></div>
{{#if isScheduled}}
<div class="pull-right">
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would reset scheduling" method="post" action="/campaigns/reset">
<form class="form-horizontal confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would reset scheduling{{/translate}}" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Cancel
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> {{#translate}}Cancel{{/translate}}
</button>
</form>
</div>
<h4>Sending scheduled <span class="datestring text-info" data-date="{{scheduled}}" title="{{scheduled}}">{{scheduled}}</span></h4>
<h4>{{#translate}}Sending scheduled{{/translate}} <span class="datestring text-info" data-date="{{scheduled}}" title="{{scheduled}}">{{scheduled}}</span></h4>
{{else}}
<div class="pull-right">
<form class="form-horizontal" method="post" action="/campaigns/pause">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> {{#translate}}Pause{{/translate}}
</button>
</form>
</div>
<h4><span class="glyphicon glyphicon-refresh spinning"></span> Sending…</h4>
<h4><span class="glyphicon glyphicon-refresh spinning"></span> {{#translate}}Sending{{/translate}}…</h4>
{{/if}}
{{/if}}
{{#if isPaused}}
<div class="pull-right">
<form id="resume-sending" class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/resume">
<form id="resume-sending" class="form-horizontal confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would resume sending messages to the selected list{{/translate}}" method="post" action="/campaigns/resume">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<form id="reset-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would reset all stats about current progress" method="post" action="/campaigns/reset">
<form id="reset-sending" class="confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would reset all stats about current progress{{/translate}}" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="resume-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Resume
<button type="submit" form="resume-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> {{#translate}}Resume{{/translate}}
</button>
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Reset
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> {{#translate}}Reset{{/translate}}
</button>
</div>
<h4>Sending paused</h4>
@ -262,24 +262,24 @@
{{#if isFinished}}
<div class="pull-right">
<form id="continue-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/send">
<form id="continue-sending" class="confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would resume sending messages to the selected list{{/translate}}" method="post" action="/campaigns/send">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<form id="reset-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would reset all stats about current progress" method="post" action="/campaigns/reset">
<form id="reset-sending" class="confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would reset all stats about current progress{{/translate}}" method="post" action="/campaigns/reset">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="continue-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Continue
<button type="submit" form="continue-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> {{#translate}}Continue{{/translate}}
</button>
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Reset
<button type="submit" form="reset-sending" class="btn btn-danger"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> {{#translate}}Reset{{/translate}}
</button>
</div>
<h4>All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers</h4>
<h4>{{#translate}}All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers{{/translate}}</h4>
{{/if}}
</div>
@ -292,28 +292,28 @@
<div class="panel-body">
{{#if isActive}}
<div class="pull-right">
<form id="inactivate-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would pause sending new entries in RSS feed as email messages to the selected list" method="post" action="/campaigns/inactivate">
<form id="inactivate-sending" class="confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would pause sending new entries in RSS feed as email messages to the selected list{{/translate}}" method="post" action="/campaigns/inactivate">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="inactivate-sending" class="btn btn-warning"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause
<button type="submit" form="inactivate-sending" class="btn btn-warning"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> {{#translate}}Pause{{/translate}}
</button>
</div>
Campaign status: <span class="label label-primary">ACTIVE</span>
{{#translate}}Campaign status:{{/translate}} <span class="label label-primary">{{#translate}}ACTIVE{{/translate}}</span>
{{else}}
<div class="pull-right">
<form id="activate-sending" class="confirm-submit" data-confirm-message="Are you sure? This action would start sending new entries in RSS feed as email messages to the selected list" method="post" action="/campaigns/activate">
<form id="activate-sending" class="confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action would start sending new entries in RSS feed as email messages to the selected list{{/translate}}" method="post" action="/campaigns/activate">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
</form>
<button type="submit" form="activate-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Activate
<button type="submit" form="activate-sending" class="btn btn-info"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> {{#translate}}Activate{{/translate}}
</button>
</div>
Campaign status: <span class="label label-default">INACTIVE</span>
{{#translate}}Campaign status:{{/translate}} <span class="label label-default">{{#translate}}INACTIVE{{/translate}}</span>
{{/if}}
</div>
</div>
@ -322,7 +322,7 @@
{{#if isTriggered}}
<div class="panel panel-default">
<div class="panel-body">
This is a <a href="/triggers">triggered</a> campaign. Messages are only sent to subscribers that hit some trigger that invokes this campaign
{{#translate}}This is a triggered campaign. Messages are only sent to subscribers that hit some trigger that invokes this campaign{{/translate}} (<a href="/triggers">{{#translate}}see more{{/translate}}</a>)
</div>
</div>
{{/if}}
@ -341,16 +341,16 @@
#
</th>
<th>
URL
{{#translate}}URL{{/translate}}
</th>
<th class="col-md-1">
Clicks
{{#translate}}Clicks{{/translate}}
</th>
<th class="col-md-1">
% of clicks
{{#translate}}% of clicks{{/translate}}
</th>
<th class="col-md-1">
% of messages
{{#translate}}% of messages{{/translate}}
</th>
</thead>
<tbody>
@ -365,7 +365,7 @@
</td>
<td>
<div class="pull-right">
<a href="/campaigns/clicked/{{../id}}/{{id}}" title="List subscribers who clicked this link"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a>
<a href="/campaigns/clicked/{{../id}}/{{id}}" title="{{#translate}}List subscribers who clicked this link{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a>
</div>
{{clicks}}
</td>
@ -380,7 +380,7 @@
{{else}}
<tr>
<td colspan="5">
No data available in table
{{#translate}}No data available in table{{/translate}}
</td>
</tr>
{{/if}}
@ -389,11 +389,11 @@
<tr>
<th></th>
<th>
Aggregated clicks
{{#translate}}Aggregated clicks{{/translate}}
</th>
<th>
<div class="pull-right">
<a href="/campaigns/clicked/{{id}}/all" title="List subscribers who clicked on a link"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a>
<a href="/campaigns/clicked/{{id}}/all" title="{{#translate}}List subscribers who clicked on a link{{/translate}}"><span class="glyphicon glyphicon-zoom-in" aria-hidden="true"></span></a>
</div>
{{clicks}}
</th>
@ -408,7 +408,7 @@
</table>
<p class="text-muted">
Clicks are counted as unique subscribers that clicked on a specific link or on any link (in aggregated view)
{{#translate}}Clicks are counted as unique subscribers that clicked on a specific link or on any link (in aggregated view){{/translate}}
</p>
</div>
@ -421,7 +421,7 @@
{{#if isRss}}
<div class="table-responsive">
<div class="well text-info">
If a new entry is found from campaign feed a new subcampaign is created of that entry and it will be listed here
{{#translate}}If a new entry is found from campaign feed a new subcampaign is created of that entry and it will be listed here{{/translate}}
</div>
<table data-topic-url="/campaigns" data-sort-column="4" data-sort-order="desc" class="table table-bordered table-hover data-table-ajax display nowrap" data-topic-args="parent={{id}}" width="100%" data-row-sort="0,1,0,1,1,0">
<thead>
@ -429,16 +429,16 @@
#
</th>
<th>
Name
{{#translate}}Name{{/translate}}
</th>
<th>
Description
{{#translate}}Description{{/translate}}
</th>
<th>
Status
{{#translate}}Status{{/translate}}
</th>
<th>
Created
{{#translate}}Created{{/translate}}
</th>
<th class="col-md-1">
&nbsp;

File diff suppressed because one or more lines are too long

View file

@ -1,10 +1,10 @@
{{{title}}}
Please Confirm Subscription
{{#translate}}Please Confirm Subscription{{/translate}}
===========================
Yes, subscribe me to this list: {{{confirmUrl}}}
{{#translate}}Yes, subscribe me to this list{{/translate}}: {{{confirmUrl}}}
If you received this email by mistake, simply delete it. You won't be subscribed unless you click the confirmation link above.
{{#translate}}If you received this email by mistake, simply delete it. You won't be subscribed unless you click the confirmation link above.{{/translate}}
For questions about this list, please contact:
{{#translate}}For questions about this list, please contact:{{/translate}}
{{{contactAddress}}}

File diff suppressed because one or more lines are too long

View file

@ -1,9 +1,9 @@
{{{title}}}
Change your password
{{#translate}}Change your password{{/translate}}
====================
We have received a password change request for your Mailtrain account ({{{username}}}).
{{#translate}}We have received a password change request for your Mailtrain account:{{/translate}} ({{{username}}}).
Reset password: {{{confirmUrl}}}
{{#translate}}Reset password{{/translate}}: {{{confirmUrl}}}
If you did not ask to change your password, then you can ignore this email and your password will not be changed.
{{#translate}}If you did not ask to change your password, then you can ignore this email and your password will not be changed.{{/translate}}

View file

@ -12,11 +12,11 @@
</div>
<p>
<a href="[LINK_PREFERENCES]" style="color: #666666; text-decoration: none;">Preferences</a>
<a href="[LINK_PREFERENCES]" style="color: #666666; text-decoration: none;">{{#translate}}Preferences{{/translate}}</a>
<span style="color: #444444;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="[LINK_UNSUBSCRIBE]" style="color: #666666; text-decoration: none;">Unsubscribe</a>
<a href="[LINK_UNSUBSCRIBE]" style="color: #666666; text-decoration: none;">{{#translate}}Unsubscribe{{/translate}}</a>
<span style="color: #444444;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="[LINK_BROWSER]" style="color: #666666; text-decoration: none;">View this email in your browser</a>
<a href="[LINK_BROWSER]" style="color: #666666; text-decoration: none;">{{#translate}}View this email in your browser{{/translate}}</a>
</p>
</body>

View file

@ -7,24 +7,24 @@
<body>
<p>Hey [FIRST_NAME/Customer],</p>
<p>{{#translate}}Hey [FIRST_NAME/Customer],{{/translate}}</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est.</p>
<p>Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est. Aenean at mollis ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus
libero lacus a est.</p>
<p>Cheers,
<p>{{#translate}}Cheers,{{/translate}}
<br/> {{defaultSender}}
</p>
<p>
{{defaultPostaddress}}
<br/>
<a href="[LINK_PREFERENCES]" style="color: #666666; text-decoration: none;">Preferences</a>
<a href="[LINK_PREFERENCES]" style="color: #666666; text-decoration: none;">{{#translate}}Preferences{{/translate}}</a>
<span style="color: #444444;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="[LINK_UNSUBSCRIBE]" style="color: #666666; text-decoration: none;">Unsubscribe</a>
<a href="[LINK_UNSUBSCRIBE]" style="color: #666666; text-decoration: none;">{{#translate}}Unsubscribe{{/translate}}</a>
<span style="color: #444444;">&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="[LINK_BROWSER]" style="color: #666666; text-decoration: none;">View this email in your browser</a>
<a href="[LINK_BROWSER]" style="color: #666666; text-decoration: none;">{{#translate}}View this email in your browser{{/translate}}</a>
</p>
</body>

View file

@ -1,14 +1,14 @@
Hey [FIRST_NAME/Customer],
{{#translate}}Hey [FIRST_NAME/Customer],{{/translate}}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est.
Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est. Aenean at mollis ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est.
Cheers,
{{#translate}}Cheers,{{/translate}}
{{defaultSender}}
{{defaultPostaddress}}
Preferences: [LINK_PREFERENCES]
Unsubscribe: [LINK_UNSUBSCRIBE]
View this email in your browser: [LINK_BROWSER]
{{#translate}}Preferences{{/translate}}: [LINK_PREFERENCES]
{{#translate}}Unsubscribe{{/translate}}: [LINK_UNSUBSCRIBE]
{{#translate}}View this email in your browser{{/translate}}: [LINK_BROWSER]

File diff suppressed because one or more lines are too long

View file

@ -1,16 +1,16 @@
{{{title}}}
Subscription Confirmed
{{#translate}}Subscription Confirmed{{/translate}}
======================
Your subscription to our list has been confirmed.
{{#translate}}Your subscription to our list has been confirmed.{{/translate}}
If you want to modify your subscription then you can:
{{#translate}}If you want to modify your subscription then you can:{{/translate}}
manage your preferences: {{preferencesUrl}}
{{#translate}}manage your preferences{{/translate}}: {{preferencesUrl}}
- or -
- {{#translate}}or{{/translate}} -
unsubscribe here: {{unsubscribeUrl}}
{{#translate}}unsubscribe here{{/translate}}: {{unsubscribeUrl}}
For questions about this list, please contact:
{{#translate}}For questions about this list, please contact:{{/translate}}
{{{contactAddress}}}

File diff suppressed because one or more lines are too long

View file

@ -1,12 +1,12 @@
{{{title}}}
You are now unsubscribed
{{#translate}}You are now unsubscribed{{/translate}}
========================
We have removed your email address from our list.
{{#translate}}We have removed your email address from our list.{{/translate}}
If you unsubscribed by mistake, you can re-subscribe at:
{{#translate}}If you unsubscribed by mistake, you can re-subscribe at:{{/translate}}
Subscribe: {{subscribeUrl}}
{{#translate}}Subscribe{{/translate}}: {{subscribeUrl}}
For questions about this list, please contact:
{{#translate}}For questions about this list, please contact:{{/translate}}
{{{contactAddress}}}

View file

@ -8,7 +8,7 @@
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="http://www.iredmail.org/">iRedMail</a></h4> Free, open source mail server solution
<h4 class="media-heading"><a href="http://www.iredmail.org/">iRedMail</a></h4> {{#translate}}Free, open source mail server solution{{/translate}}
</div>
</div>
</div>
@ -22,7 +22,7 @@
</a>
</div>
<div class="media-body">
<h4 class="media-heading"><a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=logo">SendPulse</a></h4> A reliable SMTP server, easy integration, and 12,000 messages a month free
<h4 class="media-heading"><a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=logo">SendPulse</a></h4> {{#translate}}A reliable SMTP server, easy integration, and 12,000 messages a month free{{/translate}}
</div>
</div>
</div>
@ -31,17 +31,17 @@
<div class="row">
<div class="col-md-4">
<h2>List management</h2>
<p>Mailtrain allows you to easily manage even very large lists. Million subscribers? Not a problem. You can add subscribers manually, through the API or import from a CSV file. All lists come with support for custom fields and merge tags as well.</p>
<h2>{{#translate}}List management{{/translate}}</h2>
<p>{{#translate}}Mailtrain allows you to easily manage even very large lists. Million subscribers? Not a problem. You can add subscribers manually, through the API or import from a CSV file. All lists come with support for custom fields and merge tags as well.{{/translate}}</p>
</div>
<div class="col-md-4">
<h2>Custom fields</h2>
<p>Text fields, numbers, drop downs or checkboxes, Mailtrain has them all. Every custom field can be included in the generated newsletters through merge tags.</p>
<h2>{{#translate}}Custom fields{{/translate}}</h2>
<p>{{#translate}}Text fields, numbers, drop downs or checkboxes, Mailtrain has them all. Every custom field can be included in the generated newsletters through merge tags.{{/translate}}</p>
</div>
<div class="col-md-4">
<h2>List segmentation</h2>
<p>Send messages only to list subscribers that match predefined segmentation rules. No need to create separate lists with small differences.</p>
<h2>{{#translate}}List segmentation{{/translate}}</h2>
<p>{{#translate}}Send messages only to list subscribers that match predefined segmentation rules. No need to create separate lists with small differences.{{/translate}}</p>
</div>
</div>
@ -49,11 +49,11 @@
<div class="row">
<div class="col-md-8">
<h3>Donate to author</h3>
<p>If you really like Mailtrain or your business benefits from it financially then I would really appreciate a small donation to keep the Mailtrain development engines running. You can either use Bitcoin or PayPal for donations. My Bitcoin wallet is <code>15Z8ADxhssKUiwP3jbbqJwA21744KMCfTM</code></p>
<h3>{{#translate}}Donate to author{{/translate}}</h3>
<p>{{#translate}}If you really like Mailtrain or your business benefits from it financially then I would really appreciate a small donation to keep the Mailtrain development engines running. You can either use Bitcoin or PayPal for donations. My Bitcoin wallet is <code>15Z8ADxhssKUiwP3jbbqJwA21744KMCfTM</code>{{/translate}}</p>
<p>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=DB26KWR2BQX5W" class="btn btn-info">or donate using PayPal</a>
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&amp;hosted_button_id=DB26KWR2BQX5W" class="btn btn-info">{{#translate}}or donate using PayPal{{/translate}}</a>
</p>
</div>
@ -63,32 +63,31 @@
<div class="row">
<div class="col-md-4">
<h2>RSS Campaigns</h2>
<p>Setup Mailtrain to <a href="https://github.com/andris9/mailtrain/wiki/RSS-Campaigns">track RSS feeds</a> and if a new entry is detected in a feed then Mailtrain auto-generates a new campaign using entry data as message contents and sends it to
selected subscribers.</p>
<h2>{{#translate}}RSS Campaigns{{/translate}}</h2>
<p>{{#translate}}Setup Mailtrain to track RSS feeds and if a new entry is detected in a feed then Mailtrain auto-generates a new campaign using entry data as message contents and sends it to selected subscribers.{{/translate}}</p>
</div>
<div class="col-md-4">
<h2>GPG Encryption</h2>
<p>If a list has a custom field for a GPG Public Key set then subscribers can upload their GPG public key to receive <a href="https://github.com/andris9/mailtrain/wiki/Adding-GPG-encryption-option-to-a-list">encrypted messages</a> from the list.</p>
<h2>{{#translate}}GPG Encryption{{/translate}}</h2>
<p>{{#translate}}If a list has a custom field for a GPG Public Key set then subscribers can upload their GPG public key to receive encrypted messages from the list.{{/translate}}</p>
</div>
<div class="col-md-4">
<h2>Click stats</h2>
<p>After a campaign is sent, check individual <a href="https://github.com/andris9/mailtrain/wiki/Clicks-and-opens-stats">click statistics</a> for every link included in the message.</p>
<h2>{{#translate}}Click stats{{/translate}}</h2>
<p>{{#translate}}After a campaign is sent, check individual click statistics for every link included in the message.{{/translate}}</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<h2>Open source</h2>
<p>Mailtrain is available under <a href="https://spdx.org/licenses/MIT.html#licenseText">MIT</a> license and completely open source.</p>
<h2>{{#translate}}Open source{{/translate}}</h2>
<p>{{#translate}}Mailtrain is available under GPLv3 license and completely open source.{{/translate}}</p>
</div>
<div class="col-md-4">
<h2>Send via any provider</h2>
<p>Mailtrain recommends <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=providerlist">SendPulse</a> even though you can use any provider that supports SMTP protocol to send out your newsletters. Bounce and complaints handling via webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for Postfix and ZoneMTA.</p>
<h2>{{#translate}}Send via any provider{{/translate}}</h2>
<p>{{#translate}}Mailtrain recommends <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=providerlist">SendPulse</a> even though you can use any provider that supports SMTP protocol to send out your newsletters. Bounce and complaints handling via webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for Postfix and ZoneMTA.{{/translate}}</p>
</div>
<div class="col-md-4">
<h2>Trigger based automation</h2>
<p>Define <a href="https://github.com/andris9/mailtrain/wiki/Automation-in-Mailtrain">automation triggers</a> to send specific messages when a user activates the trigger.</p>
<h2>{{#translate}}Trigger based automation{{/translate}}</h2>
<p>{{#translate}}Define automation triggers to send specific messages when a user activates the trigger.{{/translate}}</p>
</div>
</div>

View file

@ -34,7 +34,7 @@
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="sr-only">{{#translate}}Toggle navigation{{/translate}}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@ -52,8 +52,8 @@
<li><a href="{{url}}">{{title}}</a></li>
{{/if}}
{{/each}}
<li><a href="https://github.com/andris9/mailtrain/wiki"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> Wiki</a></li>
<li><a href="https://mailtrain.wordpress.com/"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> Blog</a></li>
<li><a href="https://github.com/andris9/mailtrain/wiki"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> {{#translate}}Wiki{{/translate}}</a></li>
<li><a href="https://mailtrain.wordpress.com/"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> {{#translate}}Blog{{/translate}}</a></li>
</ul>
{{#if user }}
@ -66,22 +66,22 @@
<ul class="dropdown-menu">
<li>
<a href="/users/account">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account
<span class="glyphicon glyphicon-user" aria-hidden="true"></span> {{#translate}}Account{{/translate}}
</a>
</li>
<li>
<a href="/settings">
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span> Settings
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span> {{#translate}}Settings{{/translate}}
</a>
</li>
<li>
<a href="/users/api">
<span class="glyphicon glyphicon-retweet" aria-hidden="true"></span> API
<span class="glyphicon glyphicon-retweet" aria-hidden="true"></span> {{#translate}}API{{/translate}}
</a>
</li>
<li>
<a href="/users/logout">
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> Log out
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> {{#translate}}Log out{{/translate}}
</a>
</li>
</ul>
@ -93,7 +93,7 @@
<ul class="nav navbar-nav navbar-right">
<li>
<a href="/users/login" role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Sign in
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> {{#translate}}Sign in{{/translate}}
</a>
</li>
</ul>
@ -114,11 +114,11 @@
</div>
<h1><img class="img-responsive" src="/mailtrain-header.png"></h1>
<p>Self hosted newsletter app built on top of <a href="http://nodemailer.com">Nodemailer</a></p>
<p>{{#translate}}Self hosted newsletter app built on top of Nodemailer{{/translate}}</p>
<p>
<a class="btn btn-info btn-md" href="https://github.com/andris9/mailtrain" role="button"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> Source on GitHub</a>
<a class="btn btn-info btn-md" href="https://github.com/andris9/mailtrain" role="button"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> {{#translate}}Source on GitHub{{/translate}}</a>
<a class="btn btn-success btn-md" href="http://mailtrain.org/subscription/EysIv8sAx" role="button"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> Subscribe to our newsletter</a>
<a class="btn btn-success btn-md" href="http://mailtrain.org/subscription/EysIv8sAx" role="button"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> {{#translate}}Subscribe to our newsletter{{/translate}}</a>
</p>
<div class="clearfix"></div>
@ -134,7 +134,7 @@
<footer class="footer">
<div class="container">
<p class="text-muted">&copy; 2016 Kreata OÜ <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. Source on <a href="https://github.com/andris9/mailtrain">GitHub</a></p>
<p class="text-muted">&copy; 2016 Kreata OÜ <a href="https://mailtrain.org">Mailtrain.org</a>, <a href="mailto:info@mailtrain.org">info@mailtrain.org</a>. <a href="https://github.com/andris9/mailtrain">{{#translate}}Source on GitHub{{/translate}}</a></p>
</div>
</footer>

View file

@ -1,26 +1,26 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li class="active">Create List</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li class="active">{{#translate}}Create List{{/translate}}</li>
</ol>
<h2>Create List</h2>
<h2>{{#translate}}Create List{{/translate}}</h2>
<hr>
<form class="form-horizontal" method="post" action="/lists/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="List Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}List Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
@ -28,7 +28,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Create List</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create List{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,11 +1,11 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{id}}">{{name}}</a></li>
<li class="active">Edit List</li>
<li class="active">{{#translate}}Edit List{{/translate}}</li>
</ol>
<h2>Edit List <a class="btn btn-default btn-xs" href="/lists/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> View List</a></h2>
<h2>{{#translate}}Edit List{{/translate}} <a class="btn btn-default btn-xs" href="/lists/view/{{id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}View List{{/translate}}</a></h2>
<hr>
@ -18,23 +18,23 @@
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{id}}" />
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="List Name" autofocus required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}List Name{{/translate}}" autofocus required>
</div>
</div>
<div class="form-group">
<label for="name" class="col-sm-2 control-label">List ID</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}List ID{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="cid" value="{{cid}}" readonly>
<span class="help-block">This is the list ID displayed to the subscribers</span>
<span class="help-block">{{#translate}}This is the list ID displayed to the subscribers{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="description" class="col-sm-2 control-label">Description</label>
<label for="description" class="col-sm-2 control-label">{{#translate}}Description{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" name="description" id="description">{{description}}</textarea>
<span class="help-block">HTML is allowed</span>
<span class="help-block">{{#translate}}HTML is allowed{{/translate}}</span>
</div>
</div>
@ -43,9 +43,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="lists-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete List</button>
<button type="submit" form="lists-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete List{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,77 +1,77 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/fields/{{list.id}}">Custom Fields</a></li>
<li class="active">Create Field</li>
<li><a href="/fields/{{list.id}}">{{#translate}}Custom Fields{{/translate}}</a></li>
<li class="active">{{#translate}}Create Field{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Create Custom Field</small></h2>
<h2>{{list.name}} <small>{{#translate}}Create Custom Field{{/translate}}</small></h2>
<hr>
<form class="form-horizontal" method="post" action="/fields/{{list.id}}/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Field Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Field Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Field Name" required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Field Name{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="type" class="col-sm-2 control-label">Field Type</label>
<label for="type" class="col-sm-2 control-label">{{#translate}}Field Type{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" name="type">
<option value="text" {{#if selectedText}} selected {{/if}}>Text</option>
<option value="number" {{#if selectedNumber}} selected {{/if}}>Number</option>
<option value="website" {{#if selectedWebsite}} selected {{/if}}>Website</option>
<option value="gpg" {{#if selectedGpg}} selected {{/if}}>GPG Public Key</option>
<option value="longtext" {{#if selectedLongtext}} selected {{/if}}>Multi-line text</option>
<option value="json" {{#if selectedJson}} selected {{/if}}>JSON</option>
<optgroup label="Date">
<option value="date-us" {{#if selectedDateUs}} selected {{/if}}>Date (MM/DD/YYYY)</option>
<option value="date-eur" {{#if selectedDateEur}} selected {{/if}}>Date (DD/MM/YYYY)</option>
<option value="text" {{#if selectedText}} selected {{/if}}>{{#translate}}Text{{/translate}}</option>
<option value="number" {{#if selectedNumber}} selected {{/if}}>{{#translate}}Number{{/translate}}</option>
<option value="website" {{#if selectedWebsite}} selected {{/if}}>{{#translate}}Website{{/translate}}</option>
<option value="gpg" {{#if selectedGpg}} selected {{/if}}>{{#translate}}GPG Public Key{{/translate}}</option>
<option value="longtext" {{#if selectedLongtext}} selected {{/if}}>{{#translate}}Multi-line text{{/translate}}</option>
<option value="json" {{#if selectedJson}} selected {{/if}}>{{#translate}}JSON{{/translate}}</option>
<optgroup label="{{#translate}}Date{{/translate}}">
<option value="date-us" {{#if selectedDateUs}} selected {{/if}}>{{#translate}}Date (MM/DD/YYYY){{/translate}}</option>
<option value="date-eur" {{#if selectedDateEur}} selected {{/if}}>{{#translate}}Date (DD/MM/YYYY){{/translate}}</option>
</optgroup>
<optgroup label="Birthday">
<option value="birthday-us" {{#if selectedBirthdayUs}} selected {{/if}}>Birthday (MM/DD)</option>
<option value="birthday-eur" {{#if selectedBirthdayEur}} selected {{/if}}>Birthday (DD/MM)</option>
<optgroup label="{{#translate}}Birthday{{/translate}}">
<option value="birthday-us" {{#if selectedBirthdayUs}} selected {{/if}}>{{#translate}}Birthday (MM/DD){{/translate}}</option>
<option value="birthday-eur" {{#if selectedBirthdayEur}} selected {{/if}}>{{#translate}}Birthday (DD/MM){{/translate}}</option>
</optgroup>
<optgroup label="Grouped">
<option value="dropdown" {{#if selectedDropdown}} selected {{/if}}>Drop Downs</option>
<option value="radio" {{#if selectedRadio}} selected {{/if}}>Radio Buttons</option>
<option value="checkbox" {{#if selectedCheckbox}} selected {{/if}}>Checkboxes</option>
<optgroup label="{{#translate}}Grouped{{/translate}}">
<option value="dropdown" {{#if selectedDropdown}} selected {{/if}}>{{#translate}}Drop Downs{{/translate}}</option>
<option value="radio" {{#if selectedRadio}} selected {{/if}}>{{#translate}}Radio Buttons{{/translate}}</option>
<option value="checkbox" {{#if selectedCheckbox}} selected {{/if}}>{{#translate}}Checkboxes{{/translate}}</option>
</optgroup>
<option value="option" {{#if selectedOption}} selected {{/if}}>Option for a group value</option>
<option value="option" {{#if selectedOption}} selected {{/if}}>{{#translate}}Option for a group value{{/translate}}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="group" class="col-sm-2 control-label">Group</label>
<label for="group" class="col-sm-2 control-label">{{#translate}}Group{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" name="group">
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each groups}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>{{name}}</option>
{{/each}}
</select>
<span class="help-block">Required for group options</span>
<span class="help-block">{{#translate}}Required for group options{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="default-value" class="col-sm-2 control-label">Default merge tag value</label>
<label for="default-value" class="col-sm-2 control-label">{{#translate}}Default merge tag value{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="default-value" id="default-value" value="{{field.defaultValue}}" placeholder="Default merge tag value">
<input type="text" class="form-control" name="default-value" id="default-value" value="{{field.defaultValue}}" placeholder="{{#translate}}Default merge tag value{{/translate}}">
</div>
</div>
<div class="form-group">
<label for="group-template" class="col-sm-2 control-label">Template</label>
<label for="group-template" class="col-sm-2 control-label">{{#translate}}Template{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control gpg-text" rows="3" name="group-template" id="group-template">{{field.groupTemplate}}</textarea>
<span class="help-block">For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from <code>\{{values}}</code> array, for example <code>\{{#each values}} \{{this}} \{{/each}}</code>. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as <code>values</code>, otherwise you can access the JSON keys directly).</span>
<span class="help-block">{{#translate}}For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from <code>\{{values}}</code> array, for example <code>\{{#each values}} \{{this}} \{{/each}}</code>. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as <code>values</code>, otherwise you can access the JSON keys directly).{{/translate}}</span>
</div>
</div>
@ -79,7 +79,7 @@
<div class="col-sm-offset-2 col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" name="visible" {{#if visible}} checked {{/if}}> Visible
<input type="checkbox" name="visible" {{#if visible}} checked {{/if}}> {{#translate}}Visible{{/translate}}
</label>
</div>
</div>
@ -87,7 +87,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Add Field</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Add Field{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,12 +1,12 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/fields/{{list.id}}">Custom Fields</a></li>
<li class="active">Edit Field</li>
<li><a href="/fields/{{list.id}}">{{#translate}}Custom Fields{{/translate}}</a></li>
<li class="active">{{#translate}}Edit Field{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Edit Custom Field</small> <a class="btn btn-default btn-xs" href="/fields/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Back to fields</a></h2>
<h2>{{list.name}} <small>{{#translate}}Edit Custom Field{{/translate}}</small> <a class="btn btn-default btn-xs" href="/fields/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}Back to fields{{/translate}}</a></h2>
<hr>
@ -20,36 +20,36 @@
<input type="hidden" name="id" value="{{field.id}}" />
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Field Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Field Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{field.name}}" placeholder="Field Name" required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{field.name}}" placeholder="{{#translate}}Field Name{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="type" class="col-sm-2 control-label">Field Type</label>
<label for="type" class="col-sm-2 control-label">{{#translate}}Field Type{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" disabled>
<option value="text" {{#if selectedText}} selected {{/if}}>Text</option>
<option value="number" {{#if selectedNumber}} selected {{/if}}>Number</option>
<option value="website" {{#if selectedWebsite}} selected {{/if}}>Website</option>
<option value="gpg" {{#if selectedGpg}} selected {{/if}}>GPG Public Key</option>
<option value="longtext" {{#if selectedLongtext}} selected {{/if}}>Multi-line text</option>
<option value="json" {{#if selectedJson}} selected {{/if}}>JSON</option>
<optgroup label="Date">
<option value="date-us" {{#if selectedDateUs}} selected {{/if}}>Date (MM/DD/YYYY)</option>
<option value="date-eur" {{#if selectedDateEur}} selected {{/if}}>Date (DD/MM/YYYY)</option>
<option value="text" {{#if selectedText}} selected {{/if}}>{{#translate}}Text{{/translate}}</option>
<option value="number" {{#if selectedNumber}} selected {{/if}}>{{#translate}}Number{{/translate}}</option>
<option value="website" {{#if selectedWebsite}} selected {{/if}}>{{#translate}}Website{{/translate}}</option>
<option value="gpg" {{#if selectedGpg}} selected {{/if}}>{{#translate}}GPG Public Key{{/translate}}</option>
<option value="longtext" {{#if selectedLongtext}} selected {{/if}}>{{#translate}}Multi-line text{{/translate}}</option>
<option value="json" {{#if selectedJson}} selected {{/if}}>{{#translate}}JSON{{/translate}}</option>
<optgroup label="{{#translate}}Date{{/translate}}">
<option value="date-us" {{#if selectedDateUs}} selected {{/if}}>{{#translate}}Date (MM/DD/YYYY){{/translate}}</option>
<option value="date-eur" {{#if selectedDateEur}} selected {{/if}}>{{#translate}}Date (DD/MM/YYYY){{/translate}}</option>
</optgroup>
<optgroup label="Birthday">
<option value="birthday-us" {{#if selectedBirthdayUs}} selected {{/if}}>Birthday (MM/DD)</option>
<option value="birthday-eur" {{#if selectedBirthdayEur}} selected {{/if}}>Birthday (DD/MM)</option>
<optgroup label="{{#translate}}Birthday{{/translate}}">
<option value="birthday-us" {{#if selectedBirthdayUs}} selected {{/if}}>{{#translate}}Birthday (MM/DD){{/translate}}</option>
<option value="birthday-eur" {{#if selectedBirthdayEur}} selected {{/if}}>{{#translate}}Birthday (DD/MM){{/translate}}</option>
</optgroup>
<optgroup label="Grouped">
<option value="dropdown" {{#if selectedDropdown}} selected {{/if}}>Drop Downs</option>
<option value="radio" {{#if selectedRadio}} selected {{/if}}>Radio Buttons</option>
<option value="checkbox" {{#if selectedCheckbox}} selected {{/if}}>Checkboxes</option>
<optgroup label="{{#translate}}Grouped{{/translate}}">
<option value="dropdown" {{#if selectedDropdown}} selected {{/if}}>{{#translate}}Drop Downs{{/translate}}</option>
<option value="radio" {{#if selectedRadio}} selected {{/if}}>{{#translate}}Radio Buttons{{/translate}}</option>
<option value="checkbox" {{#if selectedCheckbox}} selected {{/if}}>{{#translate}}Checkboxes{{/translate}}</option>
</optgroup>
<option value="option" {{#if selectedOption}} selected {{/if}}>Option for a group value</option>
<option value="option" {{#if selectedOption}} selected {{/if}}>{{#translate}}Option for a group value{{/translate}}</option>
</select>
</div>
</div>
@ -57,43 +57,43 @@
{{#if groups}}
<div class="form-group">
<label for="group" class="col-sm-2 control-label">Group</label>
<label for="group" class="col-sm-2 control-label">{{#translate}}Group{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" name="group">
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each groups}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>{{name}}</option>
{{/each}}
</select>
<span class="help-block">Required for group options</span>
<span class="help-block">{{#translate}}Required for group options{{/translate}}</span>
</div>
</div>
{{/if}}
<div class="form-group">
<label for="key" class="col-sm-2 control-label">Merge tag</label>
<label for="key" class="col-sm-2 control-label">{{#translate}}Merge tag{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control text-uppercase" name="key" id="key" value="{{field.key}}" placeholder="Merge Tag">
<span class="help-block">Put this tag in your content: <strong>[{{#if field.key}}{{field.key}}{{else}}TAG_VALUE{{/if}}]</strong></span>
<input type="text" class="form-control text-uppercase" name="key" id="key" value="{{field.key}}" placeholder="{{#translate}}Merge Tag{{/translate}}">
<span class="help-block">{{#translate}}Put this tag in your content:{{/translate}} <strong>[{{#if field.key}}{{field.key}}{{else}}TAG_VALUE{{/if}}]</strong></span>
</div>
</div>
{{#if field.isGroup}}
<div class="form-group">
<label for="group-template" class="col-sm-2 control-label">Template</label>
<label for="group-template" class="col-sm-2 control-label">{{#translate}}Template{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control gpg-text" rows="3" name="group-template" id="description">{{field.groupTemplate}}</textarea>
<span class="help-block">For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from <code>\{{values}}</code> array, for example <code>\{{#each values}} \{{this}} \{{/each}}</code>. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as <code>values</code>, otherwise you can access the JSON keys directly).</span>
<span class="help-block">{{#translate}}For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from <code>\{{values}}</code> array, for example <code>\{{#each values}} \{{this}} \{{/each}}</code>. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as <code>values</code>, otherwise you can access the JSON keys directly).{{/translate}}</span>
</div>
</div>
{{else}}
<div class="form-group">
<label for="default-value" class="col-sm-2 control-label">Default merge tag value</label>
<label for="default-value" class="col-sm-2 control-label">{{#translate}}Default merge tag value{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="default-value" id="default-value" value="{{field.defaultValue}}" placeholder="Default merge tag value">
<input type="text" class="form-control" name="default-value" id="default-value" value="{{field.defaultValue}}" placeholder="{{#translate}}Default merge tag value{{/translate}}">
</div>
</div>
{{/if}}
@ -102,7 +102,7 @@
<div class="col-sm-offset-2 col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" name="visible" {{#if field.visible}} checked {{/if}}> Visible
<input type="checkbox" name="visible" {{#if field.visible}} checked {{/if}}> {{#translate}}Visible{{/translate}}
</label>
</div>
</div>
@ -111,9 +111,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="fields-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Field</button>
<button type="submit" form="fields-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Field{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,15 +1,15 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Custom Fields</li>
<li class="active">{{#translate}}Custom Fields{{/translate}}</li>
</ol>
<div class="pull-right">
<a class="btn btn-primary" href="/fields/{{list.id}}/create" role="button"><i class="glyphicon glyphicon-plus"></i> Create Custom Field</a>
<a class="btn btn-primary" href="/fields/{{list.id}}/create" role="button"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Custom Field{{/translate}}</a>
</div>
<h2>{{list.name}} <small>Custom Fields</small></h2>
<h2>{{list.name}} <small>{{#translate}}Custom Fields{{/translate}}</small></h2>
<hr>
@ -20,16 +20,16 @@
#
</th>
<th>
Name
{{#translate}}Name{{/translate}}
</th>
<th class="col-md-2">
Type
{{#translate}}Type{{/translate}}
</th>
<th class="col-md-2">
Merge tag
{{#translate}}Merge tag{{/translate}}
</th>
<th class="col-md-2">
Default merge tag value
{{#translate}}Default merge tag value{{/translate}}
</th>
<th class="col-md-1">
&nbsp;
@ -56,7 +56,7 @@
<td>
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
<a href="/fields/{{../list.id}}/edit/{{id}}">
Edit
{{#translate}}Edit{{/translate}}
</a>
</td>
</tr>
@ -77,7 +77,7 @@
</td>
<td>
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
<a href="/fields/{{../../list.id}}/edit/{{id}}">Edit</a>
<a href="/fields/{{../../list.id}}/edit/{{id}}">{{#translate}}Edit{{/translate}}</a>
</td>
</tr>
{{/each}}
@ -86,7 +86,7 @@
{{#unless rows}}
<tr>
<td colspan="6">
No data available in table
{{#translate}}No data available in table{{/translate}}
</td>
</tr>
{{/unless}}

View file

@ -1,13 +1,13 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li class="active">Lists</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li class="active">{{#translate}}Lists{{/translate}}</li>
</ol>
<div class="pull-right">
<a class="btn btn-primary" href="/lists/create" role="button"><i class="glyphicon glyphicon-plus"></i> Create List</a>
<a class="btn btn-primary" href="/lists/create" role="button"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create List{{/translate}}</a>
</div>
<h2>Lists</h2>
<h2>{{#translate}}Lists{{/translate}}</h2>
<hr>
@ -18,16 +18,16 @@
#
</th>
<th>
Name
{{#translate}}Name{{/translate}}
</th>
<th class="col-md-2">
ID
{{#translate}}ID{{/translate}}
</th>
<th class="col-md-1">
Subscribers
{{#translate}}Subscribers{{/translate}}
</th>
<th>
Description
{{#translate}}Description{{/translate}}
</th>
<th class="col-md-1">
&nbsp;
@ -59,7 +59,7 @@
<td>
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
<a href="/lists/edit/{{id}}">
Edit
{{#translate}}Edit{{/translate}}
</a>
</td>
</tr>

View file

@ -1,38 +1,38 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/segments/{{list.id}}">Segments</a></li>
<li class="active">Create Segment</li>
<li><a href="/segments/{{list.id}}">{{#translate}}Segments{{/translate}}</a></li>
<li class="active">{{#translate}}Create Segment{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Create Segment</small></h2>
<h2>{{list.name}} <small>{{#translate}}Create Segment{{/translate}}</small></h2>
<hr>
<form class="form-horizontal" method="post" action="/segments/{{list.id}}/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Segment Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Segment Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Segment Name" required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Segment Name{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="group" class="col-sm-2 control-label">Rule match</label>
<label for="group" class="col-sm-2 control-label">{{#translate}}Rule match{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" name="type">
<option value=""> Select </option>
<option value="1" {{#if matchAll}} selected {{/if}}>All rules must match</option>
<option value="2" {{#if matchAny}} selected {{/if}}>Any rule can match</option>
<option value=""> {{#translate}}Select{{/translate}} </option>
<option value="1" {{#if matchAll}} selected {{/if}}>{{#translate}}All rules must match{{/translate}}</option>
<option value="2" {{#if matchAny}} selected {{/if}}>{{#translate}}Any rule can match{{/translate}}</option>
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Add Segment</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Add Segment{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,12 +1,12 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/segments/{{list.id}}">Segments</a></li>
<li class="active">Edit Segment</li>
<li><a href="/segments/{{list.id}}">{{#translate}}Segments{{/translate}}</a></li>
<li class="active">{{#translate}}Edit Segment{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Edit Segment</small> <a class="btn btn-default btn-xs" href="/segments/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Back to segments</a></h2>
<h2>{{list.name}} <small>{{#translate}}Edit Segment{{/translate}}</small> <a class="btn btn-default btn-xs" href="/segments/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}Back to segments{{/translate}}</a></h2>
<hr>
@ -20,19 +20,19 @@
<input type="hidden" name="id" value="{{id}}" />
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Segment Name</label>
<label for="name" class="col-sm-2 control-label">{{#translate}}Segment Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="Segment Name" required>
<input type="text" class="form-control input-lg" name="name" id="name" value="{{name}}" placeholder="{{#translate}}Segment Name{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="group" class="col-sm-2 control-label">Rule match</label>
<label for="group" class="col-sm-2 control-label">{{#translate}}Rule match{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" name="type">
<option value=""> Select </option>
<option value="1" {{#if matchAll}} selected {{/if}}>All rules must match</option>
<option value="2" {{#if matchAny}} selected {{/if}}>Any rule can match</option>
<option value=""> {{#translate}}Select{{/translate}} </option>
<option value="1" {{#if matchAll}} selected {{/if}}>{{#translate}}All rules must match{{/translate}}</option>
<option value="2" {{#if matchAny}} selected {{/if}}>{{#translate}}Any rule can match{{/translate}}</option>
</select>
</div>
</div>
@ -40,9 +40,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="segments-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Segment</button>
<button type="submit" form="segments-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Segment{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,13 +1,13 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/segments/{{list.id}}">Segments</a></li>
<li><a href="/segments/{{list.id}}">{{#translate}}Segments{{/translate}}</a></li>
<li><a href="/segments/{{list.id}}/view/{{id}}">{{name}}</a></li>
<li class="active">Create Segment</li>
<li class="active">{{#translate}}Create Segment{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Create Rule</small></h2>
<h2>{{list.name}} <small>{{#translate}}Create Rule{{/translate}}</small></h2>
<hr>
@ -16,7 +16,7 @@
<input type="hidden" name="column" value="{{column.column}}">
<div class="form-group">
<label for="column" class="col-sm-2 control-label">Rule</label>
<label for="column" class="col-sm-2 control-label">{{#translate}}Rule{{/translate}}</label>
<div class="col-sm-10">
<p class="form-control-static"><strong>{{column.name}}</strong></p>
</div>
@ -24,20 +24,20 @@
{{#if columnTypeString}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="value" id="value" value="{{value.value}}" placeholder="Value">
<span class="help-block">Use % for wildcard character, e.g. "%test" to match all values that end with "test"</span>
<input type="text" class="form-control" name="value" id="value" value="{{value.value}}" placeholder="{{#translate}}Value{{/translate}}">
<span class="help-block">{{#translate}}Use % for wildcard character, e.g. "%test" to match all values that end with "test"{{/translate}}</span>
</div>
</div>
{{/if}}
{{#if columnTypeNumber}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10 radio">
<label>
<input type="radio" name="range" value="" {{#unless value.range}} checked {{/unless}}> Use exact match
<input type="radio" name="range" value="" {{#unless value.range}} checked {{/unless}}> {{#translate}}Use exact match{{/translate}}
</label>
</div>
</div>
@ -50,7 +50,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> Use range match
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> {{#translate}}Use range match{{/translate}}
</label>
</div>
</div>
@ -78,10 +78,10 @@
{{#if columnTypeDate}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10 radio">
<label>
<input type="radio" name="range" value="" {{#unless value.range}} {{#unless value.relativeRange}} checked {{/unless}} {{/unless}}> Use exact match
<input type="radio" name="range" value="" {{#unless value.range}} {{#unless value.relativeRange}} checked {{/unless}} {{/unless}}> {{#translate}}Use exact match{{/translate}}
</label>
</div>
</div>
@ -96,7 +96,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> Use range match
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> {{#translate}}Use range match{{/translate}}
</label>
</div>
</div>
@ -127,7 +127,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="relative" {{#if value.relativeRange}} checked {{/if}}> Use relative range match
<input type="radio" name="range" value="relative" {{#if value.relativeRange}} checked {{/if}}> {{#translate}}Use relative range match{{/translate}}
</label>
</div>
</div>
@ -136,38 +136,38 @@
<div class="col-sm-offset-2 col-sm-10 radio">
<div class="row">
<div class="col-md-1">
<p class="form-control-static">From</p>
<p class="form-control-static">{{#translate}}From{{/translate}}</p>
</div>
<div class="col-md-4 form-inline">
<div class="input-group">
<input type="number" class="form-control" name="start-relative" placeholder="0" {{#if value.relativeRange}} value="{{value.start}}" {{/if}}>
<div class="input-group-addon">
days
{{#translate}}days{{/translate}}
<select name="start-direction">
<option value="0">
before today
{{#translate}}before today{{/translate}}
</option>
<option value="1" {{#if value.startDirection}} selected {{/if}}>
after today
{{#translate}}after today{{/translate}}
</option>
</select>
</div>
</div>
</div>
<div class="col-md-1">
<p class="form-control-static text-center">to</p>
<p class="form-control-static text-center">{{#translate}}to{{/translate}}</p>
</div>
<div class="col-md-4 form-inline">
<div class="input-group">
<input type="number" class="form-control" name="end-relative" placeholder="0" {{#if value.relativeRange}} value="{{value.end}}" {{/if}}>
<div class="input-group-addon">
days
{{#translate}}days{{/translate}}
<select name="end-direction">
<option value="0">
before today
{{#translate}}before today{{/translate}}
</option>
<option value="1" {{#if value.endDirection}} selected {{/if}}>
after today
{{#translate}}after today{{/translate}}
</option>
</select>
</div>
@ -180,10 +180,10 @@
{{#if columnTypeBirthday}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10 radio">
<label>
<input type="radio" name="range" value="" {{#unless isRange}} checked {{/unless}}> Use exact match
<input type="radio" name="range" value="" {{#unless isRange}} checked {{/unless}}> {{#translate}}Use exact match{{/translate}}
</label>
</div>
</div>
@ -198,7 +198,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="yes" {{#if isRange}} checked {{/if}}> Use range match
<input type="radio" name="range" value="yes" {{#if isRange}} checked {{/if}}> {{#translate}}Use range match{{/translate}}
</label>
</div>
</div>
@ -207,7 +207,7 @@
<div class="col-sm-offset-2 col-sm-10 radio">
<div class="row">
<div class="col-md-1">
<p class="form-control-static">From</p>
<p class="form-control-static">{{#translate}}From{{/translate}}</p>
</div>
<div class="col-md-3">
<div class="input-group date fm-birthday-generic">
@ -215,7 +215,7 @@
</div>
</div>
<div class="col-md-1">
<p class="form-control-static text-center">to</p>
<p class="form-control-static text-center">{{#translate}}to{{/translate}}</p>
</div>
<div class="col-md-3">
<div class="input-group date fm-birthday-generic">
@ -229,16 +229,16 @@
{{#if columnTypeBoolean}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10">
<div class="radio">
<label>
<input type="radio" name="value" value="yes" {{#if value.value}} checked {{/if}}> Selected
<input type="radio" name="value" value="yes" {{#if value.value}} checked {{/if}}> {{#translate}}Selected{{/translate}}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="value" value="" {{#unless value.value}} checked {{/unless}}> Not selected
<input type="radio" name="value" value="" {{#unless value.value}} checked {{/unless}}> {{#translate}}Not selected{{/translate}}
</label>
</div>
</div>
@ -248,7 +248,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> Add Rule</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Add Rule{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,13 +1,13 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/segments/{{list.id}}">Segments</a></li>
<li><a href="/segments/{{list.id}}">{{#translate}}Segments{{/translate}}</a></li>
<li><a href="/segments/{{list.id}}/view/{{id}}">{{name}}</a></li>
<li class="active">Create Segment</li>
<li class="active">{{#translate}}Create Segment{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Create Rule</small></h2>
<h2>{{list.name}} <small>{{#translate}}Create Rule{{/translate}}</small></h2>
<hr>
@ -15,10 +15,10 @@
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<div class="form-group">
<label for="column" class="col-sm-2 control-label">Rule</label>
<label for="column" class="col-sm-2 control-label">{{#translate}}Rule{{/translate}}</label>
<div class="col-sm-10">
<select id="column" class="form-control" name="column" required>
<option value=""> Select </option>
<option value=""> {{#translate}}Select{{/translate}} </option>
{{#each columns}}
<option value="{{column}}" {{#if selected}} selected {{/if}}>{{name}}</option>
{{/each}}
@ -28,7 +28,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> Next</button>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> {{#translate}}Next{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,13 +1,13 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/segments/{{list.id}}">Segments</a></li>
<li><a href="/segments/{{list.id}}">{{#translate}}Segments{{/translate}}</a></li>
<li><a href="/segments/{{list.id}}/view/{{segment.id}}">{{name}}</a></li>
<li class="active">Create Segment</li>
<li class="active">{{#translate}}Create Segment{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Create Rule</small></h2>
<h2>{{list.name}} <small>{{#translate}}Create Rule{{/translate}}</small></h2>
<hr>
@ -21,7 +21,7 @@
<input type="hidden" name="id" value="{{id}}">
<div class="form-group">
<label for="column" class="col-sm-2 control-label">Rule</label>
<label for="column" class="col-sm-2 control-label">{{#translate}}Rule{{/translate}}</label>
<div class="col-sm-10">
<p class="form-control-static"><strong>{{column.name}}</strong></p>
</div>
@ -29,20 +29,20 @@
{{#if columnTypeString}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="value" id="value" value="{{value.value}}" placeholder="Value">
<span class="help-block">Use % for wildcard character, e.g. "%test" to match all values that end with "test"</span>
<input type="text" class="form-control" name="value" id="value" value="{{value.value}}" placeholder="{{#translate}}Value{{/translate}}">
<span class="help-block">{{#translate}}Use % for wildcard character, e.g. "%test" to match all values that end with "test"{{/translate}}</span>
</div>
</div>
{{/if}}
{{#if columnTypeNumber}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10 radio">
<label>
<input type="radio" name="range" value="" {{#unless value.range}} checked {{/unless}}> Use exact match
<input type="radio" name="range" value="" {{#unless value.range}} checked {{/unless}}> {{#translate}}Use exact match{{/translate}}
</label>
</div>
</div>
@ -55,7 +55,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> Use range match
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> {{#translate}}Use range match{{/translate}}
</label>
</div>
</div>
@ -64,13 +64,13 @@
<div class="col-sm-offset-2 col-sm-10 radio">
<div class="row">
<div class="col-md-1">
<p class="form-control-static">From</p>
<p class="form-control-static">{{#translate}}From{{/translate}}</p>
</div>
<div class="col-md-2">
<input type="number" class="form-control" name="start" value="{{value.start}}" placeholder="0">
</div>
<div class="col-md-1">
<p class="form-control-static text-center">to</p>
<p class="form-control-static text-center">{{#translate}}to{{/translate}}</p>
</div>
<div class="col-md-2">
<input type="number" class="form-control" name="end" value="{{value.end}}" placeholder="0">
@ -83,10 +83,10 @@
{{#if columnTypeDate}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10 radio">
<label>
<input type="radio" name="range" value="" {{#unless value.range}} {{#unless value.relativeRange}} checked {{/unless}} {{/unless}}> Use exact match
<input type="radio" name="range" value="" {{#unless value.range}} {{#unless value.relativeRange}} checked {{/unless}} {{/unless}}> {{#translate}}Use exact match{{/translate}}
</label>
</div>
</div>
@ -101,7 +101,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> Use range match
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> {{#translate}}Use range match{{/translate}}
</label>
</div>
</div>
@ -110,7 +110,7 @@
<div class="col-sm-offset-2 col-sm-10 radio">
<div class="row">
<div class="col-md-1">
<p class="form-control-static">From</p>
<p class="form-control-static">{{#translate}}From{{/translate}}</p>
</div>
<div class="col-md-3">
<div class="input-group date fm-date-generic">
@ -118,7 +118,7 @@
</div>
</div>
<div class="col-md-1">
<p class="form-control-static text-center">to</p>
<p class="form-control-static text-center">{{#translate}}to{{/translate}}</p>
</div>
<div class="col-md-3">
<div class="input-group date fm-date-generic">
@ -132,7 +132,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="relative" {{#if value.relativeRange}} checked {{/if}}> Use relative range match
<input type="radio" name="range" value="relative" {{#if value.relativeRange}} checked {{/if}}> {{#translate}}Use relative range match{{/translate}}
</label>
</div>
</div>
@ -141,38 +141,38 @@
<div class="col-sm-offset-2 col-sm-10 radio">
<div class="row">
<div class="col-md-1">
<p class="form-control-static">From</p>
<p class="form-control-static">{{#translate}}From{{/translate}}</p>
</div>
<div class="col-md-4 form-inline">
<div class="input-group">
<input type="number" class="form-control" name="start-relative" placeholder="0" {{#if value.relativeRange}} value="{{value.start}}" {{/if}}>
<div class="input-group-addon">
days
{{#translate}}days{{/translate}}
<select name="start-direction">
<option value="0">
before today
{{#translate}}before today{{/translate}}
</option>
<option value="1" {{#if value.startDirection}} selected {{/if}}>
after today
{{#translate}}after today{{/translate}}
</option>
</select>
</div>
</div>
</div>
<div class="col-md-1">
<p class="form-control-static text-center">to</p>
<p class="form-control-static text-center">{{#translate}}to{{/translate}}</p>
</div>
<div class="col-md-4 form-inline">
<div class="input-group">
<input type="number" class="form-control" name="end-relative" placeholder="0" {{#if value.relativeRange}} value="{{value.end}}" {{/if}}>
<div class="input-group-addon">
days
{{#translate}}days{{/translate}}
<select name="end-direction">
<option value="0">
before today
{{#translate}}before today{{/translate}}
</option>
<option value="1" {{#if value.endDirection}} selected {{/if}}>
after today
{{#translate}}after today{{/translate}}
</option>
</select>
</div>
@ -185,10 +185,10 @@
{{#if columnTypeBirthday}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10 radio">
<label>
<input type="radio" name="range" value="" {{#unless value.range}} checked {{/unless}}> Use exact match
<input type="radio" name="range" value="" {{#unless value.range}} checked {{/unless}}> {{#translate}}Use exact match{{/translate}}
</label>
</div>
</div>
@ -203,7 +203,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10 radio">
<label>
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> Use range match
<input type="radio" name="range" value="yes" {{#if value.range}} checked {{/if}}> {{#translate}}Use range match{{/translate}}
</label>
</div>
</div>
@ -212,7 +212,7 @@
<div class="col-sm-offset-2 col-sm-10 radio">
<div class="row">
<div class="col-md-1">
<p class="form-control-static">From</p>
<p class="form-control-static">{{#translate}}From{{/translate}}</p>
</div>
<div class="col-md-3">
<div class="input-group date fm-birthday-generic">
@ -220,7 +220,7 @@
</div>
</div>
<div class="col-md-1">
<p class="form-control-static text-center">To</p>
<p class="form-control-static text-center">{{#translate}}to{{/translate}}</p>
</div>
<div class="col-md-3">
<div class="input-group date fm-birthday-generic">
@ -234,16 +234,16 @@
{{#if columnTypeBoolean}}
<div class="form-group">
<label for="value" class="col-sm-2 control-label">Value</label>
<label for="value" class="col-sm-2 control-label">{{#translate}}Value{{/translate}}</label>
<div class="col-sm-10">
<div class="radio">
<label>
<input type="radio" name="value" value="yes" {{#if value.value}} checked {{/if}}> Selected
<input type="radio" name="value" value="yes" {{#if value.value}} checked {{/if}}> {{#translate}}Selected{{/translate}}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="value" value="" {{#unless value.value}} checked {{/unless}}> Not selected
<input type="radio" name="value" value="" {{#unless value.value}} checked {{/unless}}> {{#translate}}Not selected{{/translate}}
</label>
</div>
</div>
@ -254,9 +254,9 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
<button type="submit" form="rule-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Rule</button>
<button type="submit" form="rule-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Rule{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,15 +1,15 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Segments</li>
<li class="active">{{#translate}}Segments{{/translate}}</li>
</ol>
<div class="pull-right">
<a class="btn btn-primary" href="/segments/{{list.id}}/create" role="button"><i class="glyphicon glyphicon-plus"></i> Create Segment</a>
<a class="btn btn-primary" href="/segments/{{list.id}}/create" role="button"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Segment{{/translate}}</a>
</div>
<h2>{{list.name}} <small>Segments</small></h2>
<h2>{{list.name}} <small>{{#translate}}Segments{{/translate}}</small></h2>
<hr>
@ -20,10 +20,10 @@
#
</th>
<th>
Name
{{#translate}}Name{{/translate}}
</th>
<th class="col-md-2">
Match
{{#translate}}Match{{/translate}}
</th>
<th class="col-md-1">
&nbsp;
@ -46,7 +46,7 @@
<td>
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
<a href="/segments/{{../list.id}}/edit/{{id}}">
Edit
{{#translate}}Edit{{/translate}}
</a>
</td>
</tr>

View file

@ -1,26 +1,26 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li><a href="/segments/{{list.id}}">Segments</a></li>
<li><a href="/segments/{{list.id}}">{{#translate}}Segments{{/translate}}</a></li>
<li class="active">{{name}}</li>
</ol>
<div class="pull-right">
<a class="btn btn-primary" href="/segments/{{list.id}}/rules/{{id}}/create" role="button"><i class="glyphicon glyphicon-plus"></i> Create Rule</a>
<a class="btn btn-primary" href="/segments/{{list.id}}/rules/{{id}}/create" role="button"><i class="glyphicon glyphicon-plus"></i> {{#translate}}Create Rule{{/translate}}</a>
</div>
<h2>{{list.name}} <small><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Segment {{name}}</small></h2>
<h2>{{list.name}} <small><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> {{#translate}}Segment{{/translate}} {{name}}</small></h2>
<hr>
<div class="well well-sm">
<div class="pull-right">
<a class="btn btn-primary btn-sm" href="/segments/{{list.id}}/edit/{{id}}" role="button"><i class="glyphicon glyphicon-wrench"></i> Edit Segment</a>
<a class="btn btn-primary btn-sm" href="/segments/{{list.id}}/edit/{{id}}" role="button"><i class="glyphicon glyphicon-wrench"></i> {{#translate}}Edit Segment{{/translate}}</a>
</div>
Match rules: <span class="label label-default">{{type}}</span>
<br /> Matching subscribers: <span class="label label-default">{{subscribers}}</span> (
<a href="/lists/view/{{list.id}}?segment={{id}}">show</a>)
{{#translate}}Match rules{{/translate}}: <span class="label label-default">{{type}}</span>
<br /> {{#translate}}Matching subscribers{{/translate}}: <span class="label label-default">{{subscribers}}</span> (
<a href="/lists/view/{{list.id}}?segment={{id}}">{{#translate}}show{{/translate}}</a>)
<br />
<div class="clearfix"></div>
</div>
@ -32,10 +32,10 @@
#
</th>
<th>
Rule
{{#translate}}Rule{{/translate}}
</th>
<th class="col-md-2">
Value
{{#translate}}Value{{/translate}}
</th>
<th class="col-md-1">
&nbsp;
@ -57,7 +57,7 @@
<td>
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
<a href="/segments/{{../list.id}}/rules/{{../id}}/edit/{{id}}">
Edit
{{#translate}}Edit{{/translate}}
</a>
</td>
</tr>

View file

@ -1,11 +1,13 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Add subscriber</li>
<li class="active">
{{#translate}}Add subscriber{{/translate}}
</li>
</ol>
<h2>{{list.name}} <small>Add subscriber</small></h2>
<h2>{{list.name}} <small>{{#translate}}Add subscriber{{/translate}}</small></h2>
<hr>
@ -14,21 +16,21 @@
<input type="hidden" name="list" value="{{list.id}}">
<div class="form-group">
<label for="email" class="col-sm-2 control-label">Email Address</label>
<label for="email" class="col-sm-2 control-label">{{#translate}}Email Address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control input-lg" name="email" id="email" placeholder="" value="{{email}}" required>
</div>
</div>
<div class="form-group">
<label for="first-name" class="col-sm-2 control-label">First Name</label>
<label for="first-name" class="col-sm-2 control-label">{{#translate}}First Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="first-name" id="first-name" placeholder="" value="{{firstName}}">
</div>
</div>
<div class="form-group">
<label for="last-name" class="col-sm-2 control-label">Last Name</label>
<label for="last-name" class="col-sm-2 control-label">{{#translate}}Last Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="last-name" id="last-name" placeholder="" value="{{lastName}}">
</div>
@ -59,8 +61,8 @@
{{/if}}
{{#if typeGpg}}
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="Begins with &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">Insert a GPG public key that will be used to encrypt messages sent this subscriber</span>
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="{{#translate}}Begins with{{/translate}} &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">{{#translate}}Insert a GPG public key that will be used to encrypt messages sent this subscriber{{/translate}}</span>
{{/if}}
{{#if typeDateUs}}
@ -90,7 +92,7 @@
{{#if typeDropdown}}
<select name="{{key}}" class="form-control">
<option value="">
Select
{{#translate}}Select{{/translate}}
</option>
{{#each options}}
<option value="{{column}}" {{#if value}} selected {{/if}}>{{name}}</option>
@ -122,11 +124,11 @@
{{/each}}
<div class="form-group">
<label for="tz" class="col-sm-2 control-label">Timezone</label>
<label for="tz" class="col-sm-2 control-label">{{#translate}}Timezone{{/translate}}</label>
<div class="col-sm-10">
<select name="tz" class="form-control">
<option value="">
Select
{{#translate}}Select{{/translate}}
</option>
{{#each timezones}}
<option value="{{key}}" {{#if selected}} selected {{/if}}>{{value}}</option>
@ -139,8 +141,8 @@
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="is-test" {{#if isTest}} checked {{/if}}> Test user?
<span class="help-block">If checked then this subscription can be used for previewing campaign messages</span>
<input type="checkbox" name="is-test" {{#if isTest}} checked {{/if}}> {{#translate}}Test user?{{/translate}}
<span class="help-block">{{#translate}}If checked then this subscription can be used for previewing campaign messages{{/translate}}</span>
</label>
</div>
</div>
@ -151,10 +153,10 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<p class="text-warning">
This person will not receive a confirmation email so make sure that you have permission to email them.
{{#translate}}This person will not receive a confirmation email so make sure that you have permission to email them.{{/translate}}
</p>
<button type="submit" class="btn btn-primary">Subscribe</button>
<button type="submit" class="btn btn-primary">{{#translate}}Subscribe{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,11 +1,13 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Edit subscriber</li>
<li class="active">
{{#translate}}Edit subscriber{{/translate}}
</li>
</ol>
<h2>{{list.name}} <small>Edit subscriber</small> <a class="btn btn-default btn-xs" href="/lists/view/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Back to list</a></h2>
<h2>{{list.name}} <small>{{#translate}}Edit subscriber{{/translate}}</small> <a class="btn btn-default btn-xs" href="/lists/view/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}Back to list{{/translate}}</a></h2>
<hr>
@ -27,21 +29,21 @@
<input type="hidden" name="cid" value="{{cid}}">
<div class="form-group">
<label for="email" class="col-sm-2 control-label">Email address</label>
<label for="email" class="col-sm-2 control-label">{{#translate}}Email address{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control input-lg" name="email" id="email" placeholder="" value="{{email}}" required>
</div>
</div>
<div class="form-group">
<label for="first-name" class="col-sm-2 control-label">First Name</label>
<label for="first-name" class="col-sm-2 control-label">{{#translate}}First Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="first-name" id="first-name" placeholder="" value="{{firstName}}">
</div>
</div>
<div class="form-group">
<label for="last-name" class="col-sm-2 control-label">Last Name</label>
<label for="last-name" class="col-sm-2 control-label">{{#translate}}Last Name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="last-name" id="last-name" placeholder="" value="{{lastName}}">
</div>
@ -72,8 +74,8 @@
{{/if}}
{{#if typeGpg}}
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="Begins with &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">Insert a GPG public key that will be used to encrypt messages sent this subscriber</span>
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="{{#translate}}Begins with{{/translate}} &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">{{#translate}}Insert a GPG public key that will be used to encrypt messages sent this subscriber{{/translate}}</span>
{{/if}}
{{#if typeDateUs}}
@ -103,7 +105,7 @@
{{#if typeDropdown}}
<select name="{{key}}" class="form-control">
<option value="">
Select
{{#translate}}Select{{/translate}}
</option>
{{#each options}}
<option value="{{column}}" {{#if value}} selected {{/if}}>{{name}}</option>
@ -135,7 +137,7 @@
{{/each}}
<div class="form-group">
<label for="tz" class="col-sm-2 control-label">Timezone</label>
<label for="tz" class="col-sm-2 control-label">{{#translate}}Timezone{{/translate}}</label>
<div class="col-sm-10">
<select name="tz" class="form-control">
<option value="">
@ -152,8 +154,8 @@
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="is-test" {{#if isTest}} checked {{/if}}> Test user?
<span class="help-block">If checked then this subscription can be used for previewing campaign messages</span>
<input type="checkbox" name="is-test" {{#if isTest}} checked {{/if}}> {{#translate}}Test user?{{/translate}}
<span class="help-block">{{#translate}}If checked then this subscription can be used for previewing campaign messages{{/translate}}</span>
</label>
</div>
</div>
@ -165,12 +167,12 @@
<div class="col-sm-offset-2 col-sm-10">
<div class="pull-right">
{{#if isSubscribed}}
<button type="submit" form="subscriber-unsubscribe" class="btn btn-default"><i class="glyphicon glyphicon-ban-circle"></i> Unsubscribe</button>
<button type="submit" form="subscriber-unsubscribe" class="btn btn-default"><i class="glyphicon glyphicon-ban-circle"></i> {{#translate}}Unsubscribe{{/translate}}</button>
{{/if}}
<button type="submit" form="subscriber-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> Delete Subscription</button>
<button type="submit" form="subscriber-delete" class="btn btn-danger"><i class="glyphicon glyphicon-remove"></i> {{#translate}}Delete Subscription{{/translate}}</button>
</div>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,16 +1,16 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Import status</li>
<li class="active">{{#translate}}Import status{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Failed addresses</small> <a class="btn btn-default btn-xs" href="/lists/view/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Back to list</a></h2>
<h2>{{list.name}} <small>{{#translate}}Failed addresses{{/translate}}</small> <a class="btn btn-default btn-xs" href="/lists/view/{{list.id}}" role="button"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}Back to list{{/translate}}</a></h2>
<hr>
<div class="well">
Role-based addresses like <em><strong>postmaster</strong>@example.com</em> are blocked when importing. Subscribers with role-based email addresses can join your list using the <a href="/subscription/{{list.cid}}">subscription form</a>.
{{#translate}}Role-based addresses like postmaster@example.com are blocked when importing. Subscribers with role-based email addresses can join your list using the subscription form{{#translate}} (<a href="/subscription/{{list.cid}}">{{#translate}}see here{{/translate}}</a>).
</div>
<div class="table-responsive">
@ -20,10 +20,10 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
Fail reason
{{#translate}}Fail reason{{/translate}}
</th>
</thead>
{{#if rows}}

View file

@ -1,11 +1,11 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Import subscribers</li>
<li class="active">{{#translate}}Import subscribers{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Import subscribers</small></h2>
<h2>{{list.name}} <small>{{#translate}}Import subscribers{{/translate}}</small></h2>
<hr>
@ -20,11 +20,11 @@
<label for="column-{{@index}}" class="col-sm-2 control-label">{{this}}</label>
<div class="col-sm-10">
<select class="form-control" id="column-{{@index}}" name="column-{{@index}}">
<option value=""> Select </option>
<option value="email">Email address</option>
<option value="first_name">First Name</option>
<option value="last_name">Last Name</option>
<option value="tz">Timezone</option>
<option value=""> {{#translate}}Select{{/translate}} </option>
<option value="email">{{#translate}}Email address{{/translate}}</option>
<option value="first_name">{{#translate}}First Name{{/translate}}</option>
<option value="last_name">{{#translate}}Last Name{{/translate}}</option>
<option value="tz">{{#translate}}Timezone{{/translate}}</option>
{{#each ../customFields}}
{{#if column}}
<option value="{{column}}">{{name}}</option>
@ -39,7 +39,7 @@
{{/each}}
</select>
<span id="helpBlock" class="help-block">Example: "{{lookup ../mapping.example @index}}"</span>
<span id="helpBlock" class="help-block">{{#translate}}Example{{/translate}}: "{{lookup ../mapping.example @index}}"</span>
</div>
</div>
@ -49,7 +49,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">Start import</button>
<button type="submit" class="btn btn-primary">{{#translate}}Start import{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,11 +1,11 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li><a href="/lists/view/{{list.id}}">{{list.name}}</a></li>
<li class="active">Import subscribers</li>
<li class="active">{{#translate}}Import subscribers{{/translate}}</li>
</ol>
<h2>{{list.name}} <small>Import subscribers</small></h2>
<h2>{{list.name}} <small>{{#translate}}Import subscribers{{/translate}}</small></h2>
<hr>
@ -14,30 +14,30 @@
<input type="hidden" name="list" value="{{list.id}}">
<div class="form-group">
<label for="listimport" class="col-sm-2 control-label">CSV File</label>
<label for="listimport" class="col-sm-2 control-label">{{#translate}}CSV File{{/translate}}</label>
<div class="col-sm-6">
<input type="file" class="form-control" name="listimport" id="listimport" required>
</div>
</div>
<div class="form-group">
<label for="delimiter" class="col-sm-2 control-label">CSV delimiter</label>
<label for="delimiter" class="col-sm-2 control-label">{{#translate}}CSV delimiter{{/translate}}</label>
<div class="col-sm-1">
<input type="text" class="form-control" name="delimiter" id="delimiter" placeholder="" value="{{delimiter}}">
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">Categorize the imported subscribers as:</label>
<label class="col-sm-2 control-label">{{#translate}}Categorize the imported subscribers as{{/translate}}:</label>
<div class="col-sm-6">
<div class="radio">
<label>
<input type="radio" name="type" id="type" value="subscribed" checked> Subscribed <span class="text-muted">Regular subscriber addresses</span>
<input type="radio" name="type" id="type" value="subscribed" checked> {{#translate}}Subscribed{{/translate}} <span class="text-muted">{{#translate}}Regular subscriber addresses{{/translate}}</span>
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="type" id="type" value="unsubscribed"> Unsubscribed <span class="text-muted">Suppressed emails that will be unsubscribed from your list</span>
<input type="radio" name="type" id="type" value="unsubscribed"> {{#translate}}Unsubscribed{{/translate}} <span class="text-muted">{{#translate}}Suppressed emails that will be unsubscribed from your list{{/translate}}</span>
</label>
</div>
</div>
@ -47,7 +47,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> Next</button>
<button type="submit" class="btn btn-primary"><span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> {{#translate}}Next{{/translate}}</button>
</div>
</div>
</form>

View file

@ -1,25 +1,25 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li><a href="/lists/">Lists</a></li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li><a href="/lists/">{{#translate}}Lists{{/translate}}</a></li>
<li class="active">{{name}}</li>
</ol>
<div class="pull-right">
<a class="btn btn-default" href="/subscription/{{cid}}" target="_blank" role="button"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> Subscription Form</a>
<a class="btn btn-default" href="/subscription/{{cid}}" target="_blank" role="button"><span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span> {{#translate}}Subscription Form{{/translate}}</a>
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
List Actions <span class="caret"></span>
{{#translate}}List Actions{{/translate}} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="/fields/{{id}}" role="button"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> Custom Fields</a></li>
<li><a href="/segments/{{id}}" role="button"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Segments</a></li>
<li><a href="/lists/edit/{{id}}" role="button"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Edit List</a></li>
<li><a href="/triggers/{{id}}/create" role="button"><span class="glyphicon glyphicon-console" aria-hidden="true"></span> Create Trigger</a></li>
<li><a href="/fields/{{id}}" role="button"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{#translate}}Custom Fields{{/translate}}</a></li>
<li><a href="/segments/{{id}}" role="button"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> {{#translate}}Segments{{/translate}}</a></li>
<li><a href="/lists/edit/{{id}}" role="button"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> {{#translate}}Edit List{{/translate}}</a></li>
<li><a href="/triggers/{{id}}/create" role="button"><span class="glyphicon glyphicon-console" aria-hidden="true"></span> {{#translate}}Create Trigger{{/translate}}</a></li>
<li role="separator" class="divider"></li>
<li><a href="/lists/subscription/{{id}}/add" role="button"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> Add Subscriber</a></li>
<li><a href="/lists/subscription/{{id}}/import" role="button"><span class="glyphicon glyphicon-cloud-upload" aria-hidden="true"></span> Import Subscribers</a></li>
<li><a href="/lists/subscription/{{id}}/add" role="button"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> {{#translate}}Add Subscriber{{/translate}}</a></li>
<li><a href="/lists/subscription/{{id}}/import" role="button"><span class="glyphicon glyphicon-cloud-upload" aria-hidden="true"></span> {{#translate}}Import Subscribers{{/translate}}</a></li>
</ul>
</div>
</div>
@ -35,35 +35,35 @@
<div class="well well-sm">
{{#if useSegment}}
<div class="pull-right">
<a class="btn btn-primary btn-sm" href="/segments/{{id}}/edit/{{segment}}" role="button"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Edit Segment</a>
<a class="btn btn-primary btn-sm" href="/segments/{{id}}/edit/{{segment}}" role="button"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> {{#translate}}Edit Segment{{/translate}}</a>
</div>
{{/if}}
<form class="form-inline" method="get">
<div class="form-group">
<label for="exampleInputName2">Segment</label>
<label for="exampleInputName2">{{#translate}}Segment{{/translate}}</label>
<select name="segment" class="form-control">
<option value="0">All Subscriptions</option>
{{#if segments}}
<optgroup label="Segments">
<optgroup label="{{#translate}}Segments{{/translate}}">
{{#each segments}}
<option value="{{id}}" {{#if selected}} selected {{/if}}>{{name}}</option>
{{/each}}
</optgroup>
{{/if}}
<optgroup label="Actions">
<option value="-1">Create New Segment…</option>
<option value="-1">{{#translate}}Create New Segment{{/translate}}…</option>
</optgroup>
</select>
</div>
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Filter</button>
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> {{#translate}}Filter{{/translate}}</button>
</form>
<div class="clearfix"></div>
</div>
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{#if showSubscriptions}}active{{/if}}"><a href="/lists/view/{{id}}" aria-controls="subscriptions" role="tab">Subscriptions</a></li>
<li role="presentation" class="{{#if showImports}}active{{/if}}"><a href="/lists/view/{{id}}?tab=imports" aria-controls="imports" role="tab">Imports</a></li>
<li role="presentation" class="{{#if showSubscriptions}}active{{/if}}"><a href="/lists/view/{{id}}" aria-controls="subscriptions" role="tab">{{#translate}}Subscriptions{{/translate}}</a></li>
<li role="presentation" class="{{#if showImports}}active{{/if}}"><a href="/lists/view/{{id}}?tab=imports" aria-controls="imports" role="tab">{{#translate}}Imports{{/translate}}</a></li>
</ul>
<div class="tab-content">
@ -79,13 +79,13 @@
#
</th>
<th>
Address
{{#translate}}Address{{/translate}}
</th>
<th>
First Name
{{#translate}}First Name{{/translate}}
</th>
<th>
Last Name
{{#translate}}Last Name{{/translate}}
</th>
{{#each customFields}}
<th>
@ -93,10 +93,10 @@
</th>
{{/each}}
<th>
Status
{{#translate}}Status{{/translate}}
</th>
<th>
Created
{{#translate}}Created{{/translate}}
</th>
<th></th>
</tr>
@ -117,25 +117,25 @@
#
</th>
<th>
Created
{{#translate}}Created{{/translate}}
</th>
<th>
Finished
{{#translate}}Finished{{/translate}}
</th>
<th>
Type
{{#translate}}Type{{/translate}}
</th>
<th>
Added
{{#translate}}Added{{/translate}}
</th>
<th>
Updated
{{#translate}}Updated{{/translate}}
</th>
<th>
Failed
{{#translate}}Failed{{/translate}}
</th>
<th>
Status
{{#translate}}Status{{/translate}}
</th>
<th>
@ -181,11 +181,11 @@
{{/if}}
</td>
<td class="text-center">
<form method="post" class="confirm-submit" data-confirm-message="Are you sure? This action should only be called to resolve stalled imports" action="/lists/subscription/import-restart">
<form method="post" class="confirm-submit" data-confirm-message="{{#translate}}Are you sure? This action should only be called to resolve stalled imports{{/translate}}" action="/lists/subscription/import-restart">
<input type="hidden" name="_csrf" value="{{../csrfToken}}">
<input type="hidden" name="list" value="{{list}}">
<input type="hidden" name="import" value="{{id}}">
<button type="submit" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> Restart</button>
<button type="submit" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-repeat" aria-hidden="true"></span> {{#translate}}Restart{{/translate}}</button>
</form>
</td>
</tr>
@ -193,7 +193,7 @@
{{else}}
<tr>
<td colspan="9">
No data available in table
{{#translate}}No data available in table{{/translate}}
</td>
</tr>
{{/if}}

View file

@ -1,5 +1,5 @@
<div class="form-group">
<label for="template-html" class="col-sm-2 control-label">Template content (HTML)</label>
<label for="template-html" class="col-sm-2 control-label">{{#translate}}Template content (HTML){{/translate}}</label>
<div class="col-sm-10">
<div class="code-editor" id="template-html">{{html}}</div>
<input type="hidden" name="html">

View file

@ -1,6 +1,6 @@
<div class="form-group" id="html-preview" {{#unless html}}style="display: none;"{{/unless}}>
<div class="col-sm-offset-2 col-sm-10">
<a role="button" data-toggle="collapse" href="#html-preview-toggle" aria-expanded="false" aria-controls="html-preview-toggle">Toggle HTML preview</a>
<a role="button" data-toggle="collapse" href="#html-preview-toggle" aria-expanded="false" aria-controls="html-preview-toggle">{{#translate}}Toggle HTML preview{{/translate}}</a>
<div class="collapse" id="html-preview-toggle">
<h6 class="small text-muted">320x480px</h6>
<iframe

View file

@ -1,21 +1,20 @@
{{#if mergeTags}}
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<a class="btn btn-default" role="button" data-toggle="collapse" href="#mergeReference" aria-expanded="false" aria-controls="mergeReference">Merge tag reference</a>
<a class="btn btn-default" role="button" data-toggle="collapse" href="#mergeReference" aria-expanded="false" aria-controls="mergeReference">{{#translate}}Merge tag reference{{/translate}}</a>
<div class="collapse" id="mergeReference">
<p style="margin-top: .8em;">
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.
{{#translate}}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.{{/translate}}
</p>
<table class="table table-bordered table-condensed table-striped">
<thead>
<tr>
<th>
Merge tag
{{#translate}}Merge tag{{/translate}}
</th>
<th>
Description
{{#translate}}Description{{/translate}}
</th>
</tr>
</thead>

View file

@ -1,5 +1,5 @@
<div class="form-group">
<label for="template-text" class="col-sm-2 control-label">Template content (plaintext)</label>
<label for="template-text" class="col-sm-2 control-label">{{#translate}}Template content (plaintext){{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control" id="template-text" name="text" rows="10">{{text}}</textarea>
</div>

View file

@ -1,5 +1,5 @@
<div class="form-group">
<label for="template-html" class="col-sm-2 control-label">Template content (HTML)</label>
<label for="template-html" class="col-sm-2 control-label">{{#translate}}Template content (HTML){{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control summernote" id="template-html" name="html" rows="8">{{html}}</textarea>
</div>

View file

@ -1,9 +1,9 @@
<ol class="breadcrumb">
<li><a href="/">Home</a></li>
<li class="active">Settings</li>
<li><a href="/">{{#translate}}Home{{/translate}}</a></li>
<li class="active">{{#translate}}Settings{{/translate}}</li>
</ol>
<h2>Settings</h2>
<h2>{{#translate}}Settings{{/translate}}</h2>
<hr>
@ -17,22 +17,22 @@
<fieldset>
<legend>
General Settings
{{#translate}}General Settings{{/translate}}
</legend>
<div class="form-group">
<label for="service-url" class="col-sm-2 control-label">Service Address (URL)</label>
<label for="service-url" class="col-sm-2 control-label">{{#translate}}Service Address (URL){{/translate}}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="service-url" id="service-url" placeholder="http://example.com/" value="{{serviceUrl}}" required>
<span class="help-block">Enter the URL this service can be reached from</span>
<span class="help-block">{{#translate}}Enter the URL this service can be reached from{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="admin-email" class="col-sm-2 control-label">Admin Email</label>
<label for="admin-email" class="col-sm-2 control-label">{{#translate}}Admin Email{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="admin-email" id="admin-email" placeholder="admin@example.com" value="{{adminEmail}}" required>
<span class="help-block">Enter the email address that will be used as "from" for system messages</span>
<span class="help-block">{{#translate}}Enter the email address that will be used as "from" for system messages{{/translate}}</span>
</div>
</div>
@ -40,8 +40,8 @@
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="disable-wysiwyg" {{#if disableWysiwyg}} checked {{/if}}> Disable WYSIWYG editor
<span class="help-block">If checked then message editor displays HTML code without the preview</span>
<input type="checkbox" name="disable-wysiwyg" {{#if disableWysiwyg}} checked {{/if}}> {{#translate}}Disable WYSIWYG editor{{/translate}}
<span class="help-block">{{#translate}}If checked then message editor displays HTML code without the preview{{/translate}}</span>
</label>
</div>
</div>
@ -51,26 +51,26 @@
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="disable-confirmations" {{#if disableConfirmations}} checked {{/if}}> Disable subscription confirmation messages
<span class="help-block">If checked then do not send a confirmation message that states the subscriber is now subscribed or unsubscribed. This does not disable double opt-in messages.</span>
<input type="checkbox" name="disable-confirmations" {{#if disableConfirmations}} checked {{/if}}> {{#translate}}Disable subscription confirmation messages{{/translate}}
<span class="help-block">{{#translate}}If checked then do not send a confirmation message that states the subscriber is now subscribed or unsubscribed. This does not disable double opt-in messages.{{/translate}}</span>
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="ua-code" class="col-sm-2 control-label">Tracking ID</label>
<label for="ua-code" class="col-sm-2 control-label">{{#translate}}Tracking ID{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="ua-code" id="ua-code-url" placeholder="UA-XXXXX-XX" value="{{uaCode}}">
<span class="help-block">Enter Google Analytics tracking code</span>
<span class="help-block">{{#translate}}Enter Google Analytics tracking code{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="shoutout" class="col-sm-2 control-label">Frontpage shout out</label>
<label for="shoutout" class="col-sm-2 control-label">{{#translate}}Frontpage shout out{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control gpg-text" rows="3" id="shoutout" name="shoutout" placeholder="">{{shoutout}}</textarea>
<span class="help-block">HTML code shown in the front page header section</span>
<span class="help-block">{{#translate}}HTML code shown in the front page header section{{/translate}}</span>
</div>
</div>
@ -78,47 +78,47 @@
<fieldset>
<legend>
Campaign defaults
{{#translate}}Campaign defaults{{/translate}}
</legend>
<div class="form-group">
<label for="default-sender" class="col-sm-2 control-label">Sender name</label>
<label for="default-sender" class="col-sm-2 control-label">{{#translate}}Sender name{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="default-sender" id="default-sender" value="{{defaultSender}}" placeholder="Sender name, eg. &quot;My Awesome Company Ltd.&quot;">
<input type="text" class="form-control" name="default-sender" id="default-sender" value="{{defaultSender}}" placeholder="{{#translate}}Sender name, eg. My Awesome Company Ltd.{{/translate}}">
</div>
</div>
<div class="form-group">
<label for="default-postaddress" class="col-sm-2 control-label">Default address</label>
<label for="default-postaddress" class="col-sm-2 control-label">{{#translate}}Default address{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="default-postaddress" id="default-postaddress" value="{{defaultPostaddress}}" placeholder="Contact address to provide, eg. &quot;1234 Main Street, Anywhere, MA 01234, USA&quot;">
<input type="text" class="form-control" name="default-postaddress" id="default-postaddress" value="{{defaultPostaddress}}" placeholder="{{#translate}}Contact address to provide, eg. 1234 Main Street, Anywhere, MA 01234, USA{{/translate}}">
</div>
</div>
<hr />
<div class="form-group">
<label for="default-from" class="col-sm-2 control-label">Default "from name"</label>
<label for="default-from" class="col-sm-2 control-label">{{#translate}}Default "from name"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="default-from" id="default-from" value="{{defaultFrom}}" placeholder="This is the name your emails will come from" required>
<input type="text" class="form-control" name="default-from" id="default-from" value="{{defaultFrom}}" placeholder="{{#translate}}This is the name your emails will come from{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="default-address" class="col-sm-2 control-label">Default "from" email</label>
<label for="default-address" class="col-sm-2 control-label">{{#translate}}Default "from" email{{/translate}}</label>
<div class="col-sm-10">
<input type="email" class="form-control" name="default-address" id="default-address" value="{{defaultAddress}}" placeholder="This is the address people will send replies to" required>
<input type="email" class="form-control" name="default-address" id="default-address" value="{{defaultAddress}}" placeholder="{{#translate}}This is the address people will send replies to{{/translate}}" required>
</div>
</div>
<div class="form-group">
<label for="default-subject" class="col-sm-2 control-label">Default "subject line"</label>
<label for="default-subject" class="col-sm-2 control-label">{{#translate}}Default "subject line"{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="default-subject" id="default-subject" value="{{defaultSubject}}" placeholder="Keep it relevant and non-spammy">
<input type="text" class="form-control" name="default-subject" id="default-subject" value="{{defaultSubject}}" placeholder="{{#translate}}Keep it relevant and non-spammy{{/translate}}">
</div>
</div>
<div class="form-group">
<label for="default-homepage" class="col-sm-2 control-label">Default homepage (URL)</label>
<label for="default-homepage" class="col-sm-2 control-label">{{#translate}}Default homepage (URL){{/translate}}</label>
<div class="col-sm-10">
<input type="url" class="form-control" name="default-homepage" id="default-homepage" value="{{defaultHomepage}}" placeholder="URL to redirect the subscribed users to, eg. http://example.com/">
<input type="url" class="form-control" name="default-homepage" id="default-homepage" value="{{defaultHomepage}}" placeholder="{{#translate}}URL to redirect the subscribed users to, eg. http://example.com/{{/translate}}">
</div>
</div>
@ -127,15 +127,15 @@
<fieldset>
<legend>
Mailer Settings
{{#translate}}Mailer Settings{{/translate}}
</legend>
<p class="text-info">These settings are required to send out e-mail messages</p>
<p class="text-info">{{#translate}}These settings are required to send out e-mail messages{{/translate}}</p>
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="{{#if useSMTP}} active {{/if}}"><a href="#smtp-settings" aria-controls="smtp-settings" role="tab" data-toggle="tab">SMTP</a></li>
<li role="presentation" class="{{#if useSES}} active {{/if}}"><a href="#aws-ses" aria-controls="aws-ses" role="tab" data-toggle="tab">AWS SES</a></li>
<li role="presentation" class="{{#if useSMTP}} active {{/if}}"><a href="#smtp-settings" aria-controls="smtp-settings" role="tab" data-toggle="tab">{{#translate}}SMTP{{/translate}}</a></li>
<li role="presentation" class="{{#if useSES}} active {{/if}}"><a href="#aws-ses" aria-controls="aws-ses" role="tab" data-toggle="tab">{{#translate}}AWS SES{{/translate}}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane {{#if useSMTP}} active {{/if}}" id="smtp-settings">
@ -146,28 +146,28 @@
<div class="radio">
<label>
<input type="radio" name="mail-transport" id="transport-smtp" value="smtp" {{#if useSMTP}} checked {{/if}}>
Use SMTP for sending mail
{{#translate}}Use SMTP for sending mail{{/translate}}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="smtp-hostname" class="col-sm-2 control-label">Hostname</label>
<label for="smtp-hostname" class="col-sm-2 control-label">{{#translate}}Hostname{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="smtp-hostname" id="smtp-hostname" placeholder="Hostname, eg. smtp.example.com" value="{{smtpHostname}}" required>
</div>
</div>
<div class="form-group">
<label for="smtp-port" class="col-sm-2 control-label">Port</label>
<label for="smtp-port" class="col-sm-2 control-label">{{#translate}}Port{{/translate}}</label>
<div class="col-sm-10">
<input type="number" class="form-control" name="smtp-port" id="smtp-port" placeholder="Port, eg. 465. Autodetected if left blank" value="{{smtpPort}}">
<input type="number" class="form-control" name="smtp-port" id="smtp-port" placeholder="{{#translate}}Port, eg. 465. Autodetected if left blank{{/translate}}" value="{{smtpPort}}">
</div>
</div>
<div class="form-group">
<label for="smtp-encryption" class="col-sm-2 control-label">Encryption</label>
<label for="smtp-encryption" class="col-sm-2 control-label">{{#translate}}Encryption{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="smtp-encryption" name="smtp-encryption">
{{#each smtpEncryption}}
@ -184,23 +184,23 @@
<div class="col-sm-offset-2 col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" name="smtp-disable-auth" {{#if smtpDisableAuth}} checked {{/if}}> Disable SMTP authentication
<input type="checkbox" name="smtp-disable-auth" {{#if smtpDisableAuth}} checked {{/if}}> {{#translate}}Disable SMTP authentication{{/translate}}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="smtp-user" class="col-sm-2 control-label">Username</label>
<label for="smtp-user" class="col-sm-2 control-label">{{#translate}}Username{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="smtp-user" id="smtp-user" placeholder="Username, eg. myaccount@example.com" value="{{smtpUser}}">
<input type="text" class="form-control" name="smtp-user" id="smtp-user" placeholder="{{#translate}}Username, eg. myaccount@example.com{{/translate}}" value="{{smtpUser}}">
</div>
</div>
<div class="form-group">
<label for="smtp-pass" class="col-sm-2 control-label">Password</label>
<label for="smtp-pass" class="col-sm-2 control-label">{{#translate}}Password{{/translate}}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="smtp-pass" id="smtp-pass" placeholder="Password" value="{{smtpPass}}">
<input type="password" class="form-control" name="smtp-pass" id="smtp-pass" placeholder="{{#translate}}Password{{/translate}}" value="{{smtpPass}}">
</div>
</div>
@ -213,28 +213,28 @@
<div class="radio">
<label>
<input type="radio" name="mail-transport" id="transport-ses" value="ses" {{#if useSES}} checked {{/if}}>
Use SES API for sending mail
{{#translate}}Use SES API for sending mail{{/translate}}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="ses-key" class="col-sm-2 control-label">Access Key</label>
<label for="ses-key" class="col-sm-2 control-label">{{#translate}}Access Key{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="ses-key" id="ses-key" placeholder="AWS Access Key Id" value="{{sesKey}}">
<input type="text" class="form-control" name="ses-key" id="ses-key" placeholder="{{#translate}}AWS Access Key Id{{/translate}}" value="{{sesKey}}">
</div>
</div>
<div class="form-group">
<label for="ses-secret" class="col-sm-2 control-label">Secret Key</label>
<label for="ses-secret" class="col-sm-2 control-label">{{#translate}}Secret Key{{/translate}}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="ses-secret" id="ses-secret" placeholder="AES Secret Access Key" value="{{sesSecret}}">
<input type="password" class="form-control" name="ses-secret" id="ses-secret" placeholder="{{#translate}}AWS Secret Access Key{{/translate}}" value="{{sesSecret}}">
</div>
</div>
<div class="form-group">
<label for="ses-region" class="col-sm-2 control-label">Region</label>
<label for="ses-region" class="col-sm-2 control-label">{{#translate}}Region{{/translate}}</label>
<div class="col-sm-10">
<select class="form-control" id="ses-region" name="ses-region">
{{#each sesRegion}}
@ -252,24 +252,24 @@
<div class="form-group">
<div class="pull-right">
<button type="submit" id="verify-button" form="smtp-verify" class="btn btn-info" data-loading-text="Checking..."><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> Check Mailer config</button>
<button type="submit" id="verify-button" form="smtp-verify" class="btn btn-info" data-loading-text="{{#translate}}Checking{{/translate}}"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> {{#translate}}Check Mailer config{{/translate}}</button>
</div>
<div class="col-sm-offset-2 col-xs-6">
<p class="form-control-static">Don't have an SMTP account yet? Create a free SendPulse account <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=settings">here</a></p>
<p class="form-control-static">{{#translate}}Don't have an SMTP account yet? Create a free SendPulse account{{/translate}} <a href="https://sendpulse.com/?utm_source=mailtrain&utm_medium=settings">{{#translate}}here{{/translate}}</a></p>
</div>
</div>
</fieldset>
<fieldset>
<legend>
Advanced Mailer settings
{{#translate}}Advanced Mailer settings{{/translate}}
</legend>
<div class="form-group">
<div class="col-sm-offset-2 col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" name="smtp-log" {{#if smtpLog}} checked {{/if}}> Log SMTP transactions
<input type="checkbox" name="smtp-log" {{#if smtpLog}} checked {{/if}}> {{#translate}}Log SMTP transactions{{/translate}}
</label>
</div>
</div>
@ -279,54 +279,52 @@
<div class="col-sm-offset-2 col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" name="smtp-self-signed" {{#if smtpSelfSigned}} checked {{/if}}> Allow self-signed certificates
<input type="checkbox" name="smtp-self-signed" {{#if smtpSelfSigned}} checked {{/if}}> {{#translate}}Allow self-signed certificates{{/translate}}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="smtp-max-connections" class="col-sm-2 control-label">Max connections</label>
<label for="smtp-max-connections" class="col-sm-2 control-label">{{#translate}}Max connections{{/translate}}</label>
<div class="col-sm-6">
<input type="number" class="form-control" name="smtp-max-connections" id="smtp-max-connections" placeholder="The count of max connections, eg. 10" value="{{smtpMaxConnections}}">
<span class="help-block">The count of maximum simultaneous connections to make against the SMTP server (defaults to 5). This limit is per sending process.</span>
<input type="number" class="form-control" name="smtp-max-connections" id="smtp-max-connections" placeholder="{{#translate}}The count of max connections, eg. 10{{/translate}}" value="{{smtpMaxConnections}}">
<span class="help-block">{{#translate}}The count of maximum simultaneous connections to make against the SMTP server (defaults to 5). This limit is per sending process.{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="smtp-max-messages" class="col-sm-2 control-label">Max messages</label>
<label for="smtp-max-messages" class="col-sm-2 control-label">{{#translate}}Max messages{{/translate}}</label>
<div class="col-sm-6">
<input type="number" class="form-control" name="smtp-max-messages" id="smtp-max-messages" placeholder="The count of max messages, eg. 100" value="{{smtpMaxMessages}}">
<span class="help-block">The number of messages to send through a single connection before the connection is closed and reopened (defaults to 100)</span>
<input type="number" class="form-control" name="smtp-max-messages" id="smtp-max-messages" placeholder="{{#translate}}The count of max messages, eg. 100{{/translate}}" value="{{smtpMaxMessages}}">
<span class="help-block">T{{#translate}}he number of messages to send through a single connection before the connection is closed and reopened (defaults to 100){{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="smtp-throttling" class="col-sm-2 control-label">Throttling</label>
<label for="smtp-throttling" class="col-sm-2 control-label">{{#translate}}Throttling{{/translate}}</label>
<div class="col-sm-6">
<input type="number" class="form-control" name="smtp-throttling" id="smtp-throttling" placeholder="Messages per hour eg. 1000" value="{{smtpThrottling}}">
<span class="help-block">Maximum number of messages to send in an hour. Leave empty or zero for no throttling. If your provider uses a different speed limit (<em>messages/minute</em> or <em>messages/second</em>) then convert this limit into <em>messages/hour</em> (1m/s => 3600m/h). This limit is per sending process.</span>
<input type="number" class="form-control" name="smtp-throttling" id="smtp-throttling" placeholder="{{#translate}}Messages per hour eg. 1000{{/translate}}" value="{{smtpThrottling}}">
<span class="help-block">{{#translate}}Maximum number of messages to send in an hour. Leave empty or zero for no throttling. If your provider uses a different speed limit (messages/minute or messages/second) then convert this limit into messages/hour (1m/s => 3600m/h). This limit is per sending process.{{/translate}}</span>
</div>
</div>
</fieldset>
<fieldset>
<legend>
VERP bounce handling
{{#translate}}VERP bounce handling{{/translate}}
</legend>
<p class="text-info">
Mailtrain is able to use <a href="https://en.wikipedia.org/wiki/Variable_envelope_return_path">VERP</a> based routing to detect bounces. In this case the message is sent to the recipient using a custom VERP address as the return path of the
message. If the message is not accepted a bounce email is sent to this special VERP address and thus a bounce is detected.
{{#translate}}Mailtrain is able to use VERP based routing to detect bounces. In this case the message is sent to the recipient using a custom VERP address as the return path of the message. If the message is not accepted a bounce email is sent to this special VERP address and thus a bounce is detected.{{/translate}}
</p>
<p class="text-info">
To get VERP working you need to set up a DNS MX record that points to your Mailtrain hostname. You must also ensure that Mailtrain VERP interface is available from port 25 of your server (port 25 usually requires root user privileges). This way if anyone
tries to send email to <code>someuser@{{verpHostname}}</code> then the email should end up to this server.
{{#translate}}To get VERP working you need to set up a DNS MX record that points to your Mailtrain hostname. You must also ensure that Mailtrain VERP interface is available from port 25 of your server (port 25 usually requires root user privileges). This way if anyone tries to send email to someuser@verp-hostname then the email should end up to this server.{{/translate}}
</p>
<p class="text-warning">
VERP usually only works if you are using your own SMTP server. Regular relay services (SES, SparkPost, Gmail etc.) tend to remove the VERP address from the message.
{{#translate}}VERP usually only works if you are using your own SMTP server. Regular relay services (SES, SparkPost, Gmail etc.) tend to remove the VERP address from the message.{{/translate}}
</p>
{{#if verpEnabled}}
@ -335,24 +333,24 @@
<div class="col-sm-offset-2 col-xs-4">
<div class="checkbox">
<label>
<input type="checkbox" name="verp-use" {{#if verpUse}} checked {{/if}}> Use VERP to catch bounces
<input type="checkbox" name="verp-use" {{#if verpUse}} checked {{/if}}> {{#translate}}Use VERP to catch bounces{{/translate}}
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="verp-hostname" class="col-sm-2 control-label">Server hostname</label>
<label for="verp-hostname" class="col-sm-2 control-label">{{#translate}}Server hostname{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="verp-hostname" id="verp-hostname" placeholder="The VERP server hostname, eg. bounces.example.com" value="{{verpHostname}}">
<span class="help-block">VERP bounce handling server hostname. This hostname is used in the SMTP envelope FROM address and the MX DNS records should point to this server</span>
<input type="text" class="form-control" name="verp-hostname" id="verp-hostname" placeholder="{{#translate}}The VERP server hostname, eg. bounces.example.com{{/translate}}" value="{{verpHostname}}">
<span class="help-block">{{#translate}}VERP bounce handling server hostname. This hostname is used in the SMTP envelope FROM address and the MX DNS records should point to this server{{/translate}}</span>
</div>
</div>
{{else}}
<div class="form-group">
<div class="col-sm-10">
<p class="form-control-static">VERP bounce handling server is not enabled. Modify your server configuration file and restart server to enable it</p>
<p class="form-control-static">{{#translate}}VERP bounce handling server is not enabled. Modify your server configuration file and restart server to enable it{{/translate}}</p>
</div>
</div>
{{/if}}
@ -360,30 +358,29 @@
<fieldset>
<legend>
GPG Signing
{{#translate}}GPG Signing{{/translate}}
</legend>
<p>
Only messages that are encrypted can be signed. Subsribers who have not set up a GPG public key in their profile receive normal email messages. Users with GPG key set receive encrypted messages and if you have signing key also set, the messages are signed
with this key.
{{#translate}}Only messages that are encrypted can be signed. Subsribers who have not set up a GPG public key in their profile receive normal email messages. Users with GPG key set receive encrypted messages and if you have signing key also set, the messages are signed with this key.{{/translate}}
</p>
<p class="text-warning">
Do not use sensitive keys here. The private key and passphrase are not encrypted in the database.
{{#translate}}Do not use sensitive keys here. The private key and passphrase are not encrypted in the database.{{/translate}}
</p>
<div class="form-group">
<label for="pgp-passphrase" class="col-sm-2 control-label">Private Key Passphrase</label>
<label for="pgp-passphrase" class="col-sm-2 control-label">{{#translate}}Private Key Passphrase{{/translate}}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="pgp-passphrase" id="pgp-passphrase" placeholder="Passphrase for the key if set" value="{{pgpPassphrase}}">
<span class="help-block">Only fill this if your private key is encrypted with a passphrase</span>
<input type="password" class="form-control" name="pgp-passphrase" id="pgp-passphrase" placeholder="{{#translate}}Passphrase for the key if set{{/translate}}" value="{{pgpPassphrase}}">
<span class="help-block">{{#translate}}Only fill this if your private key is encrypted with a passphrase{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="pgp-private-key" class="col-sm-2 control-label">GPG Private Key</label>
<label for="pgp-private-key" class="col-sm-2 control-label">{{#translate}}GPG Private Key{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control gpg-text" rows="3" id="pgp-private-key" name="pgp-private-key" placeholder="Begins with &#39;-----BEGIN PGP PRIVATE KEY BLOCK-----&#39;">{{pgpPrivateKey}}</textarea>
<span class="help-block">This value is optional. If you do not provide a private key GPG encrypted messages are sent without signing.</span>
<textarea class="form-control gpg-text" rows="3" id="pgp-private-key" name="pgp-private-key" placeholder="{{#translate}}Begins with{{/translate}} &#39;-----BEGIN PGP PRIVATE KEY BLOCK-----&#39;">{{pgpPrivateKey}}</textarea>
<span class="help-block">{{#translate}}This value is optional. If you do not provide a private key GPG encrypted messages are sent without signing.{{/translate}}</span>
</div>
</div>
@ -391,45 +388,45 @@
<fieldset>
<legend>
DKIM Signing by ZoneMTA
{{#translate}}DKIM Signing by ZoneMTA{{/translate}}
</legend>
<p>
If you are using <a href="https://github.com/zone-eu/zone-mta">ZoneMTA</a> then Mailtrain can provide a DKIM key for signing all outgoing messages. Other services usually provide their own means to DKIM sign your messages
{{#translate}}If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing all outgoing messages. Other services usually provide their own means to DKIM sign your messages{{/translate}}
</p>
<p class="text-warning">
Do not use sensitive keys here. The private key is not encrypted in the database.
{{#translate}}Do not use sensitive keys here. The private key is not encrypted in the database.{{/translate}}
</p>
<div class="form-group">
<label for="pgp-passphrase" class="col-sm-2 control-label">ZoneMTA DKIM API Key</label>
<label for="pgp-passphrase" class="col-sm-2 control-label">{{#translate}}ZoneMTA DKIM API Key{{/translate}}</label>
<div class="col-sm-10">
<input type="password" class="form-control" name="dkim-api-key" id="dkim-api-key" placeholder="Some secret value" value="{{dkimApiKey}}">
<span class="help-block">Secret value known to ZoneMTA for requesting DKIM key information. If this value was generated by the Mailtrain installation script then you can keep it as it is</span>
<input type="password" class="form-control" name="dkim-api-key" id="dkim-api-key" placeholder="{{#translate}}Some secret value{{/translate}}" value="{{dkimApiKey}}">
<span class="help-block">{{#translate}}Secret value known to ZoneMTA for requesting DKIM key information. If this value was generated by the Mailtrain installation script then you can keep it as it is{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="pgp-passphrase" class="col-sm-2 control-label">DKIM domain</label>
<label for="pgp-passphrase" class="col-sm-2 control-label">{{#translate}}DKIM domain{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="dkim-domain" id="dkim-domain" placeholder="Domain name for the DKIM key" value="{{dkimDomain}}">
<span class="help-block">Leave blank to use the sender email address domain</span>
<input type="text" class="form-control" name="dkim-domain" id="dkim-domain" placeholder="{{#translate}}Domain name for the DKIM key{{/translate}}" value="{{dkimDomain}}">
<span class="help-block">{{#translate}}Leave blank to use the sender email address domain{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="pgp-passphrase" class="col-sm-2 control-label">DKIM key selector</label>
<label for="pgp-passphrase" class="col-sm-2 control-label">{{#translate}}DKIM key selector{{/translate}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" name="dkim-selector" id="dkim-selector" placeholder="DKIM key selector" value="{{dkimSelector}}">
<span class="help-block">Signing is disabled without a valid selector value</span>
<input type="text" class="form-control" name="dkim-selector" id="dkim-selector" placeholder="{{#translate}}DKIM key selector{{/translate}}" value="{{dkimSelector}}">
<span class="help-block">{{#translate}}Signing is disabled without a valid selector value{{/translate}}</span>
</div>
</div>
<div class="form-group">
<label for="dkim-private-key" class="col-sm-2 control-label">DKIM Private Key</label>
<label for="dkim-private-key" class="col-sm-2 control-label">{{#translate}}DKIM Private Key{{/translate}}</label>
<div class="col-sm-10">
<textarea class="form-control gpg-text" rows="3" id="dkim-private-key" name="dkim-private-key" placeholder="Begins with &#39;-----BEGIN RSA PRIVATE KEY-----&#39;">{{dkimPrivateKey}}</textarea>
<span class="help-block">This value is optional. If you do not provide a private key then messages are not signed.</span>
<textarea class="form-control gpg-text" rows="3" id="dkim-private-key" name="dkim-private-key" placeholder="{{#translate}}Begins with{{/translate}} &#39;-----BEGIN RSA PRIVATE KEY-----&#39;">{{dkimPrivateKey}}</textarea>
<span class="help-block">{{#translate}}This value is optional. If you do not provide a private key then messages are not signed.{{/translate}}</span>
</div>
</div>
@ -439,7 +436,7 @@
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update{{/translate}}</button>
</div>
</div>

View file

@ -1,17 +1,17 @@
<div class="alert alert-warning alert-dismissible" role="alert" id="js-warning">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>Warning!</strong> If JavaScript was not enabled then no confirmation message was sent
<strong>{{#translate}}Warning!{{/translate}}</strong> {{#translate}}If JavaScript was not enabled then no confirmation message was sent{{/translate}}
</div>
<script>
document.getElementById('js-warning').style.display = 'none';
</script>
<h2>Almost finished.</h2>
<h2>{{#translate}}Almost finished.{{/translate}}</h2>
<p>We need to confirm your email address. To complete the subscription process, please click the link in the email we just sent you.</p>
<p>{{#translate}}We need to confirm your email address. To complete the subscription process, please click the link in the email we just sent you.{{/translate}}</p>
<p>
<a class="btn btn-primary" href="{{homepage}}" role="button">
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> return to our website
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}return to our website{{/translate}}
</a>
</p>

View file

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="Self hosted email newsletter app">
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
<meta name="author" content="Andris Reinman">
<link rel="icon" href="/favicon.ico">

View file

@ -1,4 +1,4 @@
<h2>Update your Email Address</h2>
<h2>{{#translate}}Update your Email Address{{/translate}}</h2>
<form method="post" action="/subscription/{{lcid}}/manage-address">
@ -6,20 +6,20 @@
<input type="hidden" name="cid" value="{{cid}}">
<div class="form-group">
<label for="email">Existing Email Address</label>
<label for="email">{{#translate}}Existing Email Address{{/translate}}</label>
<input type="email" class="form-control" name="email" id="email" placeholder="" value="{{email}}" readonly>
</div>
<div class="form-group">
<label for="email-new">New Email Address</label>
<input type="email" class="form-control" name="email-new" id="email-new" placeholder="Your new email address" value="{{email}}">
<label for="email-new">{{#translate}}New Email Address{{/translate}}</label>
<input type="email" class="form-control" name="email-new" id="email-new" placeholder="{{#translate}}Your new email address{{/translate}}" value="{{email}}">
</div>
<p>
<small>You will receive a confirmation request to your new email address that you need to accept before your email is actually changed</small>
<small>{{#translate}}You will receive a confirmation request to your new email address that you need to accept before your email is actually changed{{/translate}}</small>
</p>
<div class="form-group">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update Email Address</button>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update Email Address{{/translate}}</button>
</div>
</form>

View file

@ -1,4 +1,4 @@
<h2>Update your preferences</h2>
<h2>{{#translate}}Update your preferences{{/translate}}</h2>
{{#if hasPubkey}}
<form method="post" id="download-pubkey" action="/subscription/publickey">
@ -14,20 +14,20 @@
<input type="hidden" class="tz-detect" name="tz" id="tz" value="{{tz}}">
<div class="form-group">
<label for="email">Email Address</label>
<label for="email">{{#translate}}Email Address{{/translate}}</label>
<div class="input-group">
<input type="email" class="form-control" name="email" id="email" placeholder="" value="{{email}}" readonly>
<div class="input-group-addon"><a href="/subscription/{{lcid}}/manage-address/{{cid}}">want to change it?</a></div>
<div class="input-group-addon"><a href="/subscription/{{lcid}}/manage-address/{{cid}}">{{#translate}}want to change it?{{/translate}}</a></div>
</div>
</div>
<div class="form-group">
<label for="first-name">First Name</label>
<label for="first-name">{{#translate}}First Name{{/translate}}</label>
<input type="text" class="form-control" name="first-name" id="first-name" placeholder="" value="{{firstName}}">
</div>
<div class="form-group">
<label for="last-name">Last Name</label>
<label for="last-name">{{#translate}}Last Name{{/translate}}</label>
<input type="text" class="form-control" name="last-name" id="last-name" placeholder="" value="{{lastName}}">
</div>
@ -58,11 +58,11 @@
{{#if typeGpg}}
{{#if ../hasPubkey}}
<div class="pull-right">
<button type="submit" class="btn btn-link btn-xs" form="download-pubkey"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> Download signature verification key</button>
<button type="submit" class="btn btn-link btn-xs" form="download-pubkey"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> {{#translate}}Download signature verification key{{/translate}}</button>
</div>
{{/if}}
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="Begins with &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">Insert your GPG public key here to encrypt messages sent to your address <em>(optional)</em></span>
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="{{#translate}}Begins with{{/translate}} &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">{{#translate}}Insert your GPG public key here to encrypt messages sent to your address{{/translate}} <em>({{#translate}}optional{{/translate}})</em></span>
{{/if}}
{{#if typeDateUs}}
@ -92,7 +92,7 @@
{{#if typeDropdown}}
<select name="{{key}}" class="form-control">
<option value="">
Select
{{#translate}}Select{{/translate}}
</option>
{{#each options}}
<option value="{{column}}" {{#if value}} selected {{/if}}>{{name}}</option>
@ -124,6 +124,6 @@
{{/each}}
<div class="form-group">
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> Update Profile</button> or <a href="/subscription/{{lcid}}/unsubscribe/{{cid}}">Unsubscribe</a>
<button type="submit" class="btn btn-primary"><i class="glyphicon glyphicon-ok"></i> {{#translate}}Update Profile{{/translate}}</button> or <a href="/subscription/{{lcid}}/unsubscribe/{{cid}}">{{#translate}}Unsubscribe{{/translate}}</a>
</div>
</form>

View file

@ -7,7 +7,8 @@
<div class="alert alert-warning alert-dismissible" role="alert" id="js-warning">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<strong>Warning!</strong> JavaScript must be enabled in order for the subscription form to work
<strong>{{#translate}}Warning!{{/translate}}</strong>
{{#translate}}JavaScript must be enabled in order for the subscription form to work{{/translate}}
</div>
<script>
document.getElementById('js-warning').style.display = 'none';
@ -22,17 +23,17 @@
<input type="hidden" name="sub" id="sub" value="">
<div class="form-group">
<label for="email">Email Address</label>
<label for="email">{{#translate}}Email Address{{/translate}}</label>
<input type="email" class="form-control" name="email" id="email" placeholder="" value="{{email}}" required>
</div>
<div class="form-group">
<label for="first-name">First Name</label>
<label for="first-name">{{#translate}}First Name{{/translate}}</label>
<input type="text" class="form-control" name="first-name" id="first-name" placeholder="" value="{{firstName}}">
</div>
<div class="form-group">
<label for="last-name">Last Name</label>
<label for="last-name">{{#translate}}Last Name{{/translate}}</label>
<input type="text" class="form-control" name="last-name" id="last-name" placeholder="" value="{{lastName}}">
</div>
@ -63,11 +64,11 @@
{{#if typeGpg}}
{{#if ../hasPubkey}}
<div class="pull-right">
<button type="submit" class="btn btn-link btn-xs" form="download-pubkey"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> Download signature verification key</button>
<button type="submit" class="btn btn-link btn-xs" form="download-pubkey"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> {{#translate}}Download signature verification key{{/translate}}</button>
</div>
{{/if}}
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="Begins with &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">Insert your GPG public key here to encrypt messages sent to your address <em>(optional)</em></span>
<textarea class="form-control gpg-text" rows="3" name="{{column}}" placeholder="{{#translate}}Begins with{{/translate}} &#39;-----BEGIN PGP PUBLIC KEY BLOCK-----&#39;">{{value}}</textarea>
<span class="help-block">{{#translate}}Insert your GPG public key here to encrypt messages sent to your address{{/translate}} <em>({{#translate}}optional{{/translate}})</em></span>
{{/if}}
{{#if typeDateUs}}
@ -97,7 +98,7 @@
{{#if typeDropdown}}
<select name="{{key}}" class="form-control">
<option value="">
Select
{{#translate}}Select{{/translate}}
</option>
{{#each options}}
<option value="{{column}}" {{#if value}} selected {{/if}}>{{name}}</option>
@ -129,7 +130,7 @@
{{/each}}
<div class="form-group" id="js-subscribe" style="display: none">
<button type="submit" class="btn btn-primary">Subscribe to list</button>
<button type="submit" class="btn btn-primary">{{#translate}}Subscribe to list{{/translate}}</button>
</div>
<script>
document.getElementById('js-subscribe').style.display = 'block';

View file

@ -1,15 +1,21 @@
<h2>Subscription Confirmed</h2>
<h2>{{#translate}}Subscription Confirmed{{/translate}}</h2>
<p>Your subscription to our list has been confirmed.</p>
<p>
{{#translate}}Your subscription to our list has been confirmed.{{/translate}}
</p>
<p>Thank you for subscribing!</p>
<p>
{{#translate}}Thank you for subscribing!{{/translate}}
</p>
<p>
<a class="btn btn-primary" href="{{homepage}}" role="button">
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> continue to our website
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span>
{{#translate}}continue to our website{{/translate}}
</a>
or
{{#translate}}or{{/translate}}
<a class="btn btn-primary" href="{{preferences}}" role="button">
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> manage your preferences
<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span>
{{#translate}}manage your preferences{{/translate}}
</a>
</p>

View file

@ -1,9 +1,9 @@
<h2>Unsubscribe Successful</h2>
<h2>{{#translate}}Unsubscribe Successful{{/translate}}</h2>
<p>You have been removed from {{title}}.</p>
<p>{{#translate}}You have been removed from:{{/translate}} {{title}}.</p>
<p>
<a class="btn btn-primary" href="{{homepage}}" role="button">
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> return to our website
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> {{#translate}}return to our website{{/translate}}
</a>
</p>

View file

@ -1,7 +1,7 @@
<h2>Unsubscribe</h2>
<h2>{{#translate}}Unsubscribe{{/translate}}</h2>
<p>
Enter your email address to unsubscribe from {{title}}
{{#translate}}Enter your email address to unsubscribe from:{{/translate}} {{title}}
</p>
<form method="post" id="unsubscribe-form" action="/subscription/{{lcid}}/unsubscribe">
@ -11,12 +11,12 @@
<input type="hidden" name="cid" value="{{cid}}">
<div class="form-group">
<label for="email">Email address</label>
<label for="email">{{#translate}}Email address{{/translate}}</label>
<input type="email" class="form-control" name="email" id="email" placeholder="" value="{{email}}" autofocus required>
</div>
<div class="form-group">
<button type="submit" id="unsubscribe-button" class="btn btn-primary">Unsubscribe</button>
<button type="submit" id="unsubscribe-button" class="btn btn-primary">{{#translate}}Unsubscribe{{/translate}}</button>
</div>
</form>

Some files were not shown because too many files have changed in this diff Show more