Merge remote-tracking branch 'upstream/master'

This commit is contained in:
witzig 2017-03-04 18:38:44 +01:00
commit d9c1d8e595
18 changed files with 501 additions and 43 deletions

View file

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

View file

@ -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
<p>
Mailtrain {{#translate}}the best newsletter app{{/translate}}
</p>
```
### 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**

23
app.js
View file

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

View file

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

BIN
languages/et.mo Normal file

Binary file not shown.

106
languages/et.po Normal file
View file

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

100
languages/mailtrain.pot Normal file
View file

@ -0,0 +1,100 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View file

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

35
lib/translate.js Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

122
tasks/jsxgettext.js Normal file
View file

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

View file

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

View file

@ -1,6 +1,6 @@
<div class="row">
<div class="col-md-4">
<h3>Official Mailtrain Partners</h3>
<h3>{{#translate}}Official Mailtrain Partners{{/translate}}</h3>
<div class="media">
<div class="media-left">
<a href="http://www.iredmail.org/">

View file

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Self hosted email newsletter app">
<meta name="description" content="{{#translate}}Self hosted email newsletter app{{/translate}}">
<meta name="author" content="Andris Reinman">
<link rel="icon" href="/favicon.ico">