Merge pull request #1209 from coralproject/i18n-serverside-plugins

i18n + Email Support
This commit is contained in:
Kim Gardner
2017-12-18 17:03:47 -06:00
committed by GitHub
9 changed files with 133 additions and 76 deletions
+7
View File
@@ -20,6 +20,13 @@ const CONFIG = {
// WEBPACK indicates when webpack is currently building.
WEBPACK: process.env.WEBPACK === 'TRUE',
// EMAIL_SUBJECT_PREFIX is the string before emails in the subject.
EMAIL_SUBJECT_PREFIX: process.env.TALK_EMAIL_SUBJECT_PREFIX || '[Talk]',
// DEFAULT_LANG is the default language used for server sent emails and
// rendered text.
DEFAULT_LANG: process.env.TALK_DEFAULT_LANG || 'en',
// When TRUE, it ensures that database indexes created in core will not add
// indexes.
CREATE_MONGO_INDEXES: process.env.DISABLE_CREATE_MONGO_INDEXES !== 'TRUE',
+7 -1
View File
@@ -460,4 +460,10 @@ Could be read as:
## TALK_DISABLE_IGNORE_FLAGS_AGAINST_STAFF
When `TRUE`, staff members will have their accounts and comments moderated the
same as any other user in the system. (Default `FALSE`)
same as any other user in the system. (Default `FALSE`)
## TALK_EMAIL_SUBJECT_PREFIX
The prefix for the subject of emails sent. An email with the specified subject
of `Email Confirmation` would then be sent as `[Talk] Email Confirmation`.
(Default `[Talk]`)
+5
View File
@@ -166,6 +166,11 @@ en:
minute: "minute"
minutes_plural: "minutes"
email:
suspended:
subject: "Your account has been suspended"
banned:
subject: "Your account has been banned"
body: "In accordance with The Coral Projects community guidelines, your account has been banned. You are now longer allowed to comment, flag or engage with our community."
confirm:
has_been_requested: "A email confirmation has been requested for the following account:"
to_confirm: "To confirm the account, please visit the following link:"
+6
View File
@@ -0,0 +1,6 @@
const i18n = require('../services/i18n');
module.exports = (req, res, next) => {
res.locals.t = i18n.request(req);
next();
};
+7 -6
View File
@@ -7,7 +7,7 @@ const debug = require('debug')('talk:routes');
const enabled = require('debug').enabled;
const errors = require('../errors');
const express = require('express');
const i18n = require('../services/i18n');
const i18n = require('../middleware/i18n');
const path = require('path');
const plugins = require('../services/plugins');
const pubsub = require('../middleware/pubsub');
@@ -64,6 +64,9 @@ if (!DISABLE_STATIC_SERVER) {
router.get('/embed.js.map', serveFile('../dist/embed.js.map'));
}
// Add the i18n middleware to all routes.
router.use(i18n);
//==============================================================================
// STATIC ROUTES
//==============================================================================
@@ -116,19 +119,19 @@ if (process.env.NODE_ENV !== 'production') {
});
});
// GraphQL documention.
// GraphQL documentation.
router.get('/admin/docs', (req, res) => {
res.render('admin/docs');
});
}
router.use('/api/v1', require('./api'));
//==============================================================================
// ROUTES
//==============================================================================
router.use('/api/v1', require('./api'));
// Development routes.
if (process.env.NODE_ENV !== 'production') {
router.use('/assets', staticTemplate, require('./assets'));
@@ -184,8 +187,6 @@ router.use('/', (err, req, res, next) => {
console.error(err);
}
i18n.init(req);
if (err instanceof errors.APIError) {
res.status(err.status);
res.render('error', {
+1 -1
View File
@@ -1,3 +1,3 @@
<p><%= t('email.confirm.has_been_requested') %> <b><%= email %></b>.</p>
<p><%= t('email.confirm.to_confirm') %> <a href="<%= BASE_URL %>admin/confirm-email#<%= token %>">Confirm Email</a></p>
<p><%= t('email.confirm.to_confirm') %> <a href="<%= BASE_URL %>admin/confirm-email#<%= token %>"><%= t('email.confirm.confirm_email') %></a></p>
<p><%= t('email.confirm.if_you_did_not') %></p>
+76 -42
View File
@@ -1,57 +1,91 @@
const has = require('lodash/has');
const get = require('lodash/get');
const yaml = require('yamljs');
const da = yaml.load('./locales/da.yml');
const es = yaml.load('./locales/es.yml');
const en = yaml.load('./locales/en.yml');
const fr = yaml.load('./locales/fr.yml');
const pt_BR = yaml.load('./locales/pt_BR.yml');
const fs = require('fs');
const path = require('path');
const debug = require('debug')('talk:services:i18n');
const accepts = require('accepts');
const _ = require('lodash');
const yaml = require('yamljs');
const plugins = require('./plugins');
const {DEFAULT_LANG} = require('../config');
// default language
let defaultLanguage = 'en';
let language = defaultLanguage;
const languages = ['en', 'da', 'es', 'fr', 'pt_BR'];
const resolve = (...paths) => path.resolve(path.join(__dirname, '..', 'locales', ...paths));
const translations = Object.assign(en, es, fr, pt_BR, da);
// Load all the translations.
let translations = fs.readdirSync(resolve())
// Resolve all the filenames relative the the locales directory.
.map((filename) => resolve(filename))
// Translations are only yml/yaml files.
.filter((filename) => /\.(yaml|yml)$/.test(filename))
// Load the translation files from disk.
.map((filename) => fs.readFileSync(filename, 'utf8'))
// Load the translation files.
.reduce((packs, contents) => {
const pack = yaml.parse(contents);
return _.merge(packs, pack);
}, {});
// Create a list of all supported translations.
const languages = Object.keys(translations);
let loadedPluginTranslations = false;
const loadPluginTranslations = () => {
if (loadedPluginTranslations) {
return;
}
// Load the plugin translations.
plugins.get('server', 'translations').forEach(({plugin, translations: filename}) => {
debug(`added plugin '${plugin.name}'`);
const pack = yaml.parse(fs.readFileSync(filename, 'utf8'));
translations = _.merge(translations, pack);
});
loadedPluginTranslations = true;
};
const t = (language) => (key, ...replacements) => {
// Loads the translations into the translations array from plugins. This is
// done lazily to ensure that we don't have an import cycle.
loadPluginTranslations();
// Check if the translation exists on the object.
if (_.has(translations[language], key)) {
// Get the translation value.
let translation = _.get(translations[language], key);
// Replace any {n} with the arguments passed to this method.
replacements.forEach((str, n) => {
translation = translation.replace(new RegExp(`\\{${n}\\}`, 'g'), str);
});
return translation;
} else {
console.warn(`${key} language key not set`);
return key;
}
};
/**
* Exposes a service object to allow translations.
* @type {Object}
*/
const i18n = {
/**
* Create the new Task kue.
*/
init(req) {
request(req) {
const lang = accepts(req).language(languages);
language = lang ? lang : defaultLanguage;
},
const language = lang ? lang : DEFAULT_LANG;
/**
* Translates a key.
*/
t(key, ...replacements) {
if (has(translations[language], key)) {
let translation = get(translations[language], key);
// replace any {n} with the arguments passed to this method
replacements.forEach((str, i) => {
translation = translation.replace(new RegExp(`\\{${i}\\}`, 'g'), str);
});
return translation;
} else {
console.warn(`${key} language key not set`);
return key;
}
return t(language);
},
t: t(DEFAULT_LANG),
};
module.exports = i18n;
+6 -5
View File
@@ -13,7 +13,8 @@ const {
SMTP_USERNAME,
SMTP_PORT,
SMTP_PASSWORD,
SMTP_FROM_ADDRESS
SMTP_FROM_ADDRESS,
EMAIL_SUBJECT_PREFIX,
} = require('../config');
// load all the templates as strings
@@ -95,12 +96,12 @@ const mailer = module.exports = {
}
// Prefix the subject with `[Talk]`.
subject = `[Talk] ${subject}`;
subject = `${EMAIL_SUBJECT_PREFIX} ${subject}`;
attachLocals(locals);
// Attach the templating function.
locals['t'] = i18n.t;
// Attach the translation function.
locals.t = i18n.t;
return Promise.all([
@@ -112,7 +113,7 @@ const mailer = module.exports = {
])
.then(([html, text]) => {
// Create the job.
// Create the job.
return mailer.task.create({
title: 'Mail',
message: {
+18 -21
View File
@@ -24,6 +24,7 @@ const RECAPTCHA_INCORRECT_TRIGGER = 5; // after 3 incorrect attempts, recaptcha
const ActionsService = require('./actions');
const MailerService = require('./mailer');
const Wordlist = require('./wordlist');
const i18n = require('./i18n');
const Domainlist = require('./domainlist');
const {escapeRegExp} = require('./regex');
@@ -430,16 +431,14 @@ module.exports = class UsersService {
if (status === 'BANNED') {
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
if (localProfile) {
const options =
{
template: 'banned', // needed to know which template to render!
locals: { // specifies the template locals.
body: 'In accordance with The Coral Projects community guidelines, your account has been banned. You are now longer allowed to comment, flag or engage with our community.'
},
subject: 'Your account has been banned',
to: localProfile.id // This only works if the user has registered via e-mail.
// We may want a standard way to access a user's e-mail address in the future
};
const options = {
template: 'banned',
locals: {
body: i18n.t('email.banned.body'),
},
subject: i18n.t('email.banned.subject'),
to: localProfile.id
};
await MailerService.sendSimple(options);
}
}
@@ -467,16 +466,14 @@ module.exports = class UsersService {
if (message) {
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
if (localProfile) {
const options =
{
template: 'suspension', // needed to know which template to render!
locals: { // specifies the template locals.
body: message
},
subject: 'Your account has been suspended',
to: localProfile.id // This only works if the user has registered via e-mail.
// We may want a standard way to access a user's e-mail address in the future
};
const options = {
template: 'suspension',
locals: {
body: message
},
subject: i18n.t('email.suspended.subject'),
to: localProfile.id,
};
await MailerService.sendSimple(options);
}
@@ -509,7 +506,7 @@ module.exports = class UsersService {
locals: { // specifies the template locals.
body: message
},
subject: 'Email Suspension',
subject: i18n.t('email.suspended.subject'),
to: localProfile.id // This only works if the user has registered via e-mail.
// We may want a standard way to access a user's e-mail address in the future
};