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