Harvest server side strings

closes #5617
- Replace all hard-coded server-side strings with i18n translations
This commit is contained in:
rfpe 2015-11-12 13:29:45 +01:00 committed by Kevin P. Kucharczyk
parent 52b76f278a
commit 7abcc43907
75 changed files with 1090 additions and 391 deletions

View File

@ -7,6 +7,7 @@ var _ = require('lodash'),
Promise = require('bluebird'), Promise = require('bluebird'),
errors = require('../errors'), errors = require('../errors'),
config = require('../config'), config = require('../config'),
i18n = require('../i18n'),
authentication; authentication;
function setupTasks(object) { function setupTasks(object) {
@ -38,7 +39,7 @@ function setupTasks(object) {
// Handles the additional values set by the setup screen. // Handles the additional values set by the setup screen.
if (!_.isEmpty(setupUser.blogTitle)) { if (!_.isEmpty(setupUser.blogTitle)) {
userSettings.push({key: 'title', value: 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); setupUser = user.toJSON(internal);
@ -69,7 +70,7 @@ authentication = {
var setup = result.setup[0].status; var setup = result.setup[0].status;
if (!setup) { 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'); return utils.checkObject(object, 'passwordreset');
@ -77,7 +78,7 @@ authentication = {
if (checkedPasswordReset.passwordreset[0].email) { if (checkedPasswordReset.passwordreset[0].email) {
email = checkedPasswordReset.passwordreset[0].email; email = checkedPasswordReset.passwordreset[0].email;
} else { } 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'}) return settings.read({context: {internal: true}, key: 'dbHash'})
@ -94,7 +95,7 @@ authentication = {
mail: [{ mail: [{
message: { message: {
to: email, to: email,
subject: 'Reset Password', subject: i18n.t('common.api.authentication.mail.resetPassword'),
html: emailContent.html, html: emailContent.html,
text: emailContent.text text: emailContent.text
}, },
@ -103,7 +104,7 @@ authentication = {
}; };
return mail.send(payload, {context: {internal: true}}); return mail.send(payload, {context: {internal: true}});
}).then(function () { }).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) { }).catch(function (error) {
return Promise.reject(error); return Promise.reject(error);
}); });
@ -125,7 +126,7 @@ authentication = {
var setup = result.setup[0].status; var setup = result.setup[0].status;
if (!setup) { 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'); return utils.checkObject(object, 'passwordreset');
@ -143,7 +144,7 @@ authentication = {
dbHash: dbHash dbHash: dbHash
}); });
}).then(function () { }).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) { }).catch(function (error) {
return Promise.reject(new errors.UnauthorizedError(error.message)); return Promise.reject(new errors.UnauthorizedError(error.message));
}); });
@ -166,7 +167,7 @@ authentication = {
var setup = result.setup[0].status; var setup = result.setup[0].status;
if (!setup) { 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'); 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 // 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}); return dataProvider.User.edit({name: name, email: email, slug: ''}, {id: user.id});
}).then(function () { }).then(function () {
return Promise.resolve({invitation: [{message: 'Invitation accepted.'}]}); return Promise.resolve({invitation: [{message: i18n.t('common.api.authentication.mail.invitationAccepted')}]});
}).catch(function (error) { }).catch(function (error) {
return Promise.reject(new errors.UnauthorizedError(error.message)); return Promise.reject(new errors.UnauthorizedError(error.message));
}); });
@ -207,7 +208,7 @@ authentication = {
var setup = result.setup[0].status; var setup = result.setup[0].status;
if (!setup) { 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) { if (options.email) {
@ -219,7 +220,7 @@ authentication = {
} }
}); });
} else { } 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; var setup = result.setup[0].status;
if (setup) { 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); return setupTasks(object);
@ -258,7 +259,7 @@ authentication = {
}).then(function (emailContent) { }).then(function (emailContent) {
var message = { var message = {
to: setupUser.email, to: setupUser.email,
subject: 'Your New Ghost Blog', subject: i18n.t('common.api.authentication.mail.yourNewGhostBlog'),
html: emailContent.html, html: emailContent.html,
text: emailContent.text text: emailContent.text
}, },
@ -272,8 +273,8 @@ authentication = {
mail.send(payload, {context: {internal: true}}).catch(function (error) { mail.send(payload, {context: {internal: true}}).catch(function (error) {
errors.logError( errors.logError(
error.message, error.message,
'Unable to send welcome email, your blog will continue to function.', i18n.t('errors.api.authentication.unableToSendWelcomeEmail', {url: 'http://support.ghost.org/mail/'}),
'Please see http://support.ghost.org/mail/ for instructions on configuring email.' i18n.t('errors.api.authentication.checkEmailConfigInstructions')
); );
}); });
}).then(function () { }).then(function () {
@ -283,14 +284,14 @@ authentication = {
updateSetup: function updateSetup(object, options) { updateSetup: function updateSetup(object, options) {
if (!options.context || !options.context.user) { 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) { return dataProvider.User.findOne({role: 'Owner', status: 'all'}).then(function (result) {
var user = result.toJSON(); var user = result.toJSON();
if (user.id !== options.context.user) { 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); return setupTasks(object);
@ -307,14 +308,14 @@ authentication = {
} else if (object.token_type_hint && object.token_type_hint === 'refresh_token') { } else if (object.token_type_hint && object.token_type_hint === 'refresh_token') {
token = dataProvider.Refreshtoken; token = dataProvider.Refreshtoken;
} else { } 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 token.destroyByToken({token: object.token}).then(function () {
return Promise.resolve({token: object.token}); return Promise.resolve({token: object.token});
}, function () { }, function () {
// On error we still want a 200. See https://tools.ietf.org/html/rfc7009#page-5 // 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')});
}); });
} }
}; };

View File

@ -6,6 +6,7 @@ var Promise = require('bluebird'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
i18n = require('../i18n'),
docName = 'clients', docName = 'clients',
clients; clients;
@ -52,7 +53,7 @@ clients = {
return {clients: [result.toJSON(options)]}; 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')));
}); });
} }
}; };

View File

@ -4,6 +4,7 @@ var _ = require('lodash'),
config = require('../config'), config = require('../config'),
errors = require('../errors'), errors = require('../errors'),
Promise = require('bluebird'), Promise = require('bluebird'),
i18n = require('../i18n'),
configuration; configuration;
@ -58,7 +59,7 @@ configuration = {
value: data[options.key] value: data[options.key]
}]}); }]});
} else { } else {
return Promise.reject(new errors.NotFoundError('Invalid key')); return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.configuration.invalidKey')));
} }
} }
}; };

View File

@ -8,6 +8,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
i18n = require('../i18n'),
api = {}, api = {},
docName = 'db', docName = 'db',
@ -66,13 +67,13 @@ db = {
function validate(options) { function validate(options) {
// Check if a file was provided // Check if a file was provided
if (!utils.checkFileExists(options, 'importfile')) { 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 // Check if the file is valid
if (!utils.checkFileIsValid(options.importfile, importer.getTypes(), importer.getExtensions())) { if (!utils.checkFileIsValid(options.importfile, importer.getTypes(), importer.getExtensions())) {
return Promise.reject(new errors.UnsupportedMediaTypeError( 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) { _.reduce(importer.getExtensions(), function (memo, ext) {
return memo ? memo + ', ' + ext : ext; return memo ? memo + ', ' + ext : ext;
}) })

View File

@ -12,9 +12,9 @@ var _ = require('lodash').runInContext(),
fs = require('fs'), fs = require('fs'),
templatesDir = path.resolve(__dirname, '..', 'mail', 'templates'), templatesDir = path.resolve(__dirname, '..', 'mail', 'templates'),
htmlToText = require('html-to-text'), htmlToText = require('html-to-text'),
readFile = Promise.promisify(fs.readFile), readFile = Promise.promisify(fs.readFile),
docName = 'mail', docName = 'mail',
i18n = require('../i18n'),
mail; mail;
_.templateSettings.interpolate = /{{([\s\S]+?)}}/g; _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
@ -112,7 +112,7 @@ mail = {
mail: [{ mail: [{
message: { message: {
to: result.get('email'), to: result.get('email'),
subject: 'Test Ghost Email', subject: i18n.t('common.api.mail.testGhostEmail'),
html: content.html, html: content.html,
text: content.text text: content.text
} }

View File

@ -7,6 +7,7 @@ var Promise = require('bluebird'),
utils = require('./utils'), utils = require('./utils'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
canThis = permissions.canThis, canThis = permissions.canThis,
i18n = require('../i18n'),
// Holds the persistent notifications // Holds the persistent notifications
notificationsStore = [], notificationsStore = [],
@ -30,7 +31,7 @@ notifications = {
return canThis(options.context).browse.notification().then(function () { return canThis(options.context).browse.notification().then(function () {
return {notifications: notificationsStore}; return {notifications: notificationsStore};
}, function () { }, 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 canThis(options.context).add.notification().then(function () {
return options; return options;
}, function () { }, 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 canThis(options.context).destroy.notification().then(function () {
return options; return options;
}, function () { }, 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) { if (notification && !notification.dismissible) {
return Promise.reject( 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) { 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) { notificationsStore = _.reject(notificationsStore, function (element) {
@ -181,7 +182,7 @@ notifications = {
return notificationsStore; return notificationsStore;
}, function () { }, 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')));
}); });
} }
}; };

View File

@ -6,6 +6,7 @@ var Promise = require('bluebird'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
i18n = require('../i18n'),
docName = 'posts', docName = 'posts',
allowedIncludes = [ allowedIncludes = [
@ -106,7 +107,7 @@ posts = {
return {posts: [result.toJSON(options)]}; 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 {posts: [post]};
} }
return Promise.reject(new errors.NotFoundError('Post not found.')); return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound')));
}); });
}, },

View File

@ -7,6 +7,7 @@ var _ = require('lodash'),
canThis = require('../permissions').canThis, canThis = require('../permissions').canThis,
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
i18n = require('../i18n'),
docName = 'settings', docName = 'settings',
settings, settings,
@ -36,9 +37,9 @@ var _ = require('lodash'),
*/ */
updateConfigCache = function () { updateConfigCache = function () {
var errorMessages = [ var errorMessages = [
'Error: Invalid JSON in settings.labs', i18n.t('errors.api.settings.invalidJsonInLabs'),
'The column with key "labs" could not be parsed as JSON', i18n.t('errors.api.settings.labsColumnCouldNotBeParsed'),
'Please try updating a setting on the labs page, or manually editing your DB' i18n.t('errors.api.settings.tryUpdatingLabs')
], labsValue = {}; ], labsValue = {};
if (settingsCache.labs && settingsCache.labs.value) { if (settingsCache.labs && settingsCache.labs.value) {
@ -245,7 +246,7 @@ populateDefaultSetting = function (key) {
} }
// TODO: Different kind of error? // 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) { var checkSettingPermissions = function (setting) {
if (setting.type === 'core' && !(options.context && options.context.internal)) { if (setting.type === 'core' && !(options.context && options.context.internal)) {
return Promise.reject( 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 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) { checks = _.map(settingsInfo, function (settingInfo) {
@ -344,7 +345,7 @@ settings = {
if (setting.type === 'core' && !(options.context && options.context.internal)) { if (setting.type === 'core' && !(options.context && options.context.internal)) {
return Promise.reject( 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 canThis(options.context).read.setting(options.key).then(function () {
return settingsResult(result); return settingsResult(result);
}, function () { }, 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')));
}); });
}; };

View File

@ -5,6 +5,7 @@ var dataProvider = require('../models'),
Promise = require('bluebird'), Promise = require('bluebird'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
utils = require('./utils'), utils = require('./utils'),
i18n = require('../i18n'),
docName = 'slugs', docName = 'slugs',
slugs, slugs,
@ -45,7 +46,7 @@ slugs = {
*/ */
function checkAllowedTypes(options) { function checkAllowedTypes(options) {
if (allowedTypes[options.type] === undefined) { 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; return options;
} }
@ -71,7 +72,7 @@ slugs = {
// Pipeline calls each task passing the result of one to be the arguments for the next // Pipeline calls each task passing the result of one to be the arguments for the next
return pipeline(tasks, options).then(function (slug) { return pipeline(tasks, options).then(function (slug) {
if (!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}]}; return {slugs: [{slug: slug}]};

View File

@ -6,6 +6,7 @@ var Promise = require('bluebird'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
i18n = require('../i18n'),
docName = 'tags', docName = 'tags',
allowedIncludes = ['count.posts'], allowedIncludes = ['count.posts'],
@ -80,7 +81,7 @@ tags = {
return {tags: [result.toJSON(options)]}; 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 {tags: [tag]};
} }
return Promise.reject(new errors.NotFoundError('Tag not found.')); return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.tags.tagNotFound')));
}); });
}, },

View File

@ -7,6 +7,7 @@ var Promise = require('bluebird'),
settings = require('./settings'), settings = require('./settings'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
utils = require('./utils'), utils = require('./utils'),
i18n = require('../i18n'),
docName = 'themes', docName = 'themes',
themes; themes;
@ -148,7 +149,7 @@ themes = {
// Check whether the request is properly formatted. // Check whether the request is properly formatted.
if (!_.isArray(object.themes)) { 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; themeName = object.themes[0].uuid;
@ -166,7 +167,7 @@ themes = {
}); });
if (!theme) { 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) { if (!theme.name) {

View File

@ -4,6 +4,7 @@ var config = require('../config'),
storage = require('../storage'), storage = require('../storage'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
i18n = require('../i18n'),
upload; upload;
@ -27,12 +28,12 @@ upload = {
// Check if a file was provided // Check if a file was provided
if (!utils.checkFileExists(options, 'uploadimage')) { 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 // Check if the file is valid
if (!utils.checkFileIsValid(options.uploadimage, config.uploads.contentTypes, config.uploads.extensions)) { 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; filepath = options.uploadimage.path;

View File

@ -11,6 +11,7 @@ var Promise = require('bluebird'),
config = require('../config'), config = require('../config'),
mail = require('./mail'), mail = require('./mail'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
i18n = require('../i18n'),
docName = 'users', docName = 'users',
// TODO: implement created_by, updated_by // TODO: implement created_by, updated_by
@ -49,7 +50,7 @@ sendInviteEmail = function sendInviteEmail(user) {
mail: [{ mail: [{
message: { message: {
to: user.email, 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, html: emailContent.html,
text: emailContent.text text: emailContent.text
}, },
@ -137,7 +138,7 @@ users = {
return {users: [result.toJSON(options)]}; 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; var contextRoleId = contextUser.related('roles').toJSON(options)[0].id;
if (roleId !== contextRoleId && editedUserId === contextUser.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) { return dataProvider.User.findOne({role: 'Owner'}).then(function (owner) {
if (contextUser.id !== owner.id) { if (contextUser.id !== owner.id) {
if (editedUserId === owner.id) { if (editedUserId === owner.id) {
if (owner.related('roles').at(0).id !== roleId) { 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) { } else if (roleId !== contextRoleId) {
return canThis(options.context).assign.role(role).then(function () { return canThis(options.context).assign.role(role).then(function () {
@ -212,7 +213,7 @@ users = {
}); });
}); });
}).catch(function handleError(error) { }).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 {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 // Make sure user is allowed to add a user with this role
return dataProvider.Role.findOne({id: roleId}).then(function (role) { return dataProvider.Role.findOne({id: roleId}).then(function (role) {
if (role.get('name') === 'Owner') { 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); return canThis(options.context).assign.role(role);
@ -280,7 +281,7 @@ users = {
return options; return options;
}).catch(function handleError(error) { }).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.password = globalUtils.uid(50);
newUser.status = 'invited'; newUser.status = 'invited';
} else { } 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( return dataProvider.User.getByEmail(
@ -312,7 +313,7 @@ users = {
if (foundUser.get('status') === 'invited' || foundUser.get('status') === 'invited-pending') { if (foundUser.get('status') === 'invited' || foundUser.get('status') === 'invited-pending') {
return foundUser; return foundUser;
} else { } 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) { }).then(function (invitedUser) {
@ -331,7 +332,8 @@ users = {
return Promise.resolve({users: [user]}); return Promise.resolve({users: [user]});
}).catch(function (error) { }).catch(function (error) {
if (error && error.errorType === 'EmailError') { 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); errors.logWarn(error.message);
// If sending the invitation failed, set status to invited-pending // If sending the invitation failed, set status to invited-pending
@ -375,7 +377,7 @@ users = {
options.status = 'all'; options.status = 'all';
return options; return options;
}).catch(function handleError(error) { }).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 canThis(options.context).edit.user(options.data.password[0].user_id).then(function permissionGranted() {
return options; return options;
}).catch(function (error) { }).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 // 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 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')}]});
}); });
}, },

View File

@ -6,6 +6,7 @@ var Promise = require('bluebird'),
errors = require('../errors'), errors = require('../errors'),
permissions = require('../permissions'), permissions = require('../permissions'),
validation = require('../data/validation'), validation = require('../data/validation'),
i18n = require('../i18n'),
utils; utils;
@ -211,7 +212,7 @@ utils = {
return options; return options;
}).catch(errors.NoPermissionError, function handleNoPermissionError(error) { }).catch(errors.NoPermissionError, function handleNoPermissionError(error) {
// pimp error message // 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() // forward error to next catch()
return Promise.reject(error); return Promise.reject(error);
}).catch(function handleError(error) { }).catch(function handleError(error) {
@ -271,7 +272,7 @@ utils = {
*/ */
checkObject: function (object, docName, editId) { checkObject: function (object, docName, editId) {
if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) { 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 // 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)) { 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); return Promise.resolve(object);

View File

@ -4,6 +4,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
api = require('../api'), api = require('../api'),
loader = require('./loader'), loader = require('./loader'),
i18n = require('../i18n'),
// Holds the available apps // Holds the available apps
availableApps = {}; availableApps = {};
@ -44,9 +45,9 @@ module.exports = {
}); });
} catch (e) { } catch (e) {
errors.logError( errors.logError(
'Failed to parse activeApps setting value: ' + e.message, i18n.t('errors.apps.failedToParseActiveAppsSettings.error', {message: e.message}),
'Your apps will not be loaded.', i18n.t('errors.apps.failedToParseActiveAppsSettings.context'),
'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.help')
); );
return Promise.resolve(); return Promise.resolve();
@ -86,8 +87,8 @@ module.exports = {
}).catch(function (err) { }).catch(function (err) {
errors.logError( errors.logError(
err.message || err, err.message || err,
'The app will not be loaded', i18n.t('errors.apps.appWillNotBeLoaded.error'),
'Check with the app creator, or read the app documentation for more details on app requirements' i18n.t('errors.apps.appWillNotBeLoaded.help')
); );
}); });
}); });

View File

@ -7,6 +7,7 @@ var path = require('path'),
AppSandbox = require('./sandbox'), AppSandbox = require('./sandbox'),
AppDependencies = require('./dependencies'), AppDependencies = require('./dependencies'),
AppPermissions = require('./permissions'), AppPermissions = require('./permissions'),
i18n = require('../i18n'),
loader; loader;
// Get the full path to an app by name // Get the full path to an app by name
@ -66,7 +67,7 @@ loader = {
return perms.read().catch(function (err) { return perms.read().catch(function (err) {
// Provide a helpful error about which app // 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) { .then(function (appPerms) {
@ -76,7 +77,7 @@ loader = {
// Check for an install() method on the app. // Check for an install() method on the app.
if (!_.isFunction(app.install)) { 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 // Run the app.install() method
@ -97,7 +98,7 @@ loader = {
// Check for an activate() method on the app. // Check for an activate() method on the app.
if (!_.isFunction(app.activate)) { 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 // Wrapping the activate() with a when because it's possible

View File

@ -2,6 +2,7 @@ var _ = require('lodash'),
api = require('../api'), api = require('../api'),
helpers = require('../helpers'), helpers = require('../helpers'),
filters = require('../filters'), filters = require('../filters'),
i18n = require('../i18n'),
generateProxyFunctions; generateProxyFunctions;
generateProxyFunctions = function (name, permissions) { generateProxyFunctions = function (name, permissions) {
@ -23,7 +24,7 @@ generateProxyFunctions = function (name, permissions) {
var permValue = getPermissionToMethod(perm, method); var permValue = getPermissionToMethod(perm, method);
if (!permValue) { 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); return wrappedFunc.apply(context, args);
@ -84,11 +85,11 @@ generateProxyFunctions = function (name, permissions) {
function AppProxy(options) { function AppProxy(options) {
if (!options.name) { 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) { 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)); _.extend(this, generateProxyFunctions(options.name, options.permissions));

View File

@ -1,6 +1,7 @@
var path = require('path'), var path = require('path'),
Module = require('module'), Module = require('module'),
i18n = require('../i18n'),
_ = require('lodash'); _ = require('lodash');
function AppSandbox(opts) { function AppSandbox(opts) {
@ -38,7 +39,7 @@ AppSandbox.prototype.loadModule = function loadModuleSandboxed(modulePath) {
currentModule.require = function requireProxy(module) { currentModule.require = function requireProxy(module) {
// check whitelist, plugin config, etc. // check whitelist, plugin config, etc.
if (_.contains(self.opts.blacklist, module)) { 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), 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 // Check relative path from the appRoot for outside requires
relPath = path.relative(appRoot, resolvedPath); relPath = path.relative(appRoot, resolvedPath);
if (relPath.slice(0, 2) === '..') { 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 // Assign as new module path

View File

@ -14,6 +14,7 @@ var path = require('path'),
errors = require('../errors'), errors = require('../errors'),
configUrl = require('./url'), configUrl = require('./url'),
packageInfo = require('../../../package.json'), packageInfo = require('../../../package.json'),
i18n = require('../i18n'),
appRoot = path.resolve(__dirname, '../../../'), appRoot = path.resolve(__dirname, '../../../'),
corePath = path.resolve(appRoot, 'core/'), corePath = path.resolve(appRoot, 'core/'),
testingEnvs = ['testing', 'testing-mysql', 'testing-pg'], testingEnvs = ['testing', 'testing-mysql', 'testing-pg'],
@ -286,9 +287,9 @@ ConfigManager.prototype.writeFile = function () {
error; error;
if (!templateExists) { if (!templateExists) {
error = new Error('Could not locate a configuration file.'); error = new Error(i18n.t('errors.config.couldNotLocateConfigFile.error'));
error.context = appRoot; 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); return reject(error);
} }
@ -296,14 +297,20 @@ ConfigManager.prototype.writeFile = function () {
// Copy config.example.js => config.js // Copy config.example.js => config.js
read = fs.createReadStream(configExamplePath); read = fs.createReadStream(configExamplePath);
read.on('error', function (err) { 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); reject(err);
}); });
write = fs.createWriteStream(configPath); write = fs.createWriteStream(configPath);
write.on('error', function (err) { 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); reject(err);
}); });
@ -344,24 +351,33 @@ ConfigManager.prototype.validate = function () {
// Check that our url is valid // Check that our url is valid
if (!validator.isURL(config.url, {protocols: ['http', 'https'], require_protocol: true})) { 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); parsedUrl = url.parse(config.url || 'invalid', false, true);
if (/\/ghost(\/|$)/.test(parsedUrl.pathname)) { 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 // Check that we have database values
if (!config.database || !config.database.client) { 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; 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 // Check for valid server host and port values
if (!config.server || !(hasHostAndPort || hasSocket)) { 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); return Promise.resolve(config);
@ -417,9 +436,9 @@ ConfigManager.prototype.displayDeprecated = function (item, properties, address)
if (properties.length) { if (properties.length) {
return self.displayDeprecated(item[property], properties, address); return self.displayDeprecated(item[property], properties, address);
} }
errorText = 'The configuration property [' + chalk.bold(address.join('.')) + '] has been deprecated.'; errorText = i18n.t('errors.config.deprecatedProperty.error', {property: chalk.bold(address.join('.'))});
explanationText = 'This will be removed in a future version, please update your config.js file.'; explanationText = i18n.t('errors.config.deprecatedProperty.explanation');
helpText = 'Please check http://support.ghost.org/config for the most up-to-date example.'; helpText = i18n.t('errors.config.deprecatedProperty.help', {url: 'http://support.ghost.org/config'});
errors.logWarn(errorText, explanationText, helpText); errors.logWarn(errorText, explanationText, helpText);
} }
}; };

View File

@ -3,6 +3,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
updateCheck = require('../update-check'), updateCheck = require('../update-check'),
config = require('../config'), config = require('../config'),
i18n = require('../i18n'),
adminControllers; adminControllers;
adminControllers = { adminControllers = {
@ -45,8 +46,8 @@ adminControllers = {
location: 'settings-about-upgrade', location: 'settings-about-upgrade',
dismissible: false, dismissible: false,
status: 'alert', 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) { return api.notifications.browse({context: {internal: true}}).then(function then(results) {
if (!_.some(results.notifications, {message: notification.message})) { if (!_.some(results.notifications, {message: notification.message})) {

View File

@ -6,6 +6,7 @@ var _ = require('lodash'),
serverUtils = require('../../utils'), serverUtils = require('../../utils'),
errors = require('../../errors'), errors = require('../../errors'),
settings = require('../../api/settings'), settings = require('../../api/settings'),
i18n = require('../../i18n'),
excludedTables = ['accesstokens', 'refreshtokens', 'clients'], excludedTables = ['accesstokens', 'refreshtokens', 'clients'],
exporter, exporter,
@ -53,7 +54,7 @@ exporter = function () {
return exportData; return exportData;
}).catch(function (err) { }).catch(function (err) {
errors.logAndThrowError(err, 'Error exporting data', ''); errors.logAndThrowError(err, i18n.t('errors.data.export.errorExportingData'), '');
}); });
}); });
}; };

View File

@ -16,6 +16,7 @@ var Promise = require('bluebird'),
fixtures = require('./fixtures'), fixtures = require('./fixtures'),
permissions = require('./permissions'), permissions = require('./permissions'),
notifications = require('../../api/notifications'), notifications = require('../../api/notifications'),
i18n = require('../../i18n'),
// Private // Private
logInfo, logInfo,
@ -30,7 +31,7 @@ var Promise = require('bluebird'),
update; update;
logInfo = function logInfo(message) { 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'}); return models.Role.findOne({name: 'Owner'});
}).then(function (ownerRole) { }).then(function (ownerRole) {
if (adminUser) { if (adminUser) {
logInfo('Converting admin to owner'); logInfo(i18n.t('notices.data.fixtures.convertingAdmToOwner'));
return adminUser.roles().updatePivot({role_id: ownerRole.id}); return adminUser.roles().updatePivot({role_id: ownerRole.id});
} }
}); });
@ -64,7 +65,7 @@ createOwner = function createOwner() {
user.roles = [ownerRole.id]; user.roles = [ownerRole.id];
user.password = utils.uid(50); user.password = utils.uid(50);
logInfo('Creating owner'); logInfo(i18n.t('notices.data.fixtures.creatingOwner'));
return models.User.add(user, options); return models.User.add(user, options);
}); });
}; };
@ -77,7 +78,7 @@ populate = function populate() {
Role = models.Role, Role = models.Role,
Client = models.Client; Client = models.Client;
logInfo('Populating fixtures'); logInfo(i18n.t('notices.data.fixtures.populatingFixtures'));
_.each(fixtures.posts, function (post) { _.each(fixtures.posts, function (post) {
ops.push(Post.add(post, options)); ops.push(Post.add(post, options));
@ -133,12 +134,12 @@ to003 = function to003() {
Role = models.Role, Role = models.Role,
Client = models.Client; Client = models.Client;
logInfo('Upgrading fixtures to 003'); logInfo(i18n.t('notices.data.fixtures.upgradingFixturesTo', {version: '003'}));
// Add the client fixture if missing // Add the client fixture if missing
upgradeOp = Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) { upgradeOp = Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) {
if (!client) { if (!client) {
logInfo('Adding ghost-admin client fixture'); logInfo(i18n.t('notices.data.fixtures.addingClientFixture'));
return Client.add(fixtures.clients[0], options); return Client.add(fixtures.clients[0], options);
} }
}); });
@ -147,7 +148,7 @@ to003 = function to003() {
// Add the owner role if missing // Add the owner role if missing
upgradeOp = Role.findOne({name: fixtures.roles[3].name}).then(function (owner) { upgradeOp = Role.findOne({name: fixtures.roles[3].name}).then(function (owner) {
if (!owner) { if (!owner) {
logInfo('Adding owner role fixture'); logInfo(i18n.t('notices.data.fixtures.addingOwnerRoleFixture'));
_.each(fixtures.roles.slice(3), function (role) { _.each(fixtures.roles.slice(3), function (role) {
return Role.add(role, options); return Role.add(role, options);
}); });
@ -171,15 +172,15 @@ to004 = function to004() {
ops = [], ops = [],
upgradeOp, upgradeOp,
jquery = [ 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' '<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
], ],
privacyMessage = [ privacyMessage = [
'jQuery has been removed from Ghost core and is now being loaded from the jQuery Foundation\'s CDN.', i18n.t('notices.data.fixtures.jQueryRemoved'),
'This can be changed or removed in your <strong>Code Injection</strong> settings area.' 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 // add jquery setting and privacy info
upgradeOp = function () { upgradeOp = function () {
@ -188,7 +189,7 @@ to004 = function to004() {
value = setting.attributes.value; value = setting.attributes.value;
// Only add jQuery if it's not already in there // Only add jQuery if it's not already in there
if (value.indexOf(jquery.join('')) === -1) { if (value.indexOf(jquery.join('')) === -1) {
logInfo('Adding jQuery link to ghost_foot'); logInfo(i18n.t('notices.data.fixtures.addingJquery'));
value = jquery.join('') + value; value = jquery.join('') + value;
return models.Settings.edit({key: 'ghost_foot', value: value}, options).then(function () { return models.Settings.edit({key: 'ghost_foot', value: value}, options).then(function () {
if (_.isEmpty(config.privacy)) { if (_.isEmpty(config.privacy)) {
@ -210,7 +211,7 @@ to004 = function to004() {
upgradeOp = function () { upgradeOp = function () {
return models.Settings.findOne('isPrivate').then(function (setting) { return models.Settings.findOne('isPrivate').then(function (setting) {
if (setting) { if (setting) {
logInfo('Update isPrivate setting'); logInfo(i18n.t('notices.data.fixtures.updateIsPrivate'));
return models.Settings.edit({key: 'isPrivate', type: 'private'}, options); return models.Settings.edit({key: 'isPrivate', type: 'private'}, options);
} }
return Promise.resolve(); return Promise.resolve();
@ -222,7 +223,7 @@ to004 = function to004() {
upgradeOp = function () { upgradeOp = function () {
return models.Settings.findOne('password').then(function (setting) { return models.Settings.findOne('password').then(function (setting) {
if (setting) { if (setting) {
logInfo('Update password setting'); logInfo(i18n.t('notices.data.fixtures.updatePassword'));
return models.Settings.edit({key: 'password', type: 'private'}, options); return models.Settings.edit({key: 'password', type: 'private'}, options);
} }
return Promise.resolve(); return Promise.resolve();
@ -235,7 +236,7 @@ to004 = function to004() {
upgradeOp = function () { upgradeOp = function () {
return models.Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) { return models.Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) {
if (client) { if (client) {
logInfo('Update ghost-admin client fixture'); logInfo(i18n.t('notices.data.fixtures.updateAdminClientFixture'));
var adminClient = fixtures.clients[0]; var adminClient = fixtures.clients[0];
adminClient.secret = crypto.randomBytes(6).toString('hex'); adminClient.secret = crypto.randomBytes(6).toString('hex');
return models.Client.edit(adminClient, _.extend({}, options, {id: client.id})); return models.Client.edit(adminClient, _.extend({}, options, {id: client.id}));
@ -249,7 +250,7 @@ to004 = function to004() {
upgradeOp = function () { upgradeOp = function () {
return models.Client.findOne({slug: fixtures.clients[1].slug}).then(function (client) { return models.Client.findOne({slug: fixtures.clients[1].slug}).then(function (client) {
if (!client) { if (!client) {
logInfo('Add ghost-frontend client fixture'); logInfo(i18n.t('notices.data.fixtures.addFrontendClientFixture'));
var frontendClient = fixtures.clients[1]; var frontendClient = fixtures.clients[1];
frontendClient.secret = crypto.randomBytes(6).toString('hex'); frontendClient.secret = crypto.randomBytes(6).toString('hex');
return models.Client.add(frontendClient, options); return models.Client.add(frontendClient, options);
@ -276,7 +277,7 @@ to004 = function to004() {
} }
}); });
if (tagOps.length > 0) { if (tagOps.length > 0) {
logInfo('Cleaning ' + tagOps.length + ' malformed tags'); logInfo(i18n.t('notices.data.fixtures.cleaningTags', {length: tagOps.length}));
return Promise.all(tagOps); return Promise.all(tagOps);
} }
} }
@ -288,7 +289,7 @@ to004 = function to004() {
// Add post_tag order // Add post_tag order
upgradeOp = function () { upgradeOp = function () {
var tagOps = []; 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) { return models.Post.findAll(_.extend({}, options)).then(function (posts) {
if (posts) { if (posts) {
return posts.mapThen(function (post) { return posts.mapThen(function (post) {
@ -313,9 +314,9 @@ to004 = function to004() {
}); });
if (tagOps.length > 0) { 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 () { 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 () { upgradeOp = function () {
return models.Post.findOne({slug: fixtures.posts_0_7[0].slug, status: 'all'}, options).then(function (post) { return models.Post.findOne({slug: fixtures.posts_0_7[0].slug, status: 'all'}, options).then(function (post) {
if (!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 // 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 // This is a hack to ensure that this post appears at the very top of the drafts list, because
// unpublished posts always appear first // unpublished posts always appear first
@ -343,7 +344,7 @@ to004 = function to004() {
update = function update(fromVersion, toVersion) { update = function update(fromVersion, toVersion) {
var ops = []; var ops = [];
logInfo('Updating fixtures'); logInfo(i18n.t('notices.data.fixtures.updatingFixtures'));
// Are we migrating to, or past 003? // Are we migrating to, or past 003?
if ((fromVersion < '003' && toVersion >= '003') || if ((fromVersion < '003' && toVersion >= '003') ||
fromVersion === '003' && toVersion === '003' && process.env.FORCE_MIGRATION) { fromVersion === '003' && toVersion === '003' && process.env.FORCE_MIGRATION) {

View File

@ -6,6 +6,7 @@ var Promise = require('bluebird'),
errors = require('../../../errors'), errors = require('../../../errors'),
models = require('../../../models'), models = require('../../../models'),
fixtures = require('./permissions'), fixtures = require('./permissions'),
i18n = require('../../../i18n'),
// private // private
logInfo, logInfo,
@ -71,7 +72,7 @@ addAllPermissions = function (options) {
// ## Populate // ## Populate
populate = function (options) { populate = function (options) {
logInfo('Populating permissions'); logInfo(i18n.t('errors.data.fixtures.populatingPermissions'));
// ### Ensure all permissions are added // ### Ensure all permissions are added
return addAllPermissions(options).then(function () { return addAllPermissions(options).then(function () {
// ### Ensure all roles_permissions are added // ### Ensure all roles_permissions are added
@ -85,12 +86,12 @@ populate = function (options) {
to003 = function (options) { to003 = function (options) {
var ops = []; 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 // 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 // full set of permissions defined as of version 003
return models.Permissions.forge().fetch().then(function (permissions) { return models.Permissions.forge().fetch().then(function (permissions) {
logInfo('Removing old permissions'); logInfo(i18n.t('errors.data.fixtures.removingOldPermissions'));
permissions.each(function (permission) { permissions.each(function (permission) {
ops.push(permission.related('roles').detach().then(function () { ops.push(permission.related('roles').detach().then(function () {
return permission.destroy(); return permission.destroy();

View File

@ -2,6 +2,7 @@ var Promise = require('bluebird'),
_ = require('lodash'), _ = require('lodash'),
models = require('../../models'), models = require('../../models'),
utils = require('./utils'), utils = require('./utils'),
i18n = require('../../i18n'),
internal = utils.internal, internal = utils.internal,
@ -34,7 +35,7 @@ DataImporter.prototype.loadUsers = function () {
}); });
if (!users.owner) { if (!users.owner) {
return Promise.reject('Unable to find an owner'); return Promise.reject(i18n.t('errors.data.import.dataImporter.unableToFindOwner'));
} }
return users; return users;

View File

@ -5,6 +5,7 @@ var Promise = require('bluebird'),
uuid = require('node-uuid'), uuid = require('node-uuid'),
importer = require('./data-importer'), importer = require('./data-importer'),
tables = require('../schema').tables, tables = require('../schema').tables,
i18n = require('../../i18n'),
validate, validate,
handleErrors, handleErrors,
checkDuplicateAttributes, checkDuplicateAttributes,
@ -36,7 +37,7 @@ cleanError = function cleanError(error) {
value = error.raw.detail; value = error.raw.detail;
offendingProperty = error.model; 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; offendingProperty = offendingProperty || error.model;

View File

@ -3,6 +3,7 @@ var Promise = require('bluebird'),
models = require('../../models'), models = require('../../models'),
errors = require('../../errors'), errors = require('../../errors'),
globalUtils = require('../../utils'), globalUtils = require('../../utils'),
i18n = require('../../i18n'),
internal = {context: {internal: true}}, internal = {context: {internal: true}},
utils, utils,
@ -78,7 +79,7 @@ utils = {
userMap[userToMap] = existingUsers[owner.email].realId; userMap[userToMap] = existingUsers[owner.email].realId;
} else { } else {
throw new errors.DataImportError( 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
); );
} }
}); });

View File

@ -2,6 +2,7 @@ var _ = require('lodash'),
Promise = require('bluebird'), Promise = require('bluebird'),
fs = require('fs-extra'), fs = require('fs-extra'),
errors = require('../../../errors'), errors = require('../../../errors'),
i18n = require('../../../i18n'),
JSONHandler; JSONHandler;
JSONHandler = { JSONHandler = {
@ -23,7 +24,7 @@ JSONHandler = {
// if importData follows JSON-API format `{ db: [exportedData] }` // if importData follows JSON-API format `{ db: [exportedData] }`
if (_.keys(importData).length === 1) { if (_.keys(importData).length === 1) {
if (!importData.db || !Array.isArray(importData.db)) { 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]; importData = importData.db[0];
@ -31,8 +32,9 @@ JSONHandler = {
return importData; return importData;
} catch (e) { } catch (e) {
errors.logError(e, 'API DB import content', 'check that the import file is valid JSON.'); errors.logError(e, i18n.t('errors.data.importer.handlers.json.apiDbImportContent'),
return Promise.reject(new errors.BadRequestError('Failed to parse the import JSON file.')); i18n.t('errors.data.importer.handlers.json.checkImportJsonIsValid'));
return Promise.reject(new errors.BadRequestError(i18n.t('errors.data.importer.handlers.json.failedToParseImportJson')));
} }
}); });
} }

View File

@ -14,6 +14,7 @@ var _ = require('lodash'),
MarkdownHandler = require('./handlers/markdown'), MarkdownHandler = require('./handlers/markdown'),
ImageImporter = require('./importers/image'), ImageImporter = require('./importers/image'),
DataImporter = require('./importers/data'), DataImporter = require('./importers/data'),
i18n = require('../../i18n'),
// Glob levels // Glob levels
ROOT_ONLY = 0, ROOT_ONLY = 0,
@ -107,7 +108,8 @@ _.extend(ImportManager.prototype, {
_.each(filesToDelete, function (fileToDelete) { _.each(filesToDelete, function (fileToDelete) {
fs.remove(fileToDelete, function (err) { fs.remove(fileToDelete, function (err) {
if (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 // This is a temporary extra message for the old format roon export which doesn't work with Ghost
if (oldRoonMatches.length > 0) { if (oldRoonMatches.length > 0) {
throw new errors.UnsupportedMediaTypeError( 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) { 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 * 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} this.getExtensionGlob(this.getExtensions(), ALL_DIRS), {cwd: directory}
); );
if (extMatchesAll.length < 1 || extMatchesAll[0].split('/') < 1) { 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]; return extMatchesAll[0].split('/')[0];
}, },
/** /**
@ -236,7 +239,7 @@ _.extend(ImportManager.prototype, {
if (importData.hasOwnProperty(handler.type)) { if (importData.hasOwnProperty(handler.type)) {
// This limitation is here to reduce the complexity of the importer for now // This limitation is here to reduce the complexity of the importer for now
return Promise.reject(new errors.UnsupportedMediaTypeError( 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) { if (ops.length === 0) {
return Promise.reject(new errors.UnsupportedMediaTypeError( return Promise.reject(new errors.UnsupportedMediaTypeError(
'Zip did not include any content to import.' i18n.t('errors.data.importer.index.noContentToImport')
)); ));
} }

View File

@ -2,6 +2,7 @@ var _ = require('lodash'),
errors = require('../../errors'), errors = require('../../errors'),
utils = require('../utils'), utils = require('../utils'),
schema = require('../schema').tables, schema = require('../schema').tables,
i18n = require('../../i18n'),
// private // private
logInfo, logInfo,
@ -13,14 +14,14 @@ var _ = require('lodash'),
modifyUniqueCommands; modifyUniqueCommands;
logInfo = function logInfo(message) { logInfo = function logInfo(message) {
errors.logInfo('Migrations', message); errors.logInfo(i18n.t('notices.data.migration.commands.migrations'), message);
}; };
getDeleteCommands = function getDeleteCommands(oldTables, newTables) { getDeleteCommands = function getDeleteCommands(oldTables, newTables) {
var deleteTables = _.difference(oldTables, newTables); var deleteTables = _.difference(oldTables, newTables);
return _.map(deleteTables, function (table) { return _.map(deleteTables, function (table) {
return function () { return function () {
logInfo('Deleting table: ' + table); logInfo(i18n.t('notices.data.migration.commands.deletingTable', {table: table}));
return utils.deleteTable(table); return utils.deleteTable(table);
}; };
}); });
@ -29,7 +30,7 @@ getAddCommands = function getAddCommands(oldTables, newTables) {
var addTables = _.difference(newTables, oldTables); var addTables = _.difference(newTables, oldTables);
return _.map(addTables, function (table) { return _.map(addTables, function (table) {
return function () { return function () {
logInfo('Creating table: ' + table); logInfo(i18n.t('notices.data.migration.commands.creatingTable', {table: table}));
return utils.createTable(table); return utils.createTable(table);
}; };
}); });
@ -40,7 +41,7 @@ addColumnCommands = function addColumnCommands(table, columns) {
return _.map(addColumns, function (column) { return _.map(addColumns, function (column) {
return function () { return function () {
logInfo('Adding column: ' + table + '.' + column); logInfo(i18n.t('notices.data.migration.commands.addingColumn', {table: table, column: column}));
return utils.addColumn(table, column); return utils.addColumn(table, column);
}; };
}); });
@ -51,14 +52,14 @@ modifyUniqueCommands = function modifyUniqueCommands(table, indexes) {
if (schema[table][column].unique === true) { if (schema[table][column].unique === true) {
if (!_.contains(indexes, table + '_' + column + '_unique')) { if (!_.contains(indexes, table + '_' + column + '_unique')) {
return function () { 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); return utils.addUnique(table, column);
}; };
} }
} else if (!schema[table][column].unique) { } else if (!schema[table][column].unique) {
if (_.contains(indexes, table + '_' + column + '_unique')) { if (_.contains(indexes, table + '_' + column + '_unique')) {
return function () { 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); return utils.dropUnique(table, column);
}; };
} }

View File

@ -13,6 +13,7 @@ var _ = require('lodash'),
dataExport = require('../export'), dataExport = require('../export'),
utils = require('../utils'), utils = require('../utils'),
config = require('../../config'), config = require('../../config'),
i18n = require('../../i18n'),
schemaTables = _.keys(schema), schemaTables = _.keys(schema),
@ -29,26 +30,26 @@ var _ = require('lodash'),
migrateUpFreshDb; migrateUpFreshDb;
logInfo = function logInfo(message) { logInfo = function logInfo(message) {
errors.logInfo('Migrations', message); errors.logInfo(i18n.t('notices.data.migration.index.migrations'), message);
}; };
populateDefaultSettings = function populateDefaultSettings() { populateDefaultSettings = function populateDefaultSettings() {
// Initialise the default settings // Initialise the default settings
logInfo('Populating default settings'); logInfo(i18n.t('notices.data.migration.index.populatingDefaultSettings'));
return models.Settings.populateDefaults().then(function () { return models.Settings.populateDefaults().then(function () {
logInfo('Complete'); logInfo(i18n.t('notices.data.migration.index.complete'));
}); });
}; };
backupDatabase = function backupDatabase() { backupDatabase = function backupDatabase() {
logInfo('Creating database backup'); logInfo(i18n.t('notices.data.migration.index.creatingDatabaseBackup'));
return dataExport().then(function (exportedData) { return dataExport().then(function (exportedData) {
// Save the exported data to the file system for download // Save the exported data to the file system for download
return dataExport.fileName().then(function (fileName) { return dataExport.fileName().then(function (fileName) {
fileName = path.resolve(config.paths.contentPath + '/data/' + fileName); fileName = path.resolve(config.paths.contentPath + '/data/' + fileName);
return Promise.promisify(fs.writeFile)(fileName, JSON.stringify(exportedData)).then(function () { 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) { if (databaseVersion < defaultVersion || process.env.FORCE_MIGRATION) {
// 2. The database exists but is out of date // 2. The database exists but is out of date
// Migrate to latest version // 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 () { return self.migrateUp(databaseVersion, defaultVersion).then(function () {
// Finally update the databases current version // Finally update the databases current version
return versioning.setDatabaseVersion(); return versioning.setDatabaseVersion();
@ -92,7 +94,7 @@ init = function (tablesOnly) {
if (databaseVersion === defaultVersion) { if (databaseVersion === defaultVersion) {
// 1. The database exists and is up-to-date // 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 // TODO: temporary fix for missing client.secret
return fixClientSecret(); return fixClientSecret();
} }
@ -101,20 +103,20 @@ init = function (tablesOnly) {
// 3. The database exists but the currentVersion setting does not or cannot be understood // 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 // In this case we don't understand the version because it is too high
errors.logErrorAndExit( errors.logErrorAndExit(
'Your database is not compatible with this version of Ghost', i18n.t('notices.data.migration.index.databaseNotCompatible.error'),
'You will need to create a new database' i18n.t('notices.data.migration.index.databaseNotCompatible.help')
); );
} }
}, function (err) { }, function (err) {
if (err.message || err === 'Settings table does not exist') { if (err.message || err === 'Settings table does not exist') {
// 4. The database has not yet been created // 4. The database has not yet been created
// Bring everything up from initial version. // 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); return self.migrateUpFreshDb(tablesOnly);
} }
// 3. The database exists but the currentVersion setting does not or cannot be understood // 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 // 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, var tableSequence,
tables = _.map(schemaTables, function (table) { tables = _.map(schemaTables, function (table) {
return function () { return function () {
logInfo('Creating table: ' + table); logInfo(i18n.t('notices.data.migration.index.creatingTable', {table: table}));
return utils.createTable(table); return utils.createTable(table);
}; };
}); });
logInfo('Creating tables...'); logInfo(i18n.t('notices.data.migration.index.creatingTables'));
tableSequence = sequence(tables); tableSequence = sequence(tables);
if (tablesOnly) { if (tablesOnly) {
@ -189,7 +191,7 @@ migrateUp = function (fromVersion, toVersion) {
// execute the commands in sequence // execute the commands in sequence
if (!_.isEmpty(migrateOps)) { if (!_.isEmpty(migrateOps)) {
logInfo('Running migrations'); logInfo(i18n.t('notices.data.migration.index.runningMigrations'));
return sequence(migrateOps); return sequence(migrateOps);
} }

View File

@ -3,6 +3,7 @@ var _ = require('lodash'),
config = require('../../config'), config = require('../../config'),
schema = require('../schema').tables, schema = require('../schema').tables,
clients = require('./clients'), clients = require('./clients'),
i18n = require('../../i18n'),
dbConfig; dbConfig;
@ -86,7 +87,7 @@ function getTables() {
return clients[client].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) { function getIndexes(table) {
@ -97,7 +98,7 @@ function getIndexes(table) {
return clients[client].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) { function getColumns(table) {
@ -108,7 +109,7 @@ function getColumns(table) {
return clients[client].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() { function checkTables() {

View File

@ -5,6 +5,7 @@ var schema = require('../schema').tables,
errors = require('../../errors'), errors = require('../../errors'),
config = require('../../config'), config = require('../../config'),
readThemes = require('../../utils/read-themes'), readThemes = require('../../utils/read-themes'),
i18n = require('../../i18n'),
validateSchema, validateSchema,
validateSettings, validateSettings,
@ -44,7 +45,7 @@ validateSchema = function validateSchema(tableName, model) {
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable') if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
&& schema[tableName][columnKey].nullable !== true) { && schema[tableName][columnKey].nullable !== true) {
if (validator.isNull(model[columnKey]) || validator.empty(model[columnKey])) { 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)); validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
} }
} }
@ -54,8 +55,8 @@ validateSchema = function validateSchema(tableName, model) {
// check length // check length
if (schema[tableName][columnKey].hasOwnProperty('maxlength')) { if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
if (!validator.isLength(model[columnKey], 0, schema[tableName][columnKey].maxlength)) { if (!validator.isLength(model[columnKey], 0, schema[tableName][columnKey].maxlength)) {
message = 'Value in [' + tableName + '.' + columnKey + '] exceeds maximum length of ' message = i18n.t('notices.data.validation.index.valueExceedsMaxLength',
+ schema[tableName][columnKey].maxlength + ' characters.'; {tableName: tableName, columnKey: columnKey, maxlength: schema[tableName][columnKey].maxlength});
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey)); validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
} }
} }
@ -68,7 +69,7 @@ validateSchema = function validateSchema(tableName, model) {
// check type // check type
if (schema[tableName][columnKey].hasOwnProperty('type')) { if (schema[tableName][columnKey].hasOwnProperty('type')) {
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(model[columnKey])) { 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)); validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
} }
} }
@ -117,7 +118,7 @@ validateActiveTheme = function validateActiveTheme(themeName) {
return availableThemes.then(function then(themes) { return availableThemes.then(function then(themes) {
if (!themes.hasOwnProperty(themeName)) { 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) // equivalent of validator.isSomething(option1, option2)
if (validator[validationName].apply(validator, validationOptions) !== goodResult) { 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(); validationOptions.shift();

View File

@ -1,6 +1,7 @@
var _ = require('lodash'), var _ = require('lodash'),
errors = require('../../errors'), errors = require('../../errors'),
config = require('../../config'), config = require('../../config'),
i18n = require('../../i18n'),
defaultSettings = require('../default-settings'), defaultSettings = require('../default-settings'),
@ -36,7 +37,7 @@ function getDatabaseVersion() {
.then(function (versions) { .then(function (versions) {
var databaseVersion = _.reduce(versions, function (memo, version) { var databaseVersion = _.reduce(versions, function (memo, version) {
if (isNaN(version.value)) { 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; return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
}, initialVersion); }, initialVersion);
@ -49,7 +50,7 @@ function getDatabaseVersion() {
return databaseVersion; return databaseVersion;
}); });
} }
throw new Error('Settings table does not exist'); throw new Error(i18n.t('errors.data.versioning.index.settingsTableDoesNotExist'));
}); });
} }

View File

@ -4,6 +4,7 @@ var _ = require('lodash'),
config = require('../../config'), config = require('../../config'),
errors = require('../../errors'), errors = require('../../errors'),
events = require('../../events'), events = require('../../events'),
i18n = require('../../i18n'),
pingList; pingList;
// ToDo: Make this configurable // ToDo: Make this configurable
@ -68,8 +69,8 @@ function ping(post) {
req.on('error', function (error) { req.on('error', function (error) {
errors.logError( errors.logError(
error, error,
'Pinging services for updates on your blog failed, your blog will continue to function.', i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error'),
'If you get this error repeatedly, please seek help on http://support.ghost.org.' i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'})
); );
}); });
req.end(); req.end();

View File

@ -17,6 +17,7 @@ var _ = require('lodash'),
EmailError = require('./email-error'), EmailError = require('./email-error'),
DataImportError = require('./data-import-error'), DataImportError = require('./data-import-error'),
TooManyRequestsError = require('./too-many-requests-error'), TooManyRequestsError = require('./too-many-requests-error'),
i18n = require('../i18n'),
config, config,
errors, errors,
@ -43,7 +44,7 @@ errors = {
throwError: function (err) { throwError: function (err) {
if (!err) { if (!err) {
err = new Error('An error occurred'); err = new Error(i18n.t('errors.errors.anErrorOccurred'));
} }
if (_.isString(err)) { if (_.isString(err)) {
@ -71,8 +72,8 @@ errors = {
if ((process.env.NODE_ENV === 'development' || if ((process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' || process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production')) { process.env.NODE_ENV === 'production')) {
warn = warn || 'no message supplied'; warn = warn || i18n.t('errors.errors.noMessageSupplied');
var msgs = [chalk.yellow('\nWarning:', warn), '\n']; var msgs = [chalk.yellow(i18n.t('errors.errors.warning'), warn), '\n'];
if (context) { if (context) {
msgs.push(chalk.white(context), '\n'); msgs.push(chalk.white(context), '\n');
@ -109,22 +110,22 @@ errors = {
if (_.isObject(err) && _.isString(err.message)) { if (_.isObject(err) && _.isString(err.message)) {
err = err.message; err = err.message;
} else { } else {
err = 'An unknown error occurred.'; err = i18n.t('errors.errors.unknownErrorOccurred');
} }
} }
// Overwrite error to provide information that this is probably a permission problem // Overwrite error to provide information that this is probably a permission problem
// TODO: https://github.com/TryGhost/Ghost/issues/3687 // TODO: https://github.com/TryGhost/Ghost/issues/3687
if (err.indexOf('SQLITE_READONLY') !== -1) { 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.'; context = i18n.t('errors.errors.databaseIsReadOnly');
help = 'Check your database file and make sure that file owner and permissions are correct.'; help = i18n.t('errors.errors.checkDatabase');
} }
// TODO: Logging framework hookup // TODO: Logging framework hookup
// Eventually we'll have better logging which will know about envs // Eventually we'll have better logging which will know about envs
if ((process.env.NODE_ENV === 'development' || if ((process.env.NODE_ENV === 'development' ||
process.env.NODE_ENV === 'staging' || process.env.NODE_ENV === 'staging' ||
process.env.NODE_ENV === 'production')) { process.env.NODE_ENV === 'production')) {
msgs = [chalk.red('\nERROR:', err), '\n']; msgs = [chalk.red(i18n.t('errors.errors.error'), err), '\n'];
if (context) { if (context) {
msgs.push(chalk.white(context), '\n'); msgs.push(chalk.white(context), '\n');
@ -199,7 +200,7 @@ errors = {
statusCode = errorItem.code || 500; statusCode = errorItem.code || 500;
errorContent.message = _.isString(errorItem) ? errorItem : 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'; errorContent.errorType = errorItem.errorType || 'InternalServerError';
errors.push(errorContent); errors.push(errorContent);
}); });
@ -210,7 +211,7 @@ errors = {
formatAndRejectAPIError: function (error, permsMessage) { formatAndRejectAPIError: function (error, permsMessage) {
if (!error) { if (!error) {
return this.rejectError( 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); return res.status(code).send(html);
} }
// There was an error trying to render the error page, output the error // 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... // And then try to explain things to the user...
// Cheat and output the error using handlebars escapeExpression // Cheat and output the error using handlebars escapeExpression
return res.status(500).send( return res.status(500).send(
'<h1>Oops, seems there is an error in the error template.</h1>' + '<h1>' + i18n.t('errors.errors.oopsErrorTemplateHasError') + '</h1>' +
'<p>Encountered the error: </p>' + '<p>' + i18n.t('errors.errors.encounteredError') + '</p>' +
'<pre>' + hbs.handlebars.Utils.escapeExpression(templateErr.message || templateErr) + '</pre>' + '<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>' code + ' ' + '<pre>' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '</pre>'
); );
}); });
} }
if (code >= 500) { 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 // Are we admin? If so, don't worry about the user template
@ -321,7 +322,7 @@ errors = {
}, },
error404: function (req, res, next) { error404: function (req, res, next) {
var message = 'Page not found'; var message = i18n.t('errors.errors.pageNotFound');
// do not cache 404 error // 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'}); 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; statusCode = errorItem.code || 500;
errorContent.message = _.isString(errorItem) ? errorItem : 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'; errorContent.errorType = errorItem.errorType || 'InternalServerError';
returnErrors.push(errorContent); returnErrors.push(errorContent);
}); });

View File

@ -4,7 +4,8 @@ var Promise = require('bluebird'),
chalk = require('chalk'), chalk = require('chalk'),
fs = require('fs'), fs = require('fs'),
errors = require('./errors'), errors = require('./errors'),
config = require('./config'); config = require('./config'),
i18n = require('./i18n');
/** /**
* ## GhostServer * ## GhostServer
@ -59,15 +60,15 @@ GhostServer.prototype.start = function (externalApp) {
self.httpServer.on('error', function (error) { self.httpServer.on('error', function (error) {
if (error.errno === 'EADDRINUSE') { if (error.errno === 'EADDRINUSE') {
errors.logError( errors.logError(
'(EADDRINUSE) Cannot start Ghost.', i18n.t('errors.httpServer.addressInUse.error'),
'Port ' + config.server.port + ' is already in use by another program.', i18n.t('errors.httpServer.addressInUse.context', {port: config.server.port}),
'Is another Ghost instance already running?' i18n.t('errors.httpServer.addressInUse.help')
); );
} else { } else {
errors.logError( errors.logError(
'(Code: ' + error.errno + ')', i18n.t('errors.httpServer.otherError.error', {errorNumber: error.errno}),
'There was an error starting your server.', i18n.t('errors.httpServer.otherError.context'),
'Please use the error code above to search for a solution.' i18n.t('errors.httpServer.otherError.help')
); );
} }
process.exit(-1); process.exit(-1);
@ -118,7 +119,7 @@ GhostServer.prototype.restart = function () {
* To be called after `stop` * To be called after `stop`
*/ */
GhostServer.prototype.hammertime = function () { 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); return Promise.resolve(this);
}; };
@ -166,33 +167,31 @@ GhostServer.prototype.logStartMessages = function () {
// Startup & Shutdown messages // Startup & Shutdown messages
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
console.log( console.log(
chalk.green('Ghost is running in ' + process.env.NODE_ENV + '...'), chalk.green(i18n.t('notices.httpServer.ghostIsRunningIn', {env: process.env.NODE_ENV})),
'\nYour blog is now available on', i18n.t('notices.httpServer.yourBlogIsAvailableOn', {url: config.url}),
config.url, chalk.gray(i18n.t('notices.httpServer.ctrlCToShutDown'))
chalk.gray('\nCtrl+C to shut down')
); );
} else { } else {
console.log( console.log(
chalk.green('Ghost is running in ' + process.env.NODE_ENV + '...'), chalk.green(i18n.t('notices.httpServer.ghostIsRunningIn', {env: process.env.NODE_ENV})),
'\nListening on', i18n.t('notices.httpServer.listeningOn'),
config.getSocket() || config.server.host + ':' + config.server.port, config.getSocket() || config.server.host + ':' + config.server.port,
'\nUrl configured as:', i18n.t('notices.httpServer.urlConfiguredAs', {url: config.url}),
config.url, chalk.gray(i18n.t('notices.httpServer.ctrlCToShutDown'))
chalk.gray('\nCtrl+C to shut down')
); );
} }
function shutdown() { 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') { if (process.env.NODE_ENV === 'production') {
console.log( console.log(
'\nYour blog is now offline' i18n.t('notices.httpServer.yourBlogIsNowOffline')
); );
} else { } else {
console.log( console.log(
'\nGhost was running for', i18n.t('notices.httpServer.ghostWasRunningFor'),
Math.round(process.uptime()), Math.round(process.uptime()),
'seconds' i18n.t('common.time.seconds')
); );
} }
process.exit(0); process.exit(0);
@ -207,7 +206,7 @@ GhostServer.prototype.logStartMessages = function () {
* ### Log Shutdown Messages * ### Log Shutdown Messages
*/ */
GhostServer.prototype.logShutdownMessages = function () { GhostServer.prototype.logShutdownMessages = function () {
console.log(chalk.red('Ghost is closing connections')); console.log(chalk.red(i18n.t('notices.httpServer.ghostIsClosingConnections')));
}; };
module.exports = GhostServer; module.exports = GhostServer;

View File

@ -5,13 +5,14 @@
var hbs = require('express-hbs'), var hbs = require('express-hbs'),
_ = require('lodash'), _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
i18n = require('../i18n'),
hbsUtils = hbs.handlebars.Utils, hbsUtils = hbs.handlebars.Utils,
foreach; foreach;
foreach = function (context, options) { foreach = function (context, options) {
if (!options) { if (!options) {
errors.logWarn('Need to pass an iterator to #foreach'); errors.logWarn(i18n.t('warnings.helpers.foreach.iteratorNeeded'));
} }
var fn = options.fn, var fn = options.fn,

View File

@ -8,6 +8,7 @@ var _ = require('lodash'),
api = require('../api'), api = require('../api'),
jsonpath = require('jsonpath'), jsonpath = require('jsonpath'),
labs = require('../utils/labs'), labs = require('../utils/labs'),
i18n = require('../i18n'),
resources, resources,
pathAliases, pathAliases,
get; get;
@ -99,13 +100,13 @@ get = function get(context, options) {
apiMethod; apiMethod;
if (!options.fn) { 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); errors.logWarn(data.error);
return Promise.resolve(); return Promise.resolve();
} }
if (!_.contains(resources, context)) { if (!_.contains(resources, context)) {
data.error = 'Invalid resource given to get helper'; data.error = i18n.t('warnings.helpers.get.invalidResource');
errors.logWarn(data.error); errors.logWarn(data.error);
return Promise.resolve(options.inverse(self, {data: data})); return Promise.resolve(options.inverse(self, {data: data}));
} }
@ -145,9 +146,9 @@ get = function get(context, options) {
module.exports = function getWithLabs(context, options) { module.exports = function getWithLabs(context, options) {
var self = this, var self = this,
errorMessages = [ errorMessages = [
'The {{get}} helper is not available.', i18n.t('warnings.helpers.get.helperNotAvailable'),
'Public API access must be enabled if you wish to use the {{get}} helper.', i18n.t('warnings.helpers.get.apiMustBeEnabled'),
'See http://support.ghost.org/public-api-beta' i18n.t('warnings.helpers.get.seeLink', {url: 'http://support.ghost.org/public-api-beta'})
]; ];
if (labs.isSet('publicAPI') === true) { if (labs.isSet('publicAPI') === true) {

View File

@ -5,6 +5,7 @@
var _ = require('lodash'), var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
i18n = require('../i18n'),
has; has;
has = function (options) { has = function (options) {
@ -40,7 +41,7 @@ has = function (options) {
} }
if (!tagList && !authorList) { if (!tagList && !authorList) {
errors.logWarn('Invalid or no attribute given to has helper'); errors.logWarn(i18n.t('warnings.helpers.has.invalidAttribute'));
return; return;
} }

View File

@ -2,6 +2,7 @@ var hbs = require('express-hbs'),
Promise = require('bluebird'), Promise = require('bluebird'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
i18n = require('../i18n'),
coreHelpers = {}, coreHelpers = {},
registerHelpers; registerHelpers;
@ -44,7 +45,7 @@ coreHelpers.helperMissing = function (arg) {
if (arguments.length === 2) { if (arguments.length === 2) {
return undefined; 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 // Register an async handlebars helper for a given handlebars instance

View File

@ -3,6 +3,7 @@
// Checks whether we're in a given context. // Checks whether we're in a given context.
var _ = require('lodash'), var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
i18n = require('../i18n'),
is; is;
is = function (context, options) { is = function (context, options) {
@ -11,7 +12,7 @@ is = function (context, options) {
var currentContext = options.data.root.context; var currentContext = options.data.root.context;
if (!_.isString(context)) { if (!_.isString(context)) {
errors.logWarn('Invalid or no attribute given to is helper'); errors.logWarn(i18n.t('warnings.helpers.is.invalidAttribute'));
return; return;
} }

View File

@ -4,6 +4,7 @@
var _ = require('lodash'), var _ = require('lodash'),
hbs = require('express-hbs'), hbs = require('express-hbs'),
i18n = require('../i18n'),
errors = require('../errors'), errors = require('../errors'),
template = require('./template'), template = require('./template'),
@ -17,13 +18,13 @@ navigation = function (options) {
context; context;
if (!_.isObject(navigationData) || _.isFunction(navigationData)) { 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) { if (navigationData.filter(function (e) {
return (_.isUndefined(e.label) || _.isUndefined(e.url)); return (_.isUndefined(e.label) || _.isUndefined(e.url));
}).length > 0) { }).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 // check for non-null string values
@ -31,7 +32,7 @@ navigation = function (options) {
return ((!_.isNull(e.label) && !_.isString(e.label)) || return ((!_.isNull(e.label) && !_.isString(e.label)) ||
(!_.isNull(e.url) && !_.isString(e.url))); (!_.isNull(e.url) && !_.isString(e.url)));
}).length > 0) { }).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) { function _slugify(label) {

View File

@ -11,6 +11,7 @@
var config = require('../config'), var config = require('../config'),
errors = require('../errors'), errors = require('../errors'),
i18n = require('../i18n'),
page_url, page_url,
pageUrl; pageUrl;
@ -44,9 +45,7 @@ page_url = function (context, block) {
// context. This helper is deprecated and will be removed in future versions. // context. This helper is deprecated and will be removed in future versions.
// //
pageUrl = function (context, block) { pageUrl = function (context, block) {
errors.logWarn('Warning: pageUrl is deprecated, please use page_url instead\n' + errors.logWarn(i18n.t('warnings.helpers.page_url.isDeprecated'));
'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');
/*jshint unused:false*/ /*jshint unused:false*/
var self = this; var self = this;

View File

@ -5,27 +5,28 @@
var _ = require('lodash'), var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
template = require('./template'), template = require('./template'),
i18n = require('../i18n'),
pagination; pagination;
pagination = function (options) { pagination = function (options) {
/*jshint unused:false*/ /*jshint unused:false*/
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) { 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) || if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages) ||
_.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) { _.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)) || if ((!_.isNull(this.pagination.next) && !_.isNumber(this.pagination.next)) ||
(!_.isNull(this.pagination.prev) && !_.isNumber(this.pagination.prev))) { (!_.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) || if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages) ||
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) { !_.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); var context = _.merge({}, this.pagination);

View File

@ -11,12 +11,13 @@
var hbs = require('express-hbs'), var hbs = require('express-hbs'),
errors = require('../errors'), errors = require('../errors'),
_ = require('lodash'), _ = require('lodash'),
i18n = require('../i18n'),
plural; plural;
plural = function (context, options) { plural = function (context, options) {
if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) || if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) ||
_.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) { _.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) { if (context === 0) {

View File

@ -1,6 +1,7 @@
var templates = {}, var templates = {},
hbs = require('express-hbs'), hbs = require('express-hbs'),
errors = require('../errors'); errors = require('../errors'),
i18n = require('../i18n');
// ## Template utils // ## Template utils
@ -10,7 +11,7 @@ templates.execute = function (name, context, options) {
var partial = hbs.handlebars.partials[name]; var partial = hbs.handlebars.partials[name];
if (partial === undefined) { if (partial === undefined) {
errors.logAndThrowError('Template ' + name + ' not found.'); errors.logAndThrowError(i18n.t('warnings.helpers.template.templateNotFound', {name: name}));
return; return;
} }

View File

@ -5,7 +5,6 @@ var supportedLocales = ['en'],
fs = require('fs'), fs = require('fs'),
chalk = require('chalk'), chalk = require('chalk'),
MessageFormat = require('intl-messageformat'), MessageFormat = require('intl-messageformat'),
errors = require('./errors'),
// TODO: fetch this dynamically based on overall blog settings (`key = "defaultLang"` in the `settings` table // TODO: fetch this dynamically based on overall blog settings (`key = "defaultLang"` in the `settings` table
currentLocale = 'en', currentLocale = 'en',
@ -51,13 +50,16 @@ I18n = {
*/ */
findString: function findString(msgPath) { findString: function findString(msgPath) {
var matchingString, path; var matchingString, path;
// no path? no string // no path? no string
if (_.isEmpty(msgPath) || !_.isString(msgPath)) { if (_.isEmpty(msgPath) || !_.isString(msgPath)) {
chalk.yellow('i18n:t() - received an empty path.'); chalk.yellow('i18n:t() - received an empty path.');
return ''; return '';
} }
if (blos === undefined) {
I18n.init();
}
matchingString = blos; matchingString = blos;
path = msgPath.split('.'); path = msgPath.split('.');
@ -67,7 +69,7 @@ I18n = {
}); });
if (_.isNull(matchingString)) { 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.'; matchingString = 'i18n error: path "' + msgPath + '" was not found.';
} }
@ -83,7 +85,6 @@ I18n = {
// read file for current locale and keep its content in memory // read file for current locale and keep its content in memory
blos = fs.readFileSync(__dirname + '/translations/' + currentLocale + '.json'); blos = fs.readFileSync(__dirname + '/translations/' + currentLocale + '.json');
blos = JSON.parse(blos); blos = JSON.parse(blos);
if (global.Intl) { if (global.Intl) {
// Determine if the built-in `Intl` has the locale data we need. // Determine if the built-in `Intl` has the locale data we need.
var hasBuiltInLocaleData, var hasBuiltInLocaleData,

View File

@ -64,9 +64,8 @@ function builtFilesExist() {
} }
function checkExist(fileName) { function checkExist(fileName) {
var errorMessage = 'Javascript files have not been built.', var errorMessage = i18n.t('errors.index.javascriptFilesNotBuilt.error'),
errorHelp = '\nPlease read the getting started instructions at:' + errorHelp = i18n.t('errors.index.javascriptFilesNotBuilt.help', {link: '\nhttps://github.com/TryGhost/Ghost#getting-started'});
'\nhttps://github.com/TryGhost/Ghost#getting-started';
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
fs.stat(fileName, function (statErr) { fs.stat(fileName, function (statErr) {
@ -102,9 +101,9 @@ function initNotifications() {
api.notifications.add({notifications: [{ api.notifications.add({notifications: [{
type: 'info', type: 'info',
message: [ message: [
'Ghost is attempting to use a direct method to send email.', i18n.t('warnings.index.usingDirectMethodToSendEmail'),
'It is recommended that you explicitly configure an email service.', i18n.t('common.seeLinkForInstructions',
'See <a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a> for instructions' {link: '<a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a>'})
].join(' ') ].join(' ')
}]}, {context: {internal: true}}); }]}, {context: {internal: true}});
} }
@ -112,8 +111,9 @@ function initNotifications() {
api.notifications.add({notifications: [{ api.notifications.add({notifications: [{
type: 'warn', type: 'warn',
message: [ message: [
'Ghost is currently unable to send email.', i18n.t('warnings.index.unableToSendEmail'),
'See <a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a> for instructions' i18n.t('common.seeLinkForInstructions',
{link: '<a href=\'http://support.ghost.org/mail\' target=\'_blank\'>http://support.ghost.org/mail</a>'})
].join(' ') ].join(' ')
}]}, {context: {internal: true}}); }]}, {context: {internal: true}});
} }
@ -132,6 +132,9 @@ function init(options) {
// It returns a promise that is resolved when the application // It returns a promise that is resolved when the application
// has finished starting up. // has finished starting up.
// Initialize Internationalization
i18n.init();
// Load our config.js file from the local file system. // Load our config.js file from the local file system.
return config.load(options.config).then(function () { return config.load(options.config).then(function () {
return config.checkDeprecated(); return config.checkDeprecated();
@ -170,9 +173,6 @@ function init(options) {
}).then(function () { }).then(function () {
var adminHbs = hbs.create(); var adminHbs = hbs.create();
// Initialize Internationalization
i18n.init();
// Output necessary notifications on init // Output necessary notifications on init
initNotifications(); initNotifications();
// ##Configuration // ##Configuration

View File

@ -4,7 +4,8 @@ var _ = require('lodash'),
Promise = require('bluebird'), Promise = require('bluebird'),
nodemailer = require('nodemailer'), nodemailer = require('nodemailer'),
validator = require('validator'), validator = require('validator'),
config = require('../config'); config = require('../config'),
i18n = require('../i18n');
function GhostMailer(opts) { function GhostMailer(opts) {
opts = 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 we do have a from address, and it's just an email
if (validator.isEmail(from)) { if (validator.isEmail(from)) {
if (!config.theme.title) { 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 + '>'; from = '"' + config.theme.title + '" <' + from + '>';
} }
@ -68,10 +69,10 @@ GhostMailer.prototype.send = function (message) {
to = message.to || false; to = message.to || false;
if (!this.transport) { 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)) { 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)); sendMail = Promise.promisify(self.transport.sendMail.bind(self.transport));
@ -93,27 +94,27 @@ GhostMailer.prototype.send = function (message) {
} }
response.statusHandler.once('failed', function (data) { 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') { 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 += '.'; reason += '.';
return reject(new Error(reason)); return reject(new Error(reason));
}); });
response.statusHandler.once('requeue', function (data) { 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) { 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)); return reject(new Error(errorMessage));
}); });
response.statusHandler.once('sent', function () { response.statusHandler.once('sent', function () {
return resolve('Message sent. Double check inbox and spam folder!'); return resolve(i18n.t('notices.mail.messageSent'));
}); });
}); });
}); });

View File

@ -5,6 +5,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
config = require('../config'), config = require('../config'),
labs = require('../utils/labs'), labs = require('../utils/labs'),
i18n = require('../i18n'),
auth; auth;
@ -84,11 +85,11 @@ auth = {
if (!req.body.client_id || !req.body.client_secret) { if (!req.body.client_id || !req.body.client_secret) {
errors.logError( errors.logError(
'Client Authentication Failed', i18n.t('errors.middleware.auth.clientAuthenticaionFailed'),
'Client credentials were not provided', i18n.t('errors.middleware.auth.clientCredentialsNotProvided'),
'For information on how to fix this, please read http://api.ghost.org/docs/client-authentication' 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}, return passport.authenticate(['oauth2-client-password'], {session: false, failWithError: false},
@ -109,11 +110,11 @@ auth = {
if (!client || client.type !== 'ua') { if (!client || client.type !== 'ua') {
errors.logError( errors.logError(
'Client Authentication Failed', i18n.t('errors.middleware.auth.clientAuthenticaionFailed'),
'Client credentials were not valid', i18n.t('errors.middleware.auth.clientCredentialsNotValid'),
'For information on how to fix this, please read http://api.ghost.org/docs/client-authentication' 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') { if (!origin && client && client.type === 'ua') {
@ -127,10 +128,10 @@ auth = {
req.client = client; req.client = client;
return next(null, client); return next(null, client);
} else { } 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, errors.logError(error,
'You have attempted to access your Ghost admin panel from a url that does not appear in config.js.', i18n.t('errors.middleware.auth.attemptedToAccessAdmin'),
'For information on how to fix this, please read http://support.ghost.org/config/#url.' i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://support.ghost.org/config/#url'})
); );
return errors.handleAPIError(error, req, res, next); return errors.handleAPIError(error, req, res, next);
} }
@ -151,12 +152,12 @@ auth = {
req.user = user; req.user = user;
return next(null, user, info); return next(null, user, info);
} else if (isBearerAutorizationHeader(req)) { } 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) { } else if (req.client) {
return next(); 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); )(req, res, next);
}, },
@ -167,7 +168,7 @@ auth = {
if (req.user) { if (req.user) {
return next(); return next();
} else { } 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) { if (req.user) {
return next(); return next();
} else { } 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);
} }
} }
} }

View File

@ -2,6 +2,7 @@ var BusBoy = require('busboy'),
fs = require('fs-extra'), fs = require('fs-extra'),
path = require('path'), path = require('path'),
os = require('os'), os = require('os'),
i18n = require('../i18n'),
crypto = require('crypto'); crypto = require('crypto');
// ### ghostBusboy // ### ghostBusboy
@ -49,21 +50,21 @@ function ghostBusBoy(req, res, next) {
}); });
file.on('error', function onError(error) { 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 = fs.createWriteStream(filePath);
stream.on('error', function onError(error) { 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); file.pipe(stream);
}); });
busboy.on('error', function onError(error) { busboy.on('error', function onError(error) {
console.log('Error', 'Something went wrong parsing the form', error); console.log('Error', i18n.t('errors.middleware.ghostbusboy.somethingWentWrong'), error);
res.status(500).send({code: 500, message: 'Could not parse upload completely.'}); res.status(500).send({code: 500, message: i18n.t('errors.middleware.ghostbusboy.couldNotParseUpload')});
}); });
busboy.on('field', function onField(fieldname, val) { busboy.on('field', function onField(fieldname, val) {

View File

@ -3,6 +3,7 @@ var oauth2orize = require('oauth2orize'),
utils = require('../utils'), utils = require('../utils'),
errors = require('../errors'), errors = require('../errors'),
spamPrevention = require('./spam-prevention'), spamPrevention = require('./spam-prevention'),
i18n = require('../i18n'),
oauthServer, oauthServer,
oauth; oauth;
@ -10,7 +11,7 @@ var oauth2orize = require('oauth2orize'),
function exchangeRefreshToken(client, refreshToken, scope, done) { function exchangeRefreshToken(client, refreshToken, scope, done) {
models.Refreshtoken.findOne({token: refreshToken}).then(function then(model) { models.Refreshtoken.findOne({token: refreshToken}).then(function then(model) {
if (!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 { } else {
var token = model.toJSON(), var token = model.toJSON(),
accessToken = utils.uid(256), accessToken = utils.uid(256),
@ -31,7 +32,7 @@ function exchangeRefreshToken(client, refreshToken, scope, done) {
return done(error, false); return done(error, false);
}); });
} else { } 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 // Validate the client
models.Client.findOne({slug: client.slug}).then(function then(client) { models.Client.findOne({slug: client.slug}).then(function then(client) {
if (!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 // Validate the user
return models.User.check({email: username, password: password}).then(function then(user) { return models.User.check({email: username, password: password}).then(function then(user) {

View File

@ -8,6 +8,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
session = require('cookie-session'), session = require('cookie-session'),
utils = require('../utils'), utils = require('../utils'),
i18n = require('../i18n'),
privateBlogging; privateBlogging;
function verifySessionHash(salt, hash) { function verifySessionHash(salt, hash) {
@ -125,7 +126,7 @@ privateBlogging = {
return res.redirect(config.urlFor({relativeUrl: decodeURIComponent(forward)})); return res.redirect(config.urlFor({relativeUrl: decodeURIComponent(forward)}));
} else { } else {
res.error = { res.error = {
message: 'Wrong password' message: i18n.t('errors.middleware.privateblogging.wrongPassword')
}; };
return next(); return next();
} }

View File

@ -9,6 +9,7 @@
var _ = require('lodash'), var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
config = require('../config'), config = require('../config'),
i18n = require('../i18n'),
loginSecurity = [], loginSecurity = [],
forgottenSecurity = [], forgottenSecurity = [],
protectedSecurity = [], protectedSecurity = [],
@ -22,7 +23,7 @@ spamPrevention = {
remoteAddress = req.connection.remoteAddress, remoteAddress = req.connection.remoteAddress,
deniedRateLimit = '', deniedRateLimit = '',
ipCount = '', ipCount = '',
message = 'Too many attempts.', message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
rateSigninPeriod = config.rateSigninPeriod || 3600, rateSigninPeriod = config.rateSigninPeriod || 3600,
rateSigninAttempts = config.rateSigninAttempts || 10; rateSigninAttempts = config.rateSigninAttempts || 10;
@ -31,7 +32,7 @@ spamPrevention = {
} else if (req.body.grant_type === 'refresh_token') { } else if (req.body.grant_type === 'refresh_token') {
return next(); return next();
} else { } 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 // filter entries that are older than rateSigninPeriod
@ -45,10 +46,10 @@ spamPrevention = {
if (deniedRateLimit) { if (deniedRateLimit) {
errors.logError( errors.logError(
'Only ' + rateSigninAttempts + ' tries per IP address every ' + rateSigninPeriod + ' seconds.', i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', {rateSigninAttempts: rateSigninAttempts, rateSigninPeriod: rateSigninPeriod}),
'Too many login attempts.' 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)); return next(new errors.TooManyRequestsError(message));
} }
next(); next();
@ -65,7 +66,7 @@ spamPrevention = {
ipCount = '', ipCount = '',
deniedRateLimit = '', deniedRateLimit = '',
deniedEmailRateLimit = '', deniedEmailRateLimit = '',
message = 'Too many attempts.', message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
index = _.findIndex(forgottenSecurity, function findIndex(logTime) { index = _.findIndex(forgottenSecurity, function findIndex(logTime) {
return (logTime.ip === remoteAddress && logTime.email === email); return (logTime.ip === remoteAddress && logTime.email === email);
}); });
@ -77,7 +78,7 @@ spamPrevention = {
forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0}); forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0});
} }
} else { } 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 // filter entries that are older than rateForgottenPeriod
@ -95,21 +96,20 @@ spamPrevention = {
if (deniedEmailRateLimit) { if (deniedEmailRateLimit) {
errors.logError( errors.logError(
'Only ' + rateForgottenAttempts + ' forgotten password attempts per email every ' + i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
rateForgottenPeriod + ' seconds.', i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
'Forgotten password reset attempt failed'
); );
} }
if (deniedRateLimit) { if (deniedRateLimit) {
errors.logError( errors.logError(
'Only ' + rateForgottenAttempts + ' tries per IP address every ' + rateForgottenPeriod + ' seconds.', i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
'Forgotten password reset attempt failed' i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
); );
} }
if (deniedEmailRateLimit || deniedRateLimit) { 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)); return next(new errors.TooManyRequestsError(message));
} }
@ -122,7 +122,7 @@ spamPrevention = {
rateProtectedPeriod = config.rateProtectedPeriod || 3600, rateProtectedPeriod = config.rateProtectedPeriod || 3600,
rateProtectedAttempts = config.rateProtectedAttempts || 10, rateProtectedAttempts = config.rateProtectedAttempts || 10,
ipCount = '', ipCount = '',
message = 'Too many attempts.', message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
deniedRateLimit = '', deniedRateLimit = '',
password = req.body.password; password = req.body.password;
@ -130,7 +130,7 @@ spamPrevention = {
protectedSecurity.push({ip: remoteAddress, time: currentTime}); protectedSecurity.push({ip: remoteAddress, time: currentTime});
} else { } else {
res.error = { res.error = {
message: 'No password entered' message: i18n.t('errors.middleware.spamprevention.noPassword')
}; };
return next(); return next();
} }
@ -145,10 +145,10 @@ spamPrevention = {
if (deniedRateLimit) { if (deniedRateLimit) {
errors.logError( errors.logError(
'Only ' + rateProtectedAttempts + ' tries per IP address every ' + rateProtectedPeriod + ' seconds.', i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod}),
'Too many login attempts.' 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 = { res.error = {
message: message message: message
}; };

View File

@ -5,6 +5,7 @@ var _ = require('lodash'),
api = require('../api'), api = require('../api'),
config = require('../config'), config = require('../config'),
errors = require('../errors'), errors = require('../errors'),
i18n = require('../i18n'),
themeHandler; themeHandler;
themeHandler = { themeHandler = {
@ -99,14 +100,14 @@ themeHandler = {
if (!config.paths.availableThemes.hasOwnProperty(activeTheme.value)) { if (!config.paths.availableThemes.hasOwnProperty(activeTheme.value)) {
if (!res.isAdmin) { if (!res.isAdmin) {
// Throw an error if the theme is not available, but not on the admin UI // 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 { } else {
// At this point the activated theme is not present and the current // 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 // 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 // to the admin client we set an hbs instance on the app so that middleware
// processing can continue. // processing can continue.
blogApp.engine('hbs', hbs.express3()); 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(); return next();
} }

View File

@ -18,6 +18,7 @@ var _ = require('lodash'),
uuid = require('node-uuid'), uuid = require('node-uuid'),
validation = require('../../data/validation'), validation = require('../../data/validation'),
plugins = require('../plugins'), plugins = require('../plugins'),
i18n = require('../../i18n'),
ghostBookshelf, ghostBookshelf,
proto; proto;
@ -139,7 +140,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
} else if (options.context && options.context.internal) { } else if (options.context && options.context.internal) {
return 1; return 1;
} else { } else {
errors.logAndThrowError(new Error('missing context')); errors.logAndThrowError(new Error(i18n.t('errors.models.base.index.missingContext')));
} }
}, },

View File

@ -1,6 +1,7 @@
var Promise = require('bluebird'), var Promise = require('bluebird'),
ghostBookshelf = require('./index'), ghostBookshelf = require('./index'),
errors = require('../../errors'), errors = require('../../errors'),
i18n = require('../../i18n'),
Basetoken; 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')));
} }
}); });

View File

@ -1,6 +1,7 @@
var _ = require('lodash'), var _ = require('lodash'),
errors = require('../../errors'), errors = require('../../errors'),
gql = require('ghost-gql'), gql = require('ghost-gql'),
i18n = require('../../i18n'),
filter, filter,
filterUtils; filterUtils;
@ -26,8 +27,8 @@ filterUtils = {
} catch (error) { } catch (error) {
errors.logAndThrowError( errors.logAndThrowError(
new errors.ValidationError(error.message, 'filter'), new errors.ValidationError(error.message, 'filter'),
'Error parsing filter', i18n.t('errors.models.plugins.filter.errorParsing'),
'For more information on how to use filter, see http://api.ghost.org/docs/filter' i18n.t('errors.models.plugins.filter.forInformationRead', {url: 'http://api.ghost.org/docs/filter'})
); );
} }

View File

@ -10,6 +10,7 @@ var _ = require('lodash'),
events = require('../events'), events = require('../events'),
config = require('../config'), config = require('../config'),
baseUtils = require('./base/utils'), baseUtils = require('./base/utils'),
i18n = require('../i18n'),
Post, Post,
Posts; Posts;
@ -116,7 +117,7 @@ Post = ghostBookshelf.Model.extend({
// disabling sanitization until we can implement a better version // disabling sanitization until we can implement a better version
// this.set('title', this.sanitize('title').trim()); // 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()); this.set('title', title.trim());
// ### Business logic for published_at and published_by // ### Business logic for published_at and published_by
@ -255,11 +256,11 @@ Post = ghostBookshelf.Model.extend({
}).catch(function failure(error) { }).catch(function failure(error) {
errors.logError( errors.logError(
error, error,
'Unable to save tags.', i18n.t('errors.models.post.tagUpdates.error'),
'Your post was saved, but your tags were not updated.' i18n.t('errors.models.post.tagUpdates.help')
); );
return Promise.reject(new errors.InternalServerError( 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.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) { permissible: function permissible(postModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) {
@ -586,7 +587,7 @@ Post = ghostBookshelf.Model.extend({
return Promise.resolve(); 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')));
} }
}); });

View File

@ -2,6 +2,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
ghostBookshelf = require('./base'), ghostBookshelf = require('./base'),
Promise = require('bluebird'), Promise = require('bluebird'),
i18n = require('../i18n'),
Role, Role,
Roles; Roles;
@ -75,7 +76,7 @@ Role = ghostBookshelf.Model.extend({
return Promise.resolve(); 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')));
} }
}); });

View File

@ -7,6 +7,7 @@ var Settings,
validation = require('../data/validation'), validation = require('../data/validation'),
events = require('../events'), events = require('../events'),
internal = {context: {internal: true}}, internal = {context: {internal: true}},
i18n = require('../i18n'),
defaultSettings; defaultSettings;
@ -119,7 +120,7 @@ Settings = ghostBookshelf.Model.extend({
// Accept an array of models as input // Accept an array of models as input
if (item.toJSON) { item = item.toJSON(); } if (item.toJSON) { item = item.toJSON(); }
if (!(_.isString(item.key) && item.key.length > 0)) { 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); item = self.filterData(item);
@ -138,14 +139,14 @@ Settings = ghostBookshelf.Model.extend({
return setting.save(saveData, options); 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); }, errors.logAndThrowError);
}); });
}, },
populateDefault: function (key) { populateDefault: function (key) {
if (!getDefaultSettings()[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) { return this.findOne({key: key}).then(function then(foundSetting) {

View File

@ -10,6 +10,7 @@ var _ = require('lodash'),
validation = require('../data/validation'), validation = require('../data/validation'),
config = require('../config'), config = require('../config'),
events = require('../events'), events = require('../events'),
i18n = require('../i18n'),
bcryptGenSalt = Promise.promisify(bcrypt.genSalt), bcryptGenSalt = Promise.promisify(bcrypt.genSalt),
bcryptHash = Promise.promisify(bcrypt.hash), bcryptHash = Promise.promisify(bcrypt.hash),
@ -115,7 +116,7 @@ User = ghostBookshelf.Model.extend({
} else if (this.get('id')) { } else if (this.get('id')) {
return this.get('id'); return this.get('id');
} else { } 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) { if (data.roles && data.roles.length > 1) {
return Promise.reject( 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) { }).then(function then(roleToAssign) {
if (roleToAssign && roleToAssign.get('name') === 'Owner') { if (roleToAssign && roleToAssign.get('name') === 'Owner') {
return Promise.reject( 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 { } else {
// assign all other roles // assign all other roles
@ -359,11 +360,11 @@ User = ghostBookshelf.Model.extend({
// check for too many roles // check for too many roles
if (data.roles && data.roles.length > 1) { 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)) { 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() { function getAuthorRole() {
@ -411,7 +412,7 @@ User = ghostBookshelf.Model.extend({
userData = this.filterData(data); userData = this.filterData(data);
if (!validatePasswordLength(userData.password)) { 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'); options = this.filterOptions(options, 'setup');
@ -478,7 +479,7 @@ User = ghostBookshelf.Model.extend({
if (action === 'destroy') { if (action === 'destroy') {
// Owner cannot be deleted EVER // Owner cannot be deleted EVER
if (loadedPermissions.user && userModel.hasRole('Owner')) { 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' // 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.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) { setWarning: function setWarning(user, options) {
@ -525,20 +526,19 @@ User = ghostBookshelf.Model.extend({
s; s;
return this.getByEmail(object.email).then(function then(user) { return this.getByEmail(object.email).then(function then(user) {
if (!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' || if (user.get('status') === 'invited' || user.get('status') === 'invited-pending' ||
user.get('status') === 'inactive' 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') { if (user.get('status') !== 'locked') {
return bcryptCompare(object.password, user.get('password')).then(function then(matched) { return bcryptCompare(object.password, user.get('password')).then(function then(matched) {
if (!matched) { if (!matched) {
return Promise.resolve(self.setWarning(user, {validate: false})).then(function then(remaining) { return Promise.resolve(self.setWarning(user, {validate: false})).then(function then(remaining) {
s = (remaining > 1) ? 's' : ''; s = (remaining > 1) ? 's' : '';
return Promise.reject(new errors.UnauthorizedError('Your password is incorrect. <br />' + return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPasswordAttempts', {remaining: remaining, s: s})));
remaining + ' attempt' + s + ' remaining!'));
// Use comma structure, not .catch, because we don't want to catch incorrect passwords // Use comma structure, not .catch, because we don't want to catch incorrect passwords
}, function handleError(error) { }, 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. // cause a login error because of it. The user validation is not important here.
errors.logError( errors.logError(
error, error,
'Error thrown from user update during login', i18n.t('errors.models.user.userUpdateError.context'),
'Visit and save your profile after logging in to check for problems.' 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. // cause a login error because of it. The user validation is not important here.
errors.logError( errors.logError(
error, error,
'Error thrown from user update during login', i18n.t('errors.models.user.userUpdateError.context'),
'Visit and save your profile after logging in to check for problems.' i18n.t('errors.models.user.userUpdateError.help')
); );
return user; return user;
}); });
}, errors.logAndThrowError); }, errors.logAndThrowError);
} }
return Promise.reject(new errors.NoPermissionError('Your account is locked. Please reset your password ' + return Promise.reject(new errors.NoPermissionError(
'to log in again by clicking the "Forgotten password?" link!')); i18n.t('errors.models.user.accountLocked')));
}, function handleError(error) { }, function handleError(error) {
if (error.message === 'NotFound' || error.message === 'EmptyResponse') { 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); return Promise.reject(error);
@ -591,15 +591,15 @@ User = ghostBookshelf.Model.extend({
user; user;
if (newPassword !== ne2Password) { 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)) { 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)) { 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) { return self.forge({id: userId}).fetch({require: true}).then(function then(_user) {
@ -611,7 +611,7 @@ User = ghostBookshelf.Model.extend({
return true; return true;
}).then(function then(matched) { }).then(function then(matched) {
if (!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); return generatePasswordHash(newPassword);
@ -623,7 +623,7 @@ User = ghostBookshelf.Model.extend({
generateResetToken: function generateResetToken(email, expires, dbHash) { generateResetToken: function generateResetToken(email, expires, dbHash) {
return this.getByEmail(email).then(function then(foundUser) { return this.getByEmail(email).then(function then(foundUser) {
if (!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'), var hash = crypto.createHash('sha256'),
@ -653,25 +653,25 @@ User = ghostBookshelf.Model.extend({
// Check if invalid structure // Check if invalid structure
if (!parts || parts.length !== 3) { 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); expires = parseInt(parts[0], 10);
email = parts[1]; email = parts[1];
if (isNaN(expires)) { 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 // Check if token is expired to prevent replay attacks
if (expires < Date.now()) { 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 // to prevent brute force attempts to reset the password the combination of email+expires is only allowed for
// 10 attempts // 10 attempts
if (tokenSecurity[email + '+' + expires] && tokenSecurity[email + '+' + expires].count >= 10) { 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) { return this.generateResetToken(email, expires, dbHash).then(function then(generatedToken) {
@ -696,7 +696,7 @@ User = ghostBookshelf.Model.extend({
tokenSecurity[email + '+' + expires] = { tokenSecurity[email + '+' + expires] = {
count: tokenSecurity[email + '+' + expires] ? tokenSecurity[email + '+' + expires].count + 1 : 1 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; dbHash = options.dbHash;
if (newPassword !== ne2Password) { 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)) { 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 // Validate the token; returns the email address from token
@ -724,7 +724,7 @@ User = ghostBookshelf.Model.extend({
); );
}).then(function then(results) { }).then(function then(results) {
if (!results[0]) { 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 // Update the user with the new password hash
@ -748,7 +748,7 @@ User = ghostBookshelf.Model.extend({
// check if user has the owner role // check if user has the owner role
var currentRoles = contextUser.toJSON(options).roles; var currentRoles = contextUser.toJSON(options).roles;
if (!_.any(currentRoles, {id: ownerRole.id})) { 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'}), return Promise.join(ghostBookshelf.model('Role').findOne({name: 'Administrator'}),
@ -759,7 +759,7 @@ User = ghostBookshelf.Model.extend({
currentRoles = user.toJSON(options).roles; currentRoles = user.toJSON(options).roles;
if (!_.any(currentRoles, {id: adminRole.id})) { 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 // convert owner to admin

View File

@ -6,6 +6,7 @@ var _ = require('lodash'),
errors = require('../errors'), errors = require('../errors'),
Models = require('../models'), Models = require('../models'),
effectivePerms = require('./effective'), effectivePerms = require('./effective'),
i18n = require('../i18n'),
init, init,
refresh, refresh,
canThis, canThis,
@ -49,7 +50,7 @@ function parseContext(context) {
} }
function applyStatusRules(docName, method, opts) { 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 // Enforce status 'active' for users
if (docName === 'users') { if (docName === 'users') {
@ -193,7 +194,7 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, c
return; 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); context = parseContext(context);
if (!hasActionsMap()) { 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 // Kick off loading of effective user permissions if necessary

View File

@ -1,14 +1,621 @@
{ {
"common": { "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": { "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": { "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": { "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}"
}
}
}
} }
} }

View File

@ -32,7 +32,7 @@ var crypto = require('crypto'),
api = require('./api'), api = require('./api'),
config = require('./config'), config = require('./config'),
errors = require('./errors'), errors = require('./errors'),
i18n = require('./i18n'),
internal = {context: {internal: true}}, internal = {context: {internal: true}},
allowedCheckEnvironments = ['development', 'production'], allowedCheckEnvironments = ['development', 'production'],
checkEndpoint = 'updates.ghost.org', checkEndpoint = 'updates.ghost.org',
@ -46,8 +46,8 @@ function updateCheckError(error) {
errors.logError( errors.logError(
error, error,
'Checking for updates failed, your blog will continue to function.', i18n.t('errors.update-check.checkingForUpdatesFailed.error'),
'If you get this error repeatedly, please seek help on http://support.ghost.org.' i18n.t('errors.update-check.checkingForUpdatesFailed.help', {url: 'http://support.ghost.org'})
); );
} }
@ -126,7 +126,7 @@ function updateCheckRequest() {
resData = JSON.parse(resData); resData = JSON.parse(resData);
resolve(resData); resolve(resData);
} catch (e) { } catch (e) {
reject('Unable to decode update response'); reject(i18n.t('errors.update-check.unableToDecodeUpdateResponse.error'));
} }
}); });
}); });

View File

@ -4,6 +4,7 @@
var Promise = require('bluebird'), var Promise = require('bluebird'),
fs = require('fs'), fs = require('fs'),
i18n = require('../i18n'),
readFile = Promise.promisify(fs.readFile); readFile = Promise.promisify(fs.readFile);
@ -15,7 +16,7 @@ var Promise = require('bluebird'),
function parsePackageJson(path) { function parsePackageJson(path) {
return readFile(path) return readFile(path)
.catch(function () { .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; err.context = path;
return Promise.reject(err); return Promise.reject(err);
@ -29,18 +30,18 @@ function parsePackageJson(path) {
hasRequiredKeys = json.name && json.version; hasRequiredKeys = json.name && json.version;
if (!hasRequiredKeys) { 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.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 Promise.reject(err);
} }
return json; return json;
} catch (parseError) { } catch (parseError) {
err = new Error('Theme package.json file is malformed'); err = new Error(i18n.t('errors.utils.parsepackagejson.themeFileIsMalformed'));
err.context = path; 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 Promise.reject(err);
} }

View File

@ -2,6 +2,7 @@ var packages = require('../../../package.json'),
path = require('path'), path = require('path'),
crypto = require('crypto'), crypto = require('crypto'),
fs = require('fs'), fs = require('fs'),
i18n = require('../i18n'),
mode = process.env.NODE_ENV === undefined ? 'development' : process.env.NODE_ENV, mode = process.env.NODE_ENV === undefined ? 'development' : process.env.NODE_ENV,
appRoot = path.resolve(__dirname, '../../../'), appRoot = path.resolve(__dirname, '../../../'),
configFilePath = process.env.GHOST_CONFIG || path.join(appRoot, 'config.js'), configFilePath = process.env.GHOST_CONFIG || path.join(appRoot, 'config.js'),
@ -28,14 +29,16 @@ checks = {
nodeVersion: function checkNodeVersion() { nodeVersion: function checkNodeVersion() {
// Tell users if their node version is not supported, and exit // Tell users if their node version is not supported, and exit
var semver = require('semver'); var semver = require('semver');
i18n.init();
if (process.env.GHOST_NODE_VERSION_CHECK !== 'false' && if (process.env.GHOST_NODE_VERSION_CHECK !== 'false' &&
!semver.satisfies(process.versions.node, packages.engines.node) && !semver.satisfies(process.versions.node, packages.engines.node) &&
!semver.satisfies(process.versions.node, packages.engines.iojs)) { !semver.satisfies(process.versions.node, packages.engines.iojs)) {
console.error('\x1B[31mERROR: Unsupported version of Node'); console.error(i18n.t('errors.utils.startupcheck.unsupportedNodeVersion.error'));
console.error('\x1B[31mGhost needs Node version ' + packages.engines.node + console.error(i18n.t('errors.utils.startupcheck.unsupportedNodeVersion.context',
' you are using version ' + process.versions.node + '\033[0m\n'); {neededVersion: packages.engines.node, usedVersion: process.versions.node}));
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.help',
{url: 'http://support.ghost.org/supported-node-versions/'}));
process.exit(exitCodes.NODE_VERSION_UNSUPPORTED); process.exit(exitCodes.NODE_VERSION_UNSUPPORTED);
} }
@ -58,10 +61,9 @@ checks = {
config = configFile[mode]; config = configFile[mode];
if (!config) { if (!config) {
console.error('\x1B[31mERROR: Cannot find the configuration for the current NODE_ENV: ' + console.error(i18n.t('errors.utils.startupcheck.cannotFindConfigForCurrentNode.error',
process.env.NODE_ENV + '\033[0m\n'); {nodeEnv: process.env.NODE_ENV}));
console.error('\x1B[32mEnsure your config.js has a section for the current NODE_ENV value' + console.error(i18n.t('errors.utils.startupcheck.cannotFindConfigForCurrentNode.help'));
' and is formatted properly.\033[0m');
process.exit(exitCodes.NODE_ENV_CONFIG_MISSING); process.exit(exitCodes.NODE_ENV_CONFIG_MISSING);
} }
@ -89,9 +91,9 @@ checks = {
errors = errors.join('\n '); errors = errors.join('\n ');
console.error('\x1B[31mERROR: Ghost is unable to start due to missing dependencies:\033[0m\n ' + errors); console.error(i18n.t('errors.utils.startupcheck.ghostMissingDependencies.error', {error: errors}));
console.error('\x1B[32m\nPlease run `npm install --production` and try starting Ghost again.'); console.error(i18n.t('errors.utils.startupcheck.ghostMissingDependencies.explain'));
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.help', {url: 'http://support.ghost.org'}));
process.exit(exitCodes.DEPENDENCIES_MISSING); process.exit(exitCodes.DEPENDENCIES_MISSING);
}, },
@ -107,9 +109,8 @@ checks = {
contentPath, contentPath,
contentSubPaths = ['apps', 'data', 'images', 'themes'], contentSubPaths = ['apps', 'data', 'images', 'themes'],
fd, fd,
errorHeader = '\x1B[31mERROR: Unable to access Ghost\'s content path:\033[0m', errorHeader = i18n.t('errors.utils.startupcheck.unableToAccessContentPath.error'),
errorHelp = '\x1B[32mCheck that the content path exists and file system permissions are correct.' + errorHelp = i18n.t('errors.utils.startupcheck.unableToAccessContentPath.help', {url: 'http://support.ghost.org'});
'\nHelp and documentation can be found at http://support.ghost.org.\033[0m';
// Get the content path to test. If it's defined in config.js use that, if not use the default // Get the content path to test. If it's defined in config.js use that, if not use the default
try { try {
@ -202,10 +203,9 @@ checks = {
return; 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(' ' + e.message);
console.error('\n\x1B[32mCheck that the sqlite3 database file permissions allow read and write access.'); console.error(i18n.t('errors.utils.startupcheck.unableToOpenSqlite3Db.help', {url: 'http://support.ghost.org'}));
console.error('Help and documentation can be found at http://support.ghost.org.\033[0m');
process.exit(exitCodes.SQLITE_DB_NOT_WRITABLE); process.exit(exitCodes.SQLITE_DB_NOT_WRITABLE);
} }

View File

@ -4,7 +4,8 @@
var readThemes = require('./read-themes'), var readThemes = require('./read-themes'),
Promise = require('bluebird'), Promise = require('bluebird'),
_ = require('lodash'); _ = require('lodash'),
i18n = require('../i18n');
/** /**
* Validate themes: * Validate themes:
@ -27,9 +28,9 @@ function validateThemes(dir) {
if (!hasPackageJson) { if (!hasPackageJson) {
warning = { warning = {
message: 'Found a theme with no package.json file', message: i18n.t('errors.utils.validatethemes.themeWithNoPackage.message'),
context: 'Theme name: ' + name, context: i18n.t('errors.utils.validatethemes.themeWithNoPackage.context', {name: name}),
help: 'This will be required in future. Please see http://docs.ghost.org/themes/' help: i18n.t('errors.utils.validatethemes.themeWithNoPackage.help', {url: 'http://docs.ghost.org/themes/'})
}; };
result.warnings.push(warning); result.warnings.push(warning);
@ -39,9 +40,9 @@ function validateThemes(dir) {
// but JSON.parse failed (invalid json syntax) // but JSON.parse failed (invalid json syntax)
if (hasPackageJson && theme['package.json'] === null) { if (hasPackageJson && theme['package.json'] === null) {
warning = { warning = {
message: 'Found a malformed package.json', message: i18n.t('errors.utils.validatethemes.malformedPackage.message'),
context: 'Theme name: ' + name, context: i18n.t('errors.utils.validatethemes.malformedPackage.context', {name: name}),
help: 'Valid package.json will be required in future. Please see http://docs.ghost.org/themes/' help: i18n.t('errors.utils.validatethemes.malformedPackage.help', {url: 'http://docs.ghost.org/themes/'})
}; };
result.warnings.push(warning); result.warnings.push(warning);

View File

@ -4,7 +4,9 @@
// This tests using Ghost as an npm module // This tests using Ghost as an npm module
var should = require('should'), var should = require('should'),
ghost = require('../../../../core'); ghost = require('../../../../core'),
i18n = require('../../../../core/server/i18n');
i18n.init();
describe('Module', function () { describe('Module', function () {
describe('Setup', function () { describe('Setup', function () {

View File

@ -9,7 +9,9 @@ var request = require('supertest'),
should = require('should'), should = require('should'),
testUtils = require('../../utils'), testUtils = require('../../utils'),
ghost = require('../../../../core'); ghost = require('../../../../core'),
i18n = require('../../../../core/server/i18n');
i18n.init();
describe('Admin Routing', function () { describe('Admin Routing', function () {
function doEnd(done) { function doEnd(done) {

View File

@ -4,6 +4,7 @@ var testUtils = require('../../utils'),
should = require('should'), should = require('should'),
config = require('../../../server/config'), config = require('../../../server/config'),
mailer = require('../../../server/mail'), mailer = require('../../../server/mail'),
i18n = require('../../../../core/server/i18n'),
// Stuff we are testing // Stuff we are testing
MailAPI = require('../../../server/api/mail'), MailAPI = require('../../../server/api/mail'),
@ -36,6 +37,7 @@ var testUtils = require('../../utils'),
options: {} options: {}
}] }]
}; };
i18n.init();
describe('Mail API', function () { describe('Mail API', function () {
before(testUtils.teardown); before(testUtils.teardown);

View File

@ -8,12 +8,14 @@ var path = require('path'),
Promise = require('bluebird'), Promise = require('bluebird'),
helpers = require('../../server/helpers'), helpers = require('../../server/helpers'),
filters = require('../../server/filters'), filters = require('../../server/filters'),
i18n = require('../../server/i18n'),
// Stuff we are testing // Stuff we are testing
AppProxy = require('../../server/apps/proxy'), AppProxy = require('../../server/apps/proxy'),
AppSandbox = require('../../server/apps/sandbox'), AppSandbox = require('../../server/apps/sandbox'),
AppDependencies = require('../../server/apps/dependencies'), AppDependencies = require('../../server/apps/dependencies'),
AppPermissions = require('../../server/apps/permissions'); AppPermissions = require('../../server/apps/permissions');
i18n.init();
describe('Apps', function () { describe('Apps', function () {
var sandbox, var sandbox,

View File

@ -8,13 +8,14 @@ var should = require('should'),
_ = require('lodash'), _ = require('lodash'),
testUtils = require('../utils'), testUtils = require('../utils'),
i18n = require('../../server/i18n'),
// Thing we are testing // Thing we are testing
configUtils = require('../utils/configUtils'), configUtils = require('../utils/configUtils'),
config = configUtils.config, config = configUtils.config,
// storing current environment // storing current environment
currentEnv = process.env.NODE_ENV; currentEnv = process.env.NODE_ENV;
i18n.init();
// To stop jshint complaining // To stop jshint complaining
should.equal(true, true); should.equal(true, true);

View File

@ -6,8 +6,10 @@ var should = require('should'),
// Stuff we are testing // Stuff we are testing
mailer = require('../../server/mail'), mailer = require('../../server/mail'),
configUtils = require('../utils/configUtils'), configUtils = require('../utils/configUtils'),
i18n = require('../../server/i18n'),
SMTP; SMTP;
i18n.init();
// Mock SMTP config // Mock SMTP config
SMTP = { SMTP = {

View File

@ -18,7 +18,7 @@
"license": "MIT", "license": "MIT",
"main": "./core/index", "main": "./core/index",
"scripts": { "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", "start": "node index",
"test": "grunt validate --verbose" "test": "grunt validate --verbose"
}, },