Extracted strings and fixes on localization support
Language chooser in the UI
This commit is contained in:
parent
9f449c0a2f
commit
dc7789c17b
126 changed files with 2919 additions and 2028 deletions
|
@ -142,7 +142,7 @@ function createApp(appType) {
|
|||
app.disable('x-powered-by');
|
||||
|
||||
app.use(compression());
|
||||
app.use(favicon(path.join(__dirname, 'client', 'static', 'favicon.ico')));
|
||||
app.use(favicon(path.join(__dirname, '..', 'client', 'static', 'favicon.ico')));
|
||||
|
||||
app.use(logger(config.www.log, {
|
||||
stream: {
|
||||
|
@ -167,7 +167,7 @@ function createApp(appType) {
|
|||
query: {
|
||||
name: 'language'
|
||||
},
|
||||
default: 'en_US'
|
||||
default: config.defaultLanguage
|
||||
}));
|
||||
|
||||
app.use(flash());
|
||||
|
@ -191,9 +191,9 @@ function createApp(appType) {
|
|||
app.use(passport.tryAuthByRestrictedAccessToken);
|
||||
}
|
||||
|
||||
useWith404Fallback('/static', express.static(path.join(__dirname, 'client', 'static')));
|
||||
useWith404Fallback('/mailtrain', express.static(path.join(__dirname, 'client', 'dist')));
|
||||
useWith404Fallback('/locales', express.static(path.join(__dirname, 'client', 'locales')));
|
||||
useWith404Fallback('/static', express.static(path.join(__dirname, '..', 'client', 'static')));
|
||||
useWith404Fallback('/mailtrain', express.static(path.join(__dirname, '..', 'client', 'dist')));
|
||||
useWith404Fallback('/locales', express.static(path.join(__dirname, '..', 'client', 'locales')));
|
||||
|
||||
|
||||
// Make sure flash messages are available
|
||||
|
|
|
@ -24,7 +24,12 @@ editors:
|
|||
- codeeditor
|
||||
|
||||
# Default language to use
|
||||
language: en
|
||||
defaultLanguage: en_US
|
||||
|
||||
# Enabled languages
|
||||
enabledLanguages:
|
||||
- en_US
|
||||
- es
|
||||
|
||||
# Inject custom scripts in subscription/layout.mjml.hbs
|
||||
# customSubscriptionScripts: [/custom/hello-world.js]
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
const config = require('config');
|
||||
const log = require('./lib/log');
|
||||
const appBuilder = require('./app-builder');
|
||||
const translate = require('./lib/translate');
|
||||
const http = require('http');
|
||||
const triggers = require('./services/triggers');
|
||||
const importer = require('./lib/importer');
|
||||
|
|
|
@ -12,7 +12,8 @@ async function getAnonymousConfig(context, appType) {
|
|||
authMethod: passport.authMethod,
|
||||
isAuthMethodLocal: passport.isAuthMethodLocal,
|
||||
externalPasswordResetLink: config.ldap.passwordresetlink,
|
||||
language: config.language || 'en',
|
||||
defaultLanguage: config.defaultLanguage,
|
||||
enabledLanguages: config.enabledLanguages,
|
||||
isAuthenticated: !!context.user,
|
||||
trustedUrlBase: urls.getTrustedUrlBase(),
|
||||
trustedUrlBaseDir: urls.getTrustedUrlBaseDir(),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const config = require('config');
|
||||
|
||||
const knex = require('server/lib/knex')({
|
||||
const knex = require('knex')({
|
||||
client: 'mysql2',
|
||||
connection: config.mysql,
|
||||
migrations: {
|
||||
|
|
|
@ -17,8 +17,6 @@ const htmlToText = require('html-to-text');
|
|||
|
||||
const bluebird = require('bluebird');
|
||||
|
||||
const _ = require('./translate')._;
|
||||
|
||||
const transports = new Map();
|
||||
|
||||
async function getOrCreateMailer(sendConfigurationId) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const nodeify = require('server/lib/nodeify');
|
||||
const nodeify = require('nodeify');
|
||||
|
||||
module.exports.nodeifyPromise = nodeify;
|
||||
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
|
||||
const config = require('config');
|
||||
const log = require('./log');
|
||||
const _ = require('./translate')._;
|
||||
const util = require('util');
|
||||
|
||||
const passport = require('server/lib/passport');
|
||||
const passport = require('passport');
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
|
||||
const csrf = require('csurf');
|
||||
|
|
|
@ -26,7 +26,7 @@ async function sendSubscriptionConfirmed(lang, list, email, subscription) {
|
|||
unsubscribeUrl: '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid
|
||||
};
|
||||
|
||||
await _sendMail(list, email, 'subscription_confirmed', lang, tMark('subscription.confirmed'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'subscription_confirmed', lang, tMark('subscriptionconfirmed'), relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendAlreadySubscribed(lang, list, email, subscription) {
|
||||
|
@ -34,35 +34,35 @@ async function sendAlreadySubscribed(lang, list, email, subscription) {
|
|||
preferencesUrl: '/subscription/' + list.cid + '/manage/' + subscription.cid,
|
||||
unsubscribeUrl: '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid
|
||||
};
|
||||
await _sendMail(list, email, 'already_subscribed', lang, tMark('subscription.alreadyRegistered'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'already_subscribed', lang, tMark('listEmailAddressAlreadyRegistered'), relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendConfirmAddressChange(lang, list, email, cid, subscription) {
|
||||
const relativeUrls = {
|
||||
confirmUrl: '/subscription/confirm/change-address/' + cid
|
||||
};
|
||||
await _sendMail(list, email, 'confirm_address_change', lang, tMark('subscription.confirmEmailChange'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'confirm_address_change', lang, tMark('listPleaseConfirmEmailChangeIn'), relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendConfirmSubscription(lang, list, email, cid, subscription) {
|
||||
const relativeUrls = {
|
||||
confirmUrl: '/subscription/confirm/subscribe/' + cid
|
||||
};
|
||||
await _sendMail(list, email, 'confirm_subscription', lang, tMark('subscription.confirmSubscription'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'confirm_subscription', lang, tMark('pleaseConfirmSubscription'), relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendConfirmUnsubscription(lang, list, email, cid, subscription) {
|
||||
const relativeUrls = {
|
||||
confirmUrl: '/subscription/confirm/unsubscribe/' + cid
|
||||
};
|
||||
await _sendMail(list, email, 'confirm_unsubscription', lang, tMark('subscription.confirmUnsubscription'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'confirm_unsubscription', lang, tMark('listPleaseConfirmUnsubscription'), relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendUnsubscriptionConfirmed(lang, list, email, subscription) {
|
||||
const relativeUrls = {
|
||||
subscribeUrl: '/subscription/' + list.cid + '?cid=' + subscription.cid
|
||||
};
|
||||
await _sendMail(list, email, 'unsubscription_confirmed', lang, tMark('subscription.unsubscriptionConfirmed'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'unsubscription_confirmed', lang, tMark('listUnsubscriptionConfirmed'), relativeUrls, subscription);
|
||||
}
|
||||
|
||||
function getDisplayName(flds, subscription) {
|
||||
|
|
|
@ -101,11 +101,11 @@ function validateEmailGetMessage(result, address, language) {
|
|||
if (result !== 0) {
|
||||
switch (result) {
|
||||
case 5:
|
||||
return t('addressCheck.mxNotFound', {email: address});
|
||||
return t('invalidEmailAddressEmailMxRecordNotFound', {email: address});
|
||||
case 6:
|
||||
return t('addressCheck.domainNotFound', {email: address});
|
||||
return t('invalidEmailAddressEmailAddressDomainNot', {email: address});
|
||||
case 12:
|
||||
return t('addressCheck.domainRequired', {email: address});
|
||||
return t('invalidEmailAddressEmailAddressDomain', {email: address});
|
||||
default:
|
||||
return t('invalidEmailGeneric', {email: address});
|
||||
}
|
||||
|
|
|
@ -1,31 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
|
||||
const i18n = require("i18next");
|
||||
const Backend = require("i18next-node-fs-backend");
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {convertToFake, langCodes} = require('../../shared/langs');
|
||||
|
||||
const resourcesCommon = {};
|
||||
|
||||
function loadLanguage(shortCode) {
|
||||
resourcesCommon[shortCode] = {
|
||||
common: JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'locales', shortCode, 'common.json')))
|
||||
};
|
||||
}
|
||||
|
||||
loadLanguage('en');
|
||||
loadLanguage('es');
|
||||
resourcesCommon.fake = convertToFake(resourcesCommon.en);
|
||||
|
||||
const resources = {};
|
||||
for (const lng of config.enabledLanguages) {
|
||||
const shortCode = langCodes[lng].shortCode;
|
||||
resources[shortCode] = {
|
||||
common: resourcesCommon[shortCode]
|
||||
};
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(Backend)
|
||||
// .use(Cache)
|
||||
.init({
|
||||
lng: config.language,
|
||||
|
||||
resources,
|
||||
wait: true, // globally set to wait for loaded translations in translate hoc
|
||||
|
||||
// have a common namespace used around the full app
|
||||
ns: ['common'],
|
||||
fallbackLng: config.defaultLanguage,
|
||||
defaultNS: 'common',
|
||||
|
||||
debug: true,
|
||||
|
||||
backend: {
|
||||
loadPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json')
|
||||
}
|
||||
debug: false
|
||||
})
|
||||
|
||||
|
||||
|
||||
function tLog(key, args) {
|
||||
if (!args) {
|
||||
args = {};
|
||||
|
|
|
@ -11,7 +11,6 @@ const bluebird = require('bluebird');
|
|||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const mjml = require('mjml');
|
||||
const _ = require('../lib/translate')._;
|
||||
const lists = require('./lists');
|
||||
const dependencyHelpers = require('../lib/dependency-helpers');
|
||||
|
||||
|
|
|
@ -310,7 +310,7 @@ async function sendPasswordReset(language, usernameOrEmail) {
|
|||
to: {
|
||||
address: user.email
|
||||
},
|
||||
subject: tUI('account.passwordChangeRequest', language)
|
||||
subject: tUI('mailerPasswordChangeRequest', language)
|
||||
}, {
|
||||
html: 'emails/password-reset-html.hbs',
|
||||
text: 'emails/password-reset-text.hbs',
|
||||
|
|
|
@ -75,7 +75,6 @@
|
|||
"html-to-text": "^4.0.0",
|
||||
"humanize": "0.0.9",
|
||||
"i18next": "^12.0.0",
|
||||
"i18next-node-fs-backend": "^2.1.0",
|
||||
"isemail": "^3.2.0",
|
||||
"jsdom": "^13.0.0",
|
||||
"juice": "^5.0.1",
|
||||
|
|
|
@ -62,7 +62,7 @@ router.postAsync('/subscribe/:listCid', passport.loggedIn, async (req, res) => {
|
|||
};
|
||||
|
||||
const confirmCid = await confirmations.addConfirmation(list.id, 'subscribe', req.ip, data);
|
||||
await mailHelpers.sendConfirmSubscription(config.language, list, input.EMAIL, confirmCid, subscription);
|
||||
await mailHelpers.sendConfirmSubscription(req.language, list, input.EMAIL, confirmCid, subscription);
|
||||
|
||||
res.status(200);
|
||||
res.json({
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('../lib/passport');
|
||||
const _ = require('../lib/translate')._;
|
||||
const clientHelpers = require('../lib/client-helpers');
|
||||
const { getTrustedUrl } = require('../lib/urls');
|
||||
const { AppType } = require('../../shared/app');
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const users = require('../../models/users');
|
||||
const contextHelpers = require('../../lib/context-helpers');
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const namespaces = require('../../models/namespaces');
|
||||
|
||||
const router = require('../../lib/router-async').create();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const reportTemplates = require('../../models/report-templates');
|
||||
|
||||
const router = require('../../lib/router-async').create();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const reports = require('../../models/reports');
|
||||
const reportProcessor = require('../../lib/report-processor');
|
||||
const reportHelpers = require('../../lib/report-helpers');
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const shares = require('../../models/shares');
|
||||
|
||||
const router = require('../../lib/router-async').create();
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
const config = require('config');
|
||||
const passport = require('../../lib/passport');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const users = require('../../models/users');
|
||||
const shares = require('../../models/shares');
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ function getRouter(appType) {
|
|||
});
|
||||
|
||||
// This is a fallback to versafix-1 if the block thumbnail is not defined by the template
|
||||
router.use('/templates/:mosaicoTemplateId/edres', express.static(path.join(__dirname, '..', 'client', 'static', 'mosaico', 'templates', 'versafix-1', 'edres')));
|
||||
router.use('/templates/:mosaicoTemplateId/edres', express.static(path.join(__dirname, '..', '..', 'client', 'static', 'mosaico', 'templates', 'versafix-1', 'edres')));
|
||||
|
||||
|
||||
fileHelpers.installUploadHandler(router, '/upload/:type/:entityId', files.ReplacementBehavior.RENAME, null, 'file', resp => {
|
||||
|
@ -185,10 +185,10 @@ function getRouter(appType) {
|
|||
const mailtrainConfig = await clientHelpers.getAnonymousConfig(req.context, appType);
|
||||
|
||||
let languageStrings = null;
|
||||
if (config.language && config.language !== 'en') {
|
||||
const lang = config.language.split('_')[0];
|
||||
const lang = req.language;
|
||||
if (lang && lang !== 'en') {
|
||||
try {
|
||||
const file = path.join(__dirname, '..', 'client', 'static', 'mosaico', 'lang', 'mosaico-' + lang + '.json');
|
||||
const file = path.join(__dirname, '..', '..', 'client', 'static', 'mosaico', 'lang', 'mosaico-' + lang + '.json');
|
||||
languageStrings = await fs.readFile(file, 'utf8');
|
||||
} catch (err) {
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ function getRouter(appType) {
|
|||
|
||||
const mosaicoLegacyUrlPrefix = getTrustedUrl(`mosaico/uploads/`);
|
||||
if (url.startsWith(mosaicoLegacyUrlPrefix)) {
|
||||
filePath = path.join(__dirname, '..', 'client', 'static' , 'mosaico', 'uploads', url.substring(mosaicoLegacyUrlPrefix.length));
|
||||
filePath = path.join(__dirname, '..', '..', 'client', 'static' , 'mosaico', 'uploads', url.substring(mosaicoLegacyUrlPrefix.length));
|
||||
} else {
|
||||
const file = await files.getFileByUrl(contextHelpers.getAdminContext(), url);
|
||||
filePath = file.path;
|
||||
|
|
|
@ -141,7 +141,7 @@ router.getAsync('/confirm/change-address/:cid', async (req, res) => {
|
|||
|
||||
await mailHelpers.sendSubscriptionConfirmed(req.language, list, data.emailNew, subscription);
|
||||
|
||||
req.flash('info', tUI('subscription.emailChanged', req.language));
|
||||
req.flash('info', tUI('emailAddressChanged', req.language));
|
||||
res.redirect('/subscription/' + encodeURIComponent(list.cid) + '/manage/' + subscription.cid);
|
||||
});
|
||||
|
||||
|
@ -244,7 +244,7 @@ router.postAsync('/:cid/subscribe', passport.parseForm, corsOrCsrfProtection, as
|
|||
throw new Error('Email address not set');
|
||||
}
|
||||
|
||||
req.flash('danger', tUI('subscription.addressNotSet', req.language));
|
||||
req.flash('danger', tUI('emailAddressNotSet', req.language));
|
||||
return await _renderSubscribe(req, res, list, subscriptionData);
|
||||
}
|
||||
|
||||
|
@ -300,7 +300,7 @@ router.postAsync('/:cid/subscribe', passport.parseForm, corsOrCsrfProtection, as
|
|||
|
||||
if (req.xhr) {
|
||||
return res.status(200).json({
|
||||
msg: tUI('subscription.confirmSubscription', req.language)
|
||||
msg: tUI('pleaseConfirmSubscription', req.language)
|
||||
});
|
||||
}
|
||||
res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '/confirm-subscription-notice');
|
||||
|
@ -457,7 +457,7 @@ router.postAsync('/:lcid/manage-address', passport.parseForm, passport.csrfProte
|
|||
}
|
||||
|
||||
if (subscription.email === emailNew) {
|
||||
req.flash('info', tUI('subscription.nothingChanged', req.language));
|
||||
req.flash('info', tUI('nothingSeemsToBeChanged', req.language));
|
||||
|
||||
} else {
|
||||
const emailErr = await tools.validateEmail(emailNew);
|
||||
|
@ -489,7 +489,7 @@ router.postAsync('/:lcid/manage-address', passport.parseForm, passport.csrfProte
|
|||
await mailHelpers.sendConfirmAddressChange(req.language, list, emailNew, confirmCid, subscription);
|
||||
}
|
||||
|
||||
req.flash('info', tUI('subscription.furtherInstructionsSent', req.language));
|
||||
req.flash('info', tUI('anEmailWithFurtherInstructionsHasBeen', req.language));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,14 +127,14 @@ async function run() {
|
|||
}
|
||||
|
||||
if (added > 0) {
|
||||
checkStatus = tLog('feedCheck.campaignsAdded', {addedMessages: added, campaignId: rssCampaign.id});
|
||||
checkStatus = tLog('foundAddedMessagesNewCampaignMessages', {addedMessages: added, campaignId: rssCampaign.id});
|
||||
log.verbose('Feed', `Found ${added} new campaigns messages from feed ${rssCampaign.id}`);
|
||||
|
||||
process.send({
|
||||
type: 'entries-added'
|
||||
});
|
||||
} else {
|
||||
checkStatus = tLog('feedCheck.nothingNew');
|
||||
checkStatus = tLog('foundNothingNewFromTheFeed');
|
||||
}
|
||||
|
||||
rssCampaign.data.checkStatus = checkStatus;
|
||||
|
|
|
@ -292,7 +292,7 @@ async function basicSubscribe(impt) {
|
|||
let errorMsg;
|
||||
|
||||
if (!email) {
|
||||
errorMsg = tLog('importer.missingEmail');
|
||||
errorMsg = tLog('missingEmail');
|
||||
}
|
||||
|
||||
if (mappingSettings.checkEmails) {
|
||||
|
@ -329,7 +329,7 @@ async function basicUnsubscribe(impt) {
|
|||
let errorMsg;
|
||||
|
||||
if (!email) {
|
||||
errorMsg = tLog('importer.missingEmail');
|
||||
errorMsg = tLog('missingEmail');
|
||||
}
|
||||
|
||||
if (!errorMsg) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" href="/static/favicon.ico">
|
||||
<link rel="icon" href="{{publicPath}}static/favicon.ico">
|
||||
|
||||
<title>Mailtrain</title>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
|
||||
<link rel="shortcut icon" href="{{publicPath}}favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="{{publicPath}}static/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="{{publicPath}}static/favicon.ico">
|
||||
|
||||
<title>Mailtrain</title>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
|
||||
<link rel="shortcut icon" href="{{publicPath}}favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="{{publicPath}}static/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="{{publicPath}}static/favicon.ico">
|
||||
|
||||
<title>Mailtrain</title>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
|
||||
<link rel="shortcut icon" href="{{publicPath}}favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="{{publicPath}}static/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="{{publicPath}}static/favicon.ico">
|
||||
|
||||
<title>Mailtrain</title>
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
|
||||
<link rel="shortcut icon" href="{{publicPath}}favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="/static/favicon.ico">
|
||||
<link rel="shortcut icon" href="{{publicPath}}static/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="{{publicPath}}static/favicon.ico">
|
||||
|
||||
<title>Mailtrain
|
||||
{{#if title}} | {{title}}{{/if}}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
|
||||
<link rel="shortcut icon" href="{{publicPath}}favicon.ico" type="image/x-icon" />
|
||||
<link rel="shortcut icon" href="{{publicPath}}static/favicon.ico" type="image/x-icon" />
|
||||
<link rel="icon" href="{{publicPath}}static/favicon.ico">
|
||||
|
||||
<title>Mailtrain</title>
|
||||
|
|
|
@ -7,7 +7,6 @@ const subscriptions = require('../../models/subscriptions');
|
|||
const campaigns = require('../../models/campaigns');
|
||||
const handlebars = require('handlebars');
|
||||
const handlebarsHelpers = require('../../lib/handlebars-helpers');
|
||||
const _ = require('../../lib/translate')._;
|
||||
const hbs = require('hbs');
|
||||
const vm = require('vm');
|
||||
const log = require('../../lib/log');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue