diff --git a/browser/current-context.js b/browser/current-context.js index 97d4a1a7..9963a528 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 750c21f8..ae75051f 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'); @@ -112,7 +114,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 6bde460c..e9340d76 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'); @@ -535,7 +537,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 ad19a754..cf47082d 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'); @@ -112,7 +114,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 6a6736dc..8ead34aa 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) { /** @@ -44,13 +46,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 4d9292a2..90a17c56 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'); @@ -205,14 +207,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); @@ -220,7 +222,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'; @@ -250,7 +252,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); @@ -296,7 +298,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; @@ -438,15 +440,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); @@ -501,11 +509,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'; } @@ -533,7 +541,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); @@ -545,7 +553,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); @@ -581,7 +589,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; }; @@ -640,20 +648,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.'} + {arg: 'include', type: ['string'], http: {source: 'query'}, + 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'} } @@ -662,17 +671,17 @@ 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; - var accessToken = req && req.accessToken; - var tokenID = accessToken && accessToken.id; - - return tokenID; - }, description: 'Do not supply this argument, it is automatically extracted ' + - 'from request headers.' - }], + var req = ctx && ctx.req; + var accessToken = req && req.accessToken; + var tokenID = accessToken && accessToken.id; + return tokenID; + }, description: g.f('Do not supply this argument, it is automatically extracted ' + + 'from request headers.'), + }, + ], http: {verb: 'all'} } ); @@ -680,7 +689,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}, @@ -693,7 +702,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'}} ], @@ -704,7 +713,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); @@ -722,7 +731,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 48c098f5..63cd4e2e 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 3e57b373..b7a3038c 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 fa35eacc..8cf43e7e 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 abdf34c1..fe5f4765 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,14 +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'}, function (err, result) { +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()); - - result.resetKeys(function (err, result) { - console.log(result.toObject()); - }); + }); }); diff --git a/example/simple-data-source/app.js b/example/simple-data-source/app.js index 234baeb0..3074d7b9 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 bd558efa..ac439167 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..10e29a3a --- /dev/null +++ b/intl/en/messages.json @@ -0,0 +1,115 @@ +{ + "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}", + "5858e63efaa0e4ad86b61c0459ea32fa": "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.", + "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.", + "895b1f941d026870b3cc8e6af087c197": "{{username}} or {{email}} is required", + "a50d10fc6e0959b220e085454c40381e": "User not found: {0}", + "ba96498b10c179f9cd75f75c8def4f70": "{{realm}} is required", + "c34fa20eea0091747fcc9eda204b8d37": "could not find {{accessToken}}", + "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}", + "7e287fc885d9fdcf42da3a12f38572c1": "Authorization Required", + "d5552322de5605c58b62f47ad26d2716": "{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead", + "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}.", + "0e4f89f8dde1e88cbfc6c1d88f0f77cb": "Criteria to match model instances", + "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.", + "5fa3afb425819ebde958043e598cb664": "could not find a model with {{id}} {0}", + "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}`", + "9e3cbc1d5a9347cdcf6d1b4a6dbb55b7": "Fetches belongsTo relation {0}.", + "c0057a569ff9d3b509bac61a4b2f605d": "Deletes all {0} of this model.", + "cd0412f2f33a4a2a316acc834f3f21a6": "must specify an {{id}} or {{data}}", + "d6f43b266533b04d442bdb3955622592": "Creates a new instance in {0} of this model.", + "da13d3cdf21330557254670dddd8c5c7": "Counts {0} of {1}.", + "e4434de4bb8f5a3cd1d416e4d80d7e0b": "Unknown \"{0}\" {{id}} \"{1}\".", + "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.", + "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", + "5aaa76c72ae1689fd3cf62589784a4ba": "Update attributes for a model instance and persist it into the data source.", + "5f659bbc15e6e2b249fa33b3879b5f69": "Find a model instance by {{id}} from the data source.", + "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}", + "79295ac04822d2e9702f0dd1d0240336": "Update instances of the model matched by {{where}} from the data source.", + "7f2fde7f0f860ead224b11ba8d75aa1c": "Create an update list from a delta list.", + "89b57e764c2267129294b07589dbfdc2": "Delete a model instance by {{id}} from 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.", + "c46d4aba1f14809c16730faa46933495": "Filter defining fields and include", + "c65600640f206f585d300b4bcb699d95": "Create a checkpoint.", + "cf64c7afc74d3a8120abcd028f98c770": "Update an existing model instance or insert a new one into 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.", + "f1d4ac54357cc0932f385d56814ba7e4": "Conflict", + "f37d94653793e33f4203e45b4a1cf106": "Count instances of the model matched by where from the data source.", + "0731d0109e46c21a4e34af3346ed4856": "This behaviour may change in the next major version.", + "2e110abee2c95bcfc2dafd48be7e2095": "Cannot configure {0}: {{config.dataSource}} must be an instance of {{DataSource}}", + "308e1d484516a33df788f873e65faaff": "Model `{0}` is extending deprecated `DataModel. Use `PersistedModel` instead.", + "3438fab56cc7ab92dfd88f0497e523e0": "The relations property of `{0}` configuration must be an object", + "4cac5f051ae431321673e04045d37772": "Model `{0}` is extending an unknown model `{1}`. Using `PersistedModel` as the base.", + "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.", + "a80038252430df2754884bf3c845c4cf": "Remoting metadata for \"{0}.{1}\" is missing \"isStatic\" flag, the method is registered as an instance method.", + "dc568bee32deb0f6eaf63e73b20e8ceb": "Ignoring non-object \"methods\" setting of \"{0}\".", + "3aecb24fa8bdd3f79d168761ca8a6729": "Unknown {{middleware}} phase {0}" +} diff --git a/lib/application.js b/lib/application.js index 1f7b5c64..f5df7871 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; @@ -378,17 +380,17 @@ app.enableAuth = function(options) { var messages = { 403: { - message: 'Access Denied', - code: 'ACCESS_DENIED' + message: g.f('Access Denied'), + code: 'ACCESS_DENIED', }, 404: { - message: ('could not find ' + modelName + ' with id ' + modelId), - code: 'MODEL_NOT_FOUND' + message: (g.f('could not find %s with id %s', modelName, modelId)), + code: 'MODEL_NOT_FOUND', }, 401: { - message: 'Authorization Required', - code: 'AUTHORIZATION_REQUIRED' - } + message: g.f('Authorization Required'), + code: 'AUTHORIZATION_REQUIRED', + }, }; var e = new Error(messages[errStatusCode].message || messages[403].message); @@ -408,7 +410,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) { @@ -486,7 +488,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 3271c145..439e29c2 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'); @@ -149,22 +151,22 @@ 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) { assert(transport.sendMail, '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 5ffd82fb..3b5fe4c4 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; @@ -168,14 +171,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}}'))); } }; @@ -449,8 +452,8 @@ module.exports = function(registry) { http: {verb: 'get', path: '/' + pathName}, accepts: {arg: 'refresh', type: 'boolean', http: {source: 'query'}}, accessType: 'READ', - description: 'Fetches belongsTo relation ' + relationName + '.', - returns: {arg: relationName, type: modelName, root: true} + description: g.f('Fetches belongsTo relation %s.', relationName), + returns: {arg: relationName, type: modelName, root: true}, }, fn); }; @@ -458,7 +461,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'; @@ -473,7 +476,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)} @@ -483,7 +486,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} }); @@ -492,7 +495,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} }); @@ -500,8 +503,8 @@ module.exports = function(registry) { define('__destroy__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName}, - description: 'Deletes ' + relationName + ' of this model.', - accessType: 'WRITE' + description: g.f('Deletes %s of this model.', relationName), + accessType: 'WRITE', }); }; @@ -514,9 +517,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)} @@ -526,10 +530,11 @@ module.exports = function(registry) { define('__destroyById__' + relationName, { isStatic: false, http: {verb: 'delete', path: '/' + pathName + '/:fk'}, - accepts: {arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + accepts: { arg: 'fk', type: 'any', + 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); @@ -540,11 +545,12 @@ module.exports = function(registry) { http: {verb: 'put', path: '/' + pathName + '/:fk'}, accepts: [ {arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, - http: {source: 'path'}}, - {arg: 'data', type: toModelName, http: {source: 'body'}} + 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); @@ -562,10 +568,11 @@ module.exports = function(registry) { define('__link__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + pathName + '/rel/:fk'}, - accepts: [{arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, + accepts: [{ arg: 'fk', type: 'any', + 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); @@ -575,9 +582,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); @@ -589,9 +597,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: { @@ -600,7 +609,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'; @@ -634,7 +643,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} }); @@ -643,7 +652,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} }); @@ -651,15 +660,16 @@ module.exports = function(registry) { define('__delete__' + scopeName, { isStatic: isStatic, http: {verb: 'delete', path: '/' + pathName}, - description: 'Deletes all ' + scopeName + ' of this model.', - accessType: 'WRITE' + description: g.f('Deletes all %s of this model.', scopeName), + accessType: 'WRITE', }); define('__count__' + scopeName, { 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 + '.', + accepts: {arg: 'where', type: 'object', + description: g.f('Criteria to match model instances')}, + description: g.f('Counts %s of %s.', scopeName, this.modelName), accessType: 'READ', returns: {arg: 'count', type: 'number'} }); @@ -708,9 +718,9 @@ module.exports = function(registry) { acceptArgs = [ { arg: paramName, type: 'any', http: { source: 'path' }, - description: 'Foreign key for ' + relation.name + '.', - required: true - } + description: g.f('Foreign key for %s.', relation.name), + required: true, + }, ]; } else { httpPath = pathName; @@ -738,12 +748,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 = {}; @@ -836,7 +846,8 @@ module.exports = function(registry) { }); } else { - throw new Error('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 a7b8315d..4ecfa05f 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'; @@ -558,7 +560,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', description: 'Model instance data', http: {source: 'body'}}, returns: {arg: 'data', type: typeName, root: true}, @@ -567,7 +569,8 @@ module.exports = function(registry) { setRemoting(PersistedModel, 'upsert', { aliases: ['updateOrCreate'], - description: 'Update an existing model instance or insert a new one into the data source.', + description: g.f('Update an existing model instance or insert a new one ' + + 'into the data source.'), accessType: 'WRITE', accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}}, returns: {arg: 'data', type: typeName, root: true}, @@ -575,7 +578,7 @@ module.exports = function(registry) { }); 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'}, @@ -606,13 +609,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'}, @@ -620,7 +623,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'}, returns: {arg: 'data', type: [typeName], root: true}, @@ -628,7 +631,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'}, returns: {arg: 'data', type: typeName, root: true}, @@ -637,7 +640,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: { @@ -652,17 +655,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: 'where', type: 'object', http: { source: 'query'}, + description: g.f('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 }, @@ -671,7 +674,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'}}, @@ -680,7 +683,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'}, @@ -688,7 +691,8 @@ module.exports = function(registry) { }); setRemoting(PersistedModel.prototype, 'updateAttributes', { - description: 'Update attributes for a model instance and persist it into the data source.', + description: g.f('Update 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}, @@ -697,7 +701,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'}, @@ -709,8 +713,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: 'Only return changes since this checkpoint'}, @@ -721,7 +725,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 @@ -732,14 +736,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. @@ -750,14 +754,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' }, @@ -769,8 +773,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: [ @@ -780,7 +784,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 }, @@ -806,7 +810,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'}, @@ -1148,7 +1152,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(); @@ -1216,7 +1220,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); @@ -1280,16 +1284,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)))); } }); } @@ -1362,16 +1366,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)))); } }); } @@ -1586,8 +1590,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 ccce6248..c3d97621 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'); @@ -113,11 +114,11 @@ Registry.prototype.createModel = function(name, properties, options) { if (BaseModel === undefined) { if (baseName === 'DataModel') { - console.warn('Model `%s` is extending deprecated `DataModel. ' + + g.warn('Model `%s` is extending deprecated `DataModel. ' + 'Use `PersistedModel` instead.', name); BaseModel = this.getModel('PersistedModel'); } else { - console.warn('Model `%s` is extending an unknown model `%s`. ' + + g.warn('Model `%s` is extending an unknown model `%s`. ' + 'Using `PersistedModel` as the base.', name, baseName); } } @@ -197,7 +198,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); } @@ -208,7 +209,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); } @@ -225,12 +226,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); } @@ -238,8 +239,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); @@ -249,8 +250,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); } @@ -262,7 +263,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; } @@ -270,11 +271,11 @@ Registry.prototype._defineRemoteMethods = function(ModelCtor, methods) { Object.keys(methods).forEach(function(key) { var meta = methods[key]; if (typeof meta.isStatic !== 'boolean') { - console.warn('Remoting metadata for "%s.%s" is missing "isStatic" ' + + g.warn('Remoting metadata for "%s.%s" is missing "isStatic" ' + 'flag, the method is registered as an instance method.', ModelCtor.modelName, key); - console.warn('This behaviour may change in the next major version.'); + g.warn('This behaviour may change in the next major version.'); } ModelCtor.remoteMethod(key, meta); }); @@ -306,7 +307,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)); }; /** diff --git a/lib/server-app.js b/lib/server-app.js index 290c9b5a..f6b7b125 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 bef100d6..c4534e18 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "nodemailer-stub-transport": "^1.0.0", "serve-favicon": "^2.2.0", "stable": "^0.1.5", + "strong-globalize": "^2.6.2", "strong-remoting": "^2.21.0", "uid2": "0.0.3", "underscore.string": "^3.0.3"