Upgrade of modules and webpack.

Support for localization in progress.
This commit is contained in:
Tomas Bures 2018-11-17 23:26:45 +01:00
parent d8b56fff0d
commit 4862d6cac4
52 changed files with 5870 additions and 23064 deletions

View file

@ -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⊥∩Ʌ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

@ -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);
}
});
};

View file

@ -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) {

View file

@ -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,

View file

@ -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);
}

View file

@ -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;

View file

@ -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);
}