diff --git a/Gruntfile.js b/Gruntfile.js
index 3dde0d9e..6ac983cc 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -10,13 +10,27 @@ module.exports = function (grunt) {
nodeunit: {
all: ['test/**/*-test.js']
+ },
+
+ jsxgettext: {
+ test: {
+ files: [{
+ src: ['views/**/*.hbs', 'lib/**/*.js', 'routes/**/*.js', 'services/**/*.js', 'app.js', 'index.js', '!ignored'],
+ output: 'mailtrain.pot',
+ 'output-dir': './languages/'
+ }],
+ options: {
+ keyword: ['translate', '_']
+ }
+ }
}
});
// Load the plugin(s)
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-contrib-nodeunit');
+ grunt.task.loadTasks('tasks');
// Tasks
- grunt.registerTask('default', ['eslint', 'nodeunit']);
+ grunt.registerTask('default', ['eslint', 'nodeunit', 'jsxgettext']);
};
diff --git a/README.md b/README.md
index 5dcf4a30..77c29807 100644
--- a/README.md
+++ b/README.md
@@ -178,6 +178,46 @@ node setup/fakedata.js > somefile.csv
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
+
+### Translating JavaScript files
+
+To translate JavaScript strings you need to make sure that you have loaded the translating function `_` from *'./lib/translate.js'*. If you want to use variables in strings then you also need the *'util'* module.
+
+```javascript
+const _ = require('./path/to/lib/translate')._;
+const util = require('util'); // optional
+```
+
+All you need to do to translate strings is to enclose these in the `_()` function
+
+```javascript
+let str1 = _('This string will be translated');
+let str2 = util.format( _('My name is "%s"'), 'Mailtrain');
+```
+
+### Translating Handlebars files
+
+Enclose translatable strings to `{{#translate}}` tags
+
+```handlebars
+
+ Mailtrain – {{#translate}}the best newsletter app{{/translate}}
+
+```
+
+### Managing translations
+
+* Translations are loaded from Gettext MO files. In order to generate such files you need a Gettext translations editor. [POEdit](https://poedit.net/) is a great choice.
+* To update the translation catalog ([mailtrain.pot](./languages/mailtrain.pot)) run `grunt` from command line. This fetches all translatable strings from JavaScript and Handlebars files and merges these into the translation catalog
+* To add a new language use this catalog file as source. Once you want to update your translation file from the updated catalog, then select "Catalogue" -> "Update from POT file..." in POEdit and select mailtrain.pot. This would merge all new translations from the POT file to your PO file.
+ *If you have saved the PO file in [./languages](./languages) then POEdit should auto generate required MO file whenever you hit save for the PO file.
+* Once you have a correct MO file in the languages folder, then edit Mailtrain config and set ["language" option](https://github.com/andris9/mailtrain/blob/ba8bd1212335cb9bd7ba094beb7b5400f35cae6c/config/default.toml#L30-L31) to your language name. If the value is "et" then Mailtrain loads translations from ./languages/et.mo
+
+> **NB!** For now translation settings are global, so if you have set a translation in config then this applies to all users. An user can't select another translation than the default even if there is a translation file. This is because current Mailtrain code does not provide request context to functions and the functions generating strings do not know which language to use.
+
## License
* Versions 1.22.0 and up **GPL-V3.0**
diff --git a/app.js b/app.js
index 36525f50..52b94b1c 100644
--- a/app.js
+++ b/app.js
@@ -3,6 +3,9 @@
let config = require('config');
let log = require('npmlog');
+let translate = require('./lib/translate');
+let util = require('util');
+
let express = require('express');
let bodyParser = require('body-parser');
let path = require('path');
@@ -93,6 +96,21 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer
);
});
+// {{#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
+
+ if (Array.isArray(context)) {
+ result = util.format(result, ...context);
+ }
+ return new hbs.handlebars.SafeString(result);
+});
+
app.use(compression());
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
@@ -118,6 +136,11 @@ app.use(session({
}));
app.use(flash());
+app.use((req, res, next) => {
+ req._ = str => translate._(str);
+ next();
+});
+
app.use(bodyParser.urlencoded({
extended: true,
limit: config.www.postsize
diff --git a/config/default.toml b/config/default.toml
index aee1c248..c547295e 100644
--- a/config/default.toml
+++ b/config/default.toml
@@ -27,6 +27,9 @@ editors=[
["codeeditor", "Code Editor"]
]
+# Default language to use
+language="en"
+
# If you start out as a root user (eg. if you want to use ports lower than 1000)
# then you can downgrade the user once all services are up and running
#user="nobody"
diff --git a/languages/et.mo b/languages/et.mo
new file mode 100644
index 00000000..69f72405
Binary files /dev/null and b/languages/et.mo differ
diff --git a/languages/et.po b/languages/et.po
new file mode 100644
index 00000000..5a82f24d
--- /dev/null
+++ b/languages/et.po
@@ -0,0 +1,106 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-03-04 18:00+0200\n"
+"PO-Revision-Date: 2017-03-04 18:00+0200\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: et\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.12\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: views/index.hbs:1
+msgid "Official Mailtrain Partners"
+msgstr "Ametlikud Mailtraini partnerid"
+
+#: views/layout.hbs:1 routes/index.js:11
+msgid "Self hosted email newsletter app"
+msgstr "Enda majutatud e-posti uudiskirjade rakendus"
+
+#: routes/settings.js:23
+msgid "Need to be logged in to access restricted content"
+msgstr "Pead olema sisse logitud, et näha peidetud sisu"
+
+#: routes/settings.js:39
+msgid "Use TLS"
+msgstr "Kasuta TLSi"
+
+#: routes/settings.js:40
+msgid "usually selected for port 465"
+msgstr "tavaliselt valitakse, kui port on 465"
+
+#: routes/settings.js:44
+msgid "Use STARTTLS"
+msgstr "Kasuta STARTTLSi"
+
+#: routes/settings.js:45
+msgid "usually selected for port 587 and 25"
+msgstr "tavaliselt valitakse, kui port on 587 või 25"
+
+#: routes/settings.js:49
+msgid "Do not use encryption"
+msgstr "Ära kasuta ühenduse krüpteerimist"
+
+#: routes/settings.js:115
+msgid "Settings updated"
+msgstr "Seaded uuendatud"
+
+#: routes/settings.js:173
+msgid "Invalid mail transport type"
+msgstr "Viga maili transpordi tüüp"
+
+#: routes/settings.js:184
+msgid "Invalid Access Key"
+msgstr "Vigae ligipääsuvõti"
+
+#: routes/settings.js:187
+msgid "Invalid AWS credentials"
+msgstr "VIgased AWS võtmed"
+
+#: routes/settings.js:190
+msgid "Connection refused, check hostname and port."
+msgstr "Ühendusest keelduti, kontrolli domeeninime ja porti"
+
+#: routes/settings.js:195
+msgid ""
+"Did not receive greeting message from server. This might happen when "
+"connecting to a TLS port without using TLS."
+msgstr ""
+"Ei saanud serverilt vastust, see juhtub tavaliselt kui ühendus TLS "
+"serverisse ilma TLS kasutamata"
+
+#: routes/settings.js:197
+msgid "Did not receive greeting message from server."
+msgstr "Ei saanud serverilt vastust"
+
+#: routes/settings.js:200
+msgid ""
+"Connection timed out. Check your firewall settings, destination port is "
+"probably blocked."
+msgstr ""
+"Ühendus aegus. Kontrolli oma tulemüüri seadeid, tõenäoliselt on serveir port "
+"blokeeritud"
+
+#: routes/settings.js:205
+msgid "Authentication not accepted, server expects STARTTLS to be used."
+msgstr "Autentimist ei lubatud, server nõuab STARTTLS kasutamist"
+
+#: routes/settings.js:207
+msgid "Authentication failed, check username and password."
+msgstr "Autentimine ebaõnnestus, kontrolli kasutajanime ja parooli"
+
+#: routes/settings.js:217
+msgid "Failed Mailer verification."
+msgstr "E-posti seadistuse kontroll ebaõnnestus"
+
+#: routes/settings.js:217
+msgid "Server responded with: \"%s\""
+msgstr "Server vastas \"%s\""
+
+#: routes/settings.js:221
+msgid "Mailer settings verified, ready to send some mail!"
+msgstr "E-posti seaded on kontrollitud, võid hakata kirju saatma"
diff --git a/languages/mailtrain.pot b/languages/mailtrain.pot
new file mode 100644
index 00000000..9d882c15
--- /dev/null
+++ b/languages/mailtrain.pot
@@ -0,0 +1,100 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Language-Team: LANGUAGE \n"
+"Report-Msgid-Bugs-To: \n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"POT-Creation-Date: 2017-03-04 16:00+0000\n"
+
+#: views/index.hbs:1
+msgid "Official Mailtrain Partners"
+msgstr ""
+
+#: views/layout.hbs:1
+#: routes/index.js:11
+msgid "Self hosted email newsletter app"
+msgstr ""
+
+#: routes/settings.js:23
+msgid "Need to be logged in to access restricted content"
+msgstr ""
+
+#: routes/settings.js:39
+msgid "Use TLS"
+msgstr ""
+
+#: routes/settings.js:40
+msgid "usually selected for port 465"
+msgstr ""
+
+#: routes/settings.js:44
+msgid "Use STARTTLS"
+msgstr ""
+
+#: routes/settings.js:45
+msgid "usually selected for port 587 and 25"
+msgstr ""
+
+#: routes/settings.js:49
+msgid "Do not use encryption"
+msgstr ""
+
+#: routes/settings.js:115
+msgid "Settings updated"
+msgstr ""
+
+#: routes/settings.js:173
+msgid "Invalid mail transport type"
+msgstr ""
+
+#: routes/settings.js:184
+msgid "Invalid Access Key"
+msgstr ""
+
+#: routes/settings.js:187
+msgid "Invalid AWS credentials"
+msgstr ""
+
+#: routes/settings.js:190
+msgid "Connection refused, check hostname and port."
+msgstr ""
+
+#: routes/settings.js:195
+msgid ""
+"Did not receive greeting message from server. This might happen when "
+"connecting to a TLS port without using TLS."
+msgstr ""
+
+#: routes/settings.js:197
+msgid "Did not receive greeting message from server."
+msgstr ""
+
+#: routes/settings.js:200
+msgid ""
+"Connection timed out. Check your firewall settings, destination port is "
+"probably blocked."
+msgstr ""
+
+#: routes/settings.js:205
+msgid "Authentication not accepted, server expects STARTTLS to be used."
+msgstr ""
+
+#: routes/settings.js:207
+msgid "Authentication failed, check username and password."
+msgstr ""
+
+#: routes/settings.js:217
+msgid "Failed Mailer verification."
+msgstr ""
+
+#: routes/settings.js:217
+msgid "Server responded with: \"%s\""
+msgstr ""
+
+#: routes/settings.js:221
+msgid "Mailer settings verified, ready to send some mail!"
+msgstr ""
\ No newline at end of file
diff --git a/lib/mailer.js b/lib/mailer.js
index 1a8e24ae..9f0f695b 100644
--- a/lib/mailer.js
+++ b/lib/mailer.js
@@ -148,7 +148,7 @@ function createMailer(callback) {
let level = args.shift();
args.shift();
args.unshift('Mail');
- log[level].apply(log, args);
+ log[level](...args);
};
if (configItems.mailTransport === 'smtp' || !configItems.mailTransport) {
diff --git a/lib/translate.js b/lib/translate.js
new file mode 100644
index 00000000..c6f44995
--- /dev/null
+++ b/lib/translate.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const config = require('config');
+
+const Gettext = require('node-gettext');
+const gt = new Gettext();
+const fs = require('fs');
+const path = require('path');
+const log = require('npmlog');
+const gettextParser = require('gettext-parser');
+
+const language = config.language || 'en';
+
+[].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);
+ }
+});
+
+module.exports._ = str => {
+ if (typeof str !== 'string') {
+ str = String(str);
+ }
+ return gt.dgettext(language, str);
+};
diff --git a/package.json b/package.json
index 48740848..5ca1eca7 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,10 @@
"sqlinit": "node setup/sql/init.js",
"sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain.sql",
"sqldrop": "node setup/sql/drop.js",
- "sqlgen": "npm run sqldrop && DB_FROM_START=Y npm run sqlinit && npm run sqldump"
+ "sqlgen": "npm run sqldrop && DB_FROM_START=Y npm run sqlinit && npm run sqldump",
+ "langs:hbs": "jsxgettext -L handlebars -k translate -o langs/hbs.pot views/layout.hbs views/index.hbs",
+ "langs:js": "jsxgettext -o languages/js.pot routes/index.js",
+ "langs": "npm run langs:hbs && npm run langs:js"
},
"repository": {
"type": "git",
@@ -23,15 +26,17 @@
"node": ">=5.0.0"
},
"devDependencies": {
+ "gettext-parser": "^1.2.2",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0",
- "grunt-eslint": "^19.0.0"
+ "grunt-eslint": "^19.0.0",
+ "jsxgettext-andris": "^0.9.0-patch.1"
},
"dependencies": {
- "aws-sdk": "^2.15.0",
+ "aws-sdk": "^2.22.0",
"bcrypt-nodejs": "0.0.3",
- "body-parser": "^1.16.1",
+ "body-parser": "^1.17.0",
"bounce-handler": "^7.3.2-fork.2",
"compression": "^1.6.2",
"config": "^1.25.1",
@@ -42,15 +47,15 @@
"csv-generate": "^1.0.0",
"csv-parse": "^1.2.0",
"escape-html": "^1.0.3",
- "express": "^4.14.1",
+ "express": "^4.15.0",
"express-session": "^1.15.1",
- "faker": "^3.1.0",
+ "faker": "^4.1.0",
"feedparser": "^2.1.0",
- "geoip-ultralight": "^0.1.4",
+ "geoip-ultralight": "^0.1.5",
"handlebars": "^4.0.6",
"hbs": "^4.0.1",
"he": "^1.1.1",
- "html-to-text": "^3.1.0",
+ "html-to-text": "^3.2.0",
"humanize": "0.0.9",
"is-url": "^1.2.2",
"isemail": "^2.2.1",
@@ -63,20 +68,21 @@
"morgan": "^1.8.1",
"multer": "^1.3.0",
"mysql": "^2.13.0",
- "nodemailer": "^3.1.3",
+ "nodemailer": "^3.1.4",
"nodemailer-openpgp": "^1.0.2",
"npmlog": "^4.0.2",
- "openpgp": "^2.3.7",
+ "openpgp": "^2.3.8",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"redfour": "^1.0.0",
"redis": "^2.6.5",
- "request": "^2.79.0",
- "serve-favicon": "^2.3.2",
+ "request": "^2.80.0",
+ "serve-favicon": "^2.4.1",
"shortid": "^2.2.6",
"slugify": "^1.1.0",
"smtp-server": "^2.0.2",
"striptags": "^3.0.1",
- "toml": "^2.3.1"
+ "toml": "^2.3.2",
+ "node-gettext": "^2.0.0-rc.0"
}
}
diff --git a/routes/index.js b/routes/index.js
index 61b78dd2..bf995165 100644
--- a/routes/index.js
+++ b/routes/index.js
@@ -2,12 +2,13 @@
let express = require('express');
let router = new express.Router();
+let _ = require('../lib/translate')._;
/* GET home page. */
router.get('/', (req, res) => {
res.render('index', {
indexPage: true,
- title: 'Self hosted email newsletter app'
+ title: _('Self hosted email newsletter app')
});
});
diff --git a/routes/settings.js b/routes/settings.js
index 5dab5aa9..6ac92446 100644
--- a/routes/settings.js
+++ b/routes/settings.js
@@ -11,6 +11,8 @@ let url = require('url');
let multer = require('multer');
let upload = multer();
let aws = require('aws-sdk');
+let util = require('util');
+let _ = require('../lib/translate')._;
let settings = require('../lib/models/settings');
@@ -18,7 +20,7 @@ let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption
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('/settings');
@@ -34,17 +36,17 @@ router.get('/', passport.csrfProtection, (req, res, next) => {
configItems.smtpEncryption = [{
checked: configItems.smtpEncryption === 'TLS' || !configItems.smtpEncryption,
key: 'TLS',
- value: 'Use TLS',
- description: 'usually selected for port 465'
+ value: _('Use TLS'),
+ description: _('usually selected for port 465')
}, {
checked: configItems.smtpEncryption === 'STARTTLS',
key: 'STARTTLS',
- value: 'Use STARTTLS',
- description: 'usually selected for port 587 and 25'
+ value: _('Use STARTTLS'),
+ description: _('usually selected for port 587 and 25')
}, {
checked: configItems.smtpEncryption === 'NONE',
key: 'NONE',
- value: 'Do not use encryption'
+ value: _('Do not use encryption')
}];
configItems.sesRegion = [{
@@ -110,7 +112,7 @@ router.post('/update', passport.parseForm, passport.csrfProtection, (req, res) =
reload: true
});
});
- req.flash('success', 'Settings updated');
+ req.flash('success', _('Settings updated'));
return res.redirect('/settings');
}
let key = keys[i];
@@ -168,7 +170,7 @@ router.post('/smtp-verify', upload.array(), passport.parseForm, passport.csrfPro
};
} else {
return res.json({
- error: 'Invalid mail transport type'
+ error: _('Invalid mail transport type')
});
}
@@ -179,30 +181,30 @@ router.post('/smtp-verify', upload.array(), passport.parseForm, passport.csrfPro
let message = '';
switch (err.code) {
case 'InvalidClientTokenId':
- message = 'Invalid Access Key';
+ message = _('Invalid Access Key');
break;
case 'SignatureDoesNotMatch':
- message = 'Invalid AWS credentials';
+ message = _('Invalid AWS credentials');
break;
case 'ECONNREFUSED':
- message = 'Connection refused, check hostname and port.';
+ message = _('Connection refused, check hostname and port.');
break;
case 'ETIMEDOUT':
if ((err.message || '').indexOf('Greeting never received') === 0) {
if (data.smtpEncryption !== 'TLS') {
- message = 'Did not receive greeting message from server. This might happen when connecting to a TLS port without using TLS.';
+ message = _('Did not receive greeting message from server. This might happen when connecting to a TLS port without using TLS.');
} else {
- message = 'Did not receive greeting message from server.';
+ message = _('Did not receive greeting message from server.');
}
} else {
- message = 'Connection timed out. Check your firewall settings, destination port is probably blocked.';
+ message = _('Connection timed out. Check your firewall settings, destination port is probably blocked.');
}
break;
case 'EAUTH':
if (/\b5\.7\.0\b/.test(err.message) && data.smtpEncryption !== 'STARTTLS') {
- message = 'Authentication not accepted, server expects STARTTLS to be used.';
+ message = _('Authentication not accepted, server expects STARTTLS to be used.');
} else {
- message = 'Authentication failed, check username and password.';
+ message = _('Authentication failed, check username and password.');
}
break;
@@ -212,11 +214,11 @@ router.post('/smtp-verify', upload.array(), passport.parseForm, passport.csrfPro
}
res.json({
- error: (message || 'Failed Mailer verification.') + (err.response ? ' Server responded with: "' + err.response + '"' : '')
+ error: (message || _('Failed Mailer verification.')) + (err.response ? ' ' + util.format(_('Server responded with: "%s"'), err.response) : '')
});
} else {
res.json({
- message: 'Mailer settings verified, ready to send some mail!'
+ message: _('Mailer settings verified, ready to send some mail!')
});
}
});
diff --git a/setup/sql/mailtrain.sql b/setup/sql/mailtrain.sql
index 70364321..5408c5e2 100644
--- a/setup/sql/mailtrain.sql
+++ b/setup/sql/mailtrain.sql
@@ -52,6 +52,8 @@ CREATE TABLE `campaigns` (
`segment` int(11) unsigned DEFAULT NULL,
`template` int(11) unsigned NOT NULL,
`source_url` varchar(255) CHARACTER SET ascii DEFAULT NULL,
+ `editor_name` varchar(50) DEFAULT '',
+ `editor_data` longtext,
`last_check` timestamp NULL DEFAULT NULL,
`check_status` varchar(255) DEFAULT NULL,
`from` varchar(255) DEFAULT '',
@@ -201,7 +203,7 @@ CREATE TABLE `segments` (
`created` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `list` (`list`),
- KEY `name` (`name`(191)),
+ KEY `name` (`name`),
CONSTRAINT `segments_ibfk_1` FOREIGN KEY (`list`) REFERENCES `lists` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `settings` (
@@ -210,7 +212,7 @@ CREATE TABLE `settings` (
`value` text NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key` (`key`)
-) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4;
+) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4;
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (1,'smtp_hostname','smtp-pulse.com');
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (2,'smtp_port','465');
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (3,'smtp_encryption','TLS');
@@ -227,7 +229,7 @@ INSERT INTO `settings` (`id`, `key`, `value`) VALUES (13,'default_from','My Awes
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (14,'default_address','admin@example.com');
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (15,'default_subject','Test message');
INSERT INTO `settings` (`id`, `key`, `value`) VALUES (16,'default_homepage','http://localhost:3000/');
-INSERT INTO `settings` (`id`, `key`, `value`) VALUES (17,'db_schema_version','20');
+INSERT INTO `settings` (`id`, `key`, `value`) VALUES (17,'db_schema_version','21');
CREATE TABLE `subscription` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`cid` varchar(255) CHARACTER SET ascii NOT NULL,
@@ -260,6 +262,8 @@ CREATE TABLE `templates` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL DEFAULT '',
`description` text,
+ `editor_name` varchar(50) DEFAULT '',
+ `editor_data` longtext,
`html` longtext,
`text` longtext,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
diff --git a/setup/sql/upgrade-00021.sql b/setup/sql/upgrade-00021.sql
index ca3a85ec..e3c54a48 100644
--- a/setup/sql/upgrade-00021.sql
+++ b/setup/sql/upgrade-00021.sql
@@ -4,11 +4,11 @@ SET @schema_version = '21';
# Add fields editor_name, editor_data to templates
ALTER TABLE `templates` ADD COLUMN `editor_name` varchar(50) DEFAULT '' AFTER `description`;
-ALTER TABLE `templates` ADD COLUMN `editor_data` longtext DEFAULT '' AFTER `editor_name`;
+ALTER TABLE `templates` ADD COLUMN `editor_data` longtext AFTER `editor_name`;
# Add fields editor_name, editor_data to campaigns
ALTER TABLE `campaigns` ADD COLUMN `editor_name` varchar(50) DEFAULT '' AFTER `source_url`;
-ALTER TABLE `campaigns` ADD COLUMN `editor_data` longtext DEFAULT '' AFTER `editor_name`;
+ALTER TABLE `campaigns` ADD COLUMN `editor_data` longtext AFTER `editor_name`;
# Footer section
LOCK TABLES `settings` WRITE;
diff --git a/tasks/jsxgettext.js b/tasks/jsxgettext.js
new file mode 100644
index 00000000..996f2c0f
--- /dev/null
+++ b/tasks/jsxgettext.js
@@ -0,0 +1,122 @@
+/*
+Original from https://raw.githubusercontent.com/Mindflash/grunt-jsxgettext/master/lib/index.js
+License: ISC
+*/
+
+/* eslint-disable */
+
+'use strict';
+let _ = require('lodash');
+let path = require('path');
+let util = require('util');
+let jsxgettext = require('jsxgettext-andris');
+let fs = require('fs');
+let async = require('async');
+
+/*
+ options: {
+ files: ['file-path','file-path'],
+ + jsxgettext options
+ }
+ */
+let task = function (grunt, options, cb) {
+ var generators = {
+ '.ejs': jsxgettext.generateFromEJS,
+ '.hbs': jsxgettext.generateFromHandlebars,
+ '.jade': jsxgettext.generateFromJade,
+ '.swig': jsxgettext.generateFromSwig
+ };
+
+ // dynamically update generators mapping
+ if (options.generators) {
+ if (!Array.isArray(options.generators)) {
+ options.generators = [options.generators];
+ }
+
+ options.generators.forEach(elem => {
+ // elem.generator can be either a string or a generator method (i.e. own generator or import from jsxgettext)
+ if (typeof elem.generator === 'string' || elem.generator instanceof String) {
+ // elem.generator can be an extension, which is used to remap predefined generators to different extensions
+ // or elem.generator is the name of an generator method implemented in jsxgettext
+ if (elem.generator.match(/^\..*/)) {
+ generators[elem.ext] = generators[elem.generator];
+ } else {
+ generators[elem.ext] = jsxgettext[elem.generator];
+ }
+ } else {
+ generators[elem.ext] = elem.generator;
+ }
+ });
+ }
+
+ var files = {};
+ var dest = options.dest;
+
+ if (!fs.existsSync(path.dirname(dest)))
+ return cb(util.format("Destination directory %s does not exist.", dest));
+
+ options.files.filter(file => grunt.file.exists(file)).forEach(file => {
+ var ext = path.extname(file);
+ var content = grunt.file.read(file, {
+ encoding: 'utf-8'
+ });
+
+ // pre-process non js files for use by jsxgettext.generate
+ if (ext !== '.js') {
+ var args = {};
+ args[file] = content;
+ files = _.assign(files, generators[ext](args, options).shift());
+ return;
+ }
+
+ files[file] = content;
+ });
+
+ if (_.isEmpty(files))
+ return cb("No valid input files found, received: " + util.inspect(options.files, {
+ depth: null
+ }));
+
+ cb(null, jsxgettext.generate(files, options));
+};
+
+function unary(fn) {
+ if (fn.length === 1) return fn;
+ return function (args) {
+ return fn.call(this, args);
+ };
+}
+
+module.exports = function (grunt) {
+ grunt.registerMultiTask('jsxgettext', 'A Grunt task to run jsxgettext against files to extract strings into a POT file.', function () {
+ var self = this;
+ var done = self.async();
+
+ async.forEach(self.files, function (fileSet, eCb) {
+ var dest;
+ if (typeof fileSet.dest !== 'undefined' && fileSet.dest) {
+ dest = fileSet.dest;
+ } else {
+ dest = path.join(fileSet['output-dir'] || '', fileSet.output);
+ }
+
+ var options = _.defaults(self.options(), {
+ files: fileSet.src,
+ dest: dest,
+ 'output-dir': fileSet['output-dir'],
+ output: fileSet['output']
+ });
+ task(grunt, options, function (err, res) {
+
+ if (err) return eCb(err);
+
+ grunt.file.write(dest, res);
+ eCb();
+ });
+ }, err => {
+ if (err) grunt.fatal(err);
+ done();
+ });
+
+ });
+};
diff --git a/test/frontmail-test.js b/test/frontmail-test.js
index e7e08d29..54221359 100644
--- a/test/frontmail-test.js
+++ b/test/frontmail-test.js
@@ -4,7 +4,9 @@ let nodemailer = require('nodemailer');
// This is a dummy test to ensure that nodeunit would not fail on 0 assertions
module.exports['Load nodemailer'] = function (test) {
- let transport = nodemailer.createTransport();
- test.ok(transport._getVersionString());
+ let transport = nodemailer.createTransport({
+ streamTransport: true
+ });
+ test.ok(transport);
test.done();
};
diff --git a/views/index.hbs b/views/index.hbs
index 97dcb74b..6c67c28f 100644
--- a/views/index.hbs
+++ b/views/index.hbs
@@ -1,6 +1,6 @@
-
Official Mailtrain Partners
+
{{#translate}}Official Mailtrain Partners{{/translate}}