From 4ce5b5d3a0dd3044ceb684f552fb68957cf2b8ad Mon Sep 17 00:00:00 2001 From: Amir Jafarian Date: Fri, 22 Jul 2016 15:26:07 -0400 Subject: [PATCH] Support for globalization --- .gitignore | 3 ++ index.js | 3 ++ intl/en/messages.json | 62 ++++++++++++++++++++++++++++++ lib/connectors/memory.js | 13 ++++--- lib/connectors/transient.js | 3 +- lib/dao.js | 64 +++++++++++++++---------------- lib/datasource.js | 38 +++++++++---------- lib/hooks.js | 5 ++- lib/include.js | 12 +++--- lib/list.js | 5 ++- lib/model-builder.js | 16 ++++---- lib/model.js | 16 ++++---- lib/relation-definition.js | 75 ++++++++++++++++++------------------- lib/transaction.js | 7 ++-- lib/utils.js | 15 ++++---- lib/validations.js | 3 +- package.json | 1 + 17 files changed, 208 insertions(+), 133 deletions(-) create mode 100644 intl/en/messages.json diff --git a/.gitignore b/.gitignore index d56edcad..6677d076 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ docs/man npm-debug.log .project test/memory.json +intl/* +!intl/en/ + diff --git a/index.js b/index.js index a20d104d..5e9ad2ba 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); + exports.ModelBuilder = exports.LDL = require('./lib/model-builder.js').ModelBuilder; exports.DataSource = exports.Schema = require('./lib/datasource.js').DataSource; exports.ModelBaseClass = require('./lib/model.js'); diff --git a/intl/en/messages.json b/intl/en/messages.json new file mode 100644 index 00000000..d4c83b8e --- /dev/null +++ b/intl/en/messages.json @@ -0,0 +1,62 @@ +{ + "4c78325cedbb826db3a05bf5df0e8546": "You must provide an {{id}} when replacing!", + "a0cf0e09c26df14283223e84e6a10f00": "Could not update attributes. {{Object}} with {{id}} {0} does not exist!", + "e54d944c2a2c85a23caa86027ae307cf": "Cannot migrate models not attached to this datasource: {0}", + "e6161ae8459c79d810e2aa9d21282a39": "You must provide an {{id}} when updating attributes!", + "fca4d12faff1035d9d0438d73432571b": "Duplicate entry for {0}.{1}", + "09483e03b91c8bd58732a74b3ef1ec13": "Invalid date: {0}", + "0e88a84c6bc2638fbcc5d3ea95181f99": "Unknown property: {0}", + "416dfbb7b823f51c9f3800be81060b41": "No instance with {{id}} {0} found for {1}", + "5ec8efeb715a2c34b440f2d76e2cf87d": " {0}", + "6c3234937d69763fc7f6bcafccc59bbc": "{{Model::deleteById}} requires the {{id}} argument", + "6eb6fd4fbd73394000bc25f5776fd20c": "{{Model::exists}} requires the {{id}} argument", + "7bbbdece4eea90e42aa5c0bce295e503": "{{Model::findById}} requires the {{id}} argument", + "a829dee089c912e68c18920ba015400c": "WARNING: {{id}} property cannot be changed from {0} to {1} for model:{2} in {{'loaded'}} operation hook", + "a984a076c59e451948b2bcf7a393d860": "WARNING: {{id}} property cannot be changed from {0} to {1} for model:{2} in {{'before save'}} operation hook", + "db03083e9a768388fdbee865249ac67a": "Ignoring validation errors in {{updateOrCreate()}}:", + "e4434de4bb8f5a3cd1d416e4d80d7e0b": "Unknown \"{0}\" {{id}} \"{1}\".", + "f6e8c96c93b9c7687d6c172b3695e898": "{{id}} property ({0}) cannot be updated from {1} to {2}", + "0be2d39d225b1d8b2a0f92ad5c65c9ac": "No model specified for {{polymorphic}} {0}: {1}", + "0c4eb8b6c2ff6e51d7e195eee346ced9": "Table '{0}' does not exist.", + "2f4af31c144bbfab1bbf479866acd820": "\nWARNING: {{LoopBack}} connector \"{0}\" is not installed as any of the following modules:\n\n {1}\n\nTo fix, run:\n\n {{npm install {2} --save}}\n", + "6111399276924ffa3bc9a410cdfcb2e5": "No {{id}} name {0}", + "791ab3031a73ede03f7d6299a85e8289": "Timeout in connecting after {0} ms", + "7b277018e43d41bc445731092b91547d": "Not connected", + "a2487abefef4259c2131d96cdb8543b1": "Connection fails: {0}\nIt will be retried for the next request.", + "b15b20280211ad258d92947f05b6e4a5": "The connector has not been initialized.", + "ddf0aa14803f1c84f4a97f3803f7471c": "Class name required", + "e0e9504e137a3c3339144b51ed76fef2": "Connector is not defined correctly: it should create `{{connector}}` member of dataSource", + "ec42dca074f1818c447f7ad16e2d01af": "{0} is not provided by the attached connector", + "ba0fd8106eb54de4d003a844206431fd": "Model hook \"{0}\" is deprecated, use Operation hooks instead. {{http://docs.strongloop.com/display/LB/Operation+hooks}}", + "280f4550f90e133118955ec6f6f72830": "Discriminator type {0} specified but no model exists with such name", + "83abbb3ad105947dccbb21b4cf41e98f": "{{Relation.modelTo}} is not defined for relation {0} and is no polymorphic", + "eb56c2b0c30cf006e2df00a549ec9c2c": "Relation \"{0}\" is not defined for {1} model", + "514985b2327f061ffb1c932f6b909979": "Model {0} is not defined.", + "8091838319a5cc7d6a34af2f2a616ce9": "Property name should not be \"{{constructor}}\" in Model: {0}", + "da02dd6c53d4148320eeb31718a7aebe": "Invalid type for property {0}", + "da751a8a748adbde5b55fa83b707b4e2": "Property names containing dot(s) are not supported. Model: {0}, property: {1}", + "881e4b0cb86ed59549248ee540a9fd10": "Property name \"{{constructor}}\" is not allowed in {0} data", + "bdb11cc1c780c9ccac33c316cfdc9d82": "Type not defined for property {0}.{1}", + "cd930369e86cdd222f7bd117c6f9fa94": "Unknown default value provider {0}", + "cfee4d8149316d9a647c0885cf3cafaf": "Property names containing dot(s) are not supported. Model: {0}, dynamic property: {1}", + "0c0b867aca0973ba26e887d3337cc4ec": "{{Polymorphic}} model not found: `{0}` not set", + "2f062cbecdf24245731bddc77714c814": "Could not find \"{0}\" relation for {1}", + "3cde8cc9bca22c67278b202ab0720106": "No instance with id {0} found for {1}", + "6502a117987610380b9068ef98b1b0ee": "No record found in {0} for ({1}.{2} ,{3}.{4})", + "6fcc2ff0db7a4f490f5e0ce9e24691f3": "{{HasOne}} relation cannot create more than one instance of {0}", + "7e9530c0399289be0ee601a604be71ff": "{{BelongsTo}} relation {0} is empty", + "7faa840eb6ce11250a141deb42a6c489": "Unknown relation {{scope}}: {0}", + "89afd3a9249f5a8d3edda07d84ca049d": "{{Polymorphic}} model not found: `{0}`", + "a004f310d315e592843776fab964eaeb": "{{Polymorphic}} relations need a through model", + "a25e41a39c60c4702e55d0c3936576a1": "Key mismatch: {0}.{1}: {2}, {3}.{4}: {5}", + "a327355560d495454fba2c1aad6bdf09": "Unknown scope method: {0}", + "e08ab0e1ab55f26c357061447b635905": "No relation found in {0} for ({1}.{2},{3}.{4})", + "e55937649d8d7a11706b8cec22d02eae": "{{HasOne}} relation {0} is empty", + "2c4904377a87fdab502118719cc0d266": "{{Transaction}} is not supported", + "ecb7aa804bf54c682999d20d6436104c": "The {{transaction}} is not active: {0}", + "89bf6d92731fe7bd2146ce8d0bec205c": "Invalid argument, must be a string, {{regex}} literal, or {{RegExp}} object", + "8c5ab01638c1ac1d58168c6346a8481a": "Invalid {{regex}} flags: {0}", + "a6c18a7f4390cd3d59a2a7a047ae2aab": "Run \"{{npm install loopback-datasource-juggler}} {0}\" command ", + "b138294f132edfe1eb2a8211150c7238": "Unexpected `undefined` in query", + "8a39126103a157f501affa070367a1b0": "The {0} instance is not valid. Details: {1}." +} diff --git a/lib/connectors/memory.js b/lib/connectors/memory.js index 29fe9255..df38a387 100644 --- a/lib/connectors/memory.js +++ b/lib/connectors/memory.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 util = require('util'); var Connector = require('loopback-connector').Connector; var geo = require('../geo'); @@ -217,7 +218,7 @@ Memory.prototype._createSync = function(model, data, fn) { } if (this.collection(model)[id]) - return fn(new Error('Duplicate entry for ' + model + '.' + idName)); + return fn(new Error(g.f('Duplicate entry for %s.%s', model, idName))); this.collection(model)[id] = serialize(data); fn(null, id); @@ -711,7 +712,7 @@ Memory.prototype.update = Memory.prototype.updateAttributes = function updateAttributes(model, id, data, options, cb) { if (!id) { - var err = new Error('You must provide an id when updating attributes!'); + var err = new Error(g.f('You must provide an {{id}} when updating attributes!')); if (cb) { return cb(err); } else { @@ -730,14 +731,14 @@ Memory.prototype.updateAttributes = function updateAttributes(model, id, data, o if (modelData) { this.save(model, data, options, cb); } else { - cb(new Error('Could not update attributes. Object with id ' + id + ' does not exist!')); + cb(new Error(g.f('Could not update attributes. {{Object}} with {{id}} %s does not exist!', id))); } }; Memory.prototype.replaceById = function(model, id, data, options, cb) { var self = this; if (!id) { - var err = new Error('You must provide an id when replacing!'); + var err = new Error(g.f('You must provide an {{id}} when replacing!')); return process.nextTick(function() { cb(err); }); } // Do not modify the data object passed in arguments @@ -828,8 +829,8 @@ Memory.prototype.automigrate = function(models, cb) { if (invalidModels.length) { return process.nextTick(function() { - cb(new Error('Cannot migrate models not attached to this datasource: ' + - invalidModels.join(' '))); + cb(new Error(g.f('Cannot migrate models not attached to this datasource: %s', + invalidModels.join(' ')))); }); } diff --git a/lib/connectors/transient.js b/lib/connectors/transient.js index fa39a01e..af615a65 100644 --- a/lib/connectors/transient.js +++ b/lib/connectors/transient.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 util = require('util'); var Connector = require('loopback-connector').Connector; var utils = require('../utils'); @@ -108,7 +109,7 @@ Transient.prototype.update = Transient.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { if (!id) { - var err = new Error('You must provide an id when updating attributes!'); + var err = new Error(g.f('You must provide an {{id}} when updating attributes!')); if (cb) { return cb(err); } else { diff --git a/lib/dao.js b/lib/dao.js index 80730325..38fa3310 100644 --- a/lib/dao.js +++ b/lib/dao.js @@ -11,6 +11,7 @@ module.exports = DataAccessObject; /*! * Module dependencies */ +var g = require('strong-globalize')(); var async = require('async'); var jutil = require('./jutil'); var ValidationError = require('./validations').ValidationError; @@ -89,7 +90,7 @@ function applyStrictCheck(model, strict, data, inst, cb) { if (props[key]) { result[key] = data[key]; } else if (strict === 'throw') { - cb(new Error('Unknown property: ' + key)); + cb(new Error(g.f('Unknown property: %s', key))); return; } else if (strict === 'validate') { inst.__unknownProperties.push(key); @@ -533,8 +534,8 @@ DataAccessObject.upsert = function(data, options, cb) { return cb(new ValidationError(inst), inst); } else { // TODO(bajtos) Remove validateUpsert:undefined in v3.0 - console.warn('Ignoring validation errors in updateOrCreate():'); - console.warn(' %s', new ValidationError(inst).message); + g.warn('Ignoring validation errors in {{updateOrCreate()}}:'); + g.warn(' %s', new ValidationError(inst).message); // continue with updateOrCreate } } @@ -1049,7 +1050,7 @@ DataAccessObject.exists = function exists(id, options, cb) { }); } else { process.nextTick(function() { - cb(new Error('Model::exists requires the id argument')); + cb(new Error(g.f('{{Model::exists}} requires the {{id}} argument'))); }); } return cb.promise; @@ -1111,7 +1112,7 @@ DataAccessObject.findById = function findById(id, filter, options, cb) { return cb.promise; } else if (id == null || id === '') { process.nextTick(function() { - cb(new Error('Model::findById requires the id argument')); + cb(new Error(g.f('{{Model::findById}} requires the {{id}} argument'))); }); } else { var query = byIdQuery(this, id); @@ -1184,7 +1185,7 @@ function convertNullToNotFoundError(ctx, cb) { 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; cb(error); @@ -1242,7 +1243,7 @@ DataAccessObject._normalize = function(filter) { } var err = null; if ((typeof filter !== 'object') || Array.isArray(filter)) { - err = new Error(util.format('The query filter %j is not an object', filter)); + err = new Error(g.f(util.format('The query filter %j is not an {{object}}', filter))); err.statusCode = 400; throw err; } @@ -1250,14 +1251,14 @@ DataAccessObject._normalize = function(filter) { var limit = Number(filter.limit || 100); var offset = Number(filter.skip || filter.offset || 0); if (isNaN(limit) || limit <= 0 || Math.ceil(limit) !== limit) { - err = new Error(util.format('The limit parameter %j is not valid', - filter.limit)); + err = new Error(g.f(util.format('The {{limit}} parameter %j is not valid', + filter.limit))); err.statusCode = 400; throw err; } if (isNaN(offset) || offset < 0 || Math.ceil(offset) !== offset) { - err = new Error(util.format('The offset/skip parameter %j is not valid', - filter.skip || filter.offset)); + err = new Error(g.f(util.format('The {{offset/skip}} parameter %j is not valid', + filter.skip || filter.offset))); err.statusCode = 400; throw err; } @@ -1288,7 +1289,7 @@ DataAccessObject._normalize = function(filter) { if (dir === 'ASC' || dir === 'DESC') { token = parts[0] + ' ' + dir; } else { - err = new Error(util.format('The order %j has invalid direction', token)); + err = new Error(g.f(util.format('The {{order}} %j has invalid direction', token))); err.statusCode = 400; throw err; } @@ -1324,7 +1325,7 @@ DataAccessObject._normalize = function(filter) { function DateType(arg) { var d = new Date(arg); if (isNaN(d.getTime())) { - throw new Error('Invalid date: ' + arg); + throw new Error(g.f('Invalid date: %s', arg)); } return d; } @@ -1365,7 +1366,7 @@ DataAccessObject._coerce = function(where) { var err; if (typeof where !== 'object' || Array.isArray(where)) { - err = new Error(util.format('The where clause %j is not an object', where)); + err = new Error(g.f(util.format('The where clause %j is not an {{object}}', where))); err.statusCode = 400; throw err; } @@ -1380,7 +1381,7 @@ DataAccessObject._coerce = function(where) { self._coerce(clauses[k]); } } else { - err = new Error(util.format('The %s operator has invalid clauses %j', p, clauses)); + err = new Error(g.f(util.format('The %s operator has invalid clauses %j', p, clauses))); err.statusCode = 400; throw err; } @@ -1439,14 +1440,14 @@ DataAccessObject._coerce = function(where) { case 'inq': case 'nin': if (!Array.isArray(val)) { - err = new Error(util.format('The %s property has invalid clause %j', p, where[p])); + err = new Error(g.f(util.format('The %s property has invalid clause %j', p, where[p]))); err.statusCode = 400; throw err; } break; case 'between': if (!Array.isArray(val) || val.length !== 2) { - err = new Error(util.format('The %s property has invalid clause %j', p, where[p])); + err = new Error(g.f(util.format('The %s property has invalid clause %j', p, where[p]))); err.statusCode = 400; throw err; } @@ -1454,7 +1455,7 @@ DataAccessObject._coerce = function(where) { case 'like': case 'nlike': if (!(typeof val === 'string' || val instanceof RegExp)) { - err = new Error(util.format('The %s property has invalid clause %j', p, where[p])); + err = new Error(g.f(util.format('The %s property has invalid clause %j', p, where[p]))); err.statusCode = 400; throw err; } @@ -2011,7 +2012,7 @@ DataAccessObject.deleteById = function deleteById(id, options, cb) { return cb.promise; } else if (id == null || id === '') { process.nextTick(function() { - cb(new Error('Model::deleteById requires the id argument')); + cb(new Error(g.f('{{Model::deleteById}} requires the {{id}} argument'))); }); return cb.promise; } @@ -2022,7 +2023,7 @@ DataAccessObject.deleteById = function deleteById(id, options, cb) { if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { - err = new Error('No instance with id ' + id + ' found for ' + Model.modelName); + err = new Error(g.f('No instance with {{id}} %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); @@ -2485,7 +2486,7 @@ DataAccessObject.prototype.remove = if (err) return cb(err, false); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { - err = new Error('No instance with id ' + id + ' found for ' + Model.modelName); + err = new Error(g.f('No instance with {{id}} %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err, false); @@ -2509,7 +2510,7 @@ DataAccessObject.prototype.remove = if (err) return cb(err); var deleted = info && info.count > 0; if (Model.settings.strictDelete && !deleted) { - err = new Error('No instance with id ' + id + ' found for ' + Model.modelName); + err = new Error(g.f('No instance with {{id}} %s found for %s', id, Model.modelName)); err.code = 'NOT_FOUND'; err.statusCode = 404; return cb(err); @@ -2654,8 +2655,8 @@ DataAccessObject.replaceById = function(id, data, options, cb) { var hookState = {}; if (id !== data[pkName]) { - var err = new Error('id property (' + pkName + ') ' + - 'cannot be updated from ' + id + ' to ' + data[pkName]); + var err = new Error(g.f('{{id}} property (%s) ' + + 'cannot be updated from %s to %s', pkName, id, data[pkName])); err.statusCode = 400; process.nextTick(function() { cb(err); }); return cb.promise; @@ -2674,9 +2675,8 @@ DataAccessObject.replaceById = function(id, data, options, cb) { if (ctx.instance[pkName] !== id && !Model._warned.cannotOverwritePKInBeforeSaveHook) { Model._warned.cannotOverwritePKInBeforeSaveHook = true; - console.warn('WARNING: id property cannot be changed from ' + - id + ' to ' + inst[pkName] + ' for model:' + Model.modelName + - ' in \'before save\' operation hook'); + g.warn('WARNING: {{id}} property cannot be changed from %s to %s for model:%s ' + + 'in {{\'before save\'}} operation hook', id, inst[pkName], Model.modelName); } data = inst.toObject(false); @@ -2732,9 +2732,9 @@ DataAccessObject.replaceById = function(id, data, options, cb) { if (ctx.data[pkName] !== id && !Model._warned.cannotOverwritePKInLoadedHook) { Model._warned.cannotOverwritePKInLoadedHook = true; - console.warn('WARNING: id property cannot be changed from ' + - id + ' to ' + ctx.data[pkName] + ' for model:' + Model.modelName + - ' in \'loaded\' operation hook'); + g.warn('WARNING: {{id}} property cannot be changed from %s to %s for model:%s in ' + + '{{\'loaded\'}} operation hook', + id, ctx.data[pkName], Model.modelName); } inst.__persisted = true; @@ -2841,8 +2841,8 @@ function(data, options, cb) { for (var i = 0, n = idNames.length; i < n; i++) { var idName = idNames[i]; if (data[idName] !== undefined && !idEquals(data[idName], inst[idName])) { - var err = new Error('id property (' + idName + ') ' + - 'cannot be updated from ' + inst[idName] + ' to ' + data[idName]); + var err = new Error(g.f('{{id}} property (%s) ' + + 'cannot be updated from %s to %s'), idName, inst[idName], data[idName]); err.statusCode = 400; process.nextTick(function() { cb(err); diff --git a/lib/datasource.js b/lib/datasource.js index 34c76c56..b2efd3c8 100644 --- a/lib/datasource.js +++ b/lib/datasource.js @@ -20,6 +20,7 @@ var util = require('util'); var assert = require('assert'); var async = require('async'); var traverse = require('traverse'); +var g = require('strong-globalize')(); if (process.env.DEBUG === 'loopback') { // For back-compatibility @@ -237,8 +238,8 @@ DataSource._resolveConnector = function(name, loader) { var connector = tryModules(names, loader); var error = null; if (!connector) { - error = util.format('\nWARNING: LoopBack connector "%s" is not installed ' + - 'as any of the following modules:\n\n %s\n\nTo fix, run:\n\n npm install %s --save\n', + error = g.f('\nWARNING: {{LoopBack}} connector "%s" is not installed ' + + 'as any of the following modules:\n\n %s\n\nTo fix, run:\n\n {{npm install %s --save}}\n', name, names.join('\n'), names[names.length - 1]); } return { @@ -316,16 +317,15 @@ DataSource.prototype.setup = function(name, settings) { this._setupConnector(); // we have an connector now? if (!this.connector) { - throw new Error('Connector is not defined correctly: ' + - 'it should create `connector` member of dataSource'); + throw new Error(g.f('Connector is not defined correctly: ' + + 'it should create `{{connector}}` member of dataSource')); } this.connected = !err; // Connected now if (this.connected) { this.emit('connected'); } else { // The connection fails, let's report it and hope it will be recovered in the next call - console.error('Connection fails: ', err, - '\nIt will be retried for the next request.'); + console.error(g.f('Connection fails: %s\nIt will be retried for the next request.', err)); this.emit('error', err); this.connecting = false; } @@ -458,7 +458,7 @@ DataSource.prototype.defineRelations = function(modelClass, relations) { var targetModel, polymorphicName; if (r.polymorphic && r.type !== 'belongsTo' && !r.model) { - throw new Error('No model specified for polymorphic ' + r.type + ': ' + rn); + throw new Error(g.f('No model specified for {{polymorphic}} %s: %s', r.type, rn)); } if (r.polymorphic) { @@ -597,7 +597,7 @@ DataSource.prototype.define = function defineClass(className, properties, settin var args = slice.call(arguments); if (!className) { - throw new Error('Class name required'); + throw new Error(g.f('Class name required')); } if (args.length === 1) { properties = {}; @@ -817,8 +817,8 @@ DataSource.prototype.automigrate = function(models, cb) { if (invalidModels.length) { process.nextTick(function() { - cb(new Error('Cannot migrate models not attached to this datasource: ' + - invalidModels.join(' '))); + cb(new Error(g.f('Cannot migrate models not attached to this datasource: %s', + invalidModels.join(' ')))); }); return cb.promise; } @@ -872,8 +872,8 @@ DataSource.prototype.autoupdate = function(models, cb) { if (invalidModels.length) { process.nextTick(function() { - cb(new Error('Cannot migrate models not attached to this datasource: ' + - invalidModels.join(' '))); + cb(new Error(g.f('Cannot migrate models not attached to this datasource: %s', + invalidModels.join(' ')))); }); return cb.promise; } @@ -1316,7 +1316,7 @@ DataSource.prototype.discoverSchemas = function(modelName, options, cb) { var columns = results[0]; if (!columns || columns.length === 0) { - cb(new Error('Table \'' + modelName + '\' does not exist.')); + cb(new Error(g.f('Table \'%s\' does not exist.', modelName))); return cb.promise; } @@ -1716,7 +1716,7 @@ DataSource.prototype.log = function(sql, t) { */ DataSource.prototype.freeze = function freeze() { if (!this.connector) { - throw new Error('The connector has not been initialized.'); + throw new Error(g.f('The connector has not been initialized.')); } if (this.connector.freezeDataSource) { this.connector.freezeDataSource(); @@ -1779,7 +1779,7 @@ DataSource.prototype.idColumnName = function(modelName) { */ DataSource.prototype.idName = function(modelName) { if (!this.getModelDefinition(modelName).idName) { - console.error('No id name', this.getModelDefinition(modelName)); + g.error('No {{id}} name %s', this.getModelDefinition(modelName)); } return this.getModelDefinition(modelName).idName(); }; @@ -1968,7 +1968,7 @@ DataSource.prototype.enableRemote = function(operation) { if (op) { op.remoteEnabled = true; } else { - throw new Error(operation + ' is not provided by the attached connector'); + throw new Error(g.f('%s is not provided by the attached connector', operation)); } }; @@ -1997,7 +1997,7 @@ DataSource.prototype.disableRemote = function(operation) { if (op) { op.remoteEnabled = false; } else { - throw new Error(operation + ' is not provided by the attached connector'); + throw new Error(g.f('%s is not provided by the attached connector', operation)); } }; @@ -2129,7 +2129,7 @@ DataSource.prototype.ready = function(obj, args) { var params = [].slice.call(args); var cb = params.pop(); if (typeof cb === 'function') { - cb(new Error('Timeout in connecting after ' + timeout + ' ms')); + cb(new Error(g.f('Timeout in connecting after %s ms', timeout))); } }, timeout); @@ -2151,7 +2151,7 @@ DataSource.prototype.ping = function(cb) { self.discoverModelProperties('dummy', {}, cb); } else { process.nextTick(function() { - var err = self.connected ? null : new Error('Not connected'); + var err = self.connected ? null : new Error(g.f('Not connected')); cb(err); }); } diff --git a/lib/hooks.js b/lib/hooks.js index fa7a18d9..1d809870 100644 --- a/lib/hooks.js +++ b/lib/hooks.js @@ -4,6 +4,7 @@ // License text available at https://opensource.org/licenses/MIT var deprecated = require('depd')('loopback-datasource-juggler'); +var g = require('strong-globalize')(); /*! * Module exports @@ -94,7 +95,7 @@ function deprecateHook(ctor, prefixes, capitalizedName) { var hookName = candidateNames.filter(function(hook) { return !!ctor[hook]; })[0]; if (!hookName) return; // just to be sure, this should never happen if (ctor.modelName) hookName = ctor.modelName + '.' + hookName; - deprecated('Model hook "' + hookName + '" is deprecated, ' + + deprecated(g.f('Model hook "%s" is deprecated, ' + 'use Operation hooks instead. ' + - 'http://docs.strongloop.com/display/LB/Operation+hooks'); + '{{http://docs.strongloop.com/display/LB/Operation+hooks}}', hookName)); } diff --git a/lib/include.js b/lib/include.js index 7dee04e9..c9882813 100644 --- a/lib/include.js +++ b/lib/include.js @@ -4,6 +4,7 @@ // License text available at https://opensource.org/licenses/MIT var async = require('async'); +var g = require('strong-globalize')(); var utils = require('./utils'); var List = require('./list'); var includeUtils = require('./include_utils'); @@ -284,8 +285,7 @@ Inclusion.include = function(objects, include, options, cb) { var relation = relations[relationName]; if (!relation) { - cb(new Error('Relation "' + relationName + '" is not defined for ' + - self.modelName + ' model')); + cb(new Error(g.f('Relation "%s" is not defined for %s model', relationName, self.modelName))); return; } var polymorphic = relation.polymorphic; @@ -296,8 +296,8 @@ Inclusion.include = function(objects, include, options, cb) { //} if (!relation.modelTo) { if (!relation.polymorphic) { - cb(new Error('Relation.modelTo is not defined for relation' + - relationName + ' and is no polymorphic')); + cb(new Error(g.f('{{Relation.modelTo}} is not defined for relation %s and is no polymorphic', + relationName))); return; } } @@ -738,8 +738,8 @@ Inclusion.include = function(objects, include, options, cb) { var Model = lookupModel(relation.modelFrom.dataSource.modelBuilder. models, modelType); if (!Model) { - callback(new Error('Discriminator type "' + modelType + - ' specified but no model exists with such name')); + callback(new Error(g.f('Discriminator type %s specified but no model exists with such name', + modelType))); return; } relation.applyScope(null, typeFilter); diff --git a/lib/list.js b/lib/list.js index 94d211a5..6fc6e63a 100644 --- a/lib/list.js +++ b/lib/list.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 util = require('util'); var Any = require('./types').Types.Any; @@ -18,7 +19,7 @@ function List(items, itemType, parent) { try { items = JSON.parse(items); } catch (e) { - var err = new Error(util.format('could not create List from JSON string: %j', items)); + var err = new Error(g.f(util.format('could not create List from {{JSON}} string: %j', items))); err.statusCode = 400; throw err; } @@ -29,7 +30,7 @@ function List(items, itemType, parent) { items = items || []; if (!Array.isArray(items)) { - var err = new Error(util.format('Items must be an array: %j', items)); + var err = new Error(g.f(util.format('Items must be an array: %j', items))); err.statusCode = 400; throw err; } diff --git a/lib/model-builder.js b/lib/model-builder.js index 809f2252..677de9ab 100644 --- a/lib/model-builder.js +++ b/lib/model-builder.js @@ -7,6 +7,7 @@ * Module dependencies */ +var g = require('strong-globalize')(); var inflection = require('inflection'); var EventEmitter = require('events').EventEmitter; var util = require('util'); @@ -125,7 +126,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett var pathName = httpOptions.path || pluralName; if (!className) { - throw new Error('Class name required'); + throw new Error(g.f('Class name required')); } if (args.length === 1) { properties = {}; @@ -178,7 +179,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett return new ModelConstructor(data, options); } if (ModelClass.settings.unresolved) { - throw new Error('Model ' + ModelClass.modelName + ' is not defined.'); + throw new Error(g.f('Model %s is not defined.', ModelClass.modelName)); } ModelBaseClass.apply(this, arguments); }; @@ -248,14 +249,13 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett // Throw error for properties with unsupported names if (/\./.test(p)) { - throw new Error('Property names containing dot(s) are not supported. ' + - 'Model: ' + className + ', property: ' + p); + throw new Error(g.f('Property names containing dot(s) are not supported. ' + + 'Model: %s, property: %s', className, p)); } // Warn if property name is 'constructor' if (p === 'constructor') { - deprecated('Property name should not be "constructor" in Model: ' + - className); + deprecated(g.f('Property name should not be "{{constructor}}" in Model: %s', className)); } } @@ -418,7 +418,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett var prop = properties[propertyName]; var DataType = prop.type; if (!DataType) { - throw new Error('Invalid type for property ' + propertyName); + throw new Error(g.f('Invalid type for property %s', propertyName)); } if (prop.required) { @@ -527,7 +527,7 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett function DateType(arg) { var d = new Date(arg); if (isNaN(d.getTime())) { - throw new Error('Invalid date: ' + arg); + throw new Error(g.f('Invalid date: %s', arg)); } return d; } diff --git a/lib/model.js b/lib/model.js index 02284857..678fc2e1 100644 --- a/lib/model.js +++ b/lib/model.js @@ -12,6 +12,7 @@ module.exports = ModelBaseClass; * Module dependencies */ +var g = require('strong-globalize')(); var util = require('util'); var jutil = require('./jutil'); var List = require('./list'); @@ -68,7 +69,7 @@ ModelBaseClass.prototype._initProperties = function(data, options) { if (typeof data !== 'undefined' && typeof (data.constructor) !== 'function') { - throw new Error('Property name "constructor" is not allowed in ' + ctor.modelName + ' data'); + throw new Error(g.f('Property name "{{constructor}}" is not allowed in %s data', ctor.modelName)); } if (data instanceof ctor) { @@ -232,13 +233,14 @@ ModelBaseClass.prototype._initProperties = function(data, options) { // Throw error for properties with unsupported names if (/\./.test(p)) { - throw new Error( + throw new Error(g.f( 'Property names containing dot(s) are not supported. ' + - 'Model: ' + this.constructor.modelName + ', dynamic property: ' + - p); + 'Model: %s, dynamic property: %s', + this.constructor.modelName, p + )); } } else if (strict === 'throw') { - throw new Error('Unknown property: ' + p); + throw new Error(g.f('Unknown property: %s', p)); } else if (strict === 'validate') { this.__unknownProperties.push(p); } @@ -302,7 +304,7 @@ ModelBaseClass.prototype._initProperties = function(data, options) { break; default: // TODO Support user-provided functions via a registry of functions - console.warn('Unknown default value provider ' + defn); + g.warn('Unknown default value provider %s', defn); } // FIXME: We should coerce the value // will implement it after we refactor the PropertyDefinition @@ -362,7 +364,7 @@ ModelBaseClass.getPropertyType = function(propName) { return null; } if (!prop.type) { - throw new Error('Type not defined for property ' + this.modelName + '.' + propName); + throw new Error(g.f('Type not defined for property %s.%s', this.modelName, propName)); // return null; } return prop.type.name; diff --git a/lib/relation-definition.js b/lib/relation-definition.js index 61e18729..00f5f3bd 100644 --- a/lib/relation-definition.js +++ b/lib/relation-definition.js @@ -12,6 +12,7 @@ var async = require('async'); var utils = require('./utils'); var i8n = require('inflection'); var defineScope = require('./scope.js').defineScope; +var g = require('strong-globalize')(); var mergeQuery = utils.mergeQuery; var idEquals = utils.idEquals; var ModelBaseClass = require('./model.js'); @@ -176,7 +177,7 @@ RelationDefinition.prototype.defineMethod = function(name, fn) { var method; if (definition.multiple) { var scope = this.modelFrom.scopes[this.name]; - if (!scope) throw new Error('Unknown relation scope: ' + this.name); + if (!scope) throw new Error(g.f('Unknown relation {{scope}}: %s', this.name)); method = scope.defineMethod(name, function() { var relation = new relationClass(definition, this); return fn.apply(relation, arguments); @@ -301,7 +302,7 @@ Relation.prototype.callScopeMethod = function(methodName) { if (rel && typeof rel[methodName] === 'function') { return rel[methodName].apply(rel, args); } else { - throw new Error('Unknown scope method: ' + methodName); + throw new Error(g.f('Unknown scope method: %s', methodName)); } }; @@ -543,7 +544,7 @@ function lookupModelTo(modelFrom, modelTo, params, singularize) { modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName) || modelTo; } if (typeof modelTo !== 'function') { - throw new Error('Could not find "' + params.as + '" relation for ' + modelFrom.modelName); + throw new Error(g.f('Could not find "%s" relation for %s', params.as, modelFrom.modelName)); } } return modelTo; @@ -784,7 +785,7 @@ HasMany.prototype.findById = function(fkId, options, cb) { return cb(err); } if (!inst) { - err = new Error('No instance with id ' + fkId + ' found for ' + modelTo.modelName); + err = new Error(g.f('No instance with {{id}} %s found for %s', fkId, modelTo.modelName)); err.statusCode = 404; return cb(err); } @@ -792,9 +793,8 @@ HasMany.prototype.findById = function(fkId, options, cb) { if (inst[fk] != null && idEquals(inst[fk], modelInstance[pk])) { cb(null, inst); } else { - err = new Error('Key mismatch: ' + modelFrom.modelName + '.' + pk + - ': ' + modelInstance[pk] + - ', ' + modelTo.modelName + '.' + fk + ': ' + inst[fk]); + err = new Error(g.f('Key mismatch: %s.%s: %s, %s.%s: %s', + modelFrom.modelName, pk, modelInstance[pk], modelTo.modelName, fk, inst[fk])); err.statusCode = 400; cb(err); } @@ -929,10 +929,10 @@ HasManyThrough.prototype.findById = function(fkId, options, cb) { self.exists(fkId, options, function(err, exists) { if (err || !exists) { if (!err) { - err = new Error('No relation found in ' + modelThrough.modelName + - ' for (' + self.definition.modelFrom.modelName + '.' + - modelInstance[pk] + ',' + - modelTo.modelName + '.' + fkId + ')'); + err = new Error(g.f('No relation found in %s' + + ' for (%s.%s,%s.%s)', + modelThrough.modelName, self.definition.modelFrom.modelName, + modelInstance[pk], modelTo.modelName, fkId)); err.statusCode = 404; } return cb(err); @@ -942,7 +942,7 @@ HasManyThrough.prototype.findById = function(fkId, options, cb) { return cb(err); } if (!inst) { - err = new Error('No instance with id ' + fkId + ' found for ' + modelTo.modelName); + err = new Error(g.f('No instance with id %s found for %s', fkId, modelTo.modelName)); err.statusCode = 404; return cb(err); } @@ -974,10 +974,9 @@ HasManyThrough.prototype.destroyById = function(fkId, options, cb) { self.exists(fkId, options, function(err, exists) { if (err || !exists) { if (!err) { - err = new Error('No record found in ' + modelThrough.modelName + - ' for (' + self.definition.modelFrom.modelName + '.' + - modelInstance[pk] + ' ,' + - modelTo.modelName + '.' + fkId + ')'); + err = new Error(g.f('No record found in %s for (%s.%s ,%s.%s)', + modelThrough.modelName, self.definition.modelFrom.modelName, + modelInstance[pk], modelTo.modelName, fkId)); err.statusCode = 404; } return cb(err); @@ -1355,7 +1354,7 @@ BelongsTo.prototype.update = function(targetModelData, options, cb) { if (inst instanceof ModelBaseClass) { inst.updateAttributes(targetModelData, options, cb); } else { - cb(new Error('BelongsTo relation ' + definition.name + ' is empty')); + cb(new Error(g.f('{{BelongsTo}} relation %s is empty', definition.name))); } }); return cb.promise; @@ -1367,7 +1366,7 @@ BelongsTo.prototype.destroy = function(options, cb) { options = {}; } - var definition = this.definition; + var definition = this.definition; var modelInstance = this.modelInstance; var fk = definition.keyFrom; @@ -1381,7 +1380,7 @@ BelongsTo.prototype.destroy = function(options, cb) { cb && cb(err, targetModel); }); } else { - cb(new Error('BelongsTo relation ' + definition.name + ' is empty')); + cb(new Error(g.f('{{BelongsTo}} relation %s is empty', definition.name))); } }); return cb.promise; @@ -1450,12 +1449,12 @@ BelongsTo.prototype.related = function(condOrRefresh, options, cb) { if (discriminator) { var modelToName = modelInstance[discriminator]; if (typeof modelToName !== 'string') { - throw new Error('Polymorphic model not found: `' + discriminator + '` not set'); + throw new Error(g.f('{{Polymorphic}} model not found: `%s` not set', discriminator)); } modelToName = modelToName.toLowerCase(); modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName); if (!modelTo) { - throw new Error('Polymorphic model not found: `' + modelToName + '`'); + throw new Error(g.f('{{Polymorphic}} model not found: `%s`', modelToName)); } } @@ -1489,10 +1488,9 @@ BelongsTo.prototype.related = function(condOrRefresh, options, cb) { self.resetCache(inst); cb(null, inst); } else { - err = new Error('Key mismatch: ' + - self.definition.modelFrom.modelName + '.' + fk + - ': ' + modelInstance[fk] + - ', ' + modelTo.modelName + '.' + pk + ': ' + inst[pk]); + err = new Error(g.f('Key mismatch: %s.%s: %s, %s.%s: %s', + self.definition.modelFrom.modelName, fk, modelInstance[fk], + modelTo.modelName, pk, inst[pk])); err.statusCode = 400; cb(err); } @@ -1552,7 +1550,7 @@ RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, var models = modelFrom.dataSource.modelBuilder.models; if (!params.through) { - if (params.polymorphic) throw new Error('Polymorphic relations need a through model'); + if (params.polymorphic) throw new Error(g.f('{{Polymorphic}} relations need a through model')); var name1 = modelFrom.modelName + modelTo.modelName; var name2 = modelTo.modelName + modelFrom.modelName; params.through = lookupModel(models, name1) || lookupModel(models, name2) || @@ -1721,9 +1719,9 @@ HasOne.prototype.create = function(targetModelData, options, cb) { self.resetCache(targetModel); cb && cb(err, targetModel); } else { - cb && cb(new Error( - 'HasOne relation cannot create more than one instance of ' + - modelTo.modelName)); + cb && cb(new Error(g.f( + '{{HasOne}} relation cannot create more than one instance of %s', + modelTo.modelName))); } }); return cb.promise; @@ -1743,7 +1741,7 @@ HasOne.prototype.update = function(targetModelData, options, cb) { delete targetModelData[fk]; targetModel.updateAttributes(targetModelData, cb); } else { - cb(new Error('HasOne relation ' + definition.name + ' is empty')); + cb(new Error(g.f('{{HasOne}} relation %s is empty', definition.name))); } }); return cb.promise; @@ -1761,7 +1759,7 @@ HasOne.prototype.destroy = function(options, cb) { if (targetModel instanceof ModelBaseClass) { targetModel.destroy(options, cb); } else { - cb(new Error('HasOne relation ' + definition.name + ' is empty')); + cb(new Error(g.f('{{HasOne}} relation %s is empty', definition.name))); } }); return cb.promise; @@ -1898,10 +1896,9 @@ HasOne.prototype.related = function(condOrRefresh, options, cb) { self.resetCache(inst); cb(null, inst); } else { - err = new Error('Key mismatch: ' + - self.definition.modelFrom.modelName + '.' + pk + - ': ' + modelInstance[pk] + - ', ' + modelTo.modelName + '.' + fk + ': ' + inst[fk]); + err = new Error(g.f('Key mismatch: %s.%s: %s, %s.%s: %s', + self.definition.modelFrom.modelName, pk, modelInstance[pk], + modelTo.modelName, fk, inst[fk])); err.statusCode = 400; cb(err); } @@ -3169,7 +3166,7 @@ ReferencesMany.prototype.findById = function(fkId, options, cb) { var inst = instances[0]; if (!inst) { - err = new Error('No instance with id ' + fkId + ' found for ' + modelTo.modelName); + err = new Error(g.f('No instance with {{id}} %s found for %s', fkId, modelTo.modelName)); err.statusCode = 404; return cb(err); } @@ -3178,9 +3175,9 @@ ReferencesMany.prototype.findById = function(fkId, options, cb) { if (utils.findIndexOf(ids, inst[pk], idEquals) > -1) { cb(null, inst); } else { - err = new Error('Key mismatch: ' + modelFrom.modelName + '.' + fk + - ': ' + modelInstance[fk] + - ', ' + modelTo.modelName + '.' + pk + ': ' + inst[pk]); + err = new Error(g.f('Key mismatch: %s.%s: %s, %s.%s: %s', + modelFrom.modelName, fk, modelInstance[fk], + modelTo.modelName, pk, inst[pk])); err.statusCode = 400; cb(err); } diff --git a/lib/transaction.js b/lib/transaction.js index 631d15f2..db8312f9 100644 --- a/lib/transaction.js +++ b/lib/transaction.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 debug = require('debug')('loopback:connector:transaction'); var uuid = require('node-uuid'); var utils = require('./utils'); @@ -94,7 +95,7 @@ TransactionMixin.beginTransaction = function(options, cb) { }); } else { process.nextTick(function() { - var err = new Error('Transaction is not supported'); + var err = new Error(g.f('{{Transaction}} is not supported')); cb(err); }); } @@ -115,7 +116,7 @@ if (Transaction) { // Report an error if the transaction is not active if (!self.connection) { process.nextTick(function() { - cb(new Error('The transaction is not active: ' + self.id)); + cb(new Error(g.f('The {{transaction}} is not active: %s', self.id))); }); return cb.promise; } @@ -149,7 +150,7 @@ if (Transaction) { // Report an error if the transaction is not active if (!self.connection) { process.nextTick(function() { - cb(new Error('The transaction is not active: ' + self.id)); + cb(new Error(g.f('The {{transaction}} is not active: %s', self.id))); }); return cb.promise; } diff --git a/lib/utils.js b/lib/utils.js index ed2fabfd..1d285be2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -22,6 +22,7 @@ exports.hasRegExpFlags = hasRegExpFlags; exports.idEquals = idEquals; exports.findIndexOf = findIndexOf; +var g = require('strong-globalize')(); var traverse = require('traverse'); var assert = require('assert'); var Promise = require('bluebird'); @@ -30,9 +31,9 @@ function safeRequire(module) { try { return require(module); } catch (e) { - console.log('Run "npm install loopback-datasource-juggler ' + module + - '" command to use loopback-datasource-juggler using ' + module + - ' database engine'); + g.log('Run "{{npm install loopback-datasource-juggler}} %s" command ', + 'to use {{loopback-datasource-juggler}} using %s database engine', + module, module); process.exit(1); } } @@ -313,7 +314,7 @@ function removeUndefined(query, handleUndefined) { this.update(null); break; case 'throw': - throw new Error('Unexpected `undefined` in query'); + throw new Error(g.f('Unexpected `undefined` in query')); break; case 'ignore': default: @@ -518,8 +519,8 @@ function toRegExp(regex) { var isRegExp = regex instanceof RegExp; if (!(isString || isRegExp)) - return new Error('Invalid argument, must be a string, regex literal, or ' + - 'RegExp object'); + return new Error(g.f('Invalid argument, must be a string, {{regex}} literal, or ' + + '{{RegExp}} object')); if (isRegExp) return regex; @@ -538,7 +539,7 @@ function toRegExp(regex) { var hasInvalidFlags = invalidFlags.length > 0; if (hasInvalidFlags) - return new Error('Invalid regex flags: ' + invalidFlags); + return new Error(g.f('Invalid {{regex}} flags: %s', invalidFlags)); // strip regex delimiter forward slashes var expression = regex.substr(1, regex.lastIndexOf('/') - 1); diff --git a/lib/validations.js b/lib/validations.js index 62f6d048..4dadc7c1 100644 --- a/lib/validations.js +++ b/lib/validations.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 util = require('util'); var extend = util._extend; @@ -767,7 +768,7 @@ function ValidationError(obj) { this.name = 'ValidationError'; var context = obj && obj.constructor && obj.constructor.modelName; - this.message = util.format( + this.message = g.f( 'The %s instance is not valid. Details: %s.', context ? '`' + context + '`' : 'model', formatErrors(obj.errors, obj.toJSON()) || '(unknown)' diff --git a/package.json b/package.json index 83faba44..8d91f9aa 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "loopback-connector": "^2.1.0", "node-uuid": "^1.4.2", "qs": "^3.1.0", + "strong-globalize": "^2.5.8", "traverse": "^0.6.6" }, "license": "MIT"