From d25565b6f867c6adfef4e01056a5ed4c9113dc58 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 7 Mar 2017 16:30:56 +0200 Subject: [PATCH] Updated translation support --- .eslintrc | 3 + .eslintrc.js | 70 - README.md | 10 +- app.js | 12 +- languages/et.mo | Bin 2531 -> 3279 bytes languages/et.po | 2841 +++++- languages/mailtrain.pot | 3918 +++++++- lib/fakelang.js | 26 + lib/feed.js | 4 +- lib/helpers.js | 21 +- lib/mailer.js | 19 +- lib/models/campaigns.js | 25 +- lib/models/fields.js | 54 +- lib/models/links.js | 7 +- lib/models/lists.js | 13 +- lib/models/segments.js | 75 +- lib/models/subscriptions.js | 48 +- lib/models/templates.js | 11 +- lib/models/triggers.js | 51 +- lib/models/users.js | 27 +- lib/passport.js | 10 +- lib/tools.js | 20 +- lib/translate.js | 6 + package.json | 11 +- routes/archive.js | 14 +- routes/campaigns.js | 110 +- routes/fields.js | 21 +- routes/links.js | 3 +- routes/lists.js | 108 +- routes/segments.js | 61 +- routes/subscription.js | 48 +- routes/templates.js | 33 +- routes/triggers.js | 30 +- routes/users.js | 23 +- services/feedcheck.js | 10 +- services/importer.js | 3 +- services/sender.js | 8 +- services/triggers.js | 4 +- views/archive/layout.hbs | 2 +- views/campaigns/bounced.hbs | 20 +- views/campaigns/campaigns.hbs | 22 +- views/campaigns/clicked.hbs | 30 +- views/campaigns/complained.hbs | 20 +- views/campaigns/create-rss.hbs | 40 +- views/campaigns/create-triggered.hbs | 48 +- views/campaigns/create.hbs | 54 +- views/campaigns/delivered.hbs | 20 +- views/campaigns/edit-rss.hbs | 46 +- views/campaigns/edit-triggered.hbs | 52 +- views/campaigns/edit.hbs | 68 +- views/campaigns/opened.hbs | 20 +- views/campaigns/unsubscribed.hbs | 20 +- views/campaigns/upload-attachment.hbs | 12 +- views/campaigns/view.hbs | 132 +- views/emails/confirm-html.hbs | 8519 ++++++++++++++++- views/emails/confirm-text.hbs | 8 +- views/emails/password-reset-html.hbs | 8515 ++++++++++++++++- views/emails/password-reset-text.hbs | 8 +- views/emails/rss-html.hbs | 6 +- views/emails/stationery-html.hbs | 10 +- views/emails/stationery-text.hbs | 10 +- views/emails/subscription-confirmed-html.hbs | 8530 +++++++++++++++++- views/emails/subscription-confirmed-text.hbs | 14 +- views/emails/unsubscribe-confirmed-html.hbs | 8516 ++++++++++++++++- views/emails/unsubscribe-confirmed-text.hbs | 10 +- views/index.hbs | 47 +- views/layout.hbs | 24 +- views/lists/create.hbs | 18 +- views/lists/edit.hbs | 24 +- views/lists/fields/create.hbs | 68 +- views/lists/fields/edit.hbs | 76 +- views/lists/fields/fields.hbs | 24 +- views/lists/lists.hbs | 18 +- views/lists/segments/create.hbs | 24 +- views/lists/segments/edit.hbs | 26 +- views/lists/segments/rule-configure.hbs | 66 +- views/lists/segments/rule-create.hbs | 16 +- views/lists/segments/rule-edit.hbs | 76 +- views/lists/segments/segments.hbs | 16 +- views/lists/segments/view.hbs | 24 +- views/lists/subscription/add.hbs | 34 +- views/lists/subscription/edit.hbs | 34 +- views/lists/subscription/import-failed.hbs | 14 +- views/lists/subscription/import-preview.hbs | 22 +- views/lists/subscription/import.hbs | 20 +- views/lists/view.hbs | 64 +- views/partials/codeeditor.hbs | 2 +- views/partials/html-preview.hbs | 2 +- views/partials/merge-tag-reference.hbs | 9 +- views/partials/plaintext.hbs | 2 +- views/partials/summernote.hbs | 2 +- views/settings.hbs | 197 +- views/subscription/confirm-notice.hbs | 8 +- views/subscription/layout.hbs | 2 +- views/subscription/manage-address.hbs | 12 +- views/subscription/manage.hbs | 20 +- views/subscription/subscribe.hbs | 19 +- views/subscription/subscribed.hbs | 18 +- views/subscription/unsubscribe-notice.hbs | 6 +- views/subscription/unsubscribe.hbs | 8 +- views/subscription/updated-notice.hbs | 9 +- views/templates/create.hbs | 26 +- views/templates/edit.hbs | 22 +- views/templates/templates.hbs | 14 +- views/triggers/create-select.hbs | 16 +- views/triggers/create.hbs | 54 +- views/triggers/edit.hbs | 58 +- views/triggers/triggered.hbs | 18 +- views/triggers/triggers.hbs | 28 +- views/users/account.hbs | 36 +- views/users/api.hbs | 100 +- views/users/forgot.hbs | 22 +- views/users/login.hbs | 22 +- views/users/reset.hbs | 20 +- 114 files changed, 42095 insertions(+), 1902 deletions(-) create mode 100644 .eslintrc delete mode 100644 .eslintrc.js create mode 100644 lib/fakelang.js diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..91926fcd --- /dev/null +++ b/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "nodemailer" +} diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 04f79c0e..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -module.exports = { - rules: { - indent: [2, 4, { - SwitchCase: 1 - }], - quotes: [2, 'single'], - 'linebreak-style': [2, 'unix'], - semi: [2, 'always'], - strict: [2, 'global'], - eqeqeq: 2, - 'dot-notation': 2, - curly: 2, - 'no-fallthrough': 2, - 'quote-props': [2, 'as-needed'], - 'no-unused-expressions': [2, { - allowShortCircuit: true - }], - 'no-unused-vars': 2, - 'no-undefined': 2, - 'handle-callback-err': 2, - 'no-new': 2, - 'new-cap': 2, - 'no-eval': 2, - 'no-invalid-this': 2, - radix: [2, 'always'], - 'no-use-before-define': [2, 'nofunc'], - 'callback-return': [2, ['callback', 'cb', 'done']], - 'comma-dangle': [2, 'never'], - 'comma-style': [2, 'last'], - 'no-regex-spaces': 2, - 'no-empty': 2, - 'no-duplicate-case': 2, - 'no-empty-character-class': 2, - 'no-redeclare': [2, { - builtinGlobals: true - }], - 'block-scoped-var': 2, - 'no-sequences': 2, - 'no-throw-literal': 2, - 'no-useless-call': 2, - 'no-useless-concat': 2, - 'no-void': 2, - yoda: 2, - 'no-undef': 2, - 'global-require': 2, - 'no-var': 2, - 'no-bitwise': 2, - 'no-lonely-if': 2, - 'no-mixed-spaces-and-tabs': 2, - 'arrow-body-style': [2, 'as-needed'], - 'arrow-parens': [2, 'as-needed'], - 'prefer-arrow-callback': 2, - 'object-shorthand': 2, - 'prefer-spread': 2 - }, - env: { - es6: true, - node: true - }, - extends: 'eslint:recommended', - globals: { - it: true, - describe: true, - beforeEach: true, - afterEach: true - }, - fix: true -}; diff --git a/README.md b/README.md index 77c29807..43eeddd4 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,15 @@ 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 +Mailtrain is currently not translated but it supports translations. To add translations you first need to add translation support for the translatable strings. To test if strings are translatable or not, use a fake language with code "zz" + +```toml +language="zz" +``` + +This would modify all input strings. If a string is not modified then it does not support translations. + +![](https://cldup.com/qXxAbaq2F1.png) ### Translating JavaScript files diff --git a/app.js b/app.js index 52b94b1c..51a4a915 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,7 @@ let config = require('config'); let log = require('npmlog'); -let translate = require('./lib/translate'); +let _ = require('./lib/translate')._; let util = require('util'); let express = require('express'); @@ -96,14 +96,14 @@ hbs.registerHelper('flash_messages', function () { // eslint-disable-line prefer ); }); -// {{#translate}}abc{{/translate}} +// {{#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 + let result = _(options.fn(this)); // eslint-disable-line no-invalid-this if (Array.isArray(context)) { result = util.format(result, ...context); @@ -137,7 +137,7 @@ app.use(session({ app.use(flash()); app.use((req, res, next) => { - req._ = str => translate._(str); + req._ = str => _(str); next(); }); @@ -166,7 +166,7 @@ app.use((req, res, next) => { }; let menu = [{ - title: 'Home', + title: _('Home'), url: '/', selected: true }]; @@ -208,7 +208,7 @@ app.use('/api', api); // catch 404 and forward to error handler app.use((req, res, next) => { - let err = new Error('Not Found'); + let err = new Error(_('Not Found')); err.status = 404; next(err); }); diff --git a/languages/et.mo b/languages/et.mo index 69f724059f00b75f61725972a28ba2d2589b7e1d..c1600e2295f63f2b0ffd6f51f62a23581ad42ba5 100644 GIT binary patch delta 1295 zcmYk)O-v+36bJAEhJ{ga6;wbz@<`Z-FcQAR>}ZUUh^(+kLO5{Hq-MI9DY~X>=&Bxr z9LAV<*km#8cDQ)b$Wbp66BD^j_N0mNuzFfG*~=#Cbx$7rznKxOO!aTNyFT7~)%OQ} z9IpS`pE(g|W9XyknGPYIhkrtDpL7bb2yemH;2oHQm%4=5g^Tb7cnquXJNPurWrP@k zuftjRF2qk9RC$Ycy7JoL7?LGK1UQG82aTV^tCHNV92i}Do*dn4R zz_;Nf{2ETdU*MZiJkr{54Ze@tEjR$bhn(PVZ6Cr>>@WHrZEZLW=WriDlt!FDj`*v# zKR_=1ACN0Hg!DMmnYOP$yb{;p1l)yJ;1T2_`W*79^4dI(TYVT81TJu_&4%M^eg}NR zQDcrk;uj4?;snKI^e54|l+U7bX59MFIXG@yb{z9X<6s|0=kEijCZ76tD3_Jn5MR#z z>iDUnzG`DSNR;#1Mvi2`CZ2p*J2Kk&994~yPEpZDUKV{`tl5eZ*Y@bpCKRip(lr&d z#;c0dh6;yN!9*FV964R7nnMbdQC^Yuv$Ts@@7d@%PX$FTDY)WNtP5B{w^j_A?}|nj zLoC|dc2CC@$-yC@DC7WZDn0L7Ngs55nBBCVR&5dmsm_dzY_(!tu@O(AZC*dtRi2!F zlnKUnl|xXir`W%u$lL#NO&?@#_Pj*%FXoq))1&SQ{=b}`TR8io`^(PydgC1FB+jdn zB&V!X4j7}Fb<%iPYkcD+HO|W_3NViL8>evzwNQqn3}WS+HhJ*|uBz0$4U!3|YbVdLDCRC5P~X#wS@(vB>R rZ%LCx?ZqV73U7_~NYPnaODSt7-jZd+jyuiA#gjSa|5YjdIJ@%?3yT)8 delta 590 zcmXxgyDvj=6u|M*a(mHwwDpXl6%|88TLzDXU05szLp20VM5G2HX@Xct48Mp(kg!;o z#A;w95s4Uum>LoM2fpWOPjc?({_f-a&hI=neFSr_fzo{;>d6rKK(>(UE-u6m_Tn!N zW7I7Y!&#g}gWdRrOW0f@Qir?PjAz(@HyFT|!t*>PMRH=5iX?c^kNU#`hH<;FKF1o? zcj&`swDBFa(L8pbZHbK1WDJK`uX;oV@EZH@8>g^?pLL%NoMwMH;YJI%%0#-b6N5O8 zI+Zl)WY$m%9$*}wuod0q{}YLzo^TAc;Z(t8^s?T>DDI(7>>6F{?*z>`p&x3IVlY)_ zoJ`9pT8kXm37vvQCFv(QxKPt+x(X{u4Lyubu9~E0k&%_`+{|KjKAp;59l38^CTNYA zjFmBOR>(AZ(&o^!WA}v<{e$tiiP@3j{ndnh;WqabJ;v|dGMC;q^XXkQr@nS`>`VRu DlQ%(| diff --git a/languages/et.po b/languages/et.po index 5a82f24d..1d31910b 100644 --- a/languages/et.po +++ b/languages/et.po @@ -2,8 +2,8 @@ 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" +"POT-Creation-Date: 2017-03-07 15:46+0200\n" +"PO-Revision-Date: 2017-03-07 16:02+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: et\n" @@ -13,18 +13,2687 @@ msgstr "" "X-Generator: Poedit 1.8.12\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: views/archive/layout.hbs:1 views/layout.hbs:1 +#: views/subscription/layout.hbs:1 routes/index.js:11 +msgid "Self hosted email newsletter app" +msgstr "Enda majutatud e-posti uudiskirjade rakendus" + +#: views/campaigns/bounced.hbs:1 views/campaigns/campaigns.hbs:1 +#: views/campaigns/clicked.hbs:1 views/campaigns/complained.hbs:1 +#: views/campaigns/create-rss.hbs:1 views/campaigns/create-triggered.hbs:1 +#: views/campaigns/create.hbs:1 views/campaigns/delivered.hbs:1 +#: views/campaigns/edit-rss.hbs:1 views/campaigns/edit-triggered.hbs:1 +#: views/campaigns/edit.hbs:1 views/campaigns/opened.hbs:1 +#: views/campaigns/unsubscribed.hbs:1 views/campaigns/upload-attachment.hbs:1 +#: views/campaigns/view.hbs:1 views/lists/create.hbs:1 views/lists/edit.hbs:1 +#: views/lists/fields/create.hbs:1 views/lists/fields/edit.hbs:1 +#: views/lists/fields/fields.hbs:1 views/lists/lists.hbs:1 +#: views/lists/segments/create.hbs:1 views/lists/segments/edit.hbs:1 +#: views/lists/segments/rule-configure.hbs:1 +#: views/lists/segments/rule-create.hbs:1 views/lists/segments/rule-edit.hbs:1 +#: views/lists/segments/segments.hbs:1 views/lists/segments/view.hbs:1 +#: views/lists/subscription/add.hbs:1 views/lists/subscription/edit.hbs:1 +#: views/lists/subscription/import-failed.hbs:1 +#: views/lists/subscription/import-preview.hbs:1 +#: views/lists/subscription/import.hbs:1 views/lists/view.hbs:1 +#: views/settings.hbs:1 views/templates/create.hbs:1 views/templates/edit.hbs:1 +#: views/templates/templates.hbs:1 views/triggers/create-select.hbs:1 +#: views/triggers/create.hbs:1 views/triggers/edit.hbs:1 +#: views/triggers/triggered.hbs:1 views/triggers/triggers.hbs:1 +#: views/users/account.hbs:1 views/users/api.hbs:1 views/users/forgot.hbs:1 +#: views/users/login.hbs:1 views/users/reset.hbs:1 app.js:169 +msgid "Home" +msgstr "Esileht" + +#: views/campaigns/bounced.hbs:2 views/campaigns/campaigns.hbs:2 +#: views/campaigns/campaigns.hbs:7 views/campaigns/clicked.hbs:2 +#: views/campaigns/complained.hbs:2 views/campaigns/create-rss.hbs:2 +#: views/campaigns/create-triggered.hbs:2 views/campaigns/create.hbs:2 +#: views/campaigns/delivered.hbs:2 views/campaigns/edit-rss.hbs:2 +#: views/campaigns/edit-triggered.hbs:2 views/campaigns/edit.hbs:2 +#: views/campaigns/opened.hbs:2 views/campaigns/unsubscribed.hbs:2 +#: views/campaigns/upload-attachment.hbs:2 views/campaigns/view.hbs:2 +#: lib/tools.js:119 routes/campaigns.js:35 +msgid "Campaigns" +msgstr "" + +#: views/campaigns/bounced.hbs:3 views/campaigns/bounced.hbs:4 +msgid "Bounced info" +msgstr "" + +#: views/campaigns/bounced.hbs:5 views/campaigns/clicked.hbs:5 +#: views/campaigns/complained.hbs:5 views/campaigns/delivered.hbs:5 +#: views/campaigns/edit-rss.hbs:5 views/campaigns/edit-triggered.hbs:5 +#: views/campaigns/edit.hbs:5 views/campaigns/opened.hbs:5 +#: views/campaigns/unsubscribed.hbs:5 views/campaigns/upload-attachment.hbs:6 +msgid "View campaign" +msgstr "" + +#: views/campaigns/bounced.hbs:6 +msgid "Subscribers who bounced and were unsubscribed:" +msgstr "" + +#: views/campaigns/bounced.hbs:7 views/campaigns/clicked.hbs:13 +#: views/campaigns/complained.hbs:7 views/campaigns/delivered.hbs:7 +#: views/campaigns/opened.hbs:7 views/campaigns/unsubscribed.hbs:7 +#: views/lists/subscription/import-failed.hbs:9 +msgid "Address" +msgstr "" + +#: views/campaigns/bounced.hbs:8 views/campaigns/clicked.hbs:14 +#: views/campaigns/complained.hbs:8 views/campaigns/delivered.hbs:8 +#: views/campaigns/opened.hbs:8 views/campaigns/unsubscribed.hbs:8 +#: views/lists/subscription/add.hbs:6 views/lists/subscription/edit.hbs:7 +#: views/lists/subscription/import-preview.hbs:7 +#: views/subscription/manage.hbs:4 views/subscription/subscribe.hbs:4 +msgid "First Name" +msgstr "" + +#: views/campaigns/bounced.hbs:9 views/campaigns/clicked.hbs:15 +#: views/campaigns/complained.hbs:9 views/campaigns/delivered.hbs:9 +#: views/campaigns/opened.hbs:9 views/campaigns/unsubscribed.hbs:9 +#: views/lists/subscription/add.hbs:7 views/lists/subscription/edit.hbs:8 +#: views/lists/subscription/import-preview.hbs:8 +#: views/subscription/manage.hbs:5 views/subscription/subscribe.hbs:5 +msgid "Last Name" +msgstr "" + +#: views/campaigns/bounced.hbs:10 views/campaigns/complained.hbs:10 +#: views/campaigns/delivered.hbs:10 views/campaigns/unsubscribed.hbs:10 +msgid "SMTP response" +msgstr "" + +#: views/campaigns/bounced.hbs:11 +msgid "Bounce time" +msgstr "" + +#: views/campaigns/campaigns.hbs:3 views/campaigns/create-triggered.hbs:24 +#: views/campaigns/create.hbs:3 views/campaigns/create.hbs:4 +#: views/campaigns/create.hbs:27 +msgid "Create Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:4 +msgid "Regular Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:5 +msgid "RSS Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:6 +msgid "Triggered Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:8 views/campaigns/create-rss.hbs:6 +#: views/campaigns/create-triggered.hbs:5 views/campaigns/create.hbs:5 +#: views/campaigns/edit-rss.hbs:8 views/campaigns/edit-triggered.hbs:9 +#: views/campaigns/edit.hbs:10 views/campaigns/view.hbs:71 +#: views/lists/create.hbs:5 views/lists/edit.hbs:6 +#: views/lists/fields/fields.hbs:6 views/lists/lists.hbs:5 +#: views/lists/segments/segments.hbs:6 views/templates/templates.hbs:5 +msgid "Name" +msgstr "" + +#: views/campaigns/campaigns.hbs:9 views/campaigns/create-rss.hbs:8 +#: views/campaigns/create-triggered.hbs:7 views/campaigns/create.hbs:7 +#: views/campaigns/edit-rss.hbs:10 views/campaigns/edit-triggered.hbs:11 +#: views/campaigns/edit.hbs:12 views/campaigns/view.hbs:72 +#: views/lists/create.hbs:7 views/lists/edit.hbs:10 views/lists/lists.hbs:8 +#: views/partials/merge-tag-reference.hbs:4 views/templates/create.hbs:9 +#: views/templates/edit.hbs:8 views/templates/templates.hbs:6 +msgid "Description" +msgstr "" + +#: views/campaigns/campaigns.hbs:10 views/campaigns/view.hbs:73 +msgid "Status" +msgstr "" + +#: views/campaigns/campaigns.hbs:11 views/campaigns/view.hbs:74 +msgid "Created" +msgstr "" + +#: views/campaigns/clicked.hbs:3 views/campaigns/clicked.hbs:4 +msgid "Link info" +msgstr "" + +#: views/campaigns/clicked.hbs:6 views/campaigns/view.hbs:61 +msgid "URL" +msgstr "" + +#: views/campaigns/clicked.hbs:7 views/campaigns/view.hbs:62 +msgid "Clicks" +msgstr "" + +#: views/campaigns/clicked.hbs:8 views/campaigns/view.hbs:63 +msgid "% of clicks" +msgstr "" + +#: views/campaigns/clicked.hbs:9 views/campaigns/view.hbs:64 +msgid "% of messages" +msgstr "" + +#: views/campaigns/clicked.hbs:10 views/campaigns/view.hbs:67 +msgid "Aggregated clicks" +msgstr "" + +#: views/campaigns/clicked.hbs:11 +msgid "Subscribers who clicked on a link:" +msgstr "" + +#: views/campaigns/clicked.hbs:12 +msgid "Subscribers who clicked on this link:" +msgstr "" + +#: views/campaigns/clicked.hbs:16 +msgid "First click time" +msgstr "" + +#: views/campaigns/clicked.hbs:17 +msgid "Click count" +msgstr "" + +#: views/campaigns/complained.hbs:3 views/campaigns/complained.hbs:4 +msgid "Complained info" +msgstr "" + +#: views/campaigns/complained.hbs:6 +msgid "Subscribers who complained and were unsubscribed:" +msgstr "" + +#: views/campaigns/complained.hbs:11 +msgid "Complain time" +msgstr "" + +#: views/campaigns/create-rss.hbs:3 views/campaigns/create-rss.hbs:4 +#: views/campaigns/create-rss.hbs:20 +msgid "Create RSS Campaign" +msgstr "" + +#: views/campaigns/create-rss.hbs:5 views/campaigns/edit-rss.hbs:6 +msgid "" +"RSS campaign sets up a tracker against selected RSS feed address. Whenever a " +"new entry is found from this feed it is sent to selected list as an email " +"message." +msgstr "" + +#: views/campaigns/create-rss.hbs:7 views/campaigns/create-triggered.hbs:6 +#: views/campaigns/create.hbs:6 views/campaigns/edit-rss.hbs:9 +#: views/campaigns/edit-triggered.hbs:10 views/campaigns/edit.hbs:11 +msgid "Campaign Name" +msgstr "" + +#: views/campaigns/create-rss.hbs:9 views/campaigns/create-triggered.hbs:8 +#: views/campaigns/create.hbs:8 views/campaigns/edit-rss.hbs:11 +#: views/campaigns/edit-triggered.hbs:12 views/campaigns/edit.hbs:13 +#: views/lists/create.hbs:8 views/lists/edit.hbs:11 +#: views/templates/create.hbs:11 views/templates/edit.hbs:10 +msgid "HTML is allowed" +msgstr "" + +#: views/campaigns/create-rss.hbs:10 views/campaigns/create-triggered.hbs:9 +#: views/campaigns/create.hbs:9 views/campaigns/edit-rss.hbs:12 +#: views/campaigns/edit-triggered.hbs:13 views/campaigns/edit.hbs:14 +#: views/campaigns/view.hbs:6 +msgid "List" +msgstr "" + +#: views/campaigns/create-rss.hbs:11 views/campaigns/create-triggered.hbs:10 +#: views/campaigns/create-triggered.hbs:13 views/campaigns/create.hbs:10 +#: views/campaigns/create.hbs:14 views/campaigns/edit-rss.hbs:13 +#: views/campaigns/edit-triggered.hbs:14 views/campaigns/edit.hbs:15 +#: views/lists/fields/create.hbs:27 views/lists/fields/edit.hbs:28 +#: views/lists/segments/create.hbs:9 views/lists/segments/edit.hbs:10 +#: views/lists/segments/rule-create.hbs:7 views/lists/subscription/add.hbs:10 +#: views/lists/subscription/add.hbs:12 views/lists/subscription/edit.hbs:11 +#: views/lists/subscription/import-preview.hbs:5 +#: views/subscription/manage.hbs:10 views/subscription/subscribe.hbs:10 +#: views/templates/create.hbs:8 +msgid "Select" +msgstr "" + +#: views/campaigns/create-rss.hbs:12 views/campaigns/create-triggered.hbs:11 +#: views/campaigns/create.hbs:11 views/campaigns/edit-rss.hbs:14 +#: views/campaigns/edit-triggered.hbs:15 views/campaigns/edit.hbs:16 +msgid "subscribers" +msgstr "" + +#: views/campaigns/create-rss.hbs:13 views/campaigns/edit-rss.hbs:15 +msgid "RSS Feed Url" +msgstr "" + +#: views/campaigns/create-rss.hbs:14 views/campaigns/edit-rss.hbs:16 +msgid "" +"New entries from this RSS URL are sent out to list subscribers as email " +"messages" +msgstr "" + +#: views/campaigns/create-rss.hbs:15 views/campaigns/create-triggered.hbs:17 +#: views/campaigns/create.hbs:18 views/campaigns/edit-rss.hbs:18 +#: views/campaigns/edit-triggered.hbs:16 views/campaigns/edit.hbs:17 +#: views/campaigns/view.hbs:12 +msgid "Email \"from name\"" +msgstr "" + +#: views/campaigns/create-rss.hbs:16 views/campaigns/create-triggered.hbs:18 +#: views/campaigns/create.hbs:19 views/campaigns/edit-rss.hbs:19 +#: views/campaigns/edit.hbs:18 views/settings.hbs:23 +msgid "This is the name your emails will come from" +msgstr "" + +#: views/campaigns/create-rss.hbs:17 views/campaigns/create-triggered.hbs:19 +#: views/campaigns/create.hbs:20 views/campaigns/edit-rss.hbs:20 +#: views/campaigns/edit-triggered.hbs:18 views/campaigns/edit.hbs:19 +#: views/campaigns/view.hbs:13 +msgid "Email \"from\" address" +msgstr "" + +#: views/campaigns/create-rss.hbs:18 views/campaigns/create-triggered.hbs:20 +#: views/campaigns/edit-rss.hbs:21 views/campaigns/edit-triggered.hbs:19 +#: views/settings.hbs:25 +msgid "This is the address people will send replies to" +msgstr "" + +#: views/campaigns/create-rss.hbs:19 views/campaigns/create-triggered.hbs:23 +#: views/campaigns/create.hbs:26 views/campaigns/edit-rss.hbs:22 +#: views/campaigns/edit-triggered.hbs:22 views/campaigns/edit.hbs:25 +msgid "Disable clicked/opened tracking" +msgstr "" + +#: views/campaigns/create-triggered.hbs:3 +#: views/campaigns/create-triggered.hbs:4 +msgid "Create Triggered Campaign" +msgstr "" + +#: views/campaigns/create-triggered.hbs:12 views/campaigns/create.hbs:12 +#: views/campaigns/edit-triggered.hbs:7 views/campaigns/edit.hbs:7 +#: views/lists/fields/create.hbs:31 views/lists/fields/edit.hbs:33 +#: views/templates/create.hbs:13 +msgid "Template" +msgstr "" + +#: views/campaigns/create-triggered.hbs:14 views/campaigns/create.hbs:15 +msgid "Selecting a template creates a campaign specific copy from it" +msgstr "" + +#: views/campaigns/create-triggered.hbs:15 views/campaigns/create.hbs:16 +msgid "Or alternatively use an URL as the message content source:" +msgstr "" + +#: views/campaigns/create-triggered.hbs:16 views/campaigns/create.hbs:17 +#: views/campaigns/edit-triggered.hbs:25 views/campaigns/edit.hbs:28 +msgid "" +"If a message is sent then this URL will be POSTed to using Merge Tags as " +"POST body. Use this if you want to generate the HTML message yourself" +msgstr "" + +#: views/campaigns/create-triggered.hbs:21 views/campaigns/create.hbs:24 +#: views/campaigns/edit-triggered.hbs:20 views/campaigns/edit.hbs:23 +#: views/campaigns/view.hbs:15 +msgid "Email \"subject line\"" +msgstr "" + +#: views/campaigns/create-triggered.hbs:22 views/campaigns/create.hbs:25 +#: views/campaigns/edit-triggered.hbs:21 views/campaigns/edit.hbs:24 +#: views/settings.hbs:27 +msgid "Keep it relevant and non-spammy" +msgstr "" + +#: views/campaigns/create.hbs:13 +msgid "Select a template:" +msgstr "" + +#: views/campaigns/create.hbs:21 views/campaigns/edit.hbs:20 +msgid "" +"This is the address people will send replies to unless reply-to address is " +"set" +msgstr "" + +#: views/campaigns/create.hbs:22 views/campaigns/edit.hbs:21 +#: views/campaigns/view.hbs:14 +msgid "Email \"reply-to\" address" +msgstr "" + +#: views/campaigns/create.hbs:23 views/campaigns/edit.hbs:22 +msgid "If set, this is the address people will send replies to" +msgstr "" + +#: views/campaigns/delivered.hbs:3 views/campaigns/delivered.hbs:4 +msgid "Delivered info" +msgstr "" + +#: views/campaigns/delivered.hbs:6 +msgid "Subscribers who received the message and did not bounce/unsubscribe:" +msgstr "" + +#: views/campaigns/delivered.hbs:11 +msgid "Delivery time" +msgstr "" + +#: views/campaigns/edit-rss.hbs:3 views/campaigns/edit-rss.hbs:4 +msgid "Edit RSS Campaign" +msgstr "" + +#: views/campaigns/edit-rss.hbs:7 views/campaigns/edit-triggered.hbs:8 +#: views/campaigns/edit.hbs:9 views/settings.hbs:4 +msgid "General Settings" +msgstr "" + +#: views/campaigns/edit-rss.hbs:17 +msgid "" +"Use special merge tag [RSS_ENTRY] to mark the position for the " +"RSS post content. Additionally you can use any valid merge tag as well." +msgstr "" + +#: views/campaigns/edit-rss.hbs:23 views/campaigns/edit-triggered.hbs:26 +#: views/campaigns/edit.hbs:34 +msgid "Delete Campaign" +msgstr "" + +#: views/campaigns/edit-rss.hbs:24 views/campaigns/edit-triggered.hbs:27 +#: views/campaigns/edit.hbs:35 views/lists/edit.hbs:13 +#: views/lists/fields/edit.hbs:39 views/lists/segments/edit.hbs:14 +#: views/lists/segments/rule-edit.hbs:38 views/lists/subscription/edit.hbs:17 +#: views/settings.hbs:99 views/templates/edit.hbs:12 +msgid "Update" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:3 views/campaigns/edit-triggered.hbs:4 +msgid "Edit Triggered Campaign" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:6 views/campaigns/edit.hbs:6 +msgid "General" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:17 +msgid "his is the name your emails will come from" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:23 views/campaigns/edit.hbs:26 +msgid "Template Settings" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:24 views/campaigns/edit.hbs:27 +msgid "Template URL" +msgstr "" + +#: views/campaigns/edit.hbs:3 views/campaigns/edit.hbs:4 +#: views/campaigns/upload-attachment.hbs:3 +#: views/campaigns/upload-attachment.hbs:5 views/campaigns/view.hbs:3 +msgid "Edit Campaign" +msgstr "" + +#: views/campaigns/edit.hbs:8 views/campaigns/edit.hbs:29 +msgid "Attachments" +msgstr "" + +#: views/campaigns/edit.hbs:30 +msgid "File" +msgstr "" + +#: views/campaigns/edit.hbs:31 +msgid "Size" +msgstr "" + +#: views/campaigns/edit.hbs:32 views/campaigns/view.hbs:66 +#: views/lists/fields/fields.hbs:12 +msgid "No data available in table" +msgstr "" + +#: views/campaigns/edit.hbs:33 views/campaigns/upload-attachment.hbs:4 +msgid "Add Attachment" +msgstr "" + +#: views/campaigns/opened.hbs:3 views/campaigns/opened.hbs:4 +msgid "Opened info" +msgstr "" + +#: views/campaigns/opened.hbs:6 +msgid "Subscribers who opened this message:" +msgstr "" + +#: views/campaigns/opened.hbs:10 +msgid "First open" +msgstr "" + +#: views/campaigns/opened.hbs:11 +msgid "Opened count" +msgstr "" + +#: views/campaigns/unsubscribed.hbs:3 views/campaigns/unsubscribed.hbs:4 +msgid "Unsubscribed info" +msgstr "" + +#: views/campaigns/unsubscribed.hbs:6 +msgid "Subscribers who unsubscribed:" +msgstr "" + +#: views/campaigns/unsubscribed.hbs:11 views/campaigns/view.hbs:26 +#: views/lists/subscription/import.hbs:10 routes/lists.js:171 +msgid "Unsubscribed" +msgstr "" + +#: views/campaigns/upload-attachment.hbs:7 +msgid "Upload" +msgstr "" + +#: views/campaigns/view.hbs:4 +msgid "Overview" +msgstr "" + +#: views/campaigns/view.hbs:5 +msgid "Links" +msgstr "" + +#: views/campaigns/view.hbs:7 +msgid "Feed URL" +msgstr "" + +#: views/campaigns/view.hbs:8 +msgid "Last check" +msgstr "" + +#: views/campaigns/view.hbs:9 +msgid "Not yet checked" +msgstr "" + +#: views/campaigns/view.hbs:10 +msgid "activate campaign to start checking feed for new messages" +msgstr "" + +#: views/campaigns/view.hbs:11 +msgid "RSS status" +msgstr "" + +#: views/campaigns/view.hbs:16 +msgid "Preview campaign as" +msgstr "" + +#: views/campaigns/view.hbs:17 +msgid "Add new test user" +msgstr "" + +#: views/campaigns/view.hbs:18 +msgid "No test users yet, create one here" +msgstr "" + +#: views/campaigns/view.hbs:19 +msgid "Go" +msgstr "" + +#: views/campaigns/view.hbs:20 lib/models/triggers.js:25 +msgid "Delivered" +msgstr "" + +#: views/campaigns/view.hbs:21 +msgid "List subscribers who received this message" +msgstr "" + +#: views/campaigns/view.hbs:22 routes/lists.js:171 +msgid "Bounced" +msgstr "" + +#: views/campaigns/view.hbs:23 +msgid "List subscribers who bounced" +msgstr "" + +#: views/campaigns/view.hbs:24 +msgid "Complaints" +msgstr "" + +#: views/campaigns/view.hbs:25 +msgid "List subscribers who complained for this message" +msgstr "" + +#: views/campaigns/view.hbs:27 +msgid "List subscribers who unsubscribed after this message" +msgstr "" + +#: views/campaigns/view.hbs:28 +msgid "Opened" +msgstr "" + +#: views/campaigns/view.hbs:29 +msgid "List subscribers who opened this message" +msgstr "" + +#: views/campaigns/view.hbs:30 +msgid "Clicked" +msgstr "" + +#: views/campaigns/view.hbs:31 views/campaigns/view.hbs:68 +msgid "List subscribers who clicked on a link" +msgstr "" + +#: views/campaigns/view.hbs:32 +msgid "" +"Are you sure? This action would start sending messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:33 +msgid "Delay sending" +msgstr "" + +#: views/campaigns/view.hbs:34 +msgid "hours" +msgstr "" + +#: views/campaigns/view.hbs:35 +msgid "minutes" +msgstr "" + +#: views/campaigns/view.hbs:36 +msgid "Send to subscribers:" +msgstr "" + +#: views/campaigns/view.hbs:37 +msgid "Are you sure? This action would reset scheduling" +msgstr "" + +#: views/campaigns/view.hbs:38 +msgid "Cancel" +msgstr "" + +#: views/campaigns/view.hbs:39 +msgid "Sending scheduled" +msgstr "" + +#: views/campaigns/view.hbs:40 views/campaigns/view.hbs:52 +msgid "Pause" +msgstr "" + +#: views/campaigns/view.hbs:41 routes/campaigns.js:264 +msgid "Sending" +msgstr "" + +#: views/campaigns/view.hbs:42 views/campaigns/view.hbs:46 +msgid "" +"Are you sure? This action would resume sending messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:43 views/campaigns/view.hbs:47 +msgid "Are you sure? This action would reset all stats about current progress" +msgstr "" + +#: views/campaigns/view.hbs:44 +msgid "Resume" +msgstr "" + +#: views/campaigns/view.hbs:45 views/campaigns/view.hbs:49 +msgid "Reset" +msgstr "" + +#: views/campaigns/view.hbs:48 +msgid "Continue" +msgstr "" + +#: views/campaigns/view.hbs:50 +msgid "" +"All messages sent! Hit \"Continue\" if you you want to send this campaign to " +"new subscribers" +msgstr "" + +#: views/campaigns/view.hbs:51 +msgid "" +"Are you sure? This action would pause sending new entries in RSS feed as " +"email messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:53 views/campaigns/view.hbs:57 +msgid "Campaign status:" +msgstr "" + +#: views/campaigns/view.hbs:54 +msgid "ACTIVE" +msgstr "" + +#: views/campaigns/view.hbs:55 +msgid "" +"Are you sure? This action would start sending new entries in RSS feed as " +"email messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:56 +msgid "Activate" +msgstr "" + +#: views/campaigns/view.hbs:58 +msgid "INACTIVE" +msgstr "" + +#: views/campaigns/view.hbs:59 +msgid "" +"This is a triggered campaign. Messages are only sent to subscribers that hit " +"some trigger that invokes this campaign" +msgstr "" + +#: views/campaigns/view.hbs:60 +msgid "see more" +msgstr "" + +#: views/campaigns/view.hbs:65 +msgid "List subscribers who clicked this link" +msgstr "" + +#: views/campaigns/view.hbs:69 +msgid "" +"Clicks are counted as unique subscribers that clicked on a specific link or " +"on any link (in aggregated view)" +msgstr "" + +#: views/campaigns/view.hbs:70 +msgid "" +"If a new entry is found from campaign feed a new subcampaign is created of " +"that entry and it will be listed here" +msgstr "" + +#: views/emails/confirm-html.hbs:1 views/emails/confirm-html.hbs:2 +#: views/emails/confirm-text.hbs:1 +msgid "Please Confirm Subscription" +msgstr "Palun kinnita oma liitumissoov" + +#: views/emails/confirm-html.hbs:3 views/emails/confirm-text.hbs:2 +msgid "Yes, subscribe me to this list" +msgstr "Jah, soovin liituda selle listiga" + +#: views/emails/confirm-html.hbs:4 +msgid "" +"If you received this email by mistake, simply delete it. You won't be " +"subscribed if you don't click the confirmation link above." +msgstr "" +"Kui said selle kirja kogemata, siis lihtsalt kustuta see. Sind ei lisata " +"listi, kui sa ei kliki allolevale kinnituslingile" + +#: views/emails/confirm-html.hbs:5 views/emails/confirm-text.hbs:4 +#: views/emails/subscription-confirmed-html.hbs:7 +#: views/emails/subscription-confirmed-text.hbs:7 +#: views/emails/unsubscribe-confirmed-html.hbs:5 +#: views/emails/unsubscribe-confirmed-text.hbs:5 +msgid "For questions about this list, please contact:" +msgstr "Küsimustega seoses selle listiga võta ühendust järgmisel aadressil:" + +#: views/emails/confirm-text.hbs:3 +msgid "" +"If you received this email by mistake, simply delete it. You won't be " +"subscribed unless you click the confirmation link above." +msgstr "" + +#: views/emails/password-reset-html.hbs:1 +#: views/emails/password-reset-html.hbs:2 +#: views/emails/password-reset-text.hbs:1 +msgid "Change your password" +msgstr "" + +#: views/emails/password-reset-html.hbs:3 +#: views/emails/password-reset-text.hbs:2 +msgid "We have received a password change request for your Mailtrain account:" +msgstr "" + +#: views/emails/password-reset-html.hbs:4 +#: views/emails/password-reset-text.hbs:3 +msgid "Reset password" +msgstr "" + +#: views/emails/password-reset-html.hbs:5 +#: views/emails/password-reset-text.hbs:4 +msgid "" +"If you did not ask to change your password, then you can ignore this email " +"and your password will not be changed." +msgstr "" + +#: views/emails/rss-html.hbs:1 views/emails/stationery-html.hbs:3 +#: views/emails/stationery-text.hbs:3 +msgid "Preferences" +msgstr "" + +#: views/emails/rss-html.hbs:2 views/emails/stationery-html.hbs:4 +#: views/emails/stationery-text.hbs:4 views/lists/subscription/edit.hbs:15 +#: views/subscription/manage.hbs:12 views/subscription/unsubscribe.hbs:1 +#: views/subscription/unsubscribe.hbs:4 routes/lists.js:253 +msgid "Unsubscribe" +msgstr "" + +#: views/emails/rss-html.hbs:3 views/emails/stationery-html.hbs:5 +#: views/emails/stationery-text.hbs:5 +msgid "View this email in your browser" +msgstr "" + +#: views/emails/stationery-html.hbs:1 views/emails/stationery-text.hbs:1 +msgid "Hey [FIRST_NAME/Customer]," +msgstr "" + +#: views/emails/stationery-html.hbs:2 views/emails/stationery-text.hbs:2 +msgid "Cheers," +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:1 +#: views/emails/subscription-confirmed-text.hbs:1 +#: views/subscription/subscribed.hbs:1 +msgid "Subscription Confirmed" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:2 +#: views/emails/subscription-confirmed-text.hbs:2 +#: views/subscription/subscribed.hbs:2 +msgid "Your subscription to our list has been confirmed." +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:3 +#: views/emails/subscription-confirmed-text.hbs:3 +msgid "If you want to modify your subscription then you can:" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:4 +#: views/emails/subscription-confirmed-text.hbs:4 +#: views/subscription/subscribed.hbs:6 +msgid "manage your preferences" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:5 +#: views/emails/subscription-confirmed-text.hbs:5 +#: views/subscription/subscribed.hbs:5 +msgid "or" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:6 +#: views/emails/subscription-confirmed-text.hbs:6 +msgid "unsubscribe here" +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:1 +#: views/emails/unsubscribe-confirmed-text.hbs:1 +msgid "You are now unsubscribed" +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:2 +#: views/emails/unsubscribe-confirmed-text.hbs:2 +msgid "We have removed your email address from our list." +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:3 +#: views/emails/unsubscribe-confirmed-text.hbs:3 +msgid "If you unsubscribed by mistake, you can re-subscribe at:" +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:4 +#: views/emails/unsubscribe-confirmed-text.hbs:4 +#: views/lists/subscription/add.hbs:16 routes/lists.js:253 +msgid "Subscribe" +msgstr "" + #: 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" +#: views/lists/create.hbs:2 views/lists/edit.hbs:2 +#: views/lists/fields/create.hbs:2 views/lists/fields/edit.hbs:2 +#: views/lists/fields/fields.hbs:2 views/lists/lists.hbs:2 +#: views/lists/lists.hbs:4 views/lists/segments/create.hbs:2 +#: views/lists/segments/edit.hbs:2 views/lists/segments/rule-configure.hbs:2 +#: views/lists/segments/rule-create.hbs:2 views/lists/segments/rule-edit.hbs:2 +#: views/lists/segments/segments.hbs:2 views/lists/segments/view.hbs:2 +#: views/lists/subscription/add.hbs:2 views/lists/subscription/edit.hbs:2 +#: views/lists/subscription/import-failed.hbs:2 +#: views/lists/subscription/import-preview.hbs:2 +#: views/lists/subscription/import.hbs:2 views/lists/view.hbs:2 +#: lib/tools.js:111 +msgid "Lists" +msgstr "" -#: routes/settings.js:23 +#: views/lists/create.hbs:3 views/lists/create.hbs:4 views/lists/create.hbs:9 +#: views/lists/lists.hbs:3 +msgid "Create List" +msgstr "" + +#: views/lists/create.hbs:6 views/lists/edit.hbs:7 +msgid "List Name" +msgstr "" + +#: views/lists/edit.hbs:3 views/lists/edit.hbs:4 +msgid "Edit List" +msgstr "" + +#: views/lists/edit.hbs:5 +msgid "View List" +msgstr "" + +#: views/lists/edit.hbs:8 +msgid "List ID" +msgstr "" + +#: views/lists/edit.hbs:9 +msgid "This is the list ID displayed to the subscribers" +msgstr "" + +#: views/lists/edit.hbs:12 +msgid "Delete List" +msgstr "" + +#: views/lists/fields/create.hbs:3 views/lists/fields/edit.hbs:3 +#: views/lists/fields/fields.hbs:3 views/lists/fields/fields.hbs:5 +msgid "Custom Fields" +msgstr "" + +#: views/lists/fields/create.hbs:4 +msgid "Create Field" +msgstr "" + +#: views/lists/fields/create.hbs:5 views/lists/fields/fields.hbs:4 +msgid "Create Custom Field" +msgstr "" + +#: views/lists/fields/create.hbs:6 views/lists/fields/create.hbs:7 +#: views/lists/fields/edit.hbs:7 views/lists/fields/edit.hbs:8 +msgid "Field Name" +msgstr "" + +#: views/lists/fields/create.hbs:8 views/lists/fields/edit.hbs:9 +msgid "Field Type" +msgstr "" + +#: views/lists/fields/create.hbs:9 views/lists/fields/edit.hbs:10 +#: lib/models/fields.js:17 +msgid "Text" +msgstr "" + +#: views/lists/fields/create.hbs:10 views/lists/fields/edit.hbs:11 +#: lib/models/fields.js:21 +msgid "Number" +msgstr "" + +#: views/lists/fields/create.hbs:11 views/lists/fields/edit.hbs:12 +#: lib/models/fields.js:18 +msgid "Website" +msgstr "" + +#: views/lists/fields/create.hbs:12 views/lists/fields/edit.hbs:13 +#: lib/models/fields.js:20 +msgid "GPG Public Key" +msgstr "" + +#: views/lists/fields/create.hbs:13 views/lists/fields/edit.hbs:14 +#: lib/models/fields.js:19 +msgid "Multi-line text" +msgstr "" + +#: views/lists/fields/create.hbs:14 views/lists/fields/edit.hbs:15 +msgid "JSON" +msgstr "" + +#: views/lists/fields/create.hbs:15 views/lists/fields/edit.hbs:16 +msgid "Date" +msgstr "" + +#: views/lists/fields/create.hbs:16 views/lists/fields/edit.hbs:17 +msgid "Date (MM/DD/YYYY)" +msgstr "" + +#: views/lists/fields/create.hbs:17 views/lists/fields/edit.hbs:18 +#: lib/models/fields.js:26 +msgid "Date (DD/MM/YYYY)" +msgstr "" + +#: views/lists/fields/create.hbs:18 views/lists/fields/edit.hbs:19 +msgid "Birthday" +msgstr "" + +#: views/lists/fields/create.hbs:19 views/lists/fields/edit.hbs:20 +#: lib/models/fields.js:27 +msgid "Birthday (MM/DD)" +msgstr "" + +#: views/lists/fields/create.hbs:20 views/lists/fields/edit.hbs:21 +#: lib/models/fields.js:28 +msgid "Birthday (DD/MM)" +msgstr "" + +#: views/lists/fields/create.hbs:21 views/lists/fields/edit.hbs:22 +msgid "Grouped" +msgstr "" + +#: views/lists/fields/create.hbs:22 views/lists/fields/edit.hbs:23 +msgid "Drop Downs" +msgstr "" + +#: views/lists/fields/create.hbs:23 views/lists/fields/edit.hbs:24 +#: lib/models/fields.js:22 +msgid "Radio Buttons" +msgstr "" + +#: views/lists/fields/create.hbs:24 views/lists/fields/edit.hbs:25 +#: lib/models/fields.js:23 +msgid "Checkboxes" +msgstr "" + +#: views/lists/fields/create.hbs:25 views/lists/fields/edit.hbs:26 +msgid "Option for a group value" +msgstr "" + +#: views/lists/fields/create.hbs:26 views/lists/fields/edit.hbs:27 +msgid "Group" +msgstr "" + +#: views/lists/fields/create.hbs:28 views/lists/fields/edit.hbs:29 +msgid "Required for group options" +msgstr "" + +#: views/lists/fields/create.hbs:29 views/lists/fields/create.hbs:30 +#: views/lists/fields/edit.hbs:35 views/lists/fields/edit.hbs:36 +#: views/lists/fields/fields.hbs:9 +msgid "Default merge tag value" +msgstr "" + +#: views/lists/fields/create.hbs:32 views/lists/fields/edit.hbs:34 +msgid "" +"For group elements like checkboxes you can control the appearance of the " +"merge tag with an optional template. The template uses handlebars syntax and " +"you can find all values from {{values}} array, for example " +"{{#each values}} {{this}} {{/each}}. If template is not defined " +"then multiple values are joined with commas. You can also use this template " +"to render JSON values (if the JSON is an array then the array is exposed as " +"values, otherwise you can access the JSON keys directly)." +msgstr "" + +#: views/lists/fields/create.hbs:33 views/lists/fields/edit.hbs:37 +msgid "Visible" +msgstr "" + +#: views/lists/fields/create.hbs:34 +msgid "Add Field" +msgstr "" + +#: views/lists/fields/edit.hbs:4 +msgid "Edit Field" +msgstr "" + +#: views/lists/fields/edit.hbs:5 +msgid "Edit Custom Field" +msgstr "" + +#: views/lists/fields/edit.hbs:6 +msgid "Back to fields" +msgstr "" + +#: views/lists/fields/edit.hbs:30 views/lists/fields/fields.hbs:8 +#: views/partials/merge-tag-reference.hbs:3 +msgid "Merge tag" +msgstr "" + +#: views/lists/fields/edit.hbs:31 +msgid "Merge Tag" +msgstr "" + +#: views/lists/fields/edit.hbs:32 +msgid "Put this tag in your content:" +msgstr "" + +#: views/lists/fields/edit.hbs:38 +msgid "Delete Field" +msgstr "" + +#: views/lists/fields/fields.hbs:7 +msgid "Type" +msgstr "" + +#: views/lists/fields/fields.hbs:10 views/lists/fields/fields.hbs:11 +#: views/lists/lists.hbs:9 views/lists/segments/segments.hbs:8 +#: views/lists/segments/view.hbs:12 views/templates/templates.hbs:7 +#: routes/campaigns.js:287 routes/campaigns.js:576 routes/campaigns.js:626 +#: routes/lists.js:222 routes/triggers.js:297 +msgid "Edit" +msgstr "" + +#: views/lists/lists.hbs:6 +msgid "ID" +msgstr "" + +#: views/lists/lists.hbs:7 +msgid "Subscribers" +msgstr "" + +#: views/lists/segments/create.hbs:3 views/lists/segments/edit.hbs:3 +#: views/lists/segments/rule-configure.hbs:3 +#: views/lists/segments/rule-create.hbs:3 views/lists/segments/rule-edit.hbs:3 +#: views/lists/segments/segments.hbs:3 views/lists/segments/segments.hbs:5 +#: views/lists/segments/view.hbs:3 +msgid "Segments" +msgstr "" + +#: views/lists/segments/create.hbs:4 views/lists/segments/create.hbs:5 +#: views/lists/segments/rule-configure.hbs:4 +#: views/lists/segments/rule-create.hbs:4 views/lists/segments/rule-edit.hbs:4 +#: views/lists/segments/segments.hbs:4 +msgid "Create Segment" +msgstr "" + +#: views/lists/segments/create.hbs:6 views/lists/segments/create.hbs:7 +#: views/lists/segments/edit.hbs:7 views/lists/segments/edit.hbs:8 +msgid "Segment Name" +msgstr "" + +#: views/lists/segments/create.hbs:8 views/lists/segments/edit.hbs:9 +msgid "Rule match" +msgstr "" + +#: views/lists/segments/create.hbs:10 views/lists/segments/edit.hbs:11 +msgid "All rules must match" +msgstr "" + +#: views/lists/segments/create.hbs:11 views/lists/segments/edit.hbs:12 +msgid "Any rule can match" +msgstr "" + +#: views/lists/segments/create.hbs:12 +msgid "Add Segment" +msgstr "" + +#: views/lists/segments/edit.hbs:4 views/lists/segments/edit.hbs:5 +#: views/lists/segments/view.hbs:6 +msgid "Edit Segment" +msgstr "" + +#: views/lists/segments/edit.hbs:6 +msgid "Back to segments" +msgstr "" + +#: views/lists/segments/edit.hbs:13 +msgid "Delete Segment" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:5 +#: views/lists/segments/rule-create.hbs:5 views/lists/segments/rule-edit.hbs:5 +#: views/lists/segments/view.hbs:4 +msgid "Create Rule" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:6 +#: views/lists/segments/rule-create.hbs:6 views/lists/segments/rule-edit.hbs:6 +#: views/lists/segments/view.hbs:10 +msgid "Rule" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:7 +#: views/lists/segments/rule-configure.hbs:8 +#: views/lists/segments/rule-configure.hbs:10 +#: views/lists/segments/rule-configure.hbs:13 +#: views/lists/segments/rule-configure.hbs:25 +#: views/lists/segments/rule-configure.hbs:30 +#: views/lists/segments/rule-edit.hbs:7 views/lists/segments/rule-edit.hbs:8 +#: views/lists/segments/rule-edit.hbs:10 views/lists/segments/rule-edit.hbs:15 +#: views/lists/segments/rule-edit.hbs:29 views/lists/segments/rule-edit.hbs:34 +#: views/lists/segments/view.hbs:11 +msgid "Value" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:9 +#: views/lists/segments/rule-edit.hbs:9 +msgid "" +"Use % for wildcard character, e.g. \"%test\" to match all values that end " +"with \"test\"" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:11 +#: views/lists/segments/rule-configure.hbs:14 +#: views/lists/segments/rule-configure.hbs:26 +#: views/lists/segments/rule-edit.hbs:11 views/lists/segments/rule-edit.hbs:16 +#: views/lists/segments/rule-edit.hbs:30 +msgid "Use exact match" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:12 +#: views/lists/segments/rule-configure.hbs:15 +#: views/lists/segments/rule-configure.hbs:27 +#: views/lists/segments/rule-edit.hbs:12 views/lists/segments/rule-edit.hbs:17 +#: views/lists/segments/rule-edit.hbs:31 +msgid "Use range match" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:16 +#: views/lists/segments/rule-edit.hbs:20 +msgid "Use relative range match" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:17 +#: views/lists/segments/rule-configure.hbs:28 +#: views/lists/segments/rule-edit.hbs:13 views/lists/segments/rule-edit.hbs:18 +#: views/lists/segments/rule-edit.hbs:21 views/lists/segments/rule-edit.hbs:32 +msgid "From" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:18 +#: views/lists/segments/rule-configure.hbs:22 +#: views/lists/segments/rule-edit.hbs:22 views/lists/segments/rule-edit.hbs:26 +msgid "days" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:19 +#: views/lists/segments/rule-configure.hbs:23 +#: views/lists/segments/rule-edit.hbs:23 views/lists/segments/rule-edit.hbs:27 +msgid "before today" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:20 +#: views/lists/segments/rule-configure.hbs:24 +#: views/lists/segments/rule-edit.hbs:24 views/lists/segments/rule-edit.hbs:28 +msgid "after today" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:21 +#: views/lists/segments/rule-configure.hbs:29 +#: views/lists/segments/rule-edit.hbs:14 views/lists/segments/rule-edit.hbs:19 +#: views/lists/segments/rule-edit.hbs:25 views/lists/segments/rule-edit.hbs:33 +msgid "to" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:31 +#: views/lists/segments/rule-edit.hbs:35 lib/models/segments.js:156 +#: lib/models/segments.js:418 +msgid "Selected" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:32 +#: views/lists/segments/rule-edit.hbs:36 lib/models/segments.js:156 +#: lib/models/segments.js:418 +msgid "Not selected" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:33 +msgid "Add Rule" +msgstr "" + +#: views/lists/segments/rule-create.hbs:8 +#: views/lists/subscription/import.hbs:12 +msgid "Next" +msgstr "" + +#: views/lists/segments/rule-edit.hbs:37 +msgid "Delete Rule" +msgstr "" + +#: views/lists/segments/segments.hbs:7 +msgid "Match" +msgstr "" + +#: views/lists/segments/view.hbs:5 +msgid "Segment" +msgstr "" + +#: views/lists/segments/view.hbs:7 +msgid "Match rules" +msgstr "" + +#: views/lists/segments/view.hbs:8 +msgid "Matching subscribers" +msgstr "" + +#: views/lists/segments/view.hbs:9 +msgid "show" +msgstr "" + +#: views/lists/subscription/add.hbs:3 views/lists/subscription/add.hbs:4 +msgid "Add subscriber" +msgstr "" + +#: views/lists/subscription/add.hbs:5 views/subscription/manage.hbs:2 +#: views/subscription/subscribe.hbs:3 +msgid "Email Address" +msgstr "" + +#: views/lists/subscription/add.hbs:8 views/lists/subscription/edit.hbs:9 +#: views/settings.hbs:82 views/settings.hbs:97 views/subscription/manage.hbs:7 +#: views/subscription/subscribe.hbs:7 +msgid "Begins with" +msgstr "" + +#: views/lists/subscription/add.hbs:9 views/lists/subscription/edit.hbs:10 +msgid "" +"Insert a GPG public key that will be used to encrypt messages sent this " +"subscriber" +msgstr "" + +#: views/lists/subscription/add.hbs:11 views/lists/subscription/edit.hbs:12 +#: views/lists/subscription/import-preview.hbs:9 +msgid "Timezone" +msgstr "" + +#: views/lists/subscription/add.hbs:13 views/lists/subscription/edit.hbs:13 +msgid "Test user?" +msgstr "" + +#: views/lists/subscription/add.hbs:14 views/lists/subscription/edit.hbs:14 +msgid "" +"If checked then this subscription can be used for previewing campaign " +"messages" +msgstr "" + +#: views/lists/subscription/add.hbs:15 +msgid "" +"This person will not receive a confirmation email so make sure that you have " +"permission to email them." +msgstr "" + +#: views/lists/subscription/edit.hbs:3 views/lists/subscription/edit.hbs:4 +msgid "Edit subscriber" +msgstr "" + +#: views/lists/subscription/edit.hbs:5 +#: views/lists/subscription/import-failed.hbs:5 +msgid "Back to list" +msgstr "" + +#: views/lists/subscription/edit.hbs:6 +#: views/lists/subscription/import-preview.hbs:6 +#: views/subscription/unsubscribe.hbs:3 lib/helpers.js:26 +#: lib/models/segments.js:11 +msgid "Email address" +msgstr "" + +#: views/lists/subscription/edit.hbs:16 +msgid "Delete Subscription" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:3 +msgid "Import status" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:4 +msgid "Failed addresses" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:6 +msgid "see here" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:10 +msgid "Fail reason" +msgstr "" + +#: views/lists/subscription/import-preview.hbs:3 +#: views/lists/subscription/import-preview.hbs:4 +#: views/lists/subscription/import.hbs:3 views/lists/subscription/import.hbs:4 +msgid "Import subscribers" +msgstr "" + +#: views/lists/subscription/import-preview.hbs:10 +msgid "Example" +msgstr "" + +#: views/lists/subscription/import-preview.hbs:11 +msgid "Start import" +msgstr "" + +#: views/lists/subscription/import.hbs:5 +msgid "CSV File" +msgstr "" + +#: views/lists/subscription/import.hbs:6 +msgid "CSV delimiter" +msgstr "" + +#: views/lists/subscription/import.hbs:7 +msgid "Categorize the imported subscribers as" +msgstr "" + +#: views/lists/subscription/import.hbs:8 routes/lists.js:171 +msgid "Subscribed" +msgstr "" + +#: views/lists/subscription/import.hbs:9 +msgid "Regular subscriber addresses" +msgstr "" + +#: views/lists/subscription/import.hbs:11 +msgid "Suppressed emails that will be unsubscribed from your list" +msgstr "" + +#: views/partials/codeeditor.hbs:1 views/partials/summernote.hbs:1 +msgid "Template content (HTML)" +msgstr "" + +#: views/partials/html-preview.hbs:1 +msgid "Toggle HTML preview" +msgstr "" + +#: views/partials/merge-tag-reference.hbs:1 +msgid "Merge tag reference" +msgstr "" + +#: views/partials/merge-tag-reference.hbs:2 +msgid "" +"Merge tags are tags that are replaced before sending out the message. The " +"format of the merge tag is the following: [TAG_NAME] or " +"[TAG_NAME/fallback] where fallback is an optional " +"text value used when TAG_NAME is empty." +msgstr "" + +#: views/partials/plaintext.hbs:1 +msgid "Template content (plaintext)" +msgstr "" + +#: views/settings.hbs:2 views/settings.hbs:3 +msgid "Settings" +msgstr "" + +#: views/settings.hbs:5 +msgid "Service Address (URL)" +msgstr "" + +#: views/settings.hbs:6 +msgid "Enter the URL this service can be reached from" +msgstr "" + +#: views/settings.hbs:7 +msgid "Admin Email" +msgstr "" + +#: views/settings.hbs:8 +msgid "" +"Enter the email address that will be used as \"from\" for system messages" +msgstr "" + +#: views/settings.hbs:9 +msgid "Disable WYSIWYG editor" +msgstr "" + +#: views/settings.hbs:10 +msgid "If checked then message editor displays HTML code without the preview" +msgstr "" + +#: views/settings.hbs:11 +msgid "Disable subscription confirmation messages" +msgstr "" + +#: views/settings.hbs:12 +msgid "" +"If checked then do not send a confirmation message that states the " +"subscriber is now subscribed or unsubscribed. This does not disable double " +"opt-in messages." +msgstr "" + +#: views/settings.hbs:13 +msgid "Tracking ID" +msgstr "" + +#: views/settings.hbs:14 +msgid "Enter Google Analytics tracking code" +msgstr "" + +#: views/settings.hbs:15 +msgid "Frontpage shout out" +msgstr "" + +#: views/settings.hbs:16 +msgid "HTML code shown in the front page header section" +msgstr "" + +#: views/settings.hbs:17 +msgid "Campaign defaults" +msgstr "" + +#: views/settings.hbs:18 +msgid "Sender name" +msgstr "" + +#: views/settings.hbs:19 +msgid "Sender name, eg. My Awesome Company Ltd." +msgstr "" + +#: views/settings.hbs:20 +msgid "Default address" +msgstr "" + +#: views/settings.hbs:21 +msgid "" +"Contact address to provide, eg. 1234 Main Street, Anywhere, MA 01234, USA" +msgstr "" + +#: views/settings.hbs:22 +msgid "Default \"from name\"" +msgstr "" + +#: views/settings.hbs:24 +msgid "Default \"from\" email" +msgstr "" + +#: views/settings.hbs:26 +msgid "Default \"subject line\"" +msgstr "" + +#: views/settings.hbs:28 +msgid "Default homepage (URL)" +msgstr "" + +#: views/settings.hbs:29 +msgid "URL to redirect the subscribed users to, eg. http://example.com/" +msgstr "" + +#: views/settings.hbs:30 +msgid "Mailer Settings" +msgstr "" + +#: views/settings.hbs:31 +msgid "These settings are required to send out e-mail messages" +msgstr "" + +#: views/settings.hbs:32 +msgid "SMTP" +msgstr "" + +#: views/settings.hbs:33 +msgid "AWS SES" +msgstr "" + +#: views/settings.hbs:34 +msgid "Use SMTP for sending mail" +msgstr "" + +#: views/settings.hbs:35 +msgid "Hostname" +msgstr "" + +#: views/settings.hbs:36 +msgid "Port" +msgstr "" + +#: views/settings.hbs:37 +msgid "Port, eg. 465. Autodetected if left blank" +msgstr "" + +#: views/settings.hbs:38 +msgid "Encryption" +msgstr "" + +#: views/settings.hbs:39 +msgid "Disable SMTP authentication" +msgstr "" + +#: views/settings.hbs:40 +msgid "Username" +msgstr "" + +#: views/settings.hbs:41 +msgid "Username, eg. myaccount@example.com" +msgstr "" + +#: views/settings.hbs:42 views/settings.hbs:43 +msgid "Password" +msgstr "" + +#: views/settings.hbs:44 +msgid "Use SES API for sending mail" +msgstr "" + +#: views/settings.hbs:45 +msgid "Access Key" +msgstr "" + +#: views/settings.hbs:46 +msgid "AWS Access Key Id" +msgstr "" + +#: views/settings.hbs:47 +msgid "Secret Key" +msgstr "" + +#: views/settings.hbs:48 +msgid "AWS Secret Access Key" +msgstr "" + +#: views/settings.hbs:49 +msgid "Region" +msgstr "" + +#: views/settings.hbs:50 +msgid "Checking" +msgstr "" + +#: views/settings.hbs:51 +msgid "Check Mailer config" +msgstr "" + +#: views/settings.hbs:52 +msgid "Don't have an SMTP account yet? Create a free SendPulse account" +msgstr "" + +#: views/settings.hbs:53 +msgid "here" +msgstr "" + +#: views/settings.hbs:54 +msgid "Advanced Mailer settings" +msgstr "" + +#: views/settings.hbs:55 +msgid "Log SMTP transactions" +msgstr "" + +#: views/settings.hbs:56 +msgid "Allow self-signed certificates" +msgstr "" + +#: views/settings.hbs:57 +msgid "Max connections" +msgstr "" + +#: views/settings.hbs:58 +msgid "The count of max connections, eg. 10" +msgstr "" + +#: views/settings.hbs:59 +msgid "" +"The count of maximum simultaneous connections to make against the SMTP " +"server (defaults to 5). This limit is per sending process." +msgstr "" + +#: views/settings.hbs:60 +msgid "Max messages" +msgstr "" + +#: views/settings.hbs:61 +msgid "The count of max messages, eg. 100" +msgstr "" + +#: views/settings.hbs:62 +msgid "" +"he number of messages to send through a single connection before the " +"connection is closed and reopened (defaults to 100)" +msgstr "" + +#: views/settings.hbs:63 +msgid "Throttling" +msgstr "" + +#: views/settings.hbs:64 +msgid "Messages per hour eg. 1000" +msgstr "" + +#: views/settings.hbs:65 +msgid "" +"Maximum number of messages to send in an hour. Leave empty or zero for no " +"throttling. If your provider uses a different speed limit (messages/minute " +"or messages/second) then convert this limit into messages/hour (1m/s => " +"3600m/h). This limit is per sending process." +msgstr "" + +#: views/settings.hbs:66 +msgid "VERP bounce handling" +msgstr "" + +#: views/settings.hbs:67 +msgid "" +"Mailtrain is able to use VERP based routing to detect bounces. In this case " +"the message is sent to the recipient using a custom VERP address as the " +"return path of the message. If the message is not accepted a bounce email is " +"sent to this special VERP address and thus a bounce is detected." +msgstr "" + +#: views/settings.hbs:68 +msgid "" +"To get VERP working you need to set up a DNS MX record that points to your " +"Mailtrain hostname. You must also ensure that Mailtrain VERP interface is " +"available from port 25 of your server (port 25 usually requires root user " +"privileges). This way if anyone tries to send email to someuser@verp-" +"hostname then the email should end up to this server." +msgstr "" + +#: views/settings.hbs:69 +msgid "" +"VERP usually only works if you are using your own SMTP server. Regular relay " +"services (SES, SparkPost, Gmail etc.) tend to remove the VERP address from " +"the message." +msgstr "" + +#: views/settings.hbs:70 +msgid "Use VERP to catch bounces" +msgstr "" + +#: views/settings.hbs:71 +msgid "Server hostname" +msgstr "" + +#: views/settings.hbs:72 +msgid "The VERP server hostname, eg. bounces.example.com" +msgstr "" + +#: views/settings.hbs:73 +msgid "" +"VERP bounce handling server hostname. This hostname is used in the SMTP " +"envelope FROM address and the MX DNS records should point to this server" +msgstr "" + +#: views/settings.hbs:74 +msgid "" +"VERP bounce handling server is not enabled. Modify your server configuration " +"file and restart server to enable it" +msgstr "" + +#: views/settings.hbs:75 +msgid "GPG Signing" +msgstr "" + +#: views/settings.hbs:76 +msgid "" +"Only messages that are encrypted can be signed. Subsribers who have not set " +"up a GPG public key in their profile receive normal email messages. Users " +"with GPG key set receive encrypted messages and if you have signing key also " +"set, the messages are signed with this key." +msgstr "" + +#: views/settings.hbs:77 +msgid "" +"Do not use sensitive keys here. The private key and passphrase are not " +"encrypted in the database." +msgstr "" + +#: views/settings.hbs:78 +msgid "Private Key Passphrase" +msgstr "" + +#: views/settings.hbs:79 +msgid "Passphrase for the key if set" +msgstr "" + +#: views/settings.hbs:80 +msgid "Only fill this if your private key is encrypted with a passphrase" +msgstr "" + +#: views/settings.hbs:81 +msgid "GPG Private Key" +msgstr "" + +#: views/settings.hbs:83 +msgid "" +"This value is optional. If you do not provide a private key GPG encrypted " +"messages are sent without signing." +msgstr "" + +#: views/settings.hbs:84 +msgid "DKIM Signing by ZoneMTA" +msgstr "" + +#: views/settings.hbs:85 +msgid "" +"If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing " +"all outgoing messages. Other services usually provide their own means to " +"DKIM sign your messages" +msgstr "" + +#: views/settings.hbs:86 +msgid "" +"Do not use sensitive keys here. The private key is not encrypted in the " +"database." +msgstr "" + +#: views/settings.hbs:87 +msgid "ZoneMTA DKIM API Key" +msgstr "" + +#: views/settings.hbs:88 +msgid "Some secret value" +msgstr "" + +#: views/settings.hbs:89 +msgid "" +"Secret value known to ZoneMTA for requesting DKIM key information. If this " +"value was generated by the Mailtrain installation script then you can keep " +"it as it is" +msgstr "" + +#: views/settings.hbs:90 +msgid "DKIM domain" +msgstr "" + +#: views/settings.hbs:91 +msgid "Domain name for the DKIM key" +msgstr "" + +#: views/settings.hbs:92 +msgid "Leave blank to use the sender email address domain" +msgstr "" + +#: views/settings.hbs:93 views/settings.hbs:94 +msgid "DKIM key selector" +msgstr "" + +#: views/settings.hbs:95 +msgid "Signing is disabled without a valid selector value" +msgstr "" + +#: views/settings.hbs:96 +msgid "DKIM Private Key" +msgstr "" + +#: views/settings.hbs:98 +msgid "" +"This value is optional. If you do not provide a private key then messages " +"are not signed." +msgstr "" + +#: views/subscription/confirm-notice.hbs:1 views/subscription/subscribe.hbs:1 +msgid "Warning!" +msgstr "" + +#: views/subscription/confirm-notice.hbs:2 +msgid "If JavaScript was not enabled then no confirmation message was sent" +msgstr "" + +#: views/subscription/confirm-notice.hbs:3 +msgid "Almost finished." +msgstr "" + +#: views/subscription/confirm-notice.hbs:4 +msgid "" +"We need to confirm your email address. To complete the subscription process, " +"please click the link in the email we just sent you." +msgstr "" + +#: views/subscription/confirm-notice.hbs:5 +#: views/subscription/unsubscribe-notice.hbs:3 +#: views/subscription/updated-notice.hbs:3 +msgid "return to our website" +msgstr "" + +#: views/subscription/manage-address.hbs:1 +msgid "Update your Email Address" +msgstr "" + +#: views/subscription/manage-address.hbs:2 +msgid "Existing Email Address" +msgstr "" + +#: views/subscription/manage-address.hbs:3 +msgid "New Email Address" +msgstr "" + +#: views/subscription/manage-address.hbs:4 +msgid "Your new email address" +msgstr "" + +#: views/subscription/manage-address.hbs:5 +msgid "" +"You will receive a confirmation request to your new email address that you " +"need to accept before your email is actually changed" +msgstr "" + +#: views/subscription/manage-address.hbs:6 +msgid "Update Email Address" +msgstr "" + +#: views/subscription/manage.hbs:1 +msgid "Update your preferences" +msgstr "" + +#: views/subscription/manage.hbs:3 +msgid "want to change it?" +msgstr "" + +#: views/subscription/manage.hbs:6 views/subscription/subscribe.hbs:6 +msgid "Download signature verification key" +msgstr "" + +#: views/subscription/manage.hbs:8 views/subscription/subscribe.hbs:8 +msgid "" +"Insert your GPG public key here to encrypt messages sent to your address" +msgstr "" + +#: views/subscription/manage.hbs:9 views/subscription/subscribe.hbs:9 +msgid "optional" +msgstr "" + +#: views/subscription/manage.hbs:11 +msgid "Update Profile" +msgstr "" + +#: views/subscription/subscribe.hbs:2 +msgid "JavaScript must be enabled in order for the subscription form to work" +msgstr "" + +#: views/subscription/subscribe.hbs:11 +msgid "Subscribe to list" +msgstr "" + +#: views/subscription/subscribed.hbs:3 +msgid "Thank you for subscribing!" +msgstr "" + +#: views/subscription/subscribed.hbs:4 +msgid "continue to our website" +msgstr "" + +#: views/subscription/unsubscribe-notice.hbs:1 +msgid "Unsubscribe Successful" +msgstr "" + +#: views/subscription/unsubscribe-notice.hbs:2 +msgid "You have been removed from:" +msgstr "" + +#: views/subscription/unsubscribe.hbs:2 +msgid "Enter your email address to unsubscribe from:" +msgstr "" + +#: views/subscription/updated-notice.hbs:1 +msgid "Profile Updated" +msgstr "" + +#: views/subscription/updated-notice.hbs:2 +msgid "Your profile information has been updated." +msgstr "" + +#: views/templates/create.hbs:2 views/templates/edit.hbs:2 +#: views/templates/templates.hbs:2 views/templates/templates.hbs:4 +#: lib/tools.js:115 +msgid "Templates" +msgstr "" + +#: views/templates/create.hbs:3 views/templates/create.hbs:4 +#: views/templates/create.hbs:12 views/templates/templates.hbs:3 +msgid "Create Template" +msgstr "" + +#: views/templates/create.hbs:5 views/templates/edit.hbs:6 +msgid "Template name" +msgstr "" + +#: views/templates/create.hbs:6 views/templates/edit.hbs:7 +msgid "Name for this template, eg. Newsletter" +msgstr "" + +#: views/templates/create.hbs:7 +msgid "HTML Editor" +msgstr "" + +#: views/templates/create.hbs:10 views/templates/edit.hbs:9 +msgid "Optional comments about this template" +msgstr "" + +#: views/templates/edit.hbs:3 views/templates/edit.hbs:4 +msgid "Edit Template" +msgstr "" + +#: views/templates/edit.hbs:5 +msgid "Back to templates" +msgstr "" + +#: views/templates/edit.hbs:11 +msgid "Delete Template" +msgstr "" + +#: lib/feed.js:31 +msgid "Bad status code %s" +msgstr "" + +#: lib/helpers.js:17 +msgid "URL that points to the unsubscribe page" +msgstr "" + +#: lib/helpers.js:20 +msgid "URL that points to the preferences page of the subscriber" +msgstr "" + +#: lib/helpers.js:23 +msgid "URL to preview the message in a browser" +msgstr "" + +#: lib/helpers.js:29 lib/models/segments.js:31 +msgid "First name" +msgstr "" + +#: lib/helpers.js:32 lib/models/segments.js:35 +msgid "Last name" +msgstr "" + +#: lib/helpers.js:35 +msgid "Full name (first and last name combined)" +msgstr "" + +#: lib/helpers.js:38 +msgid "Unique ID that identifies the recipient" +msgstr "" + +#: lib/helpers.js:41 +msgid "Unique ID that identifies the list used for this campaign" +msgstr "" + +#: lib/helpers.js:44 +msgid "Unique ID that identifies current campaign" +msgstr "" + +#: lib/mailer.js:215 +msgid "Invalid mail transport" +msgstr "Vigane maili transport" + +#: lib/models/campaigns.js:271 lib/models/campaigns.js:298 +#: lib/models/campaigns.js:371 lib/models/campaigns.js:494 +#: lib/models/campaigns.js:752 lib/models/campaigns.js:881 +msgid "Missing Campaign ID" +msgstr "" + +#: lib/models/campaigns.js:407 +msgid "Emtpy or too large attahcment" +msgstr "" + +#: lib/models/campaigns.js:573 lib/models/campaigns.js:761 +msgid "Campaign Name must be set" +msgstr "" + +#: lib/models/campaigns.js:577 +msgid "RSS URL must be set and needs to be a valid URL" +msgstr "" + +#: lib/models/campaigns.js:730 +msgid "Selected template not found" +msgstr "" + +#: lib/models/campaigns.js:1082 +msgid "Invalid or missing message ID" +msgstr "" + +#: lib/models/fields.js:24 +msgid "Drop Down" +msgstr "" + +#: lib/models/fields.js:25 +msgid "Date (MM/DD/YYY)" +msgstr "" + +#: lib/models/fields.js:29 +msgid "JSON value for custom rendering" +msgstr "" + +#: lib/models/fields.js:30 +msgid "Option" +msgstr "" + +#: lib/models/fields.js:53 lib/models/fields.js:98 lib/models/fields.js:123 +#: lib/models/lists.js:81 lib/models/lists.js:175 lib/models/lists.js:212 +#: lib/models/segments.js:43 lib/models/segments.js:176 +#: lib/models/subscriptions.js:88 lib/models/subscriptions.js:640 +#: lib/models/subscriptions.js:703 lib/models/subscriptions.js:889 +#: lib/models/subscriptions.js:992 lib/models/subscriptions.js:1046 +#: lib/models/subscriptions.js:1109 lib/models/subscriptions.js:1152 +msgid "Missing List ID" +msgstr "" + +#: lib/models/fields.js:129 +msgid "Option field requires a group to be selected" +msgstr "" + +#: lib/models/fields.js:149 lib/models/fields.js:199 +msgid "Missing Field ID" +msgstr "" + +#: lib/models/fields.js:153 lib/models/segments.js:185 +#: lib/models/segments.js:225 +msgid "Field Name must be set" +msgstr "" + +#: lib/models/fields.js:216 +msgid "Custom field not found" +msgstr "" + +#: lib/models/fields.js:289 +msgid "Unknown column type %s" +msgstr "" + +#: lib/models/fields.js:293 +msgid "Missing column name" +msgstr "" + +#: lib/models/fields.js:297 +msgid "Missing list ID" +msgstr "" + +#: lib/models/fields.js:305 +msgid "Provided List ID not found" +msgstr "" + +#: lib/models/links.js:328 routes/campaigns.js:541 routes/campaigns.js:590 +#: services/sender.js:304 +msgid "Campaign not found" +msgstr "" + +#: lib/models/links.js:336 routes/lists.js:146 services/sender.js:311 +msgid "List not found" +msgstr "" + +#: lib/models/links.js:344 +msgid "Subscription not found" +msgstr "" + +#: lib/models/lists.js:117 lib/models/lists.js:179 +msgid "List Name must be set" +msgstr "" + +#: lib/models/lists.js:241 +msgid "Missing List CID" +msgstr "" + +#: lib/models/segments.js:15 +msgid "Signup country" +msgstr "" + +#: lib/models/segments.js:19 lib/models/triggers.js:11 +msgid "Sign up date" +msgstr "" + +#: lib/models/segments.js:23 lib/models/triggers.js:15 +msgid "Latest open" +msgstr "" + +#: lib/models/segments.js:27 lib/models/triggers.js:19 +msgid "Latest click" +msgstr "" + +#: lib/models/segments.js:69 lib/models/segments.js:216 +#: lib/models/segments.js:256 lib/models/segments.js:278 +msgid "Missing Segment ID" +msgstr "" + +#: lib/models/segments.js:85 lib/models/segments.js:549 +#: lib/models/segments.js:658 +msgid "Segment not found" +msgstr "" + +#: lib/models/segments.js:146 lib/models/segments.js:147 +#: lib/models/segments.js:408 lib/models/segments.js:409 +msgid "%s days after today" +msgstr "" + +#: lib/models/segments.js:146 lib/models/segments.js:147 +#: lib/models/segments.js:408 lib/models/segments.js:409 +msgid "%s days before today" +msgstr "" + +#: lib/models/segments.js:148 lib/models/segments.js:410 +msgid "today" +msgstr "" + +#: lib/models/segments.js:189 lib/models/segments.js:229 +msgid "Invalid segment rule type" +msgstr "" + +#: lib/models/segments.js:289 lib/models/segments.js:454 routes/segments.js:266 +#: routes/segments.js:300 routes/segments.js:370 routes/segments.js:381 +msgid "Selected segment not found" +msgstr "" + +#: lib/models/segments.js:294 lib/models/segments.js:459 routes/segments.js:272 +#: routes/segments.js:306 routes/segments.js:387 +msgid "Invalid rule type" +msgstr "" + +#: lib/models/segments.js:358 lib/models/segments.js:434 +#: lib/models/segments.js:524 +msgid "Missing Rule ID" +msgstr "" + +#: lib/models/segments.js:374 +msgid "Specified rule not found" +msgstr "" + +#: lib/models/segments.js:385 +msgid "Specified segment not found" +msgstr "" + +#: lib/models/segments.js:445 +msgid "Selected rule not found" +msgstr "" + +#: lib/models/subscriptions.js:233 +msgid "%s: Please Confirm Subscription" +msgstr "" + +#: lib/models/subscriptions.js:324 +msgid "Could not save subscription" +msgstr "" + +#: lib/models/subscriptions.js:507 lib/models/subscriptions.js:537 +msgid "Missing Subbscription ID" +msgstr "" + +#: lib/models/subscriptions.js:565 +msgid "Missing Subbscription email address" +msgstr "" + +#: lib/models/subscriptions.js:644 lib/models/subscriptions.js:893 +#: lib/models/subscriptions.js:1156 +msgid "Missing subscription ID" +msgstr "" + +#: lib/models/subscriptions.js:707 +msgid "Missing email address" +msgstr "" + +#: lib/models/subscriptions.js:996 lib/models/subscriptions.js:1050 +#: lib/models/subscriptions.js:1086 +msgid "Missing Import ID" +msgstr "" + +#: lib/models/subscriptions.js:1178 +msgid "Unknown subscription ID" +msgstr "" + +#: lib/models/subscriptions.js:1183 +msgid "Nothing seems to be changed" +msgstr "" + +#: lib/models/subscriptions.js:1197 +msgid "This address is already registered by someone else" +msgstr "" + +#: lib/models/templates.js:51 lib/models/templates.js:122 +#: lib/models/templates.js:163 +msgid "Missing Template ID" +msgstr "" + +#: lib/models/templates.js:80 lib/models/templates.js:126 +msgid "Template Name must be set" +msgstr "" + +#: lib/models/triggers.js:28 +msgid "Has Opened" +msgstr "" + +#: lib/models/triggers.js:31 +msgid "Has Clicked" +msgstr "" + +#: lib/models/triggers.js:34 +msgid "Not Opened" +msgstr "" + +#: lib/models/triggers.js:37 +msgid "Not Clicked" +msgstr "" + +#: lib/models/triggers.js:174 lib/models/triggers.js:211 +msgid "Missing or invalid list ID" +msgstr "" + +#: lib/models/triggers.js:178 lib/models/triggers.js:263 +msgid "Days in the past are not allowed" +msgstr "" + +#: lib/models/triggers.js:182 lib/models/triggers.js:203 +#: lib/models/triggers.js:267 lib/models/triggers.js:288 +msgid "Missing or invalid trigger rule" +msgstr "" + +#: lib/models/triggers.js:189 lib/models/triggers.js:274 +msgid "Invalid subscription configuration" +msgstr "" + +#: lib/models/triggers.js:196 lib/models/triggers.js:281 +msgid "Invalid campaign configuration" +msgstr "" + +#: lib/models/triggers.js:199 lib/models/triggers.js:284 +msgid "A campaing can not be a target for itself" +msgstr "" + +#: lib/models/triggers.js:232 +msgid "Could not store trigger row" +msgstr "" + +#: lib/models/triggers.js:249 +msgid "Missing or invalid Trigger ID" +msgstr "" + +#: lib/models/triggers.js:316 +msgid "Missing Trigger ID" +msgstr "" + +#: lib/models/users.js:103 +msgid "Could not store user row" +msgstr "" + +#: lib/models/users.js:173 +msgid "Email Address must be set" +msgstr "" + +#: lib/models/users.js:184 +msgid "Failed to check user data" +msgstr "" + +#: lib/models/users.js:195 +msgid "" +"Can't change email as another user with the same email address already exists" +msgstr "" + +#: lib/models/users.js:212 +msgid "Incorrect current password" +msgstr "" + +#: lib/models/users.js:216 +msgid "New password not set" +msgstr "" + +#: lib/models/users.js:220 +msgid "Passwords do not match" +msgstr "" + +#: lib/models/users.js:258 +msgid "User ID not set" +msgstr "" + +#: lib/models/users.js:286 +msgid "Username must be set" +msgstr "" + +#: lib/models/users.js:323 +msgid "Mailer password change request" +msgstr "" + +#: lib/models/users.js:347 lib/models/users.js:367 +msgid "Missing username or reset token" +msgstr "" + +#: lib/models/users.js:371 +msgid "Invalid new password" +msgstr "" + +#: lib/passport.js:38 +msgid "%s logged out" +msgstr "" + +#: lib/passport.js:51 +msgid "Failed to authenticate user" +msgstr "" + +#: lib/passport.js:67 +msgid "Logged in as %s" +msgstr "" + +#: lib/passport.js:125 +msgid "Incorrect username or password" +msgstr "" + +#: lib/tools.js:123 +msgid "Automation" +msgstr "" + +#: lib/tools.js:133 +msgid "Blocked email address \"%s\"" +msgstr "" + +#: lib/tools.js:142 +msgid "Invalid email address \"%s\"." +msgstr "" + +#: lib/tools.js:145 +msgid "MX record not found for domain" +msgstr "" + +#: lib/tools.js:148 +msgid "Address domain not found" +msgstr "" + +#: lib/tools.js:151 +msgid "Address domain name is required" +msgstr "" + +#: routes/archive.js:31 routes/archive.js:43 routes/archive.js:55 app.js:211 +msgid "Not Found" +msgstr "Lehekülge ei leitud :(" + +#: routes/archive.js:110 services/sender.js:447 +msgid "Received status code %s from %s" +msgstr "" + +#: routes/archive.js:134 routes/campaigns.js:131 routes/campaigns.js:295 +#: routes/campaigns.js:390 routes/campaigns.js:435 routes/campaigns.js:475 +#: routes/campaigns.js:739 routes/campaigns.js:762 routes/campaigns.js:781 +#: routes/campaigns.js:803 routes/triggers.js:146 +msgid "Could not find campaign with specified ID" +msgstr "" + +#: routes/archive.js:142 routes/campaigns.js:789 +msgid "Attachment not found" +msgstr "" + +#: routes/campaigns.js:26 routes/fields.js:13 routes/lists.js:49 +#: routes/segments.js:13 routes/settings.js:23 routes/templates.js:17 +#: routes/triggers.js:18 routes/users.js:75 routes/users.js:120 msgid "Need to be logged in to access restricted content" msgstr "Pead olema sisse logitud, et näha peidetud sisu" +#: routes/campaigns.js:117 +msgid "Could not create campaign" +msgstr "" + +#: routes/campaigns.js:120 +msgid "Campaign “%s” created" +msgstr "" + +#: routes/campaigns.js:204 +msgid "content from an RSS entry" +msgstr "" + +#: routes/campaigns.js:220 +msgid "Campaign settings updated" +msgstr "" + +#: routes/campaigns.js:222 +msgid "Campaign settings not updated" +msgstr "" + +#: routes/campaigns.js:238 routes/campaigns.js:639 +msgid "Campaign deleted" +msgstr "" + +#: routes/campaigns.js:240 routes/campaigns.js:641 +msgid "Could not delete specified campaign" +msgstr "" + +#: routes/campaigns.js:259 +msgid "Idling" +msgstr "" + +#: routes/campaigns.js:262 +msgid "Scheduled" +msgstr "" + +#: routes/campaigns.js:266 routes/lists.js:265 +msgid "Finished" +msgstr "" + +#: routes/campaigns.js:268 +msgid "Paused" +msgstr "" + +#: routes/campaigns.js:270 +msgid "Inactive" +msgstr "" + +#: routes/campaigns.js:272 +msgid "Active" +msgstr "" + +#: routes/campaigns.js:274 +msgid "Other" +msgstr "" + +#: routes/campaigns.js:429 +msgid "Unknown status selector" +msgstr "" + +#: routes/campaigns.js:657 +msgid "Scheduled sending" +msgstr "" + +#: routes/campaigns.js:659 +msgid "Could not schedule sending" +msgstr "" + +#: routes/campaigns.js:671 +msgid "Sending resumed" +msgstr "" + +#: routes/campaigns.js:673 +msgid "Could not resume sending" +msgstr "" + +#: routes/campaigns.js:685 +msgid "Sending reset" +msgstr "" + +#: routes/campaigns.js:687 +msgid "Could not reset sending" +msgstr "" + +#: routes/campaigns.js:699 routes/campaigns.js:727 +msgid "Sending paused" +msgstr "" + +#: routes/campaigns.js:701 routes/campaigns.js:729 +msgid "Could not pause sending" +msgstr "" + +#: routes/campaigns.js:713 +msgid "Sending activated" +msgstr "" + +#: routes/campaigns.js:715 +msgid "Could not activate sending" +msgstr "" + +#: routes/campaigns.js:750 +msgid "Attachment uploaded" +msgstr "" + +#: routes/campaigns.js:752 +msgid "Could not store attachment" +msgstr "" + +#: routes/campaigns.js:769 +msgid "Attachment deleted" +msgstr "" + +#: routes/campaigns.js:771 +msgid "Could not delete attachment" +msgstr "" + +#: routes/fields.js:28 routes/fields.js:64 routes/fields.js:118 +#: routes/segments.js:28 routes/segments.js:59 routes/segments.js:102 +#: routes/segments.js:151 routes/segments.js:223 routes/segments.js:255 +#: routes/segments.js:289 routes/segments.js:336 routes/segments.js:359 +msgid "Selected list ID not found" +msgstr "" + +#: routes/fields.js:102 +msgid "Could not create custom field" +msgstr "" + +#: routes/fields.js:129 +msgid "Selected field not found" +msgstr "" + +#: routes/fields.js:165 +msgid "Field settings updated" +msgstr "" + +#: routes/fields.js:167 +msgid "Field settings not updated" +msgstr "" + +#: routes/fields.js:183 +msgid "Custom field deleted" +msgstr "" + +#: routes/fields.js:185 +msgid "Could not delete specified field" +msgstr "" + +#: routes/links.js:40 +msgid "Oops, we couldn't find a link for the URL you clicked" +msgstr "" + +#: routes/lists.js:90 +msgid "Could not create list" +msgstr "" + +#: routes/lists.js:93 +msgid "List created" +msgstr "" + +#: routes/lists.js:101 routes/lists.js:236 routes/lists.js:301 +#: routes/lists.js:340 routes/lists.js:409 routes/lists.js:434 +#: routes/lists.js:479 routes/lists.js:501 routes/lists.js:530 +#: routes/lists.js:609 routes/lists.js:666 routes/lists.js:693 +msgid "Could not find list with specified ID" +msgstr "" + +#: routes/lists.js:115 +msgid "List settings updated" +msgstr "" + +#: routes/lists.js:117 +msgid "List settings not updated" +msgstr "" + +#: routes/lists.js:133 +msgid "List deleted" +msgstr "" + +#: routes/lists.js:135 +msgid "Could not delete specified list" +msgstr "" + +#: routes/lists.js:171 +msgid "Unknown" +msgstr "" + +#: routes/lists.js:171 +msgid "Complained" +msgstr "" + +#: routes/lists.js:202 +msgid "Invalid key" +msgstr "" + +#: routes/lists.js:204 +msgid "Expired key" +msgstr "" + +#: routes/lists.js:206 +msgid "Revoked key" +msgstr "" + +#: routes/lists.js:256 +msgid "Initializing" +msgstr "" + +#: routes/lists.js:259 +msgid "Initialized" +msgstr "" + +#: routes/lists.js:262 +msgid "Importing" +msgstr "" + +#: routes/lists.js:268 +msgid "Errored" +msgstr "" + +#: routes/lists.js:346 routes/lists.js:415 routes/lists.js:440 +msgid "Could not find subscriber with specified ID" +msgstr "" + +#: routes/lists.js:392 +msgid "Could not add subscription" +msgstr "" + +#: routes/lists.js:397 +msgid "%s was successfully added to your list" +msgstr "" + +#: routes/lists.js:399 +msgid "%s was not added to your list" +msgstr "" + +#: routes/lists.js:421 +msgid "Could not unsubscribe user" +msgstr "" + +#: routes/lists.js:424 +msgid "%s was successfully unsubscribed from your list" +msgstr "" + +#: routes/lists.js:444 +msgid "%s was successfully removed from your list" +msgstr "" + +#: routes/lists.js:456 +msgid "Another subscriber with email address %s already exists" +msgstr "" + +#: routes/lists.js:463 +msgid "Subscription settings updated" +msgstr "" + +#: routes/lists.js:465 +msgid "Subscription settings not updated" +msgstr "" + +#: routes/lists.js:507 routes/lists.js:615 routes/lists.js:651 +#: routes/lists.js:679 routes/lists.js:699 +msgid "Could not find import data with specified ID" +msgstr "" + +#: routes/lists.js:538 +msgid "Could not process CSV" +msgstr "" + +#: routes/lists.js:547 +msgid "Could not create importer" +msgstr "" + +#: routes/lists.js:598 +msgid "Empty file" +msgstr "" + +#: routes/lists.js:655 +msgid "Import started" +msgstr "" + +#: routes/lists.js:683 +msgid "Import restarted" +msgstr "" + +#: routes/segments.js:86 +msgid "Could not create segment" +msgstr "" + +#: routes/segments.js:89 +msgid "Segment created" +msgstr "" + +#: routes/segments.js:113 +msgid "Selected segment ID not found" +msgstr "" + +#: routes/segments.js:188 +msgid "Segment settings updated" +msgstr "" + +#: routes/segments.js:190 +msgid "Segment settings not updated" +msgstr "" + +#: routes/segments.js:206 +msgid "Segment deleted" +msgstr "" + +#: routes/segments.js:208 +msgid "Could not delete specified segment" +msgstr "" + +#: routes/segments.js:342 +msgid "Could not create rule" +msgstr "" + +#: routes/segments.js:345 +msgid "Rule created" +msgstr "" + +#: routes/segments.js:410 +msgid "Rule settings updated" +msgstr "" + +#: routes/segments.js:412 +msgid "Rule settings not updated" +msgstr "" + +#: routes/segments.js:428 +msgid "Rule deleted" +msgstr "" + +#: routes/segments.js:430 +msgid "Could not delete specified rule" +msgstr "" + #: routes/settings.js:39 msgid "Use TLS" msgstr "Kasuta TLSi" @@ -104,3 +2773,163 @@ 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" + +#: routes/subscription.js:22 +msgid "Selected subscription not found" +msgstr "" + +#: routes/subscription.js:32 routes/subscription.js:103 +#: routes/subscription.js:141 routes/subscription.js:166 +#: routes/subscription.js:191 routes/subscription.js:232 +#: routes/subscription.js:270 routes/subscription.js:317 +#: routes/subscription.js:339 routes/subscription.js:368 +#: routes/subscription.js:392 routes/subscription.js:424 +msgid "Selected list not found" +msgstr "" + +#: routes/subscription.js:78 routes/subscription.js:472 +msgid "%s: Subscription Confirmed" +msgstr "" + +#: routes/subscription.js:217 +msgid "Email address not set" +msgstr "" + +#: routes/subscription.js:255 +msgid "Could not store confirmation data" +msgstr "" + +#: routes/subscription.js:284 routes/subscription.js:349 +#: routes/subscription.js:402 +msgid "Subscription not found from this list" +msgstr "" + +#: routes/subscription.js:383 +msgid "Email address updated, check your mailbox for verification instructions" +msgstr "" + +#: routes/subscription.js:499 routes/subscription.js:515 +msgid "Public key is not set" +msgstr "" + +#: routes/templates.js:98 +msgid "Could not create template" +msgstr "" + +#: routes/templates.js:101 +msgid "Template created" +msgstr "" + +#: routes/templates.js:109 +msgid "Could not find template with specified ID" +msgstr "" + +#: routes/templates.js:140 +msgid "Template settings updated" +msgstr "" + +#: routes/templates.js:142 +msgid "Template settings not updated" +msgstr "" + +#: routes/templates.js:158 +msgid "Template deleted" +msgstr "" + +#: routes/templates.js:160 +msgid "Could not delete specified template" +msgstr "" + +#: routes/triggers.js:62 routes/triggers.js:79 routes/triggers.js:154 +msgid "Could not find selected list" +msgstr "" + +#: routes/triggers.js:131 +msgid "Could not create trigger" +msgstr "" + +#: routes/triggers.js:138 +msgid "Trigger “%s” created" +msgstr "" + +#: routes/triggers.js:214 +msgid "Trigger settings updated" +msgstr "" + +#: routes/triggers.js:216 +msgid "Trigger settings not updated" +msgstr "" + +#: routes/triggers.js:228 +msgid "Trigger deleted" +msgstr "" + +#: routes/triggers.js:230 +msgid "Could not delete specified trigger" +msgstr "" + +#: routes/triggers.js:242 +msgid "Could not find trigger with specified ID" +msgstr "" + +#: routes/triggers.js:255 +msgid "Trigger not found" +msgstr "" + +#: routes/users.js:32 +msgid "" +"An email with password reset instructions has been sent to your email " +"address, if it exists on our system." +msgstr "" + +#: routes/users.js:46 routes/users.js:64 +msgid "Unknown or expired reset token" +msgstr "" + +#: routes/users.js:66 +msgid "Your password has been changed successfully" +msgstr "" + +#: routes/users.js:87 +msgid "User data not found" +msgstr "" + +#: routes/users.js:110 +msgid "Access token updated" +msgstr "" + +#: routes/users.js:112 +msgid "Access token not updated" +msgstr "" + +#: routes/users.js:139 +msgid "Account information updated" +msgstr "" + +#: routes/users.js:141 +msgid "Account information not updated" +msgstr "" + +#: services/feedcheck.js:51 +msgid "Feed error: %s" +msgstr "" + +#: services/feedcheck.js:54 +msgid "Found %s new campaign messages from feed" +msgstr "" + +#: services/feedcheck.js:56 +msgid "Found nothing new from the feed" +msgstr "" + +#: services/feedcheck.js:143 +msgid "RSS entry %s" +msgstr "" + +#: services/importer.js:243 +msgid "Could not access import file" +msgstr "" + +#: services/triggers.js:51 +msgid "Unknown trigger type %s" +msgstr "" diff --git a/languages/mailtrain.pot b/languages/mailtrain.pot index 9d882c15..696c0573 100644 --- a/languages/mailtrain.pot +++ b/languages/mailtrain.pot @@ -8,21 +8,3759 @@ msgstr "" "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" +"POT-Creation-Date: 2017-03-07 14:28+0000\n" + +#: views/archive/layout.hbs:1 +#: views/layout.hbs:1 +#: views/subscription/layout.hbs:1 +#: routes/index.js:11 +msgid "Self hosted email newsletter app" +msgstr "" + +#: views/campaigns/bounced.hbs:1 +#: views/campaigns/campaigns.hbs:1 +#: views/campaigns/clicked.hbs:1 +#: views/campaigns/complained.hbs:1 +#: views/campaigns/create-rss.hbs:1 +#: views/campaigns/create-triggered.hbs:1 +#: views/campaigns/create.hbs:1 +#: views/campaigns/delivered.hbs:1 +#: views/campaigns/edit-rss.hbs:1 +#: views/campaigns/edit-triggered.hbs:1 +#: views/campaigns/edit.hbs:1 +#: views/campaigns/opened.hbs:1 +#: views/campaigns/unsubscribed.hbs:1 +#: views/campaigns/upload-attachment.hbs:1 +#: views/campaigns/view.hbs:1 +#: views/lists/create.hbs:1 +#: views/lists/edit.hbs:1 +#: views/lists/fields/create.hbs:1 +#: views/lists/fields/edit.hbs:1 +#: views/lists/fields/fields.hbs:1 +#: views/lists/lists.hbs:1 +#: views/lists/segments/create.hbs:1 +#: views/lists/segments/edit.hbs:1 +#: views/lists/segments/rule-configure.hbs:1 +#: views/lists/segments/rule-create.hbs:1 +#: views/lists/segments/rule-edit.hbs:1 +#: views/lists/segments/segments.hbs:1 +#: views/lists/segments/view.hbs:1 +#: views/lists/subscription/add.hbs:1 +#: views/lists/subscription/edit.hbs:1 +#: views/lists/subscription/import-failed.hbs:1 +#: views/lists/subscription/import-preview.hbs:1 +#: views/lists/subscription/import.hbs:1 +#: views/lists/view.hbs:1 +#: views/settings.hbs:1 +#: views/templates/create.hbs:1 +#: views/templates/edit.hbs:1 +#: views/templates/templates.hbs:1 +#: views/triggers/create-select.hbs:1 +#: views/triggers/create.hbs:1 +#: views/triggers/edit.hbs:1 +#: views/triggers/triggered.hbs:1 +#: views/triggers/triggers.hbs:1 +#: views/users/account.hbs:1 +#: views/users/api.hbs:1 +#: views/users/forgot.hbs:1 +#: views/users/login.hbs:1 +#: views/users/reset.hbs:1 +#: app.js:169 +msgid "Home" +msgstr "" + +#: views/campaigns/bounced.hbs:2 +#: views/campaigns/campaigns.hbs:2 +#: views/campaigns/campaigns.hbs:7 +#: views/campaigns/clicked.hbs:2 +#: views/campaigns/complained.hbs:2 +#: views/campaigns/create-rss.hbs:2 +#: views/campaigns/create-triggered.hbs:2 +#: views/campaigns/create.hbs:2 +#: views/campaigns/delivered.hbs:2 +#: views/campaigns/edit-rss.hbs:2 +#: views/campaigns/edit-triggered.hbs:2 +#: views/campaigns/edit.hbs:2 +#: views/campaigns/opened.hbs:2 +#: views/campaigns/unsubscribed.hbs:2 +#: views/campaigns/upload-attachment.hbs:2 +#: views/campaigns/view.hbs:2 +#: lib/tools.js:119 +#: routes/campaigns.js:35 +msgid "Campaigns" +msgstr "" + +#: views/campaigns/bounced.hbs:3 +#: views/campaigns/bounced.hbs:4 +msgid "Bounced info" +msgstr "" + +#: views/campaigns/bounced.hbs:5 +#: views/campaigns/clicked.hbs:5 +#: views/campaigns/complained.hbs:5 +#: views/campaigns/delivered.hbs:5 +#: views/campaigns/edit-rss.hbs:5 +#: views/campaigns/edit-triggered.hbs:5 +#: views/campaigns/edit.hbs:5 +#: views/campaigns/opened.hbs:5 +#: views/campaigns/unsubscribed.hbs:5 +#: views/campaigns/upload-attachment.hbs:6 +msgid "View campaign" +msgstr "" + +#: views/campaigns/bounced.hbs:6 +msgid "Subscribers who bounced and were unsubscribed:" +msgstr "" + +#: views/campaigns/bounced.hbs:7 +#: views/campaigns/clicked.hbs:13 +#: views/campaigns/complained.hbs:7 +#: views/campaigns/delivered.hbs:7 +#: views/campaigns/opened.hbs:7 +#: views/campaigns/unsubscribed.hbs:7 +#: views/lists/subscription/import-failed.hbs:9 +#: views/lists/view.hbs:18 +#: views/triggers/triggered.hbs:6 +msgid "Address" +msgstr "" + +#: views/campaigns/bounced.hbs:8 +#: views/campaigns/clicked.hbs:14 +#: views/campaigns/complained.hbs:8 +#: views/campaigns/delivered.hbs:8 +#: views/campaigns/opened.hbs:8 +#: views/campaigns/unsubscribed.hbs:8 +#: views/lists/subscription/add.hbs:6 +#: views/lists/subscription/edit.hbs:7 +#: views/lists/subscription/import-preview.hbs:7 +#: views/lists/view.hbs:19 +#: views/subscription/manage.hbs:4 +#: views/subscription/subscribe.hbs:4 +#: views/triggers/triggered.hbs:7 +msgid "First Name" +msgstr "" + +#: views/campaigns/bounced.hbs:9 +#: views/campaigns/clicked.hbs:15 +#: views/campaigns/complained.hbs:9 +#: views/campaigns/delivered.hbs:9 +#: views/campaigns/opened.hbs:9 +#: views/campaigns/unsubscribed.hbs:9 +#: views/lists/subscription/add.hbs:7 +#: views/lists/subscription/edit.hbs:8 +#: views/lists/subscription/import-preview.hbs:8 +#: views/lists/view.hbs:20 +#: views/subscription/manage.hbs:5 +#: views/subscription/subscribe.hbs:5 +#: views/triggers/triggered.hbs:8 +msgid "Last Name" +msgstr "" + +#: views/campaigns/bounced.hbs:10 +#: views/campaigns/complained.hbs:10 +#: views/campaigns/delivered.hbs:10 +#: views/campaigns/unsubscribed.hbs:10 +msgid "SMTP response" +msgstr "" + +#: views/campaigns/bounced.hbs:11 +msgid "Bounce time" +msgstr "" + +#: views/campaigns/campaigns.hbs:3 +#: views/campaigns/create-triggered.hbs:24 +#: views/campaigns/create.hbs:3 +#: views/campaigns/create.hbs:4 +#: views/campaigns/create.hbs:27 +msgid "Create Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:4 +msgid "Regular Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:5 +msgid "RSS Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:6 +msgid "Triggered Campaign" +msgstr "" + +#: views/campaigns/campaigns.hbs:8 +#: views/campaigns/create-rss.hbs:6 +#: views/campaigns/create-triggered.hbs:5 +#: views/campaigns/create.hbs:5 +#: views/campaigns/edit-rss.hbs:8 +#: views/campaigns/edit-triggered.hbs:9 +#: views/campaigns/edit.hbs:10 +#: views/campaigns/view.hbs:71 +#: views/lists/create.hbs:5 +#: views/lists/edit.hbs:6 +#: views/lists/fields/fields.hbs:6 +#: views/lists/lists.hbs:5 +#: views/lists/segments/segments.hbs:6 +#: views/templates/templates.hbs:5 +#: views/triggers/triggers.hbs:5 +msgid "Name" +msgstr "" + +#: views/campaigns/campaigns.hbs:9 +#: views/campaigns/create-rss.hbs:8 +#: views/campaigns/create-triggered.hbs:7 +#: views/campaigns/create.hbs:7 +#: views/campaigns/edit-rss.hbs:10 +#: views/campaigns/edit-triggered.hbs:11 +#: views/campaigns/edit.hbs:12 +#: views/campaigns/view.hbs:72 +#: views/lists/create.hbs:7 +#: views/lists/edit.hbs:10 +#: views/lists/lists.hbs:8 +#: views/partials/merge-tag-reference.hbs:4 +#: views/templates/create.hbs:9 +#: views/templates/edit.hbs:8 +#: views/templates/templates.hbs:6 +#: views/triggers/create.hbs:7 +#: views/triggers/edit.hbs:8 +#: views/triggers/triggers.hbs:7 +msgid "Description" +msgstr "" + +#: views/campaigns/campaigns.hbs:10 +#: views/campaigns/view.hbs:73 +#: views/lists/view.hbs:21 +#: views/lists/view.hbs:29 +#: views/triggers/triggers.hbs:6 +msgid "Status" +msgstr "" + +#: views/campaigns/campaigns.hbs:11 +#: views/campaigns/view.hbs:74 +#: views/lists/view.hbs:22 +#: views/lists/view.hbs:23 +msgid "Created" +msgstr "" + +#: views/campaigns/clicked.hbs:3 +#: views/campaigns/clicked.hbs:4 +msgid "Link info" +msgstr "" + +#: views/campaigns/clicked.hbs:6 +#: views/campaigns/view.hbs:61 +msgid "URL" +msgstr "" + +#: views/campaigns/clicked.hbs:7 +#: views/campaigns/view.hbs:62 +msgid "Clicks" +msgstr "" + +#: views/campaigns/clicked.hbs:8 +#: views/campaigns/view.hbs:63 +msgid "% of clicks" +msgstr "" + +#: views/campaigns/clicked.hbs:9 +#: views/campaigns/view.hbs:64 +msgid "% of messages" +msgstr "" + +#: views/campaigns/clicked.hbs:10 +#: views/campaigns/view.hbs:67 +msgid "Aggregated clicks" +msgstr "" + +#: views/campaigns/clicked.hbs:11 +msgid "Subscribers who clicked on a link:" +msgstr "" + +#: views/campaigns/clicked.hbs:12 +msgid "Subscribers who clicked on this link:" +msgstr "" + +#: views/campaigns/clicked.hbs:16 +msgid "First click time" +msgstr "" + +#: views/campaigns/clicked.hbs:17 +msgid "Click count" +msgstr "" + +#: views/campaigns/complained.hbs:3 +#: views/campaigns/complained.hbs:4 +msgid "Complained info" +msgstr "" + +#: views/campaigns/complained.hbs:6 +msgid "Subscribers who complained and were unsubscribed:" +msgstr "" + +#: views/campaigns/complained.hbs:11 +msgid "Complain time" +msgstr "" + +#: views/campaigns/create-rss.hbs:3 +#: views/campaigns/create-rss.hbs:4 +#: views/campaigns/create-rss.hbs:20 +msgid "Create RSS Campaign" +msgstr "" + +#: views/campaigns/create-rss.hbs:5 +#: views/campaigns/edit-rss.hbs:6 +msgid "" +"RSS campaign sets up a tracker against selected RSS feed address. Whenever " +"a new entry is found from this feed it is sent to selected list as an email " +"message." +msgstr "" + +#: views/campaigns/create-rss.hbs:7 +#: views/campaigns/create-triggered.hbs:6 +#: views/campaigns/create.hbs:6 +#: views/campaigns/edit-rss.hbs:9 +#: views/campaigns/edit-triggered.hbs:10 +#: views/campaigns/edit.hbs:11 +msgid "Campaign Name" +msgstr "" + +#: views/campaigns/create-rss.hbs:9 +#: views/campaigns/create-triggered.hbs:8 +#: views/campaigns/create.hbs:8 +#: views/campaigns/edit-rss.hbs:11 +#: views/campaigns/edit-triggered.hbs:12 +#: views/campaigns/edit.hbs:13 +#: views/lists/create.hbs:8 +#: views/lists/edit.hbs:11 +#: views/templates/create.hbs:11 +#: views/templates/edit.hbs:10 +#: views/triggers/create.hbs:9 +#: views/triggers/edit.hbs:10 +msgid "HTML is allowed" +msgstr "" + +#: views/campaigns/create-rss.hbs:10 +#: views/campaigns/create-triggered.hbs:9 +#: views/campaigns/create.hbs:9 +#: views/campaigns/edit-rss.hbs:12 +#: views/campaigns/edit-triggered.hbs:13 +#: views/campaigns/edit.hbs:14 +#: views/campaigns/view.hbs:6 +#: views/triggers/create-select.hbs:6 +#: views/triggers/create.hbs:10 +#: views/triggers/edit.hbs:12 +#: views/triggers/triggers.hbs:8 +msgid "List" +msgstr "" + +#: views/campaigns/create-rss.hbs:11 +#: views/campaigns/create-triggered.hbs:10 +#: views/campaigns/create-triggered.hbs:13 +#: views/campaigns/create.hbs:10 +#: views/campaigns/create.hbs:14 +#: views/campaigns/edit-rss.hbs:13 +#: views/campaigns/edit-triggered.hbs:14 +#: views/campaigns/edit.hbs:15 +#: views/lists/fields/create.hbs:27 +#: views/lists/fields/edit.hbs:28 +#: views/lists/segments/create.hbs:9 +#: views/lists/segments/edit.hbs:10 +#: views/lists/segments/rule-create.hbs:7 +#: views/lists/subscription/add.hbs:10 +#: views/lists/subscription/add.hbs:12 +#: views/lists/subscription/edit.hbs:11 +#: views/lists/subscription/import-preview.hbs:5 +#: views/subscription/manage.hbs:10 +#: views/subscription/subscribe.hbs:10 +#: views/templates/create.hbs:8 +#: views/triggers/create-select.hbs:7 +#: views/triggers/create.hbs:17 +#: views/triggers/create.hbs:20 +#: views/triggers/create.hbs:22 +#: views/triggers/create.hbs:26 +#: views/triggers/edit.hbs:19 +#: views/triggers/edit.hbs:22 +#: views/triggers/edit.hbs:24 +#: views/triggers/edit.hbs:28 +msgid "Select" +msgstr "" + +#: views/campaigns/create-rss.hbs:12 +#: views/campaigns/create-triggered.hbs:11 +#: views/campaigns/create.hbs:11 +#: views/campaigns/edit-rss.hbs:14 +#: views/campaigns/edit-triggered.hbs:15 +#: views/campaigns/edit.hbs:16 +#: views/triggers/create-select.hbs:8 +#: views/triggers/create.hbs:11 +#: views/triggers/edit.hbs:13 +msgid "subscribers" +msgstr "" + +#: views/campaigns/create-rss.hbs:13 +#: views/campaigns/edit-rss.hbs:15 +msgid "RSS Feed Url" +msgstr "" + +#: views/campaigns/create-rss.hbs:14 +#: views/campaigns/edit-rss.hbs:16 +msgid "" +"New entries from this RSS URL are sent out to list subscribers as email " +"messages" +msgstr "" + +#: views/campaigns/create-rss.hbs:15 +#: views/campaigns/create-triggered.hbs:17 +#: views/campaigns/create.hbs:18 +#: views/campaigns/edit-rss.hbs:18 +#: views/campaigns/edit-triggered.hbs:16 +#: views/campaigns/edit.hbs:17 +#: views/campaigns/view.hbs:12 +msgid "Email \"from name\"" +msgstr "" + +#: views/campaigns/create-rss.hbs:16 +#: views/campaigns/create-triggered.hbs:18 +#: views/campaigns/create.hbs:19 +#: views/campaigns/edit-rss.hbs:19 +#: views/campaigns/edit.hbs:18 +#: views/settings.hbs:23 +msgid "This is the name your emails will come from" +msgstr "" + +#: views/campaigns/create-rss.hbs:17 +#: views/campaigns/create-triggered.hbs:19 +#: views/campaigns/create.hbs:20 +#: views/campaigns/edit-rss.hbs:20 +#: views/campaigns/edit-triggered.hbs:18 +#: views/campaigns/edit.hbs:19 +#: views/campaigns/view.hbs:13 +msgid "Email \"from\" address" +msgstr "" + +#: views/campaigns/create-rss.hbs:18 +#: views/campaigns/create-triggered.hbs:20 +#: views/campaigns/edit-rss.hbs:21 +#: views/campaigns/edit-triggered.hbs:19 +#: views/settings.hbs:25 +msgid "This is the address people will send replies to" +msgstr "" + +#: views/campaigns/create-rss.hbs:19 +#: views/campaigns/create-triggered.hbs:23 +#: views/campaigns/create.hbs:26 +#: views/campaigns/edit-rss.hbs:22 +#: views/campaigns/edit-triggered.hbs:22 +#: views/campaigns/edit.hbs:25 +msgid "Disable clicked/opened tracking" +msgstr "" + +#: views/campaigns/create-triggered.hbs:3 +#: views/campaigns/create-triggered.hbs:4 +msgid "Create Triggered Campaign" +msgstr "" + +#: views/campaigns/create-triggered.hbs:12 +#: views/campaigns/create.hbs:12 +#: views/campaigns/edit-triggered.hbs:7 +#: views/campaigns/edit.hbs:7 +#: views/lists/fields/create.hbs:31 +#: views/lists/fields/edit.hbs:33 +#: views/templates/create.hbs:13 +msgid "Template" +msgstr "" + +#: views/campaigns/create-triggered.hbs:14 +#: views/campaigns/create.hbs:15 +msgid "Selecting a template creates a campaign specific copy from it" +msgstr "" + +#: views/campaigns/create-triggered.hbs:15 +#: views/campaigns/create.hbs:16 +msgid "Or alternatively use an URL as the message content source:" +msgstr "" + +#: views/campaigns/create-triggered.hbs:16 +#: views/campaigns/create.hbs:17 +#: views/campaigns/edit-triggered.hbs:25 +#: views/campaigns/edit.hbs:28 +msgid "" +"If a message is sent then this URL will be POSTed to using Merge Tags as " +"POST body. Use this if you want to generate the HTML message yourself" +msgstr "" + +#: views/campaigns/create-triggered.hbs:21 +#: views/campaigns/create.hbs:24 +#: views/campaigns/edit-triggered.hbs:20 +#: views/campaigns/edit.hbs:23 +#: views/campaigns/view.hbs:15 +msgid "Email \"subject line\"" +msgstr "" + +#: views/campaigns/create-triggered.hbs:22 +#: views/campaigns/create.hbs:25 +#: views/campaigns/edit-triggered.hbs:21 +#: views/campaigns/edit.hbs:24 +#: views/settings.hbs:27 +msgid "Keep it relevant and non-spammy" +msgstr "" + +#: views/campaigns/create.hbs:13 +msgid "Select a template:" +msgstr "" + +#: views/campaigns/create.hbs:21 +#: views/campaigns/edit.hbs:20 +msgid "" +"This is the address people will send replies to unless reply-to address is " +"set" +msgstr "" + +#: views/campaigns/create.hbs:22 +#: views/campaigns/edit.hbs:21 +#: views/campaigns/view.hbs:14 +msgid "Email \"reply-to\" address" +msgstr "" + +#: views/campaigns/create.hbs:23 +#: views/campaigns/edit.hbs:22 +msgid "If set, this is the address people will send replies to" +msgstr "" + +#: views/campaigns/delivered.hbs:3 +#: views/campaigns/delivered.hbs:4 +msgid "Delivered info" +msgstr "" + +#: views/campaigns/delivered.hbs:6 +msgid "Subscribers who received the message and did not bounce/unsubscribe:" +msgstr "" + +#: views/campaigns/delivered.hbs:11 +msgid "Delivery time" +msgstr "" + +#: views/campaigns/edit-rss.hbs:3 +#: views/campaigns/edit-rss.hbs:4 +msgid "Edit RSS Campaign" +msgstr "" + +#: views/campaigns/edit-rss.hbs:7 +#: views/campaigns/edit-triggered.hbs:8 +#: views/campaigns/edit.hbs:9 +#: views/settings.hbs:4 +#: views/users/account.hbs:6 +msgid "General Settings" +msgstr "" + +#: views/campaigns/edit-rss.hbs:17 +msgid "" +"Use special merge tag [RSS_ENTRY] to mark the position for the RSS post " +"content. Additionally you can use any valid merge tag as well." +msgstr "" + +#: views/campaigns/edit-rss.hbs:23 +#: views/campaigns/edit-triggered.hbs:26 +#: views/campaigns/edit.hbs:34 +msgid "Delete Campaign" +msgstr "" + +#: views/campaigns/edit-rss.hbs:24 +#: views/campaigns/edit-triggered.hbs:27 +#: views/campaigns/edit.hbs:35 +#: views/lists/edit.hbs:13 +#: views/lists/fields/edit.hbs:39 +#: views/lists/segments/edit.hbs:14 +#: views/lists/segments/rule-edit.hbs:38 +#: views/lists/subscription/edit.hbs:17 +#: views/settings.hbs:99 +#: views/templates/edit.hbs:12 +#: views/triggers/edit.hbs:30 +#: views/users/account.hbs:18 +msgid "Update" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:3 +#: views/campaigns/edit-triggered.hbs:4 +msgid "Edit Triggered Campaign" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:6 +#: views/campaigns/edit.hbs:6 +msgid "General" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:17 +msgid "his is the name your emails will come from" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:23 +#: views/campaigns/edit.hbs:26 +msgid "Template Settings" +msgstr "" + +#: views/campaigns/edit-triggered.hbs:24 +#: views/campaigns/edit.hbs:27 +msgid "Template URL" +msgstr "" + +#: views/campaigns/edit.hbs:3 +#: views/campaigns/edit.hbs:4 +#: views/campaigns/upload-attachment.hbs:3 +#: views/campaigns/upload-attachment.hbs:5 +#: views/campaigns/view.hbs:3 +msgid "Edit Campaign" +msgstr "" + +#: views/campaigns/edit.hbs:8 +#: views/campaigns/edit.hbs:29 +msgid "Attachments" +msgstr "" + +#: views/campaigns/edit.hbs:30 +msgid "File" +msgstr "" + +#: views/campaigns/edit.hbs:31 +msgid "Size" +msgstr "" + +#: views/campaigns/edit.hbs:32 +#: views/campaigns/view.hbs:66 +#: views/lists/fields/fields.hbs:12 +#: views/lists/view.hbs:32 +msgid "No data available in table" +msgstr "" + +#: views/campaigns/edit.hbs:33 +#: views/campaigns/upload-attachment.hbs:4 +msgid "Add Attachment" +msgstr "" + +#: views/campaigns/opened.hbs:3 +#: views/campaigns/opened.hbs:4 +msgid "Opened info" +msgstr "" + +#: views/campaigns/opened.hbs:6 +msgid "Subscribers who opened this message:" +msgstr "" + +#: views/campaigns/opened.hbs:10 +msgid "First open" +msgstr "" + +#: views/campaigns/opened.hbs:11 +msgid "Opened count" +msgstr "" + +#: views/campaigns/unsubscribed.hbs:3 +#: views/campaigns/unsubscribed.hbs:4 +msgid "Unsubscribed info" +msgstr "" + +#: views/campaigns/unsubscribed.hbs:6 +msgid "Subscribers who unsubscribed:" +msgstr "" + +#: views/campaigns/unsubscribed.hbs:11 +#: views/campaigns/view.hbs:26 +#: views/lists/subscription/import.hbs:10 +#: routes/lists.js:171 +msgid "Unsubscribed" +msgstr "" + +#: views/campaigns/upload-attachment.hbs:7 +msgid "Upload" +msgstr "" + +#: views/campaigns/view.hbs:4 +msgid "Overview" +msgstr "" + +#: views/campaigns/view.hbs:5 +msgid "Links" +msgstr "" + +#: views/campaigns/view.hbs:7 +msgid "Feed URL" +msgstr "" + +#: views/campaigns/view.hbs:8 +msgid "Last check" +msgstr "" + +#: views/campaigns/view.hbs:9 +msgid "Not yet checked" +msgstr "" + +#: views/campaigns/view.hbs:10 +msgid "activate campaign to start checking feed for new messages" +msgstr "" + +#: views/campaigns/view.hbs:11 +msgid "RSS status" +msgstr "" + +#: views/campaigns/view.hbs:16 +msgid "Preview campaign as" +msgstr "" + +#: views/campaigns/view.hbs:17 +msgid "Add new test user" +msgstr "" + +#: views/campaigns/view.hbs:18 +msgid "No test users yet, create one here" +msgstr "" + +#: views/campaigns/view.hbs:19 +msgid "Go" +msgstr "" + +#: views/campaigns/view.hbs:20 +#: lib/models/triggers.js:25 +msgid "Delivered" +msgstr "" + +#: views/campaigns/view.hbs:21 +msgid "List subscribers who received this message" +msgstr "" + +#: views/campaigns/view.hbs:22 +#: routes/lists.js:171 +msgid "Bounced" +msgstr "" + +#: views/campaigns/view.hbs:23 +msgid "List subscribers who bounced" +msgstr "" + +#: views/campaigns/view.hbs:24 +msgid "Complaints" +msgstr "" + +#: views/campaigns/view.hbs:25 +msgid "List subscribers who complained for this message" +msgstr "" + +#: views/campaigns/view.hbs:27 +msgid "List subscribers who unsubscribed after this message" +msgstr "" + +#: views/campaigns/view.hbs:28 +msgid "Opened" +msgstr "" + +#: views/campaigns/view.hbs:29 +msgid "List subscribers who opened this message" +msgstr "" + +#: views/campaigns/view.hbs:30 +msgid "Clicked" +msgstr "" + +#: views/campaigns/view.hbs:31 +#: views/campaigns/view.hbs:68 +msgid "List subscribers who clicked on a link" +msgstr "" + +#: views/campaigns/view.hbs:32 +msgid "Are you sure? This action would start sending messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:33 +msgid "Delay sending" +msgstr "" + +#: views/campaigns/view.hbs:34 +msgid "hours" +msgstr "" + +#: views/campaigns/view.hbs:35 +msgid "minutes" +msgstr "" + +#: views/campaigns/view.hbs:36 +msgid "Send to subscribers:" +msgstr "" + +#: views/campaigns/view.hbs:37 +msgid "Are you sure? This action would reset scheduling" +msgstr "" + +#: views/campaigns/view.hbs:38 +msgid "Cancel" +msgstr "" + +#: views/campaigns/view.hbs:39 +msgid "Sending scheduled" +msgstr "" + +#: views/campaigns/view.hbs:40 +#: views/campaigns/view.hbs:52 +msgid "Pause" +msgstr "" + +#: views/campaigns/view.hbs:41 +#: routes/campaigns.js:264 +msgid "Sending" +msgstr "" + +#: views/campaigns/view.hbs:42 +#: views/campaigns/view.hbs:46 +msgid "Are you sure? This action would resume sending messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:43 +#: views/campaigns/view.hbs:47 +msgid "Are you sure? This action would reset all stats about current progress" +msgstr "" + +#: views/campaigns/view.hbs:44 +msgid "Resume" +msgstr "" + +#: views/campaigns/view.hbs:45 +#: views/campaigns/view.hbs:49 +msgid "Reset" +msgstr "" + +#: views/campaigns/view.hbs:48 +msgid "Continue" +msgstr "" + +#: views/campaigns/view.hbs:50 +msgid "" +"All messages sent! Hit \"Continue\" if you you want to send this campaign " +"to new subscribers" +msgstr "" + +#: views/campaigns/view.hbs:51 +msgid "" +"Are you sure? This action would pause sending new entries in RSS feed as " +"email messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:53 +#: views/campaigns/view.hbs:57 +msgid "Campaign status:" +msgstr "" + +#: views/campaigns/view.hbs:54 +msgid "ACTIVE" +msgstr "" + +#: views/campaigns/view.hbs:55 +msgid "" +"Are you sure? This action would start sending new entries in RSS feed as " +"email messages to the selected list" +msgstr "" + +#: views/campaigns/view.hbs:56 +msgid "Activate" +msgstr "" + +#: views/campaigns/view.hbs:58 +msgid "INACTIVE" +msgstr "" + +#: views/campaigns/view.hbs:59 +msgid "" +"This is a triggered campaign. Messages are only sent to subscribers that " +"hit some trigger that invokes this campaign" +msgstr "" + +#: views/campaigns/view.hbs:60 +msgid "see more" +msgstr "" + +#: views/campaigns/view.hbs:65 +msgid "List subscribers who clicked this link" +msgstr "" + +#: views/campaigns/view.hbs:69 +msgid "" +"Clicks are counted as unique subscribers that clicked on a specific link or " +"on any link (in aggregated view)" +msgstr "" + +#: views/campaigns/view.hbs:70 +msgid "" +"If a new entry is found from campaign feed a new subcampaign is created of " +"that entry and it will be listed here" +msgstr "" + +#: views/emails/confirm-html.hbs:1 +#: views/emails/confirm-html.hbs:2 +#: views/emails/confirm-text.hbs:1 +msgid "Please Confirm Subscription" +msgstr "" + +#: views/emails/confirm-html.hbs:3 +#: views/emails/confirm-text.hbs:2 +msgid "Yes, subscribe me to this list" +msgstr "" + +#: views/emails/confirm-html.hbs:4 +msgid "" +"If you received this email by mistake, simply delete it. You won't be " +"subscribed if you don't click the confirmation link above." +msgstr "" + +#: views/emails/confirm-html.hbs:5 +#: views/emails/confirm-text.hbs:4 +#: views/emails/subscription-confirmed-html.hbs:7 +#: views/emails/subscription-confirmed-text.hbs:7 +#: views/emails/unsubscribe-confirmed-html.hbs:5 +#: views/emails/unsubscribe-confirmed-text.hbs:5 +msgid "For questions about this list, please contact:" +msgstr "" + +#: views/emails/confirm-text.hbs:3 +msgid "" +"If you received this email by mistake, simply delete it. You won't be " +"subscribed unless you click the confirmation link above." +msgstr "" + +#: views/emails/password-reset-html.hbs:1 +#: views/emails/password-reset-html.hbs:2 +#: views/emails/password-reset-text.hbs:1 +msgid "Change your password" +msgstr "" + +#: views/emails/password-reset-html.hbs:3 +#: views/emails/password-reset-text.hbs:2 +msgid "We have received a password change request for your Mailtrain account:" +msgstr "" + +#: views/emails/password-reset-html.hbs:4 +#: views/emails/password-reset-text.hbs:3 +msgid "Reset password" +msgstr "" + +#: views/emails/password-reset-html.hbs:5 +#: views/emails/password-reset-text.hbs:4 +msgid "" +"If you did not ask to change your password, then you can ignore this email " +"and your password will not be changed." +msgstr "" + +#: views/emails/rss-html.hbs:1 +#: views/emails/stationery-html.hbs:3 +#: views/emails/stationery-text.hbs:3 +msgid "Preferences" +msgstr "" + +#: views/emails/rss-html.hbs:2 +#: views/emails/stationery-html.hbs:4 +#: views/emails/stationery-text.hbs:4 +#: views/lists/subscription/edit.hbs:15 +#: views/subscription/manage.hbs:12 +#: views/subscription/unsubscribe.hbs:1 +#: views/subscription/unsubscribe.hbs:4 +#: routes/lists.js:253 +msgid "Unsubscribe" +msgstr "" + +#: views/emails/rss-html.hbs:3 +#: views/emails/stationery-html.hbs:5 +#: views/emails/stationery-text.hbs:5 +msgid "View this email in your browser" +msgstr "" + +#: views/emails/stationery-html.hbs:1 +#: views/emails/stationery-text.hbs:1 +msgid "Hey [FIRST_NAME/Customer]," +msgstr "" + +#: views/emails/stationery-html.hbs:2 +#: views/emails/stationery-text.hbs:2 +msgid "Cheers," +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:1 +#: views/emails/subscription-confirmed-text.hbs:1 +#: views/subscription/subscribed.hbs:1 +msgid "Subscription Confirmed" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:2 +#: views/emails/subscription-confirmed-text.hbs:2 +#: views/subscription/subscribed.hbs:2 +msgid "Your subscription to our list has been confirmed." +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:3 +#: views/emails/subscription-confirmed-text.hbs:3 +msgid "If you want to modify your subscription then you can:" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:4 +#: views/emails/subscription-confirmed-text.hbs:4 +#: views/subscription/subscribed.hbs:6 +msgid "manage your preferences" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:5 +#: views/emails/subscription-confirmed-text.hbs:5 +#: views/subscription/subscribed.hbs:5 +#: views/users/login.hbs:10 +msgid "or" +msgstr "" + +#: views/emails/subscription-confirmed-html.hbs:6 +#: views/emails/subscription-confirmed-text.hbs:6 +msgid "unsubscribe here" +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:1 +#: views/emails/unsubscribe-confirmed-text.hbs:1 +msgid "You are now unsubscribed" +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:2 +#: views/emails/unsubscribe-confirmed-text.hbs:2 +msgid "We have removed your email address from our list." +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:3 +#: views/emails/unsubscribe-confirmed-text.hbs:3 +msgid "If you unsubscribed by mistake, you can re-subscribe at:" +msgstr "" + +#: views/emails/unsubscribe-confirmed-html.hbs:4 +#: views/emails/unsubscribe-confirmed-text.hbs:4 +#: views/lists/subscription/add.hbs:16 +#: routes/lists.js:253 +msgid "Subscribe" +msgstr "" #: views/index.hbs:1 msgid "Official Mailtrain Partners" msgstr "" -#: views/layout.hbs:1 -#: routes/index.js:11 -msgid "Self hosted email newsletter app" +#: views/index.hbs:2 +msgid "Free, open source mail server solution" msgstr "" +#: views/index.hbs:3 +msgid "A reliable SMTP server, easy integration, and 12,000 messages a month free" +msgstr "" + +#: views/index.hbs:4 +msgid "List management" +msgstr "" + +#: views/index.hbs:5 +msgid "" +"Mailtrain allows you to easily manage even very large lists. Million " +"subscribers? Not a problem. You can add subscribers manually, through the " +"API or import from a CSV file. All lists come with support for custom " +"fields and merge tags as well." +msgstr "" + +#: views/index.hbs:6 +msgid "Custom fields" +msgstr "" + +#: views/index.hbs:7 +msgid "" +"Text fields, numbers, drop downs or checkboxes, Mailtrain has them all. " +"Every custom field can be included in the generated newsletters through " +"merge tags." +msgstr "" + +#: views/index.hbs:8 +msgid "List segmentation" +msgstr "" + +#: views/index.hbs:9 +msgid "" +"Send messages only to list subscribers that match predefined segmentation " +"rules. No need to create separate lists with small differences." +msgstr "" + +#: views/index.hbs:10 +msgid "Donate to author" +msgstr "" + +#: views/index.hbs:11 +msgid "" +"If you really like Mailtrain or your business benefits from it financially " +"then I would really appreciate a small donation to keep the Mailtrain " +"development engines running. You can either use Bitcoin or PayPal for " +"donations. My Bitcoin wallet is " +"15Z8ADxhssKUiwP3jbbqJwA21744KMCfTM" +msgstr "" + +#: views/index.hbs:12 +msgid "or donate using PayPal" +msgstr "" + +#: views/index.hbs:13 +msgid "RSS Campaigns" +msgstr "" + +#: views/index.hbs:14 +msgid "" +"Setup Mailtrain to track RSS feeds and if a new entry is detected in a feed " +"then Mailtrain auto-generates a new campaign using entry data as message " +"contents and sends it to selected subscribers." +msgstr "" + +#: views/index.hbs:15 +msgid "GPG Encryption" +msgstr "" + +#: views/index.hbs:16 +msgid "" +"If a list has a custom field for a GPG Public Key set then subscribers can " +"upload their GPG public key to receive encrypted messages from the list." +msgstr "" + +#: views/index.hbs:17 +msgid "Click stats" +msgstr "" + +#: views/index.hbs:18 +msgid "" +"After a campaign is sent, check individual click statistics for every link " +"included in the message." +msgstr "" + +#: views/index.hbs:19 +msgid "Open source" +msgstr "" + +#: views/index.hbs:20 +msgid "Mailtrain is available under GPLv3 license and completely open source." +msgstr "" + +#: views/index.hbs:21 +msgid "Send via any provider" +msgstr "" + +#: views/index.hbs:22 +msgid "" +"Mailtrain recommends SendPulse even though you can use any provider that supports SMTP " +"protocol to send out your newsletters. Bounce and complaints handling via " +"webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for " +"Postfix and ZoneMTA." +msgstr "" + +#: views/index.hbs:23 +msgid "Trigger based automation" +msgstr "" + +#: views/index.hbs:24 +msgid "" +"Define automation triggers to send specific messages when a user activates " +"the trigger." +msgstr "" + +#: views/layout.hbs:2 +msgid "Toggle navigation" +msgstr "" + +#: views/layout.hbs:3 +msgid "Wiki" +msgstr "" + +#: views/layout.hbs:4 +msgid "Blog" +msgstr "" + +#: views/layout.hbs:5 +#: views/users/account.hbs:2 +#: views/users/account.hbs:3 +msgid "Account" +msgstr "" + +#: views/layout.hbs:6 +#: views/settings.hbs:2 +#: views/settings.hbs:3 +msgid "Settings" +msgstr "" + +#: views/layout.hbs:7 +#: views/users/api.hbs:2 +#: views/users/api.hbs:3 +msgid "API" +msgstr "" + +#: views/layout.hbs:8 +msgid "Log out" +msgstr "" + +#: views/layout.hbs:9 +#: views/users/forgot.hbs:2 +#: views/users/login.hbs:2 +#: views/users/login.hbs:3 +#: views/users/login.hbs:9 +#: views/users/reset.hbs:2 +msgid "Sign in" +msgstr "" + +#: views/layout.hbs:10 +msgid "Self hosted newsletter app built on top of Nodemailer" +msgstr "" + +#: views/layout.hbs:11 +#: views/layout.hbs:13 +msgid "Source on GitHub" +msgstr "" + +#: views/layout.hbs:12 +msgid "Subscribe to our newsletter" +msgstr "" + +#: views/lists/create.hbs:2 +#: views/lists/edit.hbs:2 +#: views/lists/fields/create.hbs:2 +#: views/lists/fields/edit.hbs:2 +#: views/lists/fields/fields.hbs:2 +#: views/lists/lists.hbs:2 +#: views/lists/lists.hbs:4 +#: views/lists/segments/create.hbs:2 +#: views/lists/segments/edit.hbs:2 +#: views/lists/segments/rule-configure.hbs:2 +#: views/lists/segments/rule-create.hbs:2 +#: views/lists/segments/rule-edit.hbs:2 +#: views/lists/segments/segments.hbs:2 +#: views/lists/segments/view.hbs:2 +#: views/lists/subscription/add.hbs:2 +#: views/lists/subscription/edit.hbs:2 +#: views/lists/subscription/import-failed.hbs:2 +#: views/lists/subscription/import-preview.hbs:2 +#: views/lists/subscription/import.hbs:2 +#: views/lists/view.hbs:2 +#: lib/tools.js:111 +msgid "Lists" +msgstr "" + +#: views/lists/create.hbs:3 +#: views/lists/create.hbs:4 +#: views/lists/create.hbs:9 +#: views/lists/lists.hbs:3 +msgid "Create List" +msgstr "" + +#: views/lists/create.hbs:6 +#: views/lists/edit.hbs:7 +msgid "List Name" +msgstr "" + +#: views/lists/edit.hbs:3 +#: views/lists/edit.hbs:4 +#: views/lists/view.hbs:7 +msgid "Edit List" +msgstr "" + +#: views/lists/edit.hbs:5 +msgid "View List" +msgstr "" + +#: views/lists/edit.hbs:8 +msgid "List ID" +msgstr "" + +#: views/lists/edit.hbs:9 +msgid "This is the list ID displayed to the subscribers" +msgstr "" + +#: views/lists/edit.hbs:12 +msgid "Delete List" +msgstr "" + +#: views/lists/fields/create.hbs:3 +#: views/lists/fields/edit.hbs:3 +#: views/lists/fields/fields.hbs:3 +#: views/lists/fields/fields.hbs:5 +#: views/lists/view.hbs:5 +msgid "Custom Fields" +msgstr "" + +#: views/lists/fields/create.hbs:4 +msgid "Create Field" +msgstr "" + +#: views/lists/fields/create.hbs:5 +#: views/lists/fields/fields.hbs:4 +msgid "Create Custom Field" +msgstr "" + +#: views/lists/fields/create.hbs:6 +#: views/lists/fields/create.hbs:7 +#: views/lists/fields/edit.hbs:7 +#: views/lists/fields/edit.hbs:8 +msgid "Field Name" +msgstr "" + +#: views/lists/fields/create.hbs:8 +#: views/lists/fields/edit.hbs:9 +msgid "Field Type" +msgstr "" + +#: views/lists/fields/create.hbs:9 +#: views/lists/fields/edit.hbs:10 +#: lib/models/fields.js:17 +msgid "Text" +msgstr "" + +#: views/lists/fields/create.hbs:10 +#: views/lists/fields/edit.hbs:11 +#: lib/models/fields.js:21 +msgid "Number" +msgstr "" + +#: views/lists/fields/create.hbs:11 +#: views/lists/fields/edit.hbs:12 +#: lib/models/fields.js:18 +msgid "Website" +msgstr "" + +#: views/lists/fields/create.hbs:12 +#: views/lists/fields/edit.hbs:13 +#: lib/models/fields.js:20 +msgid "GPG Public Key" +msgstr "" + +#: views/lists/fields/create.hbs:13 +#: views/lists/fields/edit.hbs:14 +#: lib/models/fields.js:19 +msgid "Multi-line text" +msgstr "" + +#: views/lists/fields/create.hbs:14 +#: views/lists/fields/edit.hbs:15 +msgid "JSON" +msgstr "" + +#: views/lists/fields/create.hbs:15 +#: views/lists/fields/edit.hbs:16 +msgid "Date" +msgstr "" + +#: views/lists/fields/create.hbs:16 +#: views/lists/fields/edit.hbs:17 +msgid "Date (MM/DD/YYYY)" +msgstr "" + +#: views/lists/fields/create.hbs:17 +#: views/lists/fields/edit.hbs:18 +#: lib/models/fields.js:26 +msgid "Date (DD/MM/YYYY)" +msgstr "" + +#: views/lists/fields/create.hbs:18 +#: views/lists/fields/edit.hbs:19 +msgid "Birthday" +msgstr "" + +#: views/lists/fields/create.hbs:19 +#: views/lists/fields/edit.hbs:20 +#: lib/models/fields.js:27 +msgid "Birthday (MM/DD)" +msgstr "" + +#: views/lists/fields/create.hbs:20 +#: views/lists/fields/edit.hbs:21 +#: lib/models/fields.js:28 +msgid "Birthday (DD/MM)" +msgstr "" + +#: views/lists/fields/create.hbs:21 +#: views/lists/fields/edit.hbs:22 +msgid "Grouped" +msgstr "" + +#: views/lists/fields/create.hbs:22 +#: views/lists/fields/edit.hbs:23 +msgid "Drop Downs" +msgstr "" + +#: views/lists/fields/create.hbs:23 +#: views/lists/fields/edit.hbs:24 +#: lib/models/fields.js:22 +msgid "Radio Buttons" +msgstr "" + +#: views/lists/fields/create.hbs:24 +#: views/lists/fields/edit.hbs:25 +#: lib/models/fields.js:23 +msgid "Checkboxes" +msgstr "" + +#: views/lists/fields/create.hbs:25 +#: views/lists/fields/edit.hbs:26 +msgid "Option for a group value" +msgstr "" + +#: views/lists/fields/create.hbs:26 +#: views/lists/fields/edit.hbs:27 +msgid "Group" +msgstr "" + +#: views/lists/fields/create.hbs:28 +#: views/lists/fields/edit.hbs:29 +msgid "Required for group options" +msgstr "" + +#: views/lists/fields/create.hbs:29 +#: views/lists/fields/create.hbs:30 +#: views/lists/fields/edit.hbs:35 +#: views/lists/fields/edit.hbs:36 +#: views/lists/fields/fields.hbs:9 +msgid "Default merge tag value" +msgstr "" + +#: views/lists/fields/create.hbs:32 +#: views/lists/fields/edit.hbs:34 +msgid "" +"For group elements like checkboxes you can control the appearance of the " +"merge tag with an optional template. The template uses handlebars syntax " +"and you can find all values from {{values}} array, for example " +"{{#each values}} {{this}} {{/each}}. If template is not " +"defined then multiple values are joined with commas. You can also use this " +"template to render JSON values (if the JSON is an array then the array is " +"exposed as values, otherwise you can access the JSON keys " +"directly)." +msgstr "" + +#: views/lists/fields/create.hbs:33 +#: views/lists/fields/edit.hbs:37 +msgid "Visible" +msgstr "" + +#: views/lists/fields/create.hbs:34 +msgid "Add Field" +msgstr "" + +#: views/lists/fields/edit.hbs:4 +msgid "Edit Field" +msgstr "" + +#: views/lists/fields/edit.hbs:5 +msgid "Edit Custom Field" +msgstr "" + +#: views/lists/fields/edit.hbs:6 +msgid "Back to fields" +msgstr "" + +#: views/lists/fields/edit.hbs:30 +#: views/lists/fields/fields.hbs:8 +#: views/partials/merge-tag-reference.hbs:3 +msgid "Merge tag" +msgstr "" + +#: views/lists/fields/edit.hbs:31 +msgid "Merge Tag" +msgstr "" + +#: views/lists/fields/edit.hbs:32 +msgid "Put this tag in your content:" +msgstr "" + +#: views/lists/fields/edit.hbs:38 +msgid "Delete Field" +msgstr "" + +#: views/lists/fields/fields.hbs:7 +#: views/lists/view.hbs:25 +msgid "Type" +msgstr "" + +#: views/lists/fields/fields.hbs:10 +#: views/lists/fields/fields.hbs:11 +#: views/lists/lists.hbs:9 +#: views/lists/segments/segments.hbs:8 +#: views/lists/segments/view.hbs:12 +#: views/templates/templates.hbs:7 +#: views/triggers/triggers.hbs:14 +#: routes/campaigns.js:287 +#: routes/campaigns.js:576 +#: routes/campaigns.js:626 +#: routes/lists.js:222 +#: routes/triggers.js:297 +msgid "Edit" +msgstr "" + +#: views/lists/lists.hbs:6 +msgid "ID" +msgstr "" + +#: views/lists/lists.hbs:7 +msgid "Subscribers" +msgstr "" + +#: views/lists/segments/create.hbs:3 +#: views/lists/segments/edit.hbs:3 +#: views/lists/segments/rule-configure.hbs:3 +#: views/lists/segments/rule-create.hbs:3 +#: views/lists/segments/rule-edit.hbs:3 +#: views/lists/segments/segments.hbs:3 +#: views/lists/segments/segments.hbs:5 +#: views/lists/segments/view.hbs:3 +#: views/lists/view.hbs:6 +#: views/lists/view.hbs:13 +msgid "Segments" +msgstr "" + +#: views/lists/segments/create.hbs:4 +#: views/lists/segments/create.hbs:5 +#: views/lists/segments/rule-configure.hbs:4 +#: views/lists/segments/rule-create.hbs:4 +#: views/lists/segments/rule-edit.hbs:4 +#: views/lists/segments/segments.hbs:4 +msgid "Create Segment" +msgstr "" + +#: views/lists/segments/create.hbs:6 +#: views/lists/segments/create.hbs:7 +#: views/lists/segments/edit.hbs:7 +#: views/lists/segments/edit.hbs:8 +msgid "Segment Name" +msgstr "" + +#: views/lists/segments/create.hbs:8 +#: views/lists/segments/edit.hbs:9 +msgid "Rule match" +msgstr "" + +#: views/lists/segments/create.hbs:10 +#: views/lists/segments/edit.hbs:11 +msgid "All rules must match" +msgstr "" + +#: views/lists/segments/create.hbs:11 +#: views/lists/segments/edit.hbs:12 +msgid "Any rule can match" +msgstr "" + +#: views/lists/segments/create.hbs:12 +msgid "Add Segment" +msgstr "" + +#: views/lists/segments/edit.hbs:4 +#: views/lists/segments/edit.hbs:5 +#: views/lists/segments/view.hbs:6 +#: views/lists/view.hbs:11 +msgid "Edit Segment" +msgstr "" + +#: views/lists/segments/edit.hbs:6 +msgid "Back to segments" +msgstr "" + +#: views/lists/segments/edit.hbs:13 +msgid "Delete Segment" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:5 +#: views/lists/segments/rule-create.hbs:5 +#: views/lists/segments/rule-edit.hbs:5 +#: views/lists/segments/view.hbs:4 +msgid "Create Rule" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:6 +#: views/lists/segments/rule-create.hbs:6 +#: views/lists/segments/rule-edit.hbs:6 +#: views/lists/segments/view.hbs:10 +msgid "Rule" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:7 +#: views/lists/segments/rule-configure.hbs:8 +#: views/lists/segments/rule-configure.hbs:10 +#: views/lists/segments/rule-configure.hbs:13 +#: views/lists/segments/rule-configure.hbs:25 +#: views/lists/segments/rule-configure.hbs:30 +#: views/lists/segments/rule-edit.hbs:7 +#: views/lists/segments/rule-edit.hbs:8 +#: views/lists/segments/rule-edit.hbs:10 +#: views/lists/segments/rule-edit.hbs:15 +#: views/lists/segments/rule-edit.hbs:29 +#: views/lists/segments/rule-edit.hbs:34 +#: views/lists/segments/view.hbs:11 +msgid "Value" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:9 +#: views/lists/segments/rule-edit.hbs:9 +msgid "" +"Use % for wildcard character, e.g. \"%test\" to match all values that end " +"with \"test\"" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:11 +#: views/lists/segments/rule-configure.hbs:14 +#: views/lists/segments/rule-configure.hbs:26 +#: views/lists/segments/rule-edit.hbs:11 +#: views/lists/segments/rule-edit.hbs:16 +#: views/lists/segments/rule-edit.hbs:30 +msgid "Use exact match" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:12 +#: views/lists/segments/rule-configure.hbs:15 +#: views/lists/segments/rule-configure.hbs:27 +#: views/lists/segments/rule-edit.hbs:12 +#: views/lists/segments/rule-edit.hbs:17 +#: views/lists/segments/rule-edit.hbs:31 +msgid "Use range match" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:16 +#: views/lists/segments/rule-edit.hbs:20 +msgid "Use relative range match" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:17 +#: views/lists/segments/rule-configure.hbs:28 +#: views/lists/segments/rule-edit.hbs:13 +#: views/lists/segments/rule-edit.hbs:18 +#: views/lists/segments/rule-edit.hbs:21 +#: views/lists/segments/rule-edit.hbs:32 +msgid "From" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:18 +#: views/lists/segments/rule-configure.hbs:22 +#: views/lists/segments/rule-edit.hbs:22 +#: views/lists/segments/rule-edit.hbs:26 +msgid "days" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:19 +#: views/lists/segments/rule-configure.hbs:23 +#: views/lists/segments/rule-edit.hbs:23 +#: views/lists/segments/rule-edit.hbs:27 +msgid "before today" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:20 +#: views/lists/segments/rule-configure.hbs:24 +#: views/lists/segments/rule-edit.hbs:24 +#: views/lists/segments/rule-edit.hbs:28 +msgid "after today" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:21 +#: views/lists/segments/rule-configure.hbs:29 +#: views/lists/segments/rule-edit.hbs:14 +#: views/lists/segments/rule-edit.hbs:19 +#: views/lists/segments/rule-edit.hbs:25 +#: views/lists/segments/rule-edit.hbs:33 +msgid "to" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:31 +#: views/lists/segments/rule-edit.hbs:35 +#: lib/models/segments.js:156 +#: lib/models/segments.js:418 +msgid "Selected" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:32 +#: views/lists/segments/rule-edit.hbs:36 +#: lib/models/segments.js:156 +#: lib/models/segments.js:418 +msgid "Not selected" +msgstr "" + +#: views/lists/segments/rule-configure.hbs:33 +msgid "Add Rule" +msgstr "" + +#: views/lists/segments/rule-create.hbs:8 +#: views/lists/subscription/import.hbs:12 +#: views/triggers/create-select.hbs:9 +msgid "Next" +msgstr "" + +#: views/lists/segments/rule-edit.hbs:37 +msgid "Delete Rule" +msgstr "" + +#: views/lists/segments/segments.hbs:7 +msgid "Match" +msgstr "" + +#: views/lists/segments/view.hbs:5 +#: views/lists/view.hbs:12 +msgid "Segment" +msgstr "" + +#: views/lists/segments/view.hbs:7 +msgid "Match rules" +msgstr "" + +#: views/lists/segments/view.hbs:8 +msgid "Matching subscribers" +msgstr "" + +#: views/lists/segments/view.hbs:9 +msgid "show" +msgstr "" + +#: views/lists/subscription/add.hbs:3 +#: views/lists/subscription/add.hbs:4 +msgid "Add subscriber" +msgstr "" + +#: views/lists/subscription/add.hbs:5 +#: views/subscription/manage.hbs:2 +#: views/subscription/subscribe.hbs:3 +#: views/users/account.hbs:7 +msgid "Email Address" +msgstr "" + +#: views/lists/subscription/add.hbs:8 +#: views/lists/subscription/edit.hbs:9 +#: views/settings.hbs:82 +#: views/settings.hbs:97 +#: views/subscription/manage.hbs:7 +#: views/subscription/subscribe.hbs:7 +msgid "Begins with" +msgstr "" + +#: views/lists/subscription/add.hbs:9 +#: views/lists/subscription/edit.hbs:10 +msgid "" +"Insert a GPG public key that will be used to encrypt messages sent this " +"subscriber" +msgstr "" + +#: views/lists/subscription/add.hbs:11 +#: views/lists/subscription/edit.hbs:12 +#: views/lists/subscription/import-preview.hbs:9 +msgid "Timezone" +msgstr "" + +#: views/lists/subscription/add.hbs:13 +#: views/lists/subscription/edit.hbs:13 +msgid "Test user?" +msgstr "" + +#: views/lists/subscription/add.hbs:14 +#: views/lists/subscription/edit.hbs:14 +msgid "" +"If checked then this subscription can be used for previewing campaign " +"messages" +msgstr "" + +#: views/lists/subscription/add.hbs:15 +msgid "" +"This person will not receive a confirmation email so make sure that you " +"have permission to email them." +msgstr "" + +#: views/lists/subscription/edit.hbs:3 +#: views/lists/subscription/edit.hbs:4 +msgid "Edit subscriber" +msgstr "" + +#: views/lists/subscription/edit.hbs:5 +#: views/lists/subscription/import-failed.hbs:5 +msgid "Back to list" +msgstr "" + +#: views/lists/subscription/edit.hbs:6 +#: views/lists/subscription/import-preview.hbs:6 +#: views/subscription/unsubscribe.hbs:3 +#: lib/helpers.js:26 +#: lib/models/segments.js:11 +msgid "Email address" +msgstr "" + +#: views/lists/subscription/edit.hbs:16 +msgid "Delete Subscription" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:3 +msgid "Import status" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:4 +msgid "Failed addresses" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:6 +msgid "see here" +msgstr "" + +#: views/lists/subscription/import-failed.hbs:10 +msgid "Fail reason" +msgstr "" + +#: views/lists/subscription/import-preview.hbs:3 +#: views/lists/subscription/import-preview.hbs:4 +#: views/lists/subscription/import.hbs:3 +#: views/lists/subscription/import.hbs:4 +msgid "Import subscribers" +msgstr "" + +#: views/lists/subscription/import-preview.hbs:10 +#: views/users/api.hbs:27 +#: views/users/api.hbs:35 +#: views/users/api.hbs:43 +#: views/users/api.hbs:55 +msgid "Example" +msgstr "" + +#: views/lists/subscription/import-preview.hbs:11 +msgid "Start import" +msgstr "" + +#: views/lists/subscription/import.hbs:5 +msgid "CSV File" +msgstr "" + +#: views/lists/subscription/import.hbs:6 +msgid "CSV delimiter" +msgstr "" + +#: views/lists/subscription/import.hbs:7 +msgid "Categorize the imported subscribers as" +msgstr "" + +#: views/lists/subscription/import.hbs:8 +#: routes/lists.js:171 +msgid "Subscribed" +msgstr "" + +#: views/lists/subscription/import.hbs:9 +msgid "Regular subscriber addresses" +msgstr "" + +#: views/lists/subscription/import.hbs:11 +msgid "Suppressed emails that will be unsubscribed from your list" +msgstr "" + +#: views/lists/view.hbs:3 +msgid "Subscription Form" +msgstr "" + +#: views/lists/view.hbs:4 +msgid "List Actions" +msgstr "" + +#: views/lists/view.hbs:8 +#: views/triggers/create-select.hbs:3 +#: views/triggers/create-select.hbs:4 +#: views/triggers/create.hbs:3 +#: views/triggers/create.hbs:4 +#: views/triggers/create.hbs:27 +#: views/triggers/triggers.hbs:3 +msgid "Create Trigger" +msgstr "" + +#: views/lists/view.hbs:9 +msgid "Add Subscriber" +msgstr "" + +#: views/lists/view.hbs:10 +msgid "Import Subscribers" +msgstr "" + +#: views/lists/view.hbs:14 +msgid "Create New Segment" +msgstr "" + +#: views/lists/view.hbs:15 +msgid "Filter" +msgstr "" + +#: views/lists/view.hbs:16 +msgid "Subscriptions" +msgstr "" + +#: views/lists/view.hbs:17 +msgid "Imports" +msgstr "" + +#: views/lists/view.hbs:24 +#: routes/campaigns.js:266 +#: routes/lists.js:265 +msgid "Finished" +msgstr "" + +#: views/lists/view.hbs:26 +msgid "Added" +msgstr "" + +#: views/lists/view.hbs:27 +msgid "Updated" +msgstr "" + +#: views/lists/view.hbs:28 +msgid "Failed" +msgstr "" + +#: views/lists/view.hbs:30 +msgid "Are you sure? This action should only be called to resolve stalled imports" +msgstr "" + +#: views/lists/view.hbs:31 +msgid "Restart" +msgstr "" + +#: views/partials/codeeditor.hbs:1 +#: views/partials/summernote.hbs:1 +msgid "Template content (HTML)" +msgstr "" + +#: views/partials/html-preview.hbs:1 +msgid "Toggle HTML preview" +msgstr "" + +#: views/partials/merge-tag-reference.hbs:1 +msgid "Merge tag reference" +msgstr "" + +#: views/partials/merge-tag-reference.hbs:2 +msgid "" +"Merge tags are tags that are replaced before sending out the message. The " +"format of the merge tag is the following: [TAG_NAME] or " +"[TAG_NAME/fallback] where fallback is an optional " +"text value used when TAG_NAME is empty." +msgstr "" + +#: views/partials/plaintext.hbs:1 +msgid "Template content (plaintext)" +msgstr "" + +#: views/settings.hbs:5 +msgid "Service Address (URL)" +msgstr "" + +#: views/settings.hbs:6 +msgid "Enter the URL this service can be reached from" +msgstr "" + +#: views/settings.hbs:7 +msgid "Admin Email" +msgstr "" + +#: views/settings.hbs:8 +msgid "Enter the email address that will be used as \"from\" for system messages" +msgstr "" + +#: views/settings.hbs:9 +msgid "Disable WYSIWYG editor" +msgstr "" + +#: views/settings.hbs:10 +msgid "If checked then message editor displays HTML code without the preview" +msgstr "" + +#: views/settings.hbs:11 +msgid "Disable subscription confirmation messages" +msgstr "" + +#: views/settings.hbs:12 +msgid "" +"If checked then do not send a confirmation message that states the " +"subscriber is now subscribed or unsubscribed. This does not disable double " +"opt-in messages." +msgstr "" + +#: views/settings.hbs:13 +msgid "Tracking ID" +msgstr "" + +#: views/settings.hbs:14 +msgid "Enter Google Analytics tracking code" +msgstr "" + +#: views/settings.hbs:15 +msgid "Frontpage shout out" +msgstr "" + +#: views/settings.hbs:16 +msgid "HTML code shown in the front page header section" +msgstr "" + +#: views/settings.hbs:17 +msgid "Campaign defaults" +msgstr "" + +#: views/settings.hbs:18 +msgid "Sender name" +msgstr "" + +#: views/settings.hbs:19 +msgid "Sender name, eg. My Awesome Company Ltd." +msgstr "" + +#: views/settings.hbs:20 +msgid "Default address" +msgstr "" + +#: views/settings.hbs:21 +msgid "Contact address to provide, eg. 1234 Main Street, Anywhere, MA 01234, USA" +msgstr "" + +#: views/settings.hbs:22 +msgid "Default \"from name\"" +msgstr "" + +#: views/settings.hbs:24 +msgid "Default \"from\" email" +msgstr "" + +#: views/settings.hbs:26 +msgid "Default \"subject line\"" +msgstr "" + +#: views/settings.hbs:28 +msgid "Default homepage (URL)" +msgstr "" + +#: views/settings.hbs:29 +msgid "URL to redirect the subscribed users to, eg. http://example.com/" +msgstr "" + +#: views/settings.hbs:30 +msgid "Mailer Settings" +msgstr "" + +#: views/settings.hbs:31 +msgid "These settings are required to send out e-mail messages" +msgstr "" + +#: views/settings.hbs:32 +msgid "SMTP" +msgstr "" + +#: views/settings.hbs:33 +msgid "AWS SES" +msgstr "" + +#: views/settings.hbs:34 +msgid "Use SMTP for sending mail" +msgstr "" + +#: views/settings.hbs:35 +msgid "Hostname" +msgstr "" + +#: views/settings.hbs:36 +msgid "Port" +msgstr "" + +#: views/settings.hbs:37 +msgid "Port, eg. 465. Autodetected if left blank" +msgstr "" + +#: views/settings.hbs:38 +msgid "Encryption" +msgstr "" + +#: views/settings.hbs:39 +msgid "Disable SMTP authentication" +msgstr "" + +#: views/settings.hbs:40 +#: views/users/forgot.hbs:9 +#: views/users/login.hbs:4 +#: views/users/login.hbs:5 +msgid "Username" +msgstr "" + +#: views/settings.hbs:41 +msgid "Username, eg. myaccount@example.com" +msgstr "" + +#: views/settings.hbs:42 +#: views/settings.hbs:43 +#: views/users/login.hbs:6 +#: views/users/login.hbs:7 +msgid "Password" +msgstr "" + +#: views/settings.hbs:44 +msgid "Use SES API for sending mail" +msgstr "" + +#: views/settings.hbs:45 +msgid "Access Key" +msgstr "" + +#: views/settings.hbs:46 +msgid "AWS Access Key Id" +msgstr "" + +#: views/settings.hbs:47 +msgid "Secret Key" +msgstr "" + +#: views/settings.hbs:48 +msgid "AWS Secret Access Key" +msgstr "" + +#: views/settings.hbs:49 +msgid "Region" +msgstr "" + +#: views/settings.hbs:50 +msgid "Checking" +msgstr "" + +#: views/settings.hbs:51 +msgid "Check Mailer config" +msgstr "" + +#: views/settings.hbs:52 +msgid "Don't have an SMTP account yet? Create a free SendPulse account" +msgstr "" + +#: views/settings.hbs:53 +msgid "here" +msgstr "" + +#: views/settings.hbs:54 +msgid "Advanced Mailer settings" +msgstr "" + +#: views/settings.hbs:55 +msgid "Log SMTP transactions" +msgstr "" + +#: views/settings.hbs:56 +msgid "Allow self-signed certificates" +msgstr "" + +#: views/settings.hbs:57 +msgid "Max connections" +msgstr "" + +#: views/settings.hbs:58 +msgid "The count of max connections, eg. 10" +msgstr "" + +#: views/settings.hbs:59 +msgid "" +"The count of maximum simultaneous connections to make against the SMTP " +"server (defaults to 5). This limit is per sending process." +msgstr "" + +#: views/settings.hbs:60 +msgid "Max messages" +msgstr "" + +#: views/settings.hbs:61 +msgid "The count of max messages, eg. 100" +msgstr "" + +#: views/settings.hbs:62 +msgid "" +"he number of messages to send through a single connection before the " +"connection is closed and reopened (defaults to 100)" +msgstr "" + +#: views/settings.hbs:63 +msgid "Throttling" +msgstr "" + +#: views/settings.hbs:64 +msgid "Messages per hour eg. 1000" +msgstr "" + +#: views/settings.hbs:65 +msgid "" +"Maximum number of messages to send in an hour. Leave empty or zero for no " +"throttling. If your provider uses a different speed limit (messages/minute " +"or messages/second) then convert this limit into messages/hour (1m/s => " +"3600m/h). This limit is per sending process." +msgstr "" + +#: views/settings.hbs:66 +msgid "VERP bounce handling" +msgstr "" + +#: views/settings.hbs:67 +msgid "" +"Mailtrain is able to use VERP based routing to detect bounces. In this case " +"the message is sent to the recipient using a custom VERP address as the " +"return path of the message. If the message is not accepted a bounce email " +"is sent to this special VERP address and thus a bounce is detected." +msgstr "" + +#: views/settings.hbs:68 +msgid "" +"To get VERP working you need to set up a DNS MX record that points to your " +"Mailtrain hostname. You must also ensure that Mailtrain VERP interface is " +"available from port 25 of your server (port 25 usually requires root user " +"privileges). This way if anyone tries to send email to " +"someuser@verp-hostname then the email should end up to this server." +msgstr "" + +#: views/settings.hbs:69 +msgid "" +"VERP usually only works if you are using your own SMTP server. Regular " +"relay services (SES, SparkPost, Gmail etc.) tend to remove the VERP address " +"from the message." +msgstr "" + +#: views/settings.hbs:70 +msgid "Use VERP to catch bounces" +msgstr "" + +#: views/settings.hbs:71 +msgid "Server hostname" +msgstr "" + +#: views/settings.hbs:72 +msgid "The VERP server hostname, eg. bounces.example.com" +msgstr "" + +#: views/settings.hbs:73 +msgid "" +"VERP bounce handling server hostname. This hostname is used in the SMTP " +"envelope FROM address and the MX DNS records should point to this server" +msgstr "" + +#: views/settings.hbs:74 +msgid "" +"VERP bounce handling server is not enabled. Modify your server " +"configuration file and restart server to enable it" +msgstr "" + +#: views/settings.hbs:75 +msgid "GPG Signing" +msgstr "" + +#: views/settings.hbs:76 +msgid "" +"Only messages that are encrypted can be signed. Subsribers who have not set " +"up a GPG public key in their profile receive normal email messages. Users " +"with GPG key set receive encrypted messages and if you have signing key " +"also set, the messages are signed with this key." +msgstr "" + +#: views/settings.hbs:77 +msgid "" +"Do not use sensitive keys here. The private key and passphrase are not " +"encrypted in the database." +msgstr "" + +#: views/settings.hbs:78 +msgid "Private Key Passphrase" +msgstr "" + +#: views/settings.hbs:79 +msgid "Passphrase for the key if set" +msgstr "" + +#: views/settings.hbs:80 +msgid "Only fill this if your private key is encrypted with a passphrase" +msgstr "" + +#: views/settings.hbs:81 +msgid "GPG Private Key" +msgstr "" + +#: views/settings.hbs:83 +msgid "" +"This value is optional. If you do not provide a private key GPG encrypted " +"messages are sent without signing." +msgstr "" + +#: views/settings.hbs:84 +msgid "DKIM Signing by ZoneMTA" +msgstr "" + +#: views/settings.hbs:85 +msgid "" +"If you are using ZoneMTA then Mailtrain can provide a DKIM key for signing " +"all outgoing messages. Other services usually provide their own means to " +"DKIM sign your messages" +msgstr "" + +#: views/settings.hbs:86 +msgid "" +"Do not use sensitive keys here. The private key is not encrypted in the " +"database." +msgstr "" + +#: views/settings.hbs:87 +msgid "ZoneMTA DKIM API Key" +msgstr "" + +#: views/settings.hbs:88 +msgid "Some secret value" +msgstr "" + +#: views/settings.hbs:89 +msgid "" +"Secret value known to ZoneMTA for requesting DKIM key information. If this " +"value was generated by the Mailtrain installation script then you can keep " +"it as it is" +msgstr "" + +#: views/settings.hbs:90 +msgid "DKIM domain" +msgstr "" + +#: views/settings.hbs:91 +msgid "Domain name for the DKIM key" +msgstr "" + +#: views/settings.hbs:92 +msgid "Leave blank to use the sender email address domain" +msgstr "" + +#: views/settings.hbs:93 +#: views/settings.hbs:94 +msgid "DKIM key selector" +msgstr "" + +#: views/settings.hbs:95 +msgid "Signing is disabled without a valid selector value" +msgstr "" + +#: views/settings.hbs:96 +msgid "DKIM Private Key" +msgstr "" + +#: views/settings.hbs:98 +msgid "" +"This value is optional. If you do not provide a private key then messages " +"are not signed." +msgstr "" + +#: views/subscription/confirm-notice.hbs:1 +#: views/subscription/subscribe.hbs:1 +msgid "Warning!" +msgstr "" + +#: views/subscription/confirm-notice.hbs:2 +msgid "If JavaScript was not enabled then no confirmation message was sent" +msgstr "" + +#: views/subscription/confirm-notice.hbs:3 +msgid "Almost finished." +msgstr "" + +#: views/subscription/confirm-notice.hbs:4 +msgid "" +"We need to confirm your email address. To complete the subscription " +"process, please click the link in the email we just sent you." +msgstr "" + +#: views/subscription/confirm-notice.hbs:5 +#: views/subscription/unsubscribe-notice.hbs:3 +#: views/subscription/updated-notice.hbs:3 +msgid "return to our website" +msgstr "" + +#: views/subscription/manage-address.hbs:1 +msgid "Update your Email Address" +msgstr "" + +#: views/subscription/manage-address.hbs:2 +msgid "Existing Email Address" +msgstr "" + +#: views/subscription/manage-address.hbs:3 +msgid "New Email Address" +msgstr "" + +#: views/subscription/manage-address.hbs:4 +msgid "Your new email address" +msgstr "" + +#: views/subscription/manage-address.hbs:5 +msgid "" +"You will receive a confirmation request to your new email address that you " +"need to accept before your email is actually changed" +msgstr "" + +#: views/subscription/manage-address.hbs:6 +msgid "Update Email Address" +msgstr "" + +#: views/subscription/manage.hbs:1 +msgid "Update your preferences" +msgstr "" + +#: views/subscription/manage.hbs:3 +msgid "want to change it?" +msgstr "" + +#: views/subscription/manage.hbs:6 +#: views/subscription/subscribe.hbs:6 +msgid "Download signature verification key" +msgstr "" + +#: views/subscription/manage.hbs:8 +#: views/subscription/subscribe.hbs:8 +msgid "Insert your GPG public key here to encrypt messages sent to your address" +msgstr "" + +#: views/subscription/manage.hbs:9 +#: views/subscription/subscribe.hbs:9 +msgid "optional" +msgstr "" + +#: views/subscription/manage.hbs:11 +msgid "Update Profile" +msgstr "" + +#: views/subscription/subscribe.hbs:2 +msgid "JavaScript must be enabled in order for the subscription form to work" +msgstr "" + +#: views/subscription/subscribe.hbs:11 +msgid "Subscribe to list" +msgstr "" + +#: views/subscription/subscribed.hbs:3 +msgid "Thank you for subscribing!" +msgstr "" + +#: views/subscription/subscribed.hbs:4 +msgid "continue to our website" +msgstr "" + +#: views/subscription/unsubscribe-notice.hbs:1 +msgid "Unsubscribe Successful" +msgstr "" + +#: views/subscription/unsubscribe-notice.hbs:2 +msgid "You have been removed from:" +msgstr "" + +#: views/subscription/unsubscribe.hbs:2 +msgid "Enter your email address to unsubscribe from:" +msgstr "" + +#: views/subscription/updated-notice.hbs:1 +msgid "Profile Updated" +msgstr "" + +#: views/subscription/updated-notice.hbs:2 +msgid "Your profile information has been updated." +msgstr "" + +#: views/templates/create.hbs:2 +#: views/templates/edit.hbs:2 +#: views/templates/templates.hbs:2 +#: views/templates/templates.hbs:4 +#: lib/tools.js:115 +msgid "Templates" +msgstr "" + +#: views/templates/create.hbs:3 +#: views/templates/create.hbs:4 +#: views/templates/create.hbs:12 +#: views/templates/templates.hbs:3 +msgid "Create Template" +msgstr "" + +#: views/templates/create.hbs:5 +#: views/templates/edit.hbs:6 +msgid "Template name" +msgstr "" + +#: views/templates/create.hbs:6 +#: views/templates/edit.hbs:7 +msgid "Name for this template, eg. Newsletter" +msgstr "" + +#: views/templates/create.hbs:7 +msgid "HTML Editor" +msgstr "" + +#: views/templates/create.hbs:10 +#: views/templates/edit.hbs:9 +msgid "Optional comments about this template" +msgstr "" + +#: views/templates/edit.hbs:3 +#: views/templates/edit.hbs:4 +msgid "Edit Template" +msgstr "" + +#: views/templates/edit.hbs:5 +msgid "Back to templates" +msgstr "" + +#: views/templates/edit.hbs:11 +msgid "Delete Template" +msgstr "" + +#: views/triggers/create-select.hbs:2 +#: views/triggers/create.hbs:2 +#: views/triggers/edit.hbs:2 +#: views/triggers/triggered.hbs:2 +#: views/triggers/triggers.hbs:2 +#: views/triggers/triggers.hbs:4 +msgid "Automation Triggers" +msgstr "" + +#: views/triggers/create-select.hbs:5 +msgid "Select a list for the trigger" +msgstr "" + +#: views/triggers/create.hbs:5 +#: views/triggers/edit.hbs:6 +msgid "Trigger name" +msgstr "" + +#: views/triggers/create.hbs:6 +#: views/triggers/edit.hbs:7 +msgid "Name for this trigger, eg. Inactive subscribers" +msgstr "" + +#: views/triggers/create.hbs:8 +#: views/triggers/edit.hbs:9 +msgid "Optional comments about this trigger" +msgstr "" + +#: views/triggers/create.hbs:12 +#: views/triggers/edit.hbs:14 +msgid "Trigger rule" +msgstr "" + +#: views/triggers/create.hbs:13 +#: views/triggers/edit.hbs:15 +msgid "Trigger fires" +msgstr "" + +#: views/triggers/create.hbs:14 +#: views/triggers/edit.hbs:16 +msgid "days after:" +msgstr "" + +#: views/triggers/create.hbs:15 +#: views/triggers/edit.hbs:17 +msgid "Subscription" +msgstr "" + +#: views/triggers/create.hbs:16 +#: views/triggers/create.hbs:21 +#: views/triggers/edit.hbs:18 +#: views/triggers/edit.hbs:23 +msgid "Event" +msgstr "" + +#: views/triggers/create.hbs:18 +#: views/triggers/create.hbs:19 +#: views/triggers/create.hbs:25 +#: views/triggers/edit.hbs:20 +#: views/triggers/edit.hbs:21 +#: views/triggers/edit.hbs:27 +msgid "Campaign" +msgstr "" + +#: views/triggers/create.hbs:23 +#: views/triggers/edit.hbs:25 +msgid "Trigger action" +msgstr "" + +#: views/triggers/create.hbs:24 +#: views/triggers/edit.hbs:26 +msgid "Send campaign" +msgstr "" + +#: views/triggers/edit.hbs:3 +#: views/triggers/edit.hbs:4 +msgid "Edit Trigger" +msgstr "" + +#: views/triggers/edit.hbs:5 +msgid "Back to triggers" +msgstr "" + +#: views/triggers/edit.hbs:11 +msgid "Trigger is enabled" +msgstr "" + +#: views/triggers/edit.hbs:29 +msgid "Delete Trigger" +msgstr "" + +#: views/triggers/triggered.hbs:3 +msgid "Triggered" +msgstr "" + +#: views/triggers/triggered.hbs:4 +msgid "Triggered subscribers" +msgstr "" + +#: views/triggers/triggered.hbs:5 +msgid "Subscribers who caused this trigger to fire" +msgstr "" + +#: views/triggers/triggered.hbs:9 +msgid "Triggered time" +msgstr "" + +#: views/triggers/triggers.hbs:9 +msgid "Trigger" +msgstr "" + +#: views/triggers/triggers.hbs:10 +msgid "Target Campaign" +msgstr "" + +#: views/triggers/triggers.hbs:11 +msgid "Triggered count" +msgstr "" + +#: views/triggers/triggers.hbs:12 +msgid "Enabled" +msgstr "" + +#: views/triggers/triggers.hbs:13 +msgid "Disabled" +msgstr "" + +#: views/users/account.hbs:4 +msgid "This account is managed through LDAP." +msgstr "" + +#: views/users/account.hbs:5 +msgid "Associated Email Address" +msgstr "" + +#: views/users/account.hbs:8 +msgid "Your e-mail address" +msgstr "" + +#: views/users/account.hbs:9 +msgid "This address is used for account recovery in case you loose your password" +msgstr "" + +#: views/users/account.hbs:10 +msgid "Password change" +msgstr "" + +#: views/users/account.hbs:11 +msgid "" +"You only need to fill out this form if you want to change your current " +"password" +msgstr "" + +#: views/users/account.hbs:12 +#: views/users/account.hbs:13 +msgid "Current Password" +msgstr "" + +#: views/users/account.hbs:14 +#: views/users/account.hbs:15 +#: views/users/reset.hbs:6 +#: views/users/reset.hbs:7 +msgid "New Password" +msgstr "" + +#: views/users/account.hbs:16 +msgid "Confirm Password" +msgstr "" + +#: views/users/account.hbs:17 +#: views/users/reset.hbs:8 +msgid "Confirm New Password" +msgstr "" + +#: views/users/api.hbs:4 +msgid "Are you sure? Resetting would invalidate the currently existing token." +msgstr "" + +#: views/users/api.hbs:5 +msgid "Are you sure?" +msgstr "" + +#: views/users/api.hbs:6 +msgid "Reset Access Token" +msgstr "" + +#: views/users/api.hbs:7 +msgid "Generate Access Token" +msgstr "" + +#: views/users/api.hbs:8 +msgid "Personal access token:" +msgstr "" + +#: views/users/api.hbs:9 +msgid "Access token not yet generated" +msgstr "" + +#: views/users/api.hbs:10 +msgid "Notes about the API" +msgstr "" + +#: views/users/api.hbs:11 +msgid "" +"API response is a JSON structure with error and " +"data properties. If the response error has a " +"value set then the request failed." +msgstr "" + +#: views/users/api.hbs:12 +msgid "" +"You need to define proper Content-Type when making a request. " +"You can either use application/x-www-form-urlencoded for " +"normal form data or application/json for a JSON payload. Using " +"multipart/form-data is not supported." +msgstr "" + +#: views/users/api.hbs:13 +msgid "Add subscription" +msgstr "" + +#: views/users/api.hbs:14 +msgid "" +"This API call either inserts a new subscription or updates existing. Fields " +"not included are left as is, so if you update only LAST_NAME value, then " +"FIRST_NAME is kept untouched for an existing subscription." +msgstr "" + +#: views/users/api.hbs:15 +#: views/users/api.hbs:17 +#: views/users/api.hbs:30 +#: views/users/api.hbs:32 +#: views/users/api.hbs:38 +#: views/users/api.hbs:40 +#: views/users/api.hbs:46 +#: views/users/api.hbs:48 +msgid "arguments" +msgstr "" + +#: views/users/api.hbs:16 +#: views/users/api.hbs:31 +#: views/users/api.hbs:39 +#: views/users/api.hbs:47 +msgid "your personal access token" +msgstr "" + +#: views/users/api.hbs:18 +#: views/users/api.hbs:33 +#: views/users/api.hbs:41 +msgid "subscriber's email address" +msgstr "" + +#: views/users/api.hbs:19 +#: views/users/api.hbs:34 +#: views/users/api.hbs:42 +#: views/users/api.hbs:50 +msgid "required" +msgstr "" + +#: views/users/api.hbs:20 +msgid "subscriber's first name" +msgstr "" + +#: views/users/api.hbs:21 +msgid "subscriber's last name" +msgstr "" + +#: views/users/api.hbs:22 +msgid "" +"subscriber's timezone (eg. \"Europe/Tallinn\", \"PST\" or \"UTC\"). If not " +"set defaults to \"UTC\"" +msgstr "" + +#: views/users/api.hbs:23 +msgid "" +"custom field value. Use yes/no for option group values (checkboxes, radios, " +"drop downs)" +msgstr "" + +#: views/users/api.hbs:24 +msgid "Additional POST arguments" +msgstr "" + +#: views/users/api.hbs:25 +msgid "" +"set to \"yes\" if you want to make sure the email is marked as subscribed " +"even if it was previously marked as unsubscribed. If the email was already " +"unsubscribed/blocked then subscription status is not changed" +msgstr "" + +#: views/users/api.hbs:26 +msgid "" +"set to \"yes\" if you want to send confirmation email to the subscriber " +"before actually marking as subscribed" +msgstr "" + +#: views/users/api.hbs:28 +msgid "Remove subscription" +msgstr "" + +#: views/users/api.hbs:29 +msgid "This API call marks a subscription as unsubscribed" +msgstr "" + +#: views/users/api.hbs:36 +msgid "Delete subscription" +msgstr "" + +#: views/users/api.hbs:37 +msgid "This API call deletes a subscription" +msgstr "" + +#: views/users/api.hbs:44 +msgid "Add new custom field" +msgstr "" + +#: views/users/api.hbs:45 +msgid "This API call creates a new custom field for a list." +msgstr "" + +#: views/users/api.hbs:49 +msgid "field name" +msgstr "" + +#: views/users/api.hbs:51 +msgid "one of the following types:" +msgstr "" + +#: views/users/api.hbs:52 +msgid "If the type is 'option' then you also need to specify the parent element ID" +msgstr "" + +#: views/users/api.hbs:53 +msgid "" +"Template for the group element. If not set, then values of the elements are " +"joined with commas" +msgstr "" + +#: views/users/api.hbs:54 +msgid "" +"if not visible then the subscriber can not view or modify this value at the " +"profile page" +msgstr "" + +#: views/users/forgot.hbs:3 +#: views/users/reset.hbs:3 +msgid "Password Reset" +msgstr "" + +#: views/users/forgot.hbs:4 +msgid "Reset your password?" +msgstr "" + +#: views/users/forgot.hbs:5 +msgid "Accounts are managed through LDAP." +msgstr "" + +#: views/users/forgot.hbs:6 +#: views/users/reset.hbs:10 +msgid "Reset Password" +msgstr "" + +#: views/users/forgot.hbs:7 +msgid "" +"Please provide the username or email address that you used when you signed " +"up for your Mailtrain account." +msgstr "" + +#: views/users/forgot.hbs:8 +msgid "We will send you an email that will allow you to reset your password." +msgstr "" + +#: views/users/forgot.hbs:10 +msgid "Username or email address" +msgstr "" + +#: views/users/forgot.hbs:11 +msgid "Send verification email" +msgstr "" + +#: views/users/login.hbs:8 +msgid "Remember me" +msgstr "" + +#: views/users/login.hbs:11 +#: views/users/login.hbs:12 +msgid "Forgot password?" +msgstr "" + +#: views/users/reset.hbs:4 +msgid "Choose your new password" +msgstr "" + +#: views/users/reset.hbs:5 +msgid "Please enter a new password." +msgstr "" + +#: lib/feed.js:31 +msgid "Bad status code %s" +msgstr "" + +#: lib/helpers.js:17 +msgid "URL that points to the unsubscribe page" +msgstr "" + +#: lib/helpers.js:20 +msgid "URL that points to the preferences page of the subscriber" +msgstr "" + +#: lib/helpers.js:23 +msgid "URL to preview the message in a browser" +msgstr "" + +#: lib/helpers.js:29 +#: lib/models/segments.js:31 +msgid "First name" +msgstr "" + +#: lib/helpers.js:32 +#: lib/models/segments.js:35 +msgid "Last name" +msgstr "" + +#: lib/helpers.js:35 +msgid "Full name (first and last name combined)" +msgstr "" + +#: lib/helpers.js:38 +msgid "Unique ID that identifies the recipient" +msgstr "" + +#: lib/helpers.js:41 +msgid "Unique ID that identifies the list used for this campaign" +msgstr "" + +#: lib/helpers.js:44 +msgid "Unique ID that identifies current campaign" +msgstr "" + +#: lib/mailer.js:215 +msgid "Invalid mail transport" +msgstr "" + +#: lib/models/campaigns.js:271 +#: lib/models/campaigns.js:298 +#: lib/models/campaigns.js:371 +#: lib/models/campaigns.js:494 +#: lib/models/campaigns.js:752 +#: lib/models/campaigns.js:881 +msgid "Missing Campaign ID" +msgstr "" + +#: lib/models/campaigns.js:407 +msgid "Emtpy or too large attahcment" +msgstr "" + +#: lib/models/campaigns.js:573 +#: lib/models/campaigns.js:761 +msgid "Campaign Name must be set" +msgstr "" + +#: lib/models/campaigns.js:577 +msgid "RSS URL must be set and needs to be a valid URL" +msgstr "" + +#: lib/models/campaigns.js:730 +msgid "Selected template not found" +msgstr "" + +#: lib/models/campaigns.js:1082 +msgid "Invalid or missing message ID" +msgstr "" + +#: lib/models/fields.js:24 +msgid "Drop Down" +msgstr "" + +#: lib/models/fields.js:25 +msgid "Date (MM/DD/YYY)" +msgstr "" + +#: lib/models/fields.js:29 +msgid "JSON value for custom rendering" +msgstr "" + +#: lib/models/fields.js:30 +msgid "Option" +msgstr "" + +#: lib/models/fields.js:53 +#: lib/models/fields.js:98 +#: lib/models/fields.js:123 +#: lib/models/lists.js:81 +#: lib/models/lists.js:175 +#: lib/models/lists.js:212 +#: lib/models/segments.js:43 +#: lib/models/segments.js:176 +#: lib/models/subscriptions.js:88 +#: lib/models/subscriptions.js:640 +#: lib/models/subscriptions.js:703 +#: lib/models/subscriptions.js:889 +#: lib/models/subscriptions.js:992 +#: lib/models/subscriptions.js:1046 +#: lib/models/subscriptions.js:1109 +#: lib/models/subscriptions.js:1152 +msgid "Missing List ID" +msgstr "" + +#: lib/models/fields.js:129 +msgid "Option field requires a group to be selected" +msgstr "" + +#: lib/models/fields.js:149 +#: lib/models/fields.js:199 +msgid "Missing Field ID" +msgstr "" + +#: lib/models/fields.js:153 +#: lib/models/segments.js:185 +#: lib/models/segments.js:225 +msgid "Field Name must be set" +msgstr "" + +#: lib/models/fields.js:216 +msgid "Custom field not found" +msgstr "" + +#: lib/models/fields.js:289 +msgid "Unknown column type %s" +msgstr "" + +#: lib/models/fields.js:293 +msgid "Missing column name" +msgstr "" + +#: lib/models/fields.js:297 +msgid "Missing list ID" +msgstr "" + +#: lib/models/fields.js:305 +msgid "Provided List ID not found" +msgstr "" + +#: lib/models/links.js:328 +#: routes/campaigns.js:541 +#: routes/campaigns.js:590 +#: services/sender.js:304 +msgid "Campaign not found" +msgstr "" + +#: lib/models/links.js:336 +#: routes/lists.js:146 +#: services/sender.js:311 +msgid "List not found" +msgstr "" + +#: lib/models/links.js:344 +msgid "Subscription not found" +msgstr "" + +#: lib/models/lists.js:117 +#: lib/models/lists.js:179 +msgid "List Name must be set" +msgstr "" + +#: lib/models/lists.js:241 +msgid "Missing List CID" +msgstr "" + +#: lib/models/segments.js:15 +msgid "Signup country" +msgstr "" + +#: lib/models/segments.js:19 +#: lib/models/triggers.js:11 +msgid "Sign up date" +msgstr "" + +#: lib/models/segments.js:23 +#: lib/models/triggers.js:15 +msgid "Latest open" +msgstr "" + +#: lib/models/segments.js:27 +#: lib/models/triggers.js:19 +msgid "Latest click" +msgstr "" + +#: lib/models/segments.js:69 +#: lib/models/segments.js:216 +#: lib/models/segments.js:256 +#: lib/models/segments.js:278 +msgid "Missing Segment ID" +msgstr "" + +#: lib/models/segments.js:85 +#: lib/models/segments.js:549 +#: lib/models/segments.js:658 +msgid "Segment not found" +msgstr "" + +#: lib/models/segments.js:146 +#: lib/models/segments.js:147 +#: lib/models/segments.js:408 +#: lib/models/segments.js:409 +msgid "%s days after today" +msgstr "" + +#: lib/models/segments.js:146 +#: lib/models/segments.js:147 +#: lib/models/segments.js:408 +#: lib/models/segments.js:409 +msgid "%s days before today" +msgstr "" + +#: lib/models/segments.js:148 +#: lib/models/segments.js:410 +msgid "today" +msgstr "" + +#: lib/models/segments.js:189 +#: lib/models/segments.js:229 +msgid "Invalid segment rule type" +msgstr "" + +#: lib/models/segments.js:289 +#: lib/models/segments.js:454 +#: routes/segments.js:266 +#: routes/segments.js:300 +#: routes/segments.js:370 +#: routes/segments.js:381 +msgid "Selected segment not found" +msgstr "" + +#: lib/models/segments.js:294 +#: lib/models/segments.js:459 +#: routes/segments.js:272 +#: routes/segments.js:306 +#: routes/segments.js:387 +msgid "Invalid rule type" +msgstr "" + +#: lib/models/segments.js:358 +#: lib/models/segments.js:434 +#: lib/models/segments.js:524 +msgid "Missing Rule ID" +msgstr "" + +#: lib/models/segments.js:374 +msgid "Specified rule not found" +msgstr "" + +#: lib/models/segments.js:385 +msgid "Specified segment not found" +msgstr "" + +#: lib/models/segments.js:445 +msgid "Selected rule not found" +msgstr "" + +#: lib/models/subscriptions.js:233 +msgid "%s: Please Confirm Subscription" +msgstr "" + +#: lib/models/subscriptions.js:324 +msgid "Could not save subscription" +msgstr "" + +#: lib/models/subscriptions.js:507 +#: lib/models/subscriptions.js:537 +msgid "Missing Subbscription ID" +msgstr "" + +#: lib/models/subscriptions.js:565 +msgid "Missing Subbscription email address" +msgstr "" + +#: lib/models/subscriptions.js:644 +#: lib/models/subscriptions.js:893 +#: lib/models/subscriptions.js:1156 +msgid "Missing subscription ID" +msgstr "" + +#: lib/models/subscriptions.js:707 +msgid "Missing email address" +msgstr "" + +#: lib/models/subscriptions.js:996 +#: lib/models/subscriptions.js:1050 +#: lib/models/subscriptions.js:1086 +msgid "Missing Import ID" +msgstr "" + +#: lib/models/subscriptions.js:1178 +msgid "Unknown subscription ID" +msgstr "" + +#: lib/models/subscriptions.js:1183 +msgid "Nothing seems to be changed" +msgstr "" + +#: lib/models/subscriptions.js:1197 +msgid "This address is already registered by someone else" +msgstr "" + +#: lib/models/templates.js:51 +#: lib/models/templates.js:122 +#: lib/models/templates.js:163 +msgid "Missing Template ID" +msgstr "" + +#: lib/models/templates.js:80 +#: lib/models/templates.js:126 +msgid "Template Name must be set" +msgstr "" + +#: lib/models/triggers.js:28 +msgid "Has Opened" +msgstr "" + +#: lib/models/triggers.js:31 +msgid "Has Clicked" +msgstr "" + +#: lib/models/triggers.js:34 +msgid "Not Opened" +msgstr "" + +#: lib/models/triggers.js:37 +msgid "Not Clicked" +msgstr "" + +#: lib/models/triggers.js:174 +#: lib/models/triggers.js:211 +msgid "Missing or invalid list ID" +msgstr "" + +#: lib/models/triggers.js:178 +#: lib/models/triggers.js:263 +msgid "Days in the past are not allowed" +msgstr "" + +#: lib/models/triggers.js:182 +#: lib/models/triggers.js:203 +#: lib/models/triggers.js:267 +#: lib/models/triggers.js:288 +msgid "Missing or invalid trigger rule" +msgstr "" + +#: lib/models/triggers.js:189 +#: lib/models/triggers.js:274 +msgid "Invalid subscription configuration" +msgstr "" + +#: lib/models/triggers.js:196 +#: lib/models/triggers.js:281 +msgid "Invalid campaign configuration" +msgstr "" + +#: lib/models/triggers.js:199 +#: lib/models/triggers.js:284 +msgid "A campaing can not be a target for itself" +msgstr "" + +#: lib/models/triggers.js:232 +msgid "Could not store trigger row" +msgstr "" + +#: lib/models/triggers.js:249 +msgid "Missing or invalid Trigger ID" +msgstr "" + +#: lib/models/triggers.js:316 +msgid "Missing Trigger ID" +msgstr "" + +#: lib/models/users.js:103 +msgid "Could not store user row" +msgstr "" + +#: lib/models/users.js:173 +msgid "Email Address must be set" +msgstr "" + +#: lib/models/users.js:184 +msgid "Failed to check user data" +msgstr "" + +#: lib/models/users.js:195 +msgid "" +"Can't change email as another user with the same email address already " +"exists" +msgstr "" + +#: lib/models/users.js:212 +msgid "Incorrect current password" +msgstr "" + +#: lib/models/users.js:216 +msgid "New password not set" +msgstr "" + +#: lib/models/users.js:220 +msgid "Passwords do not match" +msgstr "" + +#: lib/models/users.js:258 +msgid "User ID not set" +msgstr "" + +#: lib/models/users.js:286 +msgid "Username must be set" +msgstr "" + +#: lib/models/users.js:323 +msgid "Mailer password change request" +msgstr "" + +#: lib/models/users.js:347 +#: lib/models/users.js:367 +msgid "Missing username or reset token" +msgstr "" + +#: lib/models/users.js:371 +msgid "Invalid new password" +msgstr "" + +#: lib/passport.js:38 +msgid "%s logged out" +msgstr "" + +#: lib/passport.js:51 +msgid "Failed to authenticate user" +msgstr "" + +#: lib/passport.js:67 +msgid "Logged in as %s" +msgstr "" + +#: lib/passport.js:125 +msgid "Incorrect username or password" +msgstr "" + +#: lib/tools.js:123 +msgid "Automation" +msgstr "" + +#: lib/tools.js:133 +msgid "Blocked email address \"%s\"" +msgstr "" + +#: lib/tools.js:142 +msgid "Invalid email address \"%s\"." +msgstr "" + +#: lib/tools.js:145 +msgid "MX record not found for domain" +msgstr "" + +#: lib/tools.js:148 +msgid "Address domain not found" +msgstr "" + +#: lib/tools.js:151 +msgid "Address domain name is required" +msgstr "" + +#: routes/archive.js:31 +#: routes/archive.js:43 +#: routes/archive.js:55 +#: app.js:211 +msgid "Not Found" +msgstr "" + +#: routes/archive.js:110 +#: services/sender.js:447 +msgid "Received status code %s from %s" +msgstr "" + +#: routes/archive.js:134 +#: routes/campaigns.js:131 +#: routes/campaigns.js:295 +#: routes/campaigns.js:390 +#: routes/campaigns.js:435 +#: routes/campaigns.js:475 +#: routes/campaigns.js:739 +#: routes/campaigns.js:762 +#: routes/campaigns.js:781 +#: routes/campaigns.js:803 +#: routes/triggers.js:146 +msgid "Could not find campaign with specified ID" +msgstr "" + +#: routes/archive.js:142 +#: routes/campaigns.js:789 +msgid "Attachment not found" +msgstr "" + +#: routes/campaigns.js:26 +#: routes/fields.js:13 +#: routes/lists.js:49 +#: routes/segments.js:13 #: routes/settings.js:23 +#: routes/templates.js:17 +#: routes/triggers.js:18 +#: routes/users.js:75 +#: routes/users.js:120 msgid "Need to be logged in to access restricted content" msgstr "" +#: routes/campaigns.js:117 +msgid "Could not create campaign" +msgstr "" + +#: routes/campaigns.js:120 +msgid "Campaign “%s” created" +msgstr "" + +#: routes/campaigns.js:204 +msgid "content from an RSS entry" +msgstr "" + +#: routes/campaigns.js:220 +msgid "Campaign settings updated" +msgstr "" + +#: routes/campaigns.js:222 +msgid "Campaign settings not updated" +msgstr "" + +#: routes/campaigns.js:238 +#: routes/campaigns.js:639 +msgid "Campaign deleted" +msgstr "" + +#: routes/campaigns.js:240 +#: routes/campaigns.js:641 +msgid "Could not delete specified campaign" +msgstr "" + +#: routes/campaigns.js:259 +msgid "Idling" +msgstr "" + +#: routes/campaigns.js:262 +msgid "Scheduled" +msgstr "" + +#: routes/campaigns.js:268 +msgid "Paused" +msgstr "" + +#: routes/campaigns.js:270 +msgid "Inactive" +msgstr "" + +#: routes/campaigns.js:272 +msgid "Active" +msgstr "" + +#: routes/campaigns.js:274 +msgid "Other" +msgstr "" + +#: routes/campaigns.js:429 +msgid "Unknown status selector" +msgstr "" + +#: routes/campaigns.js:657 +msgid "Scheduled sending" +msgstr "" + +#: routes/campaigns.js:659 +msgid "Could not schedule sending" +msgstr "" + +#: routes/campaigns.js:671 +msgid "Sending resumed" +msgstr "" + +#: routes/campaigns.js:673 +msgid "Could not resume sending" +msgstr "" + +#: routes/campaigns.js:685 +msgid "Sending reset" +msgstr "" + +#: routes/campaigns.js:687 +msgid "Could not reset sending" +msgstr "" + +#: routes/campaigns.js:699 +#: routes/campaigns.js:727 +msgid "Sending paused" +msgstr "" + +#: routes/campaigns.js:701 +#: routes/campaigns.js:729 +msgid "Could not pause sending" +msgstr "" + +#: routes/campaigns.js:713 +msgid "Sending activated" +msgstr "" + +#: routes/campaigns.js:715 +msgid "Could not activate sending" +msgstr "" + +#: routes/campaigns.js:750 +msgid "Attachment uploaded" +msgstr "" + +#: routes/campaigns.js:752 +msgid "Could not store attachment" +msgstr "" + +#: routes/campaigns.js:769 +msgid "Attachment deleted" +msgstr "" + +#: routes/campaigns.js:771 +msgid "Could not delete attachment" +msgstr "" + +#: routes/fields.js:28 +#: routes/fields.js:64 +#: routes/fields.js:118 +#: routes/segments.js:28 +#: routes/segments.js:59 +#: routes/segments.js:102 +#: routes/segments.js:151 +#: routes/segments.js:223 +#: routes/segments.js:255 +#: routes/segments.js:289 +#: routes/segments.js:336 +#: routes/segments.js:359 +msgid "Selected list ID not found" +msgstr "" + +#: routes/fields.js:102 +msgid "Could not create custom field" +msgstr "" + +#: routes/fields.js:129 +msgid "Selected field not found" +msgstr "" + +#: routes/fields.js:165 +msgid "Field settings updated" +msgstr "" + +#: routes/fields.js:167 +msgid "Field settings not updated" +msgstr "" + +#: routes/fields.js:183 +msgid "Custom field deleted" +msgstr "" + +#: routes/fields.js:185 +msgid "Could not delete specified field" +msgstr "" + +#: routes/links.js:40 +msgid "Oops, we couldn't find a link for the URL you clicked" +msgstr "" + +#: routes/lists.js:90 +msgid "Could not create list" +msgstr "" + +#: routes/lists.js:93 +msgid "List created" +msgstr "" + +#: routes/lists.js:101 +#: routes/lists.js:236 +#: routes/lists.js:301 +#: routes/lists.js:340 +#: routes/lists.js:409 +#: routes/lists.js:434 +#: routes/lists.js:479 +#: routes/lists.js:501 +#: routes/lists.js:530 +#: routes/lists.js:609 +#: routes/lists.js:666 +#: routes/lists.js:693 +msgid "Could not find list with specified ID" +msgstr "" + +#: routes/lists.js:115 +msgid "List settings updated" +msgstr "" + +#: routes/lists.js:117 +msgid "List settings not updated" +msgstr "" + +#: routes/lists.js:133 +msgid "List deleted" +msgstr "" + +#: routes/lists.js:135 +msgid "Could not delete specified list" +msgstr "" + +#: routes/lists.js:171 +msgid "Unknown" +msgstr "" + +#: routes/lists.js:171 +msgid "Complained" +msgstr "" + +#: routes/lists.js:202 +msgid "Invalid key" +msgstr "" + +#: routes/lists.js:204 +msgid "Expired key" +msgstr "" + +#: routes/lists.js:206 +msgid "Revoked key" +msgstr "" + +#: routes/lists.js:256 +msgid "Initializing" +msgstr "" + +#: routes/lists.js:259 +msgid "Initialized" +msgstr "" + +#: routes/lists.js:262 +msgid "Importing" +msgstr "" + +#: routes/lists.js:268 +msgid "Errored" +msgstr "" + +#: routes/lists.js:346 +#: routes/lists.js:415 +#: routes/lists.js:440 +msgid "Could not find subscriber with specified ID" +msgstr "" + +#: routes/lists.js:392 +msgid "Could not add subscription" +msgstr "" + +#: routes/lists.js:397 +msgid "%s was successfully added to your list" +msgstr "" + +#: routes/lists.js:399 +msgid "%s was not added to your list" +msgstr "" + +#: routes/lists.js:421 +msgid "Could not unsubscribe user" +msgstr "" + +#: routes/lists.js:424 +msgid "%s was successfully unsubscribed from your list" +msgstr "" + +#: routes/lists.js:444 +msgid "%s was successfully removed from your list" +msgstr "" + +#: routes/lists.js:456 +msgid "Another subscriber with email address %s already exists" +msgstr "" + +#: routes/lists.js:463 +msgid "Subscription settings updated" +msgstr "" + +#: routes/lists.js:465 +msgid "Subscription settings not updated" +msgstr "" + +#: routes/lists.js:507 +#: routes/lists.js:615 +#: routes/lists.js:651 +#: routes/lists.js:679 +#: routes/lists.js:699 +msgid "Could not find import data with specified ID" +msgstr "" + +#: routes/lists.js:538 +msgid "Could not process CSV" +msgstr "" + +#: routes/lists.js:547 +msgid "Could not create importer" +msgstr "" + +#: routes/lists.js:598 +msgid "Empty file" +msgstr "" + +#: routes/lists.js:655 +msgid "Import started" +msgstr "" + +#: routes/lists.js:683 +msgid "Import restarted" +msgstr "" + +#: routes/segments.js:86 +msgid "Could not create segment" +msgstr "" + +#: routes/segments.js:89 +msgid "Segment created" +msgstr "" + +#: routes/segments.js:113 +msgid "Selected segment ID not found" +msgstr "" + +#: routes/segments.js:188 +msgid "Segment settings updated" +msgstr "" + +#: routes/segments.js:190 +msgid "Segment settings not updated" +msgstr "" + +#: routes/segments.js:206 +msgid "Segment deleted" +msgstr "" + +#: routes/segments.js:208 +msgid "Could not delete specified segment" +msgstr "" + +#: routes/segments.js:342 +msgid "Could not create rule" +msgstr "" + +#: routes/segments.js:345 +msgid "Rule created" +msgstr "" + +#: routes/segments.js:410 +msgid "Rule settings updated" +msgstr "" + +#: routes/segments.js:412 +msgid "Rule settings not updated" +msgstr "" + +#: routes/segments.js:428 +msgid "Rule deleted" +msgstr "" + +#: routes/segments.js:430 +msgid "Could not delete specified rule" +msgstr "" + #: routes/settings.js:39 msgid "Use TLS" msgstr "" @@ -97,4 +3835,176 @@ msgstr "" #: routes/settings.js:221 msgid "Mailer settings verified, ready to send some mail!" +msgstr "" + +#: routes/subscription.js:22 +msgid "Selected subscription not found" +msgstr "" + +#: routes/subscription.js:32 +#: routes/subscription.js:103 +#: routes/subscription.js:141 +#: routes/subscription.js:166 +#: routes/subscription.js:191 +#: routes/subscription.js:232 +#: routes/subscription.js:270 +#: routes/subscription.js:317 +#: routes/subscription.js:339 +#: routes/subscription.js:368 +#: routes/subscription.js:392 +#: routes/subscription.js:424 +msgid "Selected list not found" +msgstr "" + +#: routes/subscription.js:78 +#: routes/subscription.js:472 +msgid "%s: Subscription Confirmed" +msgstr "" + +#: routes/subscription.js:217 +msgid "Email address not set" +msgstr "" + +#: routes/subscription.js:255 +msgid "Could not store confirmation data" +msgstr "" + +#: routes/subscription.js:284 +#: routes/subscription.js:349 +#: routes/subscription.js:402 +msgid "Subscription not found from this list" +msgstr "" + +#: routes/subscription.js:383 +msgid "Email address updated, check your mailbox for verification instructions" +msgstr "" + +#: routes/subscription.js:499 +#: routes/subscription.js:515 +msgid "Public key is not set" +msgstr "" + +#: routes/templates.js:98 +msgid "Could not create template" +msgstr "" + +#: routes/templates.js:101 +msgid "Template created" +msgstr "" + +#: routes/templates.js:109 +msgid "Could not find template with specified ID" +msgstr "" + +#: routes/templates.js:140 +msgid "Template settings updated" +msgstr "" + +#: routes/templates.js:142 +msgid "Template settings not updated" +msgstr "" + +#: routes/templates.js:158 +msgid "Template deleted" +msgstr "" + +#: routes/templates.js:160 +msgid "Could not delete specified template" +msgstr "" + +#: routes/triggers.js:62 +#: routes/triggers.js:79 +#: routes/triggers.js:154 +msgid "Could not find selected list" +msgstr "" + +#: routes/triggers.js:131 +msgid "Could not create trigger" +msgstr "" + +#: routes/triggers.js:138 +msgid "Trigger “%s” created" +msgstr "" + +#: routes/triggers.js:214 +msgid "Trigger settings updated" +msgstr "" + +#: routes/triggers.js:216 +msgid "Trigger settings not updated" +msgstr "" + +#: routes/triggers.js:228 +msgid "Trigger deleted" +msgstr "" + +#: routes/triggers.js:230 +msgid "Could not delete specified trigger" +msgstr "" + +#: routes/triggers.js:242 +msgid "Could not find trigger with specified ID" +msgstr "" + +#: routes/triggers.js:255 +msgid "Trigger not found" +msgstr "" + +#: routes/users.js:32 +msgid "" +"An email with password reset instructions has been sent to your email " +"address, if it exists on our system." +msgstr "" + +#: routes/users.js:46 +#: routes/users.js:64 +msgid "Unknown or expired reset token" +msgstr "" + +#: routes/users.js:66 +msgid "Your password has been changed successfully" +msgstr "" + +#: routes/users.js:87 +msgid "User data not found" +msgstr "" + +#: routes/users.js:110 +msgid "Access token updated" +msgstr "" + +#: routes/users.js:112 +msgid "Access token not updated" +msgstr "" + +#: routes/users.js:139 +msgid "Account information updated" +msgstr "" + +#: routes/users.js:141 +msgid "Account information not updated" +msgstr "" + +#: services/feedcheck.js:51 +msgid "Feed error: %s" +msgstr "" + +#: services/feedcheck.js:54 +msgid "Found %s new campaign messages from feed" +msgstr "" + +#: services/feedcheck.js:56 +msgid "Found nothing new from the feed" +msgstr "" + +#: services/feedcheck.js:143 +msgid "RSS entry %s" +msgstr "" + +#: services/importer.js:243 +msgid "Could not access import file" +msgstr "" + +#: services/triggers.js:51 +msgid "Unknown trigger type %s" msgstr "" \ No newline at end of file diff --git a/lib/fakelang.js b/lib/fakelang.js new file mode 100644 index 00000000..7a6027a1 --- /dev/null +++ b/lib/fakelang.js @@ -0,0 +1,26 @@ +'use strict'; + +/* lloyd|2012|http://wtfpl.org */ + +/* eslint-disable */ + +module.exports = str => { + let from = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+\\|`~[{]};:'\",<.>/?"; + let to = "ɐqɔpǝɟƃɥıɾʞʅɯuodbɹsʇnʌʍxʎz∀ԐↃᗡƎℲ⅁HIſӼ⅂WNOԀÒᴚS⊥∩ɅMX⅄Z0123456789¡@#$%ᵥ⅋⁎()-_=+\\|,~[{]};:,„´<.>/¿"; + + return str.replace(/(\{\{[^\}]+\}\}|%s)/g, '\x00\x04$1\x00').split('\x00').map(c => { + if (c.charAt(0) === '\x04') { + return c; + } + let r = ''; + for (let i = 0, len = c.length; i < len; i++) { + let pos = from.indexOf(c.charAt(i)); + if (pos < 0) { + r += c.charAt(i); + } else { + r += to.charAt(pos); + } + } + return r; + }).join('\x00').replace(/[\x00\x04]/g, ''); +} diff --git a/lib/feed.js b/lib/feed.js index fbbe1a57..6758f798 100644 --- a/lib/feed.js +++ b/lib/feed.js @@ -2,6 +2,8 @@ let FeedParser = require('feedparser'); let request = require('request'); +let _ = require('./translate')._; +let util = require('util'); module.exports.fetch = (url, callback) => { let req = request(url); @@ -26,7 +28,7 @@ module.exports.fetch = (url, callback) => { } if (res.statusCode !== 200) { - return req.emit('error', new Error('Bad status code')); + return req.emit('error', new Error(util.format(_('Bad status code %s'), res.statusCode))); } req.pipe(feedparser); diff --git a/lib/helpers.js b/lib/helpers.js index 521d46a0..f2cd7429 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -2,6 +2,7 @@ let lists = require('./models/lists'); let fields = require('./models/fields'); +let _ = require('./translate')._; module.exports = { getDefaultMergeTags, @@ -13,34 +14,34 @@ function getDefaultMergeTags(callback) { callback(null, [ { key: 'LINK_UNSUBSCRIBE', - value: 'URL that points to the unsubscribe page' + value: _('URL that points to the unsubscribe page') }, { key: 'LINK_PREFERENCES', - value: 'URL that points to the preferences page of the subscriber' + value: _('URL that points to the preferences page of the subscriber') }, { key: 'LINK_BROWSER', - value: 'URL to preview the message in a browser' + value: _('URL to preview the message in a browser') }, { key: 'EMAIL', - value: 'Email address' + value: _('Email address') }, { key: 'FIRST_NAME', - value: 'First name' + value: _('First name') }, { key: 'LAST_NAME', - value: 'Last name' + value: _('Last name') }, { key: 'FULL_NAME', - value: 'Full name (first and last name combined)' + value: _('Full name (first and last name combined)') }, { key: 'SUBSCRIPTION_ID', - value: 'Unique ID that identifies the recipient' + value: _('Unique ID that identifies the recipient') }, { key: 'LIST_ID', - value: 'Unique ID that identifies the list used for this campaign' + value: _('Unique ID that identifies the list used for this campaign') }, { key: 'CAMPAIGN_ID', - value: 'Unique ID that identifies current campaign' + value: _('Unique ID that identifies current campaign') } ]); } diff --git a/lib/mailer.js b/lib/mailer.js index 9f0f695b..83f55e5a 100644 --- a/lib/mailer.js +++ b/lib/mailer.js @@ -14,6 +14,23 @@ let templates = new Map(); let htmlToText = require('html-to-text'); let aws = require('aws-sdk'); +let _ = require('./translate')._; +let util = require('util'); + +Handlebars.registerHelper('translate', function (context, options) { // eslint-disable-line prefer-arrow-callback + if (typeof options === 'undefined' && context) { + options = context; + context = false; + } + + let result = _(options.fn(this)); // eslint-disable-line no-invalid-this + + if (Array.isArray(context)) { + result = util.format(result, ...context); + } + return new Handlebars.SafeString(result); +}); + module.exports.transport = false; module.exports.update = () => { @@ -195,7 +212,7 @@ function createMailer(callback) { } }; } else { - return callback(new Error('Invalid mail transport')); + return callback(new Error(_('Invalid mail transport'))); } module.exports.transport = nodemailer.createTransport(transportOptions, config.nodemailer); diff --git a/lib/models/campaigns.js b/lib/models/campaigns.js index 8d2a31ce..b71d1bbf 100644 --- a/lib/models/campaigns.js +++ b/lib/models/campaigns.js @@ -12,6 +12,7 @@ let feed = require('../feed'); let log = require('npmlog'); let mailer = require('../mailer'); let humanize = require('humanize'); +let _ = require('../translate')._; let allowedKeys = ['description', 'from', 'address', 'reply_to', 'subject', 'editor_name', 'editor_data', 'template', 'source_url', 'list', 'segment', 'html', 'text', 'tracking_disabled']; @@ -267,7 +268,7 @@ module.exports.filterStatusSubscribers = (campaign, status, request, columns, ca module.exports.getByCid = (cid, callback) => { cid = (cid || '').toString().trim(); if (!cid) { - return callback(new Error('Missing Campaign ID')); + return callback(new Error(_('Missing Campaign ID'))); } db.getConnection((err, connection) => { if (err) { @@ -294,7 +295,7 @@ module.exports.get = (id, withSegment, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Campaign ID')); + return callback(new Error(_('Missing Campaign ID'))); } db.getConnection((err, connection) => { @@ -367,7 +368,7 @@ module.exports.getAttachments = (campaign, callback) => { campaign = Number(campaign) || 0; if (campaign < 1) { - return callback(new Error('Missing Campaign ID')); + return callback(new Error(_('Missing Campaign ID'))); } db.getConnection((err, connection) => { @@ -403,7 +404,7 @@ module.exports.addAttachment = (id, attachment, callback) => { let size = attachment.content ? attachment.content.length : 0; if (!size) { - return callback(new Error('Emtpy or too large attahcment')); + return callback(new Error(_('Emtpy or too large attahcment'))); } db.getConnection((err, connection) => { if (err) { @@ -490,7 +491,7 @@ module.exports.getLinks = (id, linkId, callback) => { linkId = Number(linkId) || 0; if (id < 1) { - return callback(new Error('Missing Campaign ID')); + return callback(new Error(_('Missing Campaign ID'))); } db.getConnection((err, connection) => { @@ -569,11 +570,11 @@ module.exports.create = (campaign, opts, callback) => { campaign.template = Number(campaign.template) || 0; if (!name) { - return callback(new Error('Campaign Name must be set')); + return callback(new Error(_('Campaign Name must be set'))); } if (campaign.type === 2 && (!campaign.sourceUrl || !isUrl(campaign.sourceUrl))) { - return callback(new Error('RSS URL must be set and needs to be a valid URL')); + return callback(new Error(_('RSS URL must be set and needs to be a valid URL'))); } let getList = (listId, callback) => { @@ -726,7 +727,7 @@ module.exports.create = (campaign, opts, callback) => { return callback(err); } if (!template) { - return callback(new Error('Selected template not found')); + return callback(new Error(_('Selected template not found'))); } campaign.editorName = template.editorName; @@ -748,7 +749,7 @@ module.exports.update = (id, updates, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Campaign ID')); + return callback(new Error(_('Missing Campaign ID'))); } let campaign = tools.convertKeys(updates); @@ -757,7 +758,7 @@ module.exports.update = (id, updates, callback) => { campaign.trackingDisabled = campaign.trackingDisabled ? 1 : 0; if (!name) { - return callback(new Error('Campaign Name must be set')); + return callback(new Error(_('Campaign Name must be set'))); } if (/^\d+:\d+$/.test(campaign.list)) { @@ -877,7 +878,7 @@ module.exports.delete = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Campaign ID')); + return callback(new Error(_('Missing Campaign ID'))); } db.getConnection((err, connection) => { @@ -1078,7 +1079,7 @@ module.exports.getMail = (campaignId, listId, subscriptionId, callback) => { subscriptionId = Number(subscriptionId) || 0; if (campaignId < 1 || listId < 1 || subscriptionId < 1) { - return callback(new Error('Invalid or missing message ID')); + return callback(new Error(_('Invalid or missing message ID'))); } db.getConnection((err, connection) => { diff --git a/lib/models/fields.js b/lib/models/fields.js index 6bbf368d..44396a7e 100644 --- a/lib/models/fields.js +++ b/lib/models/fields.js @@ -6,26 +6,28 @@ let slugify = require('slugify'); let lists = require('./lists'); let shortid = require('shortid'); let Handlebars = require('handlebars'); +let _ = require('../translate')._; +let util = require('util'); let allowedKeys = ['name', 'key', 'default_value', 'group', 'group_template', 'visible']; let allowedTypes; module.exports.grouped = ['radio', 'checkbox', 'dropdown']; module.exports.types = { - text: 'Text', - website: 'Website', - longtext: 'Multi-line text', - gpg: 'GPG Public Key', - number: 'Number', - radio: 'Radio Buttons', - checkbox: 'Checkboxes', - dropdown: 'Drop Down', - 'date-us': 'Date (MM/DD/YYY)', - 'date-eur': 'Date (DD/MM/YYYY)', - 'birthday-us': 'Birthday (MM/DD)', - 'birthday-eur': 'Birthday (DD/MM)', - json: 'JSON value for custom rendering', - option: 'Option' + text: _('Text'), + website: _('Website'), + longtext: _('Multi-line text'), + gpg: _('GPG Public Key'), + number: _('Number'), + radio: _('Radio Buttons'), + checkbox: _('Checkboxes'), + dropdown: _('Drop Down'), + 'date-us': _('Date (MM/DD/YYY)'), + 'date-eur': _('Date (DD/MM/YYYY)'), + 'birthday-us': _('Birthday (MM/DD)'), + 'birthday-eur': _('Birthday (DD/MM)'), + json: _('JSON value for custom rendering'), + option: _('Option') }; module.exports.allowedTypes = allowedTypes = Object.keys(module.exports.types); @@ -48,7 +50,7 @@ module.exports.list = (listId, callback) => { listId = Number(listId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } db.getConnection((err, connection) => { @@ -93,7 +95,7 @@ module.exports.get = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } db.getConnection((err, connection) => { @@ -118,13 +120,13 @@ module.exports.create = (listId, field, callback) => { listId = Number(listId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } field = tools.convertKeys(field); if (field.type === 'option' && !field.group) { - return callback(new Error('Option field requires a group to be selected')); + return callback(new Error(_('Option field requires a group to be selected'))); } if (field.type !== 'option') { @@ -144,11 +146,11 @@ module.exports.update = (id, updates, callback) => { updates = tools.convertKeys(updates); if (id < 1) { - return callback(new Error('Missing Field ID')); + return callback(new Error(_('Missing Field ID'))); } if (!(updates.name || '').toString().trim()) { - return callback(new Error('Field Name must be set')); + return callback(new Error(_('Field Name must be set'))); } if (updates.key) { @@ -194,7 +196,7 @@ module.exports.delete = (fieldId, callback) => { fieldId = Number(fieldId) || 0; if (fieldId < 1) { - return callback(new Error('Missing Field ID')); + return callback(new Error(_('Missing Field ID'))); } db.getConnection((err, connection) => { @@ -211,7 +213,7 @@ module.exports.delete = (fieldId, callback) => { if (!rows || !rows.length) { connection.release(); - return callback(new Error('Custom field not found')); + return callback(new Error(_('Custom field not found'))); } let field = tools.convertKeys(rows[0]); @@ -284,15 +286,15 @@ function addCustomField(listId, name, defaultValue, type, group, groupTemplate, let key = slugify('merge ' + name, '_').toUpperCase(); if (allowedTypes.indexOf(type) < 0) { - return callback(new Error('Unknown column type ' + type)); + return callback(new Error(util.format(_('Unknown column type %s'), type))); } if (!name) { - return callback(new Error('Missing column name')); + return callback(new Error(_('Missing column name'))); } if (listId <= 0) { - return callback(new Error('Missing list ID')); + return callback(new Error(_('Missing list ID'))); } lists.get(listId, (err, list) => { @@ -300,7 +302,7 @@ function addCustomField(listId, name, defaultValue, type, group, groupTemplate, return callback(err); } if (!list) { - return callback('Provided List ID not found'); + return callback(_('Provided List ID not found')); } db.getConnection((err, connection) => { diff --git a/lib/models/links.js b/lib/models/links.js index 2d6209b6..6c5a0bae 100644 --- a/lib/models/links.js +++ b/lib/models/links.js @@ -3,6 +3,7 @@ let db = require('../db'); let shortid = require('shortid'); let util = require('util'); +let _ = require('../translate')._; let geoip = require('geoip-ultralight'); let campaigns = require('./campaigns'); @@ -324,7 +325,7 @@ function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) { return callback(err); } if (!campaign) { - return callback(new Error('Campaign not found')); + return callback(new Error(_('Campaign not found'))); } lists.getByCid(listCid, (err, list) => { @@ -332,7 +333,7 @@ function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) { return callback(err); } if (!list) { - return callback(new Error('Campaign not found')); + return callback(new Error(_('List not found'))); } subscriptions.get(list.id, subscriptionCid, (err, subscription) => { @@ -340,7 +341,7 @@ function getSubscriptionData(campaignCid, listCid, subscriptionCid, callback) { return callback(err); } if (!subscription) { - return callback(new Error('Subscription not found')); + return callback(new Error(_('Subscription not found'))); } return callback(null, { diff --git a/lib/models/lists.js b/lib/models/lists.js index c464b702..4d58952c 100644 --- a/lib/models/lists.js +++ b/lib/models/lists.js @@ -4,6 +4,7 @@ let db = require('../db'); let tools = require('../tools'); let shortid = require('shortid'); let segments = require('./segments'); +let _ = require('../translate')._; let allowedKeys = ['description']; @@ -77,7 +78,7 @@ module.exports.get = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } db.getConnection((err, connection) => { @@ -113,7 +114,7 @@ module.exports.create = (list, callback) => { let name = (data.name || '').toString().trim(); if (!data) { - return callback(new Error('List Name must be set')); + return callback(new Error(_('List Name must be set'))); } let keys = ['name']; @@ -171,11 +172,11 @@ module.exports.update = (id, updates, callback) => { let values = [name]; if (id < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (!name) { - return callback(new Error('List Name must be set')); + return callback(new Error(_('List Name must be set'))); } Object.keys(updates).forEach(key => { @@ -208,7 +209,7 @@ module.exports.delete = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } db.getConnection((err, connection) => { @@ -237,7 +238,7 @@ module.exports.delete = (id, callback) => { function resolveCid(cid, callback) { cid = (cid || '').toString().trim(); if (!cid) { - return callback(new Error('Missing List CID')); + return callback(new Error(_('Missing List CID'))); } db.getConnection((err, connection) => { diff --git a/lib/models/segments.js b/lib/models/segments.js index b0629a34..47a3afbc 100644 --- a/lib/models/segments.js +++ b/lib/models/segments.js @@ -3,34 +3,36 @@ let tools = require('../tools'); let db = require('../db'); let fields = require('./fields'); +let util = require('util'); +let _ = require('../translate')._; module.exports.defaultColumns = [{ column: 'email', - name: 'Email address', + name: _('Email address'), type: 'string' }, { column: 'opt_in_country', - name: 'Signup country', + name: _('Signup country'), type: 'string' }, { column: 'created', - name: 'Sign up date', + name: _('Sign up date'), type: 'date' }, { column: 'latest_open', - name: 'Latest open', + name: _('Latest open'), type: 'date' }, { column: 'latest_click', - name: 'Latest click', + name: _('Latest click'), type: 'date' }, { column: 'first_name', - name: 'First name', + name: _('First name'), type: 'string' }, { column: 'last_name', - name: 'Last name', + name: _('Last name'), type: 'string' }]; @@ -38,7 +40,7 @@ module.exports.list = (listId, callback) => { listId = Number(listId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } @@ -64,7 +66,7 @@ module.exports.get = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Segment ID')); + return callback(new Error(_('Missing Segment ID'))); } db.getConnection((err, connection) => { @@ -80,7 +82,7 @@ module.exports.get = (id, callback) => { } if (!rows || !rows.length) { connection.release(); - return callback(new Error('Segment not found')); + return callback(new Error(_('Segment not found'))); } let segment = tools.convertKeys(rows[0]); @@ -141,7 +143,9 @@ module.exports.get = (id, callback) => { case 'date': case 'birthday': if (rule.value.relativeRange) { - rule.formatted = (rule.value.start ? rule.value.start + ' days ' + (rule.value.startDirection ? 'after' : 'before') + ' today' : 'today') + ' … ' + (rule.value.end ? rule.value.end + ' days ' + (rule.value.endDirection ? 'after' : 'before') + ' today' : 'today'); + let startString = rule.value.startDirection ? util.format(_('%s days after today'), rule.value.start) : util.format(_('%s days before today'), rule.value.start); + let endString = rule.value.endDirection ? util.format(_('%s days after today'), rule.value.end) : util.format(_('%s days before today'), rule.value.end); + rule.formatted = (rule.value.start ? startString : _('today')) + ' … ' + (rule.value.end ? endString : _('today')); } else if (rule.value.range) { rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || ''); } else { @@ -149,7 +153,7 @@ module.exports.get = (id, callback) => { } break; case 'boolean': - rule.formatted = rule.value.value ? 'Selected' : 'Not selected'; + rule.formatted = rule.value.value ? _('Selected') : _('Not selected'); break; default: rule.formatted = rule.value.value || ''; @@ -169,7 +173,7 @@ module.exports.create = (listId, segment, callback) => { listId = Number(listId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } segment = tools.convertKeys(segment); @@ -178,11 +182,11 @@ module.exports.create = (listId, segment, callback) => { segment.type = Number(segment.type) || 0; if (!segment.name) { - return callback(new Error('Field Name must be set')); + return callback(new Error(_('Field Name must be set'))); } if (segment.type <= 0) { - return callback(new Error('Invalid segment rule type')); + return callback(new Error(_('Invalid segment rule type'))); } let keys = ['list', 'name', 'type']; @@ -209,7 +213,7 @@ module.exports.update = (id, updates, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Segment ID')); + return callback(new Error(_('Missing Segment ID'))); } let segment = tools.convertKeys(updates); @@ -218,11 +222,11 @@ module.exports.update = (id, updates, callback) => { segment.type = Number(segment.type) || 0; if (!segment.name) { - return callback(new Error('Field Name must be set')); + return callback(new Error(_('Field Name must be set'))); } if (segment.type <= 0) { - return callback(new Error('Invalid segment rule type')); + return callback(new Error(_('Invalid segment rule type'))); } let keys = ['name', 'type']; @@ -249,7 +253,7 @@ module.exports.delete = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Segment ID')); + return callback(new Error(_('Missing Segment ID'))); } db.getConnection((err, connection) => { @@ -271,7 +275,7 @@ module.exports.createRule = (segmentId, rule, callback) => { segmentId = Number(segmentId) || 0; if (segmentId < 1) { - return callback(new Error('Missing Segment ID')); + return callback(new Error(_('Missing Segment ID'))); } rule = tools.convertKeys(rule); @@ -282,12 +286,12 @@ module.exports.createRule = (segmentId, rule, callback) => { } if (!segment) { - return callback(new Error('Selected segment not found')); + return callback(new Error(_('Selected segment not found'))); } let column = segment.columns.filter(column => column.column === rule.column).pop(); if (!column) { - return callback(new Error('Invalid rule type')); + return callback(new Error(_('Invalid rule type'))); } let value; @@ -351,7 +355,7 @@ module.exports.getRule = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Rule ID')); + return callback(new Error(_('Missing Rule ID'))); } db.getConnection((err, connection) => { @@ -367,7 +371,7 @@ module.exports.getRule = (id, callback) => { } if (!rows || !rows.length) { - return callback(new Error('Specified rule not found')); + return callback(new Error(_('Specified rule not found'))); } let rule = tools.convertKeys(rows[0]); @@ -378,7 +382,7 @@ module.exports.getRule = (id, callback) => { } if (!segment) { - return callback(new Error('Specified segment not found')); + return callback(new Error(_('Specified segment not found'))); } if (rule.value) { @@ -400,7 +404,10 @@ module.exports.getRule = (id, callback) => { case 'date': case 'birthday': if (rule.value.relativeRange) { - rule.formatted = (rule.value.start ? rule.value.start + ' days ' + (rule.value.startDirection ? 'after' : 'before') + ' today' : 'today') + ' … ' + (rule.value.end ? rule.value.end + ' days ' + (rule.value.endDirection ? 'after' : 'before') + ' today' : 'today'); + + let startString = rule.value.startDirection ? util.format(_('%s days after today'), rule.value.start) : util.format(_('%s days before today'), rule.value.start); + let endString = rule.value.endDirection ? util.format(_('%s days after today'), rule.value.end) : util.format(_('%s days before today'), rule.value.end); + rule.formatted = (rule.value.start ? startString : _('today')) + ' … ' + (rule.value.end ? endString : _('today')); } else if (rule.value.range) { rule.formatted = (rule.value.start || '') + ' … ' + (rule.value.end || ''); } else { @@ -408,7 +415,7 @@ module.exports.getRule = (id, callback) => { } break; case 'boolean': - rule.formatted = rule.value.value ? 'Selected' : 'Not selected'; + rule.formatted = rule.value.value ? _('Selected') : _('Not selected'); break; default: rule.formatted = rule.value.value || ''; @@ -424,7 +431,7 @@ module.exports.updateRule = (id, rule, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Rule ID')); + return callback(new Error(_('Missing Rule ID'))); } rule = tools.convertKeys(rule); @@ -435,7 +442,7 @@ module.exports.updateRule = (id, rule, callback) => { } if (!existingRule) { - return callback(new Error('Selected rule not found')); + return callback(new Error(_('Selected rule not found'))); } module.exports.get(existingRule.segment, (err, segment) => { @@ -444,12 +451,12 @@ module.exports.updateRule = (id, rule, callback) => { } if (!segment) { - return callback(new Error('Selected segment not found')); + return callback(new Error(_('Selected segment not found'))); } let column = segment.columns.filter(column => column.column === existingRule.column).pop(); if (!column) { - return callback(new Error('Invalid rule type')); + return callback(new Error(_('Invalid rule type'))); } let value; @@ -514,7 +521,7 @@ module.exports.deleteRule = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Rule ID')); + return callback(new Error(_('Missing Rule ID'))); } db.getConnection((err, connection) => { @@ -539,7 +546,7 @@ module.exports.getQuery = (id, prefix, callback) => { } if (!segment) { - return callback(new Error('Segment not found')); + return callback(new Error(_('Segment not found'))); } prefix = prefix ? prefix + '.' : ''; @@ -648,7 +655,7 @@ module.exports.subscribers = (id, onlySubscribed, callback) => { return callback(err); } if (!segment) { - return callback(new Error('Segment not found')); + return callback(new Error(_('Segment not found'))); } module.exports.getQuery(id, false, (err, queryData) => { if (err) { diff --git a/lib/models/subscriptions.js b/lib/models/subscriptions.js index ca50b6db..941a2e02 100644 --- a/lib/models/subscriptions.js +++ b/lib/models/subscriptions.js @@ -10,6 +10,8 @@ let settings = require('./settings'); let mailer = require('../mailer'); let urllib = require('url'); let log = require('npmlog'); +let _ = require('../translate')._; +let util = require('util'); module.exports.list = (listId, start, limit, callback) => { listId = Number(listId) || 0; @@ -83,7 +85,7 @@ module.exports.filter = (listId, request, columns, segmentId, callback) => { segmentId = Number(segmentId) || 0; if (!listId) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } let processQuery = queryData => { @@ -228,7 +230,7 @@ module.exports.addConfirmation = (list, email, optInIp, data, callback) => { name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '), address: email }, - subject: list.name + ': Please Confirm Subscription', + subject: util.format(_('%s: Please Confirm Subscription'),list.name), encryptionKeys }, { html: 'emails/confirm-html.hbs', @@ -319,7 +321,7 @@ module.exports.subscribe = (cid, optInIp, callback) => { } if (!result.entryId) { - return callback(new Error('Could not save subscription')); + return callback(new Error(_('Could not save subscription'))); } db.getConnection((err, connection) => { @@ -502,7 +504,7 @@ module.exports.get = (listId, cid, callback) => { cid = (cid || '').toString().trim(); if (!cid) { - return callback(new Error('Missing Subbscription ID')); + return callback(new Error(_('Missing Subbscription ID'))); } db.getConnection((err, connection) => { @@ -532,7 +534,7 @@ module.exports.getById = (listId, id, callback) => { id = Number(id) || 0; if (!id) { - return callback(new Error('Missing Subbscription ID')); + return callback(new Error(_('Missing Subbscription ID'))); } db.getConnection((err, connection) => { @@ -560,7 +562,7 @@ module.exports.getById = (listId, id, callback) => { module.exports.getByEmail = (listId, email, callback) => { if (!email) { - return callback(new Error('Missing Subbscription email address')); + return callback(new Error(_('Missing Subbscription email address'))); } db.getConnection((err, connection) => { @@ -635,11 +637,11 @@ module.exports.update = (listId, cid, updates, allowEmail, callback) => { let values = []; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (!cid) { - return callback(new Error('Missing subscription ID')); + return callback(new Error(_('Missing subscription ID'))); } fields.list(listId, (err, fieldList) => { @@ -698,11 +700,11 @@ module.exports.unsubscribe = (listId, email, campaignId, callback) => { campaignId = (campaignId || '').toString().trim() || false; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (!email) { - return callback(new Error('Missing email address')); + return callback(new Error(_('Missing email address'))); } db.getConnection((err, connection) => { @@ -884,11 +886,11 @@ module.exports.delete = (listId, cid, callback) => { cid = (cid || '').toString().trim(); if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (!cid) { - return callback(new Error('Missing subscription ID')); + return callback(new Error(_('Missing subscription ID'))); } db.getConnection((err, connection) => { @@ -987,11 +989,11 @@ module.exports.updateImport = (listId, importId, data, callback) => { importId = Number(importId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (importId < 1) { - return callback(new Error('Missing Import ID')); + return callback(new Error(_('Missing Import ID'))); } let keys = []; @@ -1041,11 +1043,11 @@ module.exports.getImport = (listId, importId, callback) => { importId = Number(importId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (importId < 1) { - return callback(new Error('Missing Import ID')); + return callback(new Error(_('Missing Import ID'))); } db.getConnection((err, connection) => { @@ -1081,7 +1083,7 @@ module.exports.getFailedImports = (importId, callback) => { importId = Number(importId) || 0; if (importId < 1) { - return callback(new Error('Missing Import ID')); + return callback(new Error(_('Missing Import ID'))); } db.getConnection((err, connection) => { @@ -1104,7 +1106,7 @@ module.exports.listImports = (listId, callback) => { listId = Number(listId) || 0; if (listId < 1) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } db.getConnection((err, connection) => { @@ -1147,11 +1149,11 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => { let emailNew = (updates.emailNew || '').toString().trim(); if (!list || !list.id) { - return callback(new Error('Missing List ID')); + return callback(new Error(_('Missing List ID'))); } if (!cid) { - return callback(new Error('Missing subscription ID')); + return callback(new Error(_('Missing subscription ID'))); } tools.validateEmail(emailNew, false, err => { @@ -1173,12 +1175,12 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => { } if (!rows || !rows.length) { connection.release(); - return callback(new Error('Unknown subscription ID')); + return callback(new Error(_('Unknown subscription ID'))); } if (rows[0].email === emailNew) { connection.release(); - return callback(new Error('Nothing seems to be changed')); + return callback(new Error(_('Nothing seems to be changed'))); } let old = rows[0]; @@ -1192,7 +1194,7 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => { } if (rows && rows[0] && rows[0].id) { - return callback(new Error('This address is already registered by someone else')); + return callback(new Error(_('This address is already registered by someone else'))); } module.exports.addConfirmation(list, emailNew, optInIp, { diff --git a/lib/models/templates.js b/lib/models/templates.js index 6df4c6ad..0753a87f 100644 --- a/lib/models/templates.js +++ b/lib/models/templates.js @@ -2,6 +2,7 @@ let db = require('../db'); let tools = require('../tools'); +let _ = require('../translate')._; let allowedKeys = ['description', 'editor_name', 'editor_data', 'html', 'text']; @@ -47,7 +48,7 @@ module.exports.get = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Template ID')); + return callback(new Error(_('Missing Template ID'))); } db.getConnection((err, connection) => { @@ -76,7 +77,7 @@ module.exports.create = (template, callback) => { let data = tools.convertKeys(template); if (!(data.name || '').toString().trim()) { - return callback(new Error('Template Name must be set')); + return callback(new Error(_('Template Name must be set'))); } let name = (template.name || '').toString().trim(); @@ -118,11 +119,11 @@ module.exports.update = (id, updates, callback) => { let data = tools.convertKeys(updates); if (id < 1) { - return callback(new Error('Missing Template ID')); + return callback(new Error(_('Missing Template ID'))); } if (!(data.name || '').toString().trim()) { - return callback(new Error('Template Name must be set')); + return callback(new Error(_('Template Name must be set'))); } let name = (updates.name || '').toString().trim(); @@ -159,7 +160,7 @@ module.exports.delete = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Template ID')); + return callback(new Error(_('Missing Template ID'))); } db.getConnection((err, connection) => { diff --git a/lib/models/triggers.js b/lib/models/triggers.js index 479eef8d..cd682cda 100644 --- a/lib/models/triggers.js +++ b/lib/models/triggers.js @@ -4,36 +4,37 @@ let tools = require('../tools'); let db = require('../db'); let lists = require('./lists'); let util = require('util'); +let _ = require('../translate')._; module.exports.defaultColumns = [{ column: 'created', - name: 'Sign up date', + name: _('Sign up date'), type: 'date' }, { column: 'latest_open', - name: 'Latest open', + name: _('Latest open'), type: 'date' }, { column: 'latest_click', - name: 'Latest click', + name: _('Latest click'), type: 'date' }]; module.exports.defaultCampaignEvents = [{ option: 'delivered', - name: 'Delivered' + name: _('Delivered') }, { option: 'opened', - name: 'Has Opened' + name: _('Has Opened') }, { option: 'clicked', - name: 'Has Clicked' + name: _('Has Clicked') }, { option: 'not_opened', - name: 'Not Opened' + name: _('Not Opened') }, { option: 'not_clicked', - name: 'Not Clicked' + name: _('Not Clicked') }]; let defaultColumnMap = {}; @@ -170,36 +171,36 @@ module.exports.create = (trigger, callback) => { let column; if (!listId) { - return callback(new Error('Missing or invalid list ID')); + return callback(new Error(_('Missing or invalid list ID'))); } if (seconds < 0) { - return callback(new Error('Days in the past are not allowed')); + return callback(new Error(_('Days in the past are not allowed'))); } if (!rule || ['campaign', 'subscription'].indexOf(rule) < 0) { - return callback(new Error('Missing or invalid trigger rule')); + return callback(new Error(_('Missing or invalid trigger rule'))); } switch (rule) { case 'subscription': column = (trigger.column || '').toString().toLowerCase().trim(); if (!column) { - return callback(new Error('Invalid subscription configuration')); + return callback(new Error(_('Invalid subscription configuration'))); } break; case 'campaign': column = (trigger.campaignOption || '').toString().toLowerCase().trim(); sourceCampaign = Number(trigger.sourceCampaign) || 0; if (!column || !sourceCampaign) { - return callback(new Error('Invalid campaign configuration')); + return callback(new Error(_('Invalid campaign configuration'))); } if (sourceCampaign === destCampaign) { - return callback(new Error('A campaing can not be a target for itself')); + return callback(new Error(_('A campaing can not be a target for itself'))); } break; default: - return callback(new Error('Missing or invalid trigger rule')); + return callback(new Error(_('Missing or invalid trigger rule'))); } lists.get(listId, (err, list) => { @@ -207,7 +208,7 @@ module.exports.create = (trigger, callback) => { return callback(err); } if (!list) { - return callback(new Error('Missing or invalid list ID')); + return callback(new Error(_('Missing or invalid list ID'))); } db.getConnection((err, connection) => { @@ -228,7 +229,7 @@ module.exports.create = (trigger, callback) => { let id = result && result.insertId; if (!id) { - return callback(new Error('Could not store trigger row')); + return callback(new Error(_('Could not store trigger row'))); } createTriggerTable(id, err => { @@ -245,7 +246,7 @@ module.exports.create = (trigger, callback) => { module.exports.update = (id, trigger, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing or invalid Trigger ID')); + return callback(new Error(_('Missing or invalid Trigger ID'))); } trigger = tools.convertKeys(trigger); @@ -259,32 +260,32 @@ module.exports.update = (id, trigger, callback) => { let column; if (seconds < 0) { - return callback(new Error('Days in the past are not allowed')); + return callback(new Error(_('Days in the past are not allowed'))); } if (!rule || ['campaign', 'subscription'].indexOf(rule) < 0) { - return callback(new Error('Missing or invalid trigger rule')); + return callback(new Error(_('Missing or invalid trigger rule'))); } switch (rule) { case 'subscription': column = (trigger.column || '').toString().toLowerCase().trim(); if (!column) { - return callback(new Error('Invalid subscription configuration')); + return callback(new Error(_('Invalid subscription configuration'))); } break; case 'campaign': column = (trigger.campaignOption || '').toString().toLowerCase().trim(); sourceCampaign = Number(trigger.sourceCampaign) || 0; if (!column || !sourceCampaign) { - return callback(new Error('Invalid campaign configuration')); + return callback(new Error(_('Invalid campaign configuration'))); } if (sourceCampaign === destCampaign) { - return callback(new Error('A campaing can not be a target for itself')); + return callback(new Error(_('A campaing can not be a target for itself'))); } break; default: - return callback(new Error('Missing or invalid trigger rule')); + return callback(new Error(_('Missing or invalid trigger rule'))); } db.getConnection((err, connection) => { @@ -312,7 +313,7 @@ module.exports.delete = (id, callback) => { id = Number(id) || 0; if (id < 1) { - return callback(new Error('Missing Trigger ID')); + return callback(new Error(_('Missing Trigger ID'))); } db.getConnection((err, connection) => { diff --git a/lib/models/users.js b/lib/models/users.js index 10e92784..b5be0fca 100644 --- a/lib/models/users.js +++ b/lib/models/users.js @@ -9,6 +9,7 @@ let mailer = require('../mailer'); let settings = require('./settings'); let crypto = require('crypto'); let urllib = require('url'); +let _ = require('../translate')._; /** * Fetches user by ID value @@ -99,7 +100,7 @@ module.exports.add = (username, password, email, callback) => { let id = result && result.insertId; if (!id) { - return callback(new Error('Could not store user row')); + return callback(new Error(_('Could not store user row'))); } return callback(null, id); @@ -169,7 +170,7 @@ module.exports.authenticate = (username, password, callback) => { module.exports.update = (id, updates, callback) => { if (!updates.email) { - return callback(new Error('Email Address must be set')); + return callback(new Error(_('Email Address must be set'))); } let update = (connection, callback) => { @@ -180,7 +181,7 @@ module.exports.update = (id, updates, callback) => { } if (!rows.length) { - return callback('Failed to check user data'); + return callback(_('Failed to check user data')); } let keys = ['email']; @@ -191,7 +192,7 @@ module.exports.update = (id, updates, callback) => { connection.query('UPDATE users SET ' + keys.map(key => key + '=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => { if (err) { if (err.code === 'ER_DUP_ENTRY') { - err = new Error('Can\'t change email as another user with the same email address already exists'); + err = new Error(_('Can\'t change email as another user with the same email address already exists')); } return callback(err); } @@ -208,15 +209,15 @@ module.exports.update = (id, updates, callback) => { return callback(err); } if (!result) { - return callback('Incorrect current password'); + return callback(_('Incorrect current password')); } if (!updates.password) { - return callback(new Error('New password not set')); + return callback(new Error(_('New password not set'))); } if (updates.password !== updates.password2) { - return callback(new Error('Passwords do not match')); + return callback(new Error(_('Passwords do not match'))); } bcrypt.hash(updates.password, null, null, (err, hash) => { @@ -254,7 +255,7 @@ module.exports.resetToken = (id, callback) => { id = Number(id) || 0; if (!id) { - return callback(new Error('User ID not set')); + return callback(new Error(_('User ID not set'))); } db.getConnection((err, connection) => { @@ -282,7 +283,7 @@ module.exports.sendReset = (username, callback) => { username = (username || '').toString().trim(); if (!username) { - return callback(new Error('Username must be set')); + return callback(new Error(_('Username must be set'))); } db.getConnection((err, connection) => { @@ -319,7 +320,7 @@ module.exports.sendReset = (username, callback) => { to: { address: rows[0].email }, - subject: 'Mailer password change request' + subject: _('Mailer password change request') }, { html: 'emails/password-reset-html.hbs', text: 'emails/password-reset-text.hbs', @@ -343,7 +344,7 @@ module.exports.sendReset = (username, callback) => { module.exports.checkResetToken = (username, resetToken, callback) => { if (!username || !resetToken) { - return callback(new Error('Missing username or reset token')); + return callback(new Error(_('Missing username or reset token'))); } db.getConnection((err, connection) => { if (err) { @@ -363,11 +364,11 @@ module.exports.resetPassword = (data, callback) => { let updates = tools.convertKeys(data); if (!updates.username || !updates.resetToken) { - return callback(new Error('Missing username or reset token')); + return callback(new Error(_('Missing username or reset token'))); } if (!updates.password || !updates.password2 || updates.password !== updates.password2) { - return callback(new Error('Invalid new password')); + return callback(new Error(_('Invalid new password'))); } bcrypt.hash(updates.password, null, null, (err, hash) => { diff --git a/lib/passport.js b/lib/passport.js index 0e69d628..15f069db 100644 --- a/lib/passport.js +++ b/lib/passport.js @@ -2,6 +2,8 @@ let config = require('config'); let log = require('npmlog'); +let _ = require('./translate')._; +let util = require('util'); let passport = require('passport'); let LocalStrategy = require('passport-local').Strategy; @@ -33,7 +35,7 @@ module.exports.setup = app => { module.exports.logout = (req, res) => { if (req.user) { - req.flash('info', req.user.username + ' logged out'); + req.flash('info', util.format(_('%s logged out'), req.user.username)); req.logout(); } res.redirect('/'); @@ -46,7 +48,7 @@ module.exports.login = (req, res, next) => { return next(err); } if (!user) { - req.flash('danger', info && info.message || 'Failed to authenticate user'); + req.flash('danger', info && info.message || _('Failed to authenticate user')); return res.redirect('/users/login' + (req.body.next ? '?next=' + encodeURIComponent(req.body.next) : '')); } req.logIn(user, err => { @@ -62,7 +64,7 @@ module.exports.login = (req, res, next) => { req.session.cookie.expires = false; } - req.flash('success', 'Logged in as ' + user.username); + req.flash('success', util.format(_('Logged in as %s'), user.username)); return res.redirect(req.body.next || '/'); }); })(req, res, next); @@ -120,7 +122,7 @@ if (config.ldap.enabled && LdapStrategy) { if (!user) { return done(null, false, { - message: 'Incorrect username or password' + message: _('Incorrect username or password') }); } diff --git a/lib/tools.js b/lib/tools.js index 41c8b782..5a7988bd 100644 --- a/lib/tools.js +++ b/lib/tools.js @@ -6,6 +6,8 @@ let Isemail = require('isemail'); let urllib = require('url'); let juice = require('juice'); let jsdom = require('jsdom'); +let _ = require('./translate')._; +let util = require('util'); let blockedUsers = ['abuse', 'admin', 'billing', 'compliance', 'devnull', 'dns', 'ftp', 'hostmaster', 'inoc', 'ispfeedback', 'ispsupport', 'listrequest', 'list', 'maildaemon', 'noc', 'noreply', 'noreply', 'null', 'phish', 'phishing', 'postmaster', 'privacy', 'registrar', 'root', 'security', 'spam', 'support', 'sysadmin', 'tech', 'undisclosedrecipients', 'unsubscribe', 'usenet', 'uucp', 'webmaster', 'www']; @@ -106,19 +108,19 @@ function updateMenu(res) { } res.locals.menu.push({ - title: 'Lists', + title: _('Lists'), url: '/lists', key: 'lists' }, { - title: 'Templates', + title: _('Templates'), url: '/templates', key: 'templates' }, { - title: 'Campaigns', + title: _('Campaigns'), url: '/campaigns', key: 'campaigns' }, { - title: 'Automation', + title: _('Automation'), url: '/triggers', key: 'triggers' }); @@ -128,7 +130,7 @@ function validateEmail(address, checkBlocked, callback) { let user = (address || '').toString().split('@').shift().toLowerCase().replace(/[^a-z0-9]/g, ''); if (checkBlocked && blockedUsers.indexOf(user) >= 0) { - return callback(new Error('Blocked email address "' + address + '"')); + return callback(new Error(util.format(_('Blocked email address "%s"'), address))); } Isemail.validate(address, { @@ -137,16 +139,16 @@ function validateEmail(address, checkBlocked, callback) { }, result => { if (result !== 0) { - let message = 'Invalid email address "' + address + '"'; + let message = util.format(_('Invalid email address "%s".'), address); switch (result) { case 5: - message += '. MX record not found for domain'; + message += ' ' + _('MX record not found for domain'); break; case 6: - message += '. Address domain not found'; + message += ' ' + _('Address domain not found'); break; case 12: - message += '. Address domain name is required'; + message += ' ' + _('Address domain name is required'); break; } return callback(new Error(message)); diff --git a/lib/translate.js b/lib/translate.js index c6f44995..9abd9a6f 100644 --- a/lib/translate.js +++ b/lib/translate.js @@ -8,6 +8,7 @@ const fs = require('fs'); const path = require('path'); const log = require('npmlog'); const gettextParser = require('gettext-parser'); +const fakelang = require('./fakelang'); const language = config.language || 'en'; @@ -31,5 +32,10 @@ module.exports._ = str => { if (typeof str !== 'string') { str = String(str); } + + if (language === 'zz') { + return fakelang(str); + } + return gt.dgettext(language, str); }; diff --git a/package.json b/package.json index 79d65700..c4b8d02b 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "node": ">=5.0.0" }, "devDependencies": { + "eslint-config-nodemailer": "^1.0.0", "grunt": "^1.0.1", "grunt-cli": "^1.2.0", "grunt-contrib-nodeunit": "^1.0.0", @@ -33,9 +34,9 @@ "jsxgettext-andris": "^0.9.0-patch.1" }, "dependencies": { - "aws-sdk": "^2.22.0", + "aws-sdk": "^2.23.0", "bcrypt-nodejs": "0.0.3", - "body-parser": "^1.17.0", + "body-parser": "^1.17.1", "bounce-handler": "^7.3.2-fork.2", "compression": "^1.6.2", "config": "^1.25.1", @@ -46,7 +47,7 @@ "csv-generate": "^1.0.0", "csv-parse": "^1.2.0", "escape-html": "^1.0.3", - "express": "^4.15.0", + "express": "^4.15.2", "express-session": "^1.15.1", "faker": "^4.1.0", "feedparser": "^2.1.0", @@ -71,14 +72,14 @@ "nodemailer": "^3.1.4", "nodemailer-openpgp": "^1.0.2", "npmlog": "^4.0.2", - "openpgp": "^2.3.8", + "openpgp": "^2.4.0", "passport": "^0.3.2", "passport-local": "^1.0.0", "redfour": "^1.0.0", "redis": "^2.6.5", "request": "^2.80.0", "serve-favicon": "^2.4.1", - "shortid": "^2.2.6", + "shortid": "^2.2.8", "slugify": "^1.1.0", "smtp-server": "^2.0.2", "striptags": "^3.0.1", diff --git a/routes/archive.js b/routes/archive.js index a4356462..c0c0245b 100644 --- a/routes/archive.js +++ b/routes/archive.js @@ -11,6 +11,8 @@ let request = require('request'); let router = new express.Router(); let passport = require('../lib/passport'); let marked = require('marked'); +let _ = require('../lib/translate')._; +let util = require('util'); router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, next) => { settings.get('serviceUrl', (err, serviceUrl) => { @@ -26,7 +28,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, } if (!campaign) { - err = new Error('Not Found'); + err = new Error(_('Not Found')); err.status = 404; return next(err); } @@ -38,7 +40,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, } if (!list) { - err = new Error('Not Found'); + err = new Error(_('Not Found')); err.status = 404; return next(err); } @@ -50,7 +52,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, } if (!subscription) { - err = new Error('Not Found'); + err = new Error(_('Not Found')); err.status = 404; return next(err); } @@ -105,7 +107,7 @@ router.get('/:campaign/:list/:subscription', passport.csrfProtection, (req, res, return next(err); } if (httpResponse.statusCode !== 200) { - return next(new Error('Received status code ' + httpResponse.statusCode + ' from ' + campaign.sourceUrl)); + return next(new Error(util.format(_('Received status code %s from %s'), httpResponse.statusCode, campaign.sourceUrl))); } renderAndShow(body && body.toString(), false); }); @@ -129,7 +131,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection, let url = '/archive/' + encodeURIComponent(req.body.campaign || '') + '/' + encodeURIComponent(req.body.list || '') + '/' + encodeURIComponent(req.body.subscription || ''); campaigns.getByCid(req.body.campaign, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect(url); } campaigns.getAttachment(campaign.id, Number(req.body.attachment), (err, attachment) => { @@ -137,7 +139,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection, req.flash('danger', err && err.message || err); return res.redirect(url); } else if (!attachment) { - req.flash('warning', 'Attachment not found'); + req.flash('warning', _('Attachment not found')); return res.redirect(url); } diff --git a/routes/campaigns.js b/routes/campaigns.js index ed6ae632..4ee1958a 100644 --- a/routes/campaigns.js +++ b/routes/campaigns.js @@ -14,6 +14,8 @@ let striptags = require('striptags'); let passport = require('../lib/passport'); let htmlescape = require('escape-html'); let multer = require('multer'); +let _ = require('../lib/translate')._; +let util = require('util'); let uploadStorage = multer.memoryStorage(); let uploads = multer({ storage: uploadStorage @@ -21,7 +23,7 @@ let uploads = multer({ 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('campaigns'); @@ -30,7 +32,7 @@ router.all('/*', (req, res, next) => { router.get('/', (req, res) => { res.render('campaigns/campaigns', { - title: 'Campaigns' + title: _('Campaigns') }); }); @@ -112,13 +114,13 @@ router.get('/create', passport.csrfProtection, (req, res) => { router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => { campaigns.create(req.body, false, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create campaign'); + req.flash('danger', err && err.message || err || _('Could not create campaign')); return res.redirect('/campaigns/create?' + tools.queryParams(req.body)); } - req.flash('success', 'Campaign “' + req.body.name + '” created'); - res.redirect((req.body.type === 'rss') - ? '/campaigns/edit/' + id - : '/campaigns/edit/' + id + '?tab=template' + req.flash('success', util.format(_('Campaign “%s” created'), req.body.name)); + res.redirect((req.body.type === 'rss') ? + '/campaigns/edit/' + id : + '/campaigns/edit/' + id + '?tab=template' ); }); }); @@ -126,7 +128,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) = router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { campaigns.get(req.params.id, false, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } @@ -199,7 +201,7 @@ router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { campaign.mergeTags = defaultMergeTags.concat(listMergeTags); campaign.type === 2 && campaign.mergeTags.push({ key: 'RSS_ENTRY', - value: 'content from an RSS entry' + value: _('content from an RSS entry') }); res.render(view, campaign); }); @@ -215,9 +217,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => if (err) { req.flash('danger', err.message || err); } else if (updated) { - req.flash('success', 'Campaign settings updated'); + req.flash('success', _('Campaign settings updated')); } else { - req.flash('info', 'Campaign settings not updated'); + req.flash('info', _('Campaign settings not updated')); } if (req.body.id) { @@ -233,9 +235,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) = if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Campaign deleted'); + req.flash('success', _('Campaign deleted')); } else { - req.flash('info', 'Could not delete specified campaign'); + req.flash('info', _('Could not delete specified campaign')); } return res.redirect('/campaigns'); @@ -254,22 +256,22 @@ router.post('/ajax', (req, res) => { let getStatusText = data => { switch (data.status) { case 1: - return 'Idling'; + return _('Idling'); case 2: if (data.scheduled && data.scheduled > new Date()) { - return 'Scheduled'; + return _('Scheduled'); } - return ' Sending…'; + return ' ' + _('Sending') + '…'; case 3: - return 'Finished'; + return _('Finished'); case 4: - return 'Paused'; + return _('Paused'); case 5: - return 'Inactive'; + return _('Inactive'); case 6: - return 'Active'; + return _('Active'); } - return 'Other'; + return _('Other'); }; res.json({ @@ -282,7 +284,7 @@ router.post('/ajax', (req, res) => { htmlescape(striptags(row.description) || ''), getStatusText(row), '' + row.created.toISOString() + '' - ].concat('Edit')) + ].concat('' + _('Edit') + '')) }); }); }); @@ -290,7 +292,7 @@ router.post('/ajax', (req, res) => { router.get('/view/:id', passport.csrfProtection, (req, res) => { campaigns.get(req.params.id, true, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } @@ -385,7 +387,7 @@ router.post('/preview/:id', passport.parseForm, passport.csrfProtection, (req, r router.get('/opened/:id', passport.csrfProtection, (req, res) => { campaigns.get(req.params.id, true, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } @@ -424,13 +426,13 @@ router.get('/status/:id/:status', passport.csrfProtection, (req, res) => { status = 4; break; default: - req.flash('danger', 'Unknown status selector'); + req.flash('danger', _('Unknown status selector')); return res.redirect('/campaigns'); } campaigns.get(id, true, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } @@ -470,7 +472,7 @@ router.get('/status/:id/:status', passport.csrfProtection, (req, res) => { router.get('/clicked/:id/:linkId', passport.csrfProtection, (req, res) => { campaigns.get(req.params.id, true, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } @@ -536,7 +538,7 @@ router.post('/clicked/ajax/:id/:linkId', (req, res) => { campaigns.get(req.params.id, true, (err, campaign) => { if (err || !campaign) { return res.json({ - error: err && err.message || err || 'Campaign not found', + error: err && err.message || err || _('Campaign not found'), data: [] }); } @@ -571,7 +573,7 @@ router.post('/clicked/ajax/:id/:linkId', (req, res) => { htmlescape(row.lastName || ''), row.created && row.created.toISOString ? '' + row.created.toISOString() + '' : 'N/A', row.count, - 'Edit' + '' + _('Edit') + '' ]) }); }); @@ -585,7 +587,7 @@ router.post('/status/ajax/:id/:status', (req, res) => { campaigns.get(req.params.id, true, (err, campaign) => { if (err || !campaign) { return res.json({ - error: err && err.message || err || 'Campaign not found', + error: err && err.message || err || _('Campaign not found'), data: [] }); } @@ -621,7 +623,7 @@ router.post('/status/ajax/:id/:status', (req, res) => { htmlescape(row.lastName || ''), htmlescape(row.response || ''), row.updated && row.created.toISOString ? '' + row.updated.toISOString() + '' : 'N/A', - 'Edit' + '' + _('Edit') + '' ]) }); }); @@ -634,9 +636,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) = if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Campaign deleted'); + req.flash('success', _('Campaign deleted')); } else { - req.flash('info', 'Could not delete specified campaign'); + req.flash('info', _('Could not delete specified campaign')); } return res.redirect('/campaigns'); @@ -652,9 +654,9 @@ router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) => if (err) { req.flash('danger', err && err.message || err); } else if (scheduled) { - req.flash('success', 'Scheduled sending'); + req.flash('success', _('Scheduled sending')); } else { - req.flash('info', 'Could not schedule sending'); + req.flash('info', _('Could not schedule sending')); } return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); @@ -666,9 +668,9 @@ router.post('/resume', passport.parseForm, passport.csrfProtection, (req, res) = if (err) { req.flash('danger', err && err.message || err); } else if (scheduled) { - req.flash('success', 'Sending resumed'); + req.flash('success', _('Sending resumed')); } else { - req.flash('info', 'Could not resume sending'); + req.flash('info', _('Could not resume sending')); } return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); @@ -680,9 +682,9 @@ router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => if (err) { req.flash('danger', err && err.message || err); } else if (reset) { - req.flash('success', 'Sending reset'); + req.flash('success', _('Sending reset')); } else { - req.flash('info', 'Could not reset sending'); + req.flash('info', _('Could not reset sending')); } return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); @@ -694,9 +696,9 @@ router.post('/pause', passport.parseForm, passport.csrfProtection, (req, res) => if (err) { req.flash('danger', err && err.message || err); } else if (reset) { - req.flash('success', 'Sending paused'); + req.flash('success', _('Sending paused')); } else { - req.flash('info', 'Could not pause sending'); + req.flash('info', _('Could not pause sending')); } return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); @@ -708,9 +710,9 @@ router.post('/activate', passport.parseForm, passport.csrfProtection, (req, res) if (err) { req.flash('danger', err && err.message || err); } else if (reset) { - req.flash('success', 'Sending activated'); + req.flash('success', _('Sending activated')); } else { - req.flash('info', 'Could not activate sending'); + req.flash('info', _('Could not activate sending')); } return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); @@ -722,9 +724,9 @@ router.post('/inactivate', passport.parseForm, passport.csrfProtection, (req, re if (err) { req.flash('danger', err && err.message || err); } else if (reset) { - req.flash('success', 'Sending paused'); + req.flash('success', _('Sending paused')); } else { - req.flash('info', 'Could not pause sending'); + req.flash('info', _('Could not pause sending')); } return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id)); @@ -734,7 +736,7 @@ router.post('/inactivate', passport.parseForm, passport.csrfProtection, (req, re router.post('/attachment', uploads.single('attachment'), passport.parseForm, passport.csrfProtection, (req, res) => { campaigns.get(req.body.id, false, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } campaigns.addAttachment(campaign.id, { @@ -745,9 +747,9 @@ router.post('/attachment', uploads.single('attachment'), passport.parseForm, pas if (err) { req.flash('danger', err && err.message || err); } else if (attachmentId) { - req.flash('success', 'Attachment uploaded'); + req.flash('success', _('Attachment uploaded')); } else { - req.flash('info', 'Could not store attachment'); + req.flash('info', _('Could not store attachment')); } return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments'); }); @@ -757,16 +759,16 @@ router.post('/attachment', uploads.single('attachment'), passport.parseForm, pas router.post('/attachment/delete', passport.parseForm, passport.csrfProtection, (req, res) => { campaigns.get(req.body.id, false, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } campaigns.deleteAttachment(campaign.id, Number(req.body.attachment), (err, deleted) => { if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Attachment deleted'); + req.flash('success', _('Attachment deleted')); } else { - req.flash('info', 'Could not delete attachment'); + req.flash('info', _('Could not delete attachment')); } return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments'); }); @@ -776,7 +778,7 @@ router.post('/attachment/delete', passport.parseForm, passport.csrfProtection, ( router.post('/attachment/download', passport.parseForm, passport.csrfProtection, (req, res) => { campaigns.get(req.body.id, false, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } campaigns.getAttachment(campaign.id, Number(req.body.attachment), (err, attachment) => { @@ -784,7 +786,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection, req.flash('danger', err && err.message || err); return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments'); } else if (!attachment) { - req.flash('warning', 'Attachment not found'); + req.flash('warning', _('Attachment not found')); return res.redirect('/campaigns/edit/' + campaign.id + '?tab=attachments'); } @@ -798,7 +800,7 @@ router.post('/attachment/download', passport.parseForm, passport.csrfProtection, router.get('/attachment/:campaign', passport.csrfProtection, (req, res) => { campaigns.get(req.params.campaign, false, (err, campaign) => { if (err || !campaign) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } campaign.csrfToken = req.csrfToken(); diff --git a/routes/fields.js b/routes/fields.js index 593e4380..2ce557da 100644 --- a/routes/fields.js +++ b/routes/fields.js @@ -6,10 +6,11 @@ let lists = require('../lib/models/lists'); let fields = require('../lib/models/fields'); let tools = require('../lib/tools'); let passport = require('../lib/passport'); +let _ = require('../lib/translate')._; 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('lists'); @@ -24,7 +25,7 @@ router.get('/:list', (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -60,7 +61,7 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -98,7 +99,7 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => { router.post('/:list/create', passport.parseForm, passport.csrfProtection, (req, res) => { fields.create(req.params.list, req.body, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create custom field'); + req.flash('danger', err && err.message || err || _('Could not create custom field')); return res.redirect('/fields/' + encodeURIComponent(req.params.list) + '/create?' + tools.queryParams(req.body)); } req.flash('success', 'Custom field created'); @@ -114,7 +115,7 @@ router.get('/:list/edit/:field', passport.csrfProtection, (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -125,7 +126,7 @@ router.get('/:list/edit/:field', passport.csrfProtection, (req, res) => { } if (!field) { - req.flash('danger', 'Selected field not found'); + req.flash('danger', _('Selected field not found')); return res.redirect('/fields/' + encodeURIComponent(req.params.list)); } @@ -161,9 +162,9 @@ router.post('/:list/edit', passport.parseForm, passport.csrfProtection, (req, re if (err) { req.flash('danger', err.message || err); } else if (updated) { - req.flash('success', 'Field settings updated'); + req.flash('success', _('Field settings updated')); } else { - req.flash('info', 'Field settings not updated'); + req.flash('info', _('Field settings not updated')); } if (req.body.id) { @@ -179,9 +180,9 @@ router.post('/:list/delete', passport.parseForm, passport.csrfProtection, (req, if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Custom field deleted'); + req.flash('success', _('Custom field deleted')); } else { - req.flash('info', 'Could not delete specified field'); + req.flash('info', _('Could not delete specified field')); } return res.redirect('/fields/' + encodeURIComponent(req.params.list)); diff --git a/routes/links.js b/routes/links.js index b675c131..5da3ef43 100644 --- a/routes/links.js +++ b/routes/links.js @@ -5,6 +5,7 @@ let settings = require('../lib/models/settings'); let lists = require('../lib/models/lists'); let subscriptions = require('../lib/models/subscriptions'); let tools = require('../lib/tools'); +let _ = require('../lib/translate')._; let log = require('npmlog'); let express = require('express'); @@ -36,7 +37,7 @@ router.get('/:campaign/:list/:subscription/:link', (req, res) => { res.status(404); return res.render('archive/view', { layout: 'archive/layout', - message: 'Oops, we couldn\'t find a link for the URL you clicked', + message: _('Oops, we couldn\'t find a link for the URL you clicked'), campaign: { subject: 'Error 404' } diff --git a/routes/lists.js b/routes/lists.js index 8fd54dd6..91e39123 100644 --- a/routes/lists.js +++ b/routes/lists.js @@ -17,6 +17,8 @@ let humanize = require('humanize'); let mkdirp = require('mkdirp'); let pathlib = require('path'); let log = require('npmlog'); +let _ = require('../lib/translate')._; +let util = require('util'); let uploadStorage = multer.diskStorage({ destination: (req, file, callback) => { @@ -44,7 +46,7 @@ let moment = require('moment-timezone'); 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('lists'); @@ -85,10 +87,10 @@ router.get('/create', passport.csrfProtection, (req, res) => { router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => { lists.create(req.body, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create list'); + req.flash('danger', err && err.message || err || _('Could not create list')); return res.redirect('/lists/create?' + tools.queryParams(req.body)); } - req.flash('success', 'List created'); + req.flash('success', _('List created')); res.redirect('/lists/view/' + id); }); }); @@ -96,7 +98,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) = router.get('/edit/:id', passport.csrfProtection, (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } list.csrfToken = req.csrfToken(); @@ -110,9 +112,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => if (err) { req.flash('danger', err.message || err); } else if (updated) { - req.flash('success', 'List settings updated'); + req.flash('success', _('List settings updated')); } else { - req.flash('info', 'List settings not updated'); + req.flash('info', _('List settings not updated')); } if (req.body.id) { @@ -128,9 +130,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) = if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'List deleted'); + req.flash('success', _('List deleted')); } else { - req.flash('info', 'Could not delete specified list'); + req.flash('info', _('Could not delete specified list')); } return res.redirect('/lists'); @@ -141,7 +143,7 @@ router.post('/ajax/:id', (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { return res.json({ - error: err && err.message || err || 'List not found', + error: err && err.message || err || _('List not found'), data: [] }); } @@ -166,7 +168,7 @@ router.post('/ajax/:id', (req, res) => { row.customFields = fields.getRow(fieldList, row); }); - let statuses = ['Unknown', 'Subscribed', 'Unsubscribed', 'Bounced', 'Complained']; + let statuses = [_('Unknown'), _('Subscribed'), _('Unsubscribed'), _('Bounced'), _('Complained')]; res.json({ draw: req.body.draw, @@ -197,11 +199,11 @@ router.post('/ajax/:id', (req, res) => { let key = keys[i]; switch (key.verifyPrimaryKey()) { case 0: - return 'Invalid key'; + return _('Invalid key'); case 1: - return 'Expired key'; + return _('Expired key'); case 2: - return 'Revoked key'; + return _('Revoked key'); } } @@ -217,7 +219,7 @@ router.post('/ajax/:id', (req, res) => { } else { return htmlescape(cRow.value || ''); } - })).concat(statuses[row.status]).concat(row.created && row.created.toISOString ? '' + row.created.toISOString() + '' : 'N/A').concat('Edit')) + })).concat(statuses[row.status]).concat(row.created && row.created.toISOString ? '' + row.created.toISOString() + '' : 'N/A').concat('' + _('Edit') + '')) }); }); }); @@ -231,7 +233,7 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } @@ -248,22 +250,22 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => { list.imports = imports.map((entry, i) => { entry.index = i + 1; - entry.importType = entry.type === 1 ? 'Subscribe' : 'Unsubscribe'; + entry.importType = entry.type === 1 ? _('Subscribe') : _('Unsubscribe'); switch (entry.status) { case 0: - entry.importStatus = 'Initializing'; + entry.importStatus = _('Initializing'); break; case 1: - entry.importStatus = 'Initialized'; + entry.importStatus = _('Initialized'); break; case 2: - entry.importStatus = 'Importing...'; + entry.importStatus = _('Importing') + '…'; break; case 3: - entry.importStatus = 'Finished'; + entry.importStatus = _('Finished'); break; default: - entry.importStatus = 'Errored' + (entry.error ? ' (' + entry.error + ')' : ''); + entry.importStatus = _('Errored') + (entry.error ? ' (' + entry.error + ')' : ''); entry.error = true; } entry.created = entry.created && entry.created.toISOString(); @@ -296,7 +298,7 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => { router.get('/subscription/:id/add', passport.csrfProtection, (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } @@ -335,13 +337,13 @@ router.get('/subscription/:id/add', passport.csrfProtection, (req, res) => { router.get('/subscription/:id/edit/:cid', passport.csrfProtection, (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } subscriptions.get(list.id, req.params.cid, (err, subscription) => { if (err || !subscription) { - req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find subscriber with specified ID')); return res.redirect('/lists/view/' + req.params.id); } @@ -387,14 +389,14 @@ router.get('/subscription/:id/edit/:cid', passport.csrfProtection, (req, res) => router.post('/subscription/add', passport.parseForm, passport.csrfProtection, (req, res) => { subscriptions.insert(req.body.list, false, req.body, (err, response) => { if (err) { - req.flash('danger', err && err.message || err || 'Could not add subscription'); + req.flash('danger', err && err.message || err || _('Could not add subscription')); return res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/add?' + tools.queryParams(req.body)); } if (response.entryId) { - req.flash('success', req.body.email + ' was successfully added to your list'); + req.flash('success', util.format(_('%s was successfully added to your list'), req.body.email)); } else { - req.flash('warning', req.body.email + ' was not added to your list'); + req.flash('warning', util.format(_('%s was not added to your list'), req.body.email)); } res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/add'); @@ -404,22 +406,22 @@ router.post('/subscription/add', passport.parseForm, passport.csrfProtection, (r router.post('/subscription/unsubscribe', passport.parseForm, passport.csrfProtection, (req, res) => { lists.get(req.body.list, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } subscriptions.get(list.id, req.body.cid, (err, subscription) => { if (err || !subscription) { - req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find subscriber with specified ID')); return res.redirect('/lists/view/' + list.id); } subscriptions.unsubscribe(list.id, subscription.email, false, err => { if (err) { - req.flash('danger', err && err.message || err || 'Could not unsubscribe user'); + req.flash('danger', err && err.message || err || _('Could not unsubscribe user')); return res.redirect('/lists/subscription/' + list.id + '/edit/' + subscription.cid); } - req.flash('success', subscription.email + ' was successfully unsubscribed from your list'); + req.flash('success', util.format(_('%s was successfully unsubscribed from your list'), subscription.email)); res.redirect('/lists/view/' + list.id); }); }); @@ -429,17 +431,17 @@ router.post('/subscription/unsubscribe', passport.parseForm, passport.csrfProtec router.post('/subscription/delete', passport.parseForm, passport.csrfProtection, (req, res) => { lists.get(req.body.list, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } subscriptions.delete(list.id, req.body.cid, (err, email) => { if (err || !email) { - req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find subscriber with specified ID')); return res.redirect('/lists/view/' + list.id); } - req.flash('success', email + ' was successfully removed from your list'); + req.flash('success', util.format(_('%s was successfully removed from your list'), email)); res.redirect('/lists/view/' + list.id); }); }); @@ -451,16 +453,16 @@ router.post('/subscription/edit', passport.parseForm, passport.csrfProtection, ( if (err) { if (err.code === 'ER_DUP_ENTRY') { - req.flash('danger', 'Another subscriber with email address ' + req.body.email + ' already exists'); + req.flash('danger', util.format(_('Another subscriber with email address %s already exists'), req.body.email)); return res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/edit/' + req.body.cid); } else { req.flash('danger', err.message || err); } } else if (updated) { - req.flash('success', 'Subscription settings updated'); + req.flash('success', _('Subscription settings updated')); } else { - req.flash('info', 'Subscription settings not updated'); + req.flash('info', _('Subscription settings not updated')); } if (req.body.list) { @@ -474,7 +476,7 @@ router.post('/subscription/edit', passport.parseForm, passport.csrfProtection, ( router.get('/subscription/:id/import', passport.csrfProtection, (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } @@ -496,13 +498,13 @@ router.get('/subscription/:id/import', passport.csrfProtection, (req, res) => { router.get('/subscription/:id/import/:importId', passport.csrfProtection, (req, res) => { lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } subscriptions.getImport(req.params.id, req.params.importId, (err, data) => { if (err || !data) { - req.flash('danger', err && err.message || err || 'Could not find import data with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find import data with specified ID')); return res.redirect('/lists'); } @@ -525,7 +527,7 @@ router.get('/subscription/:id/import/:importId', passport.csrfProtection, (req, router.post('/subscription/import', uploads.single('listimport'), passport.parseForm, passport.csrfProtection, (req, res) => { lists.get(req.body.list, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } @@ -533,7 +535,7 @@ router.post('/subscription/import', uploads.single('listimport'), passport.parse getPreview(req.file.path, req.file.size, delimiter, (err, rows) => { if (err) { - req.flash('danger', err && err.message || err || 'Could not process CSV'); + req.flash('danger', err && err.message || err || _('Could not process CSV')); return res.redirect('/lists'); } else { @@ -542,7 +544,7 @@ router.post('/subscription/import', uploads.single('listimport'), passport.parse example: rows[1] || [] }, (err, importId) => { if (err) { - req.flash('danger', err && err.message || err || 'Could not create importer'); + req.flash('danger', err && err.message || err || _('Could not create importer')); return res.redirect('/lists'); } @@ -593,7 +595,7 @@ function getPreview(path, size, delimiter, callback) { // just ignore }); if (!data || !data.length) { - return callback(null, new Error('Empty file')); + return callback(null, new Error(_('Empty file'))); } callback(err, data); }); @@ -604,13 +606,13 @@ function getPreview(path, size, delimiter, callback) { router.post('/subscription/import-confirm', passport.parseForm, passport.csrfProtection, (req, res) => { lists.get(req.body.list, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } subscriptions.getImport(list.id, req.body.import, (err, data) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find import data with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find import data with specified ID')); return res.redirect('/lists'); } @@ -646,11 +648,11 @@ router.post('/subscription/import-confirm', passport.parseForm, passport.csrfPro mapping: JSON.stringify(data.mapping) }, (err, importer) => { if (err || !importer) { - req.flash('danger', err && err.message || err || 'Could not find import data with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find import data with specified ID')); return res.redirect('/lists'); } - req.flash('success', 'Import started'); + req.flash('success', _('Import started')); res.redirect('/lists/view/' + list.id + '?tab=imports'); }); }); @@ -661,7 +663,7 @@ router.post('/subscription/import-confirm', passport.parseForm, passport.csrfPro router.post('/subscription/import-restart', passport.parseForm, passport.csrfProtection, (req, res) => { lists.get(req.body.list, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } @@ -674,11 +676,11 @@ router.post('/subscription/import-restart', passport.parseForm, passport.csrfPro failed: 0 }, (err, importer) => { if (err || !importer) { - req.flash('danger', err && err.message || err || 'Could not find import data with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find import data with specified ID')); return res.redirect('/lists'); } - req.flash('success', 'Import restarted'); + req.flash('success', _('Import restarted')); res.redirect('/lists/view/' + list.id + '?tab=imports'); }); }); @@ -688,13 +690,13 @@ router.get('/subscription/:id/import/:importId/failed', (req, res) => { let start = 0; lists.get(req.params.id, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find list with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find list with specified ID')); return res.redirect('/lists'); } subscriptions.getImport(req.params.id, req.params.importId, (err, data) => { if (err || !data) { - req.flash('danger', err && err.message || err || 'Could not find import data with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find import data with specified ID')); return res.redirect('/lists'); } subscriptions.getFailedImports(req.params.importId, (err, rows) => { diff --git a/routes/segments.js b/routes/segments.js index c71a585f..92759598 100644 --- a/routes/segments.js +++ b/routes/segments.js @@ -6,10 +6,11 @@ let passport = require('../lib/passport'); let lists = require('../lib/models/lists'); let segments = require('../lib/models/segments'); let tools = require('../lib/tools'); +let _ = require('../lib/translate')._; 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('lists'); @@ -24,7 +25,7 @@ router.get('/:list', (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -55,7 +56,7 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -82,10 +83,10 @@ router.get('/:list/create', passport.csrfProtection, (req, res) => { router.post('/:list/create', passport.parseForm, passport.csrfProtection, (req, res) => { segments.create(req.params.list, req.body, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create segment'); + req.flash('danger', err && err.message || err || _('Could not create segment')); return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/create?' + tools.queryParams(req.body)); } - req.flash('success', 'Segment created'); + req.flash('success', _('Segment created')); res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + id); }); }); @@ -98,7 +99,7 @@ router.get('/:list/view/:id', (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -109,7 +110,7 @@ router.get('/:list/view/:id', (req, res) => { } if (!segment) { - req.flash('danger', 'Selected segment ID not found'); + req.flash('danger', _('Selected segment ID not found')); return res.redirect('/'); } @@ -147,7 +148,7 @@ router.get('/:list/edit/:segment', passport.csrfProtection, (req, res) => { } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -184,9 +185,9 @@ router.post('/:list/edit', passport.parseForm, passport.csrfProtection, (req, re if (err) { req.flash('danger', err.message || err); } else if (updated) { - req.flash('success', 'Segment settings updated'); + req.flash('success', _('Segment settings updated')); } else { - req.flash('info', 'Segment settings not updated'); + req.flash('info', _('Segment settings not updated')); } if (req.body.id) { @@ -202,9 +203,9 @@ router.post('/:list/delete', passport.parseForm, passport.csrfProtection, (req, if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Segment deleted'); + req.flash('success', _('Segment deleted')); } else { - req.flash('info', 'Could not delete specified segment'); + req.flash('info', _('Could not delete specified segment')); } return res.redirect('/segments/' + encodeURIComponent(req.params.list)); @@ -219,7 +220,7 @@ router.get('/:list/rules/:segment/create', passport.csrfProtection, (req, res) = } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -251,7 +252,7 @@ router.post('/:list/rules/:segment/next', passport.parseForm, passport.csrfProte } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -262,13 +263,13 @@ router.post('/:list/rules/:segment/next', passport.parseForm, passport.csrfProte } if (!segment) { - req.flash('danger', 'Selected segment not found'); + req.flash('danger', _('Selected segment not found')); return res.redirect('/segments/' + encodeURIComponent(req.params.list)); } let column = segment.columns.filter(column => column.column === req.body.column).pop(); if (!column) { - req.flash('danger', 'Invalid rule type'); + req.flash('danger', _('Invalid rule type')); return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/create?' + tools.queryParams(req.body)); } @@ -285,7 +286,7 @@ router.get('/:list/rules/:segment/configure', passport.csrfProtection, (req, res } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -296,13 +297,13 @@ router.get('/:list/rules/:segment/configure', passport.csrfProtection, (req, res } if (!segment) { - req.flash('danger', 'Selected segment not found'); + req.flash('danger', _('Selected segment not found')); return res.redirect('/segments/' + encodeURIComponent(req.params.list)); } let column = segment.columns.filter(column => column.column === req.query.column).pop(); if (!column) { - req.flash('danger', 'Invalid rule type'); + req.flash('danger', _('Invalid rule type')); return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/create?' + tools.queryParams(req.body)); } @@ -332,16 +333,16 @@ router.post('/:list/rules/:segment/create', passport.parseForm, passport.csrfPro } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } segments.createRule(req.params.segment, req.body, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create rule'); + req.flash('danger', err && err.message || err || _('Could not create rule')); return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + encodeURIComponent(req.params.segment) + '/configure?' + tools.queryParams(req.body)); } - req.flash('success', 'Rule created'); + req.flash('success', _('Rule created')); res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment)); }); }); @@ -355,7 +356,7 @@ router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, re } if (!list) { - req.flash('danger', 'Selected list ID not found'); + req.flash('danger', _('Selected list ID not found')); return res.redirect('/'); } @@ -366,7 +367,7 @@ router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, re } if (!segment) { - req.flash('danger', 'Selected segment not found'); + req.flash('danger', _('Selected segment not found')); return res.redirect('/segments/' + encodeURIComponent(req.params.list)); } @@ -377,13 +378,13 @@ router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, re } if (!segment) { - req.flash('danger', 'Selected segment not found'); + req.flash('danger', _('Selected segment not found')); return res.redirect('/segments/' + encodeURIComponent(req.params.list)); } let column = segment.columns.filter(column => column.column === rule.column).pop(); if (!column) { - req.flash('danger', 'Invalid rule type'); + req.flash('danger', _('Invalid rule type')); return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + segment.id); } @@ -406,9 +407,9 @@ router.post('/:list/rules/:segment/edit', passport.parseForm, passport.csrfProte if (err) { req.flash('danger', err.message || err); } else if (updated) { - req.flash('success', 'Rule settings updated'); + req.flash('success', _('Rule settings updated')); } else { - req.flash('info', 'Rule settings not updated'); + req.flash('info', _('Rule settings not updated')); } if (req.params.segment) { @@ -424,9 +425,9 @@ router.post('/:list/rules/:segment/delete', passport.parseForm, passport.csrfPro if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Rule deleted'); + req.flash('success', _('Rule deleted')); } else { - req.flash('info', 'Could not delete specified rule'); + req.flash('info', _('Could not delete specified rule')); } return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment)); diff --git a/routes/subscription.js b/routes/subscription.js index c88582df..a552a3a6 100644 --- a/routes/subscription.js +++ b/routes/subscription.js @@ -13,11 +13,13 @@ let fields = require('../lib/models/fields'); let subscriptions = require('../lib/models/subscriptions'); let settings = require('../lib/models/settings'); let openpgp = require('openpgp'); +let _ = require('../lib/translate')._; +let util = require('util'); router.get('/subscribe/:cid', (req, res, next) => { subscriptions.subscribe(req.params.cid, req.ip, (err, subscription) => { if (!err && !subscription) { - err = new Error('Selected subscription not found'); + err = new Error(_('Selected subscription not found')); err.status = 404; } @@ -27,7 +29,7 @@ router.get('/subscribe/:cid', (req, res, next) => { lists.get(subscription.list, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -73,7 +75,7 @@ router.get('/subscribe/:cid', (req, res, next) => { name: [].concat(subscription.firstName || []).concat(subscription.lastName || []).join(' '), address: subscription.email }, - subject: list.name + ': Subscription Confirmed', + subject: util.format(_('%s: Subscription Confirmed'), list.name), encryptionKeys }, { html: 'emails/subscription-confirmed-html.hbs', @@ -98,7 +100,7 @@ router.get('/subscribe/:cid', (req, res, next) => { router.get('/:cid', passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.cid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -136,7 +138,7 @@ router.get('/:cid', passport.csrfProtection, (req, res, next) => { router.get('/:cid/confirm-notice', (req, res, next) => { lists.getByCid(req.params.cid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -161,7 +163,7 @@ router.get('/:cid/confirm-notice', (req, res, next) => { router.get('/:cid/updated-notice', (req, res, next) => { lists.getByCid(req.params.cid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -186,7 +188,7 @@ router.get('/:cid/updated-notice', (req, res, next) => { router.get('/:cid/unsubscribe-notice', (req, res, next) => { lists.getByCid(req.params.cid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -212,7 +214,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req let email = (req.body.email || '').toString().trim(); if (!email) { - req.flash('danger', 'Email address not set'); + req.flash('danger', _('Email address not set')); return res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '?' + tools.queryParams(req.body)); } @@ -227,7 +229,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req lists.getByCid(req.params.cid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -250,7 +252,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req subscriptions.addConfirmation(list, email, req.ip, data, (err, confirmCid) => { if (!err && !confirmCid) { - err = new Error('Could not store confirmation data'); + err = new Error(_('Could not store confirmation data')); } if (err) { req.flash('danger', err.message || err); @@ -265,7 +267,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.lcid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -279,7 +281,7 @@ router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => { } subscriptions.get(list.id, req.params.ucid, (err, subscription) => { if (!err && !subscription) { - err = new Error('Subscription not found from this list'); + err = new Error(_('Subscription not found from this list')); err.status = 404; } @@ -312,7 +314,7 @@ router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => { router.post('/:lcid/manage', passport.parseForm, passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.lcid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -334,7 +336,7 @@ router.post('/:lcid/manage', passport.parseForm, passport.csrfProtection, (req, router.get('/:lcid/manage-address/:ucid', passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.lcid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -344,7 +346,7 @@ router.get('/:lcid/manage-address/:ucid', passport.csrfProtection, (req, res, ne subscriptions.get(list.id, req.params.ucid, (err, subscription) => { if (!err && !subscription) { - err = new Error('Subscription not found from this list'); + err = new Error(_('Subscription not found from this list')); err.status = 404; } @@ -363,7 +365,7 @@ router.get('/:lcid/manage-address/:ucid', passport.csrfProtection, (req, res, ne router.post('/:lcid/manage-address', passport.parseForm, passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.lcid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -378,7 +380,7 @@ router.post('/:lcid/manage-address', passport.parseForm, passport.csrfProtection return res.redirect('/subscription/' + encodeURIComponent(req.params.lcid) + '/manage-address/' + encodeURIComponent(req.body.cid) + '?' + tools.queryParams(req.body)); } - req.flash('info', 'Email address updated, check your mailbox for verification instructions'); + req.flash('info', _('Email address updated, check your mailbox for verification instructions')); res.redirect('/subscription/' + req.params.lcid + '/manage/' + req.body.cid); }); }); @@ -387,7 +389,7 @@ router.post('/:lcid/manage-address', passport.parseForm, passport.csrfProtection router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.lcid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -397,7 +399,7 @@ router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next) subscriptions.get(list.id, req.params.ucid, (err, subscription) => { if (!err && !subscription) { - err = new Error('Subscription not found from this list'); + err = new Error(_('Subscription not found from this list')); err.status = 404; } @@ -419,7 +421,7 @@ router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next) router.post('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtection, (req, res, next) => { lists.getByCid(req.params.lcid, (err, list) => { if (!err && !list) { - err = new Error('Selected list not found'); + err = new Error(_('Selected list not found')); err.status = 404; } @@ -467,7 +469,7 @@ router.post('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtection, ( name: [].concat(subscription.firstName || []).concat(subscription.lastName || []).join(' '), address: subscription.email }, - subject: list.name + ': Subscription Confirmed', + subject: util.format(_('%s: Subscription Confirmed'), list.name), encryptionKeys }, { html: 'emails/unsubscribe-confirmed-html.hbs', @@ -494,7 +496,7 @@ router.post('/publickey', passport.parseForm, passport.csrfProtection, (req, res return next(err); } if (!configItems.pgpPrivateKey) { - err = new Error('Public key is not set'); + err = new Error(_('Public key is not set')); err.status = 404; return next(err); } @@ -510,7 +512,7 @@ router.post('/publickey', passport.parseForm, passport.csrfProtection, (req, res } if (!privKey) { - err = new Error('Public key is not set'); + err = new Error(_('Public key is not set')); err.status = 404; return next(err); } diff --git a/routes/templates.js b/routes/templates.js index 12cb1ec1..4b08ea86 100644 --- a/routes/templates.js +++ b/routes/templates.js @@ -10,10 +10,11 @@ let helpers = require('../lib/helpers'); let striptags = require('striptags'); let passport = require('../lib/passport'); let mailer = require('../lib/mailer'); +let _ = require('../lib/translate')._; 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('templates'); @@ -68,19 +69,19 @@ router.get('/create', passport.csrfProtection, (req, res, next) => { data.text = data.text || rendererText(configItems); data.disableWysiwyg = configItems.disableWysiwyg; - data.editors = config.editors || [['summernote', 'Summernote']]; + data.editors = config.editors || [ + ['summernote', 'Summernote'] + ]; data.editors = data.editors.map(ed => { let editor = { name: ed[0], - label: ed[1], + label: ed[1] }; if (config[editor.name] && config[editor.name].templates) { - editor.templates = config[editor.name].templates.map(tmpl => { - return { - name: tmpl[0], - label: tmpl[1], - } - }); + editor.templates = config[editor.name].templates.map(tmpl => ({ + name: tmpl[0], + label: tmpl[1] + })); } return editor; }); @@ -94,10 +95,10 @@ router.get('/create', passport.csrfProtection, (req, res, next) => { router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => { templates.create(req.body, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create template'); + req.flash('danger', err && err.message || err || _('Could not create template')); return res.redirect('/templates/create?' + tools.queryParams(req.body)); } - req.flash('success', 'Template created'); + req.flash('success', _('Template created')); res.redirect('/templates/edit/' + id); }); }); @@ -105,7 +106,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) = router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { templates.get(req.params.id, (err, template) => { if (err || !template) { - req.flash('danger', err && err.message || err || 'Could not find template with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find template with specified ID')); return res.redirect('/templates'); } settings.list(['disableWysiwyg'], (err, configItems) => { @@ -136,9 +137,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => if (err) { req.flash('danger', err.message || err); } else if (updated) { - req.flash('success', 'Template settings updated'); + req.flash('success', _('Template settings updated')); } else { - req.flash('info', 'Template settings not updated'); + req.flash('info', _('Template settings not updated')); } if (req.body.id) { @@ -154,9 +155,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) = if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Template deleted'); + req.flash('success', _('Template deleted')); } else { - req.flash('info', 'Could not delete specified template'); + req.flash('info', _('Could not delete specified template')); } return res.redirect('/templates'); diff --git a/routes/triggers.js b/routes/triggers.js index c83099f7..32b25fa5 100644 --- a/routes/triggers.js +++ b/routes/triggers.js @@ -10,10 +10,12 @@ let striptags = require('striptags'); let passport = require('../lib/passport'); let tools = require('../lib/tools'); let htmlescape = require('escape-html'); +let _ = require('../lib/translate')._; +let util = require('util'); 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('triggers'); @@ -57,7 +59,7 @@ router.get('/create-select', passport.csrfProtection, (req, res, next) => { router.post('/create-select', passport.parseForm, passport.csrfProtection, (req, res) => { if (!req.body.list) { - req.flash('danger', 'Could not find selected list'); + req.flash('danger', _('Could not find selected list')); return res.redirect('/triggers/create-select'); } res.redirect('/triggers/' + encodeURIComponent(req.body.list) + '/create'); @@ -74,7 +76,7 @@ router.get('/:listId/create', passport.csrfProtection, (req, res, next) => { lists.get(req.params.listId, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find selected list'); + req.flash('danger', err && err.message || err || _('Could not find selected list')); return res.redirect('/triggers/create-select'); } fields.list(list.id, (err, fieldList) => { @@ -126,14 +128,14 @@ router.get('/:listId/create', passport.csrfProtection, (req, res, next) => { router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => { triggers.create(req.body, (err, id) => { if (err || !id) { - req.flash('danger', err && err.message || err || 'Could not create trigger'); + req.flash('danger', err && err.message || err || _('Could not create trigger')); if (req.body.list) { return res.redirect('/triggers/' + encodeURIComponent(req.body.list) + '/create?' + tools.queryParams(req.body)); } else { return res.redirect('/triggers'); } } - req.flash('success', 'Trigger “' + req.body.name + '” created'); + req.flash('success', util.format(_('Trigger “%s” created'), req.body.name)); res.redirect('/triggers'); }); }); @@ -141,7 +143,7 @@ router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) = router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { triggers.get(req.params.id, (err, trigger) => { if (err || !trigger) { - req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find campaign with specified ID')); return res.redirect('/campaigns'); } trigger.csrfToken = req.csrfToken(); @@ -149,7 +151,7 @@ router.get('/edit/:id', passport.csrfProtection, (req, res, next) => { lists.get(trigger.list, (err, list) => { if (err || !list) { - req.flash('danger', err && err.message || err || 'Could not find selected list'); + req.flash('danger', err && err.message || err || _('Could not find selected list')); return res.redirect('/triggers'); } fields.list(list.id, (err, fieldList) => { @@ -209,9 +211,9 @@ router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => req.flash('danger', err.message || err); return res.redirect('/triggers/edit/' + encodeURIComponent(req.body.id)); } else if (updated) { - req.flash('success', 'Trigger settings updated'); + req.flash('success', _('Trigger settings updated')); } else { - req.flash('info', 'Trigger settings not updated'); + req.flash('info', _('Trigger settings not updated')); } return res.redirect('/triggers'); @@ -223,9 +225,9 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) = if (err) { req.flash('danger', err && err.message || err); } else if (deleted) { - req.flash('success', 'Trigger deleted'); + req.flash('success', _('Trigger deleted')); } else { - req.flash('info', 'Could not delete specified trigger'); + req.flash('info', _('Could not delete specified trigger')); } return res.redirect('/triggers'); @@ -237,7 +239,7 @@ router.get('/status/:id', passport.csrfProtection, (req, res) => { triggers.get(id, (err, trigger) => { if (err || !trigger) { - req.flash('danger', err && err.message || err || 'Could not find trigger with specified ID'); + req.flash('danger', err && err.message || err || _('Could not find trigger with specified ID')); return res.redirect('/triggers'); } @@ -250,7 +252,7 @@ router.post('/status/ajax/:id', (req, res) => { triggers.get(req.params.id, (err, trigger) => { if (err || !trigger) { return res.json({ - error: err && err.message || err || 'Trigger not found', + error: err && err.message || err || _('Trigger not found'), data: [] }); } @@ -292,7 +294,7 @@ router.post('/status/ajax/:id', (req, res) => { htmlescape(row.firstName || ''), htmlescape(row.lastName || ''), '' + row.created.toISOString() + '', - 'Edit' + '' + _('Edit') + '' ]) }); }); diff --git a/routes/users.js b/routes/users.js index 9ec2aaa1..ce6cfc2b 100644 --- a/routes/users.js +++ b/routes/users.js @@ -6,6 +6,7 @@ let router = new express.Router(); let users = require('../lib/models/users'); let fields = require('../lib/models/fields'); let settings = require('../lib/models/settings'); +let _ = require('../lib/translate')._; router.get('/logout', (req, res) => passport.logout(req, res)); @@ -28,7 +29,7 @@ router.post('/forgot', passport.parseForm, passport.csrfProtection, (req, res) = req.flash('danger', err.message || err); return res.redirect('/users/forgot'); } else { - req.flash('success', 'An email with password reset instructions has been sent to your email address, if it exists on our system.'); + req.flash('success', _('An email with password reset instructions has been sent to your email address, if it exists on our system.')); } return res.redirect('/users/login'); }); @@ -42,7 +43,7 @@ router.get('/reset', passport.csrfProtection, (req, res) => { } if (!status) { - req.flash('danger', 'Unknown or expired reset token'); + req.flash('danger', _('Unknown or expired reset token')); return res.redirect('/users/login'); } @@ -60,9 +61,9 @@ router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => req.flash('danger', err.message || err); return res.redirect('/users/reset?username=' + encodeURIComponent(req.body.username) + '&token=' + encodeURIComponent(req.body['reset-token'])); } else if (!status) { - req.flash('danger', 'Unknown or expired reset token'); + req.flash('danger', _('Unknown or expired reset token')); } else { - req.flash('success', 'Your password has been changed successfully'); + req.flash('success', _('Your password has been changed successfully')); } return res.redirect('/users/login'); @@ -71,7 +72,7 @@ router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => router.all('/api', (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)); } next(); @@ -83,7 +84,7 @@ router.get('/api', passport.csrfProtection, (req, res, next) => { return next(err); } if (!user) { - return next(new Error('User data not found')); + return next(new Error(_('User data not found'))); } settings.list(['serviceUrl'], (err, configItems) => { if (err) { @@ -106,9 +107,9 @@ router.post('/api/reset-token', passport.parseForm, passport.csrfProtection, (re if (err) { req.flash('danger', err.message || err); } else if (success) { - req.flash('success', 'Access token updated'); + req.flash('success', _('Access token updated')); } else { - req.flash('info', 'Access token not updated'); + req.flash('info', _('Access token not updated')); } return res.redirect('/users/api'); }); @@ -116,7 +117,7 @@ router.post('/api/reset-token', passport.parseForm, passport.csrfProtection, (re router.all('/account', (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)); } next(); @@ -135,9 +136,9 @@ router.post('/account', passport.parseForm, passport.csrfProtection, (req, res) if (err) { req.flash('danger', err.message || err); } else if (success) { - req.flash('success', 'Account information updated'); + req.flash('success', _('Account information updated')); } else { - req.flash('info', 'Account information not updated'); + req.flash('info', _('Account information not updated')); } return res.redirect('/users/account'); }); diff --git a/services/feedcheck.js b/services/feedcheck.js index 9222efb4..3c506154 100644 --- a/services/feedcheck.js +++ b/services/feedcheck.js @@ -6,6 +6,8 @@ let db = require('../lib/db'); let tools = require('../lib/tools'); let feed = require('../lib/feed'); let campaigns = require('../lib/models/campaigns'); +let _ = require('../lib/translate')._; +let util = require('util'); const feed_timeout = 15 * 1000; const rss_timeout = 1 * 1000; @@ -46,12 +48,12 @@ function feedLoop() { let message; if (err) { log.error('Feed', err); - message = 'Feed error: ' + err.message; + message = util.format(_('Feed error: %s'), err.message); } else if (result) { log.verbose('Feed', 'Added %s new campaigns for %s', result, parent.id); - message = 'Found ' + result + ' new campaign messages from feed'; + message = util.format(_('Found %s new campaign messages from feed'), result); } else { - message = 'Found nothing new from the feed'; + message = _('Found nothing new from the feed'); } return updateRssInfo(parent.id, false, message, () => { setTimeout(feedLoop, rss_timeout); @@ -138,7 +140,7 @@ function checkEntries(parent, entries, callback) { let campaign = { type: 'entry', - name: entry.title || 'RSS entry ' + (entry.guid.substr(0, 67)), + name: entry.title || util.format(_('RSS entry %s'), entry.guid.substr(0, 67)), from: parent.from, address: parent.address, subject: entry.title || parent.subject, diff --git a/services/importer.js b/services/importer.js index 00e56d89..ee2ef5ed 100644 --- a/services/importer.js +++ b/services/importer.js @@ -4,6 +4,7 @@ let log = require('npmlog'); let db = require('../lib/db'); let tools = require('../lib/tools'); +let _ = require('../lib/translate')._; let fields = require('../lib/models/fields'); let subscriptions = require('../lib/models/subscriptions'); @@ -239,7 +240,7 @@ let importLoop = () => { let failed = null; if (err) { if (err.code === 'ENOENT') { - failed = 'Could not access import file'; + failed = _('Could not access import file'); } else { failed = err.message || err; } diff --git a/services/sender.js b/services/sender.js index a48338c3..2429aaa4 100644 --- a/services/sender.js +++ b/services/sender.js @@ -16,6 +16,8 @@ let url = require('url'); let htmlToText = require('html-to-text'); let request = require('request'); let libmime = require('libmime'); +let _ = require('../lib/translate')._; +let util = require('util'); let attachmentCache = new Map(); let attachmentCacheSize = 0; @@ -299,14 +301,14 @@ function formatMessage(message, callback) { return callback(err); } if (!campaign) { - return callback(new Error('Campaign not found')); + return callback(new Error(_('Campaign not found'))); } lists.get(message.listId, (err, list) => { if (err) { return callback(err); } if (!list) { - return callback(new Error('List not found')); + return callback(new Error(_('List not found'))); } settings.list(['serviceUrl', 'verpUse', 'verpHostname'], (err, configItems) => { @@ -442,7 +444,7 @@ function formatMessage(message, callback) { return callback(err); } if (httpResponse.statusCode !== 200) { - return callback(new Error('Received status code ' + httpResponse.statusCode + ' from ' + campaign.sourceUrl)); + return callback(new Error(util.format(_('Received status code %s from %s'), httpResponse.statusCode, campaign.sourceUrl))); } renderAndSend(body && body.toString(), '', false); }); diff --git a/services/triggers.js b/services/triggers.js index 511ea8fa..d223c573 100644 --- a/services/triggers.js +++ b/services/triggers.js @@ -4,6 +4,8 @@ let log = require('npmlog'); let db = require('../lib/db'); let tools = require('../lib/tools'); let triggers = require('../lib/models/triggers'); +let _ = require('../lib/translate')._; +let util = require('util'); function triggerLoop() { checkTrigger((err, triggerId) => { @@ -46,7 +48,7 @@ function checkTrigger(callback) { return callback(err); } if (!query) { - return callback(new Error('Unknown trigger type ' + trigger.id)); + return callback(new Error(util.format(_('Unknown trigger type %s'), trigger.id))); } trigger.query = query; fireTrigger(trigger, callback); diff --git a/views/archive/layout.hbs b/views/archive/layout.hbs index bc9e27a9..88cafbf4 100644 --- a/views/archive/layout.hbs +++ b/views/archive/layout.hbs @@ -6,7 +6,7 @@ - + diff --git a/views/campaigns/bounced.hbs b/views/campaigns/bounced.hbs index c35cc3a0..1844d0cd 100644 --- a/views/campaigns/bounced.hbs +++ b/views/campaigns/bounced.hbs @@ -1,14 +1,14 @@ -

{{name}} Bounced info View campaign

+

{{name}} {{#translate}}Bounced info{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -20,7 +20,7 @@
-
Subscribers who bounced and were unsubscribed:
+
{{#translate}}Subscribers who bounced and were unsubscribed:{{/translate}}
@@ -30,19 +30,19 @@ # diff --git a/views/campaigns/campaigns.hbs b/views/campaigns/campaigns.hbs index 1828e4e1..9ac302ea 100644 --- a/views/campaigns/campaigns.hbs +++ b/views/campaigns/campaigns.hbs @@ -1,22 +1,22 @@ -

Campaigns

+

{{#translate}}Campaigns{{/translate}}


@@ -27,16 +27,16 @@ #
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} - SMTP response + {{#translate}}SMTP response{{/translate}} - Bounced + {{#translate}}Bounce time{{/translate}}
- Name + {{#translate}}Name{{/translate}} - Description + {{#translate}}Description{{/translate}} - Status + {{#translate}}Status{{/translate}} - Created + {{#translate}}Created{{/translate}}   diff --git a/views/campaigns/clicked.hbs b/views/campaigns/clicked.hbs index bb20f755..9f71d470 100644 --- a/views/campaigns/clicked.hbs +++ b/views/campaigns/clicked.hbs @@ -1,14 +1,14 @@ -

{{name}} Link info View campaign

+

{{name}} {{#translate}}Link info{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -20,23 +20,23 @@ {{#if aggregated}}
- URL + {{#translate}}URL{{/translate}} - Clicks + {{#translate}}Clicks{{/translate}} - % of clicks + {{#translate}}% of clicks{{/translate}} - % of messages + {{#translate}}% of messages{{/translate}}
- Aggregated clicks + {{#translate}}Aggregated clicks{{/translate}} {{clicks}} @@ -67,7 +67,7 @@
-
{{#if aggregated}}Subscribers who clicked on a link:{{else}}Subscribers who clicked on this link:{{/if}}
+
{{#if aggregated}}{{#translate}}Subscribers who clicked on a link:{{/translate}}{{else}}{{#translate}}Subscribers who clicked on this link:{{/translate}}{{/if}}
@@ -77,19 +77,19 @@ # diff --git a/views/campaigns/complained.hbs b/views/campaigns/complained.hbs index 77f1b41a..cce19b37 100644 --- a/views/campaigns/complained.hbs +++ b/views/campaigns/complained.hbs @@ -1,14 +1,14 @@ -

{{name}} Complained info View campaign

+

{{name}} {{#translate}}Complained info{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -20,7 +20,7 @@
-
Subscribers who complained and were unsubscribed:
+
{{#translate}}Subscribers who complained and were unsubscribed:{{/translate}}
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} - First click + {{#translate}}First click time{{/translate}} - Click count + {{#translate}}Click count{{/translate}}
@@ -30,19 +30,19 @@ # diff --git a/views/campaigns/create-rss.hbs b/views/campaigns/create-rss.hbs index 1dbb715d..131a4a5d 100644 --- a/views/campaigns/create-rss.hbs +++ b/views/campaigns/create-rss.hbs @@ -1,16 +1,16 @@ -

Create RSS Campaign

+

{{#translate}}Create RSS Campaign{{/translate}}


- RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message. + {{#translate}}RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message.{{/translate}}
@@ -19,28 +19,28 @@
- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
- +
- New entries from this RSS URL are sent out to list subscribers as email messages + {{#translate}}New entries from this RSS URL are sent out to list subscribers as email messages{{/translate}}

- +
- +
- +
- +
@@ -93,7 +93,7 @@
- +
diff --git a/views/campaigns/create-triggered.hbs b/views/campaigns/create-triggered.hbs index e982d875..ab4a7088 100644 --- a/views/campaigns/create-triggered.hbs +++ b/views/campaigns/create-triggered.hbs @@ -1,10 +1,10 @@ -

Create Triggered Campaign

+

{{#translate}}Create Triggered Campaign{{/translate}}


@@ -13,28 +13,28 @@
- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
- +
- + {{#each templateItems}} {{/each}} - Selecting a template creates a campaign specific copy from it + {{#translate}}Selecting a template creates a campaign specific copy from it{{/translate}}

- Or alternatively use an URL as the message content source: + {{#translate}}Or alternatively use an URL as the message content source:{{/translate}}

- If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself + {{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}
@@ -83,28 +83,28 @@
- +
- +
- +
- +
- +
- +
@@ -113,7 +113,7 @@
- +
diff --git a/views/campaigns/create.hbs b/views/campaigns/create.hbs index e2853260..b5869898 100644 --- a/views/campaigns/create.hbs +++ b/views/campaigns/create.hbs @@ -1,10 +1,10 @@ -

Create Campaign

+

{{#translate}}Create Campaign{{/translate}}


@@ -13,28 +13,28 @@
- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
- +
- + {{#each templateItems}} {{/each}} - Selecting a template creates a campaign specific copy from it + {{#translate}}Selecting a template creates a campaign specific copy from it{{/translate}}

- Or alternatively use an URL as the message content source: + {{#translate}}Or alternatively use an URL as the message content source:{{/translate}}

- If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself + {{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}
@@ -83,34 +83,34 @@
- +
- +
- +
- +
- +
- +
- +
- +
@@ -119,7 +119,7 @@
- +
diff --git a/views/campaigns/delivered.hbs b/views/campaigns/delivered.hbs index 89bc8194..7d933d2c 100644 --- a/views/campaigns/delivered.hbs +++ b/views/campaigns/delivered.hbs @@ -1,14 +1,14 @@ -

{{name}} Delivered info View campaign

+

{{name}} {{#translate}}Delivered info{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -20,7 +20,7 @@
-
Subscribers who received the message and did not bounce/unsubscribe:
+
{{#translate}}Subscribers who received the message and did not bounce/unsubscribe:{{/translate}}
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} - SMTP response + {{#translate}}SMTP response{{/translate}} - Complained + {{#translate}}Complain time{{/translate}}
@@ -30,19 +30,19 @@ # diff --git a/views/campaigns/edit-rss.hbs b/views/campaigns/edit-rss.hbs index c8efcc2b..03c96d4e 100644 --- a/views/campaigns/edit-rss.hbs +++ b/views/campaigns/edit-rss.hbs @@ -1,20 +1,20 @@ -

Edit RSS Campaign View campaign

+

{{#translate}}Edit RSS Campaign{{/translate}} {{#translate}}View campaign{{/translate}}


- RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message. + {{#translate}}RSS campaign sets up a tracker against selected RSS feed address. Whenever a new entry is found from this feed it is sent to selected list as an email message.{{/translate}}
@@ -29,32 +29,32 @@
- General Settings + {{#translate}}General Settings{{/translate}}
- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
- +
- New entries from this RSS URL are sent out to list subscribers as email messages + {{#translate}}New entries from this RSS URL are sent out to list subscribers as email messages{{/translate}}
@@ -89,29 +89,29 @@
- Use special merge tag [RSS_ENTRY] to mark the position for the RSS post content. Additionally you can use any valid merge tag as well. + {{#translate}}Use special merge tag [RSS_ENTRY] to mark the position for the RSS post content. Additionally you can use any valid merge tag as well.{{/translate}}

- +
- +
- +
- +
@@ -123,9 +123,9 @@
- +
- +
diff --git a/views/campaigns/edit-triggered.hbs b/views/campaigns/edit-triggered.hbs index 3cdeec96..d709c177 100644 --- a/views/campaigns/edit-triggered.hbs +++ b/views/campaigns/edit-triggered.hbs @@ -1,14 +1,14 @@ -

Edit Triggered Campaign View campaign

+

{{#translate}}Edit Triggered Campaign{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -26,8 +26,8 @@
@@ -37,32 +37,32 @@
- General Settings + {{#translate}}General Settings{{/translate}}
- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
- +
+
- +
- +
- +
- +
@@ -116,15 +116,15 @@
- Template Settings + {{#translate}}Template Settings{{/translate}} {{#if sourceUrl}}
- +
- If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself + {{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}
{{else}} @@ -150,9 +150,9 @@
- +
- +
diff --git a/views/campaigns/edit.hbs b/views/campaigns/edit.hbs index 3b747608..53dac119 100644 --- a/views/campaigns/edit.hbs +++ b/views/campaigns/edit.hbs @@ -1,14 +1,14 @@ -

Edit Campaign View campaign

+

{{#translate}}Edit Campaign{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -37,9 +37,9 @@
@@ -49,32 +49,32 @@
- General Settings + {{#translate}}General Settings{{/translate}}
- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
- +
+
- +
- +
- +
- +
- +
- +
@@ -132,15 +132,15 @@
- Template Settings + {{#translate}}Template Settings{{/translate}} {{#if sourceUrl}}
- +
- If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself + {{#translate}}If a message is sent then this URL will be POSTed to using Merge Tags as POST body. Use this if you want to generate the HTML message yourself{{/translate}}
{{else}} @@ -166,7 +166,7 @@
- Attachments + {{#translate}}Attachments{{/translate}}
@@ -176,10 +176,10 @@ #
{{/if}} @@ -214,7 +214,7 @@
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} - SMTP response + {{#translate}}SMTP response{{/translate}} - Delivered + {{#translate}}Delivery time{{/translate}}
- File + {{#translate}}File{{/translate}} - Size + {{#translate}}Size{{/translate}}   @@ -206,7 +206,7 @@ {{else}}
- No data available in table + {{#translate}}No data available in table{{/translate}}
@@ -226,9 +226,9 @@
- +
- +
diff --git a/views/campaigns/opened.hbs b/views/campaigns/opened.hbs index 9e59be96..20665d53 100644 --- a/views/campaigns/opened.hbs +++ b/views/campaigns/opened.hbs @@ -1,14 +1,14 @@ -

{{name}} Opened info View campaign

+

{{name}} {{#translate}}Opened info{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -20,7 +20,7 @@
-
Subscribers who opened this message:
+
{{#translate}}Subscribers who opened this message:{{/translate}}
@@ -30,19 +30,19 @@ # diff --git a/views/campaigns/unsubscribed.hbs b/views/campaigns/unsubscribed.hbs index c6440dfa..3b5aa226 100644 --- a/views/campaigns/unsubscribed.hbs +++ b/views/campaigns/unsubscribed.hbs @@ -1,14 +1,14 @@ -

{{name}} Unsubscribed info View campaign

+

{{name}} {{#translate}}Unsubscribed info{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -20,7 +20,7 @@
-
Subscribers who unsubscribed:
+
{{#translate}}Subscribers who unsubscribed:{{/translate}}
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} - First open + {{#translate}}First open{{/translate}} - Opened count + {{#translate}}Opened count{{/translate}}
@@ -30,19 +30,19 @@ # diff --git a/views/campaigns/upload-attachment.hbs b/views/campaigns/upload-attachment.hbs index b535e3c1..d15066f6 100644 --- a/views/campaigns/upload-attachment.hbs +++ b/views/campaigns/upload-attachment.hbs @@ -1,15 +1,15 @@ -

Edit Campaign View campaign

+

{{#translate}}Edit Campaign{{/translate}} {{#translate}}View campaign{{/translate}}


@@ -25,7 +25,7 @@
- +
diff --git a/views/campaigns/view.hbs b/views/campaigns/view.hbs index bd4a7bd6..fe52ce39 100644 --- a/views/campaigns/view.hbs +++ b/views/campaigns/view.hbs @@ -1,6 +1,6 @@

{{name}}

@@ -21,9 +21,9 @@ @@ -34,7 +34,7 @@
{{#if list}} -
List
+
{{#translate}}List{{/translate}}
{{#if segment}} {{list.name}}: {{segment.name}} @@ -54,43 +54,43 @@ {{/if}} {{#if isRss}} -
Feed URL
+
{{#translate}}Feed URL{{/translate}}
{{sourceUrl}}
-
Last check
+
{{#translate}}Last check{{/translate}}
{{#if lastCheck}}{{lastCheck}}{{else}} - Not yet checked{{/if}} - {{#unless isActive}}(activate campaign to start checking feed for new messages){{/unless}} + {{#translate}}Not yet checked{{/translate}}{{/if}} + {{#unless isActive}}({{#translate}}activate campaign to start checking feed for new messages{{/translate}}){{/unless}}
{{#if checkStatus}} -
RSS status
+
{{#translate}}RSS status{{/translate}}
{{checkStatus}}
{{/if}} {{/if}} {{#if from}} -
Email "from name"
+
{{#translate}}Email "from name"{{/translate}}
{{from}}
{{/if}} {{#if address}} -
Email "from" address
+
{{#translate}}Email "from" address{{/translate}}
{{address}}
{{/if}} {{#if replyTo}} -
Email "reply-to" address
+
{{#translate}}Email "reply-to" address{{/translate}}
{{replyTo}}
{{/if}} {{#if subject}} -
Email "subject line"
+
{{#translate}}Email "subject line"{{/translate}}
{{subject}}
{{/if}} {{#unless isRss}} -
Preview campaign as
+
{{#translate}}Preview campaign as{{/translate}}
@@ -104,26 +104,26 @@ {{/each}} {{#if testUsers}} - + {{else}} - + {{/if}} - +
{{#unless isIdling}} -
Delivered
+
{{#translate}}Delivered{{/translate}}
{{delivered}}

-
Bounced
+
{{#translate}}Bounced{{/translate}}
@@ -132,7 +132,7 @@
-
Complaints
+
{{#translate}}Complaints{{/translate}}
@@ -141,7 +141,7 @@
-
Unsubscribed
+
{{#translate}}Unsubscribed{{/translate}}
@@ -152,7 +152,7 @@ {{#unless trackingDisabled}} -
Opened
+
{{#translate}}Opened{{/translate}}
@@ -161,7 +161,7 @@
-
Clicked
+
{{#translate}}Clicked{{/translate}}
@@ -180,34 +180,34 @@
{{#if isIdling}} -
+
-

Delay sending

+

{{#translate}}Delay sending{{/translate}}

-
hours
+
{{#translate}}hours{{/translate}}
-
minutes
+
{{#translate}}minutes{{/translate}}
- + {{/if}} {{/if}} @@ -216,45 +216,45 @@
{{#if isScheduled}}
-
+ -
-

Sending scheduled {{scheduled}}

+

{{#translate}}Sending scheduled{{/translate}} {{scheduled}}

{{else}}
-
-

Sending…

+

{{#translate}}Sending{{/translate}}…

{{/if}} {{/if}} {{#if isPaused}}
-
+ -
+ - -

Sending paused

@@ -262,24 +262,24 @@ {{#if isFinished}}
-
+ -
+ - -
-

All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers

+

{{#translate}}All messages sent! Hit "Continue" if you you want to send this campaign to new subscribers{{/translate}}

{{/if}}
@@ -292,28 +292,28 @@
{{#if isActive}}
-
+ -
- Campaign status: ACTIVE + {{#translate}}Campaign status:{{/translate}} {{#translate}}ACTIVE{{/translate}} {{else}}
-
+ -
- Campaign status: INACTIVE + {{#translate}}Campaign status:{{/translate}} {{#translate}}INACTIVE{{/translate}} {{/if}}
@@ -322,7 +322,7 @@ {{#if isTriggered}}
- This is a triggered campaign. Messages are only sent to subscribers that hit some trigger that invokes this campaign + {{#translate}}This is a triggered campaign. Messages are only sent to subscribers that hit some trigger that invokes this campaign{{/translate}} ({{#translate}}see more{{/translate}})
{{/if}} @@ -341,16 +341,16 @@ #
@@ -365,7 +365,7 @@ @@ -380,7 +380,7 @@ {{else}} {{/if}} @@ -389,11 +389,11 @@ @@ -408,7 +408,7 @@
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} - SMTP response + {{#translate}}SMTP response{{/translate}} - Unsubscribed + {{#translate}}Unsubscribed{{/translate}}
- URL + {{#translate}}URL{{/translate}} - Clicks + {{#translate}}Clicks{{/translate}} - % of clicks + {{#translate}}% of clicks{{/translate}} - % of messages + {{#translate}}% of messages{{/translate}}
- +
{{clicks}}
- No data available in table + {{#translate}}No data available in table{{/translate}}
- Aggregated clicks + {{#translate}}Aggregated clicks{{/translate}}
- +
{{clicks}}

- Clicks are counted as unique subscribers that clicked on a specific link or on any link (in aggregated view) + {{#translate}}Clicks are counted as unique subscribers that clicked on a specific link or on any link (in aggregated view){{/translate}}

@@ -421,7 +421,7 @@ {{#if isRss}}
- If a new entry is found from campaign feed a new subcampaign is created of that entry and it will be listed here + {{#translate}}If a new entry is found from campaign feed a new subcampaign is created of that entry and it will be listed here{{/translate}}
@@ -429,16 +429,16 @@ # @@ -77,7 +77,7 @@ {{/each}} @@ -86,7 +86,7 @@ {{#unless rows}} {{/unless}} diff --git a/views/lists/lists.hbs b/views/lists/lists.hbs index 52000689..94b1e897 100644 --- a/views/lists/lists.hbs +++ b/views/lists/lists.hbs @@ -1,13 +1,13 @@ -

Lists

+

{{#translate}}Lists{{/translate}}


@@ -18,16 +18,16 @@ # diff --git a/views/lists/segments/create.hbs b/views/lists/segments/create.hbs index aa49d171..764f96c5 100644 --- a/views/lists/segments/create.hbs +++ b/views/lists/segments/create.hbs @@ -1,38 +1,38 @@ -

{{list.name}} Create Segment

+

{{list.name}} {{#translate}}Create Segment{{/translate}}


- +
- +
- +
- +
diff --git a/views/lists/segments/edit.hbs b/views/lists/segments/edit.hbs index aec7c55f..a7572002 100644 --- a/views/lists/segments/edit.hbs +++ b/views/lists/segments/edit.hbs @@ -1,12 +1,12 @@ -

{{list.name}} Edit Segment Back to segments

+

{{list.name}} {{#translate}}Edit Segment{{/translate}} {{#translate}}Back to segments{{/translate}}


@@ -20,19 +20,19 @@
- +
- +
- +
@@ -40,9 +40,9 @@
- +
- +
diff --git a/views/lists/segments/rule-configure.hbs b/views/lists/segments/rule-configure.hbs index d0659bbe..0d37b1c0 100644 --- a/views/lists/segments/rule-configure.hbs +++ b/views/lists/segments/rule-configure.hbs @@ -1,13 +1,13 @@ -

{{list.name}} Create Rule

+

{{list.name}} {{#translate}}Create Rule{{/translate}}


@@ -16,7 +16,7 @@
- +

{{column.name}}

@@ -24,20 +24,20 @@ {{#if columnTypeString}}
- +
- - Use % for wildcard character, e.g. "%test" to match all values that end with "test" + + {{#translate}}Use % for wildcard character, e.g. "%test" to match all values that end with "test"{{/translate}}
{{/if}} {{#if columnTypeNumber}}
- +
@@ -50,7 +50,7 @@
@@ -78,10 +78,10 @@ {{#if columnTypeDate}}
- +
@@ -96,7 +96,7 @@
@@ -127,7 +127,7 @@
@@ -136,38 +136,38 @@
-

From

+

{{#translate}}From{{/translate}}

- days + {{#translate}}days{{/translate}}
-

to

+

{{#translate}}to{{/translate}}

- days + {{#translate}}days{{/translate}}
@@ -180,10 +180,10 @@ {{#if columnTypeBirthday}}
- +
@@ -198,7 +198,7 @@
@@ -207,7 +207,7 @@
-

From

+

{{#translate}}From{{/translate}}

@@ -215,7 +215,7 @@
-

to

+

{{#translate}}to{{/translate}}

@@ -229,16 +229,16 @@ {{#if columnTypeBoolean}}
- +
@@ -248,7 +248,7 @@
- +
diff --git a/views/lists/segments/rule-create.hbs b/views/lists/segments/rule-create.hbs index 31f9f5f1..6d08cc1e 100644 --- a/views/lists/segments/rule-create.hbs +++ b/views/lists/segments/rule-create.hbs @@ -1,13 +1,13 @@ -

{{list.name}} Create Rule

+

{{list.name}} {{#translate}}Create Rule{{/translate}}


@@ -15,10 +15,10 @@
- +
- +

{{column.name}}

@@ -29,20 +29,20 @@ {{#if columnTypeString}}
- +
- - Use % for wildcard character, e.g. "%test" to match all values that end with "test" + + {{#translate}}Use % for wildcard character, e.g. "%test" to match all values that end with "test"{{/translate}}
{{/if}} {{#if columnTypeNumber}}
- +
@@ -55,7 +55,7 @@
@@ -64,13 +64,13 @@
-

From

+

{{#translate}}From{{/translate}}

-

to

+

{{#translate}}to{{/translate}}

@@ -83,10 +83,10 @@ {{#if columnTypeDate}}
- +
@@ -101,7 +101,7 @@
@@ -110,7 +110,7 @@
-

From

+

{{#translate}}From{{/translate}}

@@ -118,7 +118,7 @@
-

to

+

{{#translate}}to{{/translate}}

@@ -132,7 +132,7 @@
@@ -141,38 +141,38 @@
-

From

+

{{#translate}}From{{/translate}}

- days + {{#translate}}days{{/translate}}
-

to

+

{{#translate}}to{{/translate}}

- days + {{#translate}}days{{/translate}}
@@ -185,10 +185,10 @@ {{#if columnTypeBirthday}}
- +
@@ -203,7 +203,7 @@
@@ -212,7 +212,7 @@
-

From

+

{{#translate}}From{{/translate}}

@@ -220,7 +220,7 @@
-

To

+

{{#translate}}to{{/translate}}

@@ -234,16 +234,16 @@ {{#if columnTypeBoolean}}
- +
@@ -254,9 +254,9 @@
- +
- +
diff --git a/views/lists/segments/segments.hbs b/views/lists/segments/segments.hbs index 14c9f31d..d32582a9 100644 --- a/views/lists/segments/segments.hbs +++ b/views/lists/segments/segments.hbs @@ -1,15 +1,15 @@ -

{{list.name}} Segments

+

{{list.name}} {{#translate}}Segments{{/translate}}


@@ -20,10 +20,10 @@ #
diff --git a/views/lists/segments/view.hbs b/views/lists/segments/view.hbs index cb7b10b1..d456f945 100644 --- a/views/lists/segments/view.hbs +++ b/views/lists/segments/view.hbs @@ -1,26 +1,26 @@ -

{{list.name}} Segment {{name}}

+

{{list.name}} {{#translate}}Segment{{/translate}} {{name}}


- Match rules: {{type}} -
Matching subscribers: {{subscribers}} ( - show) + {{#translate}}Match rules{{/translate}}: {{type}} +
{{#translate}}Matching subscribers{{/translate}}: {{subscribers}} ( + {{#translate}}show{{/translate}})
@@ -32,10 +32,10 @@ # diff --git a/views/lists/subscription/add.hbs b/views/lists/subscription/add.hbs index 59acd202..82a72157 100644 --- a/views/lists/subscription/add.hbs +++ b/views/lists/subscription/add.hbs @@ -1,11 +1,13 @@ -

{{list.name}} Add subscriber

+

{{list.name}} {{#translate}}Add subscriber{{/translate}}


@@ -14,21 +16,21 @@
- +
- +
- +
@@ -59,8 +61,8 @@ {{/if}} {{#if typeGpg}} - - Insert a GPG public key that will be used to encrypt messages sent this subscriber + + {{#translate}}Insert a GPG public key that will be used to encrypt messages sent this subscriber{{/translate}} {{/if}} {{#if typeDateUs}} @@ -90,7 +92,7 @@ {{#if typeDropdown}} {{#each timezones}} @@ -139,8 +141,8 @@
@@ -151,10 +153,10 @@

- This person will not receive a confirmation email so make sure that you have permission to email them. + {{#translate}}This person will not receive a confirmation email so make sure that you have permission to email them.{{/translate}}

- +
diff --git a/views/lists/subscription/edit.hbs b/views/lists/subscription/edit.hbs index 9821089e..72227d5c 100644 --- a/views/lists/subscription/edit.hbs +++ b/views/lists/subscription/edit.hbs @@ -1,11 +1,13 @@ -

{{list.name}} Edit subscriber Back to list

+

{{list.name}} {{#translate}}Edit subscriber{{/translate}} {{#translate}}Back to list{{/translate}}


@@ -27,21 +29,21 @@
- +
- +
- +
@@ -72,8 +74,8 @@ {{/if}} {{#if typeGpg}} - - Insert a GPG public key that will be used to encrypt messages sent this subscriber + + {{#translate}}Insert a GPG public key that will be used to encrypt messages sent this subscriber{{/translate}} {{/if}} {{#if typeDateUs}} @@ -103,7 +105,7 @@ {{#if typeDropdown}}
diff --git a/views/lists/subscription/import-failed.hbs b/views/lists/subscription/import-failed.hbs index 54157cf8..36ecdc6c 100644 --- a/views/lists/subscription/import-failed.hbs +++ b/views/lists/subscription/import-failed.hbs @@ -1,16 +1,16 @@ -

{{list.name}} Failed addresses Back to list

+

{{list.name}} {{#translate}}Failed addresses{{/translate}} {{#translate}}Back to list{{/translate}}


- Role-based addresses like postmaster@example.com are blocked when importing. Subscribers with role-based email addresses can join your list using the subscription form. + {{#translate}}Role-based addresses like postmaster@example.com are blocked when importing. Subscribers with role-based email addresses can join your list using the subscription form{{#translate}} ({{#translate}}see here{{/translate}}).
@@ -20,10 +20,10 @@ #
{{#if rows}} diff --git a/views/lists/subscription/import-preview.hbs b/views/lists/subscription/import-preview.hbs index 2ffb33f0..97e19145 100644 --- a/views/lists/subscription/import-preview.hbs +++ b/views/lists/subscription/import-preview.hbs @@ -1,11 +1,11 @@ -

{{list.name}} Import subscribers

+

{{list.name}} {{#translate}}Import subscribers{{/translate}}


@@ -20,11 +20,11 @@
- Example: "{{lookup ../mapping.example @index}}" + {{#translate}}Example{{/translate}}: "{{lookup ../mapping.example @index}}"
@@ -49,7 +49,7 @@
- +
diff --git a/views/lists/subscription/import.hbs b/views/lists/subscription/import.hbs index dc073347..b2bfe460 100644 --- a/views/lists/subscription/import.hbs +++ b/views/lists/subscription/import.hbs @@ -1,11 +1,11 @@ -

{{list.name}} Import subscribers

+

{{list.name}} {{#translate}}Import subscribers{{/translate}}


@@ -14,30 +14,30 @@
- +
- +
- +
@@ -47,7 +47,7 @@
- +
diff --git a/views/lists/view.hbs b/views/lists/view.hbs index 10daf756..40f39a9d 100644 --- a/views/lists/view.hbs +++ b/views/lists/view.hbs @@ -1,25 +1,25 @@ @@ -35,35 +35,35 @@
{{#if useSegment}} {{/if}}
- +
- +
@@ -79,13 +79,13 @@ #
{{#each customFields}} {{/each}} @@ -117,25 +117,25 @@ # @@ -193,7 +193,7 @@ {{else}} {{/if}} diff --git a/views/partials/codeeditor.hbs b/views/partials/codeeditor.hbs index 68bd951e..046a382e 100644 --- a/views/partials/codeeditor.hbs +++ b/views/partials/codeeditor.hbs @@ -1,5 +1,5 @@
- +
{{html}}
diff --git a/views/partials/html-preview.hbs b/views/partials/html-preview.hbs index 938f2be4..e9c825fe 100644 --- a/views/partials/html-preview.hbs +++ b/views/partials/html-preview.hbs @@ -1,6 +1,6 @@
- +
320x480px
- Name + {{#translate}}Name{{/translate}} - Description + {{#translate}}Description{{/translate}} - Status + {{#translate}}Status{{/translate}} - Created + {{#translate}}Created{{/translate}}   diff --git a/views/emails/confirm-html.hbs b/views/emails/confirm-html.hbs index 2e9cb0cd..e958a62c 100644 --- a/views/emails/confirm-html.hbs +++ b/views/emails/confirm-html.hbs @@ -3,118 +3,8473 @@ - {{title}}: Please Confirm Subscription + {{title}}: {{#translate}}Please Confirm Subscription{{/translate}} + .jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; + } + + .jumbotron .btn { + padding: 14px 24px; + font-size: 21px; + } + /* Supporting marketing content */ + + .marketing { + margin: 40px 0; + } + + .marketing p+h4 { + margin-top: 28px; + } + /* Responsive: Portrait tablets and up */ + + @media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } + } + +

{{title}}

-

Please Confirm Subscription

+

+ {{#translate}}Please Confirm Subscription{{/translate}} +

-

Yes, subscribe me to this list

+

{{#translate}}Yes, subscribe me to this list{{/translate}}

-

If you received this email by mistake, simply delete it. You won't be subscribed if you don't click the confirmation link above.

+

+ {{#translate}}If you received this email by mistake, simply delete it. You won't be subscribed if you don't click the confirmation link above.{{/translate}} +

-

For questions about this list, please contact: +

+ {{#translate}}For questions about this list, please contact:{{/translate}}
{{contactAddress}}

diff --git a/views/emails/confirm-text.hbs b/views/emails/confirm-text.hbs index 74a4e69d..75b5a70a 100644 --- a/views/emails/confirm-text.hbs +++ b/views/emails/confirm-text.hbs @@ -1,10 +1,10 @@ {{{title}}} -Please Confirm Subscription +{{#translate}}Please Confirm Subscription{{/translate}} =========================== -Yes, subscribe me to this list: {{{confirmUrl}}} +{{#translate}}Yes, subscribe me to this list{{/translate}}: {{{confirmUrl}}} -If you received this email by mistake, simply delete it. You won't be subscribed unless you click the confirmation link above. +{{#translate}}If you received this email by mistake, simply delete it. You won't be subscribed unless you click the confirmation link above.{{/translate}} -For questions about this list, please contact: +{{#translate}}For questions about this list, please contact:{{/translate}} {{{contactAddress}}} diff --git a/views/emails/password-reset-html.hbs b/views/emails/password-reset-html.hbs index cadbcc21..8ff7e6c5 100644 --- a/views/emails/password-reset-html.hbs +++ b/views/emails/password-reset-html.hbs @@ -3,115 +3,8466 @@ - Change your password + {{#translate}}Change your password{{/translate}} + .jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; + } + + .jumbotron .btn { + padding: 14px 24px; + font-size: 21px; + } + /* Supporting marketing content */ + + .marketing { + margin: 40px 0; + } + + .marketing p+h4 { + margin-top: 28px; + } + /* Responsive: Portrait tablets and up */ + + @media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } + } + +
-

Change your password

-

We have received a password change request for your Mailtrain account {{username}}.

+

{{#translate}}Change your password{{/translate}}

+

{{#translate}}We have received a password change request for your Mailtrain account:{{/translate}} {{username}}.

-

Reset password

+

{{#translate}}Reset password{{/translate}}

-

If you did not ask to change your password, then you can ignore this email and your password will not be changed.

+

{{#translate}}If you did not ask to change your password, then you can ignore this email and your password will not be changed.{{/translate}}

+ diff --git a/views/emails/password-reset-text.hbs b/views/emails/password-reset-text.hbs index 40b49e9c..1fcbbab1 100644 --- a/views/emails/password-reset-text.hbs +++ b/views/emails/password-reset-text.hbs @@ -1,9 +1,9 @@ {{{title}}} -Change your password +{{#translate}}Change your password{{/translate}} ==================== -We have received a password change request for your Mailtrain account ({{{username}}}). +{{#translate}}We have received a password change request for your Mailtrain account:{{/translate}} ({{{username}}}). -Reset password: {{{confirmUrl}}} +{{#translate}}Reset password{{/translate}}: {{{confirmUrl}}} -If you did not ask to change your password, then you can ignore this email and your password will not be changed. +{{#translate}}If you did not ask to change your password, then you can ignore this email and your password will not be changed.{{/translate}} diff --git a/views/emails/rss-html.hbs b/views/emails/rss-html.hbs index d4101c64..fcb22634 100644 --- a/views/emails/rss-html.hbs +++ b/views/emails/rss-html.hbs @@ -12,11 +12,11 @@

- Preferences + {{#translate}}Preferences{{/translate}}   |   - Unsubscribe + {{#translate}}Unsubscribe{{/translate}}   |   - View this email in your browser + {{#translate}}View this email in your browser{{/translate}}

diff --git a/views/emails/stationery-html.hbs b/views/emails/stationery-html.hbs index 21a3e987..e2cba5d2 100644 --- a/views/emails/stationery-html.hbs +++ b/views/emails/stationery-html.hbs @@ -7,24 +7,24 @@ -

Hey [FIRST_NAME/Customer],

+

{{#translate}}Hey [FIRST_NAME/Customer],{{/translate}}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est.

Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est. Aenean at mollis ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est.

-

Cheers, +

{{#translate}}Cheers,{{/translate}}
{{defaultSender}}

{{defaultPostaddress}}
- Preferences + {{#translate}}Preferences{{/translate}}   |   - Unsubscribe + {{#translate}}Unsubscribe{{/translate}}   |   - View this email in your browser + {{#translate}}View this email in your browser{{/translate}}

diff --git a/views/emails/stationery-text.hbs b/views/emails/stationery-text.hbs index 6bb25f11..8eb796a8 100644 --- a/views/emails/stationery-text.hbs +++ b/views/emails/stationery-text.hbs @@ -1,14 +1,14 @@ -Hey [FIRST_NAME/Customer], +{{#translate}}Hey [FIRST_NAME/Customer],{{/translate}} Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est. Aenean at mollis ipsum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed varius, leo a ullamcorper feugiat, ante purus sodales justo, a faucibus libero lacus a est. -Cheers, +{{#translate}}Cheers,{{/translate}} {{defaultSender}} {{defaultPostaddress}} -Preferences: [LINK_PREFERENCES] -Unsubscribe: [LINK_UNSUBSCRIBE] -View this email in your browser: [LINK_BROWSER] +{{#translate}}Preferences{{/translate}}: [LINK_PREFERENCES] +{{#translate}}Unsubscribe{{/translate}}: [LINK_UNSUBSCRIBE] +{{#translate}}View this email in your browser{{/translate}}: [LINK_BROWSER] diff --git a/views/emails/subscription-confirmed-html.hbs b/views/emails/subscription-confirmed-html.hbs index 9496d6a0..e35f05fe 100644 --- a/views/emails/subscription-confirmed-html.hbs +++ b/views/emails/subscription-confirmed-html.hbs @@ -3,124 +3,8474 @@ - {{title}}: Subscription Confirmed + {{title}}: {{#translate}}Subscription Confirmed{{/translate}} + .jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; + } + + .jumbotron .btn { + padding: 14px 24px; + font-size: 21px; + } + /* Supporting marketing content */ + + .marketing { + margin: 40px 0; + } + + .marketing p+h4 { + margin-top: 28px; + } + /* Responsive: Portrait tablets and up */ + + @media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } + } + +
-

{{title}}

-

Your subscription to our list has been confirmed.

+

{{title}}

+

{{#translate}}Your subscription to our list has been confirmed.{{/translate}}

-

- If you want to modify your subscription then you can: -

+

+ {{#translate}}If you want to modify your subscription then you can:{{/translate}} +

-

- manage your preferences - or - unsubscribe here -

+

+ {{#translate}}manage your preferences{{/translate}} + {{#translate}}or{{/translate}} + {{#translate}}unsubscribe here{{/translate}} +

-

For questions about this list, please contact: -
{{contactAddress}}

+

{{#translate}}For questions about this list, please contact:{{/translate}} +
{{contactAddress}}

diff --git a/views/emails/subscription-confirmed-text.hbs b/views/emails/subscription-confirmed-text.hbs index 07d4e614..bc500178 100644 --- a/views/emails/subscription-confirmed-text.hbs +++ b/views/emails/subscription-confirmed-text.hbs @@ -1,16 +1,16 @@ {{{title}}} -Subscription Confirmed +{{#translate}}Subscription Confirmed{{/translate}} ====================== -Your subscription to our list has been confirmed. +{{#translate}}Your subscription to our list has been confirmed.{{/translate}} -If you want to modify your subscription then you can: +{{#translate}}If you want to modify your subscription then you can:{{/translate}} -manage your preferences: {{preferencesUrl}} +{{#translate}}manage your preferences{{/translate}}: {{preferencesUrl}} -- or - +- {{#translate}}or{{/translate}} - -unsubscribe here: {{unsubscribeUrl}} +{{#translate}}unsubscribe here{{/translate}}: {{unsubscribeUrl}} -For questions about this list, please contact: +{{#translate}}For questions about this list, please contact:{{/translate}} {{{contactAddress}}} diff --git a/views/emails/unsubscribe-confirmed-html.hbs b/views/emails/unsubscribe-confirmed-html.hbs index 2554a993..7e446fb9 100644 --- a/views/emails/unsubscribe-confirmed-html.hbs +++ b/views/emails/unsubscribe-confirmed-html.hbs @@ -3,119 +3,8471 @@ - {{title}}: You are now unsubscribed + {{title}}: + {{#translate}}You are now unsubscribed{{/translate}} + + .jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; + } + + .jumbotron .btn { + padding: 14px 24px; + font-size: 21px; + } + /* Supporting marketing content */ + + .marketing { + margin: 40px 0; + } + + .marketing p+h4 { + margin-top: 28px; + } + /* Responsive: Portrait tablets and up */ + + @media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + .header, + .marketing, + .footer { + padding-right: 0; + padding-left: 0; + } + /* Space out the masthead */ + .header { + margin-bottom: 30px; + } + /* Remove the bottom border on the jumbotron for visual effect */ + .jumbotron { + border-bottom: 0; + } + } + +

{{title}}

-

We have removed your email address from our list.

+

{{#translate}}We have removed your email address from our list.{{/translate}}

- If you unsubscribed by mistake, you can re-subscribe at: + {{#translate}}If you unsubscribed by mistake, you can re-subscribe at:{{/translate}}

-

Subscribe

+

{{#translate}}Subscribe{{/translate}}

-

For questions about this list, please contact: +

{{#translate}}For questions about this list, please contact:{{/translate}}
{{contactAddress}}

diff --git a/views/emails/unsubscribe-confirmed-text.hbs b/views/emails/unsubscribe-confirmed-text.hbs index 5186f8e4..14214ed6 100644 --- a/views/emails/unsubscribe-confirmed-text.hbs +++ b/views/emails/unsubscribe-confirmed-text.hbs @@ -1,12 +1,12 @@ {{{title}}} -You are now unsubscribed +{{#translate}}You are now unsubscribed{{/translate}} ======================== -We have removed your email address from our list. +{{#translate}}We have removed your email address from our list.{{/translate}} -If you unsubscribed by mistake, you can re-subscribe at: +{{#translate}}If you unsubscribed by mistake, you can re-subscribe at:{{/translate}} -Subscribe: {{subscribeUrl}} +{{#translate}}Subscribe{{/translate}}: {{subscribeUrl}} -For questions about this list, please contact: +{{#translate}}For questions about this list, please contact:{{/translate}} {{{contactAddress}}} diff --git a/views/index.hbs b/views/index.hbs index 6c67c28f..f4902329 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -8,7 +8,7 @@
-

iRedMail

Free, open source mail server solution +

iRedMail

{{#translate}}Free, open source mail server solution{{/translate}}
@@ -22,7 +22,7 @@
-

SendPulse

A reliable SMTP server, easy integration, and 12,000 messages a month free +

SendPulse

{{#translate}}A reliable SMTP server, easy integration, and 12,000 messages a month free{{/translate}}
@@ -31,17 +31,17 @@
-

List management

-

Mailtrain allows you to easily manage even very large lists. Million subscribers? Not a problem. You can add subscribers manually, through the API or import from a CSV file. All lists come with support for custom fields and merge tags as well.

+

{{#translate}}List management{{/translate}}

+

{{#translate}}Mailtrain allows you to easily manage even very large lists. Million subscribers? Not a problem. You can add subscribers manually, through the API or import from a CSV file. All lists come with support for custom fields and merge tags as well.{{/translate}}

-

Custom fields

-

Text fields, numbers, drop downs or checkboxes, Mailtrain has them all. Every custom field can be included in the generated newsletters through merge tags.

+

{{#translate}}Custom fields{{/translate}}

+

{{#translate}}Text fields, numbers, drop downs or checkboxes, Mailtrain has them all. Every custom field can be included in the generated newsletters through merge tags.{{/translate}}

-

List segmentation

-

Send messages only to list subscribers that match predefined segmentation rules. No need to create separate lists with small differences.

+

{{#translate}}List segmentation{{/translate}}

+

{{#translate}}Send messages only to list subscribers that match predefined segmentation rules. No need to create separate lists with small differences.{{/translate}}

@@ -49,11 +49,11 @@
-

Donate to author

-

If you really like Mailtrain or your business benefits from it financially then I would really appreciate a small donation to keep the Mailtrain development engines running. You can either use Bitcoin or PayPal for donations. My Bitcoin wallet is 15Z8ADxhssKUiwP3jbbqJwA21744KMCfTM

+

{{#translate}}Donate to author{{/translate}}

+

{{#translate}}If you really like Mailtrain or your business benefits from it financially then I would really appreciate a small donation to keep the Mailtrain development engines running. You can either use Bitcoin or PayPal for donations. My Bitcoin wallet is 15Z8ADxhssKUiwP3jbbqJwA21744KMCfTM{{/translate}}

- or donate using PayPal + {{#translate}}or donate using PayPal{{/translate}}

@@ -63,32 +63,31 @@
-

RSS Campaigns

-

Setup Mailtrain to track RSS feeds and if a new entry is detected in a feed then Mailtrain auto-generates a new campaign using entry data as message contents and sends it to - selected subscribers.

+

{{#translate}}RSS Campaigns{{/translate}}

+

{{#translate}}Setup Mailtrain to track RSS feeds and if a new entry is detected in a feed then Mailtrain auto-generates a new campaign using entry data as message contents and sends it to selected subscribers.{{/translate}}

-

GPG Encryption

-

If a list has a custom field for a GPG Public Key set then subscribers can upload their GPG public key to receive encrypted messages from the list.

+

{{#translate}}GPG Encryption{{/translate}}

+

{{#translate}}If a list has a custom field for a GPG Public Key set then subscribers can upload their GPG public key to receive encrypted messages from the list.{{/translate}}

-

Click stats

-

After a campaign is sent, check individual click statistics for every link included in the message.

+

{{#translate}}Click stats{{/translate}}

+

{{#translate}}After a campaign is sent, check individual click statistics for every link included in the message.{{/translate}}

-

Open source

-

Mailtrain is available under MIT license and completely open source.

+

{{#translate}}Open source{{/translate}}

+

{{#translate}}Mailtrain is available under GPLv3 license and completely open source.{{/translate}}

-

Send via any provider

-

Mailtrain recommends SendPulse even though you can use any provider that supports SMTP protocol to send out your newsletters. Bounce and complaints handling via webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for Postfix and ZoneMTA.

+

{{#translate}}Send via any provider{{/translate}}

+

{{#translate}}Mailtrain recommends SendPulse even though you can use any provider that supports SMTP protocol to send out your newsletters. Bounce and complaints handling via webhooks is supported for SES, SparkPost, SendGrid and Mailgun, also for Postfix and ZoneMTA.{{/translate}}

-

Trigger based automation

-

Define automation triggers to send specific messages when a user activates the trigger.

+

{{#translate}}Trigger based automation{{/translate}}

+

{{#translate}}Define automation triggers to send specific messages when a user activates the trigger.{{/translate}}

diff --git a/views/layout.hbs b/views/layout.hbs index 91b4b9a5..f2f1b724 100644 --- a/views/layout.hbs +++ b/views/layout.hbs @@ -34,7 +34,7 @@

-

Self hosted newsletter app built on top of Nodemailer

+

{{#translate}}Self hosted newsletter app built on top of Nodemailer{{/translate}}

- Source on GitHub + {{#translate}}Source on GitHub{{/translate}} - Subscribe to our newsletter + {{#translate}}Subscribe to our newsletter{{/translate}}

@@ -134,7 +134,7 @@ diff --git a/views/lists/create.hbs b/views/lists/create.hbs index c3422350..1ec01821 100644 --- a/views/lists/create.hbs +++ b/views/lists/create.hbs @@ -1,26 +1,26 @@ -

Create List

+

{{#translate}}Create List{{/translate}}


- +
- +
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
@@ -28,7 +28,7 @@
- +
diff --git a/views/lists/edit.hbs b/views/lists/edit.hbs index 760afcdd..ade92785 100644 --- a/views/lists/edit.hbs +++ b/views/lists/edit.hbs @@ -1,11 +1,11 @@ -

Edit List View List

+

{{#translate}}Edit List{{/translate}} {{#translate}}View List{{/translate}}


@@ -18,23 +18,23 @@
- +
- +
- +
- This is the list ID displayed to the subscribers + {{#translate}}This is the list ID displayed to the subscribers{{/translate}}
- +
- HTML is allowed + {{#translate}}HTML is allowed{{/translate}}
@@ -43,9 +43,9 @@
- +
- +
diff --git a/views/lists/fields/create.hbs b/views/lists/fields/create.hbs index 04e58aa6..dddeab85 100644 --- a/views/lists/fields/create.hbs +++ b/views/lists/fields/create.hbs @@ -1,77 +1,77 @@ -

{{list.name}} Create Custom Field

+

{{list.name}} {{#translate}}Create Custom Field{{/translate}}


- +
- +
- +
- +
- Required for group options + {{#translate}}Required for group options{{/translate}}
- +
- +
- +
- For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from \{{values}} array, for example \{{#each values}} \{{this}} \{{/each}}. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as values, otherwise you can access the JSON keys directly). + {{#translate}}For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from \{{values}} array, for example \{{#each values}} \{{this}} \{{/each}}. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as values, otherwise you can access the JSON keys directly).{{/translate}}
@@ -79,7 +79,7 @@
@@ -87,7 +87,7 @@
- +
diff --git a/views/lists/fields/edit.hbs b/views/lists/fields/edit.hbs index 33509a99..c59b3484 100644 --- a/views/lists/fields/edit.hbs +++ b/views/lists/fields/edit.hbs @@ -1,12 +1,12 @@ -

{{list.name}} Edit Custom Field Back to fields

+

{{list.name}} {{#translate}}Edit Custom Field{{/translate}} {{#translate}}Back to fields{{/translate}}


@@ -20,36 +20,36 @@
- +
- +
- +
@@ -57,43 +57,43 @@ {{#if groups}}
- +
- Required for group options + {{#translate}}Required for group options{{/translate}}
{{/if}}
- +
- - Put this tag in your content: [{{#if field.key}}{{field.key}}{{else}}TAG_VALUE{{/if}}] + + {{#translate}}Put this tag in your content:{{/translate}} [{{#if field.key}}{{field.key}}{{else}}TAG_VALUE{{/if}}]
{{#if field.isGroup}}
- +
- For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from \{{values}} array, for example \{{#each values}} \{{this}} \{{/each}}. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as values, otherwise you can access the JSON keys directly). + {{#translate}}For group elements like checkboxes you can control the appearance of the merge tag with an optional template. The template uses handlebars syntax and you can find all values from \{{values}} array, for example \{{#each values}} \{{this}} \{{/each}}. If template is not defined then multiple values are joined with commas. You can also use this template to render JSON values (if the JSON is an array then the array is exposed as values, otherwise you can access the JSON keys directly).{{/translate}}
{{else}}
- +
- +
{{/if}} @@ -102,7 +102,7 @@
@@ -111,9 +111,9 @@
- +
- +
diff --git a/views/lists/fields/fields.hbs b/views/lists/fields/fields.hbs index 532c17a2..b7826a7c 100644 --- a/views/lists/fields/fields.hbs +++ b/views/lists/fields/fields.hbs @@ -1,15 +1,15 @@ -

{{list.name}} Custom Fields

+

{{list.name}} {{#translate}}Custom Fields{{/translate}}


@@ -20,16 +20,16 @@ #
- Name + {{#translate}}Name{{/translate}} - Type + {{#translate}}Type{{/translate}} - Merge tag + {{#translate}}Merge tag{{/translate}} - Default merge tag value + {{#translate}}Default merge tag value{{/translate}}   @@ -56,7 +56,7 @@ - Edit + {{#translate}}Edit{{/translate}}
- Edit + {{#translate}}Edit{{/translate}}
- No data available in table + {{#translate}}No data available in table{{/translate}}
- Name + {{#translate}}Name{{/translate}} - ID + {{#translate}}ID{{/translate}} - Subscribers + {{#translate}}Subscribers{{/translate}} - Description + {{#translate}}Description{{/translate}}   @@ -59,7 +59,7 @@ - Edit + {{#translate}}Edit{{/translate}}
- Name + {{#translate}}Name{{/translate}} - Match + {{#translate}}Match{{/translate}}   @@ -46,7 +46,7 @@ - Edit + {{#translate}}Edit{{/translate}}
- Rule + {{#translate}}Rule{{/translate}} - Value + {{#translate}}Value{{/translate}}   @@ -57,7 +57,7 @@ - Edit + {{#translate}}Edit{{/translate}}
- Address + {{#translate}}Address{{/translate}} - Fail reason + {{#translate}}Fail reason{{/translate}}
- Address + {{#translate}}Address{{/translate}} - First Name + {{#translate}}First Name{{/translate}} - Last Name + {{#translate}}Last Name{{/translate}} @@ -93,10 +93,10 @@ - Status + {{#translate}}Status{{/translate}} - Created + {{#translate}}Created{{/translate}}
- Created + {{#translate}}Created{{/translate}} - Finished + {{#translate}}Finished{{/translate}} - Type + {{#translate}}Type{{/translate}} - Added + {{#translate}}Added{{/translate}} - Updated + {{#translate}}Updated{{/translate}} - Failed + {{#translate}}Failed{{/translate}} - Status + {{#translate}}Status{{/translate}} @@ -181,11 +181,11 @@ {{/if}} -
+ - +
- No data available in table + {{#translate}}No data available in table{{/translate}}