initial translations support

This commit is contained in:
Andris Reinman 2017-03-04 18:15:16 +02:00
parent 811d7b51b9
commit ba8bd12123
18 changed files with 503 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,48 @@ 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](./langauges) 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 to your language name. If the value is "et" then Mailtrain loads translations from ./languages/et.mo
## 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">