From b52a7217a999ad7531f23cb5ef8d12a887f12658 Mon Sep 17 00:00:00 2001 From: Candy Date: Tue, 7 Jun 2016 10:48:28 -0400 Subject: [PATCH] Add globalization --- browser/current-context.js | 4 +- common/models/access-token.js | 4 +- common/models/acl.js | 4 +- common/models/change.js | 4 +- common/models/email.js | 6 +- common/models/user.js | 53 ++++++++------ example/client-server/client.js | 6 +- example/colors/app.js | 4 +- example/context/app.js | 6 +- example/mobile-models/app.js | 8 ++- example/simple-data-source/app.js | 4 +- index.js | 3 + intl/en/messages.json | 112 ++++++++++++++++++++++++++++++ lib/application.js | 18 ++--- lib/connectors/mail.js | 18 ++--- lib/model.js | 66 ++++++++++-------- lib/persisted-model.js | 88 ++++++++++++----------- lib/registry.js | 35 +++++----- lib/server-app.js | 4 +- package.json | 1 + 20 files changed, 306 insertions(+), 142 deletions(-) create mode 100644 intl/en/messages.json diff --git a/browser/current-context.js b/browser/current-context.js index 5a495cbd..e3d52993 100644 --- a/browser/current-context.js +++ b/browser/current-context.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + module.exports = function(loopback) { loopback.getCurrentContext = function() { return null; @@ -10,6 +12,6 @@ module.exports = function(loopback) { loopback.runInContext = loopback.createContext = function() { - throw new Error('Current context is not supported in the browser.'); + throw new Error(g.f('Current context is not supported in the browser.')); }; }; diff --git a/common/models/access-token.js b/common/models/access-token.js index f2e395d9..18578b79 100644 --- a/common/models/access-token.js +++ b/common/models/access-token.js @@ -7,6 +7,8 @@ * Module Dependencies. */ +var g = require('strong-globalize')(); + var loopback = require('../../lib/loopback'); var assert = require('assert'); var uid = require('uid2'); @@ -111,7 +113,7 @@ module.exports = function(AccessToken) { } else if (isValid) { cb(null, token); } else { - var e = new Error('Invalid Access Token'); + var e = new Error(g.f('Invalid Access Token')); e.status = e.statusCode = 401; e.code = 'INVALID_TOKEN'; cb(e); diff --git a/common/models/acl.js b/common/models/acl.js index f0d48200..4330f0cb 100644 --- a/common/models/acl.js +++ b/common/models/acl.js @@ -36,6 +36,8 @@ */ +var g = require('strong-globalize')(); + var loopback = require('../../lib/loopback'); var async = require('async'); var assert = require('assert'); @@ -536,7 +538,7 @@ module.exports = function(ACL) { break; default: process.nextTick(function() { - var err = new Error('Invalid principal type: ' + type); + var err = new Error(g.f('Invalid principal type: %s', type)); err.statusCode = 400; cb(err); }); diff --git a/common/models/change.js b/common/models/change.js index 38b5c067..53319be8 100644 --- a/common/models/change.js +++ b/common/models/change.js @@ -7,6 +7,8 @@ * Module Dependencies. */ +var g = require('strong-globalize')(); + var PersistedModel = require('../../lib/loopback').PersistedModel; var loopback = require('../../lib/loopback'); var utils = require('../../lib/utils'); @@ -110,7 +112,7 @@ module.exports = function(Change) { }) .join('\n'); - var msg = 'Cannot rectify ' + modelName + ' changes:\n' + desc; + var msg = g.f('Cannot rectify %s changes:\n%s', modelName, desc); err = new Error(msg); err.details = { errors: errors }; return callback(err); diff --git a/common/models/email.js b/common/models/email.js index f292becc..cfc8d998 100644 --- a/common/models/email.js +++ b/common/models/email.js @@ -15,6 +15,8 @@ * @inherits {Model} */ +var g = require('strong-globalize')(); + module.exports = function(Email) { /** * Send an email with the given `options`. @@ -43,13 +45,13 @@ module.exports = function(Email) { */ Email.send = function() { - throw new Error('You must connect the Email Model to a Mail connector'); + throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector')); }; /** * A shortcut for Email.send(this). */ Email.prototype.send = function() { - throw new Error('You must connect the Email Model to a Mail connector'); + throw new Error(g.f('You must connect the {{Email}} Model to a {{Mail}} connector')); }; }; diff --git a/common/models/user.js b/common/models/user.js index ecf03592..99b5cc57 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -7,6 +7,8 @@ * Module Dependencies. */ +var g = require('strong-globalize')(); + var loopback = require('../../lib/loopback'); var utils = require('../../lib/utils'); var path = require('path'); @@ -203,14 +205,14 @@ module.exports = function(User) { realmDelimiter); if (realmRequired && !query.realm) { - var err1 = new Error('realm is required'); + var err1 = new Error(g.f('realm is required')); err1.statusCode = 400; err1.code = 'REALM_REQUIRED'; fn(err1); return fn.promise; } if (!query.email && !query.username) { - var err2 = new Error('username or email is required'); + var err2 = new Error(g.f('{{username}} or {{email}} is required')); err2.statusCode = 400; err2.code = 'USERNAME_EMAIL_REQUIRED'; fn(err2); @@ -218,7 +220,7 @@ module.exports = function(User) { } self.findOne({ where: query }, function(err, user) { - var defaultError = new Error('login failed'); + var defaultError = new Error(g.f('login failed')); defaultError.statusCode = 401; defaultError.code = 'LOGIN_FAILED'; @@ -248,7 +250,7 @@ module.exports = function(User) { if (self.settings.emailVerificationRequired && !user.emailVerified) { // Fail to log in if email verification is not done yet debug('User email has not been verified'); - err = new Error('login failed as the email has not been verified'); + err = new Error(g.f('login failed as the email has not been verified')); err.statusCode = 401; err.code = 'LOGIN_FAILED_EMAIL_NOT_VERIFIED'; fn(err); @@ -295,7 +297,7 @@ module.exports = function(User) { } else if (accessToken) { accessToken.destroy(fn); } else { - fn(new Error('could not find accessToken')); + fn(new Error(g.f('could not find {{accessToken}}'))); } }); return fn.promise; @@ -446,15 +448,21 @@ module.exports = function(User) { options.text = options.text.replace(/\{href\}/g, options.verifyHref); + options.text = g.f(options.text); + options.to = options.to || user.email; options.subject = options.subject || 'Thanks for Registering'; + options.subject = g.f(options.subject); + options.headers = options.headers || {}; var template = loopback.template(options.template); options.html = template(options); + options.html = g.f(options.html); + Email.send(options, function(err, email) { if (err) { fn(err); @@ -510,11 +518,11 @@ module.exports = function(User) { }); } else { if (user) { - err = new Error('Invalid token: ' + token); + err = new Error(g.f('Invalid token: %s', token)); err.statusCode = 400; err.code = 'INVALID_TOKEN'; } else { - err = new Error('User not found: ' + uid); + err = new Error(g.f('User not found: %s', uid)); err.statusCode = 404; err.code = 'USER_NOT_FOUND'; } @@ -543,7 +551,7 @@ module.exports = function(User) { options = options || {}; if (typeof options.email !== 'string') { - var err = new Error('Email is required'); + var err = new Error(g.f('Email is required')); err.statusCode = 400; err.code = 'EMAIL_REQUIRED'; cb(err); @@ -555,7 +563,7 @@ module.exports = function(User) { return cb(err); } if (!user) { - err = new Error('Email not found'); + err = new Error(g.f('Email not found')); err.statusCode = 404; err.code = 'EMAIL_NOT_FOUND'; return cb(err); @@ -591,7 +599,7 @@ module.exports = function(User) { if (typeof plain === 'string' && plain) { return true; } - var err = new Error('Invalid password: ' + plain); + var err = new Error(g.f('Invalid password: %s', plain)); err.statusCode = 422; throw err; }; @@ -650,20 +658,21 @@ module.exports = function(User) { UserModel.remoteMethod( 'login', { - description: 'Login a user with username/email and password.', + description: g.f('Login a user with username/email and password.'), accepts: [ { arg: 'credentials', type: 'object', required: true, http: { source: 'body' }}, { arg: 'include', type: ['string'], http: { source: 'query' }, - description: 'Related objects to include in the response. ' + - 'See the description of return value for more details.' }, + description: g.f('Related objects to include in the response. ' + + 'See the description of return value for more details.') }, ], returns: { arg: 'accessToken', type: 'object', root: true, description: - 'The response body contains properties of the AccessToken created on login.\n' + + g.f('The response body contains properties of the {{AccessToken}} created on login.\n' + 'Depending on the value of `include` parameter, the body may contain ' + 'additional properties:\n\n' + - ' - `user` - `{User}` - Data of the currently logged in user. (`include=user`)\n\n', + ' - `user` - `U+007BUserU+007D` - Data of the currently logged in user. ' + + '{{(`include=user`)}}\n\n'), }, http: { verb: 'post' }, } @@ -672,7 +681,7 @@ module.exports = function(User) { UserModel.remoteMethod( 'logout', { - description: 'Logout a user with access token.', + description: g.f('Logout a user with access token.'), accepts: [ { arg: 'access_token', type: 'string', required: true, http: function(ctx) { var req = ctx && ctx.req; @@ -680,8 +689,8 @@ module.exports = function(User) { var tokenID = accessToken && accessToken.id; return tokenID; - }, description: 'Do not supply this argument, it is automatically extracted ' + - 'from request headers.', + }, description: g.f('Do not supply this argument, it is automatically extracted ' + + 'from request headers.'), }, ], http: { verb: 'all' }, @@ -691,7 +700,7 @@ module.exports = function(User) { UserModel.remoteMethod( 'confirm', { - description: 'Confirm a user registration with email verification token.', + description: g.f('Confirm a user registration with email verification token.'), accepts: [ { arg: 'uid', type: 'string', required: true }, { arg: 'token', type: 'string', required: true }, @@ -704,7 +713,7 @@ module.exports = function(User) { UserModel.remoteMethod( 'resetPassword', { - description: 'Reset password for a user with email.', + description: g.f('Reset password for a user with email.'), accepts: [ { arg: 'options', type: 'object', required: true, http: { source: 'body' }}, ], @@ -715,7 +724,7 @@ module.exports = function(User) { UserModel.afterRemote('confirm', function(ctx, inst, next) { if (ctx.args.redirect !== undefined) { if (!ctx.res) { - return next(new Error('The transport does not support HTTP redirects.')); + return next(new Error(g.f('The transport does not support HTTP redirects.'))); } ctx.res.location(ctx.args.redirect); ctx.res.status(302); @@ -733,7 +742,7 @@ module.exports = function(User) { // email validation regex var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - UserModel.validatesFormatOf('email', { with: re, message: 'Must provide a valid email' }); + UserModel.validatesFormatOf('email', { with: re, message: g.f('Must provide a valid email') }); // FIXME: We need to add support for uniqueness of composite keys in juggler if (!(UserModel.settings.realmRequired || UserModel.settings.realmDelimiter)) { diff --git a/example/client-server/client.js b/example/client-server/client.js index b110af08..67dcdd39 100644 --- a/example/client-server/client.js +++ b/example/client-server/client.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + var loopback = require('../../'); var client = loopback(); var CartItem = require('./models').CartItem; @@ -16,10 +18,10 @@ CartItem.attachTo(remote); // call the remote method CartItem.sum(1, function(err, total) { - console.log('result:', err || total); + g.log('result:%s', err || total); }); // call a built in remote method CartItem.find(function(err, items) { - console.log(items); + g.log(items); }); diff --git a/example/colors/app.js b/example/colors/app.js index cc6b0708..0ff1541b 100644 --- a/example/colors/app.js +++ b/example/colors/app.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + var loopback = require('../../'); var app = loopback(); @@ -22,4 +24,4 @@ Color.create({ name: 'blue' }); app.listen(3000); -console.log('a list of colors is available at http://localhost:3000/colors'); +g.log('a list of colors is available at {{http://localhost:3000/colors}}'); diff --git a/example/context/app.js b/example/context/app.js index a99a94c2..655767e1 100644 --- a/example/context/app.js +++ b/example/context/app.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + var loopback = require('../../'); var app = loopback(); @@ -22,7 +24,7 @@ var Color = loopback.createModel('color', { 'name': String }); Color.beforeRemote('**', function(ctx, unused, next) { // Inside LoopBack code, you can read the property from the context var ns = loopback.getCurrentContext(); - console.log('Request to host', ns && ns.get('host')); + g.log('Request to host %s', ns && ns.get('host')); next(); }); @@ -30,5 +32,5 @@ app.dataSource('db', { connector: 'memory' }); app.model(Color, { dataSource: 'db' }); app.listen(3000, function() { - console.log('A list of colors is available at http://localhost:3000/colors'); + g.log('A list of colors is available at {{http://localhost:3000/colors}}'); }); diff --git a/example/mobile-models/app.js b/example/mobile-models/app.js index d71d6dc7..db52d5f3 100644 --- a/example/mobile-models/app.js +++ b/example/mobile-models/app.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + var models = require('../../lib/models'); var loopback = require('../../'); @@ -37,15 +39,15 @@ var data = { pushSettings: [ ] }; Application.create(data, function(err, data) { - console.log('Created: ', data.toObject()); + g.log('Created: %s', data.toObject()); }); -Application.register('rfeng', 'MyApp', { description: 'My first mobile application' }, +Application.register('rfeng', 'MyApp', { description: g.f('My first mobile application') }, function(err, result) { console.log(result.toObject()); result.resetKeys(function(err, result) { - console.log(result.toObject()); + g.log(result.toObject()); }); }); diff --git a/example/simple-data-source/app.js b/example/simple-data-source/app.js index 28bbad5c..fa43a55e 100644 --- a/example/simple-data-source/app.js +++ b/example/simple-data-source/app.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + var loopback = require('../../'); var app = loopback(); @@ -25,4 +27,4 @@ Color.all(function() { app.listen(3000); -console.log('a list of colors is available at http://localhost:3000/colors'); +g.log('a list of colors is available at {{http://localhost:3000/colors}}'); diff --git a/index.js b/index.js index b7ce6392..8d7626c8 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,9 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var SG = require('strong-globalize'); +SG.SetRootDir(__dirname, {autonomousMsgLoading: 'all'}); + /** * loopback ~ public api */ diff --git a/intl/en/messages.json b/intl/en/messages.json new file mode 100644 index 00000000..bbb26a73 --- /dev/null +++ b/intl/en/messages.json @@ -0,0 +1,112 @@ +{ + "3b46d3a780fd6ae5f95ade489a0efffe": "Current context is not supported in the browser.", + "7e0fca41d098607e1c9aa353c67e0fa1": "Invalid Access Token", + "320c482401afa1207c04343ab162e803": "Invalid principal type: {0}", + "c2b5d51f007178170ca3952d59640ca4": "Cannot rectify {0} changes:\n{1}", + "b2aa64e3b5aa234388d0b457f3ba995a": "You must connect the Email Model to a Mail connector", + "0caffe1d763c8cca6a61814abe33b776": "Email is required", + "1b2a6076dccbe91a56f1672eb3b8598c": "The response body contains properties of the {{AccessToken}} created on login.\nDepending on the value of `include` parameter, the body may contain additional properties:\n\n - `user` - `U+007BUserU+007D` - Data of the currently logged in user. {{(`include=user`)}}\n\n", + "2362ba55796c733e337af169922327a2": "Related objects to include in the response. See the description of return value for more details.", + "306999d39387d87b2638199ff0bed8ad": "Reset password for a user with email.", + "39f25db6c2d9cb0c11d9c46ef6bd3402": "username or email is required", + "3aae63bb7e8e046641767571c1591441": "login failed as the email has not been verified", + "3caaa84fc103d6d5612173ae6d43b245": "Invalid token: {0}", + "42e3fa18945255412ebc6561e2c6f1dc": "Confirm a user registration with email verification token.", + "430b6326d7ebf6600a39f614ef516bc8": "Do not supply this argument, it is automatically extracted from request headers.", + "44a6c8b1ded4ed653d19ddeaaf89a606": "Email not found", + "5e81ad3847a290dc650b47618b9cbc7e": "login failed", + "83cfabbe3aa84ce52e0f5ed7c3b2e9b3": "Login a user with username/email and password.", + "8608c28f5e6df0008266e3c497836176": "Logout a user with access token.", + "860d1a0b8bd340411fb32baa72867989": "The transport does not support HTTP redirects.", + "a50d10fc6e0959b220e085454c40381e": "User not found: {0}", + "a9ce058e495aba6f1aa3e041bfc1035b": "could not find accessToken", + "ad24a933c0351aa6fc39c8f1431b0ee4": "realm is required", + "c68a93f0a9524fed4ff64372fc90c55f": "Must provide a valid email", + "f58cdc481540cd1f69a4aa4da2e37981": "Invalid password: {0}", + "f0bd73df8714cefb925e3b8da2f4c5f6": "result:{0}", + "10e01c895dc0b2fecc385f9f462f1ca6": "a list of colors is available at {{http://localhost:3000/colors}}", + "8a27e0c9ce3ebf0e0c3978efb456e13e": "Request to host {0}", + "a40684f5a9f546115258b76938d1de37": "A list of colors is available at {{http://localhost:3000/colors}}", + "1e85f822b547a75d7d385048030e4ecb": "Created: {0}", + "7d5e7ed0efaedf3f55f380caae0df8b8": "My first mobile application", + "04bd8af876f001ceaf443aad6a9002f9": "Authentication requires model {0} to be defined.", + "095afbf2f1f0e5be678f5dac5c54e717": "Access Denied", + "2d3071e3b18681c80a090dc0efbdb349": "could not find {0} with id {1}", + "316e5b82c203cf3de31a449ee07d0650": "Expected boolean, got {0}", + "57b87ae0e65f6ab7a2e3e6cbdfca49a4": "Cannot create data source {0}: {1}", + "62e933dca3cbd369087f706db669e881": "{{`app.boot`}} was removed, use the new module loopback-boot instead", + "7e287fc885d9fdcf42da3a12f38572c1": "Authorization Required", + "1d7833c3ca2f05fdad8fad7537531c40": "\t SUBJECT:{0}", + "275f22ab95671f095640ca99194b7635": "\t FROM:{0}", + "3d63008ccfb2af1db2142e8cc2716ace": "Warning: No email transport specified for sending email. Setup a transport to send mail messages.", + "4a4f04a4e480fc5d4ee73b84d9a4b904": "Sending Mail:", + "63a091ced88001ab6acb58f61ec041c5": "\t TEXT:{0}", + "93ba9a1d03da3b7696332d3f155c5bb7": "\t HTML:{0}", + "ecb06666ef95e5db27a5ac1d6a17923b": "\t TO:{0}", + "f0aed00a3d3d0b97d6594e4b70e0c201": "\t TRANSPORT:{0}", + "0da38687fed24275c1547e815914a8e3": "Delete a related item by id for {0}.", + "22fe62fa8d595b72c62208beddaa2a56": "Update a related item by id for {0}.", + "528325f3cbf1b0ab9a08447515daac9a": "Update {0} of this model.", + "543d19bad5e47ee1e9eb8af688e857b4": "Foreign key for {0}.", + "598ff0255ffd1d1b71e8de55dbe2c034": "Check the existence of {0} relation to an item by id.", + "5a36cc6ba0cc27c754f6c5ed6015ea3c": "Remove the {0} relation to an item by id.", + "61e5deebaf44d68f4e6a508f30cc31a3": "Relation `{0}` does not exist for model `{1}`", + "651f0b3cbba001635152ec3d3d954d0a": "Find a related item by id for {0}.", + "7bc7b301ad9c4fc873029d57fb9740fe": "Queries {0} of {1}.", + "7c837b88fd0e509bd3fc722d7ddf0711": "Foreign key for {0}", + "830cb6c862f8f364e9064cea0026f701": "Fetches hasOne relation {0}.", + "855ecd4a64885ba272d782435f72a4d4": "Unknown \"{0}\" id \"{1}\".", + "86254879d01a60826a851066987703f2": "Add a related item by id for {0}.", + "8ae418c605b6a45f2651be9b1677c180": "Invalid remote method: `{0}`", + "9442a303f4c26de8e29e39430f03d63a": "could not find a model with id {0}", + "c0057a569ff9d3b509bac61a4b2f605d": "Deletes all {0} of this model.", + "d6f43b266533b04d442bdb3955622592": "Creates a new instance in {0} of this model.", + "da13d3cdf21330557254670dddd8c5c7": "Counts {0} of {1}.", + "ec296bb6191b3eb43570e250ab4ba919": "must specify an id or data", + "f66ae3cf379b2fce28575a3282defe1a": "Deletes {0} of this model.", + "03f79fa268fe199de2ce4345515431c1": "No change record found for {0} with id {1}", + "0f1c71f74b040bfbe8d384a414e31f03": "Get a set of deltas and conflicts since the given checkpoint.", + "15254dec061d023d6c030083a0cef50f": "Create a new instance of the model and persist it into the data source.", + "16a11368d55b85a209fc6aea69ee5f7a": "Delete all matching records.", + "1bc1d489ddf347af47af3d9b1fc7cc15": "Run multiple updates at once. Note: this is not atomic.", + "1bc7d8283c9abda512692925c8d8e3c0": "Get the current checkpoint.", + "1caa7cc61266e7aef7db7d2f0e27ac3e": "Update the properties of the most recent change record kept for this instance.", + "2a7df74fe6e8462e617b79d5fbb536ea": "Get the most recent change record for this instance.", + "2a9684b3d5b3b67af74bac74eb1b0843": "Find all instances of the model matched by filter from the data source.", + "2e406dbfed4c5eaeff9d3daaa366c69e": "Find a model instance by id from the data source.", + "2e50838caf0c927735eb15d12866bdd7": "Get the changes to a model since a given checkpoint.Provide a filter object to reduce the number of results returned.", + "4203ab415ec66a78d3164345439ba76e": "Cannot call {0}.{1}(). The {2} method has not been setup. The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!", + "51ea9b6245bb5e672b236d640ca3b048": "An object of Change property name/value pairs", + "55ddedd4c501348f82cb89db02ec85c1": "An object of model property name/value pairs", + "62e8b0a733417978bab22c8dacf5d7e6": "Cannot apply bulk updates, the connector does not correctly report the number of updated records.", + "6329e0ac1de3c250ebb1df5afd5a2a7b": "The number of instances updated", + "6bc376432cd9972cf991aad3de371e78": "Missing data for change: {0}", + "7f2fde7f0f860ead224b11ba8d75aa1c": "Create an update list from a delta list.", + "829160747cbc802e29d8cd45c68e7dba": "Patch attributes for a model instance and persist it into the data source.", + "8bab6720ecc58ec6412358c858a53484": "Bulk update failed, the connector has modified unexpected number of records: {0}", + "a98b6cc6547706b5c6bffde0ed5fd55c": "Find first instance of the model matched by filter from the data source.", + "aada86412c03def85d98b75e4d786edc": "Patch an existing model instance or insert a new one into the data source.", + "c46d4aba1f14809c16730faa46933495": "Filter defining fields and include", + "c65600640f206f585d300b4bcb699d95": "Create a checkpoint.", + "d32cbcec1c7022d9a975699865ff6036": "Delete a model instance by id from the data source.", + "dcb6261868ff0a7b928aa215b07d068c": "Create a change stream.", + "e43e320a435ec1fa07648c1da0d558a7": "Check whether a model instance exists in the data source.", + "e92aa25b6b864e3454b65a7c422bd114": "Bulk update failed, the connector has deleted unexpected number of records: {0}", + "ea63d226b6968e328bdf6876010786b5": "Cannot apply bulk updates, the connector does not correctly report the number of deleted records.", + "f04c62976db4b2b6c8bf907087b53996": "Update instances of the model matched by where from the data source.", + "f1d4ac54357cc0932f385d56814ba7e4": "Conflict", + "f37d94653793e33f4203e45b4a1cf106": "Count instances of the model matched by where from the data source.", + "0e21aad369dd09e1965c11949303cefd": "Remoting metadata for {0}.{1} {{\"isStatic\"}} does not match new method name-based style.", + "2e110abee2c95bcfc2dafd48be7e2095": "Cannot configure {0}: {{config.dataSource}} must be an instance of {{DataSource}}", + "3438fab56cc7ab92dfd88f0497e523e0": "The relations property of `{0}` configuration must be an object", + "3591f1d3e115b46f9f195df5ca548a6a": "Model not found: model `{0}` is extending an unknown model `{1}`.", + "42a36bac5cf03c4418d664500c81047a": "{{DataSource}} option {{\"defaultForType\"}} is no longer supported", + "734a7bebb65e10899935126ba63dd51f": "The options property of `{0}` configuration must be an object", + "779467f467862836e19f494a37d6ab77": "The acls property of `{0}` configuration must be an array of objects", + "80a32e80cbed65eba2103201a7c94710": "Model not found: {0}", + "83cbdc2560ba9f09155ccfc63e08f1a1": "Property `{0}` cannot be reconfigured for `{1}`", + "97795efe0c3eb7f35ce8cf8cfe70682b": "The configuration of `{0}` is missing {{`dataSource`}} property.\nUse `null` or `false` to mark models not attached to any data source.", + "c61a5a02ba3801a892308f70f5d55a14": "Remoting metadata {{\"isStatic\"}} is deprecated. Please specify {{\"prototype.name\"}} in method name instead for {{isStatic=false}}.", + "dc568bee32deb0f6eaf63e73b20e8ceb": "Ignoring non-object \"methods\" setting of \"{0}\".", + "cb6b65cca90372e428100fd21f8b2259": "Unknown middleware phase {0}" +} diff --git a/lib/application.js b/lib/application.js index 97b11f9e..a1850708 100644 --- a/lib/application.js +++ b/lib/application.js @@ -7,6 +7,8 @@ * Module dependencies. */ +var g = require('strong-globalize')(); + var DataSource = require('loopback-datasource-juggler').DataSource; var Registry = require('./registry'); var assert = require('assert'); @@ -237,8 +239,8 @@ app.dataSource = function(name, config) { return ds; } catch (err) { if (err.message) { - err.message = 'Cannot create data source ' + JSON.stringify(name) + - ': ' + err.message; + err.message = g.f('Cannot create data source %s: %s', + JSON.stringify(name), err.message); } throw err; } @@ -322,7 +324,7 @@ app.enableAuth = function(options) { var Model = app.registry.findModel(m); if (!Model) { throw new Error( - 'Authentication requires model ' + m + ' to be defined.'); + g.f('Authentication requires model %s to be defined.', m)); } if (m.dataSource || m.app) return; @@ -377,15 +379,15 @@ app.enableAuth = function(options) { } else { var messages = { 403: { - message: 'Access Denied', + message: g.f('Access Denied'), code: 'ACCESS_DENIED', }, 404: { - message: ('could not find ' + modelName + ' with id ' + modelId), + message: (g.f('could not find %s with id %s', modelName, modelId)), code: 'MODEL_NOT_FOUND', }, 401: { - message: 'Authorization Required', + message: g.f('Authorization Required'), code: 'AUTHORIZATION_REQUIRED', }, }; @@ -407,7 +409,7 @@ app.enableAuth = function(options) { app.boot = function(options) { throw new Error( - '`app.boot` was removed, use the new module loopback-boot instead'); + g.f('{{`app.boot`}} was removed, use the new module loopback-boot instead')); }; function dataSourcesFromConfig(name, config, connectorRegistry, registry) { @@ -485,7 +487,7 @@ function setSharedMethodSharedProperties(model, app, modelConfigs) { var settingValue = settings[setting]; var settingValueType = typeof settingValue; if (settingValueType !== 'boolean') - throw new TypeError('Expected boolean, got ' + settingValueType); + throw new TypeError(g.f('Expected boolean, got %s', settingValueType)); }); // set sharedMethod.shared using the merged settings diff --git a/lib/connectors/mail.js b/lib/connectors/mail.js index 3c8941eb..44e0616c 100644 --- a/lib/connectors/mail.js +++ b/lib/connectors/mail.js @@ -7,6 +7,8 @@ * Dependencies. */ +var g = require('strong-globalize')(); + var mailer = require('nodemailer'); var assert = require('assert'); var debug = require('debug')('loopback:connector:mail'); @@ -148,15 +150,15 @@ Mailer.send = function(options, fn) { } if (debug.enabled || settings && settings.debug) { - console.log('Sending Mail:'); + g.log('Sending Mail:'); if (options.transport) { - console.log('\t TRANSPORT:', options.transport); + console.log(g.f('\t TRANSPORT:%s', options.transport)); } - console.log('\t TO:', options.to); - console.log('\t FROM:', options.from); - console.log('\t SUBJECT:', options.subject); - console.log('\t TEXT:', options.text); - console.log('\t HTML:', options.html); + g.log('\t TO:%s', options.to); + g.log('\t FROM:%s', options.from); + g.log('\t SUBJECT:%s', options.subject); + g.log('\t TEXT:%s', options.text); + g.log('\t HTML:%s', options.html); } if (transport) { @@ -164,7 +166,7 @@ Mailer.send = function(options, fn) { 'You must supply an Email.settings.transports containing a valid transport'); transport.sendMail(options, fn); } else { - console.warn('Warning: No email transport specified for sending email.' + + g.warn('Warning: No email transport specified for sending email.' + ' Setup a transport to send mail messages.'); process.nextTick(function() { fn(null, options); diff --git a/lib/model.js b/lib/model.js index 15e98b7b..530c81e5 100644 --- a/lib/model.js +++ b/lib/model.js @@ -6,6 +6,9 @@ /*! * Module Dependencies. */ + +var g = require('strong-globalize')(); + var assert = require('assert'); var RemoteObjects = require('strong-remoting'); var SharedClass = require('strong-remoting').SharedClass; @@ -167,14 +170,14 @@ module.exports = function(registry) { } else if (model) { fn(null, model); } else { - err = new Error('could not find a model with id ' + id); + err = new Error(g.f('could not find a model with id %s', id)); err.statusCode = 404; err.code = 'MODEL_NOT_FOUND'; fn(err); } }); } else { - fn(new Error('must specify an id or data')); + fn(new Error(g.f('must specify an id or data'))); } }; @@ -460,7 +463,7 @@ module.exports = function(registry) { if (ctx.result !== null) return cb(); var fk = ctx.getArgByName('fk'); - var msg = 'Unknown "' + toModelName + '" id "' + fk + '".'; + var msg = g.f('Unknown "%s" id "%s".', toModelName, fk); var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; @@ -475,7 +478,7 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'get', path: '/' + pathName }, accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }}, - description: 'Fetches hasOne relation ' + relationName + '.', + description: g.f('Fetches hasOne relation %s.', relationName), accessType: 'READ', returns: { arg: relationName, type: relation.modelTo.modelName, root: true }, rest: { after: convertNullToNotFoundError.bind(null, toModelName) }, @@ -485,7 +488,7 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'post', path: '/' + pathName }, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, - description: 'Creates a new instance in ' + relationName + ' of this model.', + description: g.f('Creates a new instance in %s of this model.', relationName), accessType: 'WRITE', returns: { arg: 'data', type: toModelName, root: true }, }); @@ -494,7 +497,7 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'put', path: '/' + pathName }, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, - description: 'Update ' + relationName + ' of this model.', + description: g.f('Update %s of this model.', relationName), accessType: 'WRITE', returns: { arg: 'data', type: toModelName, root: true }, }); @@ -502,7 +505,7 @@ module.exports = function(registry) { define('__destroy__' + relationName, { isStatic: false, http: { verb: 'delete', path: '/' + pathName }, - description: 'Deletes ' + relationName + ' of this model.', + description: g.f('Deletes %s of this model.', relationName), accessType: 'WRITE', }); }; @@ -516,9 +519,10 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'get', path: '/' + pathName + '/:fk' }, accepts: { arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + description: g.f('Foreign key for %s', relationName), + required: true, http: { source: 'path' }}, - description: 'Find a related item by id for ' + relationName + '.', + description: g.f('Find a related item by id for %s.', relationName), accessType: 'READ', returns: { arg: 'result', type: toModelName, root: true }, rest: { after: convertNullToNotFoundError.bind(null, toModelName) }, @@ -529,9 +533,10 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'delete', path: '/' + pathName + '/:fk' }, accepts: { arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + description: g.f('Foreign key for %s', relationName), + required: true, http: { source: 'path' }}, - description: 'Delete a related item by id for ' + relationName + '.', + description: g.f('Delete a related item by id for %s.', relationName), accessType: 'WRITE', returns: [], }, destroyByIdFunc); @@ -542,11 +547,12 @@ module.exports = function(registry) { http: { verb: 'put', path: '/' + pathName + '/:fk' }, accepts: [ { arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + description: g.f('Foreign key for %s', relationName), + required: true, http: { source: 'path' }}, { arg: 'data', type: toModelName, http: { source: 'body' }}, ], - description: 'Update a related item by id for ' + relationName + '.', + description: g.f('Update a related item by id for %s.', relationName), accessType: 'WRITE', returns: { arg: 'result', type: toModelName, root: true }, }, updateByIdFunc); @@ -565,9 +571,10 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'put', path: '/' + pathName + '/rel/:fk' }, accepts: [{ arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + description: g.f('Foreign key for %s', relationName), + required: true, http: { source: 'path' }}].concat(accepts), - description: 'Add a related item by id for ' + relationName + '.', + description: g.f('Add a related item by id for %s.', relationName), accessType: 'WRITE', returns: { arg: relationName, type: modelThrough.modelName, root: true }, }, addFunc); @@ -577,9 +584,10 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'delete', path: '/' + pathName + '/rel/:fk' }, accepts: { arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + description: g.f('Foreign key for %s', relationName), + required: true, http: { source: 'path' }}, - description: 'Remove the ' + relationName + ' relation to an item by id.', + description: g.f('Remove the %s relation to an item by id.', relationName), accessType: 'WRITE', returns: [], }, removeFunc); @@ -591,9 +599,10 @@ module.exports = function(registry) { isStatic: false, http: { verb: 'head', path: '/' + pathName + '/rel/:fk' }, accepts: { arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + description: g.f('Foreign key for %s', relationName), + required: true, http: { source: 'path' }}, - description: 'Check the existence of ' + relationName + ' relation to an item by id.', + description: g.f('Check the existence of %s relation to an item by id.', relationName), accessType: 'READ', returns: { arg: 'exists', type: 'boolean', root: true }, rest: { @@ -602,7 +611,7 @@ module.exports = function(registry) { if (ctx.result === false) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); - var msg = 'Unknown "' + modelName + '" id "' + id + '".'; + var msg = g.f('Unknown "%s" id "%s".', modelName, id); var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; @@ -636,7 +645,7 @@ module.exports = function(registry) { isStatic: isStatic, http: { verb: 'get', path: '/' + pathName }, accepts: { arg: 'filter', type: 'object' }, - description: 'Queries ' + scopeName + ' of ' + this.modelName + '.', + description: g.f('Queries %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: { arg: scopeName, type: [toModelName], root: true }, }); @@ -645,7 +654,7 @@ module.exports = function(registry) { isStatic: isStatic, http: { verb: 'post', path: '/' + pathName }, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, - description: 'Creates a new instance in ' + scopeName + ' of this model.', + description: g.f('Creates a new instance in %s of this model.', scopeName), accessType: 'WRITE', returns: { arg: 'data', type: toModelName, root: true }, }); @@ -653,7 +662,7 @@ module.exports = function(registry) { define('__delete__' + scopeName, { isStatic: isStatic, http: { verb: 'delete', path: '/' + pathName }, - description: 'Deletes all ' + scopeName + ' of this model.', + description: g.f('Deletes all %s of this model.', scopeName), accessType: 'WRITE', }); @@ -661,7 +670,7 @@ module.exports = function(registry) { isStatic: isStatic, http: { verb: 'get', path: '/' + pathName + '/count' }, accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' }, - description: 'Counts ' + scopeName + ' of ' + this.modelName + '.', + description: g.f('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: { arg: 'count', type: 'number' }, }); @@ -708,7 +717,7 @@ module.exports = function(registry) { acceptArgs = [ { arg: paramName, type: 'any', http: { source: 'path' }, - description: 'Foreign key for ' + relation.name + '.', + description: g.f('Foreign key for %s.', relation.name), required: true, }, ]; @@ -738,12 +747,12 @@ module.exports = function(registry) { var getterFn = relation.modelFrom.prototype[getterName]; if (typeof getterFn !== 'function') { - throw new Error('Invalid remote method: `' + getterName + '`'); + throw new Error(g.f('Invalid remote method: `%s`', getterName)); } var nestedFn = relation.modelTo.prototype[method.name]; if (typeof nestedFn !== 'function') { - throw new Error('Invalid remote method: `' + method.name + '`'); + throw new Error(g.f('Invalid remote method: `%s`', method.name)); } var opts = {}; @@ -835,8 +844,7 @@ module.exports = function(registry) { }); }); } else { - var msg = 'Relation `' + relationName + - '` does not exist for model `' + this.modelName + '`'; + var msg = g.f('Relation `%s` does not exist for model `%s`', relationName, this.modelName); throw new Error(msg); } }; diff --git a/lib/persisted-model.js b/lib/persisted-model.js index 2296067d..bcc6c4be 100644 --- a/lib/persisted-model.js +++ b/lib/persisted-model.js @@ -7,6 +7,7 @@ * Module Dependencies. */ +var g = require('strong-globalize')(); var runtime = require('./runtime'); var assert = require('assert'); var async = require('async'); @@ -65,9 +66,10 @@ module.exports = function(registry) { function throwNotAttached(modelName, methodName) { throw new Error( - 'Cannot call ' + modelName + '.' + methodName + '().' + - ' The ' + methodName + ' method has not been setup.' + - ' The PersistedModel has not been correctly attached to a DataSource!' + g.f('Cannot call %s.%s().' + + ' The %s method has not been setup.' + + ' The {{PersistedModel}} has not been correctly attached to a {{DataSource}}!', + modelName, methodName, methodName) ); } @@ -82,7 +84,7 @@ module.exports = function(registry) { var modelName = ctx.method.sharedClass.name; var id = ctx.getArgByName('id'); - var msg = 'Unknown "' + modelName + '" id "' + id + '".'; + var msg = g.f('Unknown "%s" id "%s".', modelName, id); var error = new Error(msg); error.statusCode = error.status = 404; error.code = 'MODEL_NOT_FOUND'; @@ -613,7 +615,7 @@ module.exports = function(registry) { } setRemoting(PersistedModel, 'create', { - description: 'Create a new instance of the model and persist it into the data source.', + description: g.f('Create a new instance of the model and persist it into the data source.'), accessType: 'WRITE', accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'Model instance data' }, @@ -623,7 +625,8 @@ module.exports = function(registry) { var upsertOptions = { aliases: ['upsert', 'updateOrCreate'], - description: 'Patch an existing model instance or insert a new one into the data source.', + description: g.f('Patch an existing model instance or insert a new one ' + + 'into the data source.'), accessType: 'WRITE', accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'Model instance data' }, @@ -652,7 +655,7 @@ module.exports = function(registry) { setRemoting(PersistedModel, 'replaceOrCreate', replaceOrCreateOptions); setRemoting(PersistedModel, 'exists', { - description: 'Check whether a model instance exists in the data source.', + description: g.f('Check whether a model instance exists in the data source.'), accessType: 'READ', accepts: { arg: 'id', type: 'any', description: 'Model id', required: true }, returns: { arg: 'exists', type: 'boolean' }, @@ -683,13 +686,13 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'findById', { - description: 'Find a model instance by id from the data source.', + description: g.f('Find a model instance by id from the data source.'), accessType: 'READ', accepts: [ { arg: 'id', type: 'any', description: 'Model id', required: true, http: { source: 'path' }}, { arg: 'filter', type: 'object', - description: 'Filter defining fields and include' }, + description: g.f('Filter defining fields and include') }, ], returns: { arg: 'data', type: typeName, root: true }, http: { verb: 'get', path: '/:id' }, @@ -717,7 +720,7 @@ module.exports = function(registry) { setRemoting(PersistedModel, 'find', { - description: 'Find all instances of the model matched by filter from the data source.', + description: g.f('Find all instances of the model matched by filter from the data source.'), accessType: 'READ', accepts: { arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit' }, @@ -726,7 +729,7 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'findOne', { - description: 'Find first instance of the model matched by filter from the data source.', + description: g.f('Find first instance of the model matched by filter from the data source.'), accessType: 'READ', accepts: { arg: 'filter', type: 'object', description: 'Filter defining fields, where, include, order, offset, and limit' }, @@ -736,7 +739,7 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'destroyAll', { - description: 'Delete all matching records.', + description: g.f('Delete all matching records.'), accessType: 'WRITE', accepts: { arg: 'where', type: 'object', description: 'filter.where object' }, returns: { @@ -751,17 +754,17 @@ module.exports = function(registry) { setRemoting(PersistedModel, 'updateAll', { aliases: ['update'], - description: 'Update instances of the model matched by where from the data source.', + description: g.f('Update instances of the model matched by {{where}} from the data source.'), accessType: 'WRITE', accepts: [ { arg: 'where', type: 'object', http: { source: 'query' }, description: 'Criteria to match model instances' }, { arg: 'data', type: 'object', http: { source: 'body' }, - description: 'An object of model property name/value pairs' }, + description: g.f('An object of model property name/value pairs') }, ], returns: { arg: 'count', - description: 'The number of instances updated', + description: g.f('The number of instances updated'), type: 'object', root: true, }, @@ -770,7 +773,7 @@ module.exports = function(registry) { setRemoting(PersistedModel, 'deleteById', { aliases: ['destroyById', 'removeById'], - description: 'Delete a model instance by id from the data source.', + description: g.f('Delete a model instance by id from the data source.'), accessType: 'WRITE', accepts: { arg: 'id', type: 'any', description: 'Model id', required: true, http: { source: 'path' }}, @@ -779,7 +782,7 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'count', { - description: 'Count instances of the model matched by where from the data source.', + description: g.f('Count instances of the model matched by where from the data source.'), accessType: 'READ', accepts: { arg: 'where', type: 'object', description: 'Criteria to match model instances' }, returns: { arg: 'count', type: 'number' }, @@ -788,7 +791,8 @@ module.exports = function(registry) { var updateAttributesOptions = { aliases: ['updateAttributes'], - description: 'Patch attributes for a model instance and persist it into the data source.', + description: g.f('Patch attributes for a model instance and persist it into ' + + 'the data source.'), accessType: 'WRITE', accepts: { arg: 'data', type: 'object', http: { source: 'body' }, description: 'An object of model property name/value pairs' }, returns: { arg: 'data', type: typeName, root: true }, @@ -803,7 +807,7 @@ module.exports = function(registry) { if (options.trackChanges || options.enableRemoteReplication) { setRemoting(PersistedModel, 'diff', { - description: 'Get a set of deltas and conflicts since the given checkpoint.', + description: g.f('Get a set of deltas and conflicts since the given checkpoint.'), accessType: 'READ', accepts: [ { arg: 'since', type: 'number', description: 'Find deltas since this checkpoint' }, @@ -815,8 +819,8 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'changes', { - description: 'Get the changes to a model since a given checkpoint.' + - 'Provide a filter object to reduce the number of results returned.', + description: g.f('Get the changes to a model since a given checkpoint.' + + 'Provide a filter object to reduce the number of results returned.'), accessType: 'READ', accepts: [ { arg: 'since', type: 'number', description: @@ -829,7 +833,7 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'checkpoint', { - description: 'Create a checkpoint.', + description: g.f('Create a checkpoint.'), // The replication algorithm needs to create a source checkpoint, // even though it is otherwise not making any source changes. // We need to allow this method for users that don't have full @@ -840,14 +844,14 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'currentCheckpoint', { - description: 'Get the current checkpoint.', + description: g.f('Get the current checkpoint.'), accessType: 'READ', returns: { arg: 'checkpoint', type: 'object', root: true }, http: { verb: 'get', path: '/checkpoint' }, }); setRemoting(PersistedModel, 'createUpdates', { - description: 'Create an update list from a delta list.', + description: g.f('Create an update list from a delta list.'), // This operation is read-only, it does not change any local data. // It is called by the replication algorithm to compile a list // of changes to apply on the target. @@ -858,14 +862,14 @@ module.exports = function(registry) { }); setRemoting(PersistedModel, 'bulkUpdate', { - description: 'Run multiple updates at once. Note: this is not atomic.', + description: g.f('Run multiple updates at once. Note: this is not atomic.'), accessType: 'WRITE', accepts: { arg: 'updates', type: 'array' }, http: { verb: 'post', path: '/bulk-update' }, }); setRemoting(PersistedModel, 'findLastChange', { - description: 'Get the most recent change record for this instance.', + description: g.f('Get the most recent change record for this instance.'), accessType: 'READ', accepts: { arg: 'id', type: 'any', required: true, http: { source: 'path' }, @@ -877,8 +881,8 @@ module.exports = function(registry) { setRemoting(PersistedModel, 'updateLastChange', { description: [ - 'Update the properties of the most recent change record', - 'kept for this instance.', + g.f('Update the properties of the most recent change record ' + + 'kept for this instance.'), ], accessType: 'WRITE', accepts: [ @@ -888,7 +892,7 @@ module.exports = function(registry) { }, { arg: 'data', type: 'object', http: { source: 'body' }, - description: 'An object of Change property name/value pairs', + description: g.f('An object of Change property name/value pairs'), }, ], returns: { arg: 'result', type: this.Change.modelName, root: true }, @@ -897,7 +901,7 @@ module.exports = function(registry) { } setRemoting(PersistedModel, 'createChangeStream', { - description: 'Create a change stream.', + description: g.f('Create a change stream.'), accessType: 'READ', http: [ { verb: 'post', path: '/change-stream' }, @@ -1239,7 +1243,7 @@ module.exports = function(registry) { if (err) return cb(err); if (!inst) { return cb && - cb(new Error('Missing data for change: ' + change.modelId)); + cb(new Error(g.f('Missing data for change: %s', change.modelId))); } if (inst.toObject) { update.data = inst.toObject(); @@ -1307,7 +1311,7 @@ module.exports = function(registry) { async.parallel(tasks, function(err) { if (err) return callback(err); if (conflicts.length) { - err = new Error('Conflict'); + err = new Error(g.f('Conflict')); err.statusCode = 409; err.details = { conflicts: conflicts }; return callback(err); @@ -1371,16 +1375,16 @@ module.exports = function(registry) { case undefined: case null: return cb(new Error( - 'Cannot apply bulk updates, ' + + g.f('Cannot apply bulk updates, ' + 'the connector does not correctly report ' + - 'the number of updated records.')); + 'the number of updated records.'))); default: debug('%s.updateAll modified unexpected number of instances: %j', Model.modelName, count); return cb(new Error( - 'Bulk update failed, the connector has modified unexpected ' + - 'number of records: ' + JSON.stringify(count))); + g.f('Bulk update failed, the connector has modified unexpected ' + + 'number of records: %s', JSON.stringify(count)))); } }); } @@ -1453,16 +1457,16 @@ module.exports = function(registry) { case undefined: case null: return cb(new Error( - 'Cannot apply bulk updates, ' + + g.f('Cannot apply bulk updates, ' + 'the connector does not correctly report ' + - 'the number of deleted records.')); + 'the number of deleted records.'))); default: debug('%s.deleteAll modified unexpected number of instances: %j', Model.modelName, count); return cb(new Error( - 'Bulk update failed, the connector has deleted unexpected ' + - 'number of records: ' + JSON.stringify(count))); + g.f('Bulk update failed, the connector has deleted unexpected ' + + 'number of records: %s', JSON.stringify(count)))); } }); } @@ -1676,8 +1680,8 @@ module.exports = function(registry) { this.findLastChange(id, function(err, inst) { if (err) return cb(err); if (!inst) { - err = new Error('No change record found for ' + - self.modelName + ' with id ' + id); + err = new Error(g.f('No change record found for %s with id %s', + self.modelName, id)); err.statusCode = 404; return cb(err); } diff --git a/lib/registry.js b/lib/registry.js index 9225fad1..f6d53719 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -3,6 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); var assert = require('assert'); var extend = require('util')._extend; var juggler = require('loopback-datasource-juggler'); @@ -111,8 +112,8 @@ Registry.prototype.createModel = function(name, properties, options) { var baseName = BaseModel; BaseModel = this.findModel(BaseModel); if (!BaseModel) { - throw new Error('Model not found: model `' + name + '` is extending an unknown model `' + - baseName + '`.'); + throw new Error(g.f('Model not found: model `%s` is extending an unknown model `%s`.', + name, baseName)); } } @@ -185,7 +186,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) { relations[key] = extend(relations[key] || {}, config.relations[key]); }); } else if (config.relations != null) { - console.warn('The relations property of `%s` configuration ' + + g.warn('The relations property of `%s` configuration ' + 'must be an object', modelName); } @@ -196,7 +197,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) { addACL(acls, acl); }); } else if (config.acls != null) { - console.warn('The acls property of `%s` configuration ' + + g.warn('The acls property of `%s` configuration ' + 'must be an array of objects', modelName); } @@ -213,12 +214,12 @@ Registry.prototype.configureModel = function(ModelCtor, config) { if (!(p in excludedProperties)) { settings[p] = config.options[p]; } else { - console.warn('Property `%s` cannot be reconfigured for `%s`', + g.warn('Property `%s` cannot be reconfigured for `%s`', p, modelName); } } } else if (config.options != null) { - console.warn('The options property of `%s` configuration ' + + g.warn('The options property of `%s` configuration ' + 'must be an object', modelName); } @@ -226,8 +227,8 @@ Registry.prototype.configureModel = function(ModelCtor, config) { // configuration, so that the datasource picks up updated relations if (config.dataSource) { assert(config.dataSource instanceof DataSource, - 'Cannot configure ' + ModelCtor.modelName + - ': config.dataSource must be an instance of DataSource'); + g.f('Cannot configure %s: {{config.dataSource}} must be an instance ' + + 'of {{DataSource}}', ModelCtor.modelName)); ModelCtor.attachTo(config.dataSource); debug('Attached model `%s` to dataSource `%s`', modelName, config.dataSource.name); @@ -237,8 +238,8 @@ Registry.prototype.configureModel = function(ModelCtor, config) { } else { debug('Model `%s` is not attached to any DataSource, possibly by a mistake.', modelName); - console.warn( - 'The configuration of `%s` is missing `dataSource` property.\n' + + g.warn( + 'The configuration of `%s` is missing {{`dataSource`}} property.\n' + 'Use `null` or `false` to mark models not attached to any data source.', modelName); } @@ -250,7 +251,7 @@ Registry.prototype.configureModel = function(ModelCtor, config) { Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) { if (!methods) return; if (typeof methods !== 'object') { - console.warn('Ignoring non-object "methods" setting of "%s".', + g.warn('Ignoring non-object "methods" setting of "%s".', ModelCtor.modelName); return; } @@ -264,12 +265,12 @@ Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) { key = isStatic ? key : m[1]; meta.isStatic = isStatic; } else if (meta.isStatic && m) { - throw new Error('Remoting metadata for ' + ModelCtor.modelName + '.' + - key + ' "isStatic" does not match new method name-based style.'); + throw new Error(g.f('Remoting metadata for %s.%s {{"isStatic"}} does ' + + 'not match new method name-based style.', ModelCtor.modelName, key)); } else { key = isStatic ? key : m[1]; - deprecated('Remoting metadata "isStatic" is deprecated. Please ' + - 'specify "prototype.name" in method name instead for isStatic=false.'); + deprecated(g.f('Remoting metadata {{"isStatic"}} is deprecated. Please ' + + 'specify {{"prototype.name"}} in method name instead for {{isStatic=false}}.')); } ModelCtor.remoteMethod(key, meta); }); @@ -301,7 +302,7 @@ Registry.prototype.getModel = function(modelName) { var model = this.findModel(modelName); if (model) return model; - throw new Error('Model not found: ' + modelName); + throw new Error(g.f('Model not found: %s', modelName)); }; /** @@ -367,7 +368,7 @@ Registry.prototype.createDataSource = function(name, options) { }; if (ds.settings && ds.settings.defaultForType) { - var msg = 'DataSource option "defaultForType" is no longer supported'; + var msg = g.f('{{DataSource}} option {{"defaultForType"}} is no longer supported'); throw new Error(msg); } diff --git a/lib/server-app.js b/lib/server-app.js index f17ba5ef..ea0ec3d0 100644 --- a/lib/server-app.js +++ b/lib/server-app.js @@ -3,6 +3,8 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT +var g = require('strong-globalize')(); + var assert = require('assert'); var express = require('express'); var merge = require('util')._extend; @@ -188,7 +190,7 @@ proto.middleware = function(name, paths, handler) { } if (this._requestHandlingPhases.indexOf(name) === -1) - throw new Error('Unknown middleware phase ' + name); + throw new Error(g.f('Unknown {{middleware}} phase %s', name)); debug('use %s %s %s', fullPhaseName, paths, handlerName); diff --git a/package.json b/package.json index 2aca27d0..3a8ae350 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "nodemailer-stub-transport": "^1.0.0", "serve-favicon": "^2.2.0", "stable": "^0.1.5", + "strong-globalize": "^2.6.2", "strong-remoting": "^3.0.0-alpha.1", "uid2": "0.0.3", "underscore.string": "^3.0.3"