Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added fallback to default language if any #5

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions Gruntfile.coffee

This file was deleted.

39 changes: 39 additions & 0 deletions lib/mongoose-i18n.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// <reference types="mongoose/types/aggregate" />
/// <reference types="mongoose/types/callback" />
/// <reference types="mongoose/types/collection" />
/// <reference types="mongoose/types/connection" />
/// <reference types="mongoose/types/cursor" />
/// <reference types="mongoose/types/document" />
/// <reference types="mongoose/types/error" />
/// <reference types="mongoose/types/expressions" />
/// <reference types="mongoose/types/helpers" />
/// <reference types="mongoose/types/middlewares" />
/// <reference types="mongoose/types/indexes" />
/// <reference types="mongoose/types/models" />
/// <reference types="mongoose/types/mongooseoptions" />
/// <reference types="mongoose/types/pipelinestage" />
/// <reference types="mongoose/types/populate" />
/// <reference types="mongoose/types/query" />
/// <reference types="mongoose/types/schemaoptions" />
/// <reference types="mongoose/types/schematypes" />
/// <reference types="mongoose/types/session" />
/// <reference types="mongoose/types/types" />
/// <reference types="mongoose/types/utility" />
/// <reference types="mongoose/types/validation" />
/// <reference types="mongoose/types/virtuals" />
/// <reference types="mongoose/types/inferschematype" />
import { Schema, ToObjectOptions } from "mongoose";
export interface I18nMethods<T> {
toObjectTranslated(opts?: ToObjectOptions & {
language?: string;
}): T;
toJSONTranslated(opts?: ToObjectOptions & {
language?: string;
}): T;
}
type PluginOptions = {
languages: string[];
defaultLanguage?: string;
};
declare const plugin: (schema: Schema, options: PluginOptions) => void;
export default plugin;
265 changes: 153 additions & 112 deletions lib/mongoose-i18n.js
Original file line number Diff line number Diff line change
@@ -1,125 +1,166 @@
(function() {
'use strict';
var Document, debug, exports, mongoose, removePathFromSchema, translateObject, _;

_ = require('lodash');

debug = require('debug')('mongoose-i18n');

mongoose = require('mongoose');

Document = mongoose.Document;

exports = module.exports = function(schema, options) {
if (!_.isArray(options != null ? options.languages : void 0)) {
throw new TypeError('Must pass an array of languages.');
"use strict";
// Mongoose i18n Plugin
// ==============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
const plugin = function (schema, options) {
if (!Array.isArray(options?.languages)) {
throw new TypeError("Must pass an array of languages.");
}
schema.eachPath(function(path, config) {
var defaultPath, vPath;
if (config.options.i18n) {
removePathFromSchema(path, schema);
_.each(options.languages, function(lang) {
var obj;
obj = {};
obj[lang] = config.options;
if (config.options.required) {
if ((options.defaultLanguage != null) && lang !== options.defaultLanguage) {
delete obj[lang]['required'];
schema.eachPath((path, config) => {
// process if i18n: true
if (config.options.i18n) {
// remove from options
// delete config.options.i18n;
// no longer need this path in schema
// removePathFromSchema(path, schema);
schema.remove(path);
// add path to schema for each language
options.languages.forEach((lang) => {
// use same config for each language
const obj = { [lang]: { ...config.options } };
// delete obj[lang]["i18n"];
if (config.options.required) {
// if set, only require the default language
if (options.defaultLanguage && lang !== options.defaultLanguage) {
delete obj[lang]["required"];
}
}
// add the new path to the schema
schema.add(obj, `${path}.`);
});
if (options.defaultLanguage) {
const vPath = `${path}.i18n`;
const defaultPath = `${path}.${options.defaultLanguage}`;
schema
.virtual(vPath)
.get(function () {
return this.get(defaultPath);
})
.set(function (value) {
return this.set(defaultPath, value);
});
}
}
return schema.add(obj, "" + path + ".");
});
if (options.defaultLanguage != null) {
vPath = "" + path + ".i18n";
defaultPath = "" + path + "." + options.defaultLanguage;
schema.virtual(vPath).get(function() {
return this.get(defaultPath);
});
return schema.virtual(vPath).set(function(value) {
return this.set(defaultPath, value);
});
}
}
});
schema.methods.toObjectTranslated = function(options) {
var key, populated, ret, translation, _ref;
translation = void 0;
if (options != null) {
translation = options.translation;
delete options.translation;
if (Object.keys(options).length === 0) {
options = void 0;
schema.methods.toObjectTranslated = function (opts) {
const thisDoc = this;
let language = undefined;
if (opts) {
language = opts.language;
delete opts.language;
// The native Document.prototype.toObject doesn't like an empty object
// `{}` as the parameter
if (!Object.keys(opts).length) {
opts = undefined;
}
}
if (!language && options.defaultLanguage) {
language = options.defaultLanguage;
}
}
ret = Document.prototype.toObject.call(this, options);
if (translation != null) {
translateObject(ret, schema, translation);
_ref = this.$__.populated;
for (key in _ref) {
populated = _ref[key];
translateObject(ret[key], populated.options.model.schema, translation);
// const ret = Document.prototype.toObject.call(this, opts);
const ret = this.toObject(opts);
if (language) {
translateObject(ret, schema, language, options.defaultLanguage);
// translate every populated children objects too
// for key, populated of this.$__.populated
// populated.options.model?.schema? and translateObject(ret[key], populated.options.model.schema, language, options.defaultLanguage)
Object.entries(this.$__.populated || {}).forEach(([key, populated]) => {
if (populated.options.model?.schema) {
translateObject(ret[key], populated.options.model.schema, language || "", options.defaultLanguage);
}
});
}
}
return ret;
return ret;
};
return schema.methods.toJSONTranslated = function(options) {
var key, populated, ret, translation, _ref;
translation = void 0;
if (options != null) {
translation = options.translation;
delete options.translation;
if (Object.keys(options).length === 0) {
options = void 0;
schema.methods.toJSONTranslated = function (opts) {
let language = undefined;
if (opts) {
language = opts.language;
delete opts.language;
// The native Document.prototype.toJSON doesn't like an empty object
// `{}` as the parameter
if (!Object.keys(opts).length) {
opts = undefined;
}
}
}
ret = Document.prototype.toJSON.call(this, options);
if (translation != null) {
translateObject(ret, schema, translation);
_ref = this.$__.populated;
for (key in _ref) {
populated = _ref[key];
translateObject(ret[key], populated.options.model.schema, translation);
if (!language && options.defaultLanguage) {
language = options.defaultLanguage;
}
}
return ret;
};
};

translateObject = function(object, schema, translation) {
var lastTranslatedField;
lastTranslatedField = '';
return schema.eachPath(function(path, config) {
var child, index, keys, tree, _i, _len, _ref, _ref1, _results;
if (config.options.i18n && !new RegExp("^" + lastTranslatedField + "\\.[^\.]+?$").test(path)) {
lastTranslatedField = path.replace(/^(.*?)\.([^\.]+?)$/, '$1');
keys = path.split('.');
tree = object;
while (keys.length > 2) {
tree = tree[keys.shift()];
// const ret = Document.prototype.toJSON.call(this, opts);
const ret = this.toJSON(opts);
if (language) {
translateObject(ret, schema, language, options.defaultLanguage);
// translate every populated children objects too
// for key, populated of this.$__.populated
// translateObject(ret[key], populated.options.model.schema, language, options.defaultLanguage)
Object.entries(this.$__.populated || {}).forEach(([key, populated]) => {
translateObject(ret[key], populated.options.model.schema, language || "", options.defaultLanguage);
});
}
if (_.isArray(tree)) {
_results = [];
for (index = _i = 0, _len = tree.length; _i < _len; index = ++_i) {
child = tree[index];
_results.push(tree[index][keys[0]] = (_ref = tree[index][keys[0]]) != null ? _ref[translation] : void 0);
}
return _results;
} else {
return tree[keys[0]] = (_ref1 = tree[keys[0]]) != null ? _ref1[translation] : void 0;
return ret;
};
};
// Translate an object's fields that has `i18n` enabled
//
// @param {Object} object the object returned from `Document.toObject()` or
// `Document.toJSON()`
// @param {Mongoose.Schema} schema the schema of `object`
// @param {String} language
// @param {String} defaultLanguage
const translateObject = (obj, schema, language, defaultLanguage) => {
let lastTranslatedField = "";
schema.eachPath((path, config) => {
if (config.options.i18n &&
!new RegExp(`^${lastTranslatedField}\\.[^.]+?$`).test(path)) {
lastTranslatedField = path.replace(/^(.*?)\.([^\.]+?)$/, "$1");
let tree = obj;
const keys = path.split(".");
let key;
while (keys.length > 2 && (key = keys.shift()) !== undefined) {
tree = tree[key];
}
if (Array.isArray(tree)) {
tree.forEach((child, index) => {
translateScalar(tree[index], keys[0], language, defaultLanguage);
});
}
else {
translateScalar(tree, keys[0], language, defaultLanguage);
}
}
}
});
};

removePathFromSchema = function(path, schema) {
var keys, tree;
keys = path.split('.');
tree = schema.tree;
while (keys.length > 1) {
tree = tree[keys.shift()];
};
const translateScalar = (tree, key, language, defaultLanguage) => {
const item = tree[key];
if (item === undefined) {
tree[key] = "";
}
else if (item[language]) {
tree[key] = item[language];
}
else if (defaultLanguage && item[defaultLanguage]) {
tree[key] = item[defaultLanguage];
}
else if (tree) {
tree[key] = "";
}
delete tree[keys.shift()];
return delete schema.paths[path];
};

}).call(this);
};
// Add remove method to Schema prototype
//
// @param {String} path path to be removed from schema
// const removePathFromSchema = (path: string, schema: Schema) => {
// schema.remove(path)
// const keys = path.split(".");
// let tree: any = schema.obj;
// let key: string | undefined;
// while (keys.length > 1 && (key = keys.shift()) !== undefined) {
// tree = tree[key];
// }
// key = keys.shift();
// if (key !== undefined) {
// delete tree[key];
// }
// delete schema.paths[path];
// };
module.exports = plugin;
exports.default = plugin;
25 changes: 14 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,27 @@
},
"main": "lib/mongoose-i18n.js",
"dependencies": {
"lodash": "^2.4.1",
"mongoose": ">=3.8 <3.9"
"mongoose": "^8.1.0"
},
"devDependencies": {
"chai": "^1.10.0",
"@tsconfig/node18": "^18.2.2",
"@types/chai": "^4.3.11",
"@types/chai-as-promised": "^7.1.8",
"@types/mocha": "^10.0.6",
"@types/node": "^20.11.5",
"@types/q": "^1.5.8",
"chai": "^4.2.0",
"chai-as-promised": "^4.1.1",
"coffee-script": "^1.8.0",
"debug": "^2.1.1",
"grunt": "^0.4.4",
"grunt-contrib-coffee": "^0.10.1",
"grunt-contrib-watch": "^0.6.1",
"istanbul": "duereg/istanbul",
"mocha": "^2.0.1",
"q": "^1.1.2"
"mocha": "^7.0.1",
"q": "^1.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"scripts": {
"test": "NODE_ENV=test node_modules/.bin/mocha --compilers coffee:coffee-script/register --reporter dot test/**/*.test.coffee",
"coverage": "NODE_ENV=test node_modules/.bin/istanbul cover node_modules/.bin/_mocha -- --compilers coffee:coffee-script/register --reporter dot test/**/*.test.coffee"
"test": "NODE_ENV=test mocha --loader=ts-node/esm --reporter dot test/**/*.test.ts",
"coverage": "NODE_ENV=test istanbul cover node_modules/.bin/_mocha -- --loader=ts-node/esm --reporter dot test/**/*.test.ts"
},
"keywords": [
"Mongoose",
Expand Down
Loading