Upgrade of modules and webpack.
Support for localization in progress.
This commit is contained in:
parent
d8b56fff0d
commit
4862d6cac4
52 changed files with 5870 additions and 23064 deletions
|
@ -1,26 +0,0 @@
|
|||
'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⊥∩ɅMX⅄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, '');
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const util = require('util');
|
||||
|
||||
const _ = require('../lib/translate')._;
|
||||
|
||||
module.exports.registerHelpers = handlebars => {
|
||||
// {{#translate}}abc{{/translate}}
|
||||
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);
|
||||
});
|
||||
|
||||
|
||||
/* Credits to http://chrismontrois.net/2016/01/30/handlebars-switch/
|
||||
|
||||
{{#switch letter}}
|
||||
{{#case "a"}}
|
||||
A is for alpaca
|
||||
{{/case}}
|
||||
{{#case "b"}}
|
||||
B is for bluebird
|
||||
{{/case}}
|
||||
{{/switch}}
|
||||
*/
|
||||
/* eslint no-invalid-this: "off" */
|
||||
handlebars.registerHelper('switch', function(value, options) {
|
||||
this._switch_value_ = value;
|
||||
const html = options.fn(this); // Process the body of the switch block
|
||||
delete this._switch_value_;
|
||||
return html;
|
||||
});
|
||||
|
||||
handlebars.registerHelper('case', function(value, options) {
|
||||
if (value === this._switch_value_) {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
|
@ -19,20 +19,6 @@ const bluebird = require('bluebird');
|
|||
|
||||
const _ = require('./translate')._;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
const transports = new Map();
|
||||
|
||||
async function getOrCreateMailer(sendConfigurationId) {
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
const log = require('npmlog');
|
||||
const fields = require('../models/fields');
|
||||
const settings = require('../models/settings');
|
||||
const {getTrustedUrl} = require('./urls');
|
||||
const _ = require('./translate')._;
|
||||
const {getTrustedUrl, getPublicUrl} = require('./urls');
|
||||
const { tUI } = require('./translate');
|
||||
const util = require('util');
|
||||
const contextHelpers = require('./context-helpers');
|
||||
const {getFieldColumn} = require('../shared/lists');
|
||||
|
@ -20,49 +20,49 @@ module.exports = {
|
|||
sendUnsubscriptionConfirmed
|
||||
};
|
||||
|
||||
async function sendSubscriptionConfirmed(list, email, subscription) {
|
||||
async function sendSubscriptionConfirmed(lang, list, email, subscription) {
|
||||
const relativeUrls = {
|
||||
preferencesUrl: '/subscription/' + list.cid + '/manage/' + subscription.cid,
|
||||
unsubscribeUrl: '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid
|
||||
};
|
||||
|
||||
await _sendMail(list, email, 'subscription_confirmed', _('%s: Subscription Confirmed'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'subscription_confirmed', lang, 'subscription.confirmed', relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendAlreadySubscribed(list, email, subscription) {
|
||||
async function sendAlreadySubscribed(lang, list, email, subscription) {
|
||||
const relativeUrls = {
|
||||
preferencesUrl: '/subscription/' + list.cid + '/manage/' + subscription.cid,
|
||||
unsubscribeUrl: '/subscription/' + list.cid + '/unsubscribe/' + subscription.cid
|
||||
};
|
||||
await _sendMail(list, email, 'already_subscribed', _('%s: Email Address Already Registered'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'already_subscribed', lang, 'subscription.alreadyRegistered', relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendConfirmAddressChange(list, email, cid, subscription) {
|
||||
async function sendConfirmAddressChange(lang, list, email, cid, subscription) {
|
||||
const relativeUrls = {
|
||||
confirmUrl: '/subscription/confirm/change-address/' + cid
|
||||
};
|
||||
await _sendMail(list, email, 'confirm_address_change', _('%s: Please Confirm Email Change in Subscription'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'confirm_address_change', lang, 'subscription.confirmEmailChange', relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendConfirmSubscription(list, email, cid, subscription) {
|
||||
async function sendConfirmSubscription(lang, list, email, cid, subscription) {
|
||||
const relativeUrls = {
|
||||
confirmUrl: '/subscription/confirm/subscribe/' + cid
|
||||
};
|
||||
await _sendMail(list, email, 'confirm_subscription', _('%s: Please Confirm Subscription'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'confirm_subscription', lang, 'subscription.confirmSubscription', relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendConfirmUnsubscription(list, email, cid, subscription) {
|
||||
async function sendConfirmUnsubscription(lang, list, email, cid, subscription) {
|
||||
const relativeUrls = {
|
||||
confirmUrl: '/subscription/confirm/unsubscribe/' + cid
|
||||
};
|
||||
await _sendMail(list, email, 'confirm_unsubscription', _('%s: Please Confirm Unsubscription'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'confirm_unsubscription', lang, 'subscription.confirmUnsubscription', relativeUrls, subscription);
|
||||
}
|
||||
|
||||
async function sendUnsubscriptionConfirmed(list, email, subscription) {
|
||||
async function sendUnsubscriptionConfirmed(lang, list, email, subscription) {
|
||||
const relativeUrls = {
|
||||
subscribeUrl: '/subscription/' + list.cid + '?cid=' + subscription.cid
|
||||
};
|
||||
await _sendMail(list, email, 'unsubscription_confirmed', _('%s: Unsubscription Confirmed'), relativeUrls, subscription);
|
||||
await _sendMail(list, email, 'unsubscription_confirmed', lang, 'subscription.unsubscriptionConfirmed', relativeUrls, subscription);
|
||||
}
|
||||
|
||||
function getDisplayName(flds, subscription) {
|
||||
|
@ -95,7 +95,7 @@ function getDisplayName(flds, subscription) {
|
|||
}
|
||||
}
|
||||
|
||||
async function _sendMail(list, email, template, subject, relativeUrls, subscription) {
|
||||
async function _sendMail(list, email, template, language, subjectKey, relativeUrls, subscription) {
|
||||
const flds = await fields.list(contextHelpers.getAdminContext(), list.id);
|
||||
|
||||
const encryptionKeys = [];
|
||||
|
@ -114,7 +114,7 @@ async function _sendMail(list, email, template, subject, relativeUrls, subscript
|
|||
};
|
||||
|
||||
for (let relativeUrlKey in relativeUrls) {
|
||||
data[relativeUrlKey] = getTrustedUrl(relativeUrls[relativeUrlKey]);
|
||||
data[relativeUrlKey] = getPublicUrl(relativeUrls[relativeUrlKey], {language});
|
||||
}
|
||||
|
||||
const fsTemplate = template.replace(/_/g, '-');
|
||||
|
@ -148,7 +148,7 @@ async function _sendMail(list, email, template, subject, relativeUrls, subscript
|
|||
name: getDisplayName(flds, subscription),
|
||||
address: email
|
||||
},
|
||||
subject: util.format(subject, list.name),
|
||||
subject: tUI(language, subjectKey, { list: list.name }),
|
||||
encryptionKeys
|
||||
}, {
|
||||
html,
|
||||
|
|
50
lib/tools.js
50
lib/tools.js
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
const _ = require('./translate')._;
|
||||
const util = require('util');
|
||||
const isemail = require('isemail');
|
||||
const path = require('path');
|
||||
|
@ -15,11 +14,12 @@ const mjml2html = mjml.default;
|
|||
|
||||
const hbs = require('hbs');
|
||||
const juice = require('juice');
|
||||
let he = require('he');
|
||||
const he = require('he');
|
||||
|
||||
const fsReadFile = bluebird.promisify(require('fs').readFile);
|
||||
const jsdomEnv = bluebird.promisify(require('jsdom').env);
|
||||
const fs = require('fs-extra');
|
||||
|
||||
const { JSDOM } = require('jsdom');
|
||||
const { tUI, tLog } = require('./translate');
|
||||
|
||||
|
||||
const templates = new Map();
|
||||
|
@ -39,7 +39,7 @@ async function getTemplate(template) {
|
|||
if (typeof template === 'object') {
|
||||
source = await mergeTemplateIntoLayout(template.template, template.layout);
|
||||
} else {
|
||||
source = await fsReadFile(path.join(__dirname, '..', 'views', template), 'utf-8');
|
||||
source = await fs.readFile(path.join(__dirname, '..', 'views', template), 'utf-8');
|
||||
}
|
||||
|
||||
if (template.type === 'mjml') {
|
||||
|
@ -63,7 +63,7 @@ async function mergeTemplateIntoLayout(template, layout) {
|
|||
layout = layout || '{{{body}}}';
|
||||
|
||||
async function readFile(relPath) {
|
||||
return await fsReadFile(path.join(__dirname, '..', 'views', relPath), 'utf-8');
|
||||
return await fs.readFile(path.join(__dirname, '..', 'views', relPath), 'utf-8');
|
||||
}
|
||||
|
||||
// Please dont end your custom messages with .hbs ...
|
||||
|
@ -90,21 +90,25 @@ async function validateEmail(address) {
|
|||
return result;
|
||||
}
|
||||
|
||||
function validateEmailGetMessage(result, address) {
|
||||
function validateEmailGetMessage(result, address, language) {
|
||||
let t;
|
||||
if (language) {
|
||||
t = (key, args) => tUI(language, key, args);
|
||||
} else {
|
||||
t = (key, args) => tLog(key, args);
|
||||
}
|
||||
|
||||
if (result !== 0) {
|
||||
let message = util.format(_('Invalid email address "%s".'), address);
|
||||
switch (result) {
|
||||
case 5:
|
||||
message += ' ' + _('MX record not found for domain');
|
||||
break;
|
||||
return t('addressCheck.mxNotFound', {email: address});
|
||||
case 6:
|
||||
message += ' ' + _('Address domain not found');
|
||||
break;
|
||||
return t('addressCheck.domainNotFound', {email: address});
|
||||
case 12:
|
||||
message += ' ' + _('Address domain name is required');
|
||||
break;
|
||||
return t('addressCheck.domainRequired', {email: address});
|
||||
default:
|
||||
return t('invalidEmailGeneric', {email: address});
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,17 +146,11 @@ async function prepareHtml(html) {
|
|||
return false;
|
||||
}
|
||||
|
||||
const win = await jsdomEnv(false, false, {
|
||||
html,
|
||||
features: {
|
||||
FetchExternalResources: false, // disables resource loading over HTTP / filesystem
|
||||
ProcessExternalResources: false // do not execute JS within script blocks
|
||||
}
|
||||
});
|
||||
const { window } = new JSDOM(html);
|
||||
|
||||
const head = win.document.querySelector('head');
|
||||
const head = window.document.querySelector('head');
|
||||
let hasCharsetTag = false;
|
||||
const metaTags = win.document.querySelectorAll('meta');
|
||||
const metaTags = window.document.querySelectorAll('meta');
|
||||
if (metaTags) {
|
||||
for (let i = 0; i < metaTags.length; i++) {
|
||||
if (metaTags[i].hasAttribute('charset')) {
|
||||
|
@ -163,11 +161,11 @@ async function prepareHtml(html) {
|
|||
}
|
||||
}
|
||||
if (!hasCharsetTag) {
|
||||
const charsetTag = win.document.createElement('meta');
|
||||
const charsetTag = window.document.createElement('meta');
|
||||
charsetTag.setAttribute('charset', 'utf-8');
|
||||
head.appendChild(charsetTag);
|
||||
}
|
||||
const preparedHtml = '<!doctype html><html>' + win.document.documentElement.innerHTML + '</html>';
|
||||
const preparedHtml = '<!doctype html><html>' + window.document.documentElement.innerHTML + '</html>';
|
||||
|
||||
return juice(preparedHtml);
|
||||
}
|
||||
|
|
|
@ -2,40 +2,45 @@
|
|||
|
||||
const config = require('config');
|
||||
|
||||
const Gettext = require('node-gettext');
|
||||
const gt = new Gettext();
|
||||
const fs = require('fs');
|
||||
const i18n = require("i18next");
|
||||
const Backend = require("i18next-node-fs-backend");
|
||||
|
||||
const path = require('path');
|
||||
const log = require('./log');
|
||||
const gettextParser = require('gettext-parser');
|
||||
const fakelang = require('./fakelang');
|
||||
|
||||
const language = config.language || 'en';
|
||||
i18n
|
||||
.use(Backend)
|
||||
// .use(Cache)
|
||||
.init({
|
||||
lng: config.language,
|
||||
|
||||
[].concat(config.language || []).forEach(lang => {
|
||||
let data;
|
||||
let file = path.join(__dirname, '..', 'languages', lang + '.mo');
|
||||
try {
|
||||
data = gettextParser.mo.parse(fs.readFileSync(file));
|
||||
} catch (E) {
|
||||
// ignore
|
||||
}
|
||||
if (data) {
|
||||
gt.addTranslations(lang, lang, data);
|
||||
gt.setTextDomain(lang);
|
||||
gt.setLocale(lang);
|
||||
log.info('LANG', 'Loaded language file for %s', lang);
|
||||
}
|
||||
});
|
||||
wait: true, // globally set to wait for loaded translations in translate hoc
|
||||
|
||||
module.exports._ = str => {
|
||||
if (typeof str !== 'string') {
|
||||
str = String(str);
|
||||
// have a common namespace used around the full app
|
||||
ns: ['common'],
|
||||
defaultNS: 'common',
|
||||
|
||||
debug: true,
|
||||
|
||||
backend: {
|
||||
loadPath: path.join(__dirname, 'locales/{{lng}}/{{ns}}.json')
|
||||
}
|
||||
})
|
||||
|
||||
function tLog(key, args) {
|
||||
if (!args) {
|
||||
args = {};
|
||||
}
|
||||
|
||||
if (language === 'zz') {
|
||||
return fakelang(str);
|
||||
return JSON.stringify([key, args]);
|
||||
}
|
||||
|
||||
function tUI(lang, key, args) {
|
||||
if (!args) {
|
||||
args = {};
|
||||
}
|
||||
|
||||
return gt.dgettext(language, str);
|
||||
};
|
||||
return i18n.t(key, { ...args, defaultValue, lng: lang });
|
||||
}
|
||||
|
||||
module.exports.tLog = tLog;
|
||||
module.exports.tUI = tUI;
|
24
lib/urls.js
24
lib/urls.js
|
@ -16,20 +16,30 @@ function getPublicUrlBase() {
|
|||
return urllib.resolve(config.www.publicUrlBase, '');
|
||||
}
|
||||
|
||||
function getTrustedUrl(path) {
|
||||
return urllib.resolve(config.www.trustedUrlBase, path || '');
|
||||
function _getUrl(urlBase, path, opts) {
|
||||
const url = new URL(path || '', urlBase);
|
||||
|
||||
if (opts && opts.language) {
|
||||
url.searchParams.append('lang', opts.language)
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function getSandboxUrl(path, context) {
|
||||
function getTrustedUrl(path, opts) {
|
||||
return _getUrl(config.www.trustedUrlBase, path || '', opts);
|
||||
}
|
||||
|
||||
function getSandboxUrl(path, context, opts) {
|
||||
if (context && context.user && context.user.restrictedAccessToken) {
|
||||
return urllib.resolve(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''));
|
||||
return _getUrl(config.www.sandboxUrlBase, context.user.restrictedAccessToken + '/' + (path || ''), opts);
|
||||
} else {
|
||||
return urllib.resolve(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''));
|
||||
return _getUrl(config.www.sandboxUrlBase, anonymousRestrictedAccessToken + '/' + (path || ''), opts);
|
||||
}
|
||||
}
|
||||
|
||||
function getPublicUrl(path) {
|
||||
return urllib.resolve(config.www.publicUrlBase, path || '');
|
||||
function getPublicUrl(path, opts) {
|
||||
return _getUrl(config.www.publicUrlBase, path || '', opts);
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue