initial translations support
This commit is contained in:
parent
811d7b51b9
commit
ba8bd12123
18 changed files with 503 additions and 43 deletions
16
Gruntfile.js
16
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']);
|
||||
};
|
||||
|
|
42
README.md
42
README.md
|
@ -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
23
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
|
||||
|
|
|
@ -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
BIN
languages/et.mo
Normal file
Binary file not shown.
106
languages/et.po
Normal file
106
languages/et.po
Normal 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
100
languages/mailtrain.pot
Normal 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 ""
|
|
@ -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
35
lib/translate.js
Normal 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);
|
||||
};
|
32
package.json
32
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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!')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
122
tasks/jsxgettext.js
Normal 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();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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/">
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
Loading…
Reference in a new issue