mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Harvest server side strings
closes #5617 - Replace all hard-coded server-side strings with i18n translations
This commit is contained in:
parent
52b76f278a
commit
7abcc43907
@ -7,6 +7,7 @@ var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n'),
|
||||
authentication;
|
||||
|
||||
function setupTasks(object) {
|
||||
@ -38,7 +39,7 @@ function setupTasks(object) {
|
||||
// Handles the additional values set by the setup screen.
|
||||
if (!_.isEmpty(setupUser.blogTitle)) {
|
||||
userSettings.push({key: 'title', value: setupUser.blogTitle});
|
||||
userSettings.push({key: 'description', value: 'Thoughts, stories and ideas.'});
|
||||
userSettings.push({key: 'description', value: i18n.t('common.api.authentication.sampleBlogDescription')});
|
||||
}
|
||||
|
||||
setupUser = user.toJSON(internal);
|
||||
@ -69,7 +70,7 @@ authentication = {
|
||||
var setup = result.setup[0].status;
|
||||
|
||||
if (!setup) {
|
||||
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.setupMustBeCompleted')));
|
||||
}
|
||||
|
||||
return utils.checkObject(object, 'passwordreset');
|
||||
@ -77,7 +78,7 @@ authentication = {
|
||||
if (checkedPasswordReset.passwordreset[0].email) {
|
||||
email = checkedPasswordReset.passwordreset[0].email;
|
||||
} else {
|
||||
return Promise.reject(new errors.BadRequestError('No email provided.'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.authentication.noEmailProvided')));
|
||||
}
|
||||
|
||||
return settings.read({context: {internal: true}, key: 'dbHash'})
|
||||
@ -94,7 +95,7 @@ authentication = {
|
||||
mail: [{
|
||||
message: {
|
||||
to: email,
|
||||
subject: 'Reset Password',
|
||||
subject: i18n.t('common.api.authentication.mail.resetPassword'),
|
||||
html: emailContent.html,
|
||||
text: emailContent.text
|
||||
},
|
||||
@ -103,7 +104,7 @@ authentication = {
|
||||
};
|
||||
return mail.send(payload, {context: {internal: true}});
|
||||
}).then(function () {
|
||||
return Promise.resolve({passwordreset: [{message: 'Check your email for further instructions.'}]});
|
||||
return Promise.resolve({passwordreset: [{message: i18n.t('common.api.authentication.mail.checkEmailForInstructions')}]});
|
||||
}).catch(function (error) {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
@ -125,7 +126,7 @@ authentication = {
|
||||
var setup = result.setup[0].status;
|
||||
|
||||
if (!setup) {
|
||||
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.setupMustBeCompleted')));
|
||||
}
|
||||
|
||||
return utils.checkObject(object, 'passwordreset');
|
||||
@ -143,7 +144,7 @@ authentication = {
|
||||
dbHash: dbHash
|
||||
});
|
||||
}).then(function () {
|
||||
return Promise.resolve({passwordreset: [{message: 'Password changed successfully.'}]});
|
||||
return Promise.resolve({passwordreset: [{message: i18n.t('common.api.authentication.mail.passwordChanged')}]});
|
||||
}).catch(function (error) {
|
||||
return Promise.reject(new errors.UnauthorizedError(error.message));
|
||||
});
|
||||
@ -166,7 +167,7 @@ authentication = {
|
||||
var setup = result.setup[0].status;
|
||||
|
||||
if (!setup) {
|
||||
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.setupMustBeCompleted')));
|
||||
}
|
||||
|
||||
return utils.checkObject(object, 'invitation');
|
||||
@ -189,7 +190,7 @@ authentication = {
|
||||
// Setting the slug to '' has the model regenerate the slug from the user's name
|
||||
return dataProvider.User.edit({name: name, email: email, slug: ''}, {id: user.id});
|
||||
}).then(function () {
|
||||
return Promise.resolve({invitation: [{message: 'Invitation accepted.'}]});
|
||||
return Promise.resolve({invitation: [{message: i18n.t('common.api.authentication.mail.invitationAccepted')}]});
|
||||
}).catch(function (error) {
|
||||
return Promise.reject(new errors.UnauthorizedError(error.message));
|
||||
});
|
||||
@ -207,7 +208,7 @@ authentication = {
|
||||
var setup = result.setup[0].status;
|
||||
|
||||
if (!setup) {
|
||||
return Promise.reject(new errors.NoPermissionError('Setup must be completed before making this request.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.setupMustBeCompleted')));
|
||||
}
|
||||
|
||||
if (options.email) {
|
||||
@ -219,7 +220,7 @@ authentication = {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new errors.BadRequestError('The server did not receive a valid email'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.authentication.invalidEmailReceived')));
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -243,7 +244,7 @@ authentication = {
|
||||
var setup = result.setup[0].status;
|
||||
|
||||
if (setup) {
|
||||
return Promise.reject(new errors.NoPermissionError('Setup has already been completed.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.setupAlreadyCompleted')));
|
||||
}
|
||||
|
||||
return setupTasks(object);
|
||||
@ -258,7 +259,7 @@ authentication = {
|
||||
}).then(function (emailContent) {
|
||||
var message = {
|
||||
to: setupUser.email,
|
||||
subject: 'Your New Ghost Blog',
|
||||
subject: i18n.t('common.api.authentication.mail.yourNewGhostBlog'),
|
||||
html: emailContent.html,
|
||||
text: emailContent.text
|
||||
},
|
||||
@ -272,8 +273,8 @@ authentication = {
|
||||
mail.send(payload, {context: {internal: true}}).catch(function (error) {
|
||||
errors.logError(
|
||||
error.message,
|
||||
'Unable to send welcome email, your blog will continue to function.',
|
||||
'Please see http://support.ghost.org/mail/ for instructions on configuring email.'
|
||||
i18n.t('errors.api.authentication.unableToSendWelcomeEmail', {url: 'http://support.ghost.org/mail/'}),
|
||||
i18n.t('errors.api.authentication.checkEmailConfigInstructions')
|
||||
);
|
||||
});
|
||||
}).then(function () {
|
||||
@ -283,14 +284,14 @@ authentication = {
|
||||
|
||||
updateSetup: function updateSetup(object, options) {
|
||||
if (!options.context || !options.context.user) {
|
||||
return Promise.reject(new errors.NoPermissionError('You are not logged in.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.notLoggedIn')));
|
||||
}
|
||||
|
||||
return dataProvider.User.findOne({role: 'Owner', status: 'all'}).then(function (result) {
|
||||
var user = result.toJSON();
|
||||
|
||||
if (user.id !== options.context.user) {
|
||||
return Promise.reject(new errors.NoPermissionError('You are not the blog owner.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.authentication.notTheBlogOwner')));
|
||||
}
|
||||
|
||||
return setupTasks(object);
|
||||
@ -307,14 +308,14 @@ authentication = {
|
||||
} else if (object.token_type_hint && object.token_type_hint === 'refresh_token') {
|
||||
token = dataProvider.Refreshtoken;
|
||||
} else {
|
||||
return errors.BadRequestError('Invalid token_type_hint given.');
|
||||
return errors.BadRequestError(i18n.t('errors.api.authentication.invalidTokenTypeHint'));
|
||||
}
|
||||
|
||||
return token.destroyByToken({token: object.token}).then(function () {
|
||||
return Promise.resolve({token: object.token});
|
||||
}, function () {
|
||||
// On error we still want a 200. See https://tools.ietf.org/html/rfc7009#page-5
|
||||
return Promise.resolve({token: object.token, error: 'Invalid token provided'});
|
||||
return Promise.resolve({token: object.token, error: i18n.t('errors.api.authentication.invalidTokenProvided')});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ var Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
docName = 'clients',
|
||||
clients;
|
||||
@ -52,7 +53,7 @@ clients = {
|
||||
return {clients: [result.toJSON(options)]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Client not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('common.api.clients.clientNotFound')));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ var _ = require('lodash'),
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
Promise = require('bluebird'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
configuration;
|
||||
|
||||
@ -58,7 +59,7 @@ configuration = {
|
||||
value: data[options.key]
|
||||
}]});
|
||||
} else {
|
||||
return Promise.reject(new errors.NotFoundError('Invalid key'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.configuration.invalidKey')));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -8,6 +8,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
api = {},
|
||||
docName = 'db',
|
||||
@ -66,13 +67,13 @@ db = {
|
||||
function validate(options) {
|
||||
// Check if a file was provided
|
||||
if (!utils.checkFileExists(options, 'importfile')) {
|
||||
return Promise.reject(new errors.ValidationError('Please select a file to import.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.db.selectFileToImport')));
|
||||
}
|
||||
|
||||
// Check if the file is valid
|
||||
if (!utils.checkFileIsValid(options.importfile, importer.getTypes(), importer.getExtensions())) {
|
||||
return Promise.reject(new errors.UnsupportedMediaTypeError(
|
||||
'Unsupported file. Please try any of the following formats: ' +
|
||||
i18n.t('errors.api.db.unsupportedFile') +
|
||||
_.reduce(importer.getExtensions(), function (memo, ext) {
|
||||
return memo ? memo + ', ' + ext : ext;
|
||||
})
|
||||
|
@ -12,9 +12,9 @@ var _ = require('lodash').runInContext(),
|
||||
fs = require('fs'),
|
||||
templatesDir = path.resolve(__dirname, '..', 'mail', 'templates'),
|
||||
htmlToText = require('html-to-text'),
|
||||
|
||||
readFile = Promise.promisify(fs.readFile),
|
||||
docName = 'mail',
|
||||
i18n = require('../i18n'),
|
||||
mail;
|
||||
|
||||
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
|
||||
@ -112,7 +112,7 @@ mail = {
|
||||
mail: [{
|
||||
message: {
|
||||
to: result.get('email'),
|
||||
subject: 'Test Ghost Email',
|
||||
subject: i18n.t('common.api.mail.testGhostEmail'),
|
||||
html: content.html,
|
||||
text: content.text
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ var Promise = require('bluebird'),
|
||||
utils = require('./utils'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
canThis = permissions.canThis,
|
||||
i18n = require('../i18n'),
|
||||
|
||||
// Holds the persistent notifications
|
||||
notificationsStore = [],
|
||||
@ -30,7 +31,7 @@ notifications = {
|
||||
return canThis(options.context).browse.notification().then(function () {
|
||||
return {notifications: notificationsStore};
|
||||
}, function () {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to browse notifications.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToBrowseNotif')));
|
||||
});
|
||||
},
|
||||
|
||||
@ -65,7 +66,7 @@ notifications = {
|
||||
return canThis(options.context).add.notification().then(function () {
|
||||
return options;
|
||||
}, function () {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to add notifications.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToAddNotif')));
|
||||
});
|
||||
}
|
||||
|
||||
@ -129,7 +130,7 @@ notifications = {
|
||||
return canThis(options.context).destroy.notification().then(function () {
|
||||
return options;
|
||||
}, function () {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to destroy notifications.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToDestroyNotif')));
|
||||
});
|
||||
}
|
||||
|
||||
@ -140,12 +141,12 @@ notifications = {
|
||||
|
||||
if (notification && !notification.dismissible) {
|
||||
return Promise.reject(
|
||||
new errors.NoPermissionError('You do not have permission to dismiss this notification.')
|
||||
new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToDismissNotif'))
|
||||
);
|
||||
}
|
||||
|
||||
if (!notification) {
|
||||
return Promise.reject(new errors.NotFoundError('Notification does not exist.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.notifications.notificationDoesNotExist')));
|
||||
}
|
||||
|
||||
notificationsStore = _.reject(notificationsStore, function (element) {
|
||||
@ -181,7 +182,7 @@ notifications = {
|
||||
|
||||
return notificationsStore;
|
||||
}, function () {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to destroy notifications.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToDestroyNotif')));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ var Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
docName = 'posts',
|
||||
allowedIncludes = [
|
||||
@ -106,7 +107,7 @@ posts = {
|
||||
return {posts: [result.toJSON(options)]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Post not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound')));
|
||||
});
|
||||
},
|
||||
|
||||
@ -153,7 +154,7 @@ posts = {
|
||||
return {posts: [post]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Post not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound')));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -7,6 +7,7 @@ var _ = require('lodash'),
|
||||
canThis = require('../permissions').canThis,
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
docName = 'settings',
|
||||
settings,
|
||||
@ -36,9 +37,9 @@ var _ = require('lodash'),
|
||||
*/
|
||||
updateConfigCache = function () {
|
||||
var errorMessages = [
|
||||
'Error: Invalid JSON in settings.labs',
|
||||
'The column with key "labs" could not be parsed as JSON',
|
||||
'Please try updating a setting on the labs page, or manually editing your DB'
|
||||
i18n.t('errors.api.settings.invalidJsonInLabs'),
|
||||
i18n.t('errors.api.settings.labsColumnCouldNotBeParsed'),
|
||||
i18n.t('errors.api.settings.tryUpdatingLabs')
|
||||
], labsValue = {};
|
||||
|
||||
if (settingsCache.labs && settingsCache.labs.value) {
|
||||
@ -245,7 +246,7 @@ populateDefaultSetting = function (key) {
|
||||
}
|
||||
|
||||
// TODO: Different kind of error?
|
||||
return Promise.reject(new errors.NotFoundError('Problem finding setting: ' + key));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.settings.problemFindingSetting', {key: key})));
|
||||
});
|
||||
};
|
||||
|
||||
@ -260,12 +261,12 @@ canEditAllSettings = function (settingsInfo, options) {
|
||||
var checkSettingPermissions = function (setting) {
|
||||
if (setting.type === 'core' && !(options.context && options.context.internal)) {
|
||||
return Promise.reject(
|
||||
new errors.NoPermissionError('Attempted to access core setting from external request')
|
||||
new errors.NoPermissionError(i18n.t('errors.api.settings.accessCoreSettingFromExtReq'))
|
||||
);
|
||||
}
|
||||
|
||||
return canThis(options.context).edit.setting(setting.key).catch(function () {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to edit settings.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.settings.noPermissionToEditSettings')));
|
||||
});
|
||||
},
|
||||
checks = _.map(settingsInfo, function (settingInfo) {
|
||||
@ -344,7 +345,7 @@ settings = {
|
||||
|
||||
if (setting.type === 'core' && !(options.context && options.context.internal)) {
|
||||
return Promise.reject(
|
||||
new errors.NoPermissionError('Attempted to access core setting from external request')
|
||||
new errors.NoPermissionError(i18n.t('errors.api.settings.accessCoreSettingFromExtReq'))
|
||||
);
|
||||
}
|
||||
|
||||
@ -355,7 +356,7 @@ settings = {
|
||||
return canThis(options.context).read.setting(options.key).then(function () {
|
||||
return settingsResult(result);
|
||||
}, function () {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to read settings.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.settings.noPermissionToReadSettings')));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,7 @@ var dataProvider = require('../models'),
|
||||
Promise = require('bluebird'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../i18n'),
|
||||
docName = 'slugs',
|
||||
|
||||
slugs,
|
||||
@ -45,7 +46,7 @@ slugs = {
|
||||
*/
|
||||
function checkAllowedTypes(options) {
|
||||
if (allowedTypes[options.type] === undefined) {
|
||||
return Promise.reject(new errors.BadRequestError('Unknown slug type \'' + options.type + '\'.'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.slugs.unknownSlugType', {type: options.type})));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
@ -71,7 +72,7 @@ slugs = {
|
||||
// Pipeline calls each task passing the result of one to be the arguments for the next
|
||||
return pipeline(tasks, options).then(function (slug) {
|
||||
if (!slug) {
|
||||
return Promise.reject(new errors.InternalServerError('Could not generate slug.'));
|
||||
return Promise.reject(new errors.InternalServerError(i18n.t('errors.api.slugs.couldNotGenerateSlug')));
|
||||
}
|
||||
|
||||
return {slugs: [{slug: slug}]};
|
||||
|
@ -6,6 +6,7 @@ var Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
docName = 'tags',
|
||||
allowedIncludes = ['count.posts'],
|
||||
@ -80,7 +81,7 @@ tags = {
|
||||
return {tags: [result.toJSON(options)]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Tag not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.tags.tagNotFound')));
|
||||
});
|
||||
},
|
||||
|
||||
@ -154,7 +155,7 @@ tags = {
|
||||
return {tags: [tag]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Tag not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.tags.tagNotFound')));
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -7,6 +7,7 @@ var Promise = require('bluebird'),
|
||||
settings = require('./settings'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
docName = 'themes',
|
||||
themes;
|
||||
@ -148,7 +149,7 @@ themes = {
|
||||
|
||||
// Check whether the request is properly formatted.
|
||||
if (!_.isArray(object.themes)) {
|
||||
return Promise.reject(new errors.BadRequestError('Invalid request.'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.themes.invalidRequest')));
|
||||
}
|
||||
|
||||
themeName = object.themes[0].uuid;
|
||||
@ -166,7 +167,7 @@ themes = {
|
||||
});
|
||||
|
||||
if (!theme) {
|
||||
return Promise.reject(new errors.BadRequestError('Theme does not exist.'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.themes.themeDoesNotExist')));
|
||||
}
|
||||
|
||||
if (!theme.name) {
|
||||
|
@ -4,6 +4,7 @@ var config = require('../config'),
|
||||
storage = require('../storage'),
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
upload;
|
||||
|
||||
@ -27,12 +28,12 @@ upload = {
|
||||
|
||||
// Check if a file was provided
|
||||
if (!utils.checkFileExists(options, 'uploadimage')) {
|
||||
return Promise.reject(new errors.NoPermissionError('Please select an image.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.upload.pleaseSelectImage')));
|
||||
}
|
||||
|
||||
// Check if the file is valid
|
||||
if (!utils.checkFileIsValid(options.uploadimage, config.uploads.contentTypes, config.uploads.extensions)) {
|
||||
return Promise.reject(new errors.UnsupportedMediaTypeError('Please select a valid image.'));
|
||||
return Promise.reject(new errors.UnsupportedMediaTypeError(i18n.t('errors.api.upload.pleaseSelectValidImage')));
|
||||
}
|
||||
|
||||
filepath = options.uploadimage.path;
|
||||
|
@ -11,6 +11,7 @@ var Promise = require('bluebird'),
|
||||
config = require('../config'),
|
||||
mail = require('./mail'),
|
||||
pipeline = require('../utils/pipeline'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
docName = 'users',
|
||||
// TODO: implement created_by, updated_by
|
||||
@ -49,7 +50,7 @@ sendInviteEmail = function sendInviteEmail(user) {
|
||||
mail: [{
|
||||
message: {
|
||||
to: user.email,
|
||||
subject: emailData.invitedByName + ' has invited you to join ' + emailData.blogName,
|
||||
subject: i18n.t('common.api.users.mail.invitedByName', {invitedByName: emailData.invitedByName, blogName: emailData.blogName}),
|
||||
html: emailContent.html,
|
||||
text: emailContent.text
|
||||
},
|
||||
@ -137,7 +138,7 @@ users = {
|
||||
return {users: [result.toJSON(options)]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('User not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.users.userNotFound')));
|
||||
});
|
||||
},
|
||||
|
||||
@ -192,14 +193,14 @@ users = {
|
||||
var contextRoleId = contextUser.related('roles').toJSON(options)[0].id;
|
||||
|
||||
if (roleId !== contextRoleId && editedUserId === contextUser.id) {
|
||||
return Promise.reject(new errors.NoPermissionError('You cannot change your own role.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.users.cannotChangeOwnRole')));
|
||||
}
|
||||
|
||||
return dataProvider.User.findOne({role: 'Owner'}).then(function (owner) {
|
||||
if (contextUser.id !== owner.id) {
|
||||
if (editedUserId === owner.id) {
|
||||
if (owner.related('roles').at(0).id !== roleId) {
|
||||
return Promise.reject(new errors.NoPermissionError('Cannot change Owner\'s role.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.users.cannotChangeOwnersRole')));
|
||||
}
|
||||
} else if (roleId !== contextRoleId) {
|
||||
return canThis(options.context).assign.role(role).then(function () {
|
||||
@ -212,7 +213,7 @@ users = {
|
||||
});
|
||||
});
|
||||
}).catch(function handleError(error) {
|
||||
return errors.formatAndRejectAPIError(error, 'You do not have permission to edit this user');
|
||||
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToEditUser'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -239,7 +240,7 @@ users = {
|
||||
return {users: [result.toJSON(options)]};
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('User not found.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.users.userNotFound')));
|
||||
});
|
||||
},
|
||||
|
||||
@ -269,7 +270,7 @@ users = {
|
||||
// Make sure user is allowed to add a user with this role
|
||||
return dataProvider.Role.findOne({id: roleId}).then(function (role) {
|
||||
if (role.get('name') === 'Owner') {
|
||||
return Promise.reject(new errors.NoPermissionError('Not allowed to create an owner user.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.users.notAllowedToCreateOwner')));
|
||||
}
|
||||
|
||||
return canThis(options.context).assign.role(role);
|
||||
@ -280,7 +281,7 @@ users = {
|
||||
|
||||
return options;
|
||||
}).catch(function handleError(error) {
|
||||
return errors.formatAndRejectAPIError(error, 'You do not have permission to add this user');
|
||||
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToAddUser'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -299,7 +300,7 @@ users = {
|
||||
newUser.password = globalUtils.uid(50);
|
||||
newUser.status = 'invited';
|
||||
} else {
|
||||
return Promise.reject(new errors.BadRequestError('No email provided.'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.users.noEmailProvided')));
|
||||
}
|
||||
|
||||
return dataProvider.User.getByEmail(
|
||||
@ -312,7 +313,7 @@ users = {
|
||||
if (foundUser.get('status') === 'invited' || foundUser.get('status') === 'invited-pending') {
|
||||
return foundUser;
|
||||
} else {
|
||||
return Promise.reject(new errors.BadRequestError('User is already registered.'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.users.userAlreadyRegistered')));
|
||||
}
|
||||
}
|
||||
}).then(function (invitedUser) {
|
||||
@ -331,7 +332,8 @@ users = {
|
||||
return Promise.resolve({users: [user]});
|
||||
}).catch(function (error) {
|
||||
if (error && error.errorType === 'EmailError') {
|
||||
error.message = 'Error sending email: ' + error.message + ' Please check your email settings and resend the invitation.';
|
||||
error.message = i18n.t('errors.api.users.errorSendingEmail.error', {message: error.message}) + ' ' +
|
||||
i18n.t('errors.api.users.errorSendingEmail.help');
|
||||
errors.logWarn(error.message);
|
||||
|
||||
// If sending the invitation failed, set status to invited-pending
|
||||
@ -375,7 +377,7 @@ users = {
|
||||
options.status = 'all';
|
||||
return options;
|
||||
}).catch(function handleError(error) {
|
||||
return errors.formatAndRejectAPIError(error, 'You do not have permission to destroy this user.');
|
||||
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToDestroyUser'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -442,7 +444,7 @@ users = {
|
||||
return canThis(options.context).edit.user(options.data.password[0].user_id).then(function permissionGranted() {
|
||||
return options;
|
||||
}).catch(function (error) {
|
||||
return errors.formatAndRejectAPIError(error, 'You do not have permission to change the password for this user');
|
||||
return errors.formatAndRejectAPIError(error, i18n.t('errors.api.users.noPermissionToChangeUsersPwd'));
|
||||
});
|
||||
}
|
||||
|
||||
@ -469,7 +471,7 @@ users = {
|
||||
|
||||
// Pipeline calls each task passing the result of one to be the arguments for the next
|
||||
return pipeline(tasks, object, options).then(function formatResponse() {
|
||||
return Promise.resolve({password: [{message: 'Password changed successfully.'}]});
|
||||
return Promise.resolve({password: [{message: i18n.t('notices.api.users.pwdChangedSuccessfully')}]});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -6,6 +6,7 @@ var Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
permissions = require('../permissions'),
|
||||
validation = require('../data/validation'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
utils;
|
||||
|
||||
@ -211,7 +212,7 @@ utils = {
|
||||
return options;
|
||||
}).catch(errors.NoPermissionError, function handleNoPermissionError(error) {
|
||||
// pimp error message
|
||||
error.message = 'You do not have permission to ' + method + ' ' + docName;
|
||||
error.message = i18n.t('errors.api.utils.noPermissionToCall', {method: method, docName: docName});
|
||||
// forward error to next catch()
|
||||
return Promise.reject(error);
|
||||
}).catch(function handleError(error) {
|
||||
@ -271,7 +272,7 @@ utils = {
|
||||
*/
|
||||
checkObject: function (object, docName, editId) {
|
||||
if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) {
|
||||
return errors.logAndRejectError(new errors.BadRequestError('No root key (\'' + docName + '\') provided.'));
|
||||
return errors.logAndRejectError(new errors.BadRequestError(i18n.t('errors.api.utils.noRootKeyProvided', {docName: docName})));
|
||||
}
|
||||
|
||||
// convert author property to author_id to match the name in the database
|
||||
@ -283,7 +284,7 @@ utils = {
|
||||
}
|
||||
|
||||
if (editId && object[docName][0].id && parseInt(editId, 10) !== parseInt(object[docName][0].id, 10)) {
|
||||
return errors.logAndRejectError(new errors.BadRequestError('Invalid id provided.'));
|
||||
return errors.logAndRejectError(new errors.BadRequestError(i18n.t('errors.api.utils.invalidIdProvided')));
|
||||
}
|
||||
|
||||
return Promise.resolve(object);
|
||||
|
@ -4,6 +4,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
api = require('../api'),
|
||||
loader = require('./loader'),
|
||||
i18n = require('../i18n'),
|
||||
// Holds the available apps
|
||||
availableApps = {};
|
||||
|
||||
@ -44,9 +45,9 @@ module.exports = {
|
||||
});
|
||||
} catch (e) {
|
||||
errors.logError(
|
||||
'Failed to parse activeApps setting value: ' + e.message,
|
||||
'Your apps will not be loaded.',
|
||||
'Check your settings table for typos in the activeApps value. It should look like: ["app-1", "app2"] (double quotes required).'
|
||||
i18n.t('errors.apps.failedToParseActiveAppsSettings.error', {message: e.message}),
|
||||
i18n.t('errors.apps.failedToParseActiveAppsSettings.context'),
|
||||
i18n.t('errors.apps.failedToParseActiveAppsSettings.help')
|
||||
);
|
||||
|
||||
return Promise.resolve();
|
||||
@ -86,8 +87,8 @@ module.exports = {
|
||||
}).catch(function (err) {
|
||||
errors.logError(
|
||||
err.message || err,
|
||||
'The app will not be loaded',
|
||||
'Check with the app creator, or read the app documentation for more details on app requirements'
|
||||
i18n.t('errors.apps.appWillNotBeLoaded.error'),
|
||||
i18n.t('errors.apps.appWillNotBeLoaded.help')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -7,6 +7,7 @@ var path = require('path'),
|
||||
AppSandbox = require('./sandbox'),
|
||||
AppDependencies = require('./dependencies'),
|
||||
AppPermissions = require('./permissions'),
|
||||
i18n = require('../i18n'),
|
||||
loader;
|
||||
|
||||
// Get the full path to an app by name
|
||||
@ -66,7 +67,7 @@ loader = {
|
||||
|
||||
return perms.read().catch(function (err) {
|
||||
// Provide a helpful error about which app
|
||||
return Promise.reject(new Error('Error loading app named ' + name + '; problem reading permissions: ' + err.message));
|
||||
return Promise.reject(new Error(i18n.t('errors.apps.permissionsErrorLoadingApp.error', {name: name, message: err.message})));
|
||||
});
|
||||
})
|
||||
.then(function (appPerms) {
|
||||
@ -76,7 +77,7 @@ loader = {
|
||||
|
||||
// Check for an install() method on the app.
|
||||
if (!_.isFunction(app.install)) {
|
||||
return Promise.reject(new Error('Error loading app named ' + name + '; no install() method defined.'));
|
||||
return Promise.reject(new Error(i18n.t('errors.apps.noInstallMethodLoadingApp.error', {name: name})));
|
||||
}
|
||||
|
||||
// Run the app.install() method
|
||||
@ -97,7 +98,7 @@ loader = {
|
||||
|
||||
// Check for an activate() method on the app.
|
||||
if (!_.isFunction(app.activate)) {
|
||||
return Promise.reject(new Error('Error loading app named ' + name + '; no activate() method defined.'));
|
||||
return Promise.reject(new Error(i18n.t('errors.apps.noActivateMethodLoadingApp.error', {name: name})));
|
||||
}
|
||||
|
||||
// Wrapping the activate() with a when because it's possible
|
||||
|
@ -2,6 +2,7 @@ var _ = require('lodash'),
|
||||
api = require('../api'),
|
||||
helpers = require('../helpers'),
|
||||
filters = require('../filters'),
|
||||
i18n = require('../i18n'),
|
||||
generateProxyFunctions;
|
||||
|
||||
generateProxyFunctions = function (name, permissions) {
|
||||
@ -23,7 +24,7 @@ generateProxyFunctions = function (name, permissions) {
|
||||
var permValue = getPermissionToMethod(perm, method);
|
||||
|
||||
if (!permValue) {
|
||||
throw new Error('The App "' + name + '" attempted to perform an action or access a resource (' + perm + '.' + method + ') without permission.');
|
||||
throw new Error(i18n.t('errors.apps.accessResourceWithoutPermission.error', {name:name, perm: perm, method: method}));
|
||||
}
|
||||
|
||||
return wrappedFunc.apply(context, args);
|
||||
@ -84,11 +85,11 @@ generateProxyFunctions = function (name, permissions) {
|
||||
|
||||
function AppProxy(options) {
|
||||
if (!options.name) {
|
||||
throw new Error('Must provide an app name for api context');
|
||||
throw new Error(i18n.t('errors.apps.mustProvideAppName.error'));
|
||||
}
|
||||
|
||||
if (!options.permissions) {
|
||||
throw new Error('Must provide app permissions');
|
||||
throw new Error(i18n.t('errors.apps.mustProvideAppPermissions.error'));
|
||||
}
|
||||
|
||||
_.extend(this, generateProxyFunctions(options.name, options.permissions));
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
var path = require('path'),
|
||||
Module = require('module'),
|
||||
i18n = require('../i18n'),
|
||||
_ = require('lodash');
|
||||
|
||||
function AppSandbox(opts) {
|
||||
@ -38,7 +39,7 @@ AppSandbox.prototype.loadModule = function loadModuleSandboxed(modulePath) {
|
||||
currentModule.require = function requireProxy(module) {
|
||||
// check whitelist, plugin config, etc.
|
||||
if (_.contains(self.opts.blacklist, module)) {
|
||||
throw new Error('Unsafe App require: ' + module);
|
||||
throw new Error(i18n.t('errors.apps.unsafeAppRequire.error', {msg: module}));
|
||||
}
|
||||
|
||||
var firstTwo = module.slice(0, 2),
|
||||
@ -55,7 +56,7 @@ AppSandbox.prototype.loadModule = function loadModuleSandboxed(modulePath) {
|
||||
// Check relative path from the appRoot for outside requires
|
||||
relPath = path.relative(appRoot, resolvedPath);
|
||||
if (relPath.slice(0, 2) === '..') {
|
||||
throw new Error('Unsafe App require: ' + relPath);
|
||||
throw new Error(i18n.t('errors.apps.unsafeAppRequire.error', {msg: relPath}));
|
||||
}
|
||||
|
||||
// Assign as new module path
|
||||
|
@ -14,6 +14,7 @@ var path = require('path'),
|
||||
errors = require('../errors'),
|
||||
configUrl = require('./url'),
|
||||
packageInfo = require('../../../package.json'),
|
||||
i18n = require('../i18n'),
|
||||
appRoot = path.resolve(__dirname, '../../../'),
|
||||
corePath = path.resolve(appRoot, 'core/'),
|
||||
testingEnvs = ['testing', 'testing-mysql', 'testing-pg'],
|
||||
@ -286,9 +287,9 @@ ConfigManager.prototype.writeFile = function () {
|
||||
error;
|
||||
|
||||
if (!templateExists) {
|
||||
error = new Error('Could not locate a configuration file.');
|
||||
error = new Error(i18n.t('errors.config.couldNotLocateConfigFile.error'));
|
||||
error.context = appRoot;
|
||||
error.help = 'Please check your deployment for config.js or config.example.js.';
|
||||
error.help = i18n.t('errors.config.couldNotLocateConfigFile.help');
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
@ -296,14 +297,20 @@ ConfigManager.prototype.writeFile = function () {
|
||||
// Copy config.example.js => config.js
|
||||
read = fs.createReadStream(configExamplePath);
|
||||
read.on('error', function (err) {
|
||||
errors.logError(new Error('Could not open config.example.js for read.'), appRoot, 'Please check your deployment for config.js or config.example.js.');
|
||||
errors.logError(
|
||||
new Error(i18n.t('errors.config.couldNotOpenForReading.error', {file: 'config.example.js'})),
|
||||
appRoot,
|
||||
i18n.t('errors.config.couldNotOpenForReading.help'));
|
||||
|
||||
reject(err);
|
||||
});
|
||||
|
||||
write = fs.createWriteStream(configPath);
|
||||
write.on('error', function (err) {
|
||||
errors.logError(new Error('Could not open config.js for write.'), appRoot, 'Please check your deployment for config.js or config.example.js.');
|
||||
errors.logError(
|
||||
new Error(i18n.t('errors.config.couldNotOpenForWriting.error', {file: 'config.js'})),
|
||||
appRoot,
|
||||
i18n.t('errors.config.couldNotOpenForWriting.help'));
|
||||
|
||||
reject(err);
|
||||
});
|
||||
@ -344,24 +351,33 @@ ConfigManager.prototype.validate = function () {
|
||||
|
||||
// Check that our url is valid
|
||||
if (!validator.isURL(config.url, {protocols: ['http', 'https'], require_protocol: true})) {
|
||||
errors.logError(new Error('Your site url in config.js is invalid.'), config.url, 'Please make sure this is a valid url before restarting');
|
||||
errors.logError(
|
||||
new Error(i18n.t('errors.config.invalidUrlInConfig.description'),
|
||||
config.url,
|
||||
i18n.t('errors.config.invalidUrlInConfig.help')));
|
||||
|
||||
return Promise.reject(new Error('invalid site url'));
|
||||
return Promise.reject(new Error(i18n.t('errors.config.invalidUrlInConfig.error')));
|
||||
}
|
||||
|
||||
parsedUrl = url.parse(config.url || 'invalid', false, true);
|
||||
|
||||
if (/\/ghost(\/|$)/.test(parsedUrl.pathname)) {
|
||||
errors.logError(new Error('Your site url in config.js cannot contain a subdirectory called ghost.'), config.url, 'Please rename the subdirectory before restarting');
|
||||
errors.logError(
|
||||
new Error(i18n.t('errors.config.urlCannotContainGhostSubdir.description'),
|
||||
config.url,
|
||||
i18n.t('errors.config.urlCannotContainGhostSubdir.help')));
|
||||
|
||||
return Promise.reject(new Error('ghost subdirectory not allowed'));
|
||||
return Promise.reject(new Error(i18n.t('errors.config.urlCannotContainGhostSubdir.error')));
|
||||
}
|
||||
|
||||
// Check that we have database values
|
||||
if (!config.database || !config.database.client) {
|
||||
errors.logError(new Error('Your database configuration in config.js is invalid.'), JSON.stringify(config.database), 'Please make sure this is a valid Bookshelf database configuration');
|
||||
errors.logError(
|
||||
new Error(i18n.t('errors.config.dbConfigInvalid.description')),
|
||||
JSON.stringify(config.database),
|
||||
i18n.t('errors.config.dbConfigInvalid.help'));
|
||||
|
||||
return Promise.reject(new Error('invalid database configuration'));
|
||||
return Promise.reject(new Error(i18n.t('errors.config.dbConfigInvalid.error')));
|
||||
}
|
||||
|
||||
hasHostAndPort = config.server && !!config.server.host && !!config.server.port;
|
||||
@ -369,9 +385,12 @@ ConfigManager.prototype.validate = function () {
|
||||
|
||||
// Check for valid server host and port values
|
||||
if (!config.server || !(hasHostAndPort || hasSocket)) {
|
||||
errors.logError(new Error('Your server values (socket, or host and port) in config.js are invalid.'), JSON.stringify(config.server), 'Please provide them before restarting.');
|
||||
errors.logError(
|
||||
new Error(i18n.t('errors.config.invalidServerValues.description')),
|
||||
JSON.stringify(config.server),
|
||||
i18n.t('errors.config.invalidServerValues.help'));
|
||||
|
||||
return Promise.reject(new Error('invalid server configuration'));
|
||||
return Promise.reject(new Error(i18n.t('errors.config.invalidServerValues.error')));
|
||||
}
|
||||
|
||||
return Promise.resolve(config);
|
||||
@ -417,9 +436,9 @@ ConfigManager.prototype.displayDeprecated = function (item, properties, address)
|
||||
if (properties.length) {
|
||||
return self.displayDeprecated(item[property], properties, address);
|
||||
}
|
||||
errorText = 'The configuration property [' + chalk.bold(address.join('.')) + '] has been deprecated.';
|
||||
explanationText = 'This will be removed in a future version, please update your config.js file.';
|
||||
helpText = 'Please check http://support.ghost.org/config for the most up-to-date example.';
|
||||
errorText = i18n.t('errors.config.deprecatedProperty.error', {property: chalk.bold(address.join('.'))});
|
||||
explanationText = i18n.t('errors.config.deprecatedProperty.explanation');
|
||||
helpText = i18n.t('errors.config.deprecatedProperty.help', {url: 'http://support.ghost.org/config'});
|
||||
errors.logWarn(errorText, explanationText, helpText);
|
||||
}
|
||||
};
|
||||
|
@ -3,6 +3,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
updateCheck = require('../update-check'),
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n'),
|
||||
adminControllers;
|
||||
|
||||
adminControllers = {
|
||||
@ -45,8 +46,8 @@ adminControllers = {
|
||||
location: 'settings-about-upgrade',
|
||||
dismissible: false,
|
||||
status: 'alert',
|
||||
message: 'Ghost ' + updateVersion + ' is available! Hot Damn. <a href="http://support.ghost.org/how-to-upgrade/" target="_blank">Click here</a> to upgrade.'
|
||||
};
|
||||
message: i18n.t('notices.controllers.newVersionAvailable',
|
||||
{version: updateVersion, link: '<a href="http://support.ghost.org/how-to-upgrade/" target="_blank">Click here</a>'})};
|
||||
|
||||
return api.notifications.browse({context: {internal: true}}).then(function then(results) {
|
||||
if (!_.some(results.notifications, {message: notification.message})) {
|
||||
|
@ -6,6 +6,7 @@ var _ = require('lodash'),
|
||||
serverUtils = require('../../utils'),
|
||||
errors = require('../../errors'),
|
||||
settings = require('../../api/settings'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
excludedTables = ['accesstokens', 'refreshtokens', 'clients'],
|
||||
exporter,
|
||||
@ -53,7 +54,7 @@ exporter = function () {
|
||||
|
||||
return exportData;
|
||||
}).catch(function (err) {
|
||||
errors.logAndThrowError(err, 'Error exporting data', '');
|
||||
errors.logAndThrowError(err, i18n.t('errors.data.export.errorExportingData'), '');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -16,6 +16,7 @@ var Promise = require('bluebird'),
|
||||
fixtures = require('./fixtures'),
|
||||
permissions = require('./permissions'),
|
||||
notifications = require('../../api/notifications'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
// Private
|
||||
logInfo,
|
||||
@ -30,7 +31,7 @@ var Promise = require('bluebird'),
|
||||
update;
|
||||
|
||||
logInfo = function logInfo(message) {
|
||||
errors.logInfo('Migrations', message);
|
||||
errors.logInfo(i18n.t('notices.data.fixtures.migrations'), message);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -46,7 +47,7 @@ convertAdminToOwner = function convertAdminToOwner() {
|
||||
return models.Role.findOne({name: 'Owner'});
|
||||
}).then(function (ownerRole) {
|
||||
if (adminUser) {
|
||||
logInfo('Converting admin to owner');
|
||||
logInfo(i18n.t('notices.data.fixtures.convertingAdmToOwner'));
|
||||
return adminUser.roles().updatePivot({role_id: ownerRole.id});
|
||||
}
|
||||
});
|
||||
@ -64,7 +65,7 @@ createOwner = function createOwner() {
|
||||
user.roles = [ownerRole.id];
|
||||
user.password = utils.uid(50);
|
||||
|
||||
logInfo('Creating owner');
|
||||
logInfo(i18n.t('notices.data.fixtures.creatingOwner'));
|
||||
return models.User.add(user, options);
|
||||
});
|
||||
};
|
||||
@ -77,7 +78,7 @@ populate = function populate() {
|
||||
Role = models.Role,
|
||||
Client = models.Client;
|
||||
|
||||
logInfo('Populating fixtures');
|
||||
logInfo(i18n.t('notices.data.fixtures.populatingFixtures'));
|
||||
|
||||
_.each(fixtures.posts, function (post) {
|
||||
ops.push(Post.add(post, options));
|
||||
@ -133,12 +134,12 @@ to003 = function to003() {
|
||||
Role = models.Role,
|
||||
Client = models.Client;
|
||||
|
||||
logInfo('Upgrading fixtures to 003');
|
||||
logInfo(i18n.t('notices.data.fixtures.upgradingFixturesTo', {version: '003'}));
|
||||
|
||||
// Add the client fixture if missing
|
||||
upgradeOp = Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) {
|
||||
if (!client) {
|
||||
logInfo('Adding ghost-admin client fixture');
|
||||
logInfo(i18n.t('notices.data.fixtures.addingClientFixture'));
|
||||
return Client.add(fixtures.clients[0], options);
|
||||
}
|
||||
});
|
||||
@ -147,7 +148,7 @@ to003 = function to003() {
|
||||
// Add the owner role if missing
|
||||
upgradeOp = Role.findOne({name: fixtures.roles[3].name}).then(function (owner) {
|
||||
if (!owner) {
|
||||
logInfo('Adding owner role fixture');
|
||||
logInfo(i18n.t('notices.data.fixtures.addingOwnerRoleFixture'));
|
||||
_.each(fixtures.roles.slice(3), function (role) {
|
||||
return Role.add(role, options);
|
||||
});
|
||||
@ -171,15 +172,15 @@ to004 = function to004() {
|
||||
ops = [],
|
||||
upgradeOp,
|
||||
jquery = [
|
||||
'<!-- You can safely delete this line if your theme does not require jQuery -->\n',
|
||||
i18n.t('notices.data.fixtures.canSafelyDelete'),
|
||||
'<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
|
||||
],
|
||||
privacyMessage = [
|
||||
'jQuery has been removed from Ghost core and is now being loaded from the jQuery Foundation\'s CDN.',
|
||||
'This can be changed or removed in your <strong>Code Injection</strong> settings area.'
|
||||
i18n.t('notices.data.fixtures.jQueryRemoved'),
|
||||
i18n.t('notices.data.fixtures.canBeChanged')
|
||||
];
|
||||
|
||||
logInfo('Upgrading fixtures to 004');
|
||||
logInfo(i18n.t('notices.data.fixtures.upgradingFixturesTo', {version: '004'}));
|
||||
|
||||
// add jquery setting and privacy info
|
||||
upgradeOp = function () {
|
||||
@ -188,7 +189,7 @@ to004 = function to004() {
|
||||
value = setting.attributes.value;
|
||||
// Only add jQuery if it's not already in there
|
||||
if (value.indexOf(jquery.join('')) === -1) {
|
||||
logInfo('Adding jQuery link to ghost_foot');
|
||||
logInfo(i18n.t('notices.data.fixtures.addingJquery'));
|
||||
value = jquery.join('') + value;
|
||||
return models.Settings.edit({key: 'ghost_foot', value: value}, options).then(function () {
|
||||
if (_.isEmpty(config.privacy)) {
|
||||
@ -210,7 +211,7 @@ to004 = function to004() {
|
||||
upgradeOp = function () {
|
||||
return models.Settings.findOne('isPrivate').then(function (setting) {
|
||||
if (setting) {
|
||||
logInfo('Update isPrivate setting');
|
||||
logInfo(i18n.t('notices.data.fixtures.updateIsPrivate'));
|
||||
return models.Settings.edit({key: 'isPrivate', type: 'private'}, options);
|
||||
}
|
||||
return Promise.resolve();
|
||||
@ -222,7 +223,7 @@ to004 = function to004() {
|
||||
upgradeOp = function () {
|
||||
return models.Settings.findOne('password').then(function (setting) {
|
||||
if (setting) {
|
||||
logInfo('Update password setting');
|
||||
logInfo(i18n.t('notices.data.fixtures.updatePassword'));
|
||||
return models.Settings.edit({key: 'password', type: 'private'}, options);
|
||||
}
|
||||
return Promise.resolve();
|
||||
@ -235,7 +236,7 @@ to004 = function to004() {
|
||||
upgradeOp = function () {
|
||||
return models.Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) {
|
||||
if (client) {
|
||||
logInfo('Update ghost-admin client fixture');
|
||||
logInfo(i18n.t('notices.data.fixtures.updateAdminClientFixture'));
|
||||
var adminClient = fixtures.clients[0];
|
||||
adminClient.secret = crypto.randomBytes(6).toString('hex');
|
||||
return models.Client.edit(adminClient, _.extend({}, options, {id: client.id}));
|
||||
@ -249,7 +250,7 @@ to004 = function to004() {
|
||||
upgradeOp = function () {
|
||||
return models.Client.findOne({slug: fixtures.clients[1].slug}).then(function (client) {
|
||||
if (!client) {
|
||||
logInfo('Add ghost-frontend client fixture');
|
||||
logInfo(i18n.t('notices.data.fixtures.addFrontendClientFixture'));
|
||||
var frontendClient = fixtures.clients[1];
|
||||
frontendClient.secret = crypto.randomBytes(6).toString('hex');
|
||||
return models.Client.add(frontendClient, options);
|
||||
@ -276,7 +277,7 @@ to004 = function to004() {
|
||||
}
|
||||
});
|
||||
if (tagOps.length > 0) {
|
||||
logInfo('Cleaning ' + tagOps.length + ' malformed tags');
|
||||
logInfo(i18n.t('notices.data.fixtures.cleaningTags', {length: tagOps.length}));
|
||||
return Promise.all(tagOps);
|
||||
}
|
||||
}
|
||||
@ -288,7 +289,7 @@ to004 = function to004() {
|
||||
// Add post_tag order
|
||||
upgradeOp = function () {
|
||||
var tagOps = [];
|
||||
logInfo('Collecting data on tag order for posts...');
|
||||
logInfo(i18n.t('notices.data.fixtures.collectingDataOnTagOrder'));
|
||||
return models.Post.findAll(_.extend({}, options)).then(function (posts) {
|
||||
if (posts) {
|
||||
return posts.mapThen(function (post) {
|
||||
@ -313,9 +314,9 @@ to004 = function to004() {
|
||||
});
|
||||
|
||||
if (tagOps.length > 0) {
|
||||
logInfo('Updating order on ' + tagOps.length + ' tag relationships (could take a while)...');
|
||||
logInfo(i18n.t('notices.data.fixtures.updatingOrder', {length: tagOps.length}));
|
||||
return sequence(tagOps).then(function () {
|
||||
logInfo('Tag order successfully updated');
|
||||
logInfo(i18n.t('notices.data.fixtures.updatedOrder'));
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -326,7 +327,7 @@ to004 = function to004() {
|
||||
upgradeOp = function () {
|
||||
return models.Post.findOne({slug: fixtures.posts_0_7[0].slug, status: 'all'}, options).then(function (post) {
|
||||
if (!post) {
|
||||
logInfo('Adding 0.7 upgrade post fixture');
|
||||
logInfo(i18n.t('notices.data.fixtures.addingUpgrade', {version: '0.7'}));
|
||||
// Set the published_at timestamp, but keep the post as a draft so doesn't appear on the frontend
|
||||
// This is a hack to ensure that this post appears at the very top of the drafts list, because
|
||||
// unpublished posts always appear first
|
||||
@ -343,7 +344,7 @@ to004 = function to004() {
|
||||
update = function update(fromVersion, toVersion) {
|
||||
var ops = [];
|
||||
|
||||
logInfo('Updating fixtures');
|
||||
logInfo(i18n.t('notices.data.fixtures.updatingFixtures'));
|
||||
// Are we migrating to, or past 003?
|
||||
if ((fromVersion < '003' && toVersion >= '003') ||
|
||||
fromVersion === '003' && toVersion === '003' && process.env.FORCE_MIGRATION) {
|
||||
|
@ -6,6 +6,7 @@ var Promise = require('bluebird'),
|
||||
errors = require('../../../errors'),
|
||||
models = require('../../../models'),
|
||||
fixtures = require('./permissions'),
|
||||
i18n = require('../../../i18n'),
|
||||
|
||||
// private
|
||||
logInfo,
|
||||
@ -71,7 +72,7 @@ addAllPermissions = function (options) {
|
||||
|
||||
// ## Populate
|
||||
populate = function (options) {
|
||||
logInfo('Populating permissions');
|
||||
logInfo(i18n.t('errors.data.fixtures.populatingPermissions'));
|
||||
// ### Ensure all permissions are added
|
||||
return addAllPermissions(options).then(function () {
|
||||
// ### Ensure all roles_permissions are added
|
||||
@ -85,12 +86,12 @@ populate = function (options) {
|
||||
to003 = function (options) {
|
||||
var ops = [];
|
||||
|
||||
logInfo('Upgrading permissions');
|
||||
logInfo(i18n.t('errors.data.fixtures.upgradingPermissions'));
|
||||
|
||||
// To safely upgrade, we need to clear up the existing permissions and permissions_roles before recreating the new
|
||||
// full set of permissions defined as of version 003
|
||||
return models.Permissions.forge().fetch().then(function (permissions) {
|
||||
logInfo('Removing old permissions');
|
||||
logInfo(i18n.t('errors.data.fixtures.removingOldPermissions'));
|
||||
permissions.each(function (permission) {
|
||||
ops.push(permission.related('roles').detach().then(function () {
|
||||
return permission.destroy();
|
||||
|
@ -2,6 +2,7 @@ var Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
models = require('../../models'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
internal = utils.internal,
|
||||
|
||||
@ -34,7 +35,7 @@ DataImporter.prototype.loadUsers = function () {
|
||||
});
|
||||
|
||||
if (!users.owner) {
|
||||
return Promise.reject('Unable to find an owner');
|
||||
return Promise.reject(i18n.t('errors.data.import.dataImporter.unableToFindOwner'));
|
||||
}
|
||||
|
||||
return users;
|
||||
|
@ -5,6 +5,7 @@ var Promise = require('bluebird'),
|
||||
uuid = require('node-uuid'),
|
||||
importer = require('./data-importer'),
|
||||
tables = require('../schema').tables,
|
||||
i18n = require('../../i18n'),
|
||||
validate,
|
||||
handleErrors,
|
||||
checkDuplicateAttributes,
|
||||
@ -36,7 +37,7 @@ cleanError = function cleanError(error) {
|
||||
value = error.raw.detail;
|
||||
offendingProperty = error.model;
|
||||
}
|
||||
message = 'Duplicate entry found. Multiple values of "' + value + '" found for ' + offendingProperty + '.';
|
||||
message = i18n.t('errors.data.import.index.duplicateEntryFound', {value: value, offendingProperty: offendingProperty});
|
||||
}
|
||||
|
||||
offendingProperty = offendingProperty || error.model;
|
||||
|
@ -3,6 +3,7 @@ var Promise = require('bluebird'),
|
||||
models = require('../../models'),
|
||||
errors = require('../../errors'),
|
||||
globalUtils = require('../../utils'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
internal = {context: {internal: true}},
|
||||
utils,
|
||||
@ -78,7 +79,7 @@ utils = {
|
||||
userMap[userToMap] = existingUsers[owner.email].realId;
|
||||
} else {
|
||||
throw new errors.DataImportError(
|
||||
'Attempting to import data linked to unknown user id ' + userToMap, 'user.id', userToMap
|
||||
i18n.t('errors.data.import.utils.dataLinkedToUnknownUser', {userToMap: userToMap}), 'user.id', userToMap
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -2,6 +2,7 @@ var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
fs = require('fs-extra'),
|
||||
errors = require('../../../errors'),
|
||||
i18n = require('../../../i18n'),
|
||||
JSONHandler;
|
||||
|
||||
JSONHandler = {
|
||||
@ -23,7 +24,7 @@ JSONHandler = {
|
||||
// if importData follows JSON-API format `{ db: [exportedData] }`
|
||||
if (_.keys(importData).length === 1) {
|
||||
if (!importData.db || !Array.isArray(importData.db)) {
|
||||
throw new Error('Invalid JSON format, expected `{ db: [exportedData] }`');
|
||||
throw new Error(i18n.t('errors.data.importer.handlers.json.invalidJsonFormat'));
|
||||
}
|
||||
|
||||
importData = importData.db[0];
|
||||
@ -31,8 +32,9 @@ JSONHandler = {
|
||||
|
||||
return importData;
|
||||
} catch (e) {
|
||||
errors.logError(e, 'API DB import content', 'check that the import file is valid JSON.');
|
||||
return Promise.reject(new errors.BadRequestError('Failed to parse the import JSON file.'));
|
||||
errors.logError(e, i18n.t('errors.data.importer.handlers.json.apiDbImportContent'),
|
||||
i18n.t('errors.data.importer.handlers.json.checkImportJsonIsValid'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.data.importer.handlers.json.failedToParseImportJson')));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ var _ = require('lodash'),
|
||||
MarkdownHandler = require('./handlers/markdown'),
|
||||
ImageImporter = require('./importers/image'),
|
||||
DataImporter = require('./importers/data'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
// Glob levels
|
||||
ROOT_ONLY = 0,
|
||||
@ -107,7 +108,8 @@ _.extend(ImportManager.prototype, {
|
||||
_.each(filesToDelete, function (fileToDelete) {
|
||||
fs.remove(fileToDelete, function (err) {
|
||||
if (err) {
|
||||
errors.logError(err, 'Import could not clean up file ', 'Your blog will continue to work as expected');
|
||||
errors.logError(err, i18n.t('errors.data.importer.index.couldNotCleanUpFile.error'),
|
||||
i18n.t('errors.data.importer.index.couldNotCleanUpFile.context'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -147,7 +149,7 @@ _.extend(ImportManager.prototype, {
|
||||
// This is a temporary extra message for the old format roon export which doesn't work with Ghost
|
||||
if (oldRoonMatches.length > 0) {
|
||||
throw new errors.UnsupportedMediaTypeError(
|
||||
'Your zip file looks like an old format Roon export, please re-export your Roon blog and try again.'
|
||||
i18n.t('errors.data.importer.index.unsupportedRoonExport')
|
||||
);
|
||||
}
|
||||
|
||||
@ -157,10 +159,12 @@ _.extend(ImportManager.prototype, {
|
||||
}
|
||||
|
||||
if (extMatchesAll.length < 1) {
|
||||
throw new errors.UnsupportedMediaTypeError('Zip did not include any content to import.');
|
||||
throw new errors.UnsupportedMediaTypeError(
|
||||
i18n.t('errors.data.importer.index.noContentToImport'));
|
||||
}
|
||||
|
||||
throw new errors.UnsupportedMediaTypeError('Invalid zip file structure.');
|
||||
throw new errors.UnsupportedMediaTypeError(
|
||||
i18n.t('errors.data.importer.index.invalidZipStructure'));
|
||||
},
|
||||
/**
|
||||
* Use the extract module to extract the given zip file to a temp directory & return the temp directory path
|
||||
@ -207,9 +211,8 @@ _.extend(ImportManager.prototype, {
|
||||
this.getExtensionGlob(this.getExtensions(), ALL_DIRS), {cwd: directory}
|
||||
);
|
||||
if (extMatchesAll.length < 1 || extMatchesAll[0].split('/') < 1) {
|
||||
throw new errors.ValidationError('Invalid zip file: base directory read failed');
|
||||
throw new errors.ValidationError(i18n.t('errors.data.importer.index.invalidZipFileBaseDirectory'));
|
||||
}
|
||||
|
||||
return extMatchesAll[0].split('/')[0];
|
||||
},
|
||||
/**
|
||||
@ -236,7 +239,7 @@ _.extend(ImportManager.prototype, {
|
||||
if (importData.hasOwnProperty(handler.type)) {
|
||||
// This limitation is here to reduce the complexity of the importer for now
|
||||
return Promise.reject(new errors.UnsupportedMediaTypeError(
|
||||
'Zip file contains multiple data formats. Please split up and import separately.'
|
||||
i18n.t('errors.data.importer.index.zipContainsMultipleDataFormats')
|
||||
));
|
||||
}
|
||||
|
||||
@ -253,7 +256,7 @@ _.extend(ImportManager.prototype, {
|
||||
|
||||
if (ops.length === 0) {
|
||||
return Promise.reject(new errors.UnsupportedMediaTypeError(
|
||||
'Zip did not include any content to import.'
|
||||
i18n.t('errors.data.importer.index.noContentToImport')
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ var _ = require('lodash'),
|
||||
errors = require('../../errors'),
|
||||
utils = require('../utils'),
|
||||
schema = require('../schema').tables,
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
// private
|
||||
logInfo,
|
||||
@ -13,14 +14,14 @@ var _ = require('lodash'),
|
||||
modifyUniqueCommands;
|
||||
|
||||
logInfo = function logInfo(message) {
|
||||
errors.logInfo('Migrations', message);
|
||||
errors.logInfo(i18n.t('notices.data.migration.commands.migrations'), message);
|
||||
};
|
||||
|
||||
getDeleteCommands = function getDeleteCommands(oldTables, newTables) {
|
||||
var deleteTables = _.difference(oldTables, newTables);
|
||||
return _.map(deleteTables, function (table) {
|
||||
return function () {
|
||||
logInfo('Deleting table: ' + table);
|
||||
logInfo(i18n.t('notices.data.migration.commands.deletingTable', {table: table}));
|
||||
return utils.deleteTable(table);
|
||||
};
|
||||
});
|
||||
@ -29,7 +30,7 @@ getAddCommands = function getAddCommands(oldTables, newTables) {
|
||||
var addTables = _.difference(newTables, oldTables);
|
||||
return _.map(addTables, function (table) {
|
||||
return function () {
|
||||
logInfo('Creating table: ' + table);
|
||||
logInfo(i18n.t('notices.data.migration.commands.creatingTable', {table: table}));
|
||||
return utils.createTable(table);
|
||||
};
|
||||
});
|
||||
@ -40,7 +41,7 @@ addColumnCommands = function addColumnCommands(table, columns) {
|
||||
|
||||
return _.map(addColumns, function (column) {
|
||||
return function () {
|
||||
logInfo('Adding column: ' + table + '.' + column);
|
||||
logInfo(i18n.t('notices.data.migration.commands.addingColumn', {table: table, column: column}));
|
||||
return utils.addColumn(table, column);
|
||||
};
|
||||
});
|
||||
@ -51,14 +52,14 @@ modifyUniqueCommands = function modifyUniqueCommands(table, indexes) {
|
||||
if (schema[table][column].unique === true) {
|
||||
if (!_.contains(indexes, table + '_' + column + '_unique')) {
|
||||
return function () {
|
||||
logInfo('Adding unique on: ' + table + '.' + column);
|
||||
logInfo(i18n.t('notices.data.migration.commands.addingUnique', {table: table, column: column}));
|
||||
return utils.addUnique(table, column);
|
||||
};
|
||||
}
|
||||
} else if (!schema[table][column].unique) {
|
||||
if (_.contains(indexes, table + '_' + column + '_unique')) {
|
||||
return function () {
|
||||
logInfo('Dropping unique on: ' + table + '.' + column);
|
||||
logInfo(i18n.t('notices.data.migration.commands.droppingUnique', {table: table, column: column}));
|
||||
return utils.dropUnique(table, column);
|
||||
};
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ var _ = require('lodash'),
|
||||
dataExport = require('../export'),
|
||||
utils = require('../utils'),
|
||||
config = require('../../config'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
schemaTables = _.keys(schema),
|
||||
|
||||
@ -29,26 +30,26 @@ var _ = require('lodash'),
|
||||
migrateUpFreshDb;
|
||||
|
||||
logInfo = function logInfo(message) {
|
||||
errors.logInfo('Migrations', message);
|
||||
errors.logInfo(i18n.t('notices.data.migration.index.migrations'), message);
|
||||
};
|
||||
|
||||
populateDefaultSettings = function populateDefaultSettings() {
|
||||
// Initialise the default settings
|
||||
logInfo('Populating default settings');
|
||||
logInfo(i18n.t('notices.data.migration.index.populatingDefaultSettings'));
|
||||
return models.Settings.populateDefaults().then(function () {
|
||||
logInfo('Complete');
|
||||
logInfo(i18n.t('notices.data.migration.index.complete'));
|
||||
});
|
||||
};
|
||||
|
||||
backupDatabase = function backupDatabase() {
|
||||
logInfo('Creating database backup');
|
||||
logInfo(i18n.t('notices.data.migration.index.creatingDatabaseBackup'));
|
||||
return dataExport().then(function (exportedData) {
|
||||
// Save the exported data to the file system for download
|
||||
return dataExport.fileName().then(function (fileName) {
|
||||
fileName = path.resolve(config.paths.contentPath + '/data/' + fileName);
|
||||
|
||||
return Promise.promisify(fs.writeFile)(fileName, JSON.stringify(exportedData)).then(function () {
|
||||
logInfo('Database backup written to: ' + fileName);
|
||||
logInfo(i18n.t('notices.data.migration.index.databaseBackupDestination', {filename: fileName}));
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -83,7 +84,8 @@ init = function (tablesOnly) {
|
||||
if (databaseVersion < defaultVersion || process.env.FORCE_MIGRATION) {
|
||||
// 2. The database exists but is out of date
|
||||
// Migrate to latest version
|
||||
logInfo('Database upgrade required from version ' + databaseVersion + ' to ' + defaultVersion);
|
||||
logInfo(i18n.t('notices.data.migration.index.databaseUpgradeRequired',
|
||||
{dbVersion: databaseVersion, defaultVersion: defaultVersion}));
|
||||
return self.migrateUp(databaseVersion, defaultVersion).then(function () {
|
||||
// Finally update the databases current version
|
||||
return versioning.setDatabaseVersion();
|
||||
@ -92,7 +94,7 @@ init = function (tablesOnly) {
|
||||
|
||||
if (databaseVersion === defaultVersion) {
|
||||
// 1. The database exists and is up-to-date
|
||||
logInfo('Up to date at version ' + databaseVersion);
|
||||
logInfo(i18n.t('notices.data.migration.index.upToDateAtVersion', {dbVersion: databaseVersion}));
|
||||
// TODO: temporary fix for missing client.secret
|
||||
return fixClientSecret();
|
||||
}
|
||||
@ -101,20 +103,20 @@ init = function (tablesOnly) {
|
||||
// 3. The database exists but the currentVersion setting does not or cannot be understood
|
||||
// In this case we don't understand the version because it is too high
|
||||
errors.logErrorAndExit(
|
||||
'Your database is not compatible with this version of Ghost',
|
||||
'You will need to create a new database'
|
||||
i18n.t('notices.data.migration.index.databaseNotCompatible.error'),
|
||||
i18n.t('notices.data.migration.index.databaseNotCompatible.help')
|
||||
);
|
||||
}
|
||||
}, function (err) {
|
||||
if (err.message || err === 'Settings table does not exist') {
|
||||
// 4. The database has not yet been created
|
||||
// Bring everything up from initial version.
|
||||
logInfo('Database initialisation required for version ' + versioning.getDefaultDatabaseVersion());
|
||||
logInfo(i18n.t('notices.data.migration.index.dbInitialisationRequired', {version: versioning.getDefaultDatabaseVersion()}));
|
||||
return self.migrateUpFreshDb(tablesOnly);
|
||||
}
|
||||
// 3. The database exists but the currentVersion setting does not or cannot be understood
|
||||
// In this case the setting was missing or there was some other problem
|
||||
errors.logErrorAndExit('There is a problem with the database', err.message || err);
|
||||
errors.logErrorAndExit(i18n.t('notices.data.migration.index.problemWithDatabase'), err.message || err);
|
||||
});
|
||||
};
|
||||
|
||||
@ -135,11 +137,11 @@ migrateUpFreshDb = function (tablesOnly) {
|
||||
var tableSequence,
|
||||
tables = _.map(schemaTables, function (table) {
|
||||
return function () {
|
||||
logInfo('Creating table: ' + table);
|
||||
logInfo(i18n.t('notices.data.migration.index.creatingTable', {table: table}));
|
||||
return utils.createTable(table);
|
||||
};
|
||||
});
|
||||
logInfo('Creating tables...');
|
||||
logInfo(i18n.t('notices.data.migration.index.creatingTables'));
|
||||
tableSequence = sequence(tables);
|
||||
|
||||
if (tablesOnly) {
|
||||
@ -189,7 +191,7 @@ migrateUp = function (fromVersion, toVersion) {
|
||||
|
||||
// execute the commands in sequence
|
||||
if (!_.isEmpty(migrateOps)) {
|
||||
logInfo('Running migrations');
|
||||
logInfo(i18n.t('notices.data.migration.index.runningMigrations'));
|
||||
|
||||
return sequence(migrateOps);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ var _ = require('lodash'),
|
||||
config = require('../../config'),
|
||||
schema = require('../schema').tables,
|
||||
clients = require('./clients'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
dbConfig;
|
||||
|
||||
@ -86,7 +87,7 @@ function getTables() {
|
||||
return clients[client].getTables();
|
||||
}
|
||||
|
||||
return Promise.reject('No support for database client ' + client);
|
||||
return Promise.reject(i18n.t('notices.data.utils.index.noSupportForDatabase', {client: client}));
|
||||
}
|
||||
|
||||
function getIndexes(table) {
|
||||
@ -97,7 +98,7 @@ function getIndexes(table) {
|
||||
return clients[client].getIndexes(table);
|
||||
}
|
||||
|
||||
return Promise.reject('No support for database client ' + client);
|
||||
return Promise.reject(i18n.t('notices.data.utils.index.noSupportForDatabase', {client: client}));
|
||||
}
|
||||
|
||||
function getColumns(table) {
|
||||
@ -108,7 +109,7 @@ function getColumns(table) {
|
||||
return clients[client].getColumns(table);
|
||||
}
|
||||
|
||||
return Promise.reject('No support for database client ' + client);
|
||||
return Promise.reject(i18n.t('notices.data.utils.index.noSupportForDatabase', {client: client}));
|
||||
}
|
||||
|
||||
function checkTables() {
|
||||
|
@ -5,6 +5,7 @@ var schema = require('../schema').tables,
|
||||
errors = require('../../errors'),
|
||||
config = require('../../config'),
|
||||
readThemes = require('../../utils/read-themes'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
validateSchema,
|
||||
validateSettings,
|
||||
@ -44,7 +45,7 @@ validateSchema = function validateSchema(tableName, model) {
|
||||
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
|
||||
&& schema[tableName][columnKey].nullable !== true) {
|
||||
if (validator.isNull(model[columnKey]) || validator.empty(model[columnKey])) {
|
||||
message = 'Value in [' + tableName + '.' + columnKey + '] cannot be blank.';
|
||||
message = i18n.t('notices.data.validation.index.valueCannotBeBlank', {tableName: tableName, columnKey: columnKey});
|
||||
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
|
||||
}
|
||||
}
|
||||
@ -54,8 +55,8 @@ validateSchema = function validateSchema(tableName, model) {
|
||||
// check length
|
||||
if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
|
||||
if (!validator.isLength(model[columnKey], 0, schema[tableName][columnKey].maxlength)) {
|
||||
message = 'Value in [' + tableName + '.' + columnKey + '] exceeds maximum length of '
|
||||
+ schema[tableName][columnKey].maxlength + ' characters.';
|
||||
message = i18n.t('notices.data.validation.index.valueExceedsMaxLength',
|
||||
{tableName: tableName, columnKey: columnKey, maxlength: schema[tableName][columnKey].maxlength});
|
||||
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
|
||||
}
|
||||
}
|
||||
@ -68,7 +69,7 @@ validateSchema = function validateSchema(tableName, model) {
|
||||
// check type
|
||||
if (schema[tableName][columnKey].hasOwnProperty('type')) {
|
||||
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(model[columnKey])) {
|
||||
message = 'Value in [' + tableName + '.' + columnKey + '] is not an integer.';
|
||||
message = i18n.t('notices.data.validation.index.valueIsNotInteger', {tableName: tableName, columnKey: columnKey});
|
||||
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
|
||||
}
|
||||
}
|
||||
@ -117,7 +118,7 @@ validateActiveTheme = function validateActiveTheme(themeName) {
|
||||
|
||||
return availableThemes.then(function then(themes) {
|
||||
if (!themes.hasOwnProperty(themeName)) {
|
||||
return Promise.reject(new errors.ValidationError(themeName + ' cannot be activated because it is not currently installed.', 'activeTheme'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('notices.data.validation.index.themeCannotBeActivated', {themeName: themeName}), 'activeTheme'));
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -156,7 +157,8 @@ validate = function validate(value, key, validations) {
|
||||
|
||||
// equivalent of validator.isSomething(option1, option2)
|
||||
if (validator[validationName].apply(validator, validationOptions) !== goodResult) {
|
||||
validationErrors.push(new errors.ValidationError('Validation (' + validationName + ') failed for ' + key, key));
|
||||
validationErrors.push(new errors.ValidationError(i18n.t('notices.data.validation.index.validationFailed',
|
||||
{validationName: validationName, key: key})));
|
||||
}
|
||||
|
||||
validationOptions.shift();
|
||||
|
@ -1,6 +1,7 @@
|
||||
var _ = require('lodash'),
|
||||
errors = require('../../errors'),
|
||||
config = require('../../config'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
defaultSettings = require('../default-settings'),
|
||||
|
||||
@ -36,7 +37,7 @@ function getDatabaseVersion() {
|
||||
.then(function (versions) {
|
||||
var databaseVersion = _.reduce(versions, function (memo, version) {
|
||||
if (isNaN(version.value)) {
|
||||
errors.throwError('Database version is not recognised');
|
||||
errors.throwError(i18n.t('errors.data.versioning.index.dbVersionNotRecognized'));
|
||||
}
|
||||
return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
|
||||
}, initialVersion);
|
||||
@ -49,7 +50,7 @@ function getDatabaseVersion() {
|
||||
return databaseVersion;
|
||||
});
|
||||
}
|
||||
throw new Error('Settings table does not exist');
|
||||
throw new Error(i18n.t('errors.data.versioning.index.settingsTableDoesNotExist'));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ var _ = require('lodash'),
|
||||
config = require('../../config'),
|
||||
errors = require('../../errors'),
|
||||
events = require('../../events'),
|
||||
i18n = require('../../i18n'),
|
||||
pingList;
|
||||
|
||||
// ToDo: Make this configurable
|
||||
@ -68,8 +69,8 @@ function ping(post) {
|
||||
req.on('error', function (error) {
|
||||
errors.logError(
|
||||
error,
|
||||
'Pinging services for updates on your blog failed, your blog will continue to function.',
|
||||
'If you get this error repeatedly, please seek help on http://support.ghost.org.'
|
||||
i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error'),
|
||||
i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'})
|
||||
);
|
||||
});
|
||||
req.end();
|
||||
|
@ -17,6 +17,7 @@ var _ = require('lodash'),
|
||||
EmailError = require('./email-error'),
|
||||
DataImportError = require('./data-import-error'),
|
||||
TooManyRequestsError = require('./too-many-requests-error'),
|
||||
i18n = require('../i18n'),
|
||||
config,
|
||||
errors,
|
||||
|
||||
@ -43,7 +44,7 @@ errors = {
|
||||
|
||||
throwError: function (err) {
|
||||
if (!err) {
|
||||
err = new Error('An error occurred');
|
||||
err = new Error(i18n.t('errors.errors.anErrorOccurred'));
|
||||
}
|
||||
|
||||
if (_.isString(err)) {
|
||||
@ -71,8 +72,8 @@ errors = {
|
||||
if ((process.env.NODE_ENV === 'development' ||
|
||||
process.env.NODE_ENV === 'staging' ||
|
||||
process.env.NODE_ENV === 'production')) {
|
||||
warn = warn || 'no message supplied';
|
||||
var msgs = [chalk.yellow('\nWarning:', warn), '\n'];
|
||||
warn = warn || i18n.t('errors.errors.noMessageSupplied');
|
||||
var msgs = [chalk.yellow(i18n.t('errors.errors.warning'), warn), '\n'];
|
||||
|
||||
if (context) {
|
||||
msgs.push(chalk.white(context), '\n');
|
||||
@ -109,22 +110,22 @@ errors = {
|
||||
if (_.isObject(err) && _.isString(err.message)) {
|
||||
err = err.message;
|
||||
} else {
|
||||
err = 'An unknown error occurred.';
|
||||
err = i18n.t('errors.errors.unknownErrorOccurred');
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite error to provide information that this is probably a permission problem
|
||||
// TODO: https://github.com/TryGhost/Ghost/issues/3687
|
||||
if (err.indexOf('SQLITE_READONLY') !== -1) {
|
||||
context = 'Your database is in read only mode. Visitors can read your blog, but you can\'t log in or add posts.';
|
||||
help = 'Check your database file and make sure that file owner and permissions are correct.';
|
||||
context = i18n.t('errors.errors.databaseIsReadOnly');
|
||||
help = i18n.t('errors.errors.checkDatabase');
|
||||
}
|
||||
// TODO: Logging framework hookup
|
||||
// Eventually we'll have better logging which will know about envs
|
||||
if ((process.env.NODE_ENV === 'development' ||
|
||||
process.env.NODE_ENV === 'staging' ||
|
||||
process.env.NODE_ENV === 'production')) {
|
||||
msgs = [chalk.red('\nERROR:', err), '\n'];
|
||||
msgs = [chalk.red(i18n.t('errors.errors.error'), err), '\n'];
|
||||
|
||||
if (context) {
|
||||
msgs.push(chalk.white(context), '\n');
|
||||
@ -199,7 +200,7 @@ errors = {
|
||||
statusCode = errorItem.code || 500;
|
||||
|
||||
errorContent.message = _.isString(errorItem) ? errorItem :
|
||||
(_.isObject(errorItem) ? errorItem.message : 'Unknown API Error');
|
||||
(_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownApiError'));
|
||||
errorContent.errorType = errorItem.errorType || 'InternalServerError';
|
||||
errors.push(errorContent);
|
||||
});
|
||||
@ -210,7 +211,7 @@ errors = {
|
||||
formatAndRejectAPIError: function (error, permsMessage) {
|
||||
if (!error) {
|
||||
return this.rejectError(
|
||||
new this.NoPermissionError(permsMessage || 'You do not have permission to perform this action')
|
||||
new this.NoPermissionError(permsMessage || i18n.t('errors.errors.notEnoughPermission'))
|
||||
);
|
||||
}
|
||||
|
||||
@ -293,22 +294,22 @@ errors = {
|
||||
return res.status(code).send(html);
|
||||
}
|
||||
// There was an error trying to render the error page, output the error
|
||||
self.logError(templateErr, 'Error whilst rendering error page', 'Error template has an error');
|
||||
self.logError(templateErr, i18n.t('errors.errors.errorWhilstRenderingError'), i18n.t('errors.errors.errorTemplateHasError'));
|
||||
|
||||
// And then try to explain things to the user...
|
||||
// Cheat and output the error using handlebars escapeExpression
|
||||
return res.status(500).send(
|
||||
'<h1>Oops, seems there is an error in the error template.</h1>' +
|
||||
'<p>Encountered the error: </p>' +
|
||||
'<h1>' + i18n.t('errors.errors.oopsErrorTemplateHasError') + '</h1>' +
|
||||
'<p>' + i18n.t('errors.errors.encounteredError') + '</p>' +
|
||||
'<pre>' + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + '</pre>' +
|
||||
'<br ><p>whilst trying to render an error page for the error: </p>' +
|
||||
'<br ><p>' + i18n.t('errors.errors.whilstTryingToRender') + '</p>' +
|
||||
code + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (code >= 500) {
|
||||
this.logError(err, 'Rendering Error Page', 'Ghost caught a processing error in the middleware layer.');
|
||||
this.logError(err, i18n.t('errors.errors.renderingErrorPage'), i18n.t('errors.errors.caughtProcessingError'));
|
||||
}
|
||||
|
||||
// Are we admin? If so, don't worry about the user template
|
||||
@ -321,7 +322,7 @@ errors = {
|
||||
},
|
||||
|
||||
error404: function (req, res, next) {
|
||||
var message = 'Page not found';
|
||||
var message = i18n.t('errors.errors.pageNotFound');
|
||||
|
||||
// do not cache 404 error
|
||||
res.set({'Cache-Control': 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'});
|
||||
@ -359,7 +360,7 @@ errors = {
|
||||
statusCode = errorItem.code || 500;
|
||||
|
||||
errorContent.message = _.isString(errorItem) ? errorItem :
|
||||
(_.isObject(errorItem) ? errorItem.message : 'Unknown Error');
|
||||
(_.isObject(errorItem) ? errorItem.message : i18n.t('errors.errors.unknownError'));
|
||||
errorContent.errorType = errorItem.errorType || 'InternalServerError';
|
||||
returnErrors.push(errorContent);
|
||||
});
|
||||
|
@ -4,7 +4,8 @@ var Promise = require('bluebird'),
|
||||
chalk = require('chalk'),
|
||||
fs = require('fs'),
|
||||
errors = require('./errors'),
|
||||
config = require('./config');
|
||||
config = require('./config'),
|
||||
i18n = require('./i18n');
|
||||
|
||||
/**
|
||||
* ## GhostServer
|
||||
@ -59,15 +60,15 @@ GhostServer.prototype.start = function (externalApp) {
|
||||
self.httpServer.on('error', function (error) {
|
||||
if (error.errno === 'EADDRINUSE') {
|
||||
errors.logError(
|
||||
'(EADDRINUSE) Cannot start Ghost.',
|
||||
'Port ' + config.server.port + ' is already in use by another program.',
|
||||
'Is another Ghost instance already running?'
|
||||
i18n.t('errors.httpServer.addressInUse.error'),
|
||||
i18n.t('errors.httpServer.addressInUse.context', {port: config.server.port}),
|
||||
i18n.t('errors.httpServer.addressInUse.help')
|
||||
);
|
||||
} else {
|
||||
errors.logError(
|
||||
'(Code: ' + error.errno + ')',
|
||||
'There was an error starting your server.',
|
||||
'Please use the error code above to search for a solution.'
|
||||
i18n.t('errors.httpServer.otherError.error', {errorNumber: error.errno}),
|
||||
i18n.t('errors.httpServer.otherError.context'),
|
||||
i18n.t('errors.httpServer.otherError.help')
|
||||
);
|
||||
}
|
||||
process.exit(-1);
|
||||
@ -118,7 +119,7 @@ GhostServer.prototype.restart = function () {
|
||||
* To be called after `stop`
|
||||
*/
|
||||
GhostServer.prototype.hammertime = function () {
|
||||
console.log(chalk.green('Can\'t touch this'));
|
||||
console.log(chalk.green(i18n.t('notices.httpServer.cantTouchThis')));
|
||||
|
||||
return Promise.resolve(this);
|
||||
};
|
||||
@ -166,33 +167,31 @@ GhostServer.prototype.logStartMessages = function () {
|
||||
// Startup & Shutdown messages
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log(
|
||||
chalk.green('Ghost is running in ' + process.env.NODE_ENV + '...'),
|
||||
'\nYour blog is now available on',
|
||||
config.url,
|
||||
chalk.gray('\nCtrl+C to shut down')
|
||||
chalk.green(i18n.t('notices.httpServer.ghostIsRunningIn', {env: process.env.NODE_ENV})),
|
||||
i18n.t('notices.httpServer.yourBlogIsAvailableOn', {url: config.url}),
|
||||
chalk.gray(i18n.t('notices.httpServer.ctrlCToShutDown'))
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
chalk.green('Ghost is running in ' + process.env.NODE_ENV + '...'),
|
||||
'\nListening on',
|
||||
config.getSocket() || config.server.host + ':' + config.server.port,
|
||||
'\nUrl configured as:',
|
||||
config.url,
|
||||
chalk.gray('\nCtrl+C to shut down')
|
||||
chalk.green(i18n.t('notices.httpServer.ghostIsRunningIn', {env: process.env.NODE_ENV})),
|
||||
i18n.t('notices.httpServer.listeningOn'),
|
||||
config.getSocket() || config.server.host + ':' + config.server.port,
|
||||
i18n.t('notices.httpServer.urlConfiguredAs', {url: config.url}),
|
||||
chalk.gray(i18n.t('notices.httpServer.ctrlCToShutDown'))
|
||||
);
|
||||
}
|
||||
|
||||
function shutdown() {
|
||||
console.log(chalk.red('\nGhost has shut down'));
|
||||
console.log(chalk.red(i18n.t('notices.httpServer.ghostHasShutdown')));
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log(
|
||||
'\nYour blog is now offline'
|
||||
i18n.t('notices.httpServer.yourBlogIsNowOffline')
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
'\nGhost was running for',
|
||||
i18n.t('notices.httpServer.ghostWasRunningFor'),
|
||||
Math.round(process.uptime()),
|
||||
'seconds'
|
||||
i18n.t('common.time.seconds')
|
||||
);
|
||||
}
|
||||
process.exit(0);
|
||||
@ -207,7 +206,7 @@ GhostServer.prototype.logStartMessages = function () {
|
||||
* ### Log Shutdown Messages
|
||||
*/
|
||||
GhostServer.prototype.logShutdownMessages = function () {
|
||||
console.log(chalk.red('Ghost is closing connections'));
|
||||
console.log(chalk.red(i18n.t('notices.httpServer.ghostIsClosingConnections')));
|
||||
};
|
||||
|
||||
module.exports = GhostServer;
|
||||
|
@ -5,13 +5,14 @@
|
||||
var hbs = require('express-hbs'),
|
||||
_ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
hbsUtils = hbs.handlebars.Utils,
|
||||
foreach;
|
||||
|
||||
foreach = function (context, options) {
|
||||
if (!options) {
|
||||
errors.logWarn('Need to pass an iterator to #foreach');
|
||||
errors.logWarn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
|
||||
}
|
||||
|
||||
var fn = options.fn,
|
||||
|
@ -8,6 +8,7 @@ var _ = require('lodash'),
|
||||
api = require('../api'),
|
||||
jsonpath = require('jsonpath'),
|
||||
labs = require('../utils/labs'),
|
||||
i18n = require('../i18n'),
|
||||
resources,
|
||||
pathAliases,
|
||||
get;
|
||||
@ -99,13 +100,13 @@ get = function get(context, options) {
|
||||
apiMethod;
|
||||
|
||||
if (!options.fn) {
|
||||
data.error = 'Get helper must be called as a block';
|
||||
data.error = i18n.t('warnings.helpers.get.mustBeCalledAsBlock');
|
||||
errors.logWarn(data.error);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!_.contains(resources, context)) {
|
||||
data.error = 'Invalid resource given to get helper';
|
||||
data.error = i18n.t('warnings.helpers.get.invalidResource');
|
||||
errors.logWarn(data.error);
|
||||
return Promise.resolve(options.inverse(self, {data: data}));
|
||||
}
|
||||
@ -145,9 +146,9 @@ get = function get(context, options) {
|
||||
module.exports = function getWithLabs(context, options) {
|
||||
var self = this,
|
||||
errorMessages = [
|
||||
'The {{get}} helper is not available.',
|
||||
'Public API access must be enabled if you wish to use the {{get}} helper.',
|
||||
'See http://support.ghost.org/public-api-beta'
|
||||
i18n.t('warnings.helpers.get.helperNotAvailable'),
|
||||
i18n.t('warnings.helpers.get.apiMustBeEnabled'),
|
||||
i18n.t('warnings.helpers.get.seeLink', {url: 'http://support.ghost.org/public-api-beta'})
|
||||
];
|
||||
|
||||
if (labs.isSet('publicAPI') === true) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
has;
|
||||
|
||||
has = function (options) {
|
||||
@ -40,7 +41,7 @@ has = function (options) {
|
||||
}
|
||||
|
||||
if (!tagList && !authorList) {
|
||||
errors.logWarn('Invalid or no attribute given to has helper');
|
||||
errors.logWarn(i18n.t('warnings.helpers.has.invalidAttribute'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ var hbs = require('express-hbs'),
|
||||
Promise = require('bluebird'),
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../i18n'),
|
||||
coreHelpers = {},
|
||||
registerHelpers;
|
||||
|
||||
@ -44,7 +45,7 @@ coreHelpers.helperMissing = function (arg) {
|
||||
if (arguments.length === 2) {
|
||||
return undefined;
|
||||
}
|
||||
errors.logError('Missing helper: "' + arg + '"');
|
||||
errors.logError(i18n.t('warnings.helpers.index.missingHelper', {arg: arg}));
|
||||
};
|
||||
|
||||
// Register an async handlebars helper for a given handlebars instance
|
||||
|
@ -3,6 +3,7 @@
|
||||
// Checks whether we're in a given context.
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
is;
|
||||
|
||||
is = function (context, options) {
|
||||
@ -11,7 +12,7 @@ is = function (context, options) {
|
||||
var currentContext = options.data.root.context;
|
||||
|
||||
if (!_.isString(context)) {
|
||||
errors.logWarn('Invalid or no attribute given to is helper');
|
||||
errors.logWarn(i18n.t('warnings.helpers.is.invalidAttribute'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
var _ = require('lodash'),
|
||||
hbs = require('express-hbs'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
errors = require('../errors'),
|
||||
template = require('./template'),
|
||||
@ -17,13 +18,13 @@ navigation = function (options) {
|
||||
context;
|
||||
|
||||
if (!_.isObject(navigationData) || _.isFunction(navigationData)) {
|
||||
return errors.logAndThrowError('navigation data is not an object or is a function');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.navigation.invalidData'));
|
||||
}
|
||||
|
||||
if (navigationData.filter(function (e) {
|
||||
return (_.isUndefined(e.label) || _.isUndefined(e.url));
|
||||
}).length > 0) {
|
||||
return errors.logAndThrowError('All values must be defined for label, url and current');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.navigation.valuesMustBeDefined'));
|
||||
}
|
||||
|
||||
// check for non-null string values
|
||||
@ -31,7 +32,7 @@ navigation = function (options) {
|
||||
return ((!_.isNull(e.label) && !_.isString(e.label)) ||
|
||||
(!_.isNull(e.url) && !_.isString(e.url)));
|
||||
}).length > 0) {
|
||||
return errors.logAndThrowError('Invalid value, Url and Label must be strings');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.navigation.valuesMustBeString'));
|
||||
}
|
||||
|
||||
function _slugify(label) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
var config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
page_url,
|
||||
pageUrl;
|
||||
|
||||
@ -44,9 +45,7 @@ page_url = function (context, block) {
|
||||
// context. This helper is deprecated and will be removed in future versions.
|
||||
//
|
||||
pageUrl = function (context, block) {
|
||||
errors.logWarn('Warning: pageUrl is deprecated, please use page_url instead\n' +
|
||||
'The helper pageUrl has been replaced with page_url in Ghost 0.4.2, and will be removed entirely in Ghost 0.6\n' +
|
||||
'In your theme\'s pagination.hbs file, pageUrl should be renamed to page_url');
|
||||
errors.logWarn(i18n.t('warnings.helpers.page_url.isDeprecated'));
|
||||
|
||||
/*jshint unused:false*/
|
||||
var self = this;
|
||||
|
@ -5,27 +5,28 @@
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
template = require('./template'),
|
||||
i18n = require('../i18n'),
|
||||
pagination;
|
||||
|
||||
pagination = function (options) {
|
||||
/*jshint unused:false*/
|
||||
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
|
||||
return errors.logAndThrowError('pagination data is not an object or is a function');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.invalidData'));
|
||||
}
|
||||
|
||||
if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages) ||
|
||||
_.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
|
||||
return errors.logAndThrowError('All values must be defined for page, pages, limit and total');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.valuesMustBeDefined'));
|
||||
}
|
||||
|
||||
if ((!_.isNull(this.pagination.next) && !_.isNumber(this.pagination.next)) ||
|
||||
(!_.isNull(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
|
||||
return errors.logAndThrowError('Invalid value, Next/Prev must be a number');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.nextPrevValuesMustBeNumeric'));
|
||||
}
|
||||
|
||||
if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages) ||
|
||||
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
|
||||
return errors.logAndThrowError('Invalid value, check page, pages, limit and total are numbers');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.pagination.valuesMustBeNumeric'));
|
||||
}
|
||||
|
||||
var context = _.merge({}, this.pagination);
|
||||
|
@ -11,12 +11,13 @@
|
||||
var hbs = require('express-hbs'),
|
||||
errors = require('../errors'),
|
||||
_ = require('lodash'),
|
||||
i18n = require('../i18n'),
|
||||
plural;
|
||||
|
||||
plural = function (context, options) {
|
||||
if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) ||
|
||||
_.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) {
|
||||
return errors.logAndThrowError('All values must be defined for empty, singular and plural');
|
||||
return errors.logAndThrowError(i18n.t('warnings.helpers.plural.valuesMustBeDefined'));
|
||||
}
|
||||
|
||||
if (context === 0) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
var templates = {},
|
||||
hbs = require('express-hbs'),
|
||||
errors = require('../errors');
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
// ## Template utils
|
||||
|
||||
@ -10,7 +11,7 @@ templates.execute = function (name, context, options) {
|
||||
var partial = hbs.handlebars.partials[name];
|
||||
|
||||
if (partial === undefined) {
|
||||
errors.logAndThrowError('Template ' + name + ' not found.');
|
||||
errors.logAndThrowError(i18n.t('warnings.helpers.template.templateNotFound', {name: name}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ var supportedLocales = ['en'],
|
||||
fs = require('fs'),
|
||||
chalk = require('chalk'),
|
||||
MessageFormat = require('intl-messageformat'),
|
||||
errors = require('./errors'),
|
||||
|
||||
// TODO: fetch this dynamically based on overall blog settings (`key = "defaultLang"` in the `settings` table
|
||||
currentLocale = 'en',
|
||||
@ -51,13 +50,16 @@ I18n = {
|
||||
*/
|
||||
findString: function findString(msgPath) {
|
||||
var matchingString, path;
|
||||
|
||||
// no path? no string
|
||||
if (_.isEmpty(msgPath) || !_.isString(msgPath)) {
|
||||
chalk.yellow('i18n:t() - received an empty path.');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (blos === undefined) {
|
||||
I18n.init();
|
||||
}
|
||||
|
||||
matchingString = blos;
|
||||
|
||||
path = msgPath.split('.');
|
||||
@ -67,7 +69,7 @@ I18n = {
|
||||
});
|
||||
|
||||
if (_.isNull(matchingString)) {
|
||||
errors.logError('Unable to find matching path [' + msgPath + '] in locale file.');
|
||||
console.error('Unable to find matching path [' + msgPath + '] in locale file.\n');
|
||||
matchingString = 'i18n error: path "' + msgPath + '" was not found.';
|
||||
}
|
||||
|
||||
@ -83,7 +85,6 @@ I18n = {
|
||||
// read file for current locale and keep its content in memory
|
||||
blos = fs.readFileSync(__dirname + '/translations/' + currentLocale + '.json');
|
||||
blos = JSON.parse(blos);
|
||||
|
||||
if (global.Intl) {
|
||||
// Determine if the built-in `Intl` has the locale data we need.
|
||||
var hasBuiltInLocaleData,
|
||||
|
@ -64,9 +64,8 @@ function builtFilesExist() {
|
||||
}
|
||||
|
||||
function checkExist(fileName) {
|
||||
var errorMessage = 'Javascript files have not been built.',
|
||||
errorHelp = '\nPlease read the getting started instructions at:' +
|
||||
'\nhttps://github.com/TryGhost/Ghost#getting-started';
|
||||
var errorMessage = i18n.t('errors.index.javascriptFilesNotBuilt.error'),
|
||||
errorHelp = i18n.t('errors.index.javascriptFilesNotBuilt.help', {link: '\nhttps://github.com/TryGhost/Ghost#getting-started'});
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.stat(fileName, function (statErr) {
|
||||
@ -102,9 +101,9 @@ function initNotifications() {
|
||||
api.notifications.add({notifications: [{
|
||||
type: 'info',
|
||||
message: [
|
||||
'Ghost is attempting to use a direct method to send email.',
|
||||
'It is recommended that you explicitly configure an email service.',
|
||||
'See <a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a> for instructions'
|
||||
i18n.t('warnings.index.usingDirectMethodToSendEmail'),
|
||||
i18n.t('common.seeLinkForInstructions',
|
||||
{link: '<a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a>'})
|
||||
].join(' ')
|
||||
}]}, {context: {internal: true}});
|
||||
}
|
||||
@ -112,8 +111,9 @@ function initNotifications() {
|
||||
api.notifications.add({notifications: [{
|
||||
type: 'warn',
|
||||
message: [
|
||||
'Ghost is currently unable to send email.',
|
||||
'See <a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a> for instructions'
|
||||
i18n.t('warnings.index.unableToSendEmail'),
|
||||
i18n.t('common.seeLinkForInstructions',
|
||||
{link: '<a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a>'})
|
||||
].join(' ')
|
||||
}]}, {context: {internal: true}});
|
||||
}
|
||||
@ -132,6 +132,9 @@ function init(options) {
|
||||
// It returns a promise that is resolved when the application
|
||||
// has finished starting up.
|
||||
|
||||
// Initialize Internationalization
|
||||
i18n.init();
|
||||
|
||||
// Load our config.js file from the local file system.
|
||||
return config.load(options.config).then(function () {
|
||||
return config.checkDeprecated();
|
||||
@ -170,9 +173,6 @@ function init(options) {
|
||||
}).then(function () {
|
||||
var adminHbs = hbs.create();
|
||||
|
||||
// Initialize Internationalization
|
||||
i18n.init();
|
||||
|
||||
// Output necessary notifications on init
|
||||
initNotifications();
|
||||
// ##Configuration
|
||||
|
@ -4,7 +4,8 @@ var _ = require('lodash'),
|
||||
Promise = require('bluebird'),
|
||||
nodemailer = require('nodemailer'),
|
||||
validator = require('validator'),
|
||||
config = require('../config');
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
function GhostMailer(opts) {
|
||||
opts = opts || {};
|
||||
@ -43,7 +44,7 @@ GhostMailer.prototype.from = function () {
|
||||
// If we do have a from address, and it's just an email
|
||||
if (validator.isEmail(from)) {
|
||||
if (!config.theme.title) {
|
||||
config.theme.title = 'Ghost at ' + this.getDomain();
|
||||
config.theme.title = i18n.t('common.mail.title', {domain: this.getDomain()});
|
||||
}
|
||||
from = '"' + config.theme.title + '" <' + from + '>';
|
||||
}
|
||||
@ -68,10 +69,10 @@ GhostMailer.prototype.send = function (message) {
|
||||
to = message.to || false;
|
||||
|
||||
if (!this.transport) {
|
||||
return Promise.reject(new Error('Error: No email transport configured.'));
|
||||
return Promise.reject(new Error(i18n.t('errors.mail.noEmailTransportConfigured.error')));
|
||||
}
|
||||
if (!(message && message.subject && message.html && message.to)) {
|
||||
return Promise.reject(new Error('Error: Incomplete message data.'));
|
||||
return Promise.reject(new Error(i18n.t('errors.mail.incompleteMessageData.error')));
|
||||
}
|
||||
sendMail = Promise.promisify(self.transport.sendMail.bind(self.transport));
|
||||
|
||||
@ -93,27 +94,27 @@ GhostMailer.prototype.send = function (message) {
|
||||
}
|
||||
|
||||
response.statusHandler.once('failed', function (data) {
|
||||
var reason = 'Error: Failed to send email';
|
||||
var reason = i18n.t('errors.mail.failedSendingEmail.error');
|
||||
|
||||
if (data.error && data.error.errno === 'ENOTFOUND') {
|
||||
reason += ' - no mail server found at ' + data.domain;
|
||||
reason += i18n.t('errors.mail.noMailServerAtAddress.error', {domain: data.domain});
|
||||
}
|
||||
reason += '.';
|
||||
return reject(new Error(reason));
|
||||
});
|
||||
|
||||
response.statusHandler.once('requeue', function (data) {
|
||||
var errorMessage = 'Error: Message could not be sent';
|
||||
var errorMessage = i18n.t('errors.mail.messageNotSent.error');
|
||||
|
||||
if (data.error && data.error.message) {
|
||||
errorMessage += '\nMore info: ' + data.error.message;
|
||||
errorMessage += i18n.t('errors.general.moreInfo', {info: data.error.message});
|
||||
}
|
||||
|
||||
return reject(new Error(errorMessage));
|
||||
});
|
||||
|
||||
response.statusHandler.once('sent', function () {
|
||||
return resolve('Message sent. Double check inbox and spam folder!');
|
||||
return resolve(i18n.t('notices.mail.messageSent'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -5,6 +5,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
labs = require('../utils/labs'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
auth;
|
||||
|
||||
@ -84,11 +85,11 @@ auth = {
|
||||
|
||||
if (!req.body.client_id || !req.body.client_secret) {
|
||||
errors.logError(
|
||||
'Client Authentication Failed',
|
||||
'Client credentials were not provided',
|
||||
'For information on how to fix this, please read http://api.ghost.org/docs/client-authentication'
|
||||
i18n.t('errors.middleware.auth.clientAuthenticaionFailed'),
|
||||
i18n.t('errors.middleware.auth.clientCredentialsNotProvided'),
|
||||
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
|
||||
);
|
||||
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
|
||||
}
|
||||
|
||||
return passport.authenticate(['oauth2-client-password'], {session: false, failWithError: false},
|
||||
@ -109,11 +110,11 @@ auth = {
|
||||
|
||||
if (!client || client.type !== 'ua') {
|
||||
errors.logError(
|
||||
'Client Authentication Failed',
|
||||
'Client credentials were not valid',
|
||||
'For information on how to fix this, please read http://api.ghost.org/docs/client-authentication'
|
||||
i18n.t('errors.middleware.auth.clientAuthenticaionFailed'),
|
||||
i18n.t('errors.middleware.auth.clientCredentialsNotValid'),
|
||||
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
|
||||
);
|
||||
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
|
||||
}
|
||||
|
||||
if (!origin && client && client.type === 'ua') {
|
||||
@ -127,10 +128,10 @@ auth = {
|
||||
req.client = client;
|
||||
return next(null, client);
|
||||
} else {
|
||||
error = new errors.UnauthorizedError('Access Denied from url: ' + origin + '. Please use the url configured in config.js.');
|
||||
error = new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDeniedFromUrl', {origin: origin}));
|
||||
errors.logError(error,
|
||||
'You have attempted to access your Ghost admin panel from a url that does not appear in config.js.',
|
||||
'For information on how to fix this, please read http://support.ghost.org/config/#url.'
|
||||
i18n.t('errors.middleware.auth.attemptedToAccessAdmin'),
|
||||
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://support.ghost.org/config/#url'})
|
||||
);
|
||||
return errors.handleAPIError(error, req, res, next);
|
||||
}
|
||||
@ -151,12 +152,12 @@ auth = {
|
||||
req.user = user;
|
||||
return next(null, user, info);
|
||||
} else if (isBearerAutorizationHeader(req)) {
|
||||
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
|
||||
} else if (req.client) {
|
||||
return next();
|
||||
}
|
||||
|
||||
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||
return errors.handleAPIError(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')), req, res, next);
|
||||
}
|
||||
)(req, res, next);
|
||||
},
|
||||
@ -167,7 +168,7 @@ auth = {
|
||||
if (req.user) {
|
||||
return next();
|
||||
} else {
|
||||
return errors.handleAPIError(new errors.NoPermissionError('Please Sign In'), req, res, next);
|
||||
return errors.handleAPIError(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')), req, res, next);
|
||||
}
|
||||
},
|
||||
|
||||
@ -179,7 +180,7 @@ auth = {
|
||||
if (req.user) {
|
||||
return next();
|
||||
} else {
|
||||
return errors.handleAPIError(new errors.NoPermissionError('Please Sign In'), req, res, next);
|
||||
return errors.handleAPIError(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')), req, res, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ var BusBoy = require('busboy'),
|
||||
fs = require('fs-extra'),
|
||||
path = require('path'),
|
||||
os = require('os'),
|
||||
i18n = require('../i18n'),
|
||||
crypto = require('crypto');
|
||||
|
||||
// ### ghostBusboy
|
||||
@ -49,21 +50,21 @@ function ghostBusBoy(req, res, next) {
|
||||
});
|
||||
|
||||
file.on('error', function onError(error) {
|
||||
console.log('Error', 'Something went wrong uploading the file', error);
|
||||
console.log('Error', i18n.t('errors.middleware.ghostbusboy.fileUploadingError'), error);
|
||||
});
|
||||
|
||||
stream = fs.createWriteStream(filePath);
|
||||
|
||||
stream.on('error', function onError(error) {
|
||||
console.log('Error', 'Something went wrong uploading the file', error);
|
||||
console.log('Error', i18n.t('errors.middleware.ghostbusboy.fileUploadingError'), error);
|
||||
});
|
||||
|
||||
file.pipe(stream);
|
||||
});
|
||||
|
||||
busboy.on('error', function onError(error) {
|
||||
console.log('Error', 'Something went wrong parsing the form', error);
|
||||
res.status(500).send({code: 500, message: 'Could not parse upload completely.'});
|
||||
console.log('Error', i18n.t('errors.middleware.ghostbusboy.somethingWentWrong'), error);
|
||||
res.status(500).send({code: 500, message: i18n.t('errors.middleware.ghostbusboy.couldNotParseUpload')});
|
||||
});
|
||||
|
||||
busboy.on('field', function onField(fieldname, val) {
|
||||
|
@ -3,6 +3,7 @@ var oauth2orize = require('oauth2orize'),
|
||||
utils = require('../utils'),
|
||||
errors = require('../errors'),
|
||||
spamPrevention = require('./spam-prevention'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
oauthServer,
|
||||
oauth;
|
||||
@ -10,7 +11,7 @@ var oauth2orize = require('oauth2orize'),
|
||||
function exchangeRefreshToken(client, refreshToken, scope, done) {
|
||||
models.Refreshtoken.findOne({token: refreshToken}).then(function then(model) {
|
||||
if (!model) {
|
||||
return done(new errors.NoPermissionError('Invalid refresh token.'), false);
|
||||
return done(new errors.NoPermissionError(i18n.t('errors.middleware.oauth.invalidRefreshToken')), false);
|
||||
} else {
|
||||
var token = model.toJSON(),
|
||||
accessToken = utils.uid(256),
|
||||
@ -31,7 +32,7 @@ function exchangeRefreshToken(client, refreshToken, scope, done) {
|
||||
return done(error, false);
|
||||
});
|
||||
} else {
|
||||
done(new errors.UnauthorizedError('Refresh token expired.'), false);
|
||||
done(new errors.UnauthorizedError(i18n.t('errors.middleware.oauth.refreshTokenExpired')), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -41,7 +42,7 @@ function exchangePassword(client, username, password, scope, done) {
|
||||
// Validate the client
|
||||
models.Client.findOne({slug: client.slug}).then(function then(client) {
|
||||
if (!client) {
|
||||
return done(new errors.NoPermissionError('Invalid client.'), false);
|
||||
return done(new errors.NoPermissionError(i18n.t('errors.middleware.oauth.invalidClient')), false);
|
||||
}
|
||||
// Validate the user
|
||||
return models.User.check({email: username, password: password}).then(function then(user) {
|
||||
|
@ -8,6 +8,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
session = require('cookie-session'),
|
||||
utils = require('../utils'),
|
||||
i18n = require('../i18n'),
|
||||
privateBlogging;
|
||||
|
||||
function verifySessionHash(salt, hash) {
|
||||
@ -125,7 +126,7 @@ privateBlogging = {
|
||||
return res.redirect(config.urlFor({relativeUrl: decodeURIComponent(forward)}));
|
||||
} else {
|
||||
res.error = {
|
||||
message: 'Wrong password'
|
||||
message: i18n.t('errors.middleware.privateblogging.wrongPassword')
|
||||
};
|
||||
return next();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
i18n = require('../i18n'),
|
||||
loginSecurity = [],
|
||||
forgottenSecurity = [],
|
||||
protectedSecurity = [],
|
||||
@ -22,7 +23,7 @@ spamPrevention = {
|
||||
remoteAddress = req.connection.remoteAddress,
|
||||
deniedRateLimit = '',
|
||||
ipCount = '',
|
||||
message = 'Too many attempts.',
|
||||
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
|
||||
rateSigninPeriod = config.rateSigninPeriod || 3600,
|
||||
rateSigninAttempts = config.rateSigninAttempts || 10;
|
||||
|
||||
@ -31,7 +32,7 @@ spamPrevention = {
|
||||
} else if (req.body.grant_type === 'refresh_token') {
|
||||
return next();
|
||||
} else {
|
||||
return next(new errors.BadRequestError('No username.'));
|
||||
return next(new errors.BadRequestError(i18n.t('errors.middleware.spamprevention.noUsername')));
|
||||
}
|
||||
|
||||
// filter entries that are older than rateSigninPeriod
|
||||
@ -45,10 +46,10 @@ spamPrevention = {
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateSigninAttempts + ' tries per IP address every ' + rateSigninPeriod + ' seconds.',
|
||||
'Too many login attempts.'
|
||||
i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', {rateSigninAttempts: rateSigninAttempts, rateSigninPeriod: rateSigninPeriod}),
|
||||
i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
|
||||
);
|
||||
message += rateSigninPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
message += rateSigninPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');
|
||||
return next(new errors.TooManyRequestsError(message));
|
||||
}
|
||||
next();
|
||||
@ -65,7 +66,7 @@ spamPrevention = {
|
||||
ipCount = '',
|
||||
deniedRateLimit = '',
|
||||
deniedEmailRateLimit = '',
|
||||
message = 'Too many attempts.',
|
||||
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
|
||||
index = _.findIndex(forgottenSecurity, function findIndex(logTime) {
|
||||
return (logTime.ip === remoteAddress && logTime.email === email);
|
||||
});
|
||||
@ -77,7 +78,7 @@ spamPrevention = {
|
||||
forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0});
|
||||
}
|
||||
} else {
|
||||
return next(new errors.BadRequestError('No email.'));
|
||||
return next(new errors.BadRequestError(i18n.t('errors.middleware.spamprevention.noEmail')));
|
||||
}
|
||||
|
||||
// filter entries that are older than rateForgottenPeriod
|
||||
@ -95,21 +96,20 @@ spamPrevention = {
|
||||
|
||||
if (deniedEmailRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateForgottenAttempts + ' forgotten password attempts per email every ' +
|
||||
rateForgottenPeriod + ' seconds.',
|
||||
'Forgotten password reset attempt failed'
|
||||
i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
|
||||
i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
|
||||
);
|
||||
}
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateForgottenAttempts + ' tries per IP address every ' + rateForgottenPeriod + ' seconds.',
|
||||
'Forgotten password reset attempt failed'
|
||||
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
|
||||
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
|
||||
);
|
||||
}
|
||||
|
||||
if (deniedEmailRateLimit || deniedRateLimit) {
|
||||
message += rateForgottenPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
message += rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');
|
||||
return next(new errors.TooManyRequestsError(message));
|
||||
}
|
||||
|
||||
@ -122,7 +122,7 @@ spamPrevention = {
|
||||
rateProtectedPeriod = config.rateProtectedPeriod || 3600,
|
||||
rateProtectedAttempts = config.rateProtectedAttempts || 10,
|
||||
ipCount = '',
|
||||
message = 'Too many attempts.',
|
||||
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
|
||||
deniedRateLimit = '',
|
||||
password = req.body.password;
|
||||
|
||||
@ -130,7 +130,7 @@ spamPrevention = {
|
||||
protectedSecurity.push({ip: remoteAddress, time: currentTime});
|
||||
} else {
|
||||
res.error = {
|
||||
message: 'No password entered'
|
||||
message: i18n.t('errors.middleware.spamprevention.noPassword')
|
||||
};
|
||||
return next();
|
||||
}
|
||||
@ -145,10 +145,10 @@ spamPrevention = {
|
||||
|
||||
if (deniedRateLimit) {
|
||||
errors.logError(
|
||||
'Only ' + rateProtectedAttempts + ' tries per IP address every ' + rateProtectedPeriod + ' seconds.',
|
||||
'Too many login attempts.'
|
||||
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod}),
|
||||
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
|
||||
);
|
||||
message += rateProtectedPeriod === 3600 ? ' Please wait 1 hour.' : ' Please try again later';
|
||||
message += rateProtectedPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');
|
||||
res.error = {
|
||||
message: message
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ var _ = require('lodash'),
|
||||
api = require('../api'),
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
themeHandler;
|
||||
|
||||
themeHandler = {
|
||||
@ -99,14 +100,14 @@ themeHandler = {
|
||||
if (!config.paths.availableThemes.hasOwnProperty(activeTheme.value)) {
|
||||
if (!res.isAdmin) {
|
||||
// Throw an error if the theme is not available, but not on the admin UI
|
||||
return errors.throwError('The currently active theme "' + activeTheme.value + '" is missing.');
|
||||
return errors.throwError(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value}));
|
||||
} else {
|
||||
// At this point the activated theme is not present and the current
|
||||
// request is for the admin client. In order to allow the user access
|
||||
// to the admin client we set an hbs instance on the app so that middleware
|
||||
// processing can continue.
|
||||
blogApp.engine('hbs', hbs.express3());
|
||||
errors.logWarn('The currently active theme "' + activeTheme.value + '" is missing.');
|
||||
errors.logWarn(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value}));
|
||||
|
||||
return next();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ var _ = require('lodash'),
|
||||
uuid = require('node-uuid'),
|
||||
validation = require('../../data/validation'),
|
||||
plugins = require('../plugins'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
ghostBookshelf,
|
||||
proto;
|
||||
@ -139,7 +140,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||
} else if (options.context && options.context.internal) {
|
||||
return 1;
|
||||
} else {
|
||||
errors.logAndThrowError(new Error('missing context'));
|
||||
errors.logAndThrowError(new Error(i18n.t('errors.models.base.index.missingContext')));
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
var Promise = require('bluebird'),
|
||||
ghostBookshelf = require('./index'),
|
||||
errors = require('../../errors'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
Basetoken;
|
||||
|
||||
@ -56,7 +57,7 @@ Basetoken = ghostBookshelf.Model.extend({
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('No user found'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.base.token.noUserFound')));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -77,7 +78,7 @@ Basetoken = ghostBookshelf.Model.extend({
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Token not found'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.base.token.tokenNotFound')));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
var _ = require('lodash'),
|
||||
errors = require('../../errors'),
|
||||
gql = require('ghost-gql'),
|
||||
i18n = require('../../i18n'),
|
||||
filter,
|
||||
filterUtils;
|
||||
|
||||
@ -26,8 +27,8 @@ filterUtils = {
|
||||
} catch (error) {
|
||||
errors.logAndThrowError(
|
||||
new errors.ValidationError(error.message, 'filter'),
|
||||
'Error parsing filter',
|
||||
'For more information on how to use filter, see http://api.ghost.org/docs/filter'
|
||||
i18n.t('errors.models.plugins.filter.errorParsing'),
|
||||
i18n.t('errors.models.plugins.filter.forInformationRead', {url: 'http://api.ghost.org/docs/filter'})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ var _ = require('lodash'),
|
||||
events = require('../events'),
|
||||
config = require('../config'),
|
||||
baseUtils = require('./base/utils'),
|
||||
i18n = require('../i18n'),
|
||||
Post,
|
||||
Posts;
|
||||
|
||||
@ -116,7 +117,7 @@ Post = ghostBookshelf.Model.extend({
|
||||
|
||||
// disabling sanitization until we can implement a better version
|
||||
// this.set('title', this.sanitize('title').trim());
|
||||
title = this.get('title') || '(Untitled)';
|
||||
title = this.get('title') || i18n.t('errors.models.post.untitled');
|
||||
this.set('title', title.trim());
|
||||
|
||||
// ### Business logic for published_at and published_by
|
||||
@ -255,11 +256,11 @@ Post = ghostBookshelf.Model.extend({
|
||||
}).catch(function failure(error) {
|
||||
errors.logError(
|
||||
error,
|
||||
'Unable to save tags.',
|
||||
'Your post was saved, but your tags were not updated.'
|
||||
i18n.t('errors.models.post.tagUpdates.error'),
|
||||
i18n.t('errors.models.post.tagUpdates.help')
|
||||
);
|
||||
return Promise.reject(new errors.InternalServerError(
|
||||
'Unable to save tags. Your post was saved, but your tags were not updated. ' + error
|
||||
i18n.t('errors.models.post.tagUpdates.error') + ' ' + i18n.t('errors.models.post.tagUpdates.help') + error
|
||||
));
|
||||
});
|
||||
}
|
||||
@ -555,7 +556,7 @@ Post = ghostBookshelf.Model.extend({
|
||||
return Promise.reject(new errors.InternalServerError(error.message || error));
|
||||
});
|
||||
}
|
||||
return Promise.reject(new errors.NotFoundError('No user found'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.post.noUserFound')));
|
||||
},
|
||||
|
||||
permissible: function permissible(postModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) {
|
||||
@ -586,7 +587,7 @@ Post = ghostBookshelf.Model.extend({
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.post.notEnoughPermission')));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2,6 +2,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
ghostBookshelf = require('./base'),
|
||||
Promise = require('bluebird'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
Role,
|
||||
Roles;
|
||||
@ -75,7 +76,7 @@ Role = ghostBookshelf.Model.extend({
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.role.notEnoughPermission')));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -7,6 +7,7 @@ var Settings,
|
||||
validation = require('../data/validation'),
|
||||
events = require('../events'),
|
||||
internal = {context: {internal: true}},
|
||||
i18n = require('../i18n'),
|
||||
|
||||
defaultSettings;
|
||||
|
||||
@ -119,7 +120,7 @@ Settings = ghostBookshelf.Model.extend({
|
||||
// Accept an array of models as input
|
||||
if (item.toJSON) { item = item.toJSON(); }
|
||||
if (!(_.isString(item.key) && item.key.length > 0)) {
|
||||
return Promise.reject(new errors.ValidationError('Value in [settings.key] cannot be blank.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.settings.valueCannotBeBlank')));
|
||||
}
|
||||
|
||||
item = self.filterData(item);
|
||||
@ -138,14 +139,14 @@ Settings = ghostBookshelf.Model.extend({
|
||||
return setting.save(saveData, options);
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NotFoundError('Unable to find setting to update: ' + item.key));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.settings.unableToFindSetting', {key: item.key})));
|
||||
}, errors.logAndThrowError);
|
||||
});
|
||||
},
|
||||
|
||||
populateDefault: function (key) {
|
||||
if (!getDefaultSettings()[key]) {
|
||||
return Promise.reject(new errors.NotFoundError('Unable to find default setting: ' + key));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.settings.unableToFindDefaultSetting', {key: key})));
|
||||
}
|
||||
|
||||
return this.findOne({key: key}).then(function then(foundSetting) {
|
||||
|
@ -10,6 +10,7 @@ var _ = require('lodash'),
|
||||
validation = require('../data/validation'),
|
||||
config = require('../config'),
|
||||
events = require('../events'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
bcryptGenSalt = Promise.promisify(bcrypt.genSalt),
|
||||
bcryptHash = Promise.promisify(bcrypt.hash),
|
||||
@ -115,7 +116,7 @@ User = ghostBookshelf.Model.extend({
|
||||
} else if (this.get('id')) {
|
||||
return this.get('id');
|
||||
} else {
|
||||
errors.logAndThrowError(new errors.NotFoundError('missing context'));
|
||||
errors.logAndThrowError(new errors.NotFoundError(i18n.t('errors.models.user.missingContext')));
|
||||
}
|
||||
},
|
||||
|
||||
@ -303,7 +304,7 @@ User = ghostBookshelf.Model.extend({
|
||||
|
||||
if (data.roles && data.roles.length > 1) {
|
||||
return Promise.reject(
|
||||
new errors.ValidationError('Only one role per user is supported at the moment.')
|
||||
new errors.ValidationError(i18n.t('errors.models.user.onlyOneRolePerUserSupported'))
|
||||
);
|
||||
}
|
||||
|
||||
@ -326,7 +327,7 @@ User = ghostBookshelf.Model.extend({
|
||||
}).then(function then(roleToAssign) {
|
||||
if (roleToAssign && roleToAssign.get('name') === 'Owner') {
|
||||
return Promise.reject(
|
||||
new errors.ValidationError('This method does not support assigning the owner role')
|
||||
new errors.ValidationError(i18n.t('errors.models.user.methodDoesNotSupportOwnerRole'))
|
||||
);
|
||||
} else {
|
||||
// assign all other roles
|
||||
@ -359,11 +360,11 @@ User = ghostBookshelf.Model.extend({
|
||||
|
||||
// check for too many roles
|
||||
if (data.roles && data.roles.length > 1) {
|
||||
return Promise.reject(new errors.ValidationError('Only one role per user is supported at the moment.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.onlyOneRolePerUserSupported')));
|
||||
}
|
||||
|
||||
if (!validatePasswordLength(userData.password)) {
|
||||
return Promise.reject(new errors.ValidationError('Your password must be at least 8 characters long.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
|
||||
}
|
||||
|
||||
function getAuthorRole() {
|
||||
@ -411,7 +412,7 @@ User = ghostBookshelf.Model.extend({
|
||||
userData = this.filterData(data);
|
||||
|
||||
if (!validatePasswordLength(userData.password)) {
|
||||
return Promise.reject(new errors.ValidationError('Your password must be at least 8 characters long.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
|
||||
}
|
||||
|
||||
options = this.filterOptions(options, 'setup');
|
||||
@ -478,7 +479,7 @@ User = ghostBookshelf.Model.extend({
|
||||
if (action === 'destroy') {
|
||||
// Owner cannot be deleted EVER
|
||||
if (loadedPermissions.user && userModel.hasRole('Owner')) {
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.notEnoughPermission')));
|
||||
}
|
||||
|
||||
// Users with the role 'Editor' have complex permissions when the action === 'destroy'
|
||||
@ -495,7 +496,7 @@ User = ghostBookshelf.Model.extend({
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.notEnoughPermission')));
|
||||
},
|
||||
|
||||
setWarning: function setWarning(user, options) {
|
||||
@ -525,20 +526,19 @@ User = ghostBookshelf.Model.extend({
|
||||
s;
|
||||
return this.getByEmail(object.email).then(function then(user) {
|
||||
if (!user) {
|
||||
return Promise.reject(new errors.NotFoundError('There is no user with that email address.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.noUserWithEnteredEmailAddr')));
|
||||
}
|
||||
if (user.get('status') === 'invited' || user.get('status') === 'invited-pending' ||
|
||||
user.get('status') === 'inactive'
|
||||
) {
|
||||
return Promise.reject(new errors.NoPermissionError('The user with that email address is inactive.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.userisInactive')));
|
||||
}
|
||||
if (user.get('status') !== 'locked') {
|
||||
return bcryptCompare(object.password, user.get('password')).then(function then(matched) {
|
||||
if (!matched) {
|
||||
return Promise.resolve(self.setWarning(user, {validate: false})).then(function then(remaining) {
|
||||
s = (remaining > 1) ? 's' : '';
|
||||
return Promise.reject(new errors.UnauthorizedError('Your password is incorrect. <br />' +
|
||||
remaining + ' attempt' + s + ' remaining!'));
|
||||
return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPasswordAttempts', {remaining: remaining, s: s})));
|
||||
|
||||
// Use comma structure, not .catch, because we don't want to catch incorrect passwords
|
||||
}, function handleError(error) {
|
||||
@ -546,10 +546,10 @@ User = ghostBookshelf.Model.extend({
|
||||
// cause a login error because of it. The user validation is not important here.
|
||||
errors.logError(
|
||||
error,
|
||||
'Error thrown from user update during login',
|
||||
'Visit and save your profile after logging in to check for problems.'
|
||||
i18n.t('errors.models.user.userUpdateError.context'),
|
||||
i18n.t('errors.models.user.userUpdateError.help')
|
||||
);
|
||||
return Promise.reject(new errors.UnauthorizedError('Your password is incorrect.'));
|
||||
return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPassword')));
|
||||
});
|
||||
}
|
||||
|
||||
@ -559,18 +559,18 @@ User = ghostBookshelf.Model.extend({
|
||||
// cause a login error because of it. The user validation is not important here.
|
||||
errors.logError(
|
||||
error,
|
||||
'Error thrown from user update during login',
|
||||
'Visit and save your profile after logging in to check for problems.'
|
||||
i18n.t('errors.models.user.userUpdateError.context'),
|
||||
i18n.t('errors.models.user.userUpdateError.help')
|
||||
);
|
||||
return user;
|
||||
});
|
||||
}, errors.logAndThrowError);
|
||||
}
|
||||
return Promise.reject(new errors.NoPermissionError('Your account is locked. Please reset your password ' +
|
||||
'to log in again by clicking the "Forgotten password?" link!'));
|
||||
return Promise.reject(new errors.NoPermissionError(
|
||||
i18n.t('errors.models.user.accountLocked')));
|
||||
}, function handleError(error) {
|
||||
if (error.message === 'NotFound' || error.message === 'EmptyResponse') {
|
||||
return Promise.reject(new errors.NotFoundError('There is no user with that email address.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.noUserWithEnteredEmailAddr')));
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
@ -591,15 +591,15 @@ User = ghostBookshelf.Model.extend({
|
||||
user;
|
||||
|
||||
if (newPassword !== ne2Password) {
|
||||
return Promise.reject(new errors.ValidationError('Your new passwords do not match'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.newPasswordsDoNotMatch')));
|
||||
}
|
||||
|
||||
if (userId === options.context.user && _.isEmpty(oldPassword)) {
|
||||
return Promise.reject(new errors.ValidationError('Password is required for this operation'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordRequiredForOperation')));
|
||||
}
|
||||
|
||||
if (!validatePasswordLength(newPassword)) {
|
||||
return Promise.reject(new errors.ValidationError('Your password must be at least 8 characters long.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
|
||||
}
|
||||
|
||||
return self.forge({id: userId}).fetch({require: true}).then(function then(_user) {
|
||||
@ -611,7 +611,7 @@ User = ghostBookshelf.Model.extend({
|
||||
return true;
|
||||
}).then(function then(matched) {
|
||||
if (!matched) {
|
||||
return Promise.reject(new errors.ValidationError('Your password is incorrect'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.incorrectPassword')));
|
||||
}
|
||||
|
||||
return generatePasswordHash(newPassword);
|
||||
@ -623,7 +623,7 @@ User = ghostBookshelf.Model.extend({
|
||||
generateResetToken: function generateResetToken(email, expires, dbHash) {
|
||||
return this.getByEmail(email).then(function then(foundUser) {
|
||||
if (!foundUser) {
|
||||
return Promise.reject(new errors.NotFoundError('There is no user with that email address.'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.noUserWithEnteredEmailAddr')));
|
||||
}
|
||||
|
||||
var hash = crypto.createHash('sha256'),
|
||||
@ -653,25 +653,25 @@ User = ghostBookshelf.Model.extend({
|
||||
|
||||
// Check if invalid structure
|
||||
if (!parts || parts.length !== 3) {
|
||||
return Promise.reject(new errors.BadRequestError('Invalid token structure'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.models.user.invalidTokenStructure')));
|
||||
}
|
||||
|
||||
expires = parseInt(parts[0], 10);
|
||||
email = parts[1];
|
||||
|
||||
if (isNaN(expires)) {
|
||||
return Promise.reject(new errors.BadRequestError('Invalid token expiration'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.models.user.invalidTokenExpiration')));
|
||||
}
|
||||
|
||||
// Check if token is expired to prevent replay attacks
|
||||
if (expires < Date.now()) {
|
||||
return Promise.reject(new errors.ValidationError('Expired token'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.expiredToken')));
|
||||
}
|
||||
|
||||
// to prevent brute force attempts to reset the password the combination of email+expires is only allowed for
|
||||
// 10 attempts
|
||||
if (tokenSecurity[email + '+' + expires] && tokenSecurity[email + '+' + expires].count >= 10) {
|
||||
return Promise.reject(new errors.NoPermissionError('Token locked'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.tokenLocked')));
|
||||
}
|
||||
|
||||
return this.generateResetToken(email, expires, dbHash).then(function then(generatedToken) {
|
||||
@ -696,7 +696,7 @@ User = ghostBookshelf.Model.extend({
|
||||
tokenSecurity[email + '+' + expires] = {
|
||||
count: tokenSecurity[email + '+' + expires] ? tokenSecurity[email + '+' + expires].count + 1 : 1
|
||||
};
|
||||
return Promise.reject(new errors.BadRequestError('Invalid token'));
|
||||
return Promise.reject(new errors.BadRequestError(i18n.t('errors.models.user.invalidToken')));
|
||||
});
|
||||
},
|
||||
|
||||
@ -708,11 +708,11 @@ User = ghostBookshelf.Model.extend({
|
||||
dbHash = options.dbHash;
|
||||
|
||||
if (newPassword !== ne2Password) {
|
||||
return Promise.reject(new errors.ValidationError('Your new passwords do not match'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.newPasswordsDoNotMatch')));
|
||||
}
|
||||
|
||||
if (!validatePasswordLength(newPassword)) {
|
||||
return Promise.reject(new errors.ValidationError('Your password must be at least 8 characters long.'));
|
||||
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
|
||||
}
|
||||
|
||||
// Validate the token; returns the email address from token
|
||||
@ -724,7 +724,7 @@ User = ghostBookshelf.Model.extend({
|
||||
);
|
||||
}).then(function then(results) {
|
||||
if (!results[0]) {
|
||||
return Promise.reject(new errors.NotFoundError('User not found'));
|
||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.userNotFound')));
|
||||
}
|
||||
|
||||
// Update the user with the new password hash
|
||||
@ -748,7 +748,7 @@ User = ghostBookshelf.Model.extend({
|
||||
// check if user has the owner role
|
||||
var currentRoles = contextUser.toJSON(options).roles;
|
||||
if (!_.any(currentRoles, {id: ownerRole.id})) {
|
||||
return Promise.reject(new errors.NoPermissionError('Only owners are able to transfer the owner role.'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.onlyOwnerCanTransferOwnerRole')));
|
||||
}
|
||||
|
||||
return Promise.join(ghostBookshelf.model('Role').findOne({name: 'Administrator'}),
|
||||
@ -759,7 +759,7 @@ User = ghostBookshelf.Model.extend({
|
||||
currentRoles = user.toJSON(options).roles;
|
||||
|
||||
if (!_.any(currentRoles, {id: adminRole.id})) {
|
||||
return Promise.reject(new errors.ValidationError('Only administrators can be assigned the owner role.'));
|
||||
return Promise.reject(new errors.ValidationError('errors.models.user.onlyAdmCanBeAssignedOwnerRole'));
|
||||
}
|
||||
|
||||
// convert owner to admin
|
||||
|
@ -6,6 +6,7 @@ var _ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
Models = require('../models'),
|
||||
effectivePerms = require('./effective'),
|
||||
i18n = require('../i18n'),
|
||||
init,
|
||||
refresh,
|
||||
canThis,
|
||||
@ -49,7 +50,7 @@ function parseContext(context) {
|
||||
}
|
||||
|
||||
function applyStatusRules(docName, method, opts) {
|
||||
var errorMsg = 'You do not have permission to retrieve ' + docName + ' with that status';
|
||||
var errorMsg = i18n.t('errors.permissions.applyStatusRules.error', {docName: docName});
|
||||
|
||||
// Enforce status 'active' for users
|
||||
if (docName === 'users') {
|
||||
@ -193,7 +194,7 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, c
|
||||
return;
|
||||
}
|
||||
|
||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
||||
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.permissions.noPermissionToAction')));
|
||||
});
|
||||
};
|
||||
|
||||
@ -211,7 +212,7 @@ CanThisResult.prototype.beginCheck = function (context) {
|
||||
context = parseContext(context);
|
||||
|
||||
if (!hasActionsMap()) {
|
||||
throw new Error('No actions map found, please call permissions.init() before use.');
|
||||
throw new Error(i18n.t('errors.permissions.noActionsMapFound.error'));
|
||||
}
|
||||
|
||||
// Kick off loading of effective user permissions if necessary
|
||||
|
@ -1,14 +1,621 @@
|
||||
{
|
||||
"common": {
|
||||
|
||||
"mail": {
|
||||
"title": "Ghost at {domain}"
|
||||
},
|
||||
"seeLinkForInstructions": "See {link} for instructions.",
|
||||
"time": {
|
||||
"seconds": "seconds"
|
||||
},
|
||||
"api": {
|
||||
"authentication": {
|
||||
"sampleBlogDescription": "Thoughts, stories and ideas.",
|
||||
"mail": {
|
||||
"resetPassword": "Reset Password",
|
||||
"checkEmailForInstructions": "Check your email for further instructions.",
|
||||
"passwordChanged": "Password changed successfully.",
|
||||
"invitationAccepted": "Invitation accepted.",
|
||||
"yourNewGhostBlog": "Your New Ghost Blog"
|
||||
}
|
||||
},
|
||||
"mail": {
|
||||
"testGhostEmail": "Test Ghost Email"
|
||||
},
|
||||
"users": {
|
||||
"mail": {
|
||||
"invitedByName": "{invitedByName} has invited you to join {blogName}"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
|
||||
"apps": {
|
||||
"failedToParseActiveAppsSettings": {
|
||||
"error": "Failed to parse activeApps setting value: {message}",
|
||||
"context": "Your apps will not be loaded.",
|
||||
"help": "Check your settings table for typos in the activeApps value. It should look like: [\"app-1\", \"app2\"] (double quotes required)."
|
||||
},
|
||||
"appWillNotBeLoaded": {
|
||||
"error": "The app will not be loaded",
|
||||
"help": "Check with the app creator, or read the app documentation for more details on app requirements"
|
||||
},
|
||||
"permissionsErrorLoadingApp": {
|
||||
"error": "Error loading app named {name}; problem reading permissions: {message}"
|
||||
},
|
||||
"noInstallMethodLoadingApp": {
|
||||
"error": "Error loading app named {name}; no install() method defined."
|
||||
},
|
||||
"noActivateMethodLoadingApp": {
|
||||
"error": "Error loading app named {name}; no activate() method defined."
|
||||
},
|
||||
"accessResourceWithoutPermission": {
|
||||
"error": "The App \"{name}\" attempted to perform an action or access a resource ({perm}.{method}) without permission."
|
||||
},
|
||||
"mustProvideAppName": {
|
||||
"error": "Must provide an app name for api context"
|
||||
},
|
||||
"mustProvideAppPermissions": {
|
||||
"error": "Must provide app permissions"
|
||||
},
|
||||
"unsafeAppRequire": {
|
||||
"error": "Unsafe App require: {msg}"
|
||||
}
|
||||
},
|
||||
"middleware": {
|
||||
"auth": {
|
||||
"clientAuthenticaionFailed": "Client Authentication Failed",
|
||||
"clientCredentialsNotProvided": "Client credentials were not provided",
|
||||
"clientCredentialsNotValid": "Client credentials were not valid",
|
||||
"forInformationRead": "For information on how to fix this, please read {url}.",
|
||||
"accessDenied": "Access denied.",
|
||||
"accessDeniedFromUrl": "Access Denied from url: {origin}. Please use the url configured in config.js",
|
||||
"pleaseSignIn": "Please Sign In",
|
||||
"attemptedToAccessAdmin": "You have attempted to access your Ghost admin panel from a url that does not appear in config.js."
|
||||
},
|
||||
"ghostbusboy": {
|
||||
"fileUploadingError": "Something went wrong uploading the file",
|
||||
"somethingWentWrong": "Something went wrong parsing the form",
|
||||
"couldNotParseUpload": "Could not parse upload completely."
|
||||
},
|
||||
"oauth": {
|
||||
"invalidClient": "Invalid client.",
|
||||
"invalidRefreshToken": "Invalid refresh token.",
|
||||
"refreshTokenExpired": "Refresh token expired."
|
||||
},
|
||||
"privateblogging": {
|
||||
"wrongPassword": "Wrong password"
|
||||
},
|
||||
"spamprevention": {
|
||||
"tooManyAttempts": "Too many attempts.",
|
||||
"noUsername": "No username.",
|
||||
"noPassword": "No password entered",
|
||||
"tooManySigninAttempts": {
|
||||
"error": "Only {rateSigninAttempts} tries per IP address every {rateSigninPeriod} seconds.",
|
||||
"context": "Too many login attempts."
|
||||
},
|
||||
"tryAgainLater": " Please try again later",
|
||||
"waitOneHour": " Please wait 1 hour.",
|
||||
"noEmail": "No email.",
|
||||
"forgottenPasswordEmail": {
|
||||
"error": "Only {rfa} forgotten password attempts per email every {rfp} seconds.",
|
||||
"context": "Forgotten password reset attempt failed"
|
||||
},
|
||||
"forgottenPasswordIp": {
|
||||
"error": "Only {rfa} tries per IP address every {rfp} seconds.",
|
||||
"context": "Forgotten password reset attempt failed"
|
||||
}
|
||||
},
|
||||
"themehandler": {
|
||||
"missingTheme": "The currently active theme \"{theme}\" is missing."
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"parsepackagejson": {
|
||||
"couldNotReadPackage": "Could not read package.json file",
|
||||
"nameOrVersionMissing": "\"name\" or \"version\" is missing from theme package.json file.",
|
||||
"willBeRequired": "This will be required in future. Please see {url}",
|
||||
"themeFileIsMalformed": "Theme package.json file is malformed"
|
||||
},
|
||||
"startupcheck": {
|
||||
"unsupportedNodeVersion": {
|
||||
"error": "ERROR: Unsupported version of Node",
|
||||
"context": "Ghost needs Node version {neededVersion} you are using version {usedVersion}\n",
|
||||
"help": "Please see {url} for more information"
|
||||
},
|
||||
"cannotFindConfigForCurrentNode": {
|
||||
"error": "ERROR: Cannot find the configuration for the current NODE_ENV: {nodeEnv}\n",
|
||||
"help": "Ensure your config.js has a section for the current NODE_ENV value and is formatted properly."
|
||||
},
|
||||
"ghostMissingDependencies": {
|
||||
"error": "ERROR: Ghost is unable to start due to missing dependencies:\n {error}",
|
||||
"explain": "\nPlease run `npm install --production` and try starting Ghost again.",
|
||||
"help": "Help and documentation can be found at {url}.\n"
|
||||
},
|
||||
"unableToAccessContentPath": {
|
||||
"error": "ERROR: Unable to access Ghost's content path:",
|
||||
"help": "Check that the content path exists and file system permissions are correct. \nHelp and documentation can be found at {url}."
|
||||
},
|
||||
"unableToOpenSqlite3Db": {
|
||||
"error": "ERROR: Unable to open sqlite3 database file for read/write",
|
||||
"help": "\nCheck that the sqlite3 database file permissions allow read and write access. \nHelp and documentation can be found at {url}."
|
||||
}
|
||||
},
|
||||
"validatethemes": {
|
||||
"themeWithNoPackage": {
|
||||
"message": "Found a theme with no package.json file",
|
||||
"context": "Theme name: {name}",
|
||||
"help": "This will be required in future. Please see {url}"
|
||||
},
|
||||
"malformedPackage": {
|
||||
"message": "Found a malformed package.json",
|
||||
"context": "Theme name: {name}",
|
||||
"help": "Valid package.json will be required in future. Please see {url}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"couldNotLocateConfigFile": {
|
||||
"error": "Could not locate a configuration file.",
|
||||
"help": "Please check your deployment for config.js or config.example.js."
|
||||
},
|
||||
"couldNotOpenForReading": {
|
||||
"error": "Could not open {file} for read.",
|
||||
"help": "Please check your deployment for config.js or config.example.js."
|
||||
},
|
||||
"couldNotOpenForWriting": {
|
||||
"error": "Could not open {file} for write.",
|
||||
"help": "Please check your deployment for config.js or config.example.js."
|
||||
},
|
||||
"invalidUrlInConfig": {
|
||||
"error": "invalid site url",
|
||||
"description": "Your site url in config.js is invalid.",
|
||||
"help": "Please make sure this is a valid url before restarting"
|
||||
},
|
||||
"urlCannotContainGhostSubdir": {
|
||||
"error": "ghost subdirectory not allowed",
|
||||
"description": "Your site url in config.js cannot contain a subdirectory called ghost.",
|
||||
"help": "Please rename the subdirectory before restarting"
|
||||
},
|
||||
"dbConfigInvalid": {
|
||||
"error": "invalid database configuration",
|
||||
"description": "Your database configuration in config.js is invalid.",
|
||||
"help": "Please make sure this is a valid Bookshelf database configuration"
|
||||
},
|
||||
"deprecatedProperty": {
|
||||
"error": "The configuration property [{property}] has been deprecated.",
|
||||
"explanation": "This will be removed in a future version, please update your config.js file.",
|
||||
"help": "Please check {url} for the most up-to-date example."
|
||||
},
|
||||
"invalidServerValues": {
|
||||
"error": "invalid server configuration",
|
||||
"description": "Your server values (socket, or host and port) in config.js are invalid.",
|
||||
"help": "Please provide them before restarting."
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"moreInfo": "\nMore info: {info}",
|
||||
"requiredOnFuture": "This will be required in future. Please see {link}"
|
||||
},
|
||||
"httpServer": {
|
||||
"addressInUse": {
|
||||
"error": "(EADDRINUSE) Cannot start Ghost.",
|
||||
"context": "Port {port} is already in use by another program.",
|
||||
"help": "Is another Ghost instance already running?"
|
||||
},
|
||||
"otherError": {
|
||||
"error": "(Code: {errorNumber})",
|
||||
"context": "There was an error starting your server.",
|
||||
"help": "Please use the error code above to search for a solution."
|
||||
}
|
||||
},
|
||||
"index": {
|
||||
"javascriptFilesNotBuilt": {
|
||||
"error": "Javascript files have not been built.",
|
||||
"help": "\nPlease read the getting started instructions at: {link}"
|
||||
}
|
||||
},
|
||||
"mail": {
|
||||
"noEmailTransportConfigured": {
|
||||
"error": "Error: No email transport configured."
|
||||
},
|
||||
"incompleteMessageData": {
|
||||
"error": "Error: Incomplete message data."
|
||||
},
|
||||
"failedSendingEmail": {
|
||||
"error": "Error: Failed to send email"
|
||||
},
|
||||
"noMailServerAtAddress": {
|
||||
"error": " - no mail server found at {domain}"
|
||||
},
|
||||
"messageNotSent": {
|
||||
"error": "Error: Message could not be sent"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"post": {
|
||||
"untitled": "(Untitled)",
|
||||
"noUserFound": "No user found",
|
||||
"notEnoughPermission": "You do not have permission to perform this action",
|
||||
"tagUpdates": {
|
||||
"error": "Unable to save tags.",
|
||||
"help": "Your post was saved, but your tags were not updated."
|
||||
}
|
||||
},
|
||||
"role": {
|
||||
"notEnoughPermission": "You do not have permission to perform this action"
|
||||
},
|
||||
"settings": {
|
||||
"valueCannotBeBlank": "Value in [settings.key] cannot be blank.",
|
||||
"unableToFindSetting": "Unable to find setting to update: {key}",
|
||||
"unableToFindDefaultSetting": "Unable to find default setting: {key}"
|
||||
},
|
||||
"user": {
|
||||
"missingContext": "missing context",
|
||||
"onlyOneRolePerUserSupported": "Only one role per user is supported at the moment.",
|
||||
"methodDoesNotSupportOwnerRole": "This method does not support assigning the owner role",
|
||||
"passwordDoesNotComplyLength": "Your password must be at least 8 characters long.",
|
||||
"notEnoughPermission": "You do not have permission to perform this action",
|
||||
"noUserWithEnteredEmailAddr": "There is no user with that email address.",
|
||||
"userIsInactive": "The user with that email address is inactive.",
|
||||
"incorrectPasswordAttempts": "Your password is incorrect. <br /> {remaining} attempt{s} remaining!",
|
||||
"userUpdateError": {
|
||||
"context": "Error thrown from user update during login",
|
||||
"help": "Visit and save your profile after logging in to check for problems."
|
||||
},
|
||||
"incorrectPassword": "Your password is incorrect.",
|
||||
"accountLocked": "Your account is locked. Please reset your password to log in again by clicking the \"Forgotten password?\" link!",
|
||||
"newPasswordsDoNotMatch": "Your new passwords do not match",
|
||||
"passwordRequiredForOperation": "Password is required for this operation",
|
||||
"invalidTokenStructure": "Invalid token structure",
|
||||
"invalidTokenExpiration": "Invalid token expiration",
|
||||
"expiredToken": "Expired token",
|
||||
"tokenLocked": "Token locked",
|
||||
"invalidToken": "Invalid token",
|
||||
"userNotFound": "User not found",
|
||||
"onlyOwnerCanTransferOwnerRole": "Only owners are able to transfer the owner role.",
|
||||
"onlyAdmCanBeAssignedOwnerRole": "Only administrators can be assigned the owner role."
|
||||
},
|
||||
"base": {
|
||||
"index": {
|
||||
"missingContext": "missing context"
|
||||
},
|
||||
"token": {
|
||||
"noUserFound": "No user found",
|
||||
"tokenNotFound": "Token not found"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"filter": {
|
||||
"errorParsing": "Error parsing filter",
|
||||
"forInformationRead": "For more information on how to use filter, see {url}"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"noActionsMapFound": {
|
||||
"error": "No actions map found, please call permissions.init() before use."
|
||||
},
|
||||
"applyStatusRules": {
|
||||
"error": "You do not have permission to retrieve {docName} with that status"
|
||||
},
|
||||
"noPermissionToAction": "You do not have permission to perform this action"
|
||||
},
|
||||
"update-check": {
|
||||
"checkingForUpdatesFailed": {
|
||||
"error": "Checking for updates failed, your blog will continue to function.",
|
||||
"help": "If you get this error repeatedly, please seek help from {url}."
|
||||
},
|
||||
"unableToDecodeUpdateResponse": {
|
||||
"error": "Unable to decode update response"
|
||||
}
|
||||
},
|
||||
"api": {
|
||||
"authentication": {
|
||||
"setupMustBeCompleted": "Setup must be completed before making this request.",
|
||||
"noEmailProvided": "No email provided.",
|
||||
"invalidEmailReceived": "The server did not receive a valid email",
|
||||
"setupAlreadyCompleted": "Setup has already been completed.",
|
||||
"unableToSendWelcomeEmail": "Unable to send welcome email, your blog will continue to function.",
|
||||
"checkEmailConfigInstructions": "Please see {url} for instructions on configuring email.",
|
||||
"notLoggedIn": "You are not logged in.",
|
||||
"notTheBlogOwner": "You are not the blog owner.",
|
||||
"invalidTokenTypeHint": "Invalid token_type_hint given.",
|
||||
"invalidTokenProvided": "Invalid token provided"
|
||||
},
|
||||
"clients": {
|
||||
"clientNotFound": "Client not found."
|
||||
},
|
||||
"configuration": {
|
||||
"invalidKey": "Invalid key"
|
||||
},
|
||||
"db": {
|
||||
"noPermissionToExportData": "You do not have permission to export data (no rights).",
|
||||
"noPermissionToImportData": "You do not have permission to import data (no rights).",
|
||||
"selectFileToImport": "Please select a file to import.",
|
||||
"unsupportedFile": "Unsupported file. Please try any of the following formats: "
|
||||
},
|
||||
"mail": {
|
||||
"noPermissionToSendEmail": "You do not have permission to send mail.",
|
||||
"cannotFindCurrentUser": "Could not find the current user"
|
||||
},
|
||||
"notifications": {
|
||||
"noPermissionToBrowseNotif": "You do not have permission to browse notifications.",
|
||||
"noPermissionToAddNotif": "You do not have permission to add notifications.",
|
||||
"noPermissionToDestroyNotif": "You do not have permission to destroy notifications.",
|
||||
"noPermissionToDismissNotif": "You do not have permission to dismiss this notification.",
|
||||
"notificationDoesNotExist": "Notification does not exist."
|
||||
},
|
||||
"posts": {
|
||||
"postNotFound": "Post not found."
|
||||
},
|
||||
"settings": {
|
||||
"problemFindingSetting": "Problem finding setting: {key}",
|
||||
"accessCoreSettingFromExtReq": "Attempted to access core setting from external request",
|
||||
"invalidJsonInLabs": "Error: Invalid JSON in settings.labs",
|
||||
"labsColumnCouldNotBeParsed": "The column with key \"labs\" could not be parsed as JSON",
|
||||
"tryUpdatingLabs": "Please try updating a setting on the labs page, or manually editing your DB",
|
||||
"noPermissionToEditSettings": "You do not have permission to edit settings.",
|
||||
"noPermissionToReadSettings": "You do not have permission to read settings."
|
||||
},
|
||||
"slugs": {
|
||||
"couldNotGenerateSlug": "Could not generate slug.",
|
||||
"unknownSlugType": "Unknown slug type '{type}'."
|
||||
},
|
||||
"tags": {
|
||||
"tagNotFound": "Tag not found."
|
||||
},
|
||||
"themes": {
|
||||
"noPermissionToBrowseThemes": "You do not have permission to browse themes.",
|
||||
"noPermissionToEditThemes": "You do not have permission to edit themes.",
|
||||
"themeDoesNotExist": "Theme does not exist.",
|
||||
"invalidRequest": "Invalid request."
|
||||
},
|
||||
"upload": {
|
||||
"pleaseSelectImage": "Please select an image.",
|
||||
"pleaseSelectValidImage": "Please select a valid image."
|
||||
},
|
||||
"users": {
|
||||
"userNotFound": "User not found.",
|
||||
"cannotChangeOwnRole": "You cannot change your own role.",
|
||||
"cannotChangeOwnersRole": "Cannot change Owner's role",
|
||||
"noPermissionToEditUser": "You do not have permission to edit this user",
|
||||
"notAllowedToCreateOwner": "Not allowed to create an owner user.",
|
||||
"noPermissionToAddUser": "You do not have permission to add this user",
|
||||
"noEmailProvided": "No email provided.",
|
||||
"userAlreadyRegistered": "User is already registered.",
|
||||
"errorSendingEmail": {
|
||||
"error": "Error sending email: {message}",
|
||||
"help": "Please check your email settings and resend the invitation."
|
||||
},
|
||||
"noPermissionToDestroyUser": "You do not have permission to destroy this user.",
|
||||
"noPermissionToChangeUsersPwd": "You do not have permission to change the password for this user"
|
||||
},
|
||||
"utils": {
|
||||
"noPermissionToCall": "You do not have permission to {method} {docName}",
|
||||
"noRootKeyProvided": "No root key ('{docName}') provided.",
|
||||
"invalidIdProvided": "Invalid id provided."
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"export": {
|
||||
"errorExportingData": "Error exporting data"
|
||||
},
|
||||
"fixtures": {
|
||||
"populatingPermissions": "Populating permissions",
|
||||
"upgradingPermissions": "Upgrading permissions",
|
||||
"removingOldPermissions": "Removing old permissions"
|
||||
},
|
||||
"import": {
|
||||
"dataImporter": {
|
||||
"unableToFindOwner": "Unable to find an owner"
|
||||
},
|
||||
"index": {
|
||||
"duplicateEntryFound": "Duplicate entry found. Multiple values of '{value}' found for {offendingProperty}."
|
||||
},
|
||||
"utils": {
|
||||
"dataLinkedToUnknownUser": "Attempting to import data linked to unknown user id {userToMap}"
|
||||
}
|
||||
},
|
||||
"importer": {
|
||||
"index": {
|
||||
"couldNotCleanUpFile": {
|
||||
"error": "Import could not clean up file ",
|
||||
"context": "Your blog will continue to work as expected"
|
||||
},
|
||||
"unsupportedRoonExport": "Your zip file looks like an old format Roon export, please re-export your Roon blog and try again.",
|
||||
"noContentToImport": "Zip did not include any content to import.",
|
||||
"invalidZipStructure": "Invalid zip file structure.",
|
||||
"invalidZipFileBaseDirectory": "Invalid zip file: base directory read failed",
|
||||
"zipContainsMultipleDataFormats": "Zip file contains multiple data formats. Please split up and import separately."
|
||||
},
|
||||
"handlers": {
|
||||
"json": {
|
||||
"invalidJsonFormat": "Invalid JSON format, expected `{ db: [exportedData] }`",
|
||||
"apiDbImportContent": "API DB import content",
|
||||
"checkImportJsonIsValid": "check that the import file is valid JSON.",
|
||||
"failedToParseImportJson": "Failed to parse the import JSON file."
|
||||
}
|
||||
}
|
||||
},
|
||||
"versioning": {
|
||||
"index": {
|
||||
"dbVersionNotRecognized": "Database version is not recognized",
|
||||
"settingsTableDoesNotExist": "Settings table does not exist"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"xmlrpc": {
|
||||
"pingUpdateFailed": {
|
||||
"error": "Pinging services for updates on your blog failed, your blog will continue to function.",
|
||||
"help": "If you get this error repeatedly, please seek help on {url}."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"noMessageSupplied": "no message supplied",
|
||||
"error": "\nERROR:",
|
||||
"warning": "\nWarning:",
|
||||
"anErrorOccurred": "An error occurred",
|
||||
"unknownErrorOccurred": "An unknown error occurred.",
|
||||
"unknownError": "Unknown Error",
|
||||
"unknownApiError": "Unknown API Error",
|
||||
"databaseIsReadOnly": "Your database is in read only mode. Visitors can read your blog, but you can't log in or add posts.",
|
||||
"checkDatabase": "Check your database file and make sure that file owner and permissions are correct.",
|
||||
"notEnoughPermission": "You do not have permission to perform this action",
|
||||
"errorWhilstRenderingError": "Error whilst rendering error page",
|
||||
"errorTemplateHasError": "Error template has an error",
|
||||
"oopsErrorTemplateHasError": "Oops, seems there is an error in the error template.",
|
||||
"encounteredError": "Encountered the error: ",
|
||||
"whilstTryingToRender": "whilst trying to render an error page for the error: ",
|
||||
"renderingErrorPage": "Rendering Error Page",
|
||||
"caughtProcessingError": "Ghost caught a processing error in the middleware layer.",
|
||||
"pageNotFound": "Page not found"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
|
||||
"index": {
|
||||
"usingDirectMethodToSendEmail": "Ghost is attempting to use a direct method to send email. \nIt is recommended that you explicitly configure an email service.",
|
||||
"unableToSendEmail": "Ghost is currently unable to send email."
|
||||
},
|
||||
"helpers": {
|
||||
"foreach": {
|
||||
"iteratorNeeded": "Need to pass an iterator to #foreach"
|
||||
},
|
||||
"get": {
|
||||
"mustBeCalledAsBlock": "Get helper must be called as a block",
|
||||
"invalidResource": "Invalid resource given to get helper",
|
||||
"helperNotAvailable": "The \\{\\{get\\}\\} helper is not available.",
|
||||
"apiMustBeEnabled": "Public API access must be enabled if you wish to use the \\{\\{get\\}\\} helper.",
|
||||
"seeLink": "See {url}"
|
||||
},
|
||||
"has": {
|
||||
"invalidAttribute": "Invalid or no attribute given to has helper"
|
||||
},
|
||||
"index": {
|
||||
"missingHelper": "Missing helper: '{arg}'"
|
||||
},
|
||||
"is": {
|
||||
"invalidAttribute": "Invalid or no attribute given to is helper"
|
||||
},
|
||||
"navigation": {
|
||||
"invalidData": "navigation data is not an object or is a function",
|
||||
"valuesMustBeDefined": "All values must be defined for label, url and current",
|
||||
"valuesMustBeString": "Invalid value, Url and Label must be strings"
|
||||
},
|
||||
"page_url": {
|
||||
"isDeprecated": "Warning: pageUrl is deprecated, please use page_url instead\nThe helper pageUrl has been replaced with page_url in Ghost 0.4.2, and will be removed entirely in Ghost 0.6\nIn your theme's pagination.hbs file, pageUrl should be renamed to page_url"
|
||||
},
|
||||
"pagination": {
|
||||
"invalidData": "pagination data is not an object or is a function",
|
||||
"valuesMustBeDefined": "All values must be defined for page, pages, limit and total",
|
||||
"nextPrevValuesMustBeNumeric": "Invalid value, Next/Prev must be a number",
|
||||
"valuesMustBeNumeric": "Invalid value, check page, pages, limit and total are numbers"
|
||||
},
|
||||
"plural": {
|
||||
"valuesMustBeDefined": "All values must be defined for empty, singular and plural"
|
||||
},
|
||||
"template": {
|
||||
"templateNotFound": "Template {name} not found."
|
||||
}
|
||||
}
|
||||
},
|
||||
"notices": {
|
||||
|
||||
"controllers": {
|
||||
"newVersionAvailable": "Ghost {version} is available! Hot Damn. {link} to upgrade."
|
||||
},
|
||||
"index": {
|
||||
"welcomeToGhost": "Welcome to Ghost.",
|
||||
"youAreRunningUnderEnvironment": "You're running under the <strong> {environment} </strong> environment.",
|
||||
"yourURLisSetTo": "Your URL is set to <strong> {url} </strong>."
|
||||
},
|
||||
"httpServer": {
|
||||
"cantTouchThis": "Can't touch this",
|
||||
"ghostIsRunning": "Ghost is running...",
|
||||
"yourBlogIsAvailableOn": "\nYour blog is now available on {url}",
|
||||
"ctrlCToShutDown": "\nCtrl+C to shut down",
|
||||
"ghostIsRunningIn": "Ghost is running in {env}...",
|
||||
"listeningOn": "\nListening on",
|
||||
"urlConfiguredAs": "\nUrl configured as: {url}",
|
||||
"ghostHasShutdown": "\nGhost has shut down",
|
||||
"yourBlogIsNowOffline": "\nYour blog is now offline",
|
||||
"ghostWasRunningFor": "\nGhost was running for",
|
||||
"ghostIsClosingConnections": "Ghost is closing connections"
|
||||
},
|
||||
"mail": {
|
||||
"messageSent": "Message sent. Double check inbox and spam folder!"
|
||||
},
|
||||
"api": {
|
||||
"users": {
|
||||
"pwdChangedSuccessfully": "Password changed successfully."
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"fixtures": {
|
||||
"migrations": "Migrations",
|
||||
"convertingAdmToOwner": "Converting admin to owner",
|
||||
"creatingOwner": "Creating owner",
|
||||
"populatingFixtures": "Populating fixtures",
|
||||
"upgradingFixturesTo": "Upgrading fixtures to {version}",
|
||||
"addingClientFixture": "Adding ghost-admin client fixture",
|
||||
"addingOwnerRoleFixture": "Adding owner role fixture",
|
||||
"updatingFixtures": "Updating fixtures",
|
||||
"canSafelyDelete": "<!-- You can safely delete this line if your theme does not require jQuery -->\n",
|
||||
"jQueryRemoved": "jQuery has been removed from Ghost core and is now being loaded from the jQuery Foundation's CDN.",
|
||||
"canBeChanged": "This can be changed or removed in your <strong>Code Injection</strong> settings area.",
|
||||
"addingJquery": "Adding jQuery link to ghost_foot",
|
||||
"updateIsPrivate": "Update isPrivate setting",
|
||||
"updatePassword": "Update password setting",
|
||||
"updateAdminClientFixture": "Update ghost-admin client fixture",
|
||||
"addFrontendClientFixture": "Add ghost-frontend client fixture",
|
||||
"cleaningTags": "Cleaning {length} malformed tags",
|
||||
"collectingDataOnTagOrder": "Collecting data on tag order for posts...",
|
||||
"updatingOrder": "Updating order on {length} tag relationships (could take a while)...",
|
||||
"updatedOrder": "Tag order successfully updated",
|
||||
"addingUpgrade": "Adding {version} upgrade post fixture"
|
||||
},
|
||||
"migration": {
|
||||
"commands": {
|
||||
"migrations": "Migrations",
|
||||
"deletingTable": "Deleting table: {table}",
|
||||
"creatingTable": "Creating table: {table}",
|
||||
"addingColumn": "Adding column: {table}.{column}",
|
||||
"addingUnique": "Adding unique on: {table}.{column}",
|
||||
"droppingUnique": "Dropping unique on: {table}.{column}"
|
||||
},
|
||||
"index": {
|
||||
"migrations": "Migrations",
|
||||
"complete": "Complete",
|
||||
"populatingDefaultSettings": "Populating default settings",
|
||||
"creatingDatabaseBackup": "Creating database backup",
|
||||
"databaseBackupDestination": "Database backup written to: {filename}",
|
||||
"databaseUpgradeRequired": "Database upgrade required from version {dbVersion} to {defaultVersion}",
|
||||
"upToDateAtVersion": "Up to date at version {dbVersion}",
|
||||
"databaseNotCompatible": {
|
||||
"error": "Your database is not compatible with this version of Ghost",
|
||||
"help": "You will need to create a new database"
|
||||
},
|
||||
"dbInitialisationRequired": "Database initialisation required for version {version}",
|
||||
"problemWithDatabase": "There is a problem with the database",
|
||||
"creatingTable": "Creating table: {table}",
|
||||
"creatingTables": "Creating tables...",
|
||||
"runningMigrations": "Running migrations"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"index": {
|
||||
"noSupportForDatabase": "No support for database client {client}"
|
||||
}
|
||||
},
|
||||
"validation": {
|
||||
"index": {
|
||||
"valueCannotBeBlank": "Value in [{tableName}.{columnKey}] cannot be blank.",
|
||||
"valueExceedsMaxLength": "Value in [{tableName}.{columnKey}] exceeds maximum length of {maxlength} characters.",
|
||||
"valueIsNotInteger": "Value in [{tableName}.{columnKey}] is not an integer.",
|
||||
"themeCannotBeActivated": "{themeName} cannot be activated because it is not currently installed.",
|
||||
"validationFailed": "Validation ({validationName}) failed for {key}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ var crypto = require('crypto'),
|
||||
api = require('./api'),
|
||||
config = require('./config'),
|
||||
errors = require('./errors'),
|
||||
|
||||
i18n = require('./i18n'),
|
||||
internal = {context: {internal: true}},
|
||||
allowedCheckEnvironments = ['development', 'production'],
|
||||
checkEndpoint = 'updates.ghost.org',
|
||||
@ -46,8 +46,8 @@ function updateCheckError(error) {
|
||||
|
||||
errors.logError(
|
||||
error,
|
||||
'Checking for updates failed, your blog will continue to function.',
|
||||
'If you get this error repeatedly, please seek help on http://support.ghost.org.'
|
||||
i18n.t('errors.update-check.checkingForUpdatesFailed.error'),
|
||||
i18n.t('errors.update-check.checkingForUpdatesFailed.help', {url: 'http://support.ghost.org'})
|
||||
);
|
||||
}
|
||||
|
||||
@ -126,7 +126,7 @@ function updateCheckRequest() {
|
||||
resData = JSON.parse(resData);
|
||||
resolve(resData);
|
||||
} catch (e) {
|
||||
reject('Unable to decode update response');
|
||||
reject(i18n.t('errors.update-check.unableToDecodeUpdateResponse.error'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
var Promise = require('bluebird'),
|
||||
fs = require('fs'),
|
||||
i18n = require('../i18n'),
|
||||
|
||||
readFile = Promise.promisify(fs.readFile);
|
||||
|
||||
@ -15,7 +16,7 @@ var Promise = require('bluebird'),
|
||||
function parsePackageJson(path) {
|
||||
return readFile(path)
|
||||
.catch(function () {
|
||||
var err = new Error('Could not read package.json file');
|
||||
var err = new Error(i18n.t('errors.utils.parsepackagejson.couldNotReadPackage'));
|
||||
err.context = path;
|
||||
|
||||
return Promise.reject(err);
|
||||
@ -29,18 +30,18 @@ function parsePackageJson(path) {
|
||||
hasRequiredKeys = json.name && json.version;
|
||||
|
||||
if (!hasRequiredKeys) {
|
||||
err = new Error('"name" or "version" is missing from theme package.json file.');
|
||||
err = new Error(i18n.t('errors.utils.parsepackagejson.nameOrVersionMissing'));
|
||||
err.context = path;
|
||||
err.help = 'This will be required in future. Please see http://docs.ghost.org/themes/';
|
||||
err.help = i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'http://docs.ghost.org/themes/'});
|
||||
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
return json;
|
||||
} catch (parseError) {
|
||||
err = new Error('Theme package.json file is malformed');
|
||||
err = new Error(i18n.t('errors.utils.parsepackagejson.themeFileIsMalformed'));
|
||||
err.context = path;
|
||||
err.help = 'This will be required in future. Please see http://docs.ghost.org/themes/';
|
||||
err.help = i18n.t('errors.utils.parsepackagejson.willBeRequired', {url: 'http://docs.ghost.org/themes/'});
|
||||
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ var packages = require('../../../package.json'),
|
||||
path = require('path'),
|
||||
crypto = require('crypto'),
|
||||
fs = require('fs'),
|
||||
i18n = require('../i18n'),
|
||||
mode = process.env.NODE_ENV === undefined ? 'development' : process.env.NODE_ENV,
|
||||
appRoot = path.resolve(__dirname, '../../../'),
|
||||
configFilePath = process.env.GHOST_CONFIG || path.join(appRoot, 'config.js'),
|
||||
@ -28,14 +29,16 @@ checks = {
|
||||
nodeVersion: function checkNodeVersion() {
|
||||
// Tell users if their node version is not supported, and exit
|
||||
var semver = require('semver');
|
||||
i18n.init();
|
||||
|
||||
if (process.env.GHOST_NODE_VERSION_CHECK !== 'false' &&
|
||||
!semver.satisfies(process.versions.node, packages.engines.node) &&
|
||||
!semver.satisfies(process.versions.node, packages.engines.iojs)) {
|
||||
console.error('\x1B[31mERROR: Unsupported version of Node');
|
||||
console.error('\x1B[31mGhost needs Node version ' + packages.engines.node +
|
||||
' you are using version ' + process.versions.node + '\033[0m\n');
|
||||
console.error('\x1B[32mPlease see http://support.ghost.org/supported-node-versions/ for more information\033[0m');
|
||||
console.error(i18n.t('errors.utils.startupcheck.unsupportedNodeVersion.error'));
|
||||
console.error(i18n.t('errors.utils.startupcheck.unsupportedNodeVersion.context',
|
||||
{neededVersion: packages.engines.node, usedVersion: process.versions.node}));
|
||||
console.error(i18n.t('errors.utils.startupcheck.unsupportedNodeVersion.help',
|
||||
{url: 'http://support.ghost.org/supported-node-versions/'}));
|
||||
|
||||
process.exit(exitCodes.NODE_VERSION_UNSUPPORTED);
|
||||
}
|
||||
@ -58,10 +61,9 @@ checks = {
|
||||
config = configFile[mode];
|
||||
|
||||
if (!config) {
|
||||
console.error('\x1B[31mERROR: Cannot find the configuration for the current NODE_ENV: ' +
|
||||
process.env.NODE_ENV + '\033[0m\n');
|
||||
console.error('\x1B[32mEnsure your config.js has a section for the current NODE_ENV value' +
|
||||
' and is formatted properly.\033[0m');
|
||||
console.error(i18n.t('errors.utils.startupcheck.cannotFindConfigForCurrentNode.error',
|
||||
{nodeEnv: process.env.NODE_ENV}));
|
||||
console.error(i18n.t('errors.utils.startupcheck.cannotFindConfigForCurrentNode.help'));
|
||||
|
||||
process.exit(exitCodes.NODE_ENV_CONFIG_MISSING);
|
||||
}
|
||||
@ -89,9 +91,9 @@ checks = {
|
||||
|
||||
errors = errors.join('\n ');
|
||||
|
||||
console.error('\x1B[31mERROR: Ghost is unable to start due to missing dependencies:\033[0m\n ' + errors);
|
||||
console.error('\x1B[32m\nPlease run `npm install --production` and try starting Ghost again.');
|
||||
console.error('\x1B[32mHelp and documentation can be found at http://support.ghost.org.\033[0m\n');
|
||||
console.error(i18n.t('errors.utils.startupcheck.ghostMissingDependencies.error', {error: errors}));
|
||||
console.error(i18n.t('errors.utils.startupcheck.ghostMissingDependencies.explain'));
|
||||
console.error(i18n.t('errors.utils.startupcheck.ghostMissingDependencies.help', {url: 'http://support.ghost.org'}));
|
||||
|
||||
process.exit(exitCodes.DEPENDENCIES_MISSING);
|
||||
},
|
||||
@ -107,9 +109,8 @@ checks = {
|
||||
contentPath,
|
||||
contentSubPaths = ['apps', 'data', 'images', 'themes'],
|
||||
fd,
|
||||
errorHeader = '\x1B[31mERROR: Unable to access Ghost\'s content path:\033[0m',
|
||||
errorHelp = '\x1B[32mCheck that the content path exists and file system permissions are correct.' +
|
||||
'\nHelp and documentation can be found at http://support.ghost.org.\033[0m';
|
||||
errorHeader = i18n.t('errors.utils.startupcheck.unableToAccessContentPath.error'),
|
||||
errorHelp = i18n.t('errors.utils.startupcheck.unableToAccessContentPath.help', {url: 'http://support.ghost.org'});
|
||||
|
||||
// Get the content path to test. If it's defined in config.js use that, if not use the default
|
||||
try {
|
||||
@ -202,10 +203,9 @@ checks = {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('\x1B[31mERROR: Unable to open sqlite3 database file for read/write\033[0m');
|
||||
console.error(i18n.t('errors.utils.startupcheck.unableToOpenSqlite3Db.error'));
|
||||
console.error(' ' + e.message);
|
||||
console.error('\n\x1B[32mCheck that the sqlite3 database file permissions allow read and write access.');
|
||||
console.error('Help and documentation can be found at http://support.ghost.org.\033[0m');
|
||||
console.error(i18n.t('errors.utils.startupcheck.unableToOpenSqlite3Db.help', {url: 'http://support.ghost.org'}));
|
||||
|
||||
process.exit(exitCodes.SQLITE_DB_NOT_WRITABLE);
|
||||
}
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
var readThemes = require('./read-themes'),
|
||||
Promise = require('bluebird'),
|
||||
_ = require('lodash');
|
||||
_ = require('lodash'),
|
||||
i18n = require('../i18n');
|
||||
|
||||
/**
|
||||
* Validate themes:
|
||||
@ -27,9 +28,9 @@ function validateThemes(dir) {
|
||||
|
||||
if (!hasPackageJson) {
|
||||
warning = {
|
||||
message: 'Found a theme with no package.json file',
|
||||
context: 'Theme name: ' + name,
|
||||
help: 'This will be required in future. Please see http://docs.ghost.org/themes/'
|
||||
message: i18n.t('errors.utils.validatethemes.themeWithNoPackage.message'),
|
||||
context: i18n.t('errors.utils.validatethemes.themeWithNoPackage.context', {name: name}),
|
||||
help: i18n.t('errors.utils.validatethemes.themeWithNoPackage.help', {url: 'http://docs.ghost.org/themes/'})
|
||||
};
|
||||
|
||||
result.warnings.push(warning);
|
||||
@ -39,9 +40,9 @@ function validateThemes(dir) {
|
||||
// but JSON.parse failed (invalid json syntax)
|
||||
if (hasPackageJson && theme['package.json'] === null) {
|
||||
warning = {
|
||||
message: 'Found a malformed package.json',
|
||||
context: 'Theme name: ' + name,
|
||||
help: 'Valid package.json will be required in future. Please see http://docs.ghost.org/themes/'
|
||||
message: i18n.t('errors.utils.validatethemes.malformedPackage.message'),
|
||||
context: i18n.t('errors.utils.validatethemes.malformedPackage.context', {name: name}),
|
||||
help: i18n.t('errors.utils.validatethemes.malformedPackage.help', {url: 'http://docs.ghost.org/themes/'})
|
||||
};
|
||||
|
||||
result.warnings.push(warning);
|
||||
|
@ -4,7 +4,9 @@
|
||||
// This tests using Ghost as an npm module
|
||||
var should = require('should'),
|
||||
|
||||
ghost = require('../../../../core');
|
||||
ghost = require('../../../../core'),
|
||||
i18n = require('../../../../core/server/i18n');
|
||||
i18n.init();
|
||||
|
||||
describe('Module', function () {
|
||||
describe('Setup', function () {
|
||||
|
@ -9,7 +9,9 @@ var request = require('supertest'),
|
||||
should = require('should'),
|
||||
|
||||
testUtils = require('../../utils'),
|
||||
ghost = require('../../../../core');
|
||||
ghost = require('../../../../core'),
|
||||
i18n = require('../../../../core/server/i18n');
|
||||
i18n.init();
|
||||
|
||||
describe('Admin Routing', function () {
|
||||
function doEnd(done) {
|
||||
|
@ -4,6 +4,7 @@ var testUtils = require('../../utils'),
|
||||
should = require('should'),
|
||||
config = require('../../../server/config'),
|
||||
mailer = require('../../../server/mail'),
|
||||
i18n = require('../../../../core/server/i18n'),
|
||||
|
||||
// Stuff we are testing
|
||||
MailAPI = require('../../../server/api/mail'),
|
||||
@ -36,6 +37,7 @@ var testUtils = require('../../utils'),
|
||||
options: {}
|
||||
}]
|
||||
};
|
||||
i18n.init();
|
||||
|
||||
describe('Mail API', function () {
|
||||
before(testUtils.teardown);
|
||||
|
@ -8,12 +8,14 @@ var path = require('path'),
|
||||
Promise = require('bluebird'),
|
||||
helpers = require('../../server/helpers'),
|
||||
filters = require('../../server/filters'),
|
||||
i18n = require('../../server/i18n'),
|
||||
|
||||
// Stuff we are testing
|
||||
AppProxy = require('../../server/apps/proxy'),
|
||||
AppSandbox = require('../../server/apps/sandbox'),
|
||||
AppDependencies = require('../../server/apps/dependencies'),
|
||||
AppPermissions = require('../../server/apps/permissions');
|
||||
i18n.init();
|
||||
|
||||
describe('Apps', function () {
|
||||
var sandbox,
|
||||
|
@ -8,13 +8,14 @@ var should = require('should'),
|
||||
_ = require('lodash'),
|
||||
|
||||
testUtils = require('../utils'),
|
||||
i18n = require('../../server/i18n'),
|
||||
|
||||
// Thing we are testing
|
||||
configUtils = require('../utils/configUtils'),
|
||||
config = configUtils.config,
|
||||
// storing current environment
|
||||
currentEnv = process.env.NODE_ENV;
|
||||
|
||||
i18n.init();
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
|
@ -6,8 +6,10 @@ var should = require('should'),
|
||||
// Stuff we are testing
|
||||
mailer = require('../../server/mail'),
|
||||
configUtils = require('../utils/configUtils'),
|
||||
i18n = require('../../server/i18n'),
|
||||
|
||||
SMTP;
|
||||
i18n.init();
|
||||
|
||||
// Mock SMTP config
|
||||
SMTP = {
|
||||
|
@ -18,7 +18,7 @@
|
||||
"license": "MIT",
|
||||
"main": "./core/index",
|
||||
"scripts": {
|
||||
"preinstall": "npm install semver && node -e \"require('./core/server/utils/startup-check.js').nodeVersion()\"",
|
||||
"preinstall": "npm install semver lodash chalk intl-messageformat intl && node -e \"require('./core/server/utils/startup-check.js').nodeVersion()\"",
|
||||
"start": "node index",
|
||||
"test": "grunt validate --verbose"
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user